[PATCH] ASoC: qcom: q6asm: Correct the 24-bit capture format
From: Michael McAllister
Date: Sun Nov 01 2026 - 14:10:31 EST
Recording in S24_LE on my Moto G10 produces badly scaled samples that
wrap at full scale. The PCM V2 DSP interface places 24-bit samples in
bits 31:8 of each 32-bit word, but ALSA S24_LE expects bits 23:0.
Replace S24_LE with S32_LE for capture and constrain it to 24
significant bits. Keep the DSP configured for 24-bit samples. This
matches its native layout without converting samples in the kernel.
Playback is unchanged.
Translate S32_LE back to S24_LE in the APQ8096 and SC7180 backend
fixups so that routing and the physical audio ports keep their 24-bit
width. The other Q6ASM machine drivers already set the backend format.
Tested on the Moto G10 with direct ALSA and PipeWire capture at 48 kHz,
using 16-bit and 32-bit containers, reopening the stream, and recording
after suspend. ALSA reports 24 significant bits for S32_LE. S24_LE
capture through ALSA's plug conversion also works.
Signed-off-by: Michael McAllister <me@xxxxxxxxxxxxxxxxxxxxx>
---
Hi,
I found this while getting the microphone working on my Moto G10
(SM4250). This fixes the incorrect sample format for 24-bit capture;
I've tested it with ALSA and PipeWire, including after suspend.
The APQ8096 and SC7180 changes have only been build-tested, so testing
on those boards would be appreciated.
Thanks,
Michael
sound/soc/qcom/apq8096.c | 8 ++++++++
sound/soc/qcom/qdsp6/q6asm-dai.c | 12 ++++++++++--
sound/soc/qcom/sc7180.c | 8 ++++++++
3 files changed, 26 insertions(+), 2 deletions(-)
diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c
index cfd6438db..25f2f0c0e 100644
--- a/sound/soc/qcom/apq8096.c
+++ b/sound/soc/qcom/apq8096.c
@@ -6,6 +6,7 @@
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/pcm.h>
+#include <sound/pcm_params.h>
#include "common.h"
#define SLIM_MAX_TX_PORTS 16
@@ -19,10 +20,17 @@ static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
+ /* Keep the backend at 24 bits despite the 32-bit capture container. */
+ if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE) {
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ }
+
return 0;
}
diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c
index 4261c3048..4bed05f0d 100644
--- a/sound/soc/qcom/qdsp6/q6asm-dai.c
+++ b/sound/soc/qcom/qdsp6/q6asm-dai.c
@@ -90,7 +90,7 @@ static const struct snd_pcm_hardware q6asm_dai_hardware_capture = {
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = (SNDRV_PCM_FMTBIT_S16_LE |
- SNDRV_PCM_FMTBIT_S24_LE),
+ SNDRV_PCM_FMTBIT_S32_LE),
.rates = SNDRV_PCM_RATE_8000_48000,
.rate_min = 8000,
.rate_max = 48000,
@@ -151,7 +151,7 @@ static const struct snd_pcm_hardware q6asm_dai_hardware_playback = {
SNDRV_PCM_RATE_12000 | \
SNDRV_PCM_RATE_24000), \
.formats = (SNDRV_PCM_FMTBIT_S16_LE | \
- SNDRV_PCM_FMTBIT_S24_LE), \
+ SNDRV_PCM_FMTBIT_S32_LE), \
.channels_min = 1, \
.channels_max = 4, \
.rate_min = 8000, \
@@ -377,6 +377,13 @@ static int q6asm_dai_open(struct snd_soc_component *component,
stream_id = cpu_dai->driver->id;
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ /* The DSP stores 24-bit samples in the high bits of 32-bit words. */
+ ret = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+ if (ret < 0)
+ return ret;
+ }
+
pdata = snd_soc_component_get_drvdata(component);
if (!pdata) {
dev_err(dev, "Drv data not found ..\n");
@@ -506,6 +513,7 @@ static int q6asm_dai_hw_params(struct snd_soc_component *component,
prtd->bits_per_sample = 16;
break;
case SNDRV_PCM_FORMAT_S24_LE:
+ case SNDRV_PCM_FORMAT_S32_LE:
prtd->bits_per_sample = 24;
break;
}
diff --git a/sound/soc/qcom/sc7180.c b/sound/soc/qcom/sc7180.c
index bc5a18883..9e4bd9855 100644
--- a/sound/soc/qcom/sc7180.c
+++ b/sound/soc/qcom/sc7180.c
@@ -13,6 +13,7 @@
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/pcm.h>
+#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <uapi/linux/input-event-codes.h>
@@ -425,10 +426,17 @@ static int sc7180_qdsp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels = hw_param_interval(params,
SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);
rate->min = rate->max = 48000;
channels->min = channels->max = 2;
+ /* Keep the backend at 24 bits despite the 32-bit capture container. */
+ if (params_format(params) == SNDRV_PCM_FORMAT_S32_LE) {
+ snd_mask_none(fmt);
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S24_LE);
+ }
+
return 0;
}
base-commit: 46fec1c3029b552ec63b2d01af9d43a386f043ea
--
2.39.5