Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions projects/ROCKNIX/devices/SM8750/audio-topology/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# SM8750 AudioReach topologies

The q6apm sound card gets its DSP graph from a binary topology, requested as
`qcom/sm8750/<card model>-tplg.bin`. The blobs themselves live in the
`extra-firmware` repo under `SM8750/qcom/sm8750/`, one per board:

| card model | board |
|----------------|------------------------|
| `SM8750-KONKR` | KONKR Pocket FIT Elite |

Only the sources are kept here. The KONKR blob is also built into the kernel via
`CONFIG_EXTRA_FIRMWARE`, see the SM8750 block in
`projects/ROCKNIX/packages/linux/package.mk`; built-in firmware is searched
before the rootfs, so that copy wins.

Changing a blob means a commit in `extra-firmware` and a `PKG_VERSION` bump in
`projects/ROCKNIX/packages/linux-firmware/extra-firmware/package.mk`.

## Regenerating

Sources are the `.m4` files here, built with Linaro's macro library:

```sh
git clone https://git.codelinaro.org/linaro/qcomlt/audioreach-topology.git
cd audioreach-topology
m4 -I . <this dir>/SM8750-KONKR.m4 > SM8750-KONKR.conf
alsatplg -c SM8750-KONKR.conf -o SM8750-KONKR-tplg.bin
```

`alsatplg -d <blob> -o <conf>` decodes a blob back to text, so a regenerated
blob can be diffed against the shipped one before it goes in.

Two things to know when diffing against pre-2026-08 blobs: the current macros
emit tokens 204/205 (`MODULE_IN_PORTS`/`MODULE_OUT_PORTS`, deprecated and
ignored by the kernel), and the decoder drops per-element `index` values, so a
decode/recompile round trip regroups the blocks.

## Graph

| stream | PCM | backend | UCM device |
|--------------|---------|---------------------|-------------|
| MultiMedia1 | hw:0,0 | SECONDARY_MI2S_RX | Speaker |
| MultiMedia2 | hw:0,1 | RX_CODEC_DMA_RX_0 | Headphones |
| MultiMedia3 | hw:0,2 | TX_CODEC_DMA_TX_3 | (capture) |
| MultiMedia4 | hw:0,3 | DISPLAY_PORT_RX_0 | HDMI |

The board DTS needs a matching `dp-dai-link` (`q6apmbedai DISPLAY_PORT_RX_0` ->
`&mdss_dp0`) for the DP backend to bind, and that link is what makes the
machine driver register the `DP0 Jack` control the UCM HDMI device keys off.
78 changes: 78 additions & 0 deletions projects/ROCKNIX/devices/SM8750/audio-topology/SM8750-KONKR.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# SPDX-License-Identifier: BSD-3-Clause
# AudioReach topology source for the KONKR Pocket FIT Elite (SM8750).
#
# Build with the macro library from
# https://git.codelinaro.org/linaro/qcomlt/audioreach-topology:
#
# m4 -I <audioreach-topology> SM8750-KONKR.m4 > SM8750-KONKR.conf
# alsatplg -c SM8750-KONKR.conf -o SM8750-KONKR-tplg.bin
#
# Streams: MultiMedia1 = speakers, MultiMedia2 = headphones,
# MultiMedia3 = headset mic, MultiMedia4 = DisplayPort. PCM device numbers follow
# the stream index, so DisplayPort is hw:0,3 and that is what the UCM HDMI device
# opens.
#
# DisplayPort gets a stream of its own so the three outputs are three sinks in
# one card profile. Sharing the speaker PCM instead makes pipewire model them as
# alternative card profiles, and switching profiles tears down and rebuilds the
# sinks on signals that do not move in step with the cable, which strands the
# card on an unusable profile. Needs the DP driver to tolerate a prepare while
# the display is off (patch 0618).
#
# The Odin3 topology is the same with the I2S data line on SD0.
include(`audioreach/audioreach.m4')
include(`audioreach/stream-subgraph.m4')
include(`audioreach/device-subgraph.m4')
include(`util/route.m4')
include(`util/mixer.m4')
include(`audioreach/tokens.m4')
dnl Playback MultiMedia1
STREAM_SG_PCM_ADD(audioreach/subgraph-stream-vol-playback.m4, FRONTEND_DAI_MULTIMEDIA1,
`S16_LE', 48000, 48000, 2, 2,
0x00004001, 0x00004001, 0x00006001, `110000')
dnl Playback MultiMedia2
STREAM_SG_PCM_ADD(audioreach/subgraph-stream-vol-playback.m4, FRONTEND_DAI_MULTIMEDIA2,
`S16_LE', 48000, 48000, 2, 2,
0x00004002, 0x00004002, 0x00006010, `110000')
dnl Capture MultiMedia3
STREAM_SG_PCM_ADD(audioreach/subgraph-stream-capture.m4, FRONTEND_DAI_MULTIMEDIA3,
`S16_LE', 48000, 48000, 1, 2,
0x00004003, 0x00004003, 0x00006020, `110000')
dnl Playback MultiMedia4, feeds the DisplayPort backend only
STREAM_SG_PCM_ADD(audioreach/subgraph-stream-vol-playback.m4, FRONTEND_DAI_MULTIMEDIA4,
`S16_LE', 48000, 48000, 2, 2,
0x00004004, 0x00004004, 0x00006030, `110000')
dnl Speaker amps: 2x AW88261 on SECONDARY MI2S. The populated amps are the
dnl stock quad-design's ch2/ch3, so the DSP has to drive data1 = SD1.
DEVICE_SG_ADD(audioreach/subgraph-device-i2s-playback.m4, `Secondary', SECONDARY_MI2S_RX,
`S16_LE', 48000, 48000, 2, 2,
LPAIF_INTF_TYPE_LPAIF, I2S_INTF_TYPE_SECONDARY, SD_LINE_IDX_I2S_SD1, DATA_FORMAT_FIXED_POINT,
0x00004005, 0x00004005, 0x00006050, `SECONDARY_MI2S_RX')
dnl WCD939x playback (headphones)
DEVICE_SG_ADD(audioreach/subgraph-device-codec-dma-playback.m4, `RX_CODEC_DMA_RX_0', RX_CODEC_DMA_RX_0,
`S16_LE', 48000, 48000, 2, 2,
LPAIF_INTF_TYPE_RXTX, CODEC_INTF_IDX_RX0, 0, DATA_FORMAT_FIXED_POINT,
0x00004007, 0x00004007, 0x00006070)
dnl WCD939x capture (headset mic)
DEVICE_SG_ADD(audioreach/subgraph-device-codec-dma-capture.m4, `TX_CODEC_DMA_TX_3', TX_CODEC_DMA_TX_3,
`S16_LE', 48000, 48000, 1, 2,
LPAIF_INTF_TYPE_RXTX, CODEC_INTF_IDX_TX3, 0, DATA_FORMAT_FIXED_POINT,
0x00004009, 0x00004009, 0x00006090)
dnl DisplayPort over USB-C, fed from MultiMedia4. The DP sink module needs no
dnl interface index, the stream picks its DPTX from the dai id
dnl (DISPLAY_PORT_RX_0 = dai 104).
DEVICE_SG_ADD(audioreach/subgraph-device-display-port-playback.m4, `DISPLAY_PORT_RX_0', DISPLAY_PORT_RX_0,
`S16_LE', 48000, 48000, 2, 2,
0, 0, 0, DATA_FORMAT_FIXED_POINT,
0x00004008, 0x00004008, 0x00006080, `DISPLAY_PORT_RX_0')

STREAM_DEVICE_PLAYBACK_MIXER(SECONDARY_MI2S_RX, ``SECONDARY_MI2S_RX'', ``MultiMedia1'', ``MultiMedia2'')
STREAM_DEVICE_PLAYBACK_MIXER(RX_CODEC_DMA_RX_0, ``RX_CODEC_DMA_RX_0'', ``MultiMedia1'', ``MultiMedia2'')
STREAM_DEVICE_PLAYBACK_MIXER(DISPLAY_PORT_RX_0, ``DISPLAY_PORT_RX_0'', ``MultiMedia4'')

STREAM_DEVICE_PLAYBACK_ROUTE(SECONDARY_MI2S_RX, ``SECONDARY_MI2S_RX Audio Mixer'', ``MultiMedia1, stream0.logger1'', ``MultiMedia2, stream1.logger1'')
STREAM_DEVICE_PLAYBACK_ROUTE(RX_CODEC_DMA_RX_0, ``RX_CODEC_DMA_RX_0 Audio Mixer'', ``MultiMedia1, stream0.logger1'', ``MultiMedia2, stream1.logger1'')
STREAM_DEVICE_PLAYBACK_ROUTE(DISPLAY_PORT_RX_0, ``DISPLAY_PORT_RX_0 Audio Mixer'', ``MultiMedia4, stream3.logger1'')

STREAM_DEVICE_CAPTURE_MIXER(FRONTEND_DAI_MULTIMEDIA3, ``TX_CODEC_DMA_TX_3'')
STREAM_DEVICE_CAPTURE_ROUTE(FRONTEND_DAI_MULTIMEDIA3, ``MultiMedia3 Mixer'', ``TX_CODEC_DMA_TX_3, device120.logger1'')

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,19 @@
cpu { sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>; };
platform { sound-dai = <&q6apm>; };
};

/* HDMI audio out over USB-C DP alt mode. The codec side is the
* hdmi-codec the DP bridge registers as a child of &mdss_dp0, and
* this link is also what makes the machine driver create the
* "DP0 Jack" control that UCM keys the HDMI device off.
* Needs the DISPLAY_PORT_RX_0 graph in the topology blob, see
* devices/SM8750/audio-topology/. */
dp-dai-link {
link-name = "DisplayPort Playback";
codec { sound-dai = <&mdss_dp0>; };
cpu { sound-dai = <&q6apmbedai DISPLAY_PORT_RX_0>; };
platform { sound-dai = <&q6apm>; };
};
};

wcn7860-pmu {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
Subject: [PATCH] ASoC: qdsp6: q6apm-lpass: start RX port at prepare
Subject: [PATCH] ASoC: qdsp6: q6apm-lpass: start MI2S RX port at prepare

Port start moved from .prepare to .trigger, so the MI2S bit-clock now
starts after the codec DAPM widgets power up. The Odin3 aw88166 amps
check their PLL against BCLK at DAPM PRE_PMU, before the trigger, and
fail PLL lock with no clock, leaving the speakers silent.
starts after the codec DAPM widgets power up. The Odin3 aw88166 and the
KONKR aw88261 amps check their PLL against BCLK at DAPM PRE_PMU, before
the trigger, and fail PLL lock with no clock, leaving the speakers silent.

Start the playback port at prepare again so BCLK is up before PRE_PMU.
The trigger's !is_port_started guard no-ops; capture is unchanged.

MI2S ports only. Starting the DisplayPort endpoint this early makes the
DSP reject APM_CMD_GRAPH_START: the DP controller only enables its audio
path from the codec dai's prepare, which runs after this one.
---
--- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c
+++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c
@@ -224,6 +224,20 @@
@@ -224,6 +224,36 @@
dev_err(dai->dev, "Failed to prepare Graph %d\n", rc);
goto err;
}
+
+ /*
+ * aw88166 checks its PLL against BCLK at DAPM PRE_PMU, before the
+ * PCM trigger; start the port here so BCLK is up in time. The
+ * trigger's !is_port_started guard then no-ops. Playback only.
+ * aw88166/aw88261 check their PLL against BCLK at DAPM PRE_PMU, before
+ * the PCM trigger, so the MI2S port has to be running by then. Start it
+ * here and let the trigger's !is_port_started guard no-op.
+ *
+ * MI2S only. The DisplayPort endpoint must not start this early: the DP
+ * controller's audio path is enabled from the codec dai's prepare, which
+ * runs after this one, and the DSP rejects APM_CMD_GRAPH_START on a DP
+ * port whose audio path is still off. Capture is unchanged.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ rc = q6apm_graph_start(dai_data->graph[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "Failed to start APM port %d\n", dai->id);
+ goto err;
+ switch (dai->id) {
+ case PRIMARY_MI2S_RX:
+ case SECONDARY_MI2S_RX:
+ case TERTIARY_MI2S_RX:
+ case QUATERNARY_MI2S_RX:
+ case QUINARY_MI2S_RX:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ rc = q6apm_graph_start(dai_data->graph[dai->id]);
+ if (rc < 0) {
+ dev_err(dai->dev, "Failed to start APM port %d\n", dai->id);
+ goto err;
+ }
+ dai_data->is_port_started[dai->id] = true;
+ }
+ dai_data->is_port_started[dai->id] = true;
+ break;
+ default:
+ break;
+ }
+
return 0;
err:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Subject: [PATCH] drm/msm/dp: don't fail audio prepare while the display is off

msm_dp_audio_prepare() refuses with -EINVAL when the DP display is not
powered on, to avoid unclocked access to the DP block. Skipping the setup
is right, returning an error is not: userspace prepares the DisplayPort
PCM to find out what it supports long before anything is plugged in
(pipewire does it for every UCM device while it builds the card), and one
failure there makes it discard the whole card profile, so the machine
loses its speaker and headphone sinks too.

Skip the programming as before and return success. Nothing is touched and
no link is up, so the stream goes nowhere until the display is enabled.

--- a/drivers/gpu/drm/msm/dp/dp_audio.c
+++ b/drivers/gpu/drm/msm/dp/dp_audio.c
@@ -281,13 +281,18 @@
* before OR even when DP is not connected . This can cause
* unclocked access as the audio subsystem relies on the DP
* driver to maintain the correct state of clocks. To protect
- * such cases check for connection status and bail out if not
- * connected.
+ * such cases check for connection status and skip the setup if
+ * not connected.
+ *
+ * Don't report an error for it. Userspace prepares this PCM to
+ * find out what it can do long before anything is plugged in
+ * (pipewire does it for every UCM device while it builds the
+ * card), and failing that makes it drop the whole card profile,
+ * taking the speakers down with it. Nothing is programmed here
+ * and no link is up, so the stream simply goes nowhere.
*/
- if (!msm_dp_display->power_on) {
- rc = -EINVAL;
+ if (!msm_dp_display->power_on)
goto end;
- }

audio = msm_dp_audio_get_data(msm_dp_display);
if (IS_ERR(audio)) {
Loading