From d842c03f89aef141874149f8dc8a28c104455f32 Mon Sep 17 00:00:00 2001 From: Darren Chang Date: Sun, 20 Sep 2026 20:26:53 +0800 Subject: [PATCH] ALSA: usb-audio: Add quirk for inverted sample rates on NUX NAI-24 The NUX NAI-24 (USB 3703:2000) has a UAC2 clock source that reports bmAttributes = 0x01 (internal fixed clock) and bmControls = 0x07, so the driver treats its sample rate as programmable. When the driver sends SET_CUR(SAMPLING_FREQ_CONTROL), the firmware acknowledges the request and then runs the clock on the opposite base-rate family: asking for 44100 Hz makes the device run at 48000 Hz, and asking for 48000 Hz makes it run at 44100 Hz. The result is playback that is about 8.8% fast, or 8.4% slow with heavy static on the 48000 Hz family. macOS and Windows ignore bmControls, treat the clock as fixed and resample, so they are unaffected. Work around it by sending the partner rate in SET_CUR for this device, so the device runs at the requested rate. A new QUIRK_FLAG_SWAP_RATES flag controls this, applied through the quirk flags table. Only the 44.1/48 kHz pair has been verified on hardware; the 88.2/96 kHz and 176.4/192 kHz pairs are untested but follow the same pattern. Testing: the equivalent change ran on the device as a locally built module (44100 Hz PCM, device clock 44100 Hz, no xruns, correct tempo, no static). This upstream form compiles for sound/usb without warnings and passes checkpatch, but has not been built in a full kernel tree or load-tested. Assisted-by: LLM Signed-off-by: Darren Chang Link: https://patch.msgid.link/20260920122653.41993-1-darrenchangjr01@gmail.com Signed-off-by: Takashi Iwai --- Documentation/sound/alsa-configuration.rst | 6 +++++ sound/usb/clock.c | 27 ++++++++++++++++++++++ sound/usb/quirks.c | 3 +++ sound/usb/usbaudio.h | 6 +++++ 4 files changed, 42 insertions(+) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index e500ec84e3ef..689eccf6a592 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2407,6 +2407,12 @@ quirk_flags 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. + * bit 33: ``swap_rates`` + The clock source applies a requested rate to the other base-rate + family, i.e. asking for 44100 makes the device run at 48000 and vice + versa (same for 88200 <-> 96000 and 176400 <-> 192000). The driver + sends the partner rate in SET_CUR so the device ends up running at the + requested one. This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/clock.c b/sound/usb/clock.c index 34832183a1f0..cc77eace7b59 100644 --- a/sound/usb/clock.c +++ b/sound/usb/clock.c @@ -549,6 +549,30 @@ static int get_sample_rate_v2v3(struct snd_usb_audio *chip, int iface, return le32_to_cpu(data); } +/* + * Return the partner rate in the other base-rate family. + * See QUIRK_FLAG_SWAP_RATES. + */ +static unsigned int swap_base_rate(unsigned int rate) +{ + switch (rate) { + case 44100: + return 48000; + case 48000: + return 44100; + case 88200: + return 96000; + case 96000: + return 88200; + case 176400: + return 192000; + case 192000: + return 176400; + default: + return rate; + } +} + /* * Try to set the given sample rate: * @@ -585,6 +609,9 @@ int snd_usb_set_sample_rate_v2v3(struct snd_usb_audio *chip, if (!writeable) return 0; + if (chip->quirk_flags & QUIRK_FLAG_SWAP_RATES) + rate = swap_base_rate(rate); + data = cpu_to_le32(rate); err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), UAC2_CS_CUR, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 20140cf7f4aa..1c248075c48e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2776,6 +2776,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x36f9, 0xc009, /* XIBERIA K03S */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), + DEVICE_FLG(0x3703, 0x2000, /* NUX NAI-24 */ + QUIRK_FLAG_SWAP_RATES), DEVICE_FLG(0x3c20, 0x3d21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x413c, 0xa506, /* Dell AE515 sound bar */ @@ -2901,6 +2903,7 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_GET_CUR_OK), QUIRK_STRING_ENTRY(PLAYBACK_URB_FIXUP), QUIRK_STRING_ENTRY(ALWAYS_SET_RATE), + QUIRK_STRING_ENTRY(SWAP_RATES), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 24436473931d..c6d59218d35f 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -258,6 +258,10 @@ extern bool snd_usb_skip_validation; * 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. + * QUIRK_FLAG_SWAP_RATES + * The device applies a requested sample rate to the other base-rate family + * (44100 <-> 48000, 88200 <-> 96000, 176400 <-> 192000). Send the partner + * rate in SET_CUR so that the device runs at the requested rate. */ enum { @@ -294,6 +298,7 @@ enum { QUIRK_TYPE_MIXER_GET_CUR_OK = 30, QUIRK_TYPE_PLAYBACK_URB_FIXUP = 31, QUIRK_TYPE_ALWAYS_SET_RATE = 32, + QUIRK_TYPE_SWAP_RATES = 33, /* Please also edit snd_usb_audio_quirk_flag_names and alsa-configuration.rst */ }; @@ -332,5 +337,6 @@ enum { #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) +#define QUIRK_FLAG_SWAP_RATES QUIRK_FLAG(SWAP_RATES) #endif /* __USBAUDIO_H */