Files
c630/docs/firmware.md
rob thijssen 05e54b0b62
All checks were successful
build image / build (push) Successful in 39m26s
Stop the modem crash loop, and record what it is not
Fedora runs rmtfs with -r, which starts the modem remoteproc at boot. Since
the modem never gets past RF init, every C630 sits in a fatal crash and
recovery cycle every ~3.4 seconds from boot onwards — and is already a dozen
crashes deep by the time anyone logs in, which quietly confuses any manual
experiment. Drop the -r so the modem stays offline until asked for.

Also record today's eliminations, each of which closes an avenue: both
published _nm firmware builds are rejected by the boot ROM, not just the
newest; the PEP tables in this machine's own ACPI show Windows gives the
modem seven clocks, one 752 mV rail vote, cx/mx and IPA bandwidth, and
nothing whatsoever for RF — so the host does not power the RF front end on
either OS, and the one clock mainline lacks changes nothing when forced on;
and kernel bracketing is stuck, because 5.10 will not boot here at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011XgGF5wfxLDAybVnNz6eNQ
2026-07-28 13:33:39 +03:00

28 KiB

Firmware

The C630's firmware splits into two groups, and the split is about licensing, not difficulty.

What ships in the image

Qualcomm permits redistribution of these, so Fedora packages them and the build installs them:

Package Files Enables
atheros-firmware ath10k/WCN3990/hw1.0/{firmware-5.bin,board-2.bin,wlanmdsp.mbn} WiFi and Bluetooth
qcom-firmware qcom/a630_gmu.bin, qcom/a630_sqe.fw The generic half of the Adreno 630 — necessary but not sufficient

Networking therefore works on a freshly written image. Graphics does not: see below.

What you have to supply

These are signed against per-model keys and Fedora does not ship them, so a stock install has none of them. Nothing about them is unredistributable in principle — the equivalent files for the ThinkPad X13s are in linux-firmware — it is that nobody has obtained permission for this model. See If you have no backup.

The list is not a guess: it is every firmware-name property in the mainline device tree, arch/arm64/boot/dts/qcom/sdm850-lenovo-yoga-c630.dts:

File Device tree node Lost without it
qcdxkmsuc850.mbn gpu_zap_shader Accelerated graphics
qcadsp850.mbn adsp_pas Audio — speakers, microphone
qcslpi850.mbn slpi_pas Sensor hub — lid switch, accelerometer, auto-rotate
qccdsp850.mbn cdsp_pas Compute DSP offload
qcvss850.mbn venus Hardware video decode
qcdsp1v2850.mbn mss_pil Modem, stage 1
qcdsp2850.mbn mss_pil Modem, stage 2
ipa_fws.elf ipa IPA datapath, needed by the modem

They belong under /lib/firmware/updates/qcom/sdm850/LENOVO/81JL/.

The GPU one is worth calling out, because it is easy to assume otherwise: Fedora does ship an sdm845/a630_zap.mbn, but that is the generic Snapdragon 845 zap shader and not what this device asks for. The C630's device tree names qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn specifically, so until you supply it you get an unaccelerated display.

Each file is independent, so a partial recovery is still worth having — qcdxkmsuc850.mbn on its own buys you working graphics.

Confirmed on the hardware

This is not inferred from the device tree. On a machine with the generic Adreno firmware present and the model-signed blob absent, the GPU gets two files in and then stops:

[drm:adreno_request_fw]   loaded qcom/a630_sqe.fw from new location
[drm:adreno_request_fw]   loaded qcom/a630_gmu.bin from new location
[drm:zap_shader_load_mdt] *ERROR* Unable to load qcom/sdm850/LENOVO/81JL/qcdxkmsuc850.mbn
[drm:adreno_load_gpu]     *ERROR* gpu hw init failed: -2

So the generic pieces are necessary and not sufficient, and the zap shader is a hard requirement rather than an optimisation. Two details follow from that trace:

The driver never falls back to sdm845/a630_zap.mbn, even though the file is installed — the device tree's firmware-name pins the model-specific path. Nor would falling back help: the zap shader is signed against the device's own secure-boot chain, and the Snapdragon 845 development board's copy will not validate here.

The display keeps working throughout. msm_dpu binds the panel and the console lands on msmdrmfb regardless, so a failed GPU costs you acceleration, not a picture. A desktop still runs on llvmpipe — though /dev/dri/renderD128 is present even when hw init has failed, so mesa may try freedreno, fail, and take the display manager down with it. LIBGL_ALWAYS_SOFTWARE=1 in /etc/environment avoids that.

These have to be in the initramfs

msm_dpu probes about six seconds in, while the initramfs is still the root filesystem, and does not retry. Firmware that only exists on the real root is therefore invisible to it:

msm_dpu ae01000.display-controller: Direct firmware load for qcom/a630_sqe.fw failed with error -2

overlay/etc/dracut.conf.d/10-c630.conf puts the Adreno files in the initramfs for this reason, and c630-firmware adds qcdxkmsuc850.mbn once it exists.

Only the GPU needs this. The ADSP, CDSP, SLPI, venus and modem all probe around forty seconds in, well after the real root is mounted, and load from it perfectly happily. That distinction is worth keeping: qcdsp2850.mbn alone is 60 MB, so sweeping the whole firmware directory into the initramfs would inflate it roughly twentyfold for no benefit.

Finding them on a backup

If Windows is long gone from the machine but you kept a copy of the files, one search catches all eight, since seven share a suffix:

find /mnt/backup -type f \
    \( -iname 'qc*850.mbn' -o -iname 'ipa_fws.elf' -o -iname 'bdwlan*.bin' \) \
    -printf '%10s  %p\n'

They originally lived in C:\Windows\System32\DriverStore\FileRepository\, in directories named qcdx850.inf_arm64_* and qcadsp*.inf_arm64_*. If the loose files turn up nothing, look for those directories wholesale:

find /mnt/backup -type d -iname 'qc*.inf_arm64_*'

Sizes range from 14 KB (qcdxkmsuc850.mbn) to 60 MB (qcdsp2850.mbn). Despite the .mbn extension they are ELF images, and file should say so:

qcdxkmsuc850.mbn: ELF 32-bit LSB executable, QUALCOMM DSP6, version 1 (SYSV)

Anything reporting as text or HTML is a download that went wrong.

Lenovo does not appear to publish a driver package for the 81JL containing these. If the backups turn up nothing, see If you have no backup — the files are not tied to your individual machine, so yours is not the only possible source.

If you have no backup

Losing the Windows partition is not the end of it, because these blobs are signed per model, not per machine. A copy from any other Yoga C630 works on yours.

The evidence is in linux-firmware itself, which already ships the exact analogous set for the ThinkPad X13s — one copy serving every unit of that model:

qcom/sc8280xp/LENOVO/21BX/qcdxkmsuc8280.mbn     zap shader
qcom/sc8280xp/LENOVO/21BX/qcadsp8280.mbn        audio
qcom/sc8280xp/LENOVO/21BX/qccdsp8280.mbn        compute DSP
qcom/sc8280xp/LENOVO/21BX/qcslpi8280.mbn        sensors
qcom/sc8280xp/LENOVO/21BX/qcvss8280.mbn         video

A one-to-one match with the C630's list, differing only in the model. So these files are redistributable once Lenovo and Qualcomm permit it — it is a licensing question, not a technical one.

qcom/sdm850/LENOVO/81JL/ has simply never been contributed. Fedora ships no sdm850 model firmware at all, so it will not turn up in a linux-firmware update. Four sources are known:

Two projects publish C630 firmware. They overlap but neither is a superset of the other, and both are worth knowing about.

WOA-Projecthttps://github.com/WOA-Project/Qualcomm-Reference-Drivers

Windows-on-ARM driver packages by vendor and model, under Lenovo/YogaC630/. Nine versions are published, 200.0.1.0 through 200.0.19.0; the cabinets differ between them. This is the only source here for the GPU, DSP, sensor and venus blobs:

Cabinet Contains
qcdx850.cab qcdxkmsuc850.mbn (zap shader), qcvss850.mbn (venus)
qcipa850.cab ipa_fws.elf
qcsubsys850.cab qcadsp850.mbn, qccdsp850.mbn, qcslpi850.mbn, qcdsp1v2850.mbn, qcdsp2850.mbn, WLANMDSP.MBN
qcwlan850.cab bdwlan.bin, bdwlan.b3* — ath10k board data
mcfg_subsys_ext850.cab MCFG/* modem configuration
qcsubsys_extension850.cab ADSP/CDSP runtime modules, fastrpc_shell_*

cabextract or 7z x opens them. Ignore the _nm and _CLS variants; the device tree asks for the plain names. Fetch via the GitHub API's download_url — the raw.githubusercontent.com paths return 404.

aarch64-laptopshttps://github.com/aarch64-laptops/build

Files checked directly into misc/lenovo-yoga-c630/, no extraction needed. A narrower set, but with things WOA-Project does not carry:

Path Contains
wifi/ qcdsp1v2850.mbn, qcdsp2850.mbn, wlanmdsp.mbn, bdwlan.*
wifi/create-board-2.bin/ a prebuilt board-2.bin, and the script that makes it
gpu/firmware/ a630_gmu.bin
acpi/, laptop-lenovo-yoga-c630.dts the machine's ACPI tables and a device tree

It has none of qcdxkmsuc850.mbn, qcadsp850.mbn, qccdsp850.mbn, qcslpi850.mbn, qcvss850.mbn or ipa_fws.elf. Note wifi/firmware-5.bin is an HTML error page in the checkout, not firmware — Fedora's atheros-firmware ships a real one.

Which to use. For the GPU, DSPs, sensors and venus, WOA-Project is the only option of the two. For the modem and WLAN trio both publish files, and they are different builds — same sizes for the modem pair, different checksums. On this machine both produce identical results, so nothing here distinguishes them; see What the firmware does not fix.

Lenovo's Digital Download Recovery Service. Lenovo builds a Windows recovery image against a machine's serial number and lets the owner download it. That image contains Windows/System32/DriverStore/FileRepository, and therefore the whole set as shipped for your specific machine. Slower than either repository, and untested here.

Another C630 owner. Any 81JL's copy is as good as your own.

Whichever route, drop the result into firmware/local/ and rebuild — see Baking firmware into the image.

What the firmware does not fix

Installing all of it still leaves two things not working out of the box, for reasons that are not about firmware. Audio turns out to be a missing Fedora kernel module and is fixable today; onboard WiFi remains genuinely broken. Recorded here so nobody repeats the search.

Onboard WiFi: everything is in place except a working modem

The name gives it away: wlanmdsp is the WLAN Modem DSP. On SDM845 the WiFi firmware runs on the modem, so ath10k_snoc binds to 18800000.wifi and then sits silent. It logs nothing and fails nothing — it has registered a QMI client and is waiting for service 69, wlfw, which only appears once the modem is up. qrtr-lookup shows what is and is not there.

Everything on the WiFi side checks out:

  • ath10k_snoc binds successfully and is not in deferred probe
  • the device tree carries qcom,calibration-variant = "Lenovo_C630"
  • Fedora's board-2.bin contains a matching variant=Lenovo_C630 entry
  • modemst1 and modemst2 hold ~2 MB of real NV data — not wiped

The userspace services are required, and mostly missing by default

aarch64-laptops' WiFi README lists four services. On a current Fedora two of them are now in the kernel, and the other two are packaged but inactive:

Service Status here
pd-mapper in-kernel as qcom_pd_mapper — nothing to install
qrtr-ns in-kernel — the qrtr package supplies only qrtr-lookup/qrtr-cfg
rmtfs packaged, disabled by default — serves the modem's NV partitions
tqftpserv packaged, not installed — serves files to the modem over TFTP
sudo dnf install rmtfs tqftpserv qrtr
sudo systemctl enable --now rmtfs tqftpserv

Enabling these took qrtr-lookup from 19 services to 25. A working setup is said to show around 40.

The modem fetches wlanmdsp over TFTP

This is the step that is easy to miss, and the reason tqftpserv exists. The modem asks it for the WLAN firmware by path — but where tqftpserv serves from depends on the version, and the layout below (from aarch64-laptops) does not work with Fedora's current build; see The tqftpserv paths above are version-dependent. On current Fedora:

sudo ln -sf updates/qcom/sdm850/LENOVO/81JL/wlanmdsp.mbn /lib/firmware/wlanmdsp.mbn

The modem also wants a writable area, and says so in the tqftpserv journal:

[TQFTP] RRQ: /readwrite/ota_firewall/ruleset → unable to open (2), reject
[TQFTP] WRQ: /readwrite/server_check.txt

On current Fedora that lives in /tmp/tqftpserv (recreate after each boot, or add a tmpfiles.d entry):

sudo mkdir -p /tmp/tqftpserv/ota_firewall
sudo touch /tmp/tqftpserv/ota_firewall/ruleset
sudo chmod -R 0777 /tmp/tqftpserv

Watching journalctl -u tqftpserv while starting the modem is the best available feedback channel — it shows exactly which paths the modem wants.

Where it actually stops

The modem boots and then dies at RF front-end initialisation, roughly every 42 seconds until stopped:

qcom-q6v5-mss 4080000.remoteproc: MBA booted without debug policy, loading mpss
qcom-q6v5-mss 4080000.remoteproc: fatal error received:
    RFLM@rflm_qlnk.cpp:1087 [3,3] RF stuck in QLINK start state: 0x0 after maximum
remoteproc remoteproc0: crash detected ... recovering

It never gets far enough to request wlanmdsp, so WLAN never starts.

Three firmware pairings, three distinct failures — worth recording, because the differences are informative:

MBA (stage 1) mpss (stage 2) Result
full full boots, dies at RF stuck in QLINK start state
_nm _nm PBL returned unexpected status — rejected by the boot ROM
full _nm MPSS header authentication failed: -3

The _nm ("no modem") variants are the obvious idea — Fedora ships the equivalent modem_nm.mbn for generic sdm845, and skipping cellular RF is exactly what is wanted here. They cannot be made to load. WOA-Project's nine packages contain two distinct _nm builds (an older one in 200.0.1.0 through 200.0.10.0, a newer one from 200.0.11.0 on), and both are rejected by the boot ROM in the same way:

qcom-q6v5-mss 4080000.remoteproc: PBL returned unexpected status -284557301
remoteproc remoteproc3: can't start rproc 4080000.remoteproc: -22

So this is not a matter of finding the right vintage: the PBL will not accept either _nm MBA on this hardware, and without the _nm MBA the _nm mpss fails authentication. The avenue is closed.

Firmware has been eliminated as the variable

The nine driver versions under Lenovo/YogaC630/ contain five distinct modem-firmware builds — 200.0.1.0 through 200.0.4.0 share one 2018-vintage pair (a different mpss size from all later builds, and the closest match to this machine's 2018-10 manufacture date), 200.0.10.0, 200.0.11.0, 200.0.14.0/200.0.16.0, and 200.0.19.0 are the others, and aarch64-laptops' copies differ again by checksum. Every one of them was booted on this machine, with rmtfs and tqftpserv running. Every one produces the identical QLINK failure, crashing about 0.4 s after mpss comes up.

So neither the firmware nor its vintage is the variable. Nor is the kernel version, at least not recently: Fedora's 6.12.15 (kernel-core from koji, booted with the same device tree) produces the identical crash. The aarch64-laptops setup that ticked WiFi ran a 2019 5.x branch, so a very old regression is not excluded — but their issue #51 records modem crash/recovery lockups on that setup too, so "WiFi worked" and "the modem crashed" may well have coexisted, with wlanmdsp loading in the window before the first crash. An earlier note in this project's history has the crash loop at roughly 42 s; today it is ~3.4 s. If those cadences are both real, what changed — and whether a long first window is enough for wlfw to appear — is an open question.

QLINK is the SerDes link between the SoC and the SDR845 RF transceiver chip; rflm_qlnk.cpp is the modem firmware's manager for it. "Stuck in start state 0x0" means the transceiver never answered — the signature of a chip that is absent, unpowered, or misconfigured. This unit is an LTE SKU (SIM slot, factory-label IMEI, modem worked under Windows), so the chip is present and functional; under Windows, ACPI and the Qualcomm PEP handle subsystem power, and under device-tree boot whatever they did for the RF front end has no equivalent. That is a hypothesis, not a finding — the DSDT keeps its power management in opaque PEP tables, so it could not be confirmed from the tables alone.

rmtfs -v shows the modem's storage dialogue in the half-second before the crash: it opens modem_fs1, modem_fs2, modem_fsg and modem_fsc successfully, requests two partitions Linux's rmtfs has never heard of — modem_fsg_oem_1 and modem_fsg_oem_2, names that appear inside the mpss image itself, with no provider found in any WOA-Project cabinet — allocates its 2 MB buffer, reads a superblock's worth, and dies. On this machine modemst1/modemst2 carry valid IMGEFS1/IMGEFS2 filesystems, while fsg and fsc are entirely blank. Byte copies of all four live in firmware/local/nv-backup/ (gitignored), taken before any experimentation.

The tqftpserv paths above are version-dependent

The /lib/firmware/readonly/firmware/image/ and /lib/firmware/readwrite/ layout described earlier matches the old aarch64-laptops tqftpserv. Fedora's current build is different: /readwrite/* is served from /tmp/tqftpserv (tmpfs — anything created there is gone on reboot), and /readonly/firmware/image/<file> resolves through the normal firmware search path, i.e. /lib/firmware/<file>. So wlanmdsp.mbn needs to be reachable as /lib/firmware/wlanmdsp.mbn (a symlink to the copy under updates/ works), and the writable tree belongs under /tmp/tqftpserv/.

The full Windows-side TFTP tree can be reconstructed, and was: the driver INFs are TFTP-mapping manifests, and mcfg_subsys_ext850.inf maps 159 modem configuration files — carrier configs and, notably, mcfg_hw hardware RF configs for the SDX20 — into readonly/firmware/image/modem_pr/mcfg/.... Serving all of it changes nothing: the modem crashes before requesting any of it.

The host provides no RF power, on either operating system

The obvious remaining theory was that Windows powers the SDR845 through some rail the device tree does not describe. It does not. The C630's ACPI tables carry Qualcomm's PEP power-management data, and decoding every PEP entry for \_SB.AMSS gives the complete set of host resources Windows gives the modem:

  • seven clocks — gcc_boot_rom_ahb_clk, gcc_mss_cfg_ahb_clk, gcc_mss_q6_memnoc_axi_clk, gcc_mss_snoc_axi_clk, gcc_mss_mfab_axis_clk, gcc_prng_ahb_clk and gcc_mss_axis2_clk
  • one regulator vote, SMPS2_C at 752 mV — the MSS core rail
  • ARC/NPA votes on rail_cx and rail_mx at level 384
  • IPA bus bandwidth

No GPIOs, no footswitches, and — decisively — no votes on the CXO RF clock buffers (RFCLK1/2/3), even though the PMIC registry defines them and the audio tables vote on their own buffers. RF front-end power is managed by the modem firmware itself over RPMh/SPMI on both operating systems, so there is no missing host-side rail to add to the device tree.

Mainline's mss_pil node already carries six of the seven clocks. The seventh, gcc_mss_axis2_clk, is the only host-side difference, and it is not the cause: holding it enabled with an out-of-tree module before starting the modem produces the identical QLINK crash.

The modem's own coredump would settle it, but /sys/class/remoteproc/*/coredump yields a 124 MB image with none of the firmware's strings in it — production fuses, encrypted dump. The strings are plainly visible in qcdsp2850.mbn itself, so the dump is not simply empty.

Kernel bracketing has not been possible

The remaining hypothesis is age: something between the 2019 kernel aarch64-laptops used and today. Testing it needs an old kernel that boots here, and that has not been achieved. A 5.10.261 build (defconfig plus the SDM845 platform, UFS, PHY and ext4 support, all verified present in .config) reaches the EFI stub and dies before userspace — no journal, no console output, with earlycon=efifb and arm-smmu.disable_bypass=0 both tried. Fedora's 6.12.15 does boot, and crashes identically to 7.1.5, so the bracket is currently 6.12 ≤ broken ≤ 7.1 with nothing older testable.

Booting test kernels here is otherwise cheap and safe: add a BLS entry, set next_entry in the GRUB environment for a one-shot boot, and a power-cycle returns to the pinned default. Pin the default by entry id rather than set default=0 — adding an entry can otherwise silently make the test kernel the default.

The modem starts itself, and crashes forever

Fedora runs rmtfs -r, and -r makes rmtfs start the modem remoteproc at boot. Since the modem cannot get past RF init, the machine sits in a fatal crash and recovery cycle every ~3.4 seconds from boot until shutdown. It is easy to miss — the modem is already at crash #15 or so by the time anyone logs in, which also makes manual experiments confusing, and rapid start/stop cycling on top of it will wedge the remoteproc until a reboot. overlay/etc/systemd/system/rmtfs.service.d/50-no-remoteproc.conf drops the -r, so the modem stays offline until started deliberately.

Things that sound like they should help and do not:

  • Unplugging the USB WiFi dongle. Different bus, different driver. ath10k_snoc is waiting on the modem, not competing for a radio.
  • rmtfs alone. Necessary, not sufficient. Its "failed to update start state" warnings are its optional remoteproc-control helper and are harmless.
  • The _nm variants. See above.
  • Any published firmware version. All five distinct builds fail identically; see above.
  • The MCFG configuration tree. Served in full over TFTP; the modem never gets far enough to ask for it.
  • clk_ignore_unused pd_ignore_unused. Already on the command line (postmarketOS ships the same for this machine).
  • gcc_mss_axis2_clk. The one clock Windows enables that mainline does not; holding it on changes nothing. See above.
  • Both _nm firmware builds. Rejected by the boot ROM. See above.

Untried, in rough order of promise: reporting upstream — the error string appears nowhere public, and linux-arm-msm (Bjorn Andersson, Dmitry Baryshkov, Steev Klimaszewski all have C630 history) is now clearly the best next step, since every locally testable hypothesis above has been eliminated; Lenovo's Digital Download Recovery Service image for this serial, which would show the exact original driver set and whatever provides modem_fsg_oem_* under Windows; building board-2.bin from the C630's own bdwlan.* files (only relevant once the modem stays up); and the 5.x aarch64-laptops kernel itself, to test whether its crash cadence leaves a usable window.

Audio: Fedora does not build the speaker-amp driver

The ADSP runs, the audio services register, and the codec answers:

wcd934x-slim 217:250:1:0: WCD934x chip id major 0x108, minor 0x1

So SLIMbus, the ADSP and the hardware are all fine. The trail of suspicion used to end at this SoundWire message:

qcom-soundwire wcd934x-soundwire.12.auto: din-ports (2) mismatch with controller (6)
platform sound: deferred probe pending: msm-snd-sdm845: SLIM Playback: codec dai not found

The first line is a red herring. Since kernel 6.18 (commit 9e53a66a2f2f, "soundwire: qcom: deprecate qcom,din/out-ports") it is a warning: the device-tree value overrides the register value afterwards and the probe continues.

The real blocker is the second line. The device tree's SLIM Playback link names four codec DAIs: the WCD9340, the SoundWire master inside it, and the two WSA881x speaker amplifiers hanging off that master. Fedora's aarch64 kernel has never set CONFIG_SND_SOC_WSA881X — while the newer siblings WSA883X and WSA884X, which the ThinkPad X13s needs, are both =m. With no amplifier driver the two SoundWire slaves never bind, the machine driver cannot find its DAIs, and the card sits in deferred probe forever.

Building the one missing module proves it. sound/soc/codecs/wsa881x.c is a single self-contained file; compiled against the running kernel (kernel-devel, gcc, make, then make -C /lib/modules/$(uname -r)/build M=$PWD modules with obj-m += snd-soc-wsa881x.o and snd-soc-wsa881x-objs := wsa881x.o), installed under /lib/modules/$(uname -r)/extra/ and picked up by depmod -a, it autoloads on the SoundWire modalias at boot and the Lenovo-YOGA-C630-13Q50 card registers, headphone-jack detection included. The UCM profile has been in alsa-ucm-conf since 2020; alsaucm itself is in alsa-ucm-utils, so on the minimal image:

alsaucm -c Lenovo-YOGA-C630-13Q50 set _verb HiFi set _enadev Speaker
speaker-test -D plughw:0,0 -c2 -t sine -f 440 -l 1

The overflow error on RX port and register-read -16 messages logged when a stream stops are noise, seen on working sdm845 devices too.

PipeWire needs one more thing. The ADSP front-end advertises S24_LE (24-bit samples in 32-bit containers) and then garbles it: playback runs without any error, consumes data at the right rate, and powers every DAPM widget — a byte-for-byte identical /sys/kernel/debug/asoc widget dump against a working stream — while producing ~-48 dB of near-silence, consistent with the samples being bit-shifted inside the container. PipeWire prefers the widest format a device offers, so every PipeWire client (mpv, GNOME, pw-play) is inaudible while aplay and speaker-test, which default to S16_LE, work perfectly. This cost most of a day: the failure is per-format, invisible in every log, and easy to misattribute to routing, suspend/resume or per-stream volumes, all of which look guilty and are not. overlay/etc/wireplumber/wireplumber.conf.d/51-c630-alsa.conf pins the built-in card to S16_LE, which fixes every client. The underlying q6asm format bug is worth a report to alsa-devel.

Unrelated to the hardware, two pieces of routing politics are worth knowing on a machine like this: PipeWire's RAOP discovery will happily make a neighbour's AirPlay speaker the default sink, and a newly connected Bluetooth device also takes over the default. If sound "does not work", check wpctl status for where the default actually points before blaming the drivers.

The durable fix is one line of Fedora kernel config — flipping CONFIG_SND_SOC_WSA881X to =m for aarch64, an easy case given the in-tree device tree requires it and both newer WSA drivers are already enabled. Until that lands, the module has to be built out of tree per kernel.

One thing worth knowing if you go looking: slim_qcom_ngd_ctrl can probe before the ADSP is ready and log QMI wait timeout. Reloading the module afterwards gets the codec detected, which is how the chip ID above was obtained. A series of NGD probe-ordering fixes (Bjorn Andersson, mid-2026, Cc: stable) targets exactly this class of race and should reach Fedora with the 7.2 rebase.

Extracting them

The image ships Fedora's qcom-firmware-extract. Run it once:

sudo c630-firmware
sudo reboot

That wraps qcom-firmware-extract and regenerates the initramfs afterwards, which matters — the ADSP firmware has to be available before the root filesystem is mounted, and overlay/etc/dracut.conf.d/10-c630.conf only takes effect on a rebuild.

If it cannot find Windows, mount it yourself and point the tool at it:

lsblk -o NAME,SIZE,FSTYPE,LABEL       # find the NTFS partition
sudo mkdir -p /mnt/windows
sudo mount /dev/sda4 /mnt/windows
sudo qcom-firmware-extract --windows-dir /mnt/windows

qcom-firmware-extract was written for the Snapdragon 8cx and X Elite laptops. If it does not recognise the SDM850, copy the files by hand — they are in Windows/System32/DriverStore/FileRepository/ under a qcdx*.inf_arm64_* directory:

sudo find /mnt/windows/Windows/System32/DriverStore/FileRepository \
    -iname 'qc*850.mbn' -o -iname 'ipa_fws.elf' -o -iname 'qcdxkmsuc850.mbn'

sudo mkdir -p /lib/firmware/updates/qcom/sdm850/LENOVO/81JL
sudo cp <each file> /lib/firmware/updates/qcom/sdm850/LENOVO/81JL/
sudo dracut --force --regenerate-all

Check it took:

dmesg | grep -iE 'remoteproc|adsp|cdsp'

Baking firmware into the image

If you would rather not repeat the extraction on every reinstall, drop the files into firmware/local/ in this repo, mirroring the /usr/lib/firmware/updates/ layout:

firmware/local/qcom/sdm850/LENOVO/81JL/qcadsp850.mbn
firmware/local/qcom/sdm850/LENOVO/81JL/qccdsp850.mbn
...

build/stage2.sh picks them up automatically. They are gitignored, and should stay that way — this repository is public and the blobs are not yours to publish.