From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AFEA93C457D for ; Sun, 20 Sep 2026 12:27:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789907235; cv=none; b=uDzhbYfLKOyO9FLiKsAkfUfhyEMSQmbrS5ybbDhiXuBgROF4ZGGKXmufUoLgxVj3eahAdZ2qlMY2DquNA8ss0XSDqF4OvqQWK+C3+Z3ATQGAsNR22lhyZtsG6uHNZceLJVpVUJ1yvO7hUJoxyzTlKuXbGJE8u4mzTm2TbgaBMZU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789907235; c=relaxed/simple; bh=x0Td9IYTKICH7n7tSTZQ9CEcZmy7GnUj8l+oc+qoRTY=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=edgRc/jfz1xitzOR0OuO3xO8aX5QyGEE3c0vFaDN+zAdoOvoYnieGK6fOLBC2TIyCz7Ptugo4Fa6zzqbxR/BpTiH3MjCC9KuWmx9bjiexVh3KyPeiZxvaB9OIEWFS62UK3nItnzUir6MtGZO2KTkUaHH80nWh8ozOSoKAgKfy18= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=OI9C9MFm; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="OI9C9MFm" Received: by mail-pj2-f13.google.com with SMTP id 98e67ed59e1d1-39b9184fa80so1922246a91.2 for ; Sun, 20 Sep 2026 05:27:01 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789907218; x=1790512018; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=7vjWBQwN3dE/GJAyD7dmJglKHoaPsNUHMSjnNpDOIVk=; b=OI9C9MFm3A82XvbVeEvWQ23VKk7y434ahM3Uyd3RIbKWJx0NgkRpK6NXrNaPh8Wt0G f+wo+ltlOxpYFeZYYTCZarQ05j0+b1CEBcEpunrDTie2fGWMJls0IbdVG0OJeKTy1SVE 9/UOL9OfKS74sOcA0VTnjGNI6oz2Au7zvpF6QKrNkBo4506JQU2L07EqhHUchI5PWigf wOgwRfGOdzv2Sug49dZ8nL6GAFPLOqy8MkqZ0iJK7Dt9RkY3TxJcA7AM+oM2ocjuzjwA b1bMg9Ys0pUyDXCCONmJl356taa8bixVPOzk8IUKF1rChDokvpe9Tdohetx45EfM40XB krxA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789907218; x=1790512018; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=7vjWBQwN3dE/GJAyD7dmJglKHoaPsNUHMSjnNpDOIVk=; b=Hkg3fL4wyQRTyGn9hNEMyOKRrtX28jbjbhRUfmHw05QLxd2vf6PzWWAvS1hqGPvQDO D/LFlo8KqNC5yk0/D9H9yWQld/BKGQftKYQTdY0VfDP2u/GbB2dcFS28vQmI/mQx6m3E jH8sq72SZEjmccrMyrz5kODrQexFOle1759HlZnqybpRhubhooWIEsH1n21vQ1fl7bwt 5Qa29Vjscmw6ekDwHdD7vZ6QjAUoUoobYJALq8bC5caxS/f9s4iLZ/dAHploHokqjh99 cROZZ0mbg4AC86KPCE8aueLBftZfRo2DKV5Btz24cXXuv/DBQlL9W+THvF4HiVZcbQvp Gzyw== X-Forwarded-Encrypted: i=1; AKwUvBwcFmBBaleqE4jFNP2Yzqi8iC5hkSCEW0w7WAIzocyRt5OHSfDj25rKrBtDEzU3j4+PHyeiWP06z18=@vger.kernel.org X-Gm-Message-State: AFuF++mDL0aOCzzCYcMLQNS8L22uDBIsB9ZLLdw9nNDBmodq7SoBl9qx Kp28sGwgI8OzHnGhm9EeHnV82R6P8hmfxCHBaCEm89XVXBXPFxdHfholPIBBzQufZDoOZQ== X-Gm-Gg: AYBFou3gH1poBxMu6CbZw0dJYAkpPFgITj2tUoB3+YYE4KpoW7d2zHi6CIfzNAa5i2w a7IgXAvoOXKP5A9pnRimOKV9iX55Aka5X3KgjprJFy0gYHWb2UO9yYS4LNxIPad6JtzegCRGleK bwRR4l78ak53zXbaPz/E9+YmVoIvV4b4FRqEXQ5DQTVwmdupFPYQyjI5c1eb/cS0di7OuvV+Ua2 hGEdlEi8/zKDBTmoyTj60KU2A6Q+CJP/jlc9yyA1X7NzzyS5zMNKd3hPX97vlXMoeXLifXT5NyO gJ2M3naJ4axZCMGPj807KZFmddryFN6iZ50iVMgFA3znk/mobVsPfb6EcRzguYnz8W6GSvpOl5I IxVYCtPgIJisvx51oI3K8c8gfgrA/CjYz4Kn9PlEK62ohhOM5g+R/MninX/APzVQO7pmfcpeoQW W3xMGiSYDgIbIrbPIv9NO9sugJ9/5ry0PzKquXg0iLh2V7zqFkP6ZyoO1It0w4GyFvRmn1IvtPr HrNZkx2LgnT+6aRieUbv7X6 X-Received: by 2002:a17:90a:c089:b0:39e:6c68:155a with SMTP id 98e67ed59e1d1-39e6c683333mr5944433a91.34.1789907218266; Sun, 20 Sep 2026 05:26:58 -0700 (PDT) Received: from archlinux (061093018190.ctinets.com. [61.93.18.190]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3a038ff8c39sm3188791a91.7.2026.09.20.05.26.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 20 Sep 2026 05:26:57 -0700 (PDT) From: Darren Chang To: perex@perex.cz, tiwai@suse.com Cc: corbet@lwn.net, skhan@linuxfoundation.org, rdunlap@infradead.org, linux-sound@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] ALSA: usb-audio: Add quirk for inverted sample rates on NUX NAI-24 Date: Sun, 20 Sep 2026 20:26:53 +0800 Message-ID: <20260920122653.41993-1-darrenchangjr01@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-doc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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 e500ec84e..689eccf6a 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 34832183a..cc77eace7 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 c5870901a..03e3e5ae4 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2550,6 +2550,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 */ @@ -2675,6 +2677,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 244364739..c6d59218d 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 */ -- 2.55.0