From 84446536f63d471ab16b2faa25eeab1df21ace0a Mon Sep 17 00:00:00 2001 From: Cezary Rojewski Date: Tue, 24 Feb 2026 21:56:19 +0100 Subject: ALSA: control: Verify put() result when in debug mode The put() operation is expected to return: 1) 0 on success if no changes were made 2) 1 on success if changes were made 3) error code otherwise Currently 2) is usually ignored when writing control-operations. While forcing compliance is not an option right now, make it easier for developers to adhere to the expectations and notice problems by logging them when CONFIG_SND_CTL_DEBUG is enabled. Due to large size of struct snd_ctl_elem_value, 'value_buf' is provided as a reusable buffer for kctl->put() verification. This prevents exhausting the stack when verifying the operation. >From user perspective, patch introduces a new trace/events category 'snd_ctl' containing a single 'snd_ctl_put' event type. Log sample: amixer-1086 [003] ..... 8.035939: snd_ctl_put: success: expected=0, actual=0 for ctl numid=1, iface=MIXER, name='Master Playback Volume', index=0, device=0, subdevice=0, card=0 amixer-1087 [003] ..... 8.938721: snd_ctl_put: success: expected=1, actual=1 for ctl numid=1, iface=MIXER, name='Master Playback Volume', index=0, device=0, subdevice=0, card=0 amixer-1088 [003] ..... 9.631470: snd_ctl_put: success: expected=1, actual=1 for ctl numid=1, iface=MIXER, name='Master Playback Volume', index=0, device=0, subdevice=0, card=0 amixer-1089 [000] ..... 9.636786: snd_ctl_put: fail: expected=1, actual=0 for ctl numid=5, iface=MIXER, name='Loopback Mute', index=0, device=0, subdevice=0, card=0 Signed-off-by: Cezary Rojewski Reviewed-by: Mark Brown Reviewed-by: Jaroslav Kysela Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260224205619.584795-1-cezary.rojewski@intel.com --- include/sound/core.h | 3 ++ sound/core/Makefile | 1 + sound/core/control.c | 76 +++++++++++++++++++++++++++++++++++++++++++++- sound/core/control_trace.h | 55 +++++++++++++++++++++++++++++++++ sound/core/init.c | 8 +++++ 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 sound/core/control_trace.h diff --git a/include/sound/core.h b/include/sound/core.h index 64327e971122..4093ec82a0a1 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -133,6 +133,9 @@ struct snd_card { #ifdef CONFIG_SND_DEBUG struct dentry *debugfs_root; /* debugfs root for card */ #endif +#ifdef CONFIG_SND_CTL_DEBUG + struct snd_ctl_elem_value *value_buf; /* buffer for kctl->put() verification */ +#endif #ifdef CONFIG_PM unsigned int power_state; /* power state */ diff --git a/sound/core/Makefile b/sound/core/Makefile index 31a0623cc89d..fdd3bb6e81a9 100644 --- a/sound/core/Makefile +++ b/sound/core/Makefile @@ -23,6 +23,7 @@ snd-pcm-$(CONFIG_SND_PCM_IEC958) += pcm_iec958.o # for trace-points CFLAGS_pcm_lib.o := -I$(src) CFLAGS_pcm_native.o := -I$(src) +CFLAGS_control.o := -I$(src) snd-pcm-dmaengine-y := pcm_dmaengine.o diff --git a/sound/core/control.c b/sound/core/control.c index 934e84e93838..374e703d15a9 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -19,6 +19,13 @@ #include #include +#ifdef CONFIG_SND_CTL_DEBUG +#define CREATE_TRACE_POINTS +#include "control_trace.h" +#else +#define trace_snd_ctl_put(card, kctl, iname, expected, actual) +#endif + // Max allocation size for user controls. static int max_user_ctl_alloc_size = 8 * 1024 * 1024; module_param_named(max_user_ctl_alloc_size, max_user_ctl_alloc_size, int, 0444); @@ -1264,6 +1271,72 @@ static int snd_ctl_elem_read_user(struct snd_card *card, return result; } +#if IS_ENABLED(CONFIG_SND_CTL_DEBUG) + +static const char *const snd_ctl_elem_iface_names[] = { + [SNDRV_CTL_ELEM_IFACE_CARD] = "CARD", + [SNDRV_CTL_ELEM_IFACE_HWDEP] = "HWDEP", + [SNDRV_CTL_ELEM_IFACE_MIXER] = "MIXER", + [SNDRV_CTL_ELEM_IFACE_PCM] = "PCM", + [SNDRV_CTL_ELEM_IFACE_RAWMIDI] = "RAWMIDI", + [SNDRV_CTL_ELEM_IFACE_TIMER] = "TIMER", + [SNDRV_CTL_ELEM_IFACE_SEQUENCER] = "SEQUENCER", +}; + +static int snd_ctl_put_verify(struct snd_card *card, struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *control) +{ + struct snd_ctl_elem_value *original = card->value_buf; + struct snd_ctl_elem_info info; + const char *iname; + int ret, retcmp; + + memset(original, 0, sizeof(*original)); + memset(&info, 0, sizeof(info)); + + ret = kctl->info(kctl, &info); + if (ret) + return ret; + + ret = kctl->get(kctl, original); + if (ret) + return ret; + + ret = kctl->put(kctl, control); + if (ret < 0) + return ret; + + /* Sanitize the new value (control->value) before comparing. */ + fill_remaining_elem_value(control, &info, 0); + + /* With known state for both new and original, do the comparison. */ + retcmp = memcmp(&original->value, &control->value, sizeof(original->value)); + if (retcmp) + retcmp = 1; + + iname = snd_ctl_elem_iface_names[kctl->id.iface]; + trace_snd_ctl_put(&kctl->id, iname, card->number, ret, retcmp); + + return ret; +} + +static int snd_ctl_put(struct snd_card *card, struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *control, unsigned int access) +{ + if ((access & SNDRV_CTL_ELEM_ACCESS_SKIP_CHECK) || + (access & SNDRV_CTL_ELEM_ACCESS_VOLATILE)) + return kctl->put(kctl, control); + + return snd_ctl_put_verify(card, kctl, control); +} +#else +static inline int snd_ctl_put(struct snd_card *card, struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *control, unsigned int access) +{ + return kctl->put(kctl, control); +} +#endif + static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, struct snd_ctl_elem_value *control) { @@ -1300,7 +1373,8 @@ static int snd_ctl_elem_write(struct snd_card *card, struct snd_ctl_file *file, false); } if (!result) - result = kctl->put(kctl, control); + result = snd_ctl_put(card, kctl, control, vd->access); + if (result < 0) { up_write(&card->controls_rwsem); return result; diff --git a/sound/core/control_trace.h b/sound/core/control_trace.h new file mode 100644 index 000000000000..d30e654b0860 --- /dev/null +++ b/sound/core/control_trace.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_ctl + +#if !defined(_TRACE_SND_CTL_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_SND_CTL_H + +#include +#include + +TRACE_EVENT(snd_ctl_put, + + TP_PROTO(struct snd_ctl_elem_id *id, const char *iname, unsigned int card, + int expected, int actual), + + TP_ARGS(id, iname, card, expected, actual), + + TP_STRUCT__entry( + __field(unsigned int, numid) + __string(iname, iname) + __string(kname, id->name) + __field(unsigned int, index) + __field(unsigned int, device) + __field(unsigned int, subdevice) + __field(unsigned int, card) + __field(int, expected) + __field(int, actual) + ), + + TP_fast_assign( + __entry->numid = id->numid; + __assign_str(iname); + __assign_str(kname); + __entry->index = id->index; + __entry->device = id->device; + __entry->subdevice = id->subdevice; + __entry->card = card; + __entry->expected = expected; + __entry->actual = actual; + ), + + TP_printk("%s: expected=%d, actual=%d for ctl numid=%d, iface=%s, name='%s', index=%d, device=%d, subdevice=%d, card=%d\n", + __entry->expected == __entry->actual ? "success" : "fail", + __entry->expected, __entry->actual, __entry->numid, + __get_str(iname), __get_str(kname), __entry->index, + __entry->device, __entry->subdevice, __entry->card) +); + +#endif /* _TRACE_SND_CTL_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#define TRACE_INCLUDE_FILE control_trace +#include diff --git a/sound/core/init.c b/sound/core/init.c index 2f1bd9cbdbed..0c316189e947 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -362,6 +362,11 @@ static int snd_card_init(struct snd_card *card, struct device *parent, #ifdef CONFIG_SND_DEBUG card->debugfs_root = debugfs_create_dir(dev_name(&card->card_dev), sound_debugfs_root); +#endif +#ifdef CONFIG_SND_CTL_DEBUG + card->value_buf = kmalloc(sizeof(*card->value_buf), GFP_KERNEL); + if (!card->value_buf) + return -ENOMEM; #endif return 0; @@ -587,6 +592,9 @@ static int snd_card_do_free(struct snd_card *card) snd_device_free_all(card); if (card->private_free) card->private_free(card); +#ifdef CONFIG_SND_CTL_DEBUG + kfree(card->value_buf); +#endif if (snd_info_card_free(card) < 0) { dev_warn(card->dev, "unable to free card info\n"); /* Not fatal error */ -- cgit v1.2.3 From a69f67702091429316bf7b8dd2c7a405e8c26a65 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Thu, 26 Feb 2026 21:44:10 +0100 Subject: ALSA: aoa: Constify struct codec_connection 'struct codec_connection' are not modified in this driver. Constifying these structures moves some data to a read-only section, so increases overall security. On a x86_64, with allmodconfig: Before: ====== text data bss dec hex filename 10034 3392 12 13438 347e sound/aoa/fabrics/layout.o After: ===== text data bss dec hex filename 10370 3040 12 13422 346e sound/aoa/fabrics/layout.o Signed-off-by: Christophe JAILLET Link: https://patch.msgid.link/4009c337cc1a1a57795562279270c03687973b3b.1772138640.git.christophe.jaillet@wanadoo.fr Signed-off-by: Takashi Iwai --- sound/aoa/aoa.h | 2 +- sound/aoa/fabrics/layout.c | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/sound/aoa/aoa.h b/sound/aoa/aoa.h index badff9f7cd54..b92593f170ca 100644 --- a/sound/aoa/aoa.h +++ b/sound/aoa/aoa.h @@ -48,7 +48,7 @@ struct aoa_codec { u32 connected; /* data the fabric can associate with this structure */ - void *fabric_data; + const void *fabric_data; /* private! */ struct list_head list; diff --git a/sound/aoa/fabrics/layout.c b/sound/aoa/fabrics/layout.c index c18b55305294..c3ebb6de4789 100644 --- a/sound/aoa/fabrics/layout.c +++ b/sound/aoa/fabrics/layout.c @@ -55,7 +55,7 @@ struct codec_connection { struct codec_connect_info { char *name; - struct codec_connection *connections; + const struct codec_connection *connections; }; #define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF (1<<0) @@ -116,7 +116,7 @@ MODULE_ALIAS("aoa-device-id-35"); MODULE_ALIAS("aoa-device-id-44"); /* onyx with all but microphone connected */ -static struct codec_connection onyx_connections_nomic[] = { +static const struct codec_connection onyx_connections_nomic[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, .codec_bit = 0, @@ -133,7 +133,7 @@ static struct codec_connection onyx_connections_nomic[] = { }; /* onyx on machines without headphone */ -static struct codec_connection onyx_connections_noheadphones[] = { +static const struct codec_connection onyx_connections_noheadphones[] = { { .connected = CC_SPEAKERS | CC_LINEOUT | CC_LINEOUT_LABELLED_HEADPHONE, @@ -157,7 +157,7 @@ static struct codec_connection onyx_connections_noheadphones[] = { }; /* onyx on machines with real line-out */ -static struct codec_connection onyx_connections_reallineout[] = { +static const struct codec_connection onyx_connections_reallineout[] = { { .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE, .codec_bit = 0, @@ -174,7 +174,7 @@ static struct codec_connection onyx_connections_reallineout[] = { }; /* tas on machines without line out */ -static struct codec_connection tas_connections_nolineout[] = { +static const struct codec_connection tas_connections_nolineout[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE, .codec_bit = 0, @@ -191,7 +191,7 @@ static struct codec_connection tas_connections_nolineout[] = { }; /* tas on machines with neither line out nor line in */ -static struct codec_connection tas_connections_noline[] = { +static const struct codec_connection tas_connections_noline[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE, .codec_bit = 0, @@ -204,7 +204,7 @@ static struct codec_connection tas_connections_noline[] = { }; /* tas on machines without microphone */ -static struct codec_connection tas_connections_nomic[] = { +static const struct codec_connection tas_connections_nomic[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, .codec_bit = 0, @@ -217,7 +217,7 @@ static struct codec_connection tas_connections_nomic[] = { }; /* tas on machines with everything connected */ -static struct codec_connection tas_connections_all[] = { +static const struct codec_connection tas_connections_all[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, .codec_bit = 0, @@ -233,7 +233,7 @@ static struct codec_connection tas_connections_all[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection toonie_connections[] = { +static const struct codec_connection toonie_connections[] = { { .connected = CC_SPEAKERS | CC_HEADPHONE, .codec_bit = 0, @@ -241,7 +241,7 @@ static struct codec_connection toonie_connections[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection topaz_input[] = { +static const struct codec_connection topaz_input[] = { { .connected = CC_DIGITALIN, .codec_bit = 0, @@ -249,7 +249,7 @@ static struct codec_connection topaz_input[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection topaz_output[] = { +static const struct codec_connection topaz_output[] = { { .connected = CC_DIGITALOUT, .codec_bit = 1, @@ -257,7 +257,7 @@ static struct codec_connection topaz_output[] = { {} /* terminate array by .connected == 0 */ }; -static struct codec_connection topaz_inout[] = { +static const struct codec_connection topaz_inout[] = { { .connected = CC_DIGITALIN, .codec_bit = 0, @@ -772,7 +772,7 @@ static int check_codec(struct aoa_codec *codec, { const u32 *ref; char propname[32]; - struct codec_connection *cc; + const struct codec_connection *cc; /* if the codec has a 'codec' node, we require a reference */ if (of_node_name_eq(codec->node, "codec")) { @@ -895,7 +895,7 @@ static void layout_notify(void *data) static void layout_attached_codec(struct aoa_codec *codec) { - struct codec_connection *cc; + const struct codec_connection *cc; struct snd_kcontrol *ctl; int headphones, lineout; struct layout_dev *ldev = layout_device; -- cgit v1.2.3 From 41d78cb724f4b40b7548af420ccfe524b14023bb Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:56 +0800 Subject: Revert "ALSA: usb: Increase volume range that triggers a warning" UAC uses 2 bytes to store volume values, so the maximum volume range is 0xFFFF (65535, val = -32768/32767/1). The reverted commit bumpped the range of triggering the warning to > 65535, effectively making the range check a no-op. It didn't fix anything but covered any potential problems and deviated from the original intention of the range check. This reverts commit 6b971191fcfc9e3c2c0143eea22534f1f48dbb62. Fixes: 6b971191fcfc ("ALSA: usb: Increase volume range that triggers a warning") Cc: stable@vger.kernel.org Signed-off-by: Rong Zhang Acked-by: Arun Raghavan Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-2-i@rong.moe --- sound/usb/mixer.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index ac8c71ba9483..df0d3df9c7ec 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1813,10 +1813,11 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, range = (cval->max - cval->min) / cval->res; /* - * There are definitely devices with a range of ~20,000, so let's be - * conservative and allow for a bit more. + * Are there devices with volume range more than 255? I use a bit more + * to be sure. 384 is a resolution magic number found on Logitech + * devices. It will definitively catch all buggy Logitech devices. */ - if (range > 65535) { + if (range > 384) { usb_audio_warn(mixer->chip, "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", range); -- cgit v1.2.3 From 1060dbbbb2f260e4755dbd8d2f53f5a1894397d8 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:57 +0800 Subject: ALSA: usb-audio: Add helper function for volume range checks When a potentially insane volume range is found, the volume control parameters will be printed in WARN level instead of DEBUG level. Currently, it's done by emitting a open-coded usb_audio_warn() in the corresponding check. The following changes are about to add more checks against volumen ranges. As the first step, extract the current check logic into a helper function to improve readability. No functional change intended. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-3-i@rong.moe --- sound/usb/mixer.c | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index df0d3df9c7ec..f52ca0d7e665 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1660,6 +1660,27 @@ static const struct usb_feature_control_info *get_feature_control_info(int contr return NULL; } +static bool check_insane_volume_range(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl, + struct usb_mixer_elem_info *cval) +{ + int range = (cval->max - cval->min) / cval->res; + + /* + * Are there devices with volume range more than 255? I use a bit more + * to be sure. 384 is a resolution magic number found on Logitech + * devices. It will definitively catch all buggy Logitech devices. + */ + if (range > 384) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", + range); + return true; + } + + return false; +} + static void __build_feature_ctl(struct usb_mixer_interface *mixer, const struct usbmix_name_map *imap, unsigned int ctl_mask, int control, @@ -1673,7 +1694,6 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; - unsigned int range; if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ @@ -1811,25 +1831,16 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, snd_usb_mixer_fu_apply_quirk(mixer, cval, unitid, kctl); - range = (cval->max - cval->min) / cval->res; - /* - * Are there devices with volume range more than 255? I use a bit more - * to be sure. 384 is a resolution magic number found on Logitech - * devices. It will definitively catch all buggy Logitech devices. - */ - if (range > 384) { - usb_audio_warn(mixer->chip, - "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", - range); - usb_audio_warn(mixer->chip, - "[%d] FU [%s] ch = %d, val = %d/%d/%d", + if (check_insane_volume_range(mixer, kctl, cval)) { + usb_audio_warn(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", cval->head.id, kctl->id.name, cval->channels, cval->min, cval->max, cval->res); + } else { + usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", + cval->head.id, kctl->id.name, cval->channels, + cval->min, cval->max, cval->res); } - usb_audio_dbg(mixer->chip, "[%d] FU [%s] ch = %d, val = %d/%d/%d\n", - cval->head.id, kctl->id.name, cval->channels, - cval->min, cval->max, cval->res); snd_usb_mixer_add_control(&cval->head, kctl); } -- cgit v1.2.3 From 52dc4b190a31d5a5f43aadc51f5297048bfb6d52 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:58 +0800 Subject: ALSA: usb-audio: Improve volume range checks Currently the volume range check is only meant to discover quirky microphone on webcam devices and facing these issues: - The check is only meaningful for dB volume, but it doesn't check if the TLV callback is the corresponding one - A common quirky pattern "val = 0/100/1" doesn't trigger any warning - Some modern devices trigger the check, but they are legit - The warning message doesn't apply to some quirky messages with linear volume - The term "range" in the warning message is confusing. At readers' first glance it should be (max - min), but it turns out to be ((max - min) / res) Solve these issues by improving the checking logic to: - Ignore mixers with non-dB TLV - Warn on unlikely small volume ranges (max - min < 256) - Add some heuristics to determine if the volume range is unlikely big - Rephrase the warning message to mention linear volume - Rephrase the warning message in correct wording Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-4-i@rong.moe --- sound/usb/mixer.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index f52ca0d7e665..7007e0c9489b 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1664,20 +1664,62 @@ static bool check_insane_volume_range(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl, struct usb_mixer_elem_info *cval) { - int range = (cval->max - cval->min) / cval->res; + int range, steps, threshold; /* - * Are there devices with volume range more than 255? I use a bit more - * to be sure. 384 is a resolution magic number found on Logitech - * devices. It will definitively catch all buggy Logitech devices. + * If a device quirk has overrode our TLV callback, no warning should + * be generated since our checks are only meaningful for dB volume. */ - if (range > 384) { + if (!(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) || + kctl->tlv.c != snd_usb_mixer_vol_tlv) + return false; + + /* + * Meaningless volume control capability (<1dB). This should cover + * devices mapping their volume to val = 0/100/1, which are very likely + * to be quirky. + */ + range = cval->max - cval->min; + if (range < 256) { usb_audio_warn(mixer->chip, - "Warning! Unlikely big volume range (=%u), cval->res is probably wrong.", + "Warning! Unlikely small volume range (=%u), linear volume or custom curve?", range); return true; } + steps = range / cval->res; + + /* + * There are definitely devices with ~20,000 ranges (e.g., HyperX Cloud + * III with val = -18944/0/1), so we use some heuristics here: + * + * min < 0 < max: Attenuator + amplifier? Likely to be sane + * + * min < 0 = max: DSP? Voltage attenuator with FW conversion to dB? + * Likely to be sane + * + * min < max < 0: Measured values? Neutral + * + * min = 0 < max: Oversimplified FW conversion? Linear volume? Likely to + * be quirky (e.g., MV-SILICON) + * + * 0 < min < max: Amplifier with fixed gains? Likely to be quirky + * (e.g., Logitech webcam) + */ + if (cval->min < 0 && 0 <= cval->max) + threshold = 24576; /* 65535 * (3 / 8) */ + else if (cval->min < cval->max && cval->max < 0) + threshold = 1024; + else + threshold = 384; + + if (steps > threshold) { + usb_audio_warn(mixer->chip, + "Warning! Unlikely big volume step count (=%u), linear volume or wrong cval->res?", + steps); + return true; + } + return false; } -- cgit v1.2.3 From 30f68d090c0ee55a7c9b422e65d43b9ff68434c8 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:47:59 +0800 Subject: ALSA: usb-audio: Support string-descriptor-based quirk table entry Some quirky devices do not have a unique VID/PID. Matching them using DEVICE_FLG() or VENDOR_FLG() may result in conflicts. Add two new macros DEVICE_STRING_FLG() and VENDOR_STRING_FLG() to match USB string descriptors (manufacturer and/or product) in addition to VID and/or PID, so that we can deconflict these devices safely. No functional change intended. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-5-i@rong.moe --- sound/usb/quirks.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d54a1a44a69b..d365eb41910a 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2,8 +2,11 @@ /* */ +#include +#include #include #include +#include #include #include #include @@ -2135,16 +2138,39 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip, /* * driver behavior quirk flags */ +struct usb_string_match { + const char *manufacturer; + const char *product; +}; + struct usb_audio_quirk_flags_table { u32 id; u32 flags; + const struct usb_string_match *usb_string_match; }; #define DEVICE_FLG(vid, pid, _flags) \ { .id = USB_ID(vid, pid), .flags = (_flags) } #define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags) +/* Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. */ +#define DEVICE_STRING_FLG(vid, pid, _manufacturer, _product, _flags) \ +{ \ + .id = USB_ID(vid, pid), \ + .usb_string_match = &(const struct usb_string_match) { \ + .manufacturer = _manufacturer, \ + .product = _product, \ + }, \ + .flags = (_flags), \ +} + +/* Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. */ +#define VENDOR_STRING_FLG(vid, _manufacturer, _flags) \ + DEVICE_STRING_FLG(vid, 0, _manufacturer, NULL, _flags) + static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { + /* Device and string descriptor matches */ + /* Device matches */ DEVICE_FLG(0x001f, 0x0b21, /* AB13X USB Audio */ QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), @@ -2416,6 +2442,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + /* Vendor and string descriptor matches */ + /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */ QUIRK_FLAG_GET_SAMPLE_RATE), @@ -2560,14 +2588,64 @@ void snd_usb_apply_flag_dbg(const char *reason, } } +#define USB_STRING_SIZE 128 + +static char *snd_usb_get_string(struct snd_usb_audio *chip, int id) +{ + char *buf; + int ret; + + /* + * Devices without the corresponding string descriptor. + * This is non-fatal as *_STRING_FLG have nothing to do in this case. + */ + if (id == 0) + return ERR_PTR(-ENODATA); + + buf = kmalloc(USB_STRING_SIZE, GFP_KERNEL); + if (buf == NULL) + return ERR_PTR(-ENOMEM); + + ret = usb_string(chip->dev, id, buf, USB_STRING_SIZE); + if (ret < 0) { + usb_audio_warn(chip, "failed to get string for id%d: %d\n", id, ret); + kfree(buf); + return ERR_PTR(ret); + } + + return buf; +} + void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip) { const struct usb_audio_quirk_flags_table *p; + char *manufacturer __free(kfree) = NULL; + char *product __free(kfree) = NULL; for (p = quirk_flags_table; p->id; p++) { if (chip->usb_id == p->id || (!USB_ID_PRODUCT(p->id) && USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) { + /* Handle DEVICE_STRING_FLG/VENDOR_STRING_FLG. */ + if (p->usb_string_match && p->usb_string_match->manufacturer) { + if (!manufacturer) { + manufacturer = snd_usb_get_string(chip, + chip->dev->descriptor.iManufacturer); + } + if (IS_ERR_OR_NULL(manufacturer) || + strcmp(p->usb_string_match->manufacturer, manufacturer)) + continue; + } + if (p->usb_string_match && p->usb_string_match->product) { + if (!product) { + product = snd_usb_get_string(chip, + chip->dev->descriptor.iProduct); + } + if (IS_ERR_OR_NULL(product) || + strcmp(p->usb_string_match->product, product)) + continue; + } + snd_usb_apply_flag_dbg("builtin table", chip, p->flags); chip->quirk_flags |= p->flags; return; -- cgit v1.2.3 From b13031ca112a922352e585ab7220c0a99979f328 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:00 +0800 Subject: ALSA: usb-audio: Deconflict VID between Focusrite Novation & MV-SILICON MV-SILICON is a SoC manufacturer producing multifunctional audio SoCs. Many budget-oriented OEM devices are built on top of them. However, some of them are just too budget-constrained that their manufacturers didn't even have a USB VID and simply picked a random VID. Some OEMs unfortunately picked the VID of Focusrite Novation (0x1235), resulting in VID conflicts as we had defined a VENDOR_FLG() for the latter. Add a VENDOR_STRING_FLG() for MV-SILICON to stop the matching procedure for these quirky devices, so that quirk flags for Focusrite Novation won't be accidentally applied on them. Quirky device samples: usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: G1 usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: mvsilicon B1 usb audio usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 1-1.2: New USB device found, idVendor=1235, idProduct=0002, bcdDevice= 1.00 usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=... usb 1-1.2: Product: V8 usb 1-1.2: Manufacturer: MV-SILICON usb 1-1.2: SerialNumber: ... * https://github.com/linuxhw/Dmesg/blob/main/Desktop/Others/Intel/Intel%20X79/96ED1CC44499/LINUXMINT-19.3/5.0.0-32-GENERIC/X86_64/5BE1E4C74C#L1122 usb 2-1.6: New USB device found, idVendor=1235, idProduct=0002, bcdDevice= 1.00 usb 2-1.6: New USB device strings: Mfr=1, Product=2, SerialNumber=... usb 2-1.6: Product: V9 usb 2-1.6: Manufacturer: MV-SILICON usb 2-1.6: SerialNumber: ... * https://github.com/linuxhw/Dmesg/blob/main/Desktop/Hewlett-Packard/ProLiant/ProLiant%20ML110%20G6/79B1D707316A/KUBUNTU-21.04/5.11.0-33-GENERIC/X86_64/A43F59C4AB#L1009 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-6-i@rong.moe --- sound/usb/quirks.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index d365eb41910a..00d1a7c2260e 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2443,6 +2443,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), /* Vendor and string descriptor matches */ + VENDOR_STRING_FLG(0x1235, /* Conflict with Focusrite Novation */ + "MV-SILICON", + 0), /* Stop matching */ /* Vendor matches */ VENDOR_FLG(0x045e, /* MS Lifecam */ -- cgit v1.2.3 From 3787a6a10e707fca1a61d364a7c256c59823ed59 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:01 +0800 Subject: ALSA: usb-audio: Add QUIRK_FLAG_MIXER_{PLAYBACK,CAPTURE}_LINEAR_VOL Some quirky devices tune their volume by linearly tuning the voltage level (linear volume). In other words, such devices has a linear TLV mapping of DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0). Add quirk flags MIXER_PLAYBACK_LINEAR_VOL and MIXER_CAPTURE_LINEAR_VOL to represent this case respectively for playback and capture mixers. No functional change intended. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-7-i@rong.moe --- Documentation/sound/alsa-configuration.rst | 7 ++++++ sound/usb/mixer_quirks.c | 34 ++++++++++++++++++++++++++++++ sound/usb/quirks.c | 2 ++ sound/usb/usbaudio.h | 12 +++++++++++ 4 files changed, 55 insertions(+) diff --git a/Documentation/sound/alsa-configuration.rst b/Documentation/sound/alsa-configuration.rst index 55b845d38236..f75f08763941 100644 --- a/Documentation/sound/alsa-configuration.rst +++ b/Documentation/sound/alsa-configuration.rst @@ -2376,6 +2376,13 @@ quirk_flags Skip the probe-time interface setup (usb_set_interface, init_pitch, init_sample_rate); redundant with snd_usb_endpoint_prepare() at stream-open time + * bit 27: ``mixer_playback_linear_vol`` + Set linear volume mapping for devices where the playback volume + control value is mapped to voltage (instead of dB) level linearly. + In short: ``x(raw) = (raw - raw_min) / (raw_max - raw_min)``; + ``V(x) = k * x``; ``dB(x) = 20 * log10(x)``. Overrides bit 24 + * bit 28: ``mixer_capture_linear_vol`` + Similar to bit 27 but for capture streams. Overrides bit 25 This module supports multiple devices, autoprobe and hotplugging. diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 11e205da7964..539044c0c644 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4634,6 +4634,25 @@ triggered: usb_audio_dbg(chip, "something wrong in kctl name %s\n", id->name); } +static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + static const DECLARE_TLV_DB_LINEAR(scale, TLV_DB_GAIN_MUTE, 0); + + if (cval->min_mute) { + /* + * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + * resulting in min_mute being a no-op. + */ + usb_audio_warn(mixer->chip, "LINEAR_VOL overrides MIN_MUTE\n"); + } + + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; +} + void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, int unitid, struct snd_kcontrol *kctl) @@ -4660,6 +4679,21 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, "applying capture min mute quirk\n"); cval->min_mute = 1; } + + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL) + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) { + usb_audio_info(mixer->chip, + "applying playback linear volume quirk\n"); + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } + + if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL) + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) { + usb_audio_info(mixer->chip, + "applying capture linear volume quirk\n"); + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } + /* ALSA-ify some Plantronics headset control names */ if (USB_ID_VENDOR(mixer->chip->usb_id) == 0x047f && (cval->control == UAC_FU_MUTE || cval->control == UAC_FU_VOLUME)) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 00d1a7c2260e..7a5cec9cc4bd 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2543,6 +2543,8 @@ static const char *const snd_usb_audio_quirk_flag_names[] = { QUIRK_STRING_ENTRY(MIXER_PLAYBACK_MIN_MUTE), QUIRK_STRING_ENTRY(MIXER_CAPTURE_MIN_MUTE), QUIRK_STRING_ENTRY(SKIP_IFACE_SETUP), + QUIRK_STRING_ENTRY(MIXER_PLAYBACK_LINEAR_VOL), + QUIRK_STRING_ENTRY(MIXER_CAPTURE_LINEAR_VOL), NULL }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 085530cf62d9..58fd07f8c3c9 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -228,6 +228,14 @@ extern bool snd_usb_skip_validation; * Skip the probe-time interface setup (usb_set_interface, * init_pitch, init_sample_rate); redundant with * snd_usb_endpoint_prepare() at stream-open time + * QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL + * Set linear volume mapping for devices where the playback volume control + * value is mapped to voltage (instead of dB) level linearly. In short: + * x(raw) = (raw - raw_min) / (raw_max - raw_min); V(x) = k * x; + * dB(x) = 20 * log10(x). Overrides QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE + * QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL + * Similar to QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL, but for capture streams. + * Overrides QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE */ enum { @@ -258,6 +266,8 @@ enum { QUIRK_TYPE_MIXER_PLAYBACK_MIN_MUTE = 24, QUIRK_TYPE_MIXER_CAPTURE_MIN_MUTE = 25, QUIRK_TYPE_SKIP_IFACE_SETUP = 26, + QUIRK_TYPE_MIXER_PLAYBACK_LINEAR_VOL = 27, + QUIRK_TYPE_MIXER_CAPTURE_LINEAR_VOL = 28, /* Please also edit snd_usb_audio_quirk_flag_names */ }; @@ -290,5 +300,7 @@ enum { #define QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE QUIRK_FLAG(MIXER_PLAYBACK_MIN_MUTE) #define QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE QUIRK_FLAG(MIXER_CAPTURE_MIN_MUTE) #define QUIRK_FLAG_SKIP_IFACE_SETUP QUIRK_FLAG(SKIP_IFACE_SETUP) +#define QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL QUIRK_FLAG(MIXER_PLAYBACK_LINEAR_VOL) +#define QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL QUIRK_FLAG(MIXER_CAPTURE_LINEAR_VOL) #endif /* __USBAUDIO_H */ -- cgit v1.2.3 From f510f3bacc2f0f862bb4b878e80e5519cec3419e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:02 +0800 Subject: ALSA: usb-audio: Add linear volume quirk for Hotone Audio Pulze Mini Hotone Audio Pulze Mini is a modeling amplifier with UAC interface. Its Playback and Capture mixers use linear volume with val = 0/100/1. Add a quirk table entry matching VID/PID=0x84ef/0x0082 and applying linear volume quirk flags, so that it can work properly. Quirky device sample: usb 7-1: New USB device found, idVendor=84ef, idProduct=0082, bcdDevice= 1.03 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Pulze Mini usb 7-1: Manufacturer: Hotone Audio usb 7-1: SerialNumber: 20240807 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-8-i@rong.moe --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 7a5cec9cc4bd..17f6be4d2350 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2441,6 +2441,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_ALIGN_TRANSFER), DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */ QUIRK_FLAG_ALIGN_TRANSFER), + DEVICE_FLG(0x84ef, 0x0082, /* Hotone Audio Pulze Mini */ + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL | QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL), /* Vendor and string descriptor matches */ VENDOR_STRING_FLG(0x1235, /* Conflict with Focusrite Novation */ -- cgit v1.2.3 From dfd4b0d46e774d7fbd23a438ead45de08bde783e Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Wed, 4 Mar 2026 03:48:03 +0800 Subject: ALSA: usb-audio: Apply linear volume quirk on MV-SILICON devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MV-SILICON is a SoC manufacturer producing multifunctional audio SoCs. Many devices built on top of their SDK share a common quirk that the Playback and Capture mixers use linear volume with val = 0/4096/1. The SDK seems to always report "MV-SILICON" for manufacturer string. Hence, match it so that we don't need to define quirk table entries separately for each devices. The "val = 0/4096/1" pattern is also checked against before applying the quirk, in order that the quirk won't accidentally break unseen variants. Quirky device samples: usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: G1 usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 7-1: New USB device found, idVendor=1235, idProduct=0003, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: mvsilicon B1 usb audio usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 5-1.4: New USB device found, idVendor=8888, idProduct=1719, bcdDevice= 1.00 usb 5-1.4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 5-1.4: Product: HF310 USB Audio usb 5-1.4: Manufacturer: MV-SILICON usb 5-1.4: SerialNumber: 20190808 usb 7-1: New USB device found, idVendor=2717, idProduct=5086, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Redmi 电脑音箱 usb 7-1: Manufacturer: MV-SILICON usb 7-1: SerialNumber: 20190808 usb 2-1.2: New USB device found, idVendor=3142, idProduct=a601, bcdDevice= 1.00 usb 2-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 2-1.2: Product: fifine Microphone usb 2-1.2: Manufacturer: MV-SILICON usb 2-1.2: SerialNumber: 20190808 * https://forum.ubuntu-it.org/viewtopic.php?t=659345 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260303194805.266158-9-i@rong.moe --- sound/usb/mixer_quirks.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 539044c0c644..e97814dc9025 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4588,6 +4588,24 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, } } +static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + if (cval->min == 0 && cval->max == 4096 && cval->res == 1) { + /* The final effects will be printed later. */ + usb_audio_info(mixer->chip, "applying MV-SILICON quirks (0/4096/1 variant)\n"); + + /* Respect MIN_MUTE set by module parameters. */ + if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE)) + mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL; + if (!(mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE)) + mixer->chip->quirk_flags |= QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL; + } else { + usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant"); + } +} + /* * Some Plantronics headsets have control names that don't meet ALSA naming * standards. This function fixes nonstandard source names. By the time @@ -4664,6 +4682,10 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, break; } + if (cval->control == UAC_FU_VOLUME && + !strncmp(mixer->chip->card->longname, "MV-SILICON", 10)) + snd_usb_mv_silicon_quirks(mixer, cval, kctl); + /* lowest playback value is muted on some devices */ if (mixer->chip->quirk_flags & QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE) if (strstr(kctl->id.name, "Playback")) { -- cgit v1.2.3 From 27b9bcad2bf77e12c08e36d8d72bd6ce0db46041 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Wed, 4 Mar 2026 15:02:19 +0800 Subject: ALSA: hda/senary: Add hardware init verbs and fixup framework Port the essential hardware initialization logic from the vendor driver and introduce the standard HDA fixup framework to handle different machine configurations. Key changes: 1. Add hardware init verbs: - Implement `senary_init_verb` to send the vendor-specific initialization sequence required by the SN6186 chip. - Override pin capabilities for Node 0x19 to ensure proper headset microphone support. 2. Introduce fixup framework: - Define a default pin configuration table (`senary_pincfg_default`) to provide a fallback for devices with invalid BIOS configurations. - Establish a quirk table structure for future machine-specific fixes. - Since the standard quirk matching relies on Subsystem IDs, we manually apply the default fixup if `snd_hda_pick_fixup` does not find a specific match. This ensures the chip is correctly initialized during probe and resume, and provides a scalable mechanism for supporting specific hardware quirks. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260304070219.450083-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/senarytech.c | 63 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c index 6239a25bb8f3..f9a389df3a17 100644 --- a/sound/hda/codecs/senarytech.c +++ b/sound/hda/codecs/senarytech.c @@ -36,6 +36,32 @@ struct senary_spec { unsigned int gpio_mic_led_mask; }; +enum { + SENARY_FIXUP_PINCFG_DEFAULT, +}; + +static const struct hda_pintbl senary_pincfg_default[] = { + { 0x16, 0x02211020 }, /* Headphone */ + { 0x17, 0x40f001f0 }, /* Not used */ + { 0x18, 0x05a1904d }, /* Mic */ + { 0x19, 0x02a1104e }, /* Headset Mic */ + { 0x1a, 0x01819030 }, /* Line-in */ + { 0x1d, 0x01014010 }, /* Line-out */ + {} +}; + +static const struct hda_fixup senary_fixups[] = { + [SENARY_FIXUP_PINCFG_DEFAULT] = { + .type = HDA_FIXUP_PINS, + .v.pins = senary_pincfg_default, + }, +}; + +/* Quirk table for specific machines can be added here */ +static const struct hda_quirk sn6186_fixups[] = { + {} +}; + #ifdef CONFIG_SND_HDA_INPUT_BEEP /* additional beep mixers; private_value will be overwritten */ static const struct snd_kcontrol_new senary_beep_mixer[] = { @@ -93,6 +119,19 @@ static void senary_auto_parse_eapd(struct hda_codec *codec) } } +/* Hardware specific initialization verbs */ +static void senary_init_verb(struct hda_codec *codec) +{ + /* Vendor specific init sequence */ + snd_hda_codec_write(codec, 0x1b, 0x0, 0x05a, 0xaa); + snd_hda_codec_write(codec, 0x1b, 0x0, 0x059, 0x48); + snd_hda_codec_write(codec, 0x1b, 0x0, 0x01b, 0x00); + snd_hda_codec_write(codec, 0x1b, 0x0, 0x01c, 0x00); + + /* Override pin caps for headset mic */ + snd_hda_override_pin_caps(codec, 0x19, 0x2124); +} + static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins, const hda_nid_t *pins, bool on) { @@ -136,6 +175,7 @@ static int senary_init(struct hda_codec *codec) snd_hda_gen_init(codec); senary_init_gpio_led(codec); + senary_init_verb(codec); if (!spec->dynamic_eapd) senary_auto_turn_eapd(codec, spec->num_eapds, spec->eapds, true); snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); @@ -181,11 +221,30 @@ static int senary_probe(struct hda_codec *codec, const struct hda_device_id *id) senary_auto_parse_eapd(codec); spec->gen.own_eapd_ctl = 1; - if (!spec->gen.vmaster_mute.hook) - spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook; + /* Setup fixups based on codec vendor ID */ + switch (codec->core.vendor_id) { + case 0x1fa86186: + codec->pin_amp_workaround = 1; + spec->gen.mixer_nid = 0x15; + snd_hda_pick_fixup(codec, NULL, sn6186_fixups, senary_fixups); + + /* If no specific quirk found, apply the default pin configuration */ + if (codec->fixup_id == HDA_FIXUP_ID_NOT_SET) + codec->fixup_id = SENARY_FIXUP_PINCFG_DEFAULT; + break; + default: + snd_hda_pick_fixup(codec, NULL, sn6186_fixups, senary_fixups); + break; + } snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + /* Run hardware init verbs once during probe */ + senary_init_verb(codec); + + if (!spec->gen.vmaster_mute.hook) + spec->gen.vmaster_mute.hook = senary_auto_vmaster_hook; + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, spec->parse_flags); if (err < 0) -- cgit v1.2.3 From b364a0d23cae157691cde2c0137998d66b45b703 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 5 Mar 2026 14:04:25 +0100 Subject: ALSA: usb-audio: Use strings in struct usb_dev for manufacturer & co The USB core already prepares the strings for manufacturer, product and serial number, and we don't have to extract the string at each time. Replace the manual usb_string() calls with the corresponding pointers in struct usb_dev as a code cleanup. Link: https://patch.msgid.link/20260305130426.975604-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/card.c | 20 ++++++++------------ sound/usb/midi2.c | 6 ++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index 270dad84d825..fd81f32a66fb 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -631,9 +631,9 @@ static void usb_audio_make_shortname(struct usb_device *dev, } /* retrieve the device string as shortname */ - if (!dev->descriptor.iProduct || - usb_string(dev, dev->descriptor.iProduct, - card->shortname, sizeof(card->shortname)) <= 0) { + if (dev->product && *dev->product) { + strscpy(card->shortname, dev->product); + } else { /* no name available from anywhere, so use ID */ scnprintf(card->shortname, sizeof(card->shortname), "USB Device %#04x:%#04x", @@ -668,15 +668,11 @@ static void usb_audio_make_longname(struct usb_device *dev, else if (quirk && quirk->vendor_name) s = quirk->vendor_name; *card->longname = 0; - if (s && *s) { - strscpy(card->longname, s, sizeof(card->longname)); - } else { - /* retrieve the vendor and device strings as longname */ - if (dev->descriptor.iManufacturer) - usb_string(dev, dev->descriptor.iManufacturer, - card->longname, sizeof(card->longname)); - /* we don't really care if there isn't any vendor string */ - } + if (s && *s) + strscpy(card->longname, s); + else if (dev->manufacturer && *dev->manufacturer) + strscpy(card->longname, dev->manufacturer); + if (*card->longname) { strim(card->longname); if (*card->longname) diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c index ef602e81576d..3546ba926cb3 100644 --- a/sound/usb/midi2.c +++ b/sound/usb/midi2.c @@ -1057,10 +1057,8 @@ static void set_fallback_rawmidi_names(struct snd_usb_midi2_interface *umidi) strscpy(ump->core.name, ump->info.name, sizeof(ump->core.name)); /* use serial number string as unique UMP product id */ - if (!*ump->info.product_id && dev->descriptor.iSerialNumber) - usb_string(dev, dev->descriptor.iSerialNumber, - ump->info.product_id, - sizeof(ump->info.product_id)); + if (!*ump->info.product_id && dev->serial && *dev->serial) + strscpy(ump->info.product_id, dev->serial); } } -- cgit v1.2.3 From 9558a2cbec2eb034ae7d570ff56ad74f9a918177 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Fri, 6 Mar 2026 01:46:39 +0800 Subject: ALSA: usb-audio: Refine string-descriptor-based quirk matching Remove snd_usb_get_string() and use the manufacturer and product strings stored in struct usb_device directly to match quirk table entries. Their NULLity can be checked to determine if the device has no these strings. This simplifies the code a lot. Meanwhile, allow quirk table entries to match "no string" explicitly, and add appropriate comments to show the expected usages of DEVICE_STRING_FLG() and VENDOR_STRING_FLG(). These changes are tiny and doesn't form another separate patch, so that back-and-forth changes can be avoided. Suggested-by: Terry Junge Link: https://lore.kernel.org/r/b59da54a-9c80-4212-a337-c5ea98da52d1@cosmicgizmosystems.com Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260305174711.1106324-1-i@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 90 ++++++++++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 17f6be4d2350..ab67f7826e9b 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2153,7 +2153,28 @@ struct usb_audio_quirk_flags_table { { .id = USB_ID(vid, pid), .flags = (_flags) } #define VENDOR_FLG(vid, _flags) DEVICE_FLG(vid, 0, _flags) -/* Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. */ +/* + * Use as a last resort if using DEVICE_FLG() is prone to VID/PID conflicts. + * + * Usage: + * // match vid, pid, "manufacturer", and "product" + * DEVICE_STRING_FLG(vid, pid, "manufacturer", "product", flags) + * + * // match vid, pid, "manufacturer", and any product string + * DEVICE_STRING_FLG(vid, pid, "manufacturer", NULL, flags) + * + * // match vid, pid, "manufacturer", and device must have no product string + * DEVICE_STRING_FLG(vid, pid, "manufacturer", "", flags) + * + * // match vid, pid, any manufacturer string, and "product" + * DEVICE_STRING_FLG(vid, pid, NULL, "product", flags) + * + * // match vid, pid, no manufacturer string, and "product" + * DEVICE_STRING_FLG(vid, pid, "", "product", flags) + * + * // match vid, pid, no manufacturer string, and no product string + * DEVICE_STRING_FLG(vid, pid, "", "", flags) + */ #define DEVICE_STRING_FLG(vid, pid, _manufacturer, _product, _flags) \ { \ .id = USB_ID(vid, pid), \ @@ -2164,7 +2185,16 @@ struct usb_audio_quirk_flags_table { .flags = (_flags), \ } -/* Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. */ +/* + * Use as a last resort if using VENDOR_FLG() is prone to VID conflicts. + * + * Usage: + * // match vid, and "manufacturer" + * VENDOR_STRING_FLG(vid, "manufacturer", flags) + * + * // match vid, and device must have no manufacturer string + * VENDOR_STRING_FLG(vid, "", flags) + */ #define VENDOR_STRING_FLG(vid, _manufacturer, _flags) \ DEVICE_STRING_FLG(vid, 0, _manufacturer, NULL, _flags) @@ -2595,63 +2625,23 @@ void snd_usb_apply_flag_dbg(const char *reason, } } -#define USB_STRING_SIZE 128 - -static char *snd_usb_get_string(struct snd_usb_audio *chip, int id) -{ - char *buf; - int ret; - - /* - * Devices without the corresponding string descriptor. - * This is non-fatal as *_STRING_FLG have nothing to do in this case. - */ - if (id == 0) - return ERR_PTR(-ENODATA); - - buf = kmalloc(USB_STRING_SIZE, GFP_KERNEL); - if (buf == NULL) - return ERR_PTR(-ENOMEM); - - ret = usb_string(chip->dev, id, buf, USB_STRING_SIZE); - if (ret < 0) { - usb_audio_warn(chip, "failed to get string for id%d: %d\n", id, ret); - kfree(buf); - return ERR_PTR(ret); - } - - return buf; -} - void snd_usb_init_quirk_flags_table(struct snd_usb_audio *chip) { const struct usb_audio_quirk_flags_table *p; - char *manufacturer __free(kfree) = NULL; - char *product __free(kfree) = NULL; for (p = quirk_flags_table; p->id; p++) { if (chip->usb_id == p->id || (!USB_ID_PRODUCT(p->id) && USB_ID_VENDOR(chip->usb_id) == USB_ID_VENDOR(p->id))) { /* Handle DEVICE_STRING_FLG/VENDOR_STRING_FLG. */ - if (p->usb_string_match && p->usb_string_match->manufacturer) { - if (!manufacturer) { - manufacturer = snd_usb_get_string(chip, - chip->dev->descriptor.iManufacturer); - } - if (IS_ERR_OR_NULL(manufacturer) || - strcmp(p->usb_string_match->manufacturer, manufacturer)) - continue; - } - if (p->usb_string_match && p->usb_string_match->product) { - if (!product) { - product = snd_usb_get_string(chip, - chip->dev->descriptor.iProduct); - } - if (IS_ERR_OR_NULL(product) || - strcmp(p->usb_string_match->product, product)) - continue; - } + if (p->usb_string_match && p->usb_string_match->manufacturer && + strcmp(p->usb_string_match->manufacturer, + chip->dev->manufacturer ? chip->dev->manufacturer : "")) + continue; + if (p->usb_string_match && p->usb_string_match->product && + strcmp(p->usb_string_match->product, + chip->dev->product ? chip->dev->product : "")) + continue; snd_usb_apply_flag_dbg("builtin table", chip, p->flags); chip->quirk_flags |= p->flags; -- cgit v1.2.3 From c4791ce96b88a444b04c7089ae2827a3b3ae1877 Mon Sep 17 00:00:00 2001 From: Frederic Popp Date: Sun, 8 Mar 2026 16:22:16 +0100 Subject: ALSA: usb-audio: add Studio 1824 support Adapt the already implemented support for the Studio 1824c audio interface to the predecessor Studio 1824. Basically just a change adding the different hardware ID in the relevant places. Tested as much as possible. All implemented functionality seemingly works. Signed-off-by: Frederic Popp Link: https://patch.msgid.link/20260308153334.50433-2-frederic.l.popp@t-online.de Signed-off-by: Takashi Iwai --- sound/usb/format.c | 4 ++++ sound/usb/mixer_quirks.c | 3 +++ sound/usb/mixer_s1810c.c | 2 ++ 3 files changed, 9 insertions(+) diff --git a/sound/usb/format.c b/sound/usb/format.c index 1207c507882a..030b4307927a 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -455,6 +455,10 @@ static int parse_uac2_sample_rate_range(struct snd_usb_audio *chip, if (chip->usb_id == USB_ID(0x194f, 0x010d) && !s1810c_valid_sample_rate(fp, rate)) goto skip_rate; + /* Filter out invalid rates on Presonus Studio 1824 */ + if (chip->usb_id == USB_ID(0x194f, 0x0107) && + !s1810c_valid_sample_rate(fp, rate)) + goto skip_rate; /* Filter out invalid rates on Focusrite devices */ if (USB_ID_VENDOR(chip->usb_id) == 0x1235 && diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index e97814dc9025..a01510a855c2 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4477,6 +4477,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ err = snd_sc1810_init_mixer(mixer); break; + case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */ + err = snd_sc1810_init_mixer(mixer); + break; case USB_ID(0x2a39, 0x3fb0): /* RME Babyface Pro FS */ err = snd_bbfpro_controls_create(mixer); break; diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c index 7eac7d1bce64..2e5a8d37ec57 100644 --- a/sound/usb/mixer_s1810c.c +++ b/sound/usb/mixer_s1810c.c @@ -362,6 +362,7 @@ static int snd_s1810c_init_mixer_maps(struct snd_usb_audio *chip) snd_s1810c_send_ctl_packet(dev, a, 3, 0, 1, MIXER_LEVEL_0DB); break; + case USB_ID(0x194f, 0x0107): /* 1824 */ case USB_ID(0x194f, 0x010d): /* 1824c */ /* Set all output faders to unity gain */ a = SC1810C_SEL_OUTPUT; @@ -685,6 +686,7 @@ int snd_sc1810_init_mixer(struct usb_mixer_interface *mixer) return ret; break; + case USB_ID(0x194f, 0x0107): /* Presonus Studio 1824 */ case USB_ID(0x194f, 0x010d): /* Presonus Studio 1824c */ ret = snd_s1810c_switch_init(mixer, &snd_s1824c_mono_sw); if (ret < 0) -- cgit v1.2.3 From edf04f1af05d714c7aba0cf008ded1245365fcd7 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Tue, 10 Mar 2026 10:36:49 +0800 Subject: ALSA: hda/senary: Fix beep error handling and optimize EAPD switching This patch addresses a potential state inconsistency bug and optimizes runtime performance: 1. Fix error handling in set_beep_amp(): Previously, beep_nid was assigned before adding kcontrols. If kcontrol creation failed, the function returned error but left beep_nid set, causing inconsistent driver state. Moved the assignment to the end of the function. 2. Optimize senary_auto_turn_eapd(): Removed the redundant snd_hda_query_pin_caps() check inside the loop. The target pins are sourced from spec->eapds, which is strictly filtered during the initial parse phase. Checking capabilities again during every mute/unmute hook is unnecessary overhead. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260310023649.155858-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/senarytech.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c index f9a389df3a17..29b554cdd81d 100644 --- a/sound/hda/codecs/senarytech.c +++ b/sound/hda/codecs/senarytech.c @@ -76,7 +76,6 @@ static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, unsigned int beep_amp = HDA_COMPOSE_AMP_VAL(nid, 1, idx, dir); int i; - spec->gen.beep_nid = nid; for (i = 0; i < ARRAY_SIZE(senary_beep_mixer); i++) { knew = snd_hda_gen_add_kctl(&spec->gen, NULL, &senary_beep_mixer[i]); @@ -84,6 +83,8 @@ static int set_beep_amp(struct senary_spec *spec, hda_nid_t nid, return -ENOMEM; knew->private_value = beep_amp; } + + spec->gen.beep_nid = nid; return 0; } @@ -138,10 +139,9 @@ static void senary_auto_turn_eapd(struct hda_codec *codec, int num_pins, int i; for (i = 0; i < num_pins; i++) { - if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD) - snd_hda_codec_write(codec, pins[i], 0, - AC_VERB_SET_EAPD_BTLENABLE, - on ? 0x02 : 0); + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_EAPD_BTLENABLE, + on ? 0x02 : 0); } } -- cgit v1.2.3 From fd7df93013c5118812e63a52635dc6c3a805a1de Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Tue, 10 Mar 2026 11:29:20 +0100 Subject: ALSA: aoa: Skip devices with no codecs in i2sbus_resume() In i2sbus_resume(), skip devices with an empty codec list, which avoids using an uninitialized 'sysclock_factor' in the 32-bit format path in i2sbus_pcm_prepare(). In i2sbus_pcm_prepare(), replace two list_for_each_entry() loops with a single list_first_entry() now that the codec list is guaranteed to be non-empty by all callers. Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") Cc: stable@vger.kernel.org Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20260310102921.210109-3-thorsten.blum@linux.dev Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/core.c | 3 +++ sound/aoa/soundbus/i2sbus/pcm.c | 16 +++++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index f974b96e98cd..22c956267f4e 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -405,6 +405,9 @@ static int i2sbus_resume(struct macio_dev* dev) int err, ret = 0; list_for_each_entry(i2sdev, &control->list, item) { + if (list_empty(&i2sdev->sound.codec_list)) + continue; + /* reset i2s bus format etc. */ i2sbus_pcm_prepare_both(i2sdev); diff --git a/sound/aoa/soundbus/i2sbus/pcm.c b/sound/aoa/soundbus/i2sbus/pcm.c index aff99003d833..97c807e67d56 100644 --- a/sound/aoa/soundbus/i2sbus/pcm.c +++ b/sound/aoa/soundbus/i2sbus/pcm.c @@ -383,6 +383,9 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) /* set stop command */ command->command = cpu_to_le16(DBDMA_STOP); + cii = list_first_entry(&i2sdev->sound.codec_list, + struct codec_info_item, list); + /* ok, let's set the serial format and stuff */ switch (runtime->format) { /* 16 bit formats */ @@ -390,13 +393,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) case SNDRV_PCM_FORMAT_U16_BE: /* FIXME: if we add different bus factors we need to * do more here!! */ - bi.bus_factor = 0; - list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { - bi.bus_factor = cii->codec->bus_factor; - break; - } - if (!bi.bus_factor) - return -ENODEV; + bi.bus_factor = cii->codec->bus_factor; input_16bit = 1; break; case SNDRV_PCM_FORMAT_S32_BE: @@ -410,10 +407,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) return -EINVAL; } /* we assume all sysclocks are the same! */ - list_for_each_entry(cii, &i2sdev->sound.codec_list, list) { - bi.sysclock_factor = cii->codec->sysclock_factor; - break; - } + bi.sysclock_factor = cii->codec->sysclock_factor; if (clock_and_divisors(bi.sysclock_factor, bi.bus_factor, -- cgit v1.2.3 From 103a7b97c63905ab4d1463bf040e27f668e6a340 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Wed, 11 Mar 2026 17:00:10 -0300 Subject: ALSA: usb-audio: map UAC3 front wide channels in convert_chmap_v3() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit convert_chmap_v3() translates UAC3 channel relationship values into ALSA channel-map positions. UAC3_CH_FRONT_WIDE_LEFT and UAC3_CH_FRONT_WIDE_RIGHT currently fall back to SNDRV_CHMAP_UNKNOWN, although ALSA already provides matching channel-map positions via SNDRV_CHMAP_FLW and SNDRV_CHMAP_FRW. Map these two UAC3 positions to their ALSA equivalents and update the comment to clarify that unsupported UAC3 channel relationships remain reported as SNDRV_CHMAP_UNKNOWN. No functional change for other channel relationships. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260311200010.103710-1-cassiogabrielcontato@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/stream.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/usb/stream.c b/sound/usb/stream.c index d38c39e28f38..2532bf97e05e 100644 --- a/sound/usb/stream.c +++ b/sound/usb/stream.c @@ -366,6 +366,8 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor /* * TODO: this conversion is not complete, update it * after adding UAC3 values to asound.h + * NOTE: not all UAC3 channel relationship have a + * direct ALSA chmap equivalent. */ switch (is->bChRelationship) { case UAC3_CH_MONO: @@ -390,6 +392,12 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor case UAC3_CH_FRONT_RIGHT_OF_CENTER: map = SNDRV_CHMAP_FRC; break; + case UAC3_CH_FRONT_WIDE_LEFT: + map = SNDRV_CHMAP_FLW; + break; + case UAC3_CH_FRONT_WIDE_RIGHT: + map = SNDRV_CHMAP_FRW; + break; case UAC3_CH_SIDE_LEFT: map = SNDRV_CHMAP_SL; break; -- cgit v1.2.3 From 032322b44c02f5e8a127d1dca6798f91cc72eb1d Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Mon, 16 Mar 2026 16:50:47 +0800 Subject: ALSA: pcm: oss: use proper stream lock for runtime->state access __snd_pcm_set_state() writes runtime->state under the PCM stream lock. However, the OSS I/O functions snd_pcm_oss_write3(), snd_pcm_oss_read3(), snd_pcm_oss_writev3() and snd_pcm_oss_readv3() read runtime->state without holding the stream lock, only holding oss.params_lock (a different mutex that does not synchronize with the stream lock). Since __snd_pcm_set_state() is called from IRQ context (e.g., snd_pcm_period_elapsed -> snd_pcm_update_state -> __snd_pcm_xrun -> snd_pcm_stop -> snd_pcm_post_stop) while the OSS read/write paths run in process context, these are concurrent accesses that constitute a data race. Rather than using READ_ONCE()/WRITE_ONCE() barriers, introduce a snd_pcm_get_state() helper that reads runtime->state under the stream lock, matching the locking discipline used elsewhere in the PCM layer. Also export snd_pcm_set_state() for completeness. Use snd_pcm_get_state() in all four OSS I/O functions, caching the result in a local variable where the same snapshot is used for multiple comparisons to avoid taking the lock repeatedly. Signed-off-by: Cen Zhang Link: https://patch.msgid.link/20260316085047.2876451-1-zzzccc427@gmail.com Signed-off-by: Takashi Iwai --- include/sound/pcm.h | 4 ++++ sound/core/oss/pcm_oss.c | 44 +++++++++++++++++++++++++------------------- sound/core/pcm_native.c | 23 +++++++++++++++++++++-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index a7860c047503..76fc33dce537 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -729,6 +729,10 @@ static inline void __snd_pcm_set_state(struct snd_pcm_runtime *runtime, runtime->status->state = state; /* copy for mmap */ } +void snd_pcm_set_state(struct snd_pcm_substream *substream, + snd_pcm_state_t state); +snd_pcm_state_t snd_pcm_get_state(struct snd_pcm_substream *substream); + /** * bytes_to_samples - Unit conversion of the size from bytes to samples * @runtime: PCM runtime instance diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index d4fd4dfc7fc3..a140a0d9abb8 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -1227,14 +1227,16 @@ static int snd_pcm_oss_capture_position_fixup(struct snd_pcm_substream *substrea snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const char *ptr, snd_pcm_uframes_t frames, int in_kernel) { struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: write: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_oss_prepare(substream); @@ -1249,7 +1251,7 @@ snd_pcm_sframes_t snd_pcm_oss_write3(struct snd_pcm_substream *substream, const break; /* test, if we can't store new data, because the stream */ /* has not been started */ - if (runtime->state == SNDRV_PCM_STATE_PREPARED) + if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_PREPARED) return -EAGAIN; } return ret; @@ -1259,20 +1261,22 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p { struct snd_pcm_runtime *runtime = substream->runtime; snd_pcm_sframes_t delay; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: read: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); if (ret < 0) break; - } else if (runtime->state == SNDRV_PCM_STATE_SETUP) { + } else if (state == SNDRV_PCM_STATE_SETUP) { ret = snd_pcm_oss_prepare(substream); if (ret < 0) break; @@ -1285,7 +1289,7 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p frames, in_kernel); mutex_lock(&runtime->oss.params_lock); if (ret == -EPIPE) { - if (runtime->state == SNDRV_PCM_STATE_DRAINING) { + if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_DRAINING) { ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); if (ret < 0) break; @@ -1301,15 +1305,16 @@ snd_pcm_sframes_t snd_pcm_oss_read3(struct snd_pcm_substream *substream, char *p #ifdef CONFIG_SND_PCM_OSS_PLUGINS snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) { - struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: writev: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_oss_prepare(substream); @@ -1322,7 +1327,7 @@ snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void /* test, if we can't store new data, because the stream */ /* has not been started */ - if (runtime->state == SNDRV_PCM_STATE_PREPARED) + if (snd_pcm_get_state(substream) == SNDRV_PCM_STATE_PREPARED) return -EAGAIN; } return ret; @@ -1330,21 +1335,22 @@ snd_pcm_sframes_t snd_pcm_oss_writev3(struct snd_pcm_substream *substream, void snd_pcm_sframes_t snd_pcm_oss_readv3(struct snd_pcm_substream *substream, void **bufs, snd_pcm_uframes_t frames) { - struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_state_t state; int ret; while (1) { - if (runtime->state == SNDRV_PCM_STATE_XRUN || - runtime->state == SNDRV_PCM_STATE_SUSPENDED) { + state = snd_pcm_get_state(substream); + if (state == SNDRV_PCM_STATE_XRUN || + state == SNDRV_PCM_STATE_SUSPENDED) { #ifdef OSS_DEBUG pcm_dbg(substream->pcm, "pcm_oss: readv: recovering from %s\n", - runtime->state == SNDRV_PCM_STATE_XRUN ? + state == SNDRV_PCM_STATE_XRUN ? "XRUN" : "SUSPEND"); #endif ret = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DRAIN, NULL); if (ret < 0) break; - } else if (runtime->state == SNDRV_PCM_STATE_SETUP) { + } else if (state == SNDRV_PCM_STATE_SETUP) { ret = snd_pcm_oss_prepare(substream); if (ret < 0) break; diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 67cf6a0e17ba..394f86bc4d29 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -618,13 +618,32 @@ static int period_to_usecs(struct snd_pcm_runtime *runtime) return usecs; } -static void snd_pcm_set_state(struct snd_pcm_substream *substream, - snd_pcm_state_t state) +/** + * snd_pcm_set_state - Set the PCM runtime state with stream lock + * @substream: PCM substream + * @state: state to set + */ +void snd_pcm_set_state(struct snd_pcm_substream *substream, + snd_pcm_state_t state) { guard(pcm_stream_lock_irq)(substream); if (substream->runtime->state != SNDRV_PCM_STATE_DISCONNECTED) __snd_pcm_set_state(substream->runtime, state); } +EXPORT_SYMBOL_GPL(snd_pcm_set_state); + +/** + * snd_pcm_get_state - Read the PCM runtime state with stream lock + * @substream: PCM substream + * + * Return: the current PCM state + */ +snd_pcm_state_t snd_pcm_get_state(struct snd_pcm_substream *substream) +{ + guard(pcm_stream_lock_irqsave)(substream); + return substream->runtime->state; +} +EXPORT_SYMBOL_GPL(snd_pcm_get_state); static inline void snd_pcm_timer_notify(struct snd_pcm_substream *substream, int event) -- cgit v1.2.3 From 785639b5bf2a87eaf0cda14baaa068b3728c7be2 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 16 Mar 2026 10:39:38 -0300 Subject: ALSA: timer: keep a list of open masters for slave lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_timer_check_slave() still walks all registered timers and all open timer instances to find a matching master for a newly opened slave. Maintain a global list of open master instances that can accept slave links and use it for the slave lookup path instead. This keeps the existing matching semantics while avoiding the nested walk over snd_timer_list and each timer open_list_head. The reverse path in snd_timer_check_master() already scans only the pending slave list, so this makes both lookup paths closer in shape without changing the master/slave linking logic. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260316-alsa-timer-master-list-v1-1-fb95e547110a@gmail.com Signed-off-by: Takashi Iwai --- include/sound/timer.h | 1 + sound/core/timer.c | 29 ++++++++++++++++++++--------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/include/sound/timer.h b/include/sound/timer.h index 760e132cc0cd..83bafe70cf33 100644 --- a/include/sound/timer.h +++ b/include/sound/timer.h @@ -102,6 +102,7 @@ struct snd_timer_instance { unsigned int slave_id; struct list_head open_list; struct list_head active_list; + struct list_head master_list; struct list_head ack_list; struct list_head slave_list_head; struct list_head slave_active_head; diff --git a/sound/core/timer.c b/sound/core/timer.c index 6a70df7ae019..820901d503af 100644 --- a/sound/core/timer.c +++ b/sound/core/timer.c @@ -129,6 +129,9 @@ static LIST_HEAD(snd_timer_list); /* list of slave instances */ static LIST_HEAD(snd_timer_slave_list); +/* list of open master instances that can accept slave links */ +static LIST_HEAD(snd_timer_master_list); + /* lock for slave active lists */ static DEFINE_SPINLOCK(slave_active_lock); @@ -161,6 +164,7 @@ struct snd_timer_instance *snd_timer_instance_new(const char *owner) } INIT_LIST_HEAD(&timeri->open_list); INIT_LIST_HEAD(&timeri->active_list); + INIT_LIST_HEAD(&timeri->master_list); INIT_LIST_HEAD(&timeri->ack_list); INIT_LIST_HEAD(&timeri->slave_list_head); INIT_LIST_HEAD(&timeri->slave_active_head); @@ -245,6 +249,12 @@ static int check_matching_master_slave(struct snd_timer_instance *master, return 1; } +static bool snd_timer_has_slave_key(const struct snd_timer_instance *timeri) +{ + return !(timeri->flags & SNDRV_TIMER_IFLG_SLAVE) && + timeri->slave_class > SNDRV_TIMER_SCLASS_NONE; +} + /* * look for a master instance matching with the slave id of the given slave. * when found, relink the open_link of the slave. @@ -253,19 +263,15 @@ static int check_matching_master_slave(struct snd_timer_instance *master, */ static int snd_timer_check_slave(struct snd_timer_instance *slave) { - struct snd_timer *timer; struct snd_timer_instance *master; int err = 0; - /* FIXME: it's really dumb to look up all entries.. */ - list_for_each_entry(timer, &snd_timer_list, device_list) { - list_for_each_entry(master, &timer->open_list_head, open_list) { - err = check_matching_master_slave(master, slave); - if (err != 0) /* match found or error */ - goto out; - } + list_for_each_entry(master, &snd_timer_master_list, master_list) { + err = check_matching_master_slave(master, slave); + if (err != 0) /* match found or error */ + goto out; } - out: +out: return err < 0 ? err : 0; } @@ -377,6 +383,8 @@ int snd_timer_open(struct snd_timer_instance *timeri, timeri->slave_id = slave_id; list_add_tail(&timeri->open_list, &timer->open_list_head); + if (snd_timer_has_slave_key(timeri)) + list_add_tail(&timeri->master_list, &snd_timer_master_list); timer->num_instances++; err = snd_timer_check_master(timeri); list_added: @@ -431,6 +439,9 @@ static void snd_timer_close_locked(struct snd_timer_instance *timeri, num_slaves--; } + if (!list_empty(&timeri->master_list)) + list_del_init(&timeri->master_list); + /* force to stop the timer */ snd_timer_stop(timeri); -- cgit v1.2.3 From df3eec203b940bad98a7c0b7ec0edaaaa8cd0247 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Wed, 18 Mar 2026 11:08:46 -0300 Subject: ALSA: usb-audio: validate full match when resolving quirk aliases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_alias_quirk() resolves a quirk for an aliased USB ID by scanning usb_audio_ids[], but it currently checks only the vendor/product pair. This is weak for quirk table entries that also depend on additional USB_DEVICE_ID match fields, such as device or interface class, subclass, protocol, interface number, or bcdDevice range. Keep the aliased vid:pid as the lookup key, then validate only the remaining match criteria of each candidate entry against the real device/interface descriptors by clearing USB_DEVICE_ID_MATCH_DEVICE from a temporary copy and passing it to usb_match_one_id(). Suggested-by: Takashi Iwai Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260318-alsa-usb-fix-quirk-alias-v3-1-bd3b17a32939@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/card.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/sound/usb/card.c b/sound/usb/card.c index fd81f32a66fb..f42d72cd0378 100644 --- a/sound/usb/card.c +++ b/sound/usb/card.c @@ -866,19 +866,25 @@ static void find_last_interface(struct snd_usb_audio *chip) /* look for the corresponding quirk */ static const struct snd_usb_audio_quirk * -get_alias_quirk(struct usb_device *dev, unsigned int id) +get_alias_quirk(struct usb_interface *intf, unsigned int id) { const struct usb_device_id *p; + struct usb_device_id match_id; for (p = usb_audio_ids; p->match_flags; p++) { - /* FIXME: this checks only vendor:product pair in the list */ - if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) == - USB_DEVICE_ID_MATCH_DEVICE && - p->idVendor == USB_ID_VENDOR(id) && - p->idProduct == USB_ID_PRODUCT(id)) - return (const struct snd_usb_audio_quirk *)p->driver_info; - } + if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) != + USB_DEVICE_ID_MATCH_DEVICE) + continue; + if (p->idVendor != USB_ID_VENDOR(id) || + p->idProduct != USB_ID_PRODUCT(id)) + continue; + match_id = *p; + match_id.match_flags &= ~USB_DEVICE_ID_MATCH_DEVICE; + if (!match_id.match_flags || usb_match_one_id(intf, &match_id)) + return (const struct snd_usb_audio_quirk *) + p->driver_info; + } return NULL; } @@ -927,7 +933,7 @@ static int usb_audio_probe(struct usb_interface *intf, id = USB_ID(le16_to_cpu(dev->descriptor.idVendor), le16_to_cpu(dev->descriptor.idProduct)); if (get_alias_id(dev, &id)) - quirk = get_alias_quirk(dev, id); + quirk = get_alias_quirk(intf, id); if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum) return -ENXIO; if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE) -- cgit v1.2.3 From 4b097a7b25a01a3732f0e7569921efc9ad22bc81 Mon Sep 17 00:00:00 2001 From: Ben Copeland Date: Thu, 19 Mar 2026 12:45:21 +0000 Subject: selftests: ALSA: Skip utimer test when CONFIG_SND_UTIMER is not enabled The timer_f.utimer test hard-fails with ASSERT_EQ when SNDRV_TIMER_IOCTL_CREATE returns -1 on kernels without CONFIG_SND_UTIMER. This causes the entire alsa kselftest suite to report a failure rather than skipping the unsupported test. When CONFIG_SND_UTIMER is not enabled, the ioctl is not recognised and the kernel returns -ENOTTY. If the timer device or subdevice does not exist, -ENXIO is returned. Skip the test in both cases, but still fail on any other unexpected error. Suggested-by: Mark Brown Link: https://lore.kernel.org/linux-kselftest/0e9c25d3-efbd-433b-9fb1-0923010101b9@stanley.mountain/ Signed-off-by: Ben Copeland Reviewed-by: Mark Brown Link: https://patch.msgid.link/20260319124521.191491-1-ben.copeland@linaro.org Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/utimer-test.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/alsa/utimer-test.c b/tools/testing/selftests/alsa/utimer-test.c index d221972cd8fb..1a9ff010cb11 100644 --- a/tools/testing/selftests/alsa/utimer-test.c +++ b/tools/testing/selftests/alsa/utimer-test.c @@ -15,6 +15,7 @@ #include #include #include +#include #define FRAME_RATE 8000 #define PERIOD_SIZE 4410 @@ -52,7 +53,14 @@ FIXTURE_SETUP(timer_f) { timer_dev_fd = open("/dev/snd/timer", O_RDONLY); ASSERT_GE(timer_dev_fd, 0); - ASSERT_EQ(ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info), 0); + if (ioctl(timer_dev_fd, SNDRV_TIMER_IOCTL_CREATE, self->utimer_info) < 0) { + int err = errno; + + close(timer_dev_fd); + if (err == ENOTTY || err == ENXIO) + SKIP(return, "CONFIG_SND_UTIMER not enabled"); + ASSERT_EQ(err, 0); + } ASSERT_GE(self->utimer_info->fd, 0); close(timer_dev_fd); -- cgit v1.2.3 From 1e512ac1254c8e370dd18efe9da4dfc92492cdc5 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Sat, 21 Mar 2026 20:02:21 -0300 Subject: ALSA: pcm: Use pcm_lib_apply_appl_ptr() in x32 sync_ptr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_pcm_ioctl_sync_ptr_x32() still handles incoming appl_ptr updates differently from the other SYNC_PTR paths. The native handler and the 32-bit compat handler both pass appl_ptr through pcm_lib_apply_appl_ptr(), but the x32 handler still writes control->appl_ptr directly. That direct assignment skips the common appl_ptr validation against runtime->boundary and also bypasses the substream ack() callback. This makes the x32 ioctl path behave differently from the native and compat32 cases, and it can miss the driver notification that explicit appl_ptr synchronization relies on. Use pcm_lib_apply_appl_ptr() for x32 too, so appl_ptr updates are validated consistently and drivers relying on ack() notifications see the same behavior. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260321-alsa-pcm-x32-sync-ptr-v1-1-02ce655657c6@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_compat.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/core/pcm_compat.c b/sound/core/pcm_compat.c index e71f393d3b01..5313f50f17da 100644 --- a/sound/core/pcm_compat.c +++ b/sound/core/pcm_compat.c @@ -430,11 +430,13 @@ static int snd_pcm_ioctl_sync_ptr_x32(struct snd_pcm_substream *substream, if (!boundary) boundary = 0x7fffffff; scoped_guard(pcm_stream_lock_irq, substream) { - /* FIXME: we should consider the boundary for the sync from app */ - if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) - control->appl_ptr = scontrol.appl_ptr; - else + if (!(sflags & SNDRV_PCM_SYNC_PTR_APPL)) { + err = pcm_lib_apply_appl_ptr(substream, scontrol.appl_ptr); + if (err < 0) + return err; + } else { scontrol.appl_ptr = control->appl_ptr % boundary; + } if (!(sflags & SNDRV_PCM_SYNC_PTR_AVAIL_MIN)) control->avail_min = scontrol.avail_min; else -- cgit v1.2.3 From 32f35f9d8e457f5b2ee1df3f7a45af42965bedfe Mon Sep 17 00:00:00 2001 From: songxiebing Date: Wed, 25 Mar 2026 09:51:19 +0800 Subject: ALSA: core/seq: Optimize the return logic in cc_ev_to_ump_midi2 There are multiple early return branches within the func, and compiler optimizations(such as -O2/-O3)lead to abnormal stack frame analysis - objtool cannot comfirm that the stack frames of all branches can be correctly restored, thus generating false warnings. Below: >> sound/core/seq/seq_ump_convert.o: warning: objtool: cc_ev_to_ump_midi2+0x589: return with modified stack frame So we modify it by uniformly returning at the and of the function. Signed-off-by: songxiebing Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202503200535.J3hAvcjw-lkp@intel.com/ Link: https://patch.msgid.link/20260325015119.175835-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/core/seq/seq_ump_convert.c | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index db2f169cae11..ff4ee26adad1 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -841,7 +841,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, unsigned char index = event->data.control.param & 0x7f; unsigned char val = event->data.control.value & 0x7f; struct ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel]; - int ret; + int ret = 0; /* process special CC's (bank/rpn/nrpn) */ switch (index) { @@ -851,47 +851,54 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, cc->cc_rpn_msb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) reset_rpn(cc); - return ret; + break; case UMP_CC_RPN_LSB: ret = fill_rpn(cc, data, channel, true); cc->rpn_set = 1; cc->cc_rpn_lsb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) reset_rpn(cc); - return ret; + break; case UMP_CC_NRPN_MSB: ret = fill_rpn(cc, data, channel, true); cc->nrpn_set = 1; cc->cc_nrpn_msb = val; - return ret; + break; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); cc->nrpn_set = 1; cc->cc_nrpn_lsb = val; - return ret; + break; case UMP_CC_DATA: cc->cc_data_msb_set = 1; cc->cc_data_msb = val; - return fill_rpn(cc, data, channel, false); + ret = fill_rpn(cc, data, channel, false); + break; case UMP_CC_BANK_SELECT: cc->bank_set = 1; cc->cc_bank_msb = val; - return 0; // skip + ret = 0; // skip + break; case UMP_CC_BANK_SELECT_LSB: cc->bank_set = 1; cc->cc_bank_lsb = val; - return 0; // skip + ret = 0; // skip + break; case UMP_CC_DATA_LSB: cc->cc_data_lsb_set = 1; cc->cc_data_lsb = val; - return fill_rpn(cc, data, channel, false); + ret = fill_rpn(cc, data, channel, false); + break; + default: + data->cc.status = status; + data->cc.channel = channel; + data->cc.index = index; + data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); + ret = 1; + break; } - data->cc.status = status; - data->cc.channel = channel; - data->cc.index = index; - data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f); - return 1; + return ret; } /* convert one-parameter control event to MIDI 2.0 UMP */ -- cgit v1.2.3 From a213b6b019519063ce10569b19da20eac6ab884f Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 23 Mar 2026 10:46:24 -0300 Subject: ALSA: usb-audio: rotate standard MIDI output port scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_usbmidi_standard_output() iterates output ports in ascending order and drains each active port until the URB is full. On interfaces where multiple USB-MIDI cables share one endpoint, sustained traffic on a lower-numbered port can consume every refill before higher-numbered ports are even examined. That behavior dates back to the original implementation and still applies with the current multi-URB output path. snd_usbmidi_do_output() can refill several idle URBs in one pass, but each refill restarts the scan at port 0, so a busy lower-numbered port can keep higher-numbered ports from making progress at all. Use ep->current_port as the starting point of the scan and advance it after each URB fill. This keeps the existing packet formatting and per-port state handling intact while preventing persistent starvation of higher-numbered ports. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260323-usbmidi-port-fairness-v1-1-2d68e97592a1@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index a8bddc90c0ed..0a5b8941ebda 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -699,15 +699,18 @@ static void snd_usbmidi_transmit_byte(struct usbmidi_out_port *port, static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, struct urb *urb) { - int p; + int port0 = ep->current_port; + int i; + + for (i = 0; i < 0x10; ++i) { + int portnum = (port0 + i) & 15; + struct usbmidi_out_port *port = &ep->ports[portnum]; - /* FIXME: lower-numbered ports can starve higher-numbered ports */ - for (p = 0; p < 0x10; ++p) { - struct usbmidi_out_port *port = &ep->ports[p]; if (!port->active) continue; while (urb->transfer_buffer_length + 3 < ep->max_transfer) { uint8_t b; + if (snd_rawmidi_transmit(port->substream, &b, 1) != 1) { port->active = 0; break; @@ -715,6 +718,7 @@ static void snd_usbmidi_standard_output(struct snd_usb_midi_out_endpoint *ep, snd_usbmidi_transmit_byte(port, b, urb); } } + ep->current_port = (port0 + 1) & 15; } static const struct usb_protocol_ops snd_usbmidi_standard_ops = { -- cgit v1.2.3 From bbc6c0dda54fc0ad8f8aed0b796c23e186e1a188 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Tue, 24 Mar 2026 16:59:41 -0300 Subject: ALSA: seq_oss: return full count for successful SEQ_FULLSIZE writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_seq_oss_write() currently returns the raw load_patch() callback result for SEQ_FULLSIZE events. That callback is documented as returning 0 on success and -errno on failure, but snd_seq_oss_write() is the file write path and should report the number of user bytes consumed on success. Some in-tree backends also return backend-specific positive values, which can still be shorter than the original write size. Return the full byte count for successful SEQ_FULLSIZE writes. Preserve negative errors and convert any nonnegative completion to the original count. Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260324-alsa-seq-oss-fullsize-write-return-v1-1-66d448510538@gmail.com Signed-off-by: Takashi Iwai --- sound/core/seq/oss/seq_oss_rw.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/core/seq/oss/seq_oss_rw.c b/sound/core/seq/oss/seq_oss_rw.c index 8a142fd54a19..307ef98c44c7 100644 --- a/sound/core/seq/oss/seq_oss_rw.c +++ b/sound/core/seq/oss/seq_oss_rw.c @@ -101,9 +101,9 @@ snd_seq_oss_write(struct seq_oss_devinfo *dp, const char __user *buf, int count, break; } fmt = (*(unsigned short *)rec.c) & 0xffff; - /* FIXME the return value isn't correct */ - return snd_seq_oss_synth_load_patch(dp, rec.s.dev, - fmt, buf, 0, count); + err = snd_seq_oss_synth_load_patch(dp, rec.s.dev, + fmt, buf, 0, count); + return err < 0 ? err : count; } if (ev_is_long(&rec)) { /* extended code */ -- cgit v1.2.3 From 1a56641b7ae4f19216774a59d68024be3e6197d0 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Fri, 27 Mar 2026 10:59:45 -0300 Subject: ALSA: pcm: Serialize snd_pcm_suspend_all() with open_mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_pcm_suspend_all() walks all PCM substreams and uses a lockless runtime check to skip closed streams. It then calls snd_pcm_suspend() for each remaining substream and finally runs snd_pcm_sync_stop() in a second pass. The runtime lifetime is still controlled by pcm->open_mutex in the open/release path. That means a concurrent close can clear or free substream->runtime after the initial check in snd_pcm_suspend_all(), leaving the later suspend or sync-stop path to dereference a stale or NULL runtime pointer. Serialize snd_pcm_suspend_all() with pcm->open_mutex so the runtime pointer stays stable across both loops. This matches the existing PCM runtime lifetime rule already used by other core paths that access substream->runtime outside the stream lock. Suggested-by: Takashi Iwai Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260327-alsa-pcm-suspend-open-close-lock-v2-1-cc4baca4dcd6@gmail.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 394f86bc4d29..aefb861ab873 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -1780,6 +1780,9 @@ static int snd_pcm_suspend(struct snd_pcm_substream *substream) * snd_pcm_suspend_all - trigger SUSPEND to all substreams in the given pcm * @pcm: the PCM instance * + * Takes and releases pcm->open_mutex to serialize against + * concurrent open/close while walking the substreams. + * * After this call, all streams are changed to SUSPENDED state. * * Return: Zero if successful (or @pcm is %NULL), or a negative error code. @@ -1792,8 +1795,9 @@ int snd_pcm_suspend_all(struct snd_pcm *pcm) if (! pcm) return 0; + guard(mutex)(&pcm->open_mutex); + for_each_pcm_substream(pcm, stream, substream) { - /* FIXME: the open/close code should lock this as well */ if (!substream->runtime) continue; -- cgit v1.2.3 From ec9a788620be1c11535fe99e9b2779f9eef2b099 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 27 Mar 2026 16:30:53 +0100 Subject: ALSA: usb-audio: Replace hard-coded number with MAX_CHANNELS One place in mixer.c still used a hard-coded number 16 instead of MAX_CHANNELS. Replace with it, so that we can extend the max number of channels gracefully. Link: https://lore.kernel.org/F1B104A5-CD6A-4A26-AB46-14BF233C0579@getmailspring.com Tested-by: Phil Willoughby Link: https://patch.msgid.link/20260327153056.691575-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 7007e0c9489b..e764757979e0 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1769,7 +1769,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, cval->master_readonly = readonly_mask; } else { int i, c = 0; - for (i = 0; i < 16; i++) + for (i = 0; i < MAX_CHANNELS; i++) if (ctl_mask & BIT(i)) c++; cval->channels = c; -- cgit v1.2.3 From 16ee07bfa935f4f36deba896956f57d388221944 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 27 Mar 2026 16:30:54 +0100 Subject: ALSA: usb-audio: Extend max number of channels to 64 The current limitation of 16 as MAX_CHANNELS is rather historical at the time of UAC1 definition. As there seem already devices with a higher number of mixer channels, we should extend it too. As an ad hoc update, let's raise it to 64 so that it can still fit in a single long-long integer. Link: https://lore.kernel.org/F1B104A5-CD6A-4A26-AB46-14BF233C0579@getmailspring.com Tested-by: Phil Willoughby Link: https://patch.msgid.link/20260327153056.691575-2-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 14 +++++++------- sound/usb/mixer.h | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e764757979e0..69026cf54979 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1725,7 +1725,7 @@ static bool check_insane_volume_range(struct usb_mixer_interface *mixer, static void __build_feature_ctl(struct usb_mixer_interface *mixer, const struct usbmix_name_map *imap, - unsigned int ctl_mask, int control, + u64 ctl_mask, int control, struct usb_audio_term *iterm, struct usb_audio_term *oterm, int unitid, int nameid, int readonly_mask) @@ -1887,7 +1887,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, } static void build_feature_ctl(struct mixer_build *state, void *raw_desc, - unsigned int ctl_mask, int control, + u64 ctl_mask, int control, struct usb_audio_term *iterm, int unitid, int readonly_mask) { @@ -1899,7 +1899,7 @@ static void build_feature_ctl(struct mixer_build *state, void *raw_desc, } static void build_feature_ctl_badd(struct usb_mixer_interface *mixer, - unsigned int ctl_mask, int control, int unitid, + u64 ctl_mask, int control, int unitid, const struct usbmix_name_map *badd_map) { __build_feature_ctl(mixer, badd_map, ctl_mask, control, @@ -2075,7 +2075,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, bmaControls = ftr->bmaControls; } - if (channels > 32) { + if (channels > MAX_CHANNELS) { usb_audio_info(state->chip, "usbmixer: too many channels (%d) in unit %d\n", channels, unitid); @@ -2113,7 +2113,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, if (state->mixer->protocol == UAC_VERSION_1) { /* check all control types */ for (i = 0; i < 10; i++) { - unsigned int ch_bits = 0; + u64 ch_bits = 0; int control = audio_feature_info[i].control; for (j = 0; j < channels; j++) { @@ -2139,7 +2139,7 @@ static int parse_audio_feature_unit(struct mixer_build *state, int unitid, } } else { /* UAC_VERSION_2/3 */ for (i = 0; i < ARRAY_SIZE(audio_feature_info); i++) { - unsigned int ch_bits = 0; + u64 ch_bits = 0; unsigned int ch_read_only = 0; int control = audio_feature_info[i].control; @@ -3452,7 +3452,7 @@ static void snd_usb_mixer_dump_cval(struct snd_info_buffer *buffer, [USB_MIXER_U32] = "U32", [USB_MIXER_BESPOKEN] = "BESPOKEN", }; - snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%x, " + snd_iprintf(buffer, " Info: id=%i, control=%i, cmask=0x%llx, " "channels=%i, type=\"%s\"\n", cval->head.id, cval->control, cval->cmask, cval->channels, val_types[cval->val_type]); diff --git a/sound/usb/mixer.h b/sound/usb/mixer.h index 167fbfcf01ac..afbb3dd9f177 100644 --- a/sound/usb/mixer.h +++ b/sound/usb/mixer.h @@ -44,7 +44,7 @@ struct usb_mixer_interface { void (*private_suspend)(struct usb_mixer_interface *mixer); }; -#define MAX_CHANNELS 16 /* max logical channels */ +#define MAX_CHANNELS 64 /* max logical channels */ enum { USB_MIXER_BOOLEAN, @@ -81,7 +81,7 @@ struct usb_mixer_elem_list { struct usb_mixer_elem_info { struct usb_mixer_elem_list head; unsigned int control; /* CS or ICN (high byte) */ - unsigned int cmask; /* channel mask bitmap: 0 = master */ + u64 cmask; /* channel mask bitmap: 0 = master */ unsigned int idx_off; /* Control index offset */ unsigned int ch_readonly; unsigned int master_readonly; -- cgit v1.2.3 From 796e119e9b14763be905ad0d023c71a14bc2e931 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Wed, 25 Mar 2026 02:24:04 -0300 Subject: ALSA: core: Validate compress device numbers without dynamic minors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without CONFIG_SND_DYNAMIC_MINORS, ALSA reserves only two fixed minors for compress devices on each card: comprD0 and comprD1. snd_find_free_minor() currently computes the compress minor as type + dev without validating dev first, so device numbers greater than 1 spill into the HWDEP minor range instead of failing registration. ASoC passes rtd->id to snd_compress_new(), so this can happen on real non-dynamic-minor builds. Add a dedicated fixed-minor check for SNDRV_DEVICE_TYPE_COMPRESS in snd_find_free_minor() and reject out-of-range device numbers with -EINVAL before constructing the minor. Also remove the stale TODO in compress_offload.c that still claims multiple compress nodes are missing. Fixes: 3eafc959b32f ("ALSA: core: add support for compressed devices") Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260325-alsa-compress-static-minors-v1-1-0628573bee1c@gmail.com Signed-off-by: Takashi Iwai --- sound/core/compress_offload.c | 7 ------- sound/core/sound.c | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index fdba6e4b25fd..5a0308eb4e31 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -41,13 +41,6 @@ #define COMPR_CODEC_CAPS_OVERFLOW #endif -/* TODO: - * - add substream support for multiple devices in case of - * SND_DYNAMIC_MINORS is not used - * - Multiple node representation - * driver should be able to register multiple nodes - */ - struct snd_compr_file { unsigned long caps; struct snd_compr_stream stream; diff --git a/sound/core/sound.c b/sound/core/sound.c index 93436db24710..8d05fe0d263b 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -216,9 +216,16 @@ static int snd_find_free_minor(int type, struct snd_card *card, int dev) case SNDRV_DEVICE_TYPE_RAWMIDI: case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + if (snd_BUG_ON(!card)) + return -EINVAL; + minor = SNDRV_MINOR(card->number, type + dev); + break; case SNDRV_DEVICE_TYPE_COMPRESS: if (snd_BUG_ON(!card)) return -EINVAL; + if (dev < 0 || + dev >= SNDRV_MINOR_HWDEP - SNDRV_MINOR_COMPRESS) + return -EINVAL; minor = SNDRV_MINOR(card->number, type + dev); break; default: -- cgit v1.2.3 From 18d4969e22cc3ff738257e1d7738aafc65a6d2d2 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sat, 28 Mar 2026 18:28:08 +0800 Subject: ALSA: asihpi: detect truncated control names asihpi_ctl_init() builds mixer control names in the fixed 44-byte hpi_ctl->name buffer with sprintf(). This is not only a defensive cleanup. The current in-tree name tables and format strings can already exceed 44 bytes. For example, "Bitstream 0 Internal 0 Monitor Playback Volume" is 46 characters before the trailing NUL, so the current sprintf() call writes past the end of hpi_ctl->name. The generated control name is used as the ALSA control element key, so blindly truncating it is not sufficient. Switch the formatting to snprintf() and emit an error if truncation happens, showing the truncated name while still keeping the write bounded to hpi_ctl->name. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260328102808.33969-1-pengpeng@iscas.ac.cn Signed-off-by: Takashi Iwai --- sound/pci/asihpi/asihpi.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/sound/pci/asihpi/asihpi.c b/sound/pci/asihpi/asihpi.c index 3a64d0562803..b1c7ed7f1604 100644 --- a/sound/pci/asihpi/asihpi.c +++ b/sound/pci/asihpi/asihpi.c @@ -1362,6 +1362,7 @@ static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, struct hpi_control *hpi_ctl, char *name) { + int len; char *dir; memset(snd_control, 0, sizeof(*snd_control)); snd_control->name = hpi_ctl->name; @@ -1384,23 +1385,30 @@ static void asihpi_ctl_init(struct snd_kcontrol_new *snd_control, dir = "Playback "; /* PCM Playback source, or output node */ if (hpi_ctl->src_node_type && hpi_ctl->dst_node_type) - sprintf(hpi_ctl->name, "%s %d %s %d %s%s", - asihpi_src_names[hpi_ctl->src_node_type], - hpi_ctl->src_node_index, - asihpi_dst_names[hpi_ctl->dst_node_type], - hpi_ctl->dst_node_index, - dir, name); + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s %d %s%s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + dir, name); else if (hpi_ctl->dst_node_type) { - sprintf(hpi_ctl->name, "%s %d %s%s", - asihpi_dst_names[hpi_ctl->dst_node_type], - hpi_ctl->dst_node_index, - dir, name); + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s%s", + asihpi_dst_names[hpi_ctl->dst_node_type], + hpi_ctl->dst_node_index, + dir, name); } else { - sprintf(hpi_ctl->name, "%s %d %s%s", - asihpi_src_names[hpi_ctl->src_node_type], - hpi_ctl->src_node_index, - dir, name); + len = snprintf(hpi_ctl->name, sizeof(hpi_ctl->name), + "%s %d %s%s", + asihpi_src_names[hpi_ctl->src_node_type], + hpi_ctl->src_node_index, + dir, name); } + + if (len >= sizeof(hpi_ctl->name)) + pr_err("asihpi: truncated control name: %s\n", + hpi_ctl->name); } /*------------------------------------------------------------ -- cgit v1.2.3 From 0da18c2dd1cc2a026416222ed206e2f269edf055 Mon Sep 17 00:00:00 2001 From: Phil Willoughby Date: Sat, 28 Mar 2026 11:08:41 +0000 Subject: ALSA: usb-audio: Add quirks for Arturia AF16Rig The AF16Rig supports 34 channels at 44.1k/48k, 18 channels at 88.2k/96k and 10 channels at 176.4k/192k. This quirks is necessary because the automatic probing process we would otherwise use fails. The root cause of that is that the AF16Rig clock is not readable (its descriptor says that it is but the reads fail). Except as described below, the values in the audio format quirks were copied from the USB descriptors of the device. The rate information is from the datasheet of the device. The clock is the internal clock of the AF16Rig. Tested-By: Phil Willoughby I have tested all the configurations enabled by this patch. Cc: Jaroslav Kysela Cc: Takashi Iwai Signed-off-by: Phil Willoughby Link: https://patch.msgid.link/20260328112426.14816-1-willerz@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 165 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index eafc0d73cca1..8f79a15055a6 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3900,5 +3900,170 @@ YAMAHA_DEVICE(0x7010, "UB99"), QUIRK_RME_DIGIFACE(0x3f8c), QUIRK_RME_DIGIFACE(0x3fa0), +/* Arturia AudioFuse 16Rig Audio */ +/* AF16Rig MIDI has USB PID 0xaf21 and appears to work OK without quirks */ +{ + USB_DEVICE(0x1c75, 0xaf20), + QUIRK_DRIVER_INFO { + .vendor_name = "Arturia", + .product_name = "AF16Rig", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + { + QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 34, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03b8, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 44100, 48000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 18, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 1, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03a8, + .rates = SNDRV_PCM_RATE_88200| + SNDRV_PCM_RATE_96000, + .rate_min = 88200, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 88200, 96000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 1, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03e8, + .rates = SNDRV_PCM_RATE_176400| + SNDRV_PCM_RATE_192000, + .rate_min = 176400, + .rate_max = 192000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 176400, 192000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 34, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03b8, + .rates = SNDRV_PCM_RATE_44100| + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 44100, 48000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 18, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 2, + .altsetting = 2, + .altset_idx = 2, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03a8, + .rates = SNDRV_PCM_RATE_88200| + SNDRV_PCM_RATE_96000, + .rate_min = 88200, + .rate_max = 96000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 88200, 96000 }, + .clock = 41, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .channels = 10, + .fmt_type = UAC_FORMAT_TYPE_I_PCM, + .fmt_bits = 24, + .fmt_sz = 4, + .iface = 2, + .altsetting = 3, + .altset_idx = 3, + .endpoint = 0x81, + .ep_attr = USB_ENDPOINT_XFER_ISOC| + USB_ENDPOINT_SYNC_ASYNC, + .datainterval = 1, + .protocol = UAC_VERSION_2, + .maxpacksize = 0x03e8, + .rates = SNDRV_PCM_RATE_176400| + SNDRV_PCM_RATE_192000, + .rate_min = 176400, + .rate_max = 192000, + .nr_rates = 2, + .rate_table = (unsigned int[]) { 176400, 192000 }, + .clock = 41, + } + }, + { QUIRK_DATA_IGNORE(3) }, /* Firmware update */ + QUIRK_COMPOSITE_END + } + } +}, + #undef USB_DEVICE_VENDOR_SPEC #undef USB_AUDIO_DEVICE -- cgit v1.2.3 From 3bd246d1cf609a80cae19e4aefb599256a72b1a6 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Sat, 28 Mar 2026 01:53:35 -0300 Subject: ALSA: hda/proc: show GPI and GPO state in codec proc output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit print_gpio() prints the GPIO capability header and the bidirectional GPIO state, but it never reports the separate GPI and GPO pins even though AC_PAR_GPIO_CAP exposes their counts. The HD-audio specification defines dedicated GPI and GPO verbs alongside the GPIO ones, so codecs with input-only or output-only general-purpose pins currently lose that state from /proc/asound/card*/codec#* altogether. Add the missing read verb definitions and extend print_gpio() to dump the GPI and GPO pins, too, while leaving the existing IO[] output unchanged. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260328-hda-proc-gpi-gpo-v1-1-fabb36564bee@gmail.com Signed-off-by: Takashi Iwai --- include/sound/hda_verbs.h | 7 +++- sound/hda/common/proc.c | 100 +++++++++++++++++++++++++++++++--------------- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h index 006d358acce2..127e7016e4fe 100644 --- a/include/sound/hda_verbs.h +++ b/include/sound/hda_verbs.h @@ -56,7 +56,12 @@ enum { #define AC_VERB_GET_DIGI_CONVERT_1 0x0f0d #define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */ #define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f -/* f10-f1a: GPIO */ +/* f10-f1a: GPI/GPO/GPIO */ +#define AC_VERB_GET_GPI_DATA 0x0f10 +#define AC_VERB_GET_GPI_WAKE_MASK 0x0f11 +#define AC_VERB_GET_GPI_UNSOLICITED_RSP_MASK 0x0f12 +#define AC_VERB_GET_GPI_STICKY_MASK 0x0f13 +#define AC_VERB_GET_GPO_DATA 0x0f14 #define AC_VERB_GET_GPIO_DATA 0x0f15 #define AC_VERB_GET_GPIO_MASK 0x0f16 #define AC_VERB_GET_GPIO_DIRECTION 0x0f17 diff --git a/sound/hda/common/proc.c b/sound/hda/common/proc.c index 3bc33c5617b2..c83796b13d3d 100644 --- a/sound/hda/common/proc.c +++ b/sound/hda/common/proc.c @@ -640,41 +640,78 @@ static void print_gpio(struct snd_info_buffer *buffer, { unsigned int gpio = param_read(codec, codec->core.afg, AC_PAR_GPIO_CAP); - unsigned int enable, direction, wake, unsol, sticky, data; - int i, max; + int i, gpio_max, gpo_max, gpi_max; + + gpio_max = gpio & AC_GPIO_IO_COUNT; + gpo_max = (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT; + gpi_max = (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT; + snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, " "unsolicited=%d, wake=%d\n", - gpio & AC_GPIO_IO_COUNT, - (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT, - (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT, + gpio_max, gpo_max, gpi_max, (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0, (gpio & AC_GPIO_WAKE) ? 1 : 0); - max = gpio & AC_GPIO_IO_COUNT; - if (!max || max > 8) - return; - enable = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_MASK, 0); - direction = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_DIRECTION, 0); - wake = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_WAKE_MASK, 0); - unsol = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0); - sticky = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_STICKY_MASK, 0); - data = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_GPIO_DATA, 0); - for (i = 0; i < max; ++i) - snd_iprintf(buffer, - " IO[%d]: enable=%d, dir=%d, wake=%d, " - "sticky=%d, data=%d, unsol=%d\n", i, - (enable & (1<mixers); print_nid_array(buffer, codec, nid, &codec->nids); } @@ -940,4 +977,3 @@ int snd_hda_codec_proc_new(struct hda_codec *codec) snprintf(name, sizeof(name), "codec#%d", codec->core.addr); return snd_card_ro_proc_new(codec->card, name, codec, print_codec_info); } - -- cgit v1.2.3 From 38f6e93dedbc1b6c2a6e97110ab7e872c257a5e3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 28 Mar 2026 14:43:05 +0100 Subject: ALSA: hda: Add missing SET_GPI_* and SET_GPO_* verb definitions We've added the definitions of the missing GPI and GPO verbs for reading in the previous commit, but the counter-part for setting values is missing. Add the definitions of missing verbs for comprehensiveness. Link: https://patch.msgid.link/20260328134319.207482-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- include/sound/hda_verbs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/sound/hda_verbs.h b/include/sound/hda_verbs.h index 127e7016e4fe..6066954409aa 100644 --- a/include/sound/hda_verbs.h +++ b/include/sound/hda_verbs.h @@ -104,6 +104,11 @@ enum { #define AC_VERB_SET_DIGI_CONVERT_2 0x70e #define AC_VERB_SET_DIGI_CONVERT_3 0x73e #define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f +#define AC_VERB_SET_GPI_DATA 0x710 +#define AC_VERB_SET_GPI_WAKE_MASK 0x711 +#define AC_VERB_SET_SPI_UNSOLICITED_RSP_MASK 0x712 +#define AC_VERB_SET_GPI_STICKY_MASK 0x713 +#define AC_VERB_SET_GPO_DATA 0x714 #define AC_VERB_SET_GPIO_DATA 0x715 #define AC_VERB_SET_GPIO_MASK 0x716 #define AC_VERB_SET_GPIO_DIRECTION 0x717 -- cgit v1.2.3 From 31183edd9cb3465af5c8b9cb16f42259cbf27109 Mon Sep 17 00:00:00 2001 From: Phil Willoughby Date: Sat, 28 Mar 2026 16:02:58 +0000 Subject: ALSA: usb-audio: tidy up the AF16Rig quirks Use macros to make the AF16Rig quirk table smaller. Add a disabled block containing the theoretical quirks for the other clock sources that the AF16Rig has. It's disabled because I cannot test it. Fixes: 0da18c2dd1cc ("ALSA: usb-audio: Add quirks for Arturia AF16Rig") Tested-By: Phil Willoughby Signed-off-by: Phil Willoughby Link: https://patch.msgid.link/20260328160326.23665-1-willerz@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 200 ++++++++++++----------------------------------- 1 file changed, 50 insertions(+), 150 deletions(-) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 8f79a15055a6..b6dfe3b63c67 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -3900,6 +3900,44 @@ YAMAHA_DEVICE(0x7010, "UB99"), QUIRK_RME_DIGIFACE(0x3f8c), QUIRK_RME_DIGIFACE(0x3fa0), +#define QUIRK_AF16RIG(channel_count_, alt_setting_, \ + low_rate_, high_rate_, pack_size_, \ + clock_, interface_, endpoint_) \ + { \ + QUIRK_DATA_AUDIOFORMAT(interface_) { \ + .formats = SNDRV_PCM_FMTBIT_S32_LE, \ + .channels = channel_count_, \ + .fmt_type = UAC_FORMAT_TYPE_I_PCM, \ + .fmt_bits = 24, \ + .fmt_sz = 4, \ + .iface = interface_, \ + .altsetting = alt_setting_, \ + .altset_idx = alt_setting_, \ + .endpoint = endpoint_, \ + .ep_attr = USB_ENDPOINT_XFER_ISOC | \ + USB_ENDPOINT_SYNC_ASYNC, \ + .datainterval = 1, \ + .protocol = UAC_VERSION_2, \ + .maxpacksize = pack_size_, \ + .rates = SNDRV_PCM_RATE_##low_rate_ | \ + SNDRV_PCM_RATE_##high_rate_, \ + .rate_min = low_rate_, \ + .rate_max = high_rate_, \ + .nr_rates = 2, \ + .rate_table = (unsigned int[]) { \ + low_rate_, high_rate_ }, \ + .clock = clock_, \ + } \ + } + +#define QUIRK_AF16RIG_CLOCK(clock) \ + QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 1, 0x01), \ + QUIRK_AF16RIG(34, 1, 44100, 48000, 0x3b8, clock, 2, 0x81), \ + QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 1, 0x01), \ + QUIRK_AF16RIG(18, 2, 88200, 96000, 0x3a8, clock, 2, 0x81), \ + QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 1, 0x01), \ + QUIRK_AF16RIG(10, 3, 176400, 192000, 0x3e8, clock, 2, 0x81) + /* Arturia AudioFuse 16Rig Audio */ /* AF16Rig MIDI has USB PID 0xaf21 and appears to work OK without quirks */ { @@ -3909,161 +3947,23 @@ QUIRK_RME_DIGIFACE(0x3fa0), .product_name = "AF16Rig", QUIRK_DATA_COMPOSITE { { QUIRK_DATA_STANDARD_MIXER(0) }, - { - QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 34, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .endpoint = 0x01, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03b8, - .rates = SNDRV_PCM_RATE_44100| - SNDRV_PCM_RATE_48000, - .rate_min = 44100, - .rate_max = 48000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 44100, 48000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 18, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 1, - .altsetting = 1, - .altset_idx = 1, - .endpoint = 0x01, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03a8, - .rates = SNDRV_PCM_RATE_88200| - SNDRV_PCM_RATE_96000, - .rate_min = 88200, - .rate_max = 96000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 88200, 96000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(1) { /* Playback */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 10, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 1, - .altsetting = 3, - .altset_idx = 3, - .endpoint = 0x01, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03e8, - .rates = SNDRV_PCM_RATE_176400| - SNDRV_PCM_RATE_192000, - .rate_min = 176400, - .rate_max = 192000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 176400, 192000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 34, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 2, - .altsetting = 1, - .altset_idx = 1, - .endpoint = 0x81, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03b8, - .rates = SNDRV_PCM_RATE_44100| - SNDRV_PCM_RATE_48000, - .rate_min = 44100, - .rate_max = 48000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 44100, 48000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 18, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 2, - .altsetting = 2, - .altset_idx = 2, - .endpoint = 0x81, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03a8, - .rates = SNDRV_PCM_RATE_88200| - SNDRV_PCM_RATE_96000, - .rate_min = 88200, - .rate_max = 96000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 88200, 96000 }, - .clock = 41, - } - }, - { - QUIRK_DATA_AUDIOFORMAT(2) { /* Capture */ - .formats = SNDRV_PCM_FMTBIT_S32_LE, - .channels = 10, - .fmt_type = UAC_FORMAT_TYPE_I_PCM, - .fmt_bits = 24, - .fmt_sz = 4, - .iface = 2, - .altsetting = 3, - .altset_idx = 3, - .endpoint = 0x81, - .ep_attr = USB_ENDPOINT_XFER_ISOC| - USB_ENDPOINT_SYNC_ASYNC, - .datainterval = 1, - .protocol = UAC_VERSION_2, - .maxpacksize = 0x03e8, - .rates = SNDRV_PCM_RATE_176400| - SNDRV_PCM_RATE_192000, - .rate_min = 176400, - .rate_max = 192000, - .nr_rates = 2, - .rate_table = (unsigned int[]) { 176400, 192000 }, - .clock = 41, - } - }, + QUIRK_AF16RIG_CLOCK(41), /* Internal clock */ +#if 0 +/* These are disabled because I don't have the required hardware to test + * them. I suspect that the ADAT clock might not follow 176400 or 192000 + * because the AF16Rig won't accept ADAT audio data at those rates. + */ + QUIRK_AF16RIG_CLOCK(43), /* ADAT clock */ + QUIRK_AF16RIG_CLOCK(44), /* BNC word clock */ +#endif { QUIRK_DATA_IGNORE(3) }, /* Firmware update */ QUIRK_COMPOSITE_END } } }, +#undef QUIRK_AF16RIG_CLOCK +#undef QUIRK_AF16RIG + #undef USB_DEVICE_VENDOR_SPEC #undef USB_AUDIO_DEVICE -- cgit v1.2.3 From 9220b8cc51c960e98a9532ec990c55bc546e3b46 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Sat, 28 Mar 2026 20:42:01 -0300 Subject: ALSA: hda: intel: Drop obsolete probe-work unlock workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit ab949d519601 ("ALSA: hda - Fix deadlock of controller device lock at unbinding") added a temporary device_unlock()/device_lock() pair around probe-work cancellation to avoid a deadlock between controller unbind and codec probe. That deadlock depended on the driver core taking both a device lock and its parent lock during bind and unbind. Since commit 8c97a46af04b ("driver core: hold dev's parent lock when needed") and follow-up fixes, the parent lock is only taken when bus->need_parent_lock is set. The HDA bus does not set that flag, so codec binding no longer locks the controller device as the codec's parent. Keep cancel_delayed_work_sync(), since the async probe/remove race still needs to be serialized, but drop the stale unlock/relock workaround and its outdated FIXME comment. Keeping it around only opens an unnecessary unlocked window inside azx_remove(). Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260328-hda-intel-drop-obsolete-probe-workaround-v1-1-bc43aeafc98b@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/controllers/intel.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 3f434994c18d..89de18dc953d 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2421,20 +2421,7 @@ static void azx_remove(struct pci_dev *pci) /* cancel the pending probing work */ chip = card->private_data; hda = container_of(chip, struct hda_intel, chip); - /* FIXME: below is an ugly workaround. - * Both device_release_driver() and driver_probe_device() - * take *both* the device's and its parent's lock before - * calling the remove() and probe() callbacks. The codec - * probe takes the locks of both the codec itself and its - * parent, i.e. the PCI controller dev. Meanwhile, when - * the PCI controller is unbound, it takes its lock, too - * ==> ouch, a deadlock! - * As a workaround, we unlock temporarily here the controller - * device during cancel_work_sync() call. - */ - device_unlock(&pci->dev); cancel_delayed_work_sync(&hda->probe_work); - device_lock(&pci->dev); clear_bit(chip->dev_index, probed_devs); pci_set_drvdata(pci, NULL); -- cgit v1.2.3 From 472571498baaa67b6ea70d6c0154730be3da3c36 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Mon, 30 Mar 2026 13:41:31 +0800 Subject: ALSA: hda/cs8409: Fix error message in cs8409_i2c_bulk_read() The error message in cs8409_i2c_bulk_read() incorrectly says "I2C Bulk Write Failed" when it should say "I2C Bulk Read Failed". This is a copy-paste error from cs8409_i2c_bulk_write(). Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260330054131.434994-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/cirrus/cs8409.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index fad705092777..2d8f482e6474 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -268,7 +268,7 @@ static int cs8409_i2c_bulk_read(struct sub_codec *scodec, struct cs8409_i2c_para return 0; error: - codec_err(codec, "I2C Bulk Write Failed 0x%02x\n", scodec->addr); + codec_err(codec, "I2C Bulk Read Failed 0x%02x\n", scodec->addr); return -EIO; } -- cgit v1.2.3 From 4ec93f070eda6b765b62efcaed9241c3b3b0b6ad Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 30 Mar 2026 01:00:34 -0300 Subject: ALSA: aoa: i2sbus: fix OF node lifetime handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit i2sbus_add_dev() keeps the matched "sound" child pointer after for_each_child_of_node() has dropped the iterator reference. Take an extra reference before saving that node and drop it after the layout-id/device-id lookup is complete. The function also stores np in dev->sound.ofdev.dev.of_node without taking a reference for the embedded soundbus device. Since i2sbus overrides the embedded platform device release callback, balance that reference explicitly in the local error path and in i2sbus_release_dev(). Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260330-aoa-i2sbus-ofnode-lifetime-v1-1-51c309f4ff06@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/core.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sound/aoa/soundbus/i2sbus/core.c b/sound/aoa/soundbus/i2sbus/core.c index 22c956267f4e..833c44c0a950 100644 --- a/sound/aoa/soundbus/i2sbus/core.c +++ b/sound/aoa/soundbus/i2sbus/core.c @@ -84,6 +84,7 @@ static void i2sbus_release_dev(struct device *dev) for (i = aoa_resource_i2smmio; i <= aoa_resource_rxdbdma; i++) free_irq(i2sdev->interrupts[i], i2sdev); i2sbus_control_remove_dev(i2sdev->control, i2sdev); + of_node_put(i2sdev->sound.ofdev.dev.of_node); mutex_destroy(&i2sdev->lock); kfree(i2sdev); } @@ -147,7 +148,6 @@ static int i2sbus_get_and_fixup_rsrc(struct device_node *np, int index, } /* Returns 1 if added, 0 for otherwise; don't return a negative value! */ -/* FIXME: look at device node refcounting */ static int i2sbus_add_dev(struct macio_dev *macio, struct i2sbus_control *control, struct device_node *np) @@ -178,8 +178,9 @@ static int i2sbus_add_dev(struct macio_dev *macio, i = 0; for_each_child_of_node(np, child) { if (of_node_name_eq(child, "sound")) { + of_node_put(sound); i++; - sound = child; + sound = of_node_get(child); } } if (i == 1) { @@ -205,6 +206,7 @@ static int i2sbus_add_dev(struct macio_dev *macio, } } } + of_node_put(sound); /* for the time being, until we can handle non-layout-id * things in some fabric, refuse to attach if there is no * layout-id property or we haven't been forced to attach. @@ -219,7 +221,7 @@ static int i2sbus_add_dev(struct macio_dev *macio, mutex_init(&dev->lock); spin_lock_init(&dev->low_lock); dev->sound.ofdev.archdata.dma_mask = macio->ofdev.archdata.dma_mask; - dev->sound.ofdev.dev.of_node = np; + dev->sound.ofdev.dev.of_node = of_node_get(np); dev->sound.ofdev.dev.dma_mask = &dev->sound.ofdev.archdata.dma_mask; dev->sound.ofdev.dev.parent = &macio->ofdev.dev; dev->sound.ofdev.dev.release = i2sbus_release_dev; @@ -327,6 +329,7 @@ static int i2sbus_add_dev(struct macio_dev *macio, for (i=0;i<3;i++) release_and_free_resource(dev->allocated_resource[i]); mutex_destroy(&dev->lock); + of_node_put(dev->sound.ofdev.dev.of_node); kfree(dev); return 0; } -- cgit v1.2.3 From 6389dbd5c4a2d819ec342f89bd65883ab021278e Mon Sep 17 00:00:00 2001 From: Leonard Lausen Date: Fri, 27 Mar 2026 22:25:15 +0000 Subject: ALSA: hda: cs35l41: Fix boost type for HP Dragonfly 13.5 inch G4 The HP Dragonfly 13.5 inch G4 (SSID 103C8B63) has _DSD properties in ACPI firmware with valid reset-gpios and cs-gpios for the four CS35L41 amplifiers on SPI. However, the _DSD specifies cirrus,boost-type as Internal (0), while the hardware requires External Boost. With Internal Boost configured, the amplifiers trigger "Amp short error" when audio is played at moderate-to-high volume, eventually shutting down entirely. Add a configuration table entry to override the boost type to External, similar to the existing workaround for 103C89C6. All GPIO indices are set to -1 since the _DSD provides valid reset-gpios and cs-gpios. Confirmed on BIOS V90 01.11.00 (January 2026), the latest available. Link: https://bugzilla.kernel.org/show_bug.cgi?id=219520 Originally-by: Nicholas Wang Signed-off-by: Leonard Lausen Link: https://patch.msgid.link/db84dcf91bc8dbd217b35572b177d967655ff903@lausen.nl Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index 16d5ea77192f..732ae534db36 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -55,6 +55,11 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8A30", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8A31", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8A6E", 4, EXTERNAL, { CS35L41_LEFT, CS35L41_LEFT, CS35L41_RIGHT, CS35L41_RIGHT }, 0, -1, -1, 0, 0, 0 }, +/* + * Device 103C8B63 has _DSD with valid reset-gpios and cs-gpios, however the + * boost type is incorrectly set to Internal. Override to External Boost. + */ + { "103C8B63", 4, EXTERNAL, { CS35L41_RIGHT, CS35L41_LEFT, CS35L41_RIGHT, CS35L41_LEFT }, -1, -1, -1, 0, 0, 0 }, { "103C8BB3", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8BB4", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8BDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, @@ -475,6 +480,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8A30", generic_dsd_config }, { "CSC3551", "103C8A31", generic_dsd_config }, { "CSC3551", "103C8A6E", generic_dsd_config }, + { "CSC3551", "103C8B63", generic_dsd_config }, { "CSC3551", "103C8BB3", generic_dsd_config }, { "CSC3551", "103C8BB4", generic_dsd_config }, { "CSC3551", "103C8BDD", generic_dsd_config }, -- cgit v1.2.3 From 8dbbd39d0605b93a176f2c775dd2b6bb7c7a8adb Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Mon, 30 Mar 2026 14:46:17 +0100 Subject: ALSA: hda/realtek: Add support for HP Laptops Add support for HP Auster, Trekker and Agusta G7KX. Laptops use 2 CS35L41 Amps with HDA, using Internal boost, with I2C Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260330134651.443439-2-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 4c49f1195e1b..ededb650a235 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7130,6 +7130,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8e60, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e61, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e62, "HP Trekker ", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e75, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e8a, "HP NexusX", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8e9c, "HP 16 Clipper OmniBook X X360", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e9d, "HP 17 Turbine OmniBook X UMA", ALC287_FIXUP_CS35L41_I2C_2), @@ -7151,8 +7152,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8ee4, "HP Bantie A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8ee5, "HP Bantie A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), SND_PCI_QUIRK(0x103c, 0x8ee7, "HP Abe A6U", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), + SND_PCI_QUIRK(0x103c, 0x8f07, "HP Agusta G7KX", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8f0c, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8f0e, "HP ZBook X G2i 16W", ALC236_FIXUP_HP_GPIO_LED), + SND_PCI_QUIRK(0x103c, 0x8f2d, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8f2e, "HP Auster 14", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f41, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), -- cgit v1.2.3 From 66a6333ba5087b00b7d6cb9ff671f4e2739383b3 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Mon, 30 Mar 2026 14:46:18 +0100 Subject: ALSA: hda/realtek: Add support for ASUS 2026 Commercial laptops using CS35L41 HDA Add support for laptops: - ASUS PM5406CGA - ASUS PM5606CGA - ASUS P5406CCA - ASUS P5606CCA Laptops use 2 CS35L41 Amps with HDA, using Internal boost, with I2C or SPI. Signed-off-by: Stefan Binding Link: https://patch.msgid.link/20260330134651.443439-3-sbinding@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ededb650a235..9940fe7d5f9d 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7297,6 +7297,10 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x31e1, "ASUS B5605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x31f1, "ASUS B3605CCA", ALC294_FIXUP_ASUS_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3391, "ASUS PM3606CKA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3601, "ASUS PM5406CGA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3611, "ASUS PM5606CGA", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x3701, "ASUS P5406CCA", ALC245_FIXUP_CS35L41_SPI_2), + SND_PCI_QUIRK(0x1043, 0x3711, "ASUS P5606CCA", ALC245_FIXUP_CS35L41_SPI_2), SND_PCI_QUIRK(0x1043, 0x3a20, "ASUS G614JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), SND_PCI_QUIRK(0x1043, 0x3a30, "ASUS G814JVR/JIR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), SND_PCI_QUIRK(0x1043, 0x3a40, "ASUS G814JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), -- cgit v1.2.3 From c1258a2924d3a2453a6e7a6581acd8d6e5c6ba70 Mon Sep 17 00:00:00 2001 From: Lei Huang Date: Tue, 31 Mar 2026 10:40:36 +0800 Subject: ALSA: hda/realtek: fix bad indentation for alc269 Mention complains about this coding style: ERROR: code indent should use tabs where possible #6640: FILE: sound/hda/codecs/realtek/alc269.c:6640: + [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = {$ fix it up. Fixes: 5de5db35350d ("ALSA: hda/realtek - Enable Mute LED for Lenovo platform") Signed-off-by: Lei Huang Link: https://patch.msgid.link/20260331024036.30782-1-huanglei814@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 9940fe7d5f9d..cb7b2dd107b5 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -6591,10 +6591,10 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc288_fixup_surface_swap_dacs, }, - [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = { - .type = HDA_FIXUP_FUNC, - .v.func = alc233_fixup_lenovo_gpio2_mic_hotkey, - }, + [ALC233_FIXUP_LENOVO_GPIO2_MIC_HOTKEY] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc233_fixup_lenovo_gpio2_mic_hotkey, + }, [ALC245_FIXUP_BASS_HP_DAC] = { .type = HDA_FIXUP_FUNC, /* Borrow the DAC routing selected for those Thinkpads */ -- cgit v1.2.3 From d1888bf848ade6a9e71c7ba516fd215aa1bd8d65 Mon Sep 17 00:00:00 2001 From: Lei Huang Date: Tue, 31 Mar 2026 15:54:05 +0800 Subject: ALSA: hda/realtek: fix code style (ERROR: else should follow close brace '}') Fix checkpatch code style errors: ERROR: else should follow close brace '}' #2300: FILE: sound/hda/codecs/realtek/alc269.c:2300: + } + else Fixes: 31278997add6 ("ALSA: hda/realtek - Add headset quirk for Dell DT") Signed-off-by: Lei Huang Link: https://patch.msgid.link/20260331075405.78148-1-huanglei814@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index cb7b2dd107b5..bf837c6480f3 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2270,9 +2270,9 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, struct alc_spec *spec = codec->spec; spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; alc255_set_default_jack_type(codec); - } - else + } else { alc_fixup_headset_mode(codec, fix, action); + } } static void alc288_update_headset_jack_cb(struct hda_codec *codec, -- cgit v1.2.3 From 5ed060d5491597490fb53ec69da3edc4b1e8c165 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Tue, 31 Mar 2026 18:14:04 -0300 Subject: ALSA: aoa: i2sbus: clear stale prepared state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i2sbus PCM code uses pi->active to constrain the sibling stream to an already prepared duplex format and rate in i2sbus_pcm_open(). That state is set from i2sbus_pcm_prepare(), but the current code only clears it on close. As a result, the sibling stream can inherit stale constraints after the prepared state has been torn down. Clear pi->active when hw_params() or hw_free() tears down the prepared state, and set it again only after prepare succeeds. Replace the stale FIXME in the duplex constraint comment with a description of the current driver behavior: i2sbus still programs a single shared transport configuration for both directions, so mixed formats are not supported in duplex mode. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604010125.AvkWBYKI-lkp@intel.com/ Fixes: f3d9478b2ce4 ("[ALSA] snd-aoa: add snd-aoa") Cc: stable@vger.kernel.org Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260331-aoa-i2sbus-clear-stale-active-v2-1-3764ae2889a1@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/soundbus/i2sbus/pcm.c | 55 ++++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/sound/aoa/soundbus/i2sbus/pcm.c b/sound/aoa/soundbus/i2sbus/pcm.c index 97c807e67d56..63004ece94f9 100644 --- a/sound/aoa/soundbus/i2sbus/pcm.c +++ b/sound/aoa/soundbus/i2sbus/pcm.c @@ -165,17 +165,16 @@ static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in) * currently in use (if any). */ hw->rate_min = 5512; hw->rate_max = 192000; - /* if the other stream is active, then we can only - * support what it is currently using. - * FIXME: I lied. This comment is wrong. We can support - * anything that works with the same serial format, ie. - * when recording 24 bit sound we can well play 16 bit - * sound at the same time iff using the same transfer mode. + /* If the other stream is already prepared, keep this stream + * on the same duplex format and rate. + * + * i2sbus_pcm_prepare() still programs one shared transport + * configuration for both directions, so mixed duplex formats + * are not supported here. */ if (other->active) { - /* FIXME: is this guaranteed by the alsa api? */ hw->formats &= pcm_format_to_bits(i2sdev->format); - /* see above, restrict rates to the one we already have */ + /* Restrict rates to the one already in use. */ hw->rate_min = i2sdev->rate; hw->rate_max = i2sdev->rate; } @@ -283,6 +282,23 @@ void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev) } #endif +static void i2sbus_pcm_clear_active(struct i2sbus_dev *i2sdev, int in) +{ + struct pcm_info *pi; + + guard(mutex)(&i2sdev->lock); + + get_pcm_info(i2sdev, in, &pi, NULL); + pi->active = 0; +} + +static inline int i2sbus_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, int in) +{ + i2sbus_pcm_clear_active(snd_pcm_substream_chip(substream), in); + return 0; +} + static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) { struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream); @@ -291,14 +307,27 @@ static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in) get_pcm_info(i2sdev, in, &pi, NULL); if (pi->dbdma_ring.stopping) i2sbus_wait_for_stop(i2sdev, pi); + i2sbus_pcm_clear_active(i2sdev, in); return 0; } +static int i2sbus_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return i2sbus_hw_params(substream, params, 0); +} + static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream) { return i2sbus_hw_free(substream, 0); } +static int i2sbus_record_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return i2sbus_hw_params(substream, params, 1); +} + static int i2sbus_record_hw_free(struct snd_pcm_substream *substream) { return i2sbus_hw_free(substream, 1); @@ -335,7 +364,6 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) return -EINVAL; runtime = pi->substream->runtime; - pi->active = 1; if (other->active && ((i2sdev->format != runtime->format) || (i2sdev->rate != runtime->rate))) @@ -444,9 +472,11 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) /* early exit if already programmed correctly */ /* not locking these is fine since we touch them only in this function */ - if (in_le32(&i2sdev->intfregs->serial_format) == sfr - && in_le32(&i2sdev->intfregs->data_word_sizes) == dws) + if (in_le32(&i2sdev->intfregs->serial_format) == sfr && + in_le32(&i2sdev->intfregs->data_word_sizes) == dws) { + pi->active = 1; return 0; + } /* let's notify the codecs about clocks going away. * For now we only do mastering on the i2s cell... */ @@ -484,6 +514,7 @@ static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in) if (cii->codec->switch_clock) cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE); + pi->active = 1; return 0; } @@ -728,6 +759,7 @@ static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream static const struct snd_pcm_ops i2sbus_playback_ops = { .open = i2sbus_playback_open, .close = i2sbus_playback_close, + .hw_params = i2sbus_playback_hw_params, .hw_free = i2sbus_playback_hw_free, .prepare = i2sbus_playback_prepare, .trigger = i2sbus_playback_trigger, @@ -796,6 +828,7 @@ static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream static const struct snd_pcm_ops i2sbus_record_ops = { .open = i2sbus_record_open, .close = i2sbus_record_close, + .hw_params = i2sbus_record_hw_params, .hw_free = i2sbus_record_hw_free, .prepare = i2sbus_record_prepare, .trigger = i2sbus_record_trigger, -- cgit v1.2.3 From 579e7b820de5dd5124585413bb5e9c278d255436 Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Wed, 1 Apr 2026 16:26:25 +0800 Subject: ALSA: hda/cmedia: Remove duplicate pin configuration parsing The cmedia_probe() function calls snd_hda_parse_pin_defcfg() and snd_hda_gen_parse_auto_config() twice unnecessarily. Remove The duplicate code. Fixes: 0f1e8306dcbe ("ALSA: hda/cmedia: Rewrite to new probe method") Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260401082625.157868-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/cmedia.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sound/hda/codecs/cmedia.c b/sound/hda/codecs/cmedia.c index e6e12c01339f..88dd80d987d4 100644 --- a/sound/hda/codecs/cmedia.c +++ b/sound/hda/codecs/cmedia.c @@ -39,13 +39,6 @@ static int cmedia_probe(struct hda_codec *codec, const struct hda_device_id *id) spec->out_vol_mask = (1ULL << 0x10); } - err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); - if (err < 0) - goto error; - err = snd_hda_gen_parse_auto_config(codec, cfg); - if (err < 0) - goto error; - err = snd_hda_parse_pin_defcfg(codec, cfg, NULL, 0); if (err < 0) goto error; -- cgit v1.2.3 From c6cd83cceec5f2a1e2dd319d98640b1f0007d668 Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 1 Apr 2026 18:01:57 +0900 Subject: ALSA: ctxfi: Rename SPDIFI1 to SPDIFI_BAY Rename the SPDIFI1 enum value to SPDIFI_BAY to better reflect its purpose as the S/PDIF input on the internal drive bay, as opposed to the S/PDIF input via Flexijack or optical (SPDIFIO; not SPDIFI-zero). Signed-off-by: Harin Lee Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401090159.2404387-2-me@harin.net --- sound/pci/ctxfi/ctatc.c | 4 ++-- sound/pci/ctxfi/ctdaio.c | 6 +++--- sound/pci/ctxfi/ctdaio.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index da2667cb2489..9e0532fb33ff 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -1429,10 +1429,10 @@ static int atc_get_resources(struct ct_atc *atc) for (i = 0; i < NUM_DAIOTYP; i++) { if (((i == MIC) && !cap.dedicated_mic) || ((i == RCA) && !cap.dedicated_rca) || - i == SPDIFI1) + i == SPDIFI_BAY) continue; if (atc->model == CTSB073X && i == SPDIFIO) - da_desc.type = SPDIFI1; + da_desc.type = SPDIFI_BAY; else da_desc.type = i; da_desc.output = (i < LINEIM) || (i == RCA); diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 4dbb1dd7af32..128cf2f69ac1 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -35,7 +35,7 @@ static const struct daio_rsc_idx idx_20k1[NUM_DAIOTYP] = { [LINEIM] = {.left = 0x1b5, .right = 0x1bd}, [SPDIFOO] = {.left = 0x20, .right = 0x21}, [SPDIFIO] = {.left = 0x15, .right = 0x1d}, - [SPDIFI1] = {.left = 0x95, .right = 0x9d}, + [SPDIFI_BAY] = {.left = 0x95, .right = 0x9d}, }; static const struct daio_rsc_idx idx_20k2[NUM_DAIOTYP] = { @@ -106,7 +106,7 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw) switch (type) { case SPDIFOO: return 0; case SPDIFIO: return 0; - case SPDIFI1: return 1; + case SPDIFI_BAY: return 1; case LINEO1: return 4; case LINEO2: return 7; case LINEO3: return 5; @@ -120,7 +120,7 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw) switch (type) { case SPDIFOO: return 0; case SPDIFIO: return 0; - case SPDIFI1: return 1; + case SPDIFI_BAY: return 1; case LINEO1: return 4; case LINEO2: return 7; case LINEO3: return 5; diff --git a/sound/pci/ctxfi/ctdaio.h b/sound/pci/ctxfi/ctdaio.h index ff77d55539a5..c9f6207fe92f 100644 --- a/sound/pci/ctxfi/ctdaio.h +++ b/sound/pci/ctxfi/ctdaio.h @@ -32,7 +32,7 @@ enum DAIOTYP { SPDIFIO, /* S/PDIF In (Flexijack/Optical) on the card */ MIC, /* Dedicated mic on Titanium HD */ RCA, /* Dedicated RCA on SE-300PCIE */ - SPDIFI1, /* S/PDIF In on internal Drive Bay */ + SPDIFI_BAY, /* S/PDIF In on internal drive bay */ NUM_DAIOTYP }; -- cgit v1.2.3 From 07b116b44e52d78af40c2d39a8e1e34ef1283d0d Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 1 Apr 2026 18:01:58 +0900 Subject: ALSA: ctxfi: Use correct DAIO type for da_desc Skip the unused DAIO type per model (SPDIFIO on CTSB073X, SPDIFI_BAY on all others) and use the correct DAIO type directly as da_desc type. This removes the mismatch and misleading between the actual DAIO resource and the da_desc type like SPDIFI_BAY (formerly SPDIFI1). Update related functions accordingly, and drop the unreachable SPDIFI_BAY case from the hw20k2 daio_device_index(). Signed-off-by: Harin Lee Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401090159.2404387-3-me@harin.net --- sound/pci/ctxfi/ctatc.c | 21 ++++++++++++--------- sound/pci/ctxfi/ctdaio.c | 1 - 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index 9e0532fb33ff..7c2f896d531d 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -983,6 +983,11 @@ static int atc_select_mic_in(struct ct_atc *atc) return 0; } +static inline enum DAIOTYP atc_spdif_in_type(struct ct_atc *atc) +{ + return (atc->model == CTSB073X) ? SPDIFI_BAY : SPDIFIO; +} + static struct capabilities atc_capabilities(struct ct_atc *atc) { struct hw *hw = atc->hw; @@ -1121,7 +1126,7 @@ static int atc_spdif_out_unmute(struct ct_atc *atc, unsigned char state) static int atc_spdif_in_unmute(struct ct_atc *atc, unsigned char state) { - return atc_daio_unmute(atc, state, SPDIFIO); + return atc_daio_unmute(atc, state, atc_spdif_in_type(atc)); } static int atc_spdif_out_get_status(struct ct_atc *atc, unsigned int *status) @@ -1427,14 +1432,12 @@ static int atc_get_resources(struct ct_atc *atc) daio_mgr = (struct daio_mgr *)atc->rsc_mgrs[DAIO]; da_desc.msr = atc->msr; for (i = 0; i < NUM_DAIOTYP; i++) { - if (((i == MIC) && !cap.dedicated_mic) || - ((i == RCA) && !cap.dedicated_rca) || - i == SPDIFI_BAY) + if (((i == SPDIFIO) && (atc->model == CTSB073X)) || + ((i == SPDIFI_BAY) && (atc->model != CTSB073X)) || + ((i == MIC) && !cap.dedicated_mic) || + ((i == RCA) && !cap.dedicated_rca)) continue; - if (atc->model == CTSB073X && i == SPDIFIO) - da_desc.type = SPDIFI_BAY; - else - da_desc.type = i; + da_desc.type = i; da_desc.output = (i < LINEIM) || (i == RCA); err = daio_mgr->get_daio(daio_mgr, &da_desc, (struct daio **)&atc->daios[i]); @@ -1569,7 +1572,7 @@ static void atc_connect_resources(struct ct_atc *atc) mixer->set_input_right(mixer, MIX_MIC_IN, &src->rsc); } - dai = container_of(atc->daios[SPDIFIO], struct dai, daio); + dai = container_of(atc->daios[atc_spdif_in_type(atc)], struct dai, daio); atc_connect_dai(atc->rsc_mgrs[SRC], dai, (struct src **)&atc->srcs[0], (struct srcimp **)&atc->srcimps[0]); diff --git a/sound/pci/ctxfi/ctdaio.c b/sound/pci/ctxfi/ctdaio.c index 128cf2f69ac1..69aacd06716c 100644 --- a/sound/pci/ctxfi/ctdaio.c +++ b/sound/pci/ctxfi/ctdaio.c @@ -120,7 +120,6 @@ static int daio_device_index(enum DAIOTYP type, struct hw *hw) switch (type) { case SPDIFOO: return 0; case SPDIFIO: return 0; - case SPDIFI_BAY: return 1; case LINEO1: return 4; case LINEO2: return 7; case LINEO3: return 5; -- cgit v1.2.3 From 80449e1966cb9df57617a1d22bccd1e29cbc4222 Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Wed, 1 Apr 2026 18:01:59 +0900 Subject: ALSA: ctxfi: Precompute SRC allocation loop bound Replace the capability checks in the SRC and SRCIMP allocation loops with a precomputed loop bound. Cards with a dedicated mic input (SB1270, OK0010) allocate all NUM_ATC_SRCS entries, otherwise stop at 4. Signed-off-by: Harin Lee Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401090159.2404387-4-me@harin.net --- sound/pci/ctxfi/ctatc.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index 7c2f896d531d..516c0a12ed9f 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -1409,9 +1409,11 @@ static int atc_get_resources(struct ct_atc *atc) struct sum_desc sum_dsc = {0}; struct sum_mgr *sum_mgr; struct capabilities cap; + int atc_srcs_limit; int err, i; cap = atc->capabilities(atc); + atc_srcs_limit = cap.dedicated_mic ? NUM_ATC_SRCS : 4; atc->daios = kcalloc(NUM_DAIOTYP, sizeof(void *), GFP_KERNEL); if (!atc->daios) @@ -1453,9 +1455,7 @@ static int atc_get_resources(struct ct_atc *atc) src_dsc.multi = 1; src_dsc.msr = atc->msr; src_dsc.mode = ARCRW; - for (i = 0; i < NUM_ATC_SRCS; i++) { - if (((i > 3) && !cap.dedicated_mic)) - continue; + for (i = 0; i < atc_srcs_limit; i++) { err = src_mgr->get_src(src_mgr, &src_dsc, (struct src **)&atc->srcs[i]); if (err) @@ -1464,9 +1464,7 @@ static int atc_get_resources(struct ct_atc *atc) srcimp_mgr = atc->rsc_mgrs[SRCIMP]; srcimp_dsc.msr = 8; - for (i = 0; i < NUM_ATC_SRCS; i++) { - if (((i > 3) && !cap.dedicated_mic)) - continue; + for (i = 0; i < atc_srcs_limit; i++) { err = srcimp_mgr->get_srcimp(srcimp_mgr, &srcimp_dsc, (struct srcimp **)&atc->srcimps[i]); if (err) -- cgit v1.2.3 From 872c7433582a3570dd0c827967ba291450096bf0 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Wed, 1 Apr 2026 08:45:37 -0300 Subject: ALSA: es1688: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ISA ES1688 driver still carries a disabled suspend/resume block in its isa_driver definition, while the same file already provides minimal power-management handling for the PnP ES968 path. Add ISA-specific PM callbacks and factor the existing ES1688 suspend and resume sequence into common card-level helpers shared by both probe paths. Suspend moves the card to D3hot. Resume reinitializes the chip with snd_es1688_reset() and restores the card to D0, propagating reset failures to the caller. This wires up power-management callbacks for the ISA path and keeps the PM handling consistent between the ISA and PnP probe paths. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260401-alsa-es1688-pm-v1-1-510767628fe6@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/es1688/es1688.c | 50 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c index 6a95dfb7600a..7255b34f9148 100644 --- a/sound/isa/es1688/es1688.c +++ b/sound/isa/es1688/es1688.c @@ -184,12 +184,44 @@ static int snd_es1688_isa_probe(struct device *dev, unsigned int n) return 0; } +#ifdef CONFIG_PM +static int snd_es1688_card_suspend(struct snd_card *card) +{ + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int snd_es1688_card_resume(struct snd_card *card) +{ + struct snd_es1688 *chip = card->private_data; + int err; + + err = snd_es1688_reset(chip); + if (err < 0) + return err; + + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_es1688_isa_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_es1688_card_suspend(dev_get_drvdata(dev)); +} + +static int snd_es1688_isa_resume(struct device *dev, unsigned int n) +{ + return snd_es1688_card_resume(dev_get_drvdata(dev)); +} +#endif + static struct isa_driver snd_es1688_driver = { .match = snd_es1688_match, .probe = snd_es1688_isa_probe, -#if 0 /* FIXME */ - .suspend = snd_es1688_suspend, - .resume = snd_es1688_resume, +#ifdef CONFIG_PM + .suspend = snd_es1688_isa_suspend, + .resume = snd_es1688_isa_resume, #endif .driver = { .name = DEV_NAME @@ -266,20 +298,12 @@ static void snd_es968_pnp_remove(struct pnp_card_link *pcard) static int snd_es968_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) { - struct snd_card *card = pnp_get_card_drvdata(pcard); - - snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); - return 0; + return snd_es1688_card_suspend(pnp_get_card_drvdata(pcard)); } static int snd_es968_pnp_resume(struct pnp_card_link *pcard) { - struct snd_card *card = pnp_get_card_drvdata(pcard); - struct snd_es1688 *chip = card->private_data; - - snd_es1688_reset(chip); - snd_power_change_state(card, SNDRV_CTL_POWER_D0); - return 0; + return snd_es1688_card_resume(pnp_get_card_drvdata(pcard)); } #endif -- cgit v1.2.3 From cf6c18cf83e48986ac40a053d09d3c33624135f6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 1 Apr 2026 17:57:34 +0100 Subject: ALSA: compress: Refuse to update timestamps for unconfigured streams There are a number of mechanisms, including the userspace accessible timestamp and buffer availability ioctl()s, which allow us to trigger a timestamp update on a stream before it has been configured. Since drivers might rely on stream configuration for reporting of pcm_io_frames, including potentially doing a division by the number of channels, and these operations are not meaningful for an unconfigured stream reject attempts to read timestamps before any configuration is done. Signed-off-by: Mark Brown Acked-by: Vinod Koul Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401-alsa-unconfigured-tstamp-v1-1-694c2cb5f71d@kernel.org --- sound/core/compress_offload.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index 5a0308eb4e31..db9f516df842 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -185,6 +185,14 @@ static int snd_compr_update_tstamp(struct snd_compr_stream *stream, { if (!stream->ops->pointer) return -ENOTSUPP; + + switch (stream->runtime->state) { + case SNDRV_PCM_STATE_OPEN: + return -EBADFD; + default: + break; + } + stream->ops->pointer(stream, tstamp); pr_debug("dsp consumed till %u total %llu bytes\n", tstamp->byte_offset, tstamp->copied_total); -- cgit v1.2.3 From 61327f3d817cb5820559ad4f8d0d9abed3d379b1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 1 Apr 2026 17:57:35 +0100 Subject: ALSA: compress: Pay attention if drivers error out retrieving pointers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we have a return code on the driver pointer operation but the core ignores that. Let's start paying attention. Reported-by: Péter Ujfalusi Signed-off-by: Mark Brown Acked-by: Vinod Koul Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260401-alsa-unconfigured-tstamp-v1-2-694c2cb5f71d@kernel.org --- sound/core/compress_offload.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/compress_offload.c b/sound/core/compress_offload.c index db9f516df842..fd63d219bf86 100644 --- a/sound/core/compress_offload.c +++ b/sound/core/compress_offload.c @@ -183,6 +183,8 @@ snd_compr_tstamp32_from_64(struct snd_compr_tstamp *tstamp32, static int snd_compr_update_tstamp(struct snd_compr_stream *stream, struct snd_compr_tstamp64 *tstamp) { + int ret; + if (!stream->ops->pointer) return -ENOTSUPP; @@ -193,7 +195,9 @@ static int snd_compr_update_tstamp(struct snd_compr_stream *stream, break; } - stream->ops->pointer(stream, tstamp); + ret = stream->ops->pointer(stream, tstamp); + if (ret != 0) + return ret; pr_debug("dsp consumed till %u total %llu bytes\n", tstamp->byte_offset, tstamp->copied_total); if (stream->direction == SND_COMPRESS_PLAYBACK) -- cgit v1.2.3 From 1558905669e4da922fbaa7cf6507eb14779bffbd Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Thu, 2 Apr 2026 10:36:04 +0800 Subject: ALSA: aoa/tas: Fix OF node leak on probe failure Add missing of_node_put() in the error path. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260402023604.54682-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/aoa/codecs/tas.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/aoa/codecs/tas.c b/sound/aoa/codecs/tas.c index 13da2b159ad0..25214d3da65d 100644 --- a/sound/aoa/codecs/tas.c +++ b/sound/aoa/codecs/tas.c @@ -872,6 +872,7 @@ static int tas_i2c_probe(struct i2c_client *client) return 0; fail: mutex_destroy(&tas->mtx); + of_node_put(tas->codec.node); kfree(tas); return -EINVAL; } -- cgit v1.2.3 From 4513d3e0bbc0585b86ccf2631902593ff97e88f5 Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Thu, 2 Apr 2026 13:36:57 +0800 Subject: ALSA: usb-audio: apply quirk for MOONDROP JU Jiu It(ID 31b2:0111 JU Jiu) reports a MIN value -12800 for volume control, but will mute when setting it less than -10880. Thanks to my girlfriend Kagura for reporting this issue. Cc: Kagura Cc: stable@vger.kernel.org Signed-off-by: Cryolitia PukNgae Link: https://patch.msgid.link/20260402-syy-v1-1-068d3bc30ddc@linux.dev Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index 69026cf54979..a25e8145af67 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1204,6 +1204,13 @@ static void volume_control_quirks(struct usb_mixer_elem_info *cval, cval->min = -11264; /* Mute under it */ } break; + case USB_ID(0x31b2, 0x0111): /* MOONDROP JU Jiu */ + if (!strcmp(kctl->id.name, "PCM Playback Volume")) { + usb_audio_info(chip, + "set volume quirk for MOONDROP JU Jiu\n"); + cval->min = -10880; /* Mute under it */ + } + break; } } -- cgit v1.2.3 From e5d5aef802a5f41283084f7d443ef4fd4b65d86d Mon Sep 17 00:00:00 2001 From: wangdicheng Date: Fri, 3 Apr 2026 09:47:36 +0800 Subject: ALSA: aoa/onyx: Fix OF node leak on probe failure Add missing of_node_put() in the error path. Signed-off-by: wangdicheng Link: https://patch.msgid.link/20260403014736.33014-1-wangdich9700@163.com Signed-off-by: Takashi Iwai --- sound/aoa/codecs/onyx.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index 04961c456d2c..da0eebf5dfbc 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -980,10 +980,12 @@ static int onyx_i2c_probe(struct i2c_client *client) onyx->codec.node = of_node_get(node); if (aoa_codec_register(&onyx->codec)) { - goto fail; + goto fail_put; } printk(KERN_DEBUG PFX "created and attached onyx instance\n"); return 0; + fail_put: + of_node_put(onyx->codec.node); fail: kfree(onyx); return -ENODEV; -- cgit v1.2.3 From 6692ed9b4ced29aa819c95cc4ad9e2dc8720c081 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Fri, 3 Apr 2026 00:21:34 -0300 Subject: ALSA: hda: Notify IEC958 Default PCM switch state changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "IEC958 Default PCM Playback Switch" control is backed directly by mout->share_spdif. The share-switch callbacks currently access that state without serialization, and spdif_share_sw_put() always returns 0, so normal userspace writes never emit the standard ALSA control value notification. snd_hda_multi_out_analog_open() may also clear mout->share_spdif when the analog PCM capabilities and the SPDIF capabilities no longer intersect. That fallback is still needed to avoid creating an impossible hw constraint set, but it changes the mixer backing value without notifying subscribers. Protect the share-switch callbacks with spdif_mutex like the other SPDIF control handlers, return the actual change value from spdif_share_sw_put(), and notify the cached control when the open path forcibly disables shared SPDIF mode after dropping spdif_mutex. This keeps the existing auto-disable behavior while making switch state changes visible to userspace. Fixes: 9a08160bdbe3 ("[ALSA] hda-codec - Add "IEC958 Default PCM" switch") Fixes: 022b466fc353 ("ALSA: hda - Avoid invalid formats and rates with shared SPDIF") Suggested-by: Takashi Iwai Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260403-hda-spdif-share-notify-v3-1-4eb1356b0f17@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/common/codec.c | 46 ++++++++++++++++++++++++++++++++++++-------- sound/hda/common/hda_local.h | 1 + 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 09b1329bb8f3..5123df32ad89 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -2529,7 +2529,10 @@ EXPORT_SYMBOL_GPL(snd_hda_spdif_ctls_assign); static int spdif_share_sw_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_multi_out *mout = (void *)kcontrol->private_value; + + guard(mutex)(&codec->spdif_mutex); ucontrol->value.integer.value[0] = mout->share_spdif; return 0; } @@ -2537,9 +2540,15 @@ static int spdif_share_sw_get(struct snd_kcontrol *kcontrol, static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); - mout->share_spdif = !!ucontrol->value.integer.value[0]; - return 0; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_multi_out *mout = (void *)kcontrol->private_value; + bool val = !!ucontrol->value.integer.value[0]; + int change; + + guard(mutex)(&codec->spdif_mutex); + change = mout->share_spdif != val; + mout->share_spdif = val; + return change; } static const struct snd_kcontrol_new spdif_share_sw = { @@ -2550,6 +2559,14 @@ static const struct snd_kcontrol_new spdif_share_sw = { .put = spdif_share_sw_put, }; +static void notify_spdif_share_sw(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + if (mout->share_spdif_kctl) + snd_ctl_notify_one(codec->card, SNDRV_CTL_EVENT_MASK_VALUE, + mout->share_spdif_kctl, 0); +} + /** * snd_hda_create_spdif_share_sw - create Default PCM switch * @codec: the HDA codec @@ -2559,15 +2576,24 @@ int snd_hda_create_spdif_share_sw(struct hda_codec *codec, struct hda_multi_out *mout) { struct snd_kcontrol *kctl; + int err; if (!mout->dig_out_nid) return 0; - kctl = snd_ctl_new1(&spdif_share_sw, mout); + kctl = snd_ctl_new1(&spdif_share_sw, codec); if (!kctl) return -ENOMEM; - /* ATTENTION: here mout is passed as private_data, instead of codec */ - return snd_hda_ctl_add(codec, mout->dig_out_nid, kctl); + /* snd_ctl_new1() stores @codec in private_data; stash @mout in + * private_value for the share-switch callbacks and cache the + * assigned control for forced-disable notifications. + */ + kctl->private_value = (unsigned long)mout; + err = snd_hda_ctl_add(codec, mout->dig_out_nid, kctl); + if (err < 0) + return err; + mout->share_spdif_kctl = kctl; + return 0; } EXPORT_SYMBOL_GPL(snd_hda_create_spdif_share_sw); @@ -3701,6 +3727,8 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_pcm_stream *hinfo) { struct snd_pcm_runtime *runtime = substream->runtime; + bool notify_share_sw = false; + runtime->hw.channels_max = mout->max_channels; if (mout->dig_out_nid) { if (!mout->analog_rates) { @@ -3729,10 +3757,12 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, hinfo->maxbps = mout->spdif_maxbps; } else { mout->share_spdif = 0; - /* FIXME: need notify? */ + notify_share_sw = true; } } } + if (notify_share_sw) + notify_spdif_share_sw(codec, mout); return snd_pcm_hw_constraint_step(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, 2); } diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h index ab423f1cef54..98b2c4acebc2 100644 --- a/sound/hda/common/hda_local.h +++ b/sound/hda/common/hda_local.h @@ -221,6 +221,7 @@ struct hda_multi_out { unsigned int spdif_rates; unsigned int spdif_maxbps; u64 spdif_formats; + struct snd_kcontrol *share_spdif_kctl; /* cached shared SPDIF switch */ }; int snd_hda_create_spdif_share_sw(struct hda_codec *codec, -- cgit v1.2.3 From 9551af27f8167bbb5f862a12f7f9bc5830e8f4e1 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Fri, 3 Apr 2026 00:47:13 -0300 Subject: ALSA: aoa: onyx: Update IEC958 sample-rate status for PCM playback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onyx_prepare() accepts 32/44.1/48 kHz PCM playback, but it leaves the Onyx IEC958 sample-rate status bits at the driver's initial 44.1 kHz setting in DIG_INFO3. As a result, 32 kHz and 48 kHz PCM streams advertise a stale IEC958 sample rate unless userspace rewrites IEC958 Playback Default first. Update only the consumer sample-frequency bits in DIG_INFO3 from the PCM runtime during prepare, resolving the long-standing FIXME in the PCM playback path while leaving the other user-controlled IEC958 status bits unchanged. Mark IEC958 Playback Default as volatile as well, since prepare() now changes the exposed register contents outside the control put callback. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260403-onyx-spdif-pcm-rate-v1-1-dcfaf931cf83@gmail.com Signed-off-by: Takashi Iwai --- sound/aoa/codecs/onyx.c | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/sound/aoa/codecs/onyx.c b/sound/aoa/codecs/onyx.c index da0eebf5dfbc..4fb593e88fb1 100644 --- a/sound/aoa/codecs/onyx.c +++ b/sound/aoa/codecs/onyx.c @@ -32,6 +32,7 @@ #include #include #include +#include MODULE_AUTHOR("Johannes Berg "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa"); @@ -514,8 +515,36 @@ static int onyx_spdif_put(struct snd_kcontrol *kcontrol, return 1; } +static int onyx_set_spdif_pcm_rate(struct onyx *onyx, unsigned int rate) +{ + u8 dig_info3, fs; + + switch (rate) { + case 32000: + fs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + fs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + fs = IEC958_AES3_CON_FS_48000; + break; + default: + return -EINVAL; + } + + if (onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &dig_info3)) + return -EBUSY; + dig_info3 = (dig_info3 & ~IEC958_AES3_CON_FS) | fs; + if (onyx_write_register(onyx, ONYX_REG_DIG_INFO3, dig_info3)) + return -EBUSY; + + return 0; +} + static const struct snd_kcontrol_new onyx_spdif_ctrl = { - .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, .iface = SNDRV_CTL_ELEM_IFACE_PCM, .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT), .info = onyx_spdif_info, @@ -695,9 +724,9 @@ static int onyx_prepare(struct codec_info_item *cii, case 32000: case 44100: case 48000: - /* these rates are ok for all outputs */ - /* FIXME: program spdif channel control bits here so that - * userspace doesn't have to if it only plays pcm! */ + if (onyx->codec.connected & 2) + return onyx_set_spdif_pcm_rate(onyx, + substream->runtime->rate); return 0; default: /* got some rate that the digital output can't do, -- cgit v1.2.3 From 7b5b7d04498d84bc7abc05b4e09a0d17c820d3b0 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Sun, 5 Apr 2026 09:42:08 +0800 Subject: ALSA: hda/realtek: Fix code style error Output of checkpatch shows error: ERROR: else should follow close brace '}' 2168: FILE: sound/hda/codecs/realtek/realtek.c:2168: + } + else So fix it. Signed-off-by: songxiebing Link: https://patch.msgid.link/20260405014208.167364-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/realtek.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index aad265c0a5b6..b240f13b0438 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -2164,8 +2164,7 @@ void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec, if (action == HDA_FIXUP_ACT_PRE_PROBE) { struct alc_spec *spec = codec->spec; spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; - } - else + } else alc_fixup_headset_mode(codec, fix, action); } EXPORT_SYMBOL_NS_GPL(alc_fixup_headset_mode_no_hp_mic, "SND_HDA_CODEC_REALTEK"); -- cgit v1.2.3 From 2428cd6e8b6fa80c36db4652702ca0acd2ce3f08 Mon Sep 17 00:00:00 2001 From: Panagiotis Petrakopoulos Date: Mon, 6 Apr 2026 01:25:48 +0300 Subject: ALSA: scarlett2: Add missing sentinel initializer field A "-Wmissing-field-initializers" warning was emitted when compiling the module using the W=2 option. There is a sentinel initializer field missing in the end of scarlett2_devices[]. Tested using a Scarlett Solo 4th gen. Fixes: d98cc489029d ("ALSA: scarlett2: Move USB IDs out from device_info struct") Signed-off-by: Panagiotis Petrakopoulos Link: https://patch.msgid.link/20260405222548.8903-1-npetrakopoulos2003@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_scarlett2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index fd1fb668929a..8eaa96222759 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -2262,7 +2262,7 @@ static const struct scarlett2_device_entry scarlett2_devices[] = { { USB_ID(0x1235, 0x820c), &clarett_8pre_info, "Clarett+" }, /* End of list */ - { 0, NULL }, + { 0, NULL, NULL }, }; /* get the starting port index number for a given port type/direction */ -- cgit v1.2.3 From e9418da50d9e5c496c22fe392e4ad74c038a94eb Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Mon, 6 Apr 2026 16:48:57 +0900 Subject: ALSA: ctxfi: Limit PTP to a single page Commit 391e69143d0a increased CT_PTP_NUM from 1 to 4 to support 256 playback streams, but the additional pages are not used by the card correctly. The CT20K2 hardware already has multiple VMEM_PTPAL registers, but using them separately would require refactoring the entire virtual memory allocation logic. ct_vm_map() always uses PTEs in vm->ptp[0].area regardless of CT_PTP_NUM. On AMD64 systems, a single PTP covers 512 PTEs (2M). When aggregate memory allocations exceed this limit, ct_vm_map() tries to access beyond the allocated space and causes a page fault: BUG: unable to handle page fault for address: ffffd4ae8a10a000 Oops: Oops: 0002 [#1] SMP PTI RIP: 0010:ct_vm_map+0x17c/0x280 [snd_ctxfi] Call Trace: atc_pcm_playback_prepare+0x225/0x3b0 ct_pcm_playback_prepare+0x38/0x60 snd_pcm_do_prepare+0x2f/0x50 snd_pcm_action_single+0x36/0x90 snd_pcm_action_nonatomic+0xbf/0xd0 snd_pcm_ioctl+0x28/0x40 __x64_sys_ioctl+0x97/0xe0 do_syscall_64+0x81/0x610 entry_SYSCALL_64_after_hwframe+0x76/0x7e Revert CT_PTP_NUM to 1. The 256 SRC_RESOURCE_NUM and playback_count remain unchanged. Fixes: 391e69143d0a ("ALSA: ctxfi: Bump playback substreams to 256") Cc: stable@vger.kernel.org Signed-off-by: Harin Lee Link: https://patch.msgid.link/20260406074857.216034-1-me@harin.net Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/ctvmem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/pci/ctxfi/ctvmem.h b/sound/pci/ctxfi/ctvmem.h index da54cbcdb0be..43a0065b40c3 100644 --- a/sound/pci/ctxfi/ctvmem.h +++ b/sound/pci/ctxfi/ctvmem.h @@ -15,7 +15,7 @@ #ifndef CTVMEM_H #define CTVMEM_H -#define CT_PTP_NUM 4 /* num of device page table pages */ +#define CT_PTP_NUM 1 /* num of device page table pages */ #include #include -- cgit v1.2.3 From 7d61662197ecdc458e33e475b6ada7f6da61d364 Mon Sep 17 00:00:00 2001 From: Harin Lee Date: Mon, 6 Apr 2026 16:49:13 +0900 Subject: ALSA: ctxfi: Add fallback to default RSR for S/PDIF spdif_passthru_playback_get_resources() uses atc->pll_rate as the RSR for the MSR calculation loop. However, pll_rate is only updated in atc_pll_init() and not in hw_pll_init(), so it remains 0 after the card init. When spdif_passthru_playback_setup() skips atc_pll_init() for 32000 Hz, (rsr * desc.msr) always becomes 0, causing the loop to spin indefinitely. Add fallback to use atc->rsr when atc->pll_rate is 0. This reflects the hardware state, since hw_card_init() already configures the PLL to the default RSR. Fixes: 8cc72361481f ("ALSA: SB X-Fi driver merge") Cc: stable@vger.kernel.org Signed-off-by: Harin Lee Link: https://patch.msgid.link/20260406074913.217374-1-me@harin.net Signed-off-by: Takashi Iwai --- sound/pci/ctxfi/ctatc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index 516c0a12ed9f..02fe09330939 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -794,7 +794,8 @@ static int spdif_passthru_playback_get_resources(struct ct_atc *atc, struct src *src; int err; int n_amixer = apcm->substream->runtime->channels, i; - unsigned int pitch, rsr = atc->pll_rate; + unsigned int pitch; + unsigned int rsr = atc->pll_rate ? atc->pll_rate : atc->rsr; /* first release old resources */ atc_pcm_release_resources(atc, apcm); -- cgit v1.2.3 From d38e9457ddf0780dd55c953886ae48abbe4d33b8 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 6 Apr 2026 00:20:03 -0300 Subject: ALSA: gus: add shared GF1 suspend and resume helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusclassic and gusextreme still leave their ISA PM callbacks disabled because the shared GF1 core only provides probe-time startup and full shutdown paths. Those helpers are not suitable for suspend and resume. They reset software handlers and tear down runtime state such as the DRAM allocator, timer state, DMA queues, PCM state and UART setup. Resume instead needs a narrower recovery path that rebuilds the GF1 hardware state without rerunning probe-only detection or discarding the bookkeeping kept by the card instance. Add shared GF1 suspend and resume helpers for that recovery path. Suspend now quiesces GF1 PCM, aborts queued GF1 DMA work, resets the UART and powers the chip down without tearing down allocator, timer or rawmidi bookkeeping. Resume rebuilds the GF1 hardware state, restores timer and UART handlers, and brings the chip back to a usable post-resume state for the ISA front-ends. The scope is limited to restoring post-resume usability. It does not attempt transparent continuation of active GF1 PCM or synth state across suspend, and userspace may still need to reprepare streams or reload onboard sample data after resume. Open rawmidi substreams are restored only to a usable post-resume state. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-1-b6829a7457cd@gmail.com --- include/sound/gus.h | 8 ++++++ sound/isa/gus/gus_dma.c | 33 +++++++++++++++++++++++++ sound/isa/gus/gus_main.c | 36 +++++++++++++++++++++++++++ sound/isa/gus/gus_pcm.c | 7 +++--- sound/isa/gus/gus_reset.c | 62 +++++++++++++++++++++++++++++++++++++++-------- sound/isa/gus/gus_timer.c | 14 +++++++++++ sound/isa/gus/gus_uart.c | 47 +++++++++++++++++++++++++++++++++++ 7 files changed, 194 insertions(+), 13 deletions(-) diff --git a/include/sound/gus.h b/include/sound/gus.h index 321ae93625eb..3feb42627de1 100644 --- a/include/sound/gus.h +++ b/include/sound/gus.h @@ -536,6 +536,7 @@ int snd_gf1_dma_transfer_block(struct snd_gus_card * gus, struct snd_gf1_dma_block * block, int atomic, int synth); +void snd_gf1_dma_suspend(struct snd_gus_card *gus); /* gus_volume.c */ @@ -552,6 +553,8 @@ struct snd_gus_voice *snd_gf1_alloc_voice(struct snd_gus_card * gus, int type, i void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice); int snd_gf1_start(struct snd_gus_card * gus); int snd_gf1_stop(struct snd_gus_card * gus); +int snd_gf1_suspend(struct snd_gus_card *gus); +int snd_gf1_resume(struct snd_gus_card *gus); /* gus_mixer.c */ @@ -572,6 +575,8 @@ int snd_gus_create(struct snd_card *card, int effect, struct snd_gus_card ** rgus); int snd_gus_initialize(struct snd_gus_card * gus); +int snd_gus_suspend(struct snd_gus_card *gus); +int snd_gus_resume(struct snd_gus_card *gus); /* gus_irq.c */ @@ -583,6 +588,8 @@ void snd_gus_irq_profile_init(struct snd_gus_card *gus); /* gus_uart.c */ int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device); +void snd_gf1_uart_suspend(struct snd_gus_card *gus); +void snd_gf1_uart_resume(struct snd_gus_card *gus); /* gus_dram.c */ int snd_gus_dram_write(struct snd_gus_card *gus, char __user *ptr, @@ -593,5 +600,6 @@ int snd_gus_dram_read(struct snd_gus_card *gus, char __user *ptr, /* gus_timer.c */ void snd_gf1_timers_init(struct snd_gus_card *gus); void snd_gf1_timers_done(struct snd_gus_card *gus); +void snd_gf1_timers_resume(struct snd_gus_card *gus); #endif /* __SOUND_GUS_H */ diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c index ffc69e26227e..30bd76eee96e 100644 --- a/sound/isa/gus/gus_dma.c +++ b/sound/isa/gus/gus_dma.c @@ -173,6 +173,39 @@ int snd_gf1_dma_done(struct snd_gus_card * gus) return 0; } +void snd_gf1_dma_suspend(struct snd_gus_card *gus) +{ + struct snd_gf1_dma_block *block; + + guard(mutex)(&gus->dma_mutex); + if (!gus->gf1.dma_shared) + return; + + snd_dma_disable(gus->gf1.dma1); + snd_gf1_dma_ack(gus); + if (gus->gf1.dma_ack) + gus->gf1.dma_ack(gus, gus->gf1.dma_private_data); + gus->gf1.dma_ack = NULL; + gus->gf1.dma_private_data = NULL; + + while ((block = gus->gf1.dma_data_pcm)) { + gus->gf1.dma_data_pcm = block->next; + if (block->ack) + block->ack(gus, block->private_data); + kfree(block); + } + while ((block = gus->gf1.dma_data_synth)) { + gus->gf1.dma_data_synth = block->next; + if (block->ack) + block->ack(gus, block->private_data); + kfree(block); + } + + gus->gf1.dma_data_pcm_last = NULL; + gus->gf1.dma_data_synth_last = NULL; + gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER; +} + int snd_gf1_dma_transfer_block(struct snd_gus_card * gus, struct snd_gf1_dma_block * __block, int atomic, diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c index b2b189c83569..6adf8b698e2b 100644 --- a/sound/isa/gus/gus_main.c +++ b/sound/isa/gus/gus_main.c @@ -404,6 +404,42 @@ int snd_gus_initialize(struct snd_gus_card *gus) return 0; } +int snd_gus_suspend(struct snd_gus_card *gus) +{ + int err; + + if (gus->pcm) { + err = snd_pcm_suspend_all(gus->pcm); + if (err < 0) + return err; + } + + err = snd_gf1_suspend(gus); + if (err < 0) + return err; + + snd_power_change_state(gus->card, SNDRV_CTL_POWER_D3hot); + return 0; +} +EXPORT_SYMBOL(snd_gus_suspend); + +int snd_gus_resume(struct snd_gus_card *gus) +{ + int err; + + err = snd_gus_init_dma_irq(gus, 1); + if (err < 0) + return err; + + err = snd_gf1_resume(gus); + if (err < 0) + return err; + + snd_power_change_state(gus->card, SNDRV_CTL_POWER_D0); + return 0; +} +EXPORT_SYMBOL(snd_gus_resume); + /* gus_io.c */ EXPORT_SYMBOL(snd_gf1_delay); EXPORT_SYMBOL(snd_gf1_write8); diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c index caf371897b78..a0757e1ede46 100644 --- a/sound/isa/gus/gus_pcm.c +++ b/sound/isa/gus/gus_pcm.c @@ -471,7 +471,8 @@ static int snd_gf1_pcm_playback_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_START) { snd_gf1_pcm_trigger_up(substream); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + } else if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { scoped_guard(spinlock, &pcmp->lock) { pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE; } @@ -558,7 +559,8 @@ static int snd_gf1_pcm_capture_trigger(struct snd_pcm_substream *substream, if (cmd == SNDRV_PCM_TRIGGER_START) { val = gus->gf1.pcm_rcntrl_reg; - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + } else if (cmd == SNDRV_PCM_TRIGGER_STOP || + cmd == SNDRV_PCM_TRIGGER_SUSPEND) { val = 0; } else { return -EINVAL; @@ -856,4 +858,3 @@ int snd_gf1_pcm_new(struct snd_gus_card *gus, int pcm_dev, int control_index) return 0; } - diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c index a7a3e764bb77..998fa245708c 100644 --- a/sound/isa/gus/gus_reset.c +++ b/sound/isa/gus/gus_reset.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -263,11 +264,18 @@ void snd_gf1_free_voice(struct snd_gus_card * gus, struct snd_gus_voice *voice) private_free(voice); } -/* - * call this function only by start of driver - */ +static void snd_gf1_init_software_state(struct snd_gus_card *gus) +{ + unsigned int i; -int snd_gf1_start(struct snd_gus_card * gus) + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL); + for (i = 0; i < 32; i++) { + gus->gf1.voices[i].number = i; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i); + } +} + +static void snd_gf1_hw_start(struct snd_gus_card *gus, bool initial) { unsigned int i; @@ -277,14 +285,14 @@ int snd_gf1_start(struct snd_gus_card * gus) udelay(160); snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); - snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL); - for (i = 0; i < 32; i++) { - gus->gf1.voices[i].number = i; - snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i); + if (initial) { + snd_gf1_init_software_state(gus); + snd_gf1_uart_cmd(gus, 0x03); + } else { + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + outb(0x03, GUSP(gus, MIDICTRL)); } - snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */ - if (gus->gf1.enh_mode) { /* enhanced mode !!!! */ snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); @@ -293,6 +301,8 @@ int snd_gf1_start(struct snd_gus_card * gus) snd_gf1_select_active_voices(gus); snd_gf1_delay(gus); gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8; + gus->gf1.hw_lfo = 0; + gus->gf1.sw_lfo = 0; /* initialize LFOs & clear LFOs memory */ if (gus->gf1.enh_mode && gus->gf1.memory) { gus->gf1.hw_lfo = 1; @@ -321,7 +331,15 @@ int snd_gf1_start(struct snd_gus_card * gus) outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); } +} +int snd_gf1_start(struct snd_gus_card *gus) +{ + /* + * Probe-time startup initializes both GF1 hardware and the + * software state that suspend/resume keeps across PM cycles. + */ + snd_gf1_hw_start(gus, true); snd_gf1_timers_init(gus); snd_gf1_look_regs(gus); snd_gf1_mem_init(gus); @@ -357,3 +375,27 @@ int snd_gf1_stop(struct snd_gus_card * gus) return 0; } + +int snd_gf1_suspend(struct snd_gus_card *gus) +{ + snd_gf1_dma_suspend(gus); + snd_gf1_uart_suspend(gus); + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); + snd_gf1_stop_voices(gus, 0, 31); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); + snd_dma_disable(gus->gf1.dma2); + + return 0; +} + +int snd_gf1_resume(struct snd_gus_card *gus) +{ + snd_gf1_hw_start(gus, false); + snd_gf1_timers_resume(gus); + snd_gf1_uart_resume(gus); + + return 0; +} diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c index e3a8847e02cf..14dcde138bc7 100644 --- a/sound/isa/gus/gus_timer.c +++ b/sound/isa/gus/gus_timer.c @@ -178,3 +178,17 @@ void snd_gf1_timers_done(struct snd_gus_card * gus) gus->gf1.timer2 = NULL; } } + +void snd_gf1_timers_resume(struct snd_gus_card *gus) +{ + if (gus->gf1.timer1) { + gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1; + if (gus->gf1.timer_enabled & 4) + snd_gf1_timer1_start(gus->gf1.timer1); + } + if (gus->gf1.timer2) { + gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2; + if (gus->gf1.timer_enabled & 8) + snd_gf1_timer2_start(gus->gf1.timer2); + } +} diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c index 770d8f3e4cff..25057a5a81b0 100644 --- a/sound/isa/gus/gus_uart.c +++ b/sound/isa/gus/gus_uart.c @@ -232,3 +232,50 @@ int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device) gus->midi_uart = rmidi; return err; } + +void snd_gf1_uart_suspend(struct snd_gus_card *gus) +{ + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + outb(0x03, GUSP(gus, MIDICTRL)); +} + +void snd_gf1_uart_resume(struct snd_gus_card *gus) +{ + unsigned short uart_cmd; + bool active; + int i; + + scoped_guard(spinlock_irqsave, &gus->uart_cmd_lock) { + active = gus->midi_substream_input || gus->midi_substream_output; + } + if (!active) + return; + + /* snd_gf1_hw_start() already left MIDICTRL in reset. */ + usleep_range(160, 200); + + guard(spinlock_irqsave)(&gus->uart_cmd_lock); + if (!gus->midi_substream_input && !gus->midi_substream_output) + return; + + if (gus->midi_substream_output) + gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out; + if (gus->midi_substream_input) + gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in; + + if (!gus->uart_enable) + return; + + uart_cmd = gus->gf1.uart_cmd; + snd_gf1_uart_cmd(gus, 0x00); + + if (gus->midi_substream_input) { + for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++) + snd_gf1_uart_get(gus); + if (i >= 1000) + dev_err(gus->card->dev, + "gus midi uart resume - cleanup error\n"); + } + + snd_gf1_uart_cmd(gus, uart_cmd); +} -- cgit v1.2.3 From d9bfa935a9855664f5cea12131fa809fd56ff82c Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 6 Apr 2026 00:20:04 -0300 Subject: ALSA: gusclassic: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusclassic still leaves its ISA PM callbacks disabled because the shared GF1 core had no suspend and resume path suitable for PM recovery. Wire the driver up to the new shared GUS suspend and resume helpers so a suspend/resume cycle restores usable GF1 operation without rerunning probe-only detection or tearing down the runtime bookkeeping kept by the card instance. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-2-b6829a7457cd@gmail.com --- sound/isa/gus/gusclassic.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c index 101202acefb3..363c819ced89 100644 --- a/sound/isa/gus/gusclassic.c +++ b/sound/isa/gus/gusclassic.c @@ -145,6 +145,7 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n) error = snd_gusclassic_create(card, dev, n, &gus); if (error < 0) return error; + card->private_data = gus; error = snd_gusclassic_detect(gus); if (error < 0) @@ -193,11 +194,29 @@ static int snd_gusclassic_probe(struct device *dev, unsigned int n) return 0; } +#ifdef CONFIG_PM +static int snd_gusclassic_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + + return snd_gus_suspend(card->private_data); +} + +static int snd_gusclassic_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + + return snd_gus_resume(card->private_data); +} +#endif + static struct isa_driver snd_gusclassic_driver = { .match = snd_gusclassic_match, .probe = snd_gusclassic_probe, -#if 0 /* FIXME */ +#ifdef CONFIG_PM .suspend = snd_gusclassic_suspend, + .resume = snd_gusclassic_resume, #endif .driver = { .name = DEV_NAME -- cgit v1.2.3 From 7da8af2541d01ce1a7ad5efbb3fee8567fdfc959 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 6 Apr 2026 00:20:05 -0300 Subject: ALSA: gusextreme: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusextreme still leaves its ISA PM callbacks disabled because the shared GF1 core had no suspend and resume path suitable for PM recovery. Resume on this board needs one extra step before the shared GF1 path can touch the chip again: the ES1688 side must restore the GF1 routing. Split that routing sequence into a helper, reuse it for probe and resume, reset the ES1688 side first on resume, and then wire the driver up to the shared GUS PM helpers. This restores usable post-resume GF1 operation on GUS Extreme without rerunning probe-only detection in the shared GF1 path. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-3-b6829a7457cd@gmail.com --- sound/isa/gus/gusextreme.c | 57 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c index ed921b89b00a..0984731740c4 100644 --- a/sound/isa/gus/gusextreme.c +++ b/sound/isa/gus/gusextreme.c @@ -44,6 +44,11 @@ static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +struct snd_gusextreme { + struct snd_es1688 es1688; + struct snd_gus_card *gus; +}; + module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for " CRD_NAME " soundcard."); module_param_array(id, charp, NULL, 0444); @@ -142,17 +147,15 @@ static int snd_gusextreme_gus_card_create(struct snd_card *card, 0, channels[n], pcm_channels[n], 0, rgus); } -static int snd_gusextreme_detect(struct snd_gus_card *gus, - struct snd_es1688 *es1688) +static void snd_gusextreme_enable_gf1(struct snd_gus_card *gus, + struct snd_es1688 *es1688) { - unsigned char d; - /* * This is main stuff - enable access to GF1 chip... * I'm not sure, if this will work for card which have * ES1688 chip in another place than 0x220. - * - * I used reverse-engineering in DOSEMU. [--jk] + * + * I used reverse-engineering in DOSEMU. [--jk] * * ULTRINIT.EXE: * 0x230 = 0,2,3 @@ -172,7 +175,14 @@ static int snd_gusextreme_detect(struct snd_gus_card *gus, outb(0, 0x201); outb(gus->gf1.port & 0x010 ? 3 : 1, ES1688P(es1688, INIT1)); } +} + +static int snd_gusextreme_detect(struct snd_gus_card *gus, + struct snd_es1688 *es1688) +{ + unsigned char d; + snd_gusextreme_enable_gf1(gus, es1688); udelay(100); snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ @@ -223,16 +233,18 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n) { struct snd_card *card; struct snd_gus_card *gus; + struct snd_gusextreme *gusextreme; struct snd_es1688 *es1688; struct snd_opl3 *opl3; int error; error = snd_devm_card_new(dev, index[n], id[n], THIS_MODULE, - sizeof(struct snd_es1688), &card); + sizeof(*gusextreme), &card); if (error < 0) return error; - es1688 = card->private_data; + gusextreme = card->private_data; + es1688 = &gusextreme->es1688; if (mpu_port[n] == SNDRV_AUTO_PORT) mpu_port[n] = 0; @@ -250,6 +262,7 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n) error = snd_gusextreme_gus_card_create(card, dev, n, &gus); if (error < 0) return error; + gusextreme->gus = gus; error = snd_gusextreme_detect(gus, es1688); if (error < 0) @@ -321,10 +334,36 @@ static int snd_gusextreme_probe(struct device *dev, unsigned int n) return 0; } +#ifdef CONFIG_PM +static int snd_gusextreme_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusextreme *gusextreme = card->private_data; + + return snd_gus_suspend(gusextreme->gus); +} + +static int snd_gusextreme_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusextreme *gusextreme = card->private_data; + int err; + + err = snd_es1688_reset(&gusextreme->es1688); + if (err < 0) + return err; + + snd_gusextreme_enable_gf1(gusextreme->gus, &gusextreme->es1688); + usleep_range(100, 200); + return snd_gus_resume(gusextreme->gus); +} +#endif + static struct isa_driver snd_gusextreme_driver = { .match = snd_gusextreme_match, .probe = snd_gusextreme_probe, -#if 0 /* FIXME */ +#ifdef CONFIG_PM .suspend = snd_gusextreme_suspend, .resume = snd_gusextreme_resume, #endif -- cgit v1.2.3 From 1b64e52380abfd368baa8a53d73336ffcd52c0c0 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Mon, 6 Apr 2026 00:20:06 -0300 Subject: ALSA: gusmax: add ISA suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gusmax still leaves its ISA PM callbacks disabled even though the shared GF1 suspend and resume path now exists. This board needs one extra piece of PM glue around the shared GF1 helpers. The attached WSS codec has its own register image that must be saved and restored across suspend, and the MAX control register must be rewritten on resume before the codec and GF1 sides are brought back. Use the existing wss->suspend() and wss->resume() hooks for the codec, then wire the driver up to the shared GUS suspend and resume helpers for the GF1 side. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260406-b4-alsa-gus-isa-pm-v1-4-b6829a7457cd@gmail.com --- sound/isa/gus/gusmax.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c index b572411c4422..f1fd7ff2121d 100644 --- a/sound/isa/gus/gusmax.c +++ b/sound/isa/gus/gusmax.c @@ -328,12 +328,38 @@ static int snd_gusmax_probe(struct device *pdev, unsigned int dev) return 0; } +#ifdef CONFIG_PM +static int snd_gusmax_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusmax *maxcard = card->private_data; + + maxcard->wss->suspend(maxcard->wss); + return snd_gus_suspend(maxcard->gus); +} + +static int snd_gusmax_resume(struct device *dev, unsigned int n) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_gusmax *maxcard = card->private_data; + + /* Restore the board routing latch before resuming the codec and GF1. */ + outb(maxcard->gus->max_cntrl_val, GUSP(maxcard->gus, MAXCNTRLPORT)); + maxcard->wss->resume(maxcard->wss); + return snd_gus_resume(maxcard->gus); +} +#endif + #define DEV_NAME "gusmax" static struct isa_driver snd_gusmax_driver = { .match = snd_gusmax_match, .probe = snd_gusmax_probe, - /* FIXME: suspend/resume */ +#ifdef CONFIG_PM + .suspend = snd_gusmax_suspend, + .resume = snd_gusmax_resume, +#endif .driver = { .name = DEV_NAME }, -- cgit v1.2.3 From 22cb174c0af85e83d02a7b44fcc20c77008ee885 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Tue, 7 Apr 2026 12:35:41 -0300 Subject: ALSA: tea6330t: move snd_tea6330t_detect() EXPORT_SYMBOL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the remaining standalone snd_tea6330t_detect() EXPORT_SYMBOL() declaration next to its function definition so tea6330t.c follows the usual layout. No functional change intended. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260407-alsa-interwave-pm-v2-1-8dd96c6129e9@gmail.com --- sound/i2c/tea6330t.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/i2c/tea6330t.c b/sound/i2c/tea6330t.c index e8c50a036bea..5341907b85d1 100644 --- a/sound/i2c/tea6330t.c +++ b/sound/i2c/tea6330t.c @@ -51,6 +51,7 @@ int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer) snd_i2c_unlock(bus); return res; } +EXPORT_SYMBOL(snd_tea6330t_detect); #if 0 static void snd_tea6330t_set(struct tea6330t *tea, @@ -356,5 +357,4 @@ int snd_tea6330t_update_mixer(struct snd_card *card, return err; } -EXPORT_SYMBOL(snd_tea6330t_detect); EXPORT_SYMBOL(snd_tea6330t_update_mixer); -- cgit v1.2.3 From 19cbb3e0c27f28feb7781641994226cb2ee206a2 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Tue, 7 Apr 2026 12:35:42 -0300 Subject: ALSA: tea6330t: add mixer state restore helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The InterWave STB variant uses a TEA6330T mixer on its private I2C bus. The mixer state is cached in software, but there is no helper to push that register image back to hardware after system resume. Add a small restore helper that reapplies the cached TEA6330T register image to the device so board drivers can restore the external mixer state as part of their PM resume path. Take snd_i2c_lock() around the full device lookup and restore sequence so the bus device list traversal is also protected. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260407-alsa-interwave-pm-v2-2-8dd96c6129e9@gmail.com --- include/sound/tea6330t.h | 1 + sound/i2c/tea6330t.c | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/include/sound/tea6330t.h b/include/sound/tea6330t.h index 1c77b78f6533..3a34033d2aa3 100644 --- a/include/sound/tea6330t.h +++ b/include/sound/tea6330t.h @@ -12,5 +12,6 @@ int snd_tea6330t_detect(struct snd_i2c_bus *bus, int equalizer); int snd_tea6330t_update_mixer(struct snd_card *card, struct snd_i2c_bus *bus, int equalizer, int fader); +int snd_tea6330t_restore_mixer(struct snd_i2c_bus *bus); #endif /* __SOUND_TEA6330T_H */ diff --git a/sound/i2c/tea6330t.c b/sound/i2c/tea6330t.c index 5341907b85d1..39c5e87c6ab0 100644 --- a/sound/i2c/tea6330t.c +++ b/sound/i2c/tea6330t.c @@ -356,5 +356,42 @@ int snd_tea6330t_update_mixer(struct snd_card *card, snd_i2c_device_free(device); return err; } - EXPORT_SYMBOL(snd_tea6330t_update_mixer); + +int snd_tea6330t_restore_mixer(struct snd_i2c_bus *bus) +{ + struct snd_i2c_device *device; + struct tea6330t *tea; + unsigned char bytes[7]; + unsigned int idx; + int err; + + if (!bus) + return -EINVAL; + + snd_i2c_lock(bus); + list_for_each_entry(device, &bus->devices, list) { + if (device->addr != TEA6330T_ADDR) + continue; + + tea = device->private_data; + if (!tea) { + err = -EINVAL; + goto unlock; + } + + bytes[0] = TEA6330T_SADDR_VOLUME_LEFT; + for (idx = 0; idx < 6; idx++) + bytes[idx + 1] = tea->regs[idx]; + err = snd_i2c_sendbytes(device, bytes, 7); + err = err < 0 ? err : 0; + goto unlock; + } + + err = -ENODEV; + +unlock: + snd_i2c_unlock(bus); + return err; +} +EXPORT_SYMBOL(snd_tea6330t_restore_mixer); -- cgit v1.2.3 From 6f800c3397b7f64da4c9eb636a1206f8d8636c95 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Tue, 7 Apr 2026 12:35:43 -0300 Subject: ALSA: interwave: add ISA and PnP suspend and resume callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit interwave still leaves both its ISA and PnP PM callbacks disabled even though the shared GUS suspend and resume path now exists. This board needs InterWave-specific glue around the shared GUS PM path. The attached WSS codec has its own register image that must be saved and restored across suspend, the InterWave-specific GF1 compatibility, decode, MPU401, and emulation settings must be rewritten after the shared GF1 resume path reinitializes the chip, and the probe-detected InterWave memory layout must be restored without rerunning the destructive DRAM/ROM detection path. Track the optional STB TEA6330T bus at probe time, restore its cached mixer state after resume, add resume-safe helpers for the InterWave register and memory-configuration state, and wire both the ISA and PnP front-ends up to the shared GUS PM helpers. The resume path intentionally restores only the cached hardware setup. It does not attempt to preserve sample RAM contents across suspend. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260407-alsa-interwave-pm-v2-3-8dd96c6129e9@gmail.com --- sound/isa/gus/interwave.c | 178 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 151 insertions(+), 27 deletions(-) diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c index 18adcd35e117..616c11e51a2f 100644 --- a/sound/isa/gus/interwave.c +++ b/sound/isa/gus/interwave.c @@ -96,6 +96,7 @@ struct snd_interwave { struct snd_gus_card *gus; struct snd_wss *wss; #ifdef SNDRV_STB + struct snd_i2c_bus *i2c_bus; struct resource *i2c_res; #endif unsigned short gus_status_reg; @@ -363,18 +364,30 @@ struct rom_hdr { /* 511 */ unsigned char csum; }; -static void snd_interwave_detect_memory(struct snd_gus_card *gus) +static const unsigned int snd_interwave_memory_configs[] = { + 0x00000001, 0x00000101, 0x01010101, 0x00000401, + 0x04040401, 0x00040101, 0x04040101, 0x00000004, + 0x00000404, 0x04040404, 0x00000010, 0x00001010, + 0x10101010 +}; + +static int snd_interwave_find_memory_config(unsigned int lmct) { - static const unsigned int lmc[13] = - { - 0x00000001, 0x00000101, 0x01010101, 0x00000401, - 0x04040401, 0x00040101, 0x04040101, 0x00000004, - 0x00000404, 0x04040404, 0x00000010, 0x00001010, - 0x10101010 - }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_interwave_memory_configs); i++) { + if (lmct == snd_interwave_memory_configs[i]) + return i; + } + + return -EINVAL; +} +static void snd_interwave_detect_memory(struct snd_gus_card *gus) +{ int bank_pos, pages; unsigned int i, lmct; + int lmc_cfg; int psizes[4]; unsigned char iwave[8]; unsigned char csum; @@ -399,17 +412,20 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus) #if 0 dev_dbg(gus->card->dev, "lmct = 0x%08x\n", lmct); #endif - for (i = 0; i < ARRAY_SIZE(lmc); i++) - if (lmct == lmc[i]) { + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg >= 0) { #if 0 - dev_dbg(gus->card->dev, "found !!! %i\n", i); + dev_dbg(gus->card->dev, "found !!! %i\n", lmc_cfg); #endif - snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i); - snd_interwave_bank_sizes(gus, psizes); - break; - } - if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode) - snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, + (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | + lmc_cfg); + snd_interwave_bank_sizes(gus, psizes); + } else if (!gus->gf1.enh_mode) { + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, + (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | + 2); + } for (i = 0; i < 4; i++) { gus->gf1.mem_alloc.banks_8[i].address = gus->gf1.mem_alloc.banks_16[i].address = i << 22; @@ -454,24 +470,66 @@ static void snd_interwave_detect_memory(struct snd_gus_card *gus) snd_interwave_reset(gus); } +static void __snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); + snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); + snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); +} + +static void snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + scoped_guard(spinlock_irqsave, &gus->reg_lock) + __snd_interwave_restore_regs(gus); +} + +static void snd_interwave_restore_memory(struct snd_gus_card *gus) +{ + unsigned short mem_cfg; + unsigned int lmct = 0; + int i, lmc_cfg; + + if (!gus->gf1.memory) + return; + + for (i = 0; i < 4; i++) + lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); + + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg < 0) { + if (!gus->gf1.enh_mode) { + lmc_cfg = 2; + } else { + dev_warn(gus->card->dev, + "cannot restore InterWave memory layout 0x%08x\n", + lmct); + return; + } + } + + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); + mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; + mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); + } +} + static void snd_interwave_init(int dev, struct snd_gus_card *gus) { - /* ok.. some InterWave specific initialization */ + /* Probe-time setup also clears the timer control register. */ scoped_guard(spinlock_irqsave, &gus->reg_lock) { snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00); - snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); - snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); - snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); - snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); - snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); - snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); + __snd_interwave_restore_regs(gus); } gus->equal_irq = 1; gus->codec_flag = 1; gus->interwave = 1; gus->max_flag = 1; gus->joystick_dac = joystick_dac[dev]; - } static const struct snd_kcontrol_new snd_interwave_controls[] = { @@ -724,6 +782,7 @@ static int snd_interwave_probe(struct snd_card *card, int dev, err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1); if (err < 0) return err; + iwcard->i2c_bus = i2c_bus; } #endif @@ -828,10 +887,59 @@ static int snd_interwave_isa_probe(struct device *pdev, return 0; } +#ifdef CONFIG_PM +static int snd_interwave_card_suspend(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + + iwcard->wss->suspend(iwcard->wss); + return snd_gus_suspend(iwcard->gus); +} + +static int snd_interwave_card_resume(struct snd_card *card) +{ + struct snd_interwave *iwcard = card->private_data; + int err; + + err = snd_gus_resume(iwcard->gus); + if (err < 0) + return err; + + snd_interwave_restore_regs(iwcard->gus); + snd_interwave_restore_memory(iwcard->gus); + iwcard->wss->resume(iwcard->wss); +#ifdef SNDRV_STB + if (iwcard->i2c_bus) { + err = snd_tea6330t_restore_mixer(iwcard->i2c_bus); + if (err < 0) + dev_warn(card->dev, + "failed to restore TEA6330T mixer state: %d\n", + err); + } +#endif + + return 0; +} + +static int snd_interwave_isa_suspend(struct device *pdev, unsigned int dev, + pm_message_t state) +{ + return snd_interwave_card_suspend(dev_get_drvdata(pdev)); +} + +static int snd_interwave_isa_resume(struct device *pdev, unsigned int dev) +{ + return snd_interwave_card_resume(dev_get_drvdata(pdev)); +} +#endif + static struct isa_driver snd_interwave_driver = { .match = snd_interwave_isa_match, .probe = snd_interwave_isa_probe, - /* FIXME: suspend,resume */ +#ifdef CONFIG_PM + .suspend = snd_interwave_isa_suspend, + .resume = snd_interwave_isa_resume, +#endif .driver = { .name = INTERWAVE_DRIVER }, @@ -871,12 +979,28 @@ static int snd_interwave_pnp_detect(struct pnp_card_link *pcard, return 0; } +#ifdef CONFIG_PM +static int snd_interwave_pnpc_suspend(struct pnp_card_link *pcard, + pm_message_t state) +{ + return snd_interwave_card_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_interwave_pnpc_resume(struct pnp_card_link *pcard) +{ + return snd_interwave_card_resume(pnp_get_card_drvdata(pcard)); +} +#endif + static struct pnp_card_driver interwave_pnpc_driver = { .flags = PNP_DRIVER_RES_DISABLE, .name = INTERWAVE_PNP_DRIVER, .id_table = snd_interwave_pnpids, .probe = snd_interwave_pnp_detect, - /* FIXME: suspend,resume */ +#ifdef CONFIG_PM + .suspend = snd_interwave_pnpc_suspend, + .resume = snd_interwave_pnpc_resume, +#endif }; #endif /* CONFIG_PNP */ -- cgit v1.2.3 From 66f6f543283e91b8899b0dd109d8f15a529e8464 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Tue, 7 Apr 2026 18:13:06 -0300 Subject: ALSA: i2c: ak4xxx-adda: implement AK4529 reset handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delta 410 uses snd_akm4xxx_reset() both around DFS changes and from its PM callbacks, but the AK4529 case in this helper is still left unimplemented and never drives the codec reset path. The AK4529 datasheet documents register 09h.RSTN as an internal timing reset. Clearing RSTN powers down the ADC and DAC blocks, but does not reinitialize the register map. That matches the existing ak4xxx helper model, which already keeps the desired codec state in the software register cache. Implement AK4529 reset handling by clearing 09h.RSTN on state == 1, then replaying the cached register image and setting RSTN back to 1 on state == 0. This restores cached Delta 410 mixer state after resume and gives the AK4529 DFS-change path a real codec reset sequence. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260407-ak4529-reset-handling-v1-1-b971c18b1a32@gmail.com Signed-off-by: Takashi Iwai --- sound/i2c/other/ak4xxx-adda.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index b24c80410d45..bdeb3cef3640 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -53,6 +53,31 @@ static void ak4524_reset(struct snd_akm4xxx *ak, int state) } } +/* reset procedure for AK4529 */ +static void ak4529_reset(struct snd_akm4xxx *ak, int state) +{ + static const unsigned char regs[] = { + 0x0a, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x0b, 0x0c, 0x08, + }; + unsigned int i; + unsigned char reg; + + if (state) { + snd_akm4xxx_write(ak, 0, 0x09, + snd_akm4xxx_get(ak, 0, 0x09) & ~0x01); + return; + } + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + reg = regs[i]; + snd_akm4xxx_write(ak, 0, reg, + snd_akm4xxx_get(ak, 0, reg)); + } + snd_akm4xxx_write(ak, 0, 0x09, + snd_akm4xxx_get(ak, 0, 0x09) | 0x01); +} + /* reset procedure for AK4355 and AK4358 */ static void ak435X_reset(struct snd_akm4xxx *ak, int state) { @@ -99,7 +124,7 @@ void snd_akm4xxx_reset(struct snd_akm4xxx *ak, int state) ak4524_reset(ak, state); break; case SND_AK4529: - /* FIXME: needed for ak4529? */ + ak4529_reset(ak, state); break; case SND_AK4355: ak435X_reset(ak, state); -- cgit v1.2.3 From 292286b2d229fb732421429b027d38ac3f969383 Mon Sep 17 00:00:00 2001 From: songxiebing Date: Wed, 8 Apr 2026 16:33:11 +0800 Subject: ALSA: usb-audio: qcom: Fix incorrect type in enable_audio_stream Fix sparse warning: sound/usb/qcom/qc_audio_offload.c:943:27: sparse: incorrect type in argument 2 expected unsigned int val but got snd_pcm_format_t. Explicitly cast pcm_format to unsigned int for snd_mask_leave(). Fixes: 326bbc348298 ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604062109.Oxi8JjWW-lkp@intel.com/ Signed-off-by: songxiebing Link: https://patch.msgid.link/20260408083311.774173-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index f161eb29f911..9c0d370860f3 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -947,7 +947,7 @@ static int enable_audio_stream(struct snd_usb_substream *subs, _snd_pcm_hw_params_any(¶ms); m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); - snd_mask_leave(m, pcm_format); + snd_mask_leave(m, (__force unsigned int)pcm_format); i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); snd_interval_setinteger(i); -- cgit v1.2.3 From de65275fc94e2e0acc79bd016d60889bf251ccd9 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 9 Apr 2026 10:40:28 +0800 Subject: ALSA: hda/realtek: Add quirk for CSL Unity BF24B The CSL Unity BF24B all-in-one PC uses a Realtek ALC662 rev3 audio codec and requires the correct GPIO configuration to enable sound output from both the speakers and the headphone. Link: https://bugzilla.kernel.org/show_bug.cgi?id=221258 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20260409024028.1297587-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc662.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index 5073165d1f3c..3abe41c7315c 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -255,6 +255,25 @@ static void alc_fixup_headset_mode_alc668(struct hda_codec *codec, alc_fixup_headset_mode(codec, fix, action); } +static void alc662_fixup_csl_amp(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + struct alc_spec *spec = codec->spec; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + spec->gpio_mask |= 0x03; + spec->gpio_dir |= 0x03; + break; + case HDA_FIXUP_ACT_INIT: + /* need to toggle GPIO to enable the amp */ + alc_update_gpio_data(codec, 0x03, true); + msleep(100); + alc_update_gpio_data(codec, 0x03, false); + break; + } +} + enum { ALC662_FIXUP_ASPIRE, ALC662_FIXUP_LED_GPIO1, @@ -313,6 +332,7 @@ enum { ALC897_FIXUP_HEADSET_MIC_PIN2, ALC897_FIXUP_UNIS_H3C_X500S, ALC897_FIXUP_HEADSET_MIC_PIN3, + ALC662_FIXUP_CSL_GPIO, }; static const struct hda_fixup alc662_fixups[] = { @@ -766,11 +786,16 @@ static const struct hda_fixup alc662_fixups[] = { { } }, }, + [ALC662_FIXUP_CSL_GPIO] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc662_fixup_csl_amp, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x1019, 0x9859, "JP-IK LEAP W502", ALC897_FIXUP_HEADSET_MIC_PIN3), + SND_PCI_QUIRK(0x1022, 0xc950, "CSL Unity BF24B", ALC662_FIXUP_CSL_GPIO), SND_PCI_QUIRK(0x1025, 0x022f, "Acer Aspire One", ALC662_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1025, 0x0241, "Packard Bell DOTS", ALC662_FIXUP_INV_DMIC), SND_PCI_QUIRK(0x1025, 0x0308, "Acer Aspire 8942G", ALC662_FIXUP_ASPIRE), -- cgit v1.2.3 From aa6c1052b7730e18d5999f9a5cfb1dadaac82310 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Wed, 8 Apr 2026 12:17:37 -0300 Subject: ALSA: i2c: ak4xxx-adda: seed AK5365 cache with reset defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_akm4xxx_init() clears the register and volume caches before dispatching by codec type. The AK5365 case then returns immediately, leaving the software cache at zero instead of the documented AK5365 reset defaults. The AK5365 capture volume controls read from volumes[] and the proc register dump reads from images[], so the initial capture volume state and proc output are wrong until a control write happens. Seed the AK5365 cache with its documented reset defaults instead of adding a guessed init sequence. The datasheet documents the reset values and states that MCLK/LRCK changes do not require a PDN/PWN reset because the chip has a built-in reset-free circuit. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260408-ak5365-cache-defaults-v1-1-fff639aca3e3@gmail.com Signed-off-by: Takashi Iwai --- sound/i2c/other/ak4xxx-adda.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sound/i2c/other/ak4xxx-adda.c b/sound/i2c/other/ak4xxx-adda.c index bdeb3cef3640..9dd36b82a6ac 100644 --- a/sound/i2c/other/ak4xxx-adda.c +++ b/sound/i2c/other/ak4xxx-adda.c @@ -281,6 +281,9 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) 0x07, 0x00, /* 7: ROUT muted */ 0xff, 0xff }; + static const unsigned char ak5365_defaults[] = { + 0x01, 0x00, 0x00, 0x2b, 0x7f, 0x7f, 0x28, 0x89, + }; int chip; const unsigned char *ptr, *inits; @@ -327,10 +330,12 @@ void snd_akm4xxx_init(struct snd_akm4xxx *ak) ak->total_regs = 0x05; break; case SND_AK5365: - /* FIXME: any init sequence? */ ak->num_chips = 1; ak->name = "ak5365"; ak->total_regs = 0x08; + memcpy(ak->images, ak5365_defaults, sizeof(ak5365_defaults)); + snd_akm4xxx_set_vol(ak, 0, 0x04, 127); + snd_akm4xxx_set_vol(ak, 0, 0x05, 127); return; case SND_AK4620: inits = inits_ak4620; -- cgit v1.2.3 From 49690509ebdcbfa7618dd5a5ff3c89f7af9a5b43 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Thu, 9 Apr 2026 02:07:45 -0300 Subject: ALSA: msnd: prepare system sleep support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit System suspend cannot work for msnd today because the PCM trigger paths reject SNDRV_PCM_TRIGGER_SUSPEND, and the driver has only refcounted IRQ helpers. Add the small helpers needed by the PM callbacks and restore master volume from the cached ALSA mixer state when the DSP is reinitialized. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260409-msnd-pm-support-v1-1-2abef720d0e7@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/msnd/msnd.c | 72 ++++++++++++++++++++++++------------ sound/isa/msnd/msnd.h | 1 + sound/isa/msnd/msnd_pinnacle_mixer.c | 4 ++ 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/sound/isa/msnd/msnd.c b/sound/isa/msnd/msnd.c index 5e350234d572..77367e102fda 100644 --- a/sound/isa/msnd/msnd.c +++ b/sound/isa/msnd/msnd.c @@ -127,11 +127,8 @@ int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len) } EXPORT_SYMBOL(snd_msnd_upload_host); -int snd_msnd_enable_irq(struct snd_msnd *dev) +static int __snd_msnd_enable_irq(struct snd_msnd *dev) { - if (dev->irq_ref++) - return 0; - dev_dbg(dev->card->dev, LOGNAME ": Enabling IRQ\n"); guard(spinlock_irqsave)(&dev->lock); @@ -152,17 +149,9 @@ int snd_msnd_enable_irq(struct snd_msnd *dev) return -EIO; } -EXPORT_SYMBOL(snd_msnd_enable_irq); -int snd_msnd_disable_irq(struct snd_msnd *dev) +static int __snd_msnd_disable_irq(struct snd_msnd *dev) { - if (--dev->irq_ref > 0) - return 0; - - if (dev->irq_ref < 0) - dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n", - dev->irq_ref); - dev_dbg(dev->card->dev, LOGNAME ": Disabling IRQ\n"); guard(spinlock_irqsave)(&dev->lock); @@ -178,8 +167,39 @@ int snd_msnd_disable_irq(struct snd_msnd *dev) return -EIO; } + +int snd_msnd_enable_irq(struct snd_msnd *dev) +{ + if (dev->irq_ref++) + return 0; + + return __snd_msnd_enable_irq(dev); +} +EXPORT_SYMBOL(snd_msnd_enable_irq); + +int snd_msnd_disable_irq(struct snd_msnd *dev) +{ + if (--dev->irq_ref > 0) + return 0; + + if (dev->irq_ref < 0) + dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n", + dev->irq_ref); + + return __snd_msnd_disable_irq(dev); +} EXPORT_SYMBOL(snd_msnd_disable_irq); +int snd_msnd_force_irq(struct snd_msnd *dev, bool enable) +{ + if (!dev->irq_ref) + return 0; + + return enable ? __snd_msnd_enable_irq(dev) : + __snd_msnd_disable_irq(dev); +} +EXPORT_SYMBOL(snd_msnd_force_irq); + static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size) { long tmp = (size * HZ * chip->play_sample_size) / 8; @@ -507,25 +527,27 @@ static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_msnd *chip = snd_pcm_substream_chip(substream); - int result = 0; - if (cmd == SNDRV_PCM_TRIGGER_START) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: dev_dbg(chip->card->dev, "%s(START)\n", __func__); chip->banksPlayed = 0; set_bit(F_WRITING, &chip->flags); snd_msnd_DAPQ(chip, 1); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: dev_dbg(chip->card->dev, "%s(STOP)\n", __func__); - /* interrupt diagnostic, comment this out later */ clear_bit(F_WRITING, &chip->flags); snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); - } else { + break; + default: dev_dbg(chip->card->dev, "%s(?????)\n", __func__); - result = -EINVAL; + return -EINVAL; } dev_dbg(chip->card->dev, "%s() ENDE\n", __func__); - return result; + return 0; } static snd_pcm_uframes_t @@ -589,17 +611,22 @@ static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream, { struct snd_msnd *chip = snd_pcm_substream_chip(substream); - if (cmd == SNDRV_PCM_TRIGGER_START) { + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: chip->last_recbank = -1; set_bit(F_READING, &chip->flags); if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0) return 0; clear_bit(F_READING, &chip->flags); - } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: clear_bit(F_READING, &chip->flags); snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); return 0; + default: + break; } return -EINVAL; } @@ -668,4 +695,3 @@ EXPORT_SYMBOL(snd_msnd_pcm); MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers"); MODULE_LICENSE("GPL"); - diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h index 3d7810ed9186..b25beca25c0d 100644 --- a/sound/isa/msnd/msnd.h +++ b/sound/isa/msnd/msnd.h @@ -280,6 +280,7 @@ int snd_msnd_upload_host(struct snd_msnd *chip, const u8 *bin, int len); int snd_msnd_enable_irq(struct snd_msnd *chip); int snd_msnd_disable_irq(struct snd_msnd *chip); +int snd_msnd_force_irq(struct snd_msnd *chip, bool enable); void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file); int snd_msnd_DAPQ(struct snd_msnd *chip, int start); int snd_msnd_DARQ(struct snd_msnd *chip, int start); diff --git a/sound/isa/msnd/msnd_pinnacle_mixer.c b/sound/isa/msnd/msnd_pinnacle_mixer.c index ec354483b9f8..8ca987221753 100644 --- a/sound/isa/msnd/msnd_pinnacle_mixer.c +++ b/sound/isa/msnd/msnd_pinnacle_mixer.c @@ -310,6 +310,10 @@ EXPORT_SYMBOL(snd_msndmix_new); void snd_msndmix_setup(struct snd_msnd *dev) { + writew(dev->left_levels[MSND_MIXER_VOLUME], + dev->SMA + SMA_wCurrMastVolLeft); + writew(dev->right_levels[MSND_MIXER_VOLUME], + dev->SMA + SMA_wCurrMastVolRight); update_pot(MSND_MIXER_LINE, bInPotPos, HDEXAR_IN_SET_POTS); update_potm(MSND_MIXER_AUX, bAuxPotPos, HDEXAR_AUX_SET_POTS); update_volm(MSND_MIXER_PCM, wCurrPlayVol); -- cgit v1.2.3 From efca489a86fc6a5364215fdf03c2fad3b864d03a Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Thu, 9 Apr 2026 02:07:46 -0300 Subject: ALSA: msnd: add ISA and PnP system sleep callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The msnd drivers do not implement system sleep callbacks today, so they have no defined way to recover DSP state after suspend. Add common card suspend/resume helpers, rerun the DSP initialization path on resume, restore the cached capture-source state, and rearm the shared IRQ for already-open users. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260409-msnd-pm-support-v1-2-2abef720d0e7@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/msnd/msnd.h | 2 + sound/isa/msnd/msnd_pinnacle.c | 95 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/sound/isa/msnd/msnd.h b/sound/isa/msnd/msnd.h index b25beca25c0d..56a700e6a5cb 100644 --- a/sound/isa/msnd/msnd.h +++ b/sound/isa/msnd/msnd.h @@ -253,6 +253,8 @@ struct snd_msnd { spinlock_t mixer_lock; int nresets; unsigned recsrc; + u8 pm_recsrc; + bool pm_mpu_input; #define LEVEL_ENTRIES 32 int left_levels[LEVEL_ENTRIES]; int right_levels[LEVEL_ENTRIES]; diff --git a/sound/isa/msnd/msnd_pinnacle.c b/sound/isa/msnd/msnd_pinnacle.c index c4eec391cd29..5b729bb02ef6 100644 --- a/sound/isa/msnd/msnd_pinnacle.c +++ b/sound/isa/msnd/msnd_pinnacle.c @@ -513,6 +513,19 @@ static void snd_msnd_mpu401_close(struct snd_mpu401 *mpu) snd_msnd_disable_irq(mpu->private_data); } +#ifdef CONFIG_PM +static u8 snd_msnd_pm_recsrc(struct snd_msnd *chip) +{ + /* Convert recsrc to the Capture Source selector: 0=Analog, 1=MASS, 2=SPDIF. */ + if (chip->recsrc & BIT(4)) + return 1; + if ((chip->recsrc & BIT(17)) && + test_bit(F_HAVEDIGITAL, &chip->flags)) + return 2; + return 0; +} +#endif + static long mpu_io[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; @@ -1001,10 +1014,73 @@ static int snd_msnd_isa_probe(struct device *pdev, unsigned int idx) return 0; } +#ifdef CONFIG_PM +static int snd_msnd_card_suspend(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + struct snd_mpu401 *mpu; + int err; + + mpu = chip->rmidi ? chip->rmidi->private_data : NULL; + chip->pm_recsrc = snd_msnd_pm_recsrc(chip); + chip->pm_mpu_input = mpu && test_bit(MPU401_MODE_BIT_INPUT, &mpu->mode); + if (chip->pm_mpu_input) + snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_STOP); + + err = snd_msnd_force_irq(chip, false); + if (err < 0) { + if (chip->pm_mpu_input) + snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_START); + return err; + } + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + return 0; +} + +static int snd_msnd_card_resume(struct snd_card *card) +{ + struct snd_msnd *chip = card->private_data; + int err; + + err = snd_msnd_initialize(card); + if (err < 0) + return err; + + snd_msnd_calibrate_adc(chip, chip->play_sample_rate); + snd_msndmix_force_recsrc(chip, chip->pm_recsrc); + + err = snd_msnd_force_irq(chip, true); + if (err < 0) + return err; + + if (chip->pm_mpu_input) + snd_msnd_send_dsp_cmd(chip, HDEX_MIDI_IN_START); + + chip->nresets = 0; + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_msnd_isa_suspend(struct device *dev, unsigned int idx, + pm_message_t state) +{ + return snd_msnd_card_suspend(dev_get_drvdata(dev)); +} + +static int snd_msnd_isa_resume(struct device *dev, unsigned int idx) +{ + return snd_msnd_card_resume(dev_get_drvdata(dev)); +} +#endif + static struct isa_driver snd_msnd_driver = { .match = snd_msnd_isa_match, .probe = snd_msnd_isa_probe, - /* FIXME: suspend, resume */ +#ifdef CONFIG_PM + .suspend = snd_msnd_isa_suspend, + .resume = snd_msnd_isa_resume, +#endif .driver = { .name = DEV_NAME }, @@ -1111,6 +1187,18 @@ static int snd_msnd_pnp_detect(struct pnp_card_link *pcard, return 0; } +#ifdef CONFIG_PM +static int snd_msnd_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_msnd_card_suspend(pnp_get_card_drvdata(pcard)); +} + +static int snd_msnd_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_msnd_card_resume(pnp_get_card_drvdata(pcard)); +} +#endif + static int isa_registered; static int pnp_registered; @@ -1127,6 +1215,10 @@ static struct pnp_card_driver msnd_pnpc_driver = { .name = "msnd_pinnacle", .id_table = msnd_pnpids, .probe = snd_msnd_pnp_detect, +#ifdef CONFIG_PM + .suspend = snd_msnd_pnp_suspend, + .resume = snd_msnd_pnp_resume, +#endif }; #endif /* CONFIG_PNP */ @@ -1161,4 +1253,3 @@ static void __exit snd_msnd_exit(void) module_init(snd_msnd_init); module_exit(snd_msnd_exit); - -- cgit v1.2.3 From 9575766a682f50ec4bcb85ecd438685bdc09f9cc Mon Sep 17 00:00:00 2001 From: Lianqin Hu Date: Thu, 9 Apr 2026 08:21:37 +0000 Subject: ALSA: usb-audio: Add iface reset and delay quirk for HUAWEI USB-C HEADSET Setting up the interface when suspended/resumeing fail on this card. Adding a reset and delay quirk will eliminate this problem. usb 1-1: new full-speed USB device number 2 using xhci-hcd usb 1-1: New USB device found, idVendor=12d1, idProduct=3a07 usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 1-1: Product: HUAWEI USB-C HEADSET usb 1-1: Manufacturer: bestechnic usb 1-1: SerialNumber: 0296C100000000000000000000000 Signed-off-by: Lianqin Hu Link: https://patch.msgid.link/TYUPR06MB62176A18EA7A9DD0AC2826BCD2582@TYUPR06MB6217.apcprd06.prod.outlook.com Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 48acd8dac689..6cf85922b7be 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2347,8 +2347,9 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x1101, 0x0003, /* Audioengine D1 */ QUIRK_FLAG_GET_SAMPLE_RATE), - DEVICE_FLG(0x12d1, 0x3a07, /* Huawei Technologies Co., Ltd. */ - QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE), + DEVICE_FLG(0x12d1, 0x3a07, /* HUAWEI USB-C HEADSET */ + QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | + QUIRK_FLAG_FORCE_IFACE_RESET | QUIRK_FLAG_IFACE_DELAY), DEVICE_FLG(0x1224, 0x2a25, /* Jieli Technology USB PHY 2.0 */ QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIC_RES_16), DEVICE_FLG(0x1395, 0x740a, /* Sennheiser DECT */ -- cgit v1.2.3 From b0762dd2fcab5b8b4b953314f3f6eb9d92bc16bc Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:14 +0200 Subject: ALSA: hda: Add sync version of snd_hda_codec_write() We used snd_hda_codec_read() for the verb write when a synchronization is needed after the write, e.g. for the power state toggle or such cases. It works in principle, but it looks rather confusing and too hackish. For improving the code readability, introduce a new helper function, snd_hda_codec_write_sync(), which is another variant of snd_hda_codec_write(), and replace the existing snd_hda_codec_read() calls with this one. No behavior change but just the code refactoring. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-2-tiwai@suse.de --- include/sound/hda_codec.h | 11 +++++++++++ sound/hda/codecs/generic.c | 2 +- sound/hda/codecs/hdmi/intelhdmi.c | 2 +- sound/hda/codecs/realtek/realtek.c | 3 +-- sound/hda/codecs/sigmatel.c | 4 ++-- sound/hda/common/codec.c | 11 +++++------ 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 5d9f0ef228af..292d6024388b 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -336,6 +336,17 @@ snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int flags, return snd_hdac_codec_write(&codec->core, nid, flags, verb, parm); } +/* sync after write */ +static inline int +snd_hda_codec_write_sync(struct hda_codec *codec, hda_nid_t nid, int flags, + unsigned int verb, unsigned int parm) +{ + /* use snd_hda_codec_read() for writing; + * the returned value is usually discarded + */ + return snd_hdac_codec_read(&codec->core, nid, flags, verb, parm); +} + #define snd_hda_param_read(codec, nid, param) \ snd_hdac_read_parm(&(codec)->core, nid, param) #define snd_hda_get_sub_nodes(codec, nid, start_nid) \ diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c index 092428ada29d..660a9f2c0ded 100644 --- a/sound/hda/codecs/generic.c +++ b/sound/hda/codecs/generic.c @@ -863,7 +863,7 @@ static void sync_power_state_change(struct hda_codec *codec, hda_nid_t nid) { if (nid) { msleep(10); - snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0); + snd_hda_codec_write_sync(codec, nid, 0, AC_VERB_GET_POWER_STATE, 0); } } diff --git a/sound/hda/codecs/hdmi/intelhdmi.c b/sound/hda/codecs/hdmi/intelhdmi.c index 9460c8db39a9..6a7882544ab7 100644 --- a/sound/hda/codecs/hdmi/intelhdmi.c +++ b/sound/hda/codecs/hdmi/intelhdmi.c @@ -85,7 +85,7 @@ static void haswell_set_power_state(struct hda_codec *codec, hda_nid_t fg, } } - snd_hda_codec_read(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_POWER_STATE, power_state); snd_hda_codec_set_power_to_all(codec, fg, power_state); } diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index b240f13b0438..39a1ead3b743 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -411,9 +411,8 @@ void alc_headset_mic_no_shutup(struct hda_codec *codec) return; snd_array_for_each(&codec->init_pins, i, pin) { - /* use read here for syncing after issuing each verb */ if (pin->nid != mic_pin) - snd_hda_codec_read(codec, pin->nid, 0, + snd_hda_codec_write_sync(codec, pin->nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0); } diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index acbbc7c3508b..4ff80a65168f 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -311,12 +311,12 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask, snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_GPIO_MASK, gpiomask); - snd_hda_codec_read(codec, fg, 0, + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */ msleep(1); - snd_hda_codec_read(codec, fg, 0, + snd_hda_codec_write_sync(codec, fg, 0, AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */ } diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 5123df32ad89..3ac4bf6005d6 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -606,9 +606,8 @@ void snd_hda_shutup_pins(struct hda_codec *codec) if (codec->bus->shutdown) return; snd_array_for_each(&codec->init_pins, i, pin) { - /* use read here for syncing after issuing each verb */ - snd_hda_codec_read(codec, pin->nid, 0, - AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write_sync(codec, pin->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0); } codec->pins_shutup = 1; } @@ -2794,9 +2793,9 @@ static unsigned int hda_set_power_state(struct hda_codec *codec, if (codec->power_filter) state = codec->power_filter(codec, fg, state); if (state == power_state || power_state != AC_PWRST_D3) - snd_hda_codec_read(codec, fg, flags, - AC_VERB_SET_POWER_STATE, - state); + snd_hda_codec_write_sync(codec, fg, flags, + AC_VERB_SET_POWER_STATE, + state); snd_hda_codec_set_power_to_all(codec, fg, power_state); } state = snd_hda_sync_power_state(codec, fg, power_state); -- cgit v1.2.3 From cd8fd5a0566e0d93fbd408e6b06ca484a78b5ccd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:15 +0200 Subject: ALSA: hda: Add a simple GPIO setup helper function Introduce a common GPIO setup helper function, so that we can clean up the open code found in many codec drivers later. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-3-tiwai@suse.de --- include/sound/hda_codec.h | 4 ++++ sound/hda/common/codec.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index 292d6024388b..24581080e26a 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -481,6 +481,10 @@ void snd_hda_unlock_devices(struct hda_bus *bus); void snd_hda_bus_reset(struct hda_bus *bus); void snd_hda_bus_reset_codecs(struct hda_bus *bus); +void snd_hda_codec_set_gpio(struct hda_codec *codec, unsigned int mask, + unsigned int dir, unsigned int data, + unsigned int delay); + int snd_hda_codec_set_name(struct hda_codec *codec, const char *name); /* diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index 3ac4bf6005d6..c2af2511a831 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -4052,6 +4052,35 @@ void snd_hda_bus_reset_codecs(struct hda_bus *bus) } } +/** + * snd_hda_codec_set_gpio - Set up GPIO bits for AFG + * @codec: the HDA codec + * @mask: GPIO bitmask + * @dir: GPIO direction bits + * @data: GPIO data bits + * @delay: the delay in msec before writing GPIO data bits + */ +void snd_hda_codec_set_gpio(struct hda_codec *codec, unsigned int mask, + unsigned int dir, unsigned int data, + unsigned int delay) +{ + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_MASK, mask); + if (delay) { + snd_hda_codec_write_sync(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + msleep(delay); + snd_hda_codec_write_sync(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, data); + } else { + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, data); + } +} +EXPORT_SYMBOL_GPL(snd_hda_codec_set_gpio); + /** * snd_print_pcm_bits - Print the supported PCM fmt bits to the string buffer * @pcm: PCM caps bits -- cgit v1.2.3 From d19ecd85a245a2052a502f72bee83982f535c2e6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:16 +0200 Subject: ALSA: hda/realtek: Clean up with snd_hda_codec_set_gpio() Use a new helper function to clean up the code. Along with it, make alc_write_gpio() static as well, which is used only locally in realtek.c. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-4-tiwai@suse.de --- sound/hda/codecs/realtek/realtek.c | 13 ++++--------- sound/hda/codecs/realtek/realtek.h | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index 39a1ead3b743..d9b2f1993eaf 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -123,22 +123,17 @@ void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, } EXPORT_SYMBOL_NS_GPL(alc_update_gpio_data, "SND_HDA_CODEC_REALTEK"); -void alc_write_gpio(struct hda_codec *codec) +static void alc_write_gpio(struct hda_codec *codec) { struct alc_spec *spec = codec->spec; if (!spec->gpio_mask) return; - snd_hda_codec_write(codec, codec->core.afg, 0, - AC_VERB_SET_GPIO_MASK, spec->gpio_mask); - snd_hda_codec_write(codec, codec->core.afg, 0, - AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir); - if (spec->gpio_write_delay) - msleep(1); - alc_write_gpio_data(codec); + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, + spec->gpio_write_delay ? 1 : 0); } -EXPORT_SYMBOL_NS_GPL(alc_write_gpio, "SND_HDA_CODEC_REALTEK"); void alc_fixup_gpio(struct hda_codec *codec, int action, unsigned int mask) { diff --git a/sound/hda/codecs/realtek/realtek.h b/sound/hda/codecs/realtek/realtek.h index b2a919904c4c..2ca15a6c2a08 100644 --- a/sound/hda/codecs/realtek/realtek.h +++ b/sound/hda/codecs/realtek/realtek.h @@ -171,7 +171,6 @@ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask); void alc_write_gpio_data(struct hda_codec *codec); void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, bool on); -void alc_write_gpio(struct hda_codec *codec); /* common GPIO fixups */ void alc_fixup_gpio(struct hda_codec *codec, int action, unsigned int mask); -- cgit v1.2.3 From ef27d8ce0a3b55744b86e1866c62dc7537749180 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:17 +0200 Subject: ALSA: hda/alc662: Simplify the quirk for CSL Unity BF24B The previous implementation of the quirk for CSL Unity BF24B in commit de65275fc94e ("ALSA: hda/realtek: Add quirk for CSL Unity BF24B") introduced the unnecessary GPIO caching which leads to a superfluous write at each init/resume. Use the new helper to write GPIO bits directly for optimization. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-5-tiwai@suse.de --- sound/hda/codecs/realtek/alc662.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index 3abe41c7315c..2cf3664a35ff 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -258,19 +258,11 @@ static void alc_fixup_headset_mode_alc668(struct hda_codec *codec, static void alc662_fixup_csl_amp(struct hda_codec *codec, const struct hda_fixup *fix, int action) { - struct alc_spec *spec = codec->spec; - - switch (action) { - case HDA_FIXUP_ACT_PRE_PROBE: - spec->gpio_mask |= 0x03; - spec->gpio_dir |= 0x03; - break; - case HDA_FIXUP_ACT_INIT: + if (action == HDA_FIXUP_ACT_INIT) { /* need to toggle GPIO to enable the amp */ - alc_update_gpio_data(codec, 0x03, true); + snd_hda_codec_set_gpio(codec, 0x03, 0x03, 0x03, 0); msleep(100); - alc_update_gpio_data(codec, 0x03, false); - break; + snd_hda_codec_set_gpio(codec, 0x03, 0x03, 0x00, 0); } } -- cgit v1.2.3 From 735b3739517b18983cb0347ba56d76bf1018e0c4 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:18 +0200 Subject: ALSA: hda/analog: Fix GPIO verb orders So far we used the verb cache to restore the GPIO mask, direction and data bits at PM resume. But, due to the nature of the cache resume mechanism, the calling order isn't guaranteed, and this might lead to some inconsistency at the restored state. For assuring the GPIO verb orders, use the new GPIO helper function to explicitly set up the GPIO bits, instead of using the codec verb caches, while keeping the current data bits in ad198x_spec. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-6-tiwai@suse.de --- sound/hda/codecs/analog.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/hda/codecs/analog.c b/sound/hda/codecs/analog.c index 11b1d30b23fd..1ba8ae54e25e 100644 --- a/sound/hda/codecs/analog.c +++ b/sound/hda/codecs/analog.c @@ -38,6 +38,8 @@ struct ad198x_spec { unsigned int beep_amp; /* beep amp value, set via set_beep_amp() */ int num_smux_conns; + + unsigned int gpio_data; }; @@ -934,9 +936,9 @@ static void ad1884_vmaster_hp_gpio_hook(void *private_data, int enabled) if (spec->eapd_nid) ad_vmaster_eapd_hook(private_data, enabled); - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, - enabled ? 0x00 : 0x02); + spec->gpio_data = enabled ? 0x00 : 0x02; + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, spec->gpio_data); } static void ad1884_fixup_hp_eapd(struct hda_codec *codec, @@ -948,12 +950,7 @@ static void ad1884_fixup_hp_eapd(struct hda_codec *codec, case HDA_FIXUP_ACT_PRE_PROBE: spec->gen.vmaster_mute.hook = ad1884_vmaster_hp_gpio_hook; spec->gen.own_eapd_ctl = 1; - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_MASK, 0x02); - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x02); - snd_hda_codec_write_cache(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, 0x02); + spec->gpio_data = 0x02; break; case HDA_FIXUP_ACT_PROBE: if (spec->gen.autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT) @@ -961,6 +958,9 @@ static void ad1884_fixup_hp_eapd(struct hda_codec *codec, else spec->eapd_nid = spec->gen.autocfg.speaker_pins[0]; break; + case HDA_FIXUP_ACT_INIT: + snd_hda_codec_set_gpio(codec, 0x02, 0x02, spec->gpio_data, 0); + break; } } -- cgit v1.2.3 From 37e4fccc21aa0e812d77e3e3f7b8373475ee3715 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:19 +0200 Subject: ALSA: hda/sigmatel: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-7-tiwai@suse.de --- sound/hda/codecs/sigmatel.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sound/hda/codecs/sigmatel.c b/sound/hda/codecs/sigmatel.c index 4ff80a65168f..ee3bd21adc36 100644 --- a/sound/hda/codecs/sigmatel.c +++ b/sound/hda/codecs/sigmatel.c @@ -309,15 +309,7 @@ static void stac_gpio_set(struct hda_codec *codec, unsigned int mask, /* Configure GPIOx as CMOS */ snd_hda_codec_write(codec, fg, 0, 0x7e7, 0); - snd_hda_codec_write(codec, fg, 0, - AC_VERB_SET_GPIO_MASK, gpiomask); - snd_hda_codec_write_sync(codec, fg, 0, - AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */ - - msleep(1); - - snd_hda_codec_write_sync(codec, fg, 0, - AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */ + snd_hda_codec_set_gpio(codec, gpiomask, gpiodir, gpiostate, 1); } /* hook for controlling mic-mute LED GPIO */ -- cgit v1.2.3 From d35f8e8c6fc5c92c96be38e9ca94d99233ce1ddd Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:20 +0200 Subject: ALSA: hda/ca0132: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-8-tiwai@suse.de --- sound/hda/codecs/ca0132.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index a0677d7da8e2..ad533b04ab29 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -3755,22 +3755,12 @@ static void ca0132_gpio_setup(struct hda_codec *codec) switch (ca0132_quirk(spec)) { case QUIRK_SBZ: - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x07); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_MASK, 0x07); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, 0x04); + snd_hda_codec_set_gpio(codec, 0x07, 0x07, 0x04, 0); snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0x06); break; case QUIRK_R3DI: - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DIRECTION, 0x1E); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_MASK, 0x1F); - snd_hda_codec_write(codec, 0x01, 0, - AC_VERB_SET_GPIO_DATA, 0x0C); + snd_hda_codec_set_gpio(codec, 0x1F, 0x1E, 0x0C, 0); break; default: break; -- cgit v1.2.3 From daadb7fce1b53336acb195f34bd42d79754afa0e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:21 +0200 Subject: ALSA: hda/cirrus: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-9-tiwai@suse.de --- sound/hda/codecs/cirrus/cs420x.c | 11 +++-------- sound/hda/codecs/cirrus/cs421x.c | 11 +++-------- sound/hda/codecs/cirrus/cs8409.c | 22 ++++++---------------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/sound/hda/codecs/cirrus/cs420x.c b/sound/hda/codecs/cirrus/cs420x.c index 52eda0a338ea..42559edbba05 100644 --- a/sound/hda/codecs/cirrus/cs420x.c +++ b/sound/hda/codecs/cirrus/cs420x.c @@ -264,14 +264,9 @@ static int cs_init(struct hda_codec *codec) snd_hda_gen_init(codec); - if (spec->gpio_mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); if (spec->vendor_nid == CS420X_VENDOR_NID) { init_input_coef(codec); diff --git a/sound/hda/codecs/cirrus/cs421x.c b/sound/hda/codecs/cirrus/cs421x.c index c8349a2c5a36..645b06599e5f 100644 --- a/sound/hda/codecs/cirrus/cs421x.c +++ b/sound/hda/codecs/cirrus/cs421x.c @@ -442,14 +442,9 @@ static int cs421x_init(struct hda_codec *codec) snd_hda_gen_init(codec); - if (spec->gpio_mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); cs4210_spdif_automute(codec, NULL); diff --git a/sound/hda/codecs/cirrus/cs8409.c b/sound/hda/codecs/cirrus/cs8409.c index 2d8f482e6474..c43ff3ef75b6 100644 --- a/sound/hda/codecs/cirrus/cs8409.c +++ b/sound/hda/codecs/cirrus/cs8409.c @@ -1042,14 +1042,9 @@ static void cs8409_cs42l42_hw_init(struct hda_codec *codec) struct cs8409_spec *spec = codec->spec; struct sub_codec *cs42l42 = spec->scodecs[CS8409_CODEC0]; - if (spec->gpio_mask) { - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); for (; seq->nid; seq++) cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); @@ -1442,14 +1437,9 @@ static void dolphin_hw_init(struct hda_codec *codec) struct sub_codec *cs42l42; int i; - if (spec->gpio_mask) { - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, CS8409_PIN_AFG, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } + if (spec->gpio_mask) + snd_hda_codec_set_gpio(codec, spec->gpio_mask, spec->gpio_dir, + spec->gpio_data, 0); for (; seq->nid; seq++) cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); -- cgit v1.2.3 From 9851bc2b9013674ba7dc151843f29b6255fedba3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:22 +0200 Subject: ALSA: hda/conexant: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-10-tiwai@suse.de --- sound/hda/codecs/conexant.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index aa726eb323eb..3a9717df39b4 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -154,14 +154,8 @@ static void cxt_init_gpio_led(struct hda_codec *codec) struct conexant_spec *spec = codec->spec; unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; - if (mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_led); - } + if (mask) + snd_hda_codec_set_gpio(codec, mask, mask, spec->gpio_led, 0); } static void cx_fixup_headset_recog(struct hda_codec *codec) @@ -775,9 +769,7 @@ static void cxt_setup_gpio_unmute(struct hda_codec *codec, { if (gpio_mute_mask) { // set gpio data to 0. - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, gpio_mute_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, gpio_mute_mask); + snd_hda_codec_set_gpio(codec, gpio_mute_mask, gpio_mute_mask, 0, 0); snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_STICKY_MASK, 0); } } -- cgit v1.2.3 From c2938a83a257ca1e3a8e74b385f543d6bd6eab8b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 11:38:23 +0200 Subject: ALSA: hda/senarytech: Clean up with the new GPIO helper Use the new GPIO helper function to clean up the open code. Merely a code refactoring, and no behavior change. Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260409093826.1317626-11-tiwai@suse.de --- sound/hda/codecs/senarytech.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/sound/hda/codecs/senarytech.c b/sound/hda/codecs/senarytech.c index 29b554cdd81d..3ee8bc0ea3ab 100644 --- a/sound/hda/codecs/senarytech.c +++ b/sound/hda/codecs/senarytech.c @@ -159,14 +159,8 @@ static void senary_init_gpio_led(struct hda_codec *codec) struct senary_spec *spec = codec->spec; unsigned int mask = spec->gpio_mute_led_mask | spec->gpio_mic_led_mask; - if (mask) { - snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_MASK, - mask); - snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DIRECTION, - mask); - snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_led); - } + if (mask) + snd_hda_codec_set_gpio(codec, mask, mask, spec->gpio_led, 0); } static int senary_init(struct hda_codec *codec) -- cgit v1.2.3 From 4f84e6caf38b05991b3b2afc0ddf4e48c2752d1d Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Thu, 9 Apr 2026 02:33:05 +0800 Subject: ALSA: usb-audio: Add quirk flags for Feaulle Rainbow Feaulle Rainbow is a wired USB-C dynamic in-ear monitor (IEM) featuring active noise cancellation (ANC). The supported sample rates are 48000Hz and 96000Hz at 16bit or 24bit, but it does not support reading the current sample rate and results in an error message printed to kmsg. Set QUIRK_FLAG_GET_SAMPLE_RATE to skip the sample rate check. Its playback mixer reports val = -15360/0/128. Setting -15360 (-60dB) mutes the playback, so QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE is needed. Add a quirk table entry matching VID/PID=0x0e0b/0xfa01 and applying the mentioned quirk flags, so that it can work properly. Quirky device sample: usb 7-1: New USB device found, idVendor=0e0b, idProduct=fa01, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Feaulle Rainbow usb 7-1: Manufacturer: Generic usb 7-1: SerialNumber: 20210726905926 Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260409-feaulle-rainbow-v1-1-09179e09000d@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/quirks.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 6cf85922b7be..519d9d1a2a41 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2337,6 +2337,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0d8c, 0x0014, /* C-Media */ QUIRK_FLAG_CTL_MSG_DELAY_1M | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), + DEVICE_FLG(0x0e0b, 0xfa01, /* Feaulle Rainbow */ + QUIRK_FLAG_GET_SAMPLE_RATE | QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE), DEVICE_FLG(0x0ecb, 0x205c, /* JBL Quantum610 Wireless */ QUIRK_FLAG_FIXED_RATE), DEVICE_FLG(0x0ecb, 0x2069, /* JBL Quantum810 Wireless */ -- cgit v1.2.3 From 01f218d439acd5e129d214ea57e760ee2e34e869 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 9 Apr 2026 16:37:29 +0200 Subject: ALSA: hda/alc269: Drop superfluous GPIO write at resume alc269_resume() has an extra code to write GPIO data, but this is basically already done in the standard alc_init(), hence it's superfluous. Let's drop the code. Since all external callers of alc_write_gpio_data() are gone after this, fold the only usage of alc_write_gpio_data() into the caller and drop the export as well. Link: https://patch.msgid.link/20260409143735.1412134-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 7 ------- sound/hda/codecs/realtek/realtek.c | 12 ++---------- sound/hda/codecs/realtek/realtek.h | 1 - 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 9a799919e0c8..0a5eadec3ef6 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -1004,13 +1004,6 @@ static int alc269_resume(struct hda_codec *codec) snd_hda_regmap_sync(codec); hda_call_check_power_status(codec, 0x01); - /* on some machine, the BIOS will clear the codec gpio data when enter - * suspend, and won't restore the data after resume, so we restore it - * in the driver. - */ - if (spec->gpio_data) - alc_write_gpio_data(codec); - if (spec->has_alc5505_dsp) alc5505_dsp_resume(codec); diff --git a/sound/hda/codecs/realtek/realtek.c b/sound/hda/codecs/realtek/realtek.c index d9b2f1993eaf..db365a746b1a 100644 --- a/sound/hda/codecs/realtek/realtek.c +++ b/sound/hda/codecs/realtek/realtek.c @@ -99,15 +99,6 @@ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask) } EXPORT_SYMBOL_NS_GPL(alc_setup_gpio, "SND_HDA_CODEC_REALTEK"); -void alc_write_gpio_data(struct hda_codec *codec) -{ - struct alc_spec *spec = codec->spec; - - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); -} -EXPORT_SYMBOL_NS_GPL(alc_write_gpio_data, "SND_HDA_CODEC_REALTEK"); - void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, bool on) { @@ -119,7 +110,8 @@ void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, else spec->gpio_data &= ~mask; if (oldval != spec->gpio_data) - alc_write_gpio_data(codec); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, + spec->gpio_data); } EXPORT_SYMBOL_NS_GPL(alc_update_gpio_data, "SND_HDA_CODEC_REALTEK"); diff --git a/sound/hda/codecs/realtek/realtek.h b/sound/hda/codecs/realtek/realtek.h index 2ca15a6c2a08..de95642bb648 100644 --- a/sound/hda/codecs/realtek/realtek.h +++ b/sound/hda/codecs/realtek/realtek.h @@ -168,7 +168,6 @@ void alc_process_coef_fw(struct hda_codec *codec, const struct coef_fw *fw); * GPIO helpers */ void alc_setup_gpio(struct hda_codec *codec, unsigned int mask); -void alc_write_gpio_data(struct hda_codec *codec); void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask, bool on); -- cgit v1.2.3 From 48bd344e1040b9f2eb512be73c13f5db83efc191 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 16:01:56 +0200 Subject: ALSA: usx2y: us144mkii: fix NULL deref on missing interface 0 A malicious USB device with the TASCAM US-144MKII device id can have a configuration containing bInterfaceNumber=1 but no interface 0. USB configuration descriptors are not required to assign interface numbers sequentially, so usb_ifnum_to_if(dev, 0) returns will NULL, which will then be dereferenced directly. Fix this up by checking the return value properly. Cc: Jaroslav Kysela Cc: Takashi Iwai Fixes: dee1bcf28a3d ("ALSA: usb-audio: Add initial driver for TASCAM US-144MKII") Cc: stable Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Link: https://patch.msgid.link/2026040955-fall-gaining-e338@gregkh Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us144mkii.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/usb/usx2y/us144mkii.c b/sound/usb/usx2y/us144mkii.c index 0cf4fa74e210..94553b61013c 100644 --- a/sound/usb/usx2y/us144mkii.c +++ b/sound/usb/usx2y/us144mkii.c @@ -420,7 +420,11 @@ static int tascam_probe(struct usb_interface *intf, /* The device has two interfaces; we drive both from this driver. */ if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { - tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0)); + struct usb_interface *intf_zero = usb_ifnum_to_if(dev, 0); + + if (!intf_zero) + return -ENODEV; + tascam = usb_get_intfdata(intf_zero); if (tascam) { usb_set_intfdata(intf, tascam); tascam->iface1 = intf; -- cgit v1.2.3 From 07704bbf36f57e4379e4cadf96410dab14621e3b Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Thu, 9 Apr 2026 16:05:54 +0200 Subject: ALSA: fireworks: bound device-supplied status before string array lookup The status field in an EFW response is a 32-bit value supplied by the firewire device. efr_status_names[] has 17 entries so a status value outside that range goes off into the weeds when looking at the %s value. Even worse, the status could return EFR_STATUS_INCOMPLETE which is 0x80000000, and is obviously not in that array of potential strings. Fix this up by properly bounding the index against the array size and printing "unknown" if it's not recognized. Cc: Clemens Ladisch Cc: Takashi Sakamoto Cc: Jaroslav Kysela Cc: Takashi Iwai Fixes: bde8a8f23bbe ("ALSA: fireworks: Add transaction and some commands") Cc: stable Assisted-by: gregkh_clanker_t1000 Signed-off-by: Greg Kroah-Hartman Reviewed-by: Takashi Sakamoto Link: https://patch.msgid.link/2026040953-astute-camera-1aa1@gregkh Signed-off-by: Takashi Iwai --- sound/firewire/fireworks/fireworks_command.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c index 2b595ee0bc35..05550f36fac5 100644 --- a/sound/firewire/fireworks/fireworks_command.c +++ b/sound/firewire/fireworks/fireworks_command.c @@ -151,10 +151,13 @@ efw_transaction(struct snd_efw *efw, unsigned int category, (be32_to_cpu(header->category) != category) || (be32_to_cpu(header->command) != command) || (be32_to_cpu(header->status) != EFR_STATUS_OK)) { + u32 st = be32_to_cpu(header->status); + dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n", be32_to_cpu(header->category), be32_to_cpu(header->command), - efr_status_names[be32_to_cpu(header->status)]); + st < ARRAY_SIZE(efr_status_names) ? + efr_status_names[st] : "unknown"); err = -EIO; goto end; } -- cgit v1.2.3 From b9c826916fdce6419b94eb0cd8810fdac18c2386 Mon Sep 17 00:00:00 2001 From: Berk Cem Goksel Date: Fri, 10 Apr 2026 08:13:41 +0300 Subject: ALSA: 6fire: fix use-after-free on disconnect In usb6fire_chip_abort(), the chip struct is allocated as the card's private data (via snd_card_new with sizeof(struct sfire_chip)). When snd_card_free_when_closed() is called and no file handles are open, the card and embedded chip are freed synchronously. The subsequent chip->card = NULL write then hits freed slab memory. Call trace: usb6fire_chip_abort sound/usb/6fire/chip.c:59 [inline] usb6fire_chip_disconnect+0x348/0x358 sound/usb/6fire/chip.c:182 usb_unbind_interface+0x1a8/0x88c drivers/usb/core/driver.c:458 ... hub_event+0x1a04/0x4518 drivers/usb/core/hub.c:5953 Fix by moving the card lifecycle out of usb6fire_chip_abort() and into usb6fire_chip_disconnect(). The card pointer is saved in a local before any teardown, snd_card_disconnect() is called first to prevent new opens, URBs are aborted while chip is still valid, and snd_card_free_when_closed() is called last so chip is never accessed after the card may be freed. Fixes: a0810c3d6dd2 ("ALSA: 6fire: Release resources at card release") Cc: stable@vger.kernel.org Cc: Andrey Konovalov Signed-off-by: Berk Cem Goksel Link: https://patch.msgid.link/20260410051341.1069716-1-berkcgoksel@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/6fire/chip.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/sound/usb/6fire/chip.c b/sound/usb/6fire/chip.c index 5ff78814e687..874f6cd503ca 100644 --- a/sound/usb/6fire/chip.c +++ b/sound/usb/6fire/chip.c @@ -53,11 +53,6 @@ static void usb6fire_chip_abort(struct sfire_chip *chip) usb6fire_comm_abort(chip); if (chip->control) usb6fire_control_abort(chip); - if (chip->card) { - snd_card_disconnect(chip->card); - snd_card_free_when_closed(chip->card); - chip->card = NULL; - } } } @@ -168,6 +163,7 @@ destroy_chip: static void usb6fire_chip_disconnect(struct usb_interface *intf) { struct sfire_chip *chip; + struct snd_card *card; chip = usb_get_intfdata(intf); if (chip) { /* if !chip, fw upload has been performed */ @@ -178,8 +174,19 @@ static void usb6fire_chip_disconnect(struct usb_interface *intf) chips[chip->regidx] = NULL; } + /* + * Save card pointer before teardown. + * snd_card_free_when_closed() may free card (and + * the embedded chip) immediately, so it must be + * called last and chip must not be accessed after. + */ + card = chip->card; chip->shutdown = true; + if (card) + snd_card_disconnect(card); usb6fire_chip_abort(chip); + if (card) + snd_card_free_when_closed(card); } } } -- cgit v1.2.3 From fb79bf127ac2577b4876132da6dba768018aad4c Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Fri, 10 Apr 2026 00:54:32 -0300 Subject: ALSA: sc6000: Keep the programmed board state in card-private data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver may auto-select IRQ and DMA resources at probe time, but sc6000_init_board() still derives the SC-6000 soft configuration from the module parameter arrays. When irq=auto or dma=auto is used, the codec is created with the selected resources while the board is programmed with the unresolved values. Store the mapped ports and generated SC-6000 board configuration in card-private data, build that configuration from the live probe results instead of the raw module parameters, and keep the probe-time board programming in a shared helper. This fixes the resource-programming mismatch and leaves the driver with a stable board-state block that can be reused by suspend/resume. Fixes: c282866101bf ("ALSA: sc6000: add support for SC-6600 and SC-7000") Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260410-alsa-sc6000-pm-v1-1-4d9e95493d26@gmail.com --- sound/isa/sc6000.c | 152 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 60 deletions(-) diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index 6d618cc2ba45..9949e06403f6 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -100,6 +100,15 @@ MODULE_PARM_DESC(joystick, "Enable gameport."); #define PFX "sc6000: " #define DRV_NAME "SC-6000" +struct snd_sc6000 { + char __iomem *vport; + char __iomem *vmss_port; + u8 mss_config; + u8 config; + u8 hw_cfg[2]; + bool old_dsp; +}; + /* hardware dependent functions */ /* @@ -267,7 +276,7 @@ static int sc6000_dsp_reset(char __iomem *vport) /* detection and initialization */ static int sc6000_hw_cfg_write(struct device *devptr, - char __iomem *vport, const int *cfg) + char __iomem *vport, const u8 *cfg) { if (sc6000_write(devptr, vport, COMMAND_6C) < 0) { dev_warn(devptr, "CMD 0x%x: failed!\n", COMMAND_6C); @@ -353,8 +362,7 @@ static int sc6000_init_mss(struct device *devptr, return 0; } -static void sc6000_hw_cfg_encode(struct device *devptr, - char __iomem *vport, int *cfg, +static void sc6000_hw_cfg_encode(struct device *devptr, u8 *cfg, long xport, long xmpu, long xmss_port, int joystick) { @@ -376,27 +384,83 @@ static void sc6000_hw_cfg_encode(struct device *devptr, dev_dbg(devptr, "hw cfg %x, %x\n", cfg[0], cfg[1]); } -static int sc6000_init_board(struct device *devptr, - char __iomem *vport, - char __iomem *vmss_port, int dev) +static void sc6000_prepare_board(struct device *devptr, + struct snd_sc6000 *sc6000, + unsigned int dev, int xirq, int xdma) +{ + sc6000->mss_config = sc6000_irq_to_softcfg(xirq) | + sc6000_dma_to_softcfg(xdma); + sc6000->config = sc6000->mss_config | + sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); + sc6000_hw_cfg_encode(devptr, sc6000->hw_cfg, port[dev], mpu_port[dev], + mss_port[dev], joystick[dev]); +} + +static void sc6000_detect_old_dsp(struct device *devptr, + struct snd_sc6000 *sc6000) +{ + sc6000_write(devptr, sc6000->vport, COMMAND_5C); + sc6000->old_dsp = sc6000_read(sc6000->vport) < 0; +} + +static int sc6000_program_board(struct device *devptr, + struct snd_sc6000 *sc6000) +{ + int err; + + if (!sc6000->old_dsp) { + if (sc6000_hw_cfg_write(devptr, sc6000->vport, + sc6000->hw_cfg) < 0) { + dev_err(devptr, "sc6000_hw_cfg_write: failed!\n"); + return -EIO; + } + } + + err = sc6000_setup_board(devptr, sc6000->vport, sc6000->config); + if (err < 0) { + dev_err(devptr, "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + + sc6000_dsp_reset(sc6000->vport); + + if (!sc6000->old_dsp) { + sc6000_write(devptr, sc6000->vport, COMMAND_60); + sc6000_write(devptr, sc6000->vport, 0x02); + sc6000_dsp_reset(sc6000->vport); + } + + err = sc6000_setup_board(devptr, sc6000->vport, sc6000->config); + if (err < 0) { + dev_err(devptr, "sc6000_setup_board: failed!\n"); + return -ENODEV; + } + + err = sc6000_init_mss(devptr, sc6000->vport, sc6000->config, + sc6000->vmss_port, sc6000->mss_config); + if (err < 0) { + dev_err(devptr, "Cannot initialize Microsoft Sound System mode.\n"); + return -ENODEV; + } + + return 0; +} + +static int sc6000_init_board(struct device *devptr, struct snd_sc6000 *sc6000) { char answer[15]; char version[2]; - int mss_config = sc6000_irq_to_softcfg(irq[dev]) | - sc6000_dma_to_softcfg(dma[dev]); - int config = mss_config | - sc6000_mpu_irq_to_softcfg(mpu_irq[dev]); int err; - int old = 0; - err = sc6000_dsp_reset(vport); + err = sc6000_dsp_reset(sc6000->vport); if (err < 0) { dev_err(devptr, "sc6000_dsp_reset: failed!\n"); return err; } memset(answer, 0, sizeof(answer)); - err = sc6000_dsp_get_answer(devptr, vport, GET_DSP_COPYRIGHT, answer, 15); + err = sc6000_dsp_get_answer(devptr, sc6000->vport, GET_DSP_COPYRIGHT, + answer, 15); if (err <= 0) { dev_err(devptr, "sc6000_dsp_copyright: failed!\n"); return -ENODEV; @@ -408,54 +472,17 @@ static int sc6000_init_board(struct device *devptr, if (strncmp("SC-6000", answer, 7)) dev_warn(devptr, "Warning: non SC-6000 audio card!\n"); - if (sc6000_dsp_get_answer(devptr, vport, GET_DSP_VERSION, version, 2) < 2) { + if (sc6000_dsp_get_answer(devptr, sc6000->vport, + GET_DSP_VERSION, version, 2) < 2) { dev_err(devptr, "sc6000_dsp_version: failed!\n"); return -ENODEV; } dev_info(devptr, "Detected model: %s, DSP version %d.%d\n", answer, version[0], version[1]); - /* set configuration */ - sc6000_write(devptr, vport, COMMAND_5C); - if (sc6000_read(vport) < 0) - old = 1; - - if (!old) { - int cfg[2]; - sc6000_hw_cfg_encode(devptr, - vport, &cfg[0], port[dev], mpu_port[dev], - mss_port[dev], joystick[dev]); - if (sc6000_hw_cfg_write(devptr, vport, cfg) < 0) { - dev_err(devptr, "sc6000_hw_cfg_write: failed!\n"); - return -EIO; - } - } - err = sc6000_setup_board(devptr, vport, config); - if (err < 0) { - dev_err(devptr, "sc6000_setup_board: failed!\n"); - return -ENODEV; - } - - sc6000_dsp_reset(vport); - - if (!old) { - sc6000_write(devptr, vport, COMMAND_60); - sc6000_write(devptr, vport, 0x02); - sc6000_dsp_reset(vport); - } + sc6000_detect_old_dsp(devptr, sc6000); - err = sc6000_setup_board(devptr, vport, config); - if (err < 0) { - dev_err(devptr, "sc6000_setup_board: failed!\n"); - return -ENODEV; - } - err = sc6000_init_mss(devptr, vport, config, vmss_port, mss_config); - if (err < 0) { - dev_err(devptr, "Cannot initialize Microsoft Sound System mode.\n"); - return -ENODEV; - } - - return 0; + return sc6000_program_board(devptr, sc6000); } static int snd_sc6000_mixer(struct snd_wss *chip) @@ -538,10 +565,10 @@ static int snd_sc6000_match(struct device *devptr, unsigned int dev) static void snd_sc6000_free(struct snd_card *card) { - char __iomem *vport = (char __force __iomem *)card->private_data; + struct snd_sc6000 *sc6000 = card->private_data; - if (vport) - sc6000_setup_board(card->dev, vport, 0); + if (sc6000->vport) + sc6000_setup_board(card->dev, sc6000->vport, 0); } static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) @@ -552,15 +579,17 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) int xirq = irq[dev]; int xdma = dma[dev]; struct snd_card *card; + struct snd_sc6000 *sc6000; struct snd_wss *chip; struct snd_opl3 *opl3; char __iomem *vport; char __iomem *vmss_port; err = snd_devm_card_new(devptr, index[dev], id[dev], THIS_MODULE, - 0, &card); + sizeof(*sc6000), &card); if (err < 0) return err; + sc6000 = card->private_data; if (xirq == SNDRV_AUTO_IRQ) { xirq = snd_legacy_find_free_irq(possible_irqs); @@ -587,7 +616,7 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) dev_err(devptr, "I/O port cannot be iomapped.\n"); return -EBUSY; } - card->private_data = (void __force *)vport; + sc6000->vport = vport; /* to make it marked as used */ if (!devm_request_region(devptr, mss_port[dev], 4, DRV_NAME)) { @@ -600,12 +629,15 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) dev_err(devptr, "MSS port I/O cannot be iomapped.\n"); return -EBUSY; } + sc6000->vmss_port = vmss_port; dev_dbg(devptr, "Initializing BASE[0x%lx] IRQ[%d] DMA[%d] MIRQ[%d]\n", port[dev], xirq, xdma, mpu_irq[dev] == SNDRV_AUTO_IRQ ? 0 : mpu_irq[dev]); - err = sc6000_init_board(devptr, vport, vmss_port, dev); + sc6000_prepare_board(devptr, sc6000, dev, xirq, xdma); + + err = sc6000_init_board(devptr, sc6000); if (err < 0) return err; card->private_free = snd_sc6000_free; -- cgit v1.2.3 From 47f72d57ddb11222479c80bd07f5bc036d84c94d Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Fri, 10 Apr 2026 00:54:33 -0300 Subject: ALSA: sc6000: Restore board setup across suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_wss_resume() restores only the codec register image. The SC-6000 driver also programs card-specific DSP routing and enters MSS mode during probe, and that setup is not replayed after suspend. Cache the WSS chip pointer in the SC-6000 card state and wire ISA suspend and resume callbacks to the shared board-programming helper, so the board is reinitialized before the codec state is restored. This keeps the old/new DSP split in one place and restores the board-level MSS setup that the codec resume path does not cover. Signed-off-by: Cássio Gabriel Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20260410-alsa-sc6000-pm-v1-2-4d9e95493d26@gmail.com --- sound/isa/sc6000.c | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/sound/isa/sc6000.c b/sound/isa/sc6000.c index 9949e06403f6..cd3a63c7c2a7 100644 --- a/sound/isa/sc6000.c +++ b/sound/isa/sc6000.c @@ -103,6 +103,7 @@ MODULE_PARM_DESC(joystick, "Enable gameport."); struct snd_sc6000 { char __iomem *vport; char __iomem *vmss_port; + struct snd_wss *chip; u8 mss_config; u8 config; u8 hw_cfg[2]; @@ -646,6 +647,7 @@ static int __snd_sc6000_probe(struct device *devptr, unsigned int dev) WSS_HW_DETECT, 0, &chip); if (err < 0) return err; + sc6000->chip = chip; err = snd_wss_pcm(chip, 0); if (err < 0) { @@ -702,10 +704,47 @@ static int snd_sc6000_probe(struct device *devptr, unsigned int dev) return snd_card_free_on_error(devptr, __snd_sc6000_probe(devptr, dev)); } +#ifdef CONFIG_PM +static int snd_sc6000_suspend(struct device *devptr, unsigned int dev, + pm_message_t state) +{ + struct snd_card *card = dev_get_drvdata(devptr); + struct snd_sc6000 *sc6000 = card->private_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + sc6000->chip->suspend(sc6000->chip); + return 0; +} + +static int snd_sc6000_resume(struct device *devptr, unsigned int dev) +{ + struct snd_card *card = dev_get_drvdata(devptr); + struct snd_sc6000 *sc6000 = card->private_data; + int err; + + err = sc6000_dsp_reset(sc6000->vport); + if (err < 0) { + dev_err(devptr, "sc6000_dsp_reset: failed!\n"); + return err; + } + + err = sc6000_program_board(devptr, sc6000); + if (err < 0) + return err; + + sc6000->chip->resume(sc6000->chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif + static struct isa_driver snd_sc6000_driver = { .match = snd_sc6000_match, .probe = snd_sc6000_probe, - /* FIXME: suspend/resume */ +#ifdef CONFIG_PM + .suspend = snd_sc6000_suspend, + .resume = snd_sc6000_resume, +#endif .driver = { .name = DRV_NAME, }, -- cgit v1.2.3 From b7feba842c0d5f6c5b01592f80d164e974767501 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Fri, 10 Apr 2026 10:56:52 -0300 Subject: ALSA: interwave: guard PM-only restore helpers with CONFIG_PM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The InterWave PM patch added snd_interwave_restore_regs() and snd_interwave_restore_memory() as static helpers, but both are used only from the resume path under CONFIG_PM. On configurations without CONFIG_PM, such as alpha allyesconfig, this leaves both helpers unused and triggers -Wunused-function warnings with W=1. Move the PM-only helpers into the existing CONFIG_PM section. Keep __snd_interwave_restore_regs() outside the guard because it is also used during probe-time initialization. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202604101958.x16oNkfo-lkp@intel.com/ Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260410-alsa-interwave-pm-warning-fix-v1-1-434d14c9c262@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/gus/interwave.c | 76 +++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c index 616c11e51a2f..6c3a2977dcb3 100644 --- a/sound/isa/gus/interwave.c +++ b/sound/isa/gus/interwave.c @@ -480,44 +480,6 @@ static void __snd_interwave_restore_regs(struct snd_gus_card *gus) snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); } -static void snd_interwave_restore_regs(struct snd_gus_card *gus) -{ - scoped_guard(spinlock_irqsave, &gus->reg_lock) - __snd_interwave_restore_regs(gus); -} - -static void snd_interwave_restore_memory(struct snd_gus_card *gus) -{ - unsigned short mem_cfg; - unsigned int lmct = 0; - int i, lmc_cfg; - - if (!gus->gf1.memory) - return; - - for (i = 0; i < 4; i++) - lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); - - lmc_cfg = snd_interwave_find_memory_config(lmct); - if (lmc_cfg < 0) { - if (!gus->gf1.enh_mode) { - lmc_cfg = 2; - } else { - dev_warn(gus->card->dev, - "cannot restore InterWave memory layout 0x%08x\n", - lmct); - return; - } - } - - scoped_guard(spinlock_irqsave, &gus->reg_lock) { - mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); - mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; - mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); - snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); - } -} - static void snd_interwave_init(int dev, struct snd_gus_card *gus) { /* Probe-time setup also clears the timer control register. */ @@ -888,6 +850,44 @@ static int snd_interwave_isa_probe(struct device *pdev, } #ifdef CONFIG_PM +static void snd_interwave_restore_regs(struct snd_gus_card *gus) +{ + scoped_guard(spinlock_irqsave, &gus->reg_lock) + __snd_interwave_restore_regs(gus); +} + +static void snd_interwave_restore_memory(struct snd_gus_card *gus) +{ + unsigned short mem_cfg; + unsigned int lmct = 0; + int i, lmc_cfg; + + if (!gus->gf1.memory) + return; + + for (i = 0; i < 4; i++) + lmct |= (gus->gf1.mem_alloc.banks_16[i].size >> 18) << (i * 8); + + lmc_cfg = snd_interwave_find_memory_config(lmct); + if (lmc_cfg < 0) { + if (!gus->gf1.enh_mode) { + lmc_cfg = 2; + } else { + dev_warn(gus->card->dev, + "cannot restore InterWave memory layout 0x%08x\n", + lmct); + return; + } + } + + scoped_guard(spinlock_irqsave, &gus->reg_lock) { + mem_cfg = snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG); + mem_cfg = (mem_cfg & 0xfff0) | lmc_cfg; + mem_cfg = (mem_cfg & 0xff1f) | (4 << 5); + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, mem_cfg); + } +} + static int snd_interwave_card_suspend(struct snd_card *card) { struct snd_interwave *iwcard = card->private_data; -- cgit v1.2.3 From 34fe4a9df2476f52a809d0cd9659ff73de605774 Mon Sep 17 00:00:00 2001 From: Abhinav Mahadevan Date: Fri, 10 Apr 2026 20:03:35 +0530 Subject: ALSA: usb-audio: Add quirk for PreSonus AudioBox USB The PreSonus AudioBox USB (0x194f:0x0301) only supports S24_3LE format for both playback and capture. It does not support S16_LE despite being a USB full-speed device. Add explicit format quirks for both the playback (interface 2) and capture (interface 3) interfaces to ensure correct format negotiation. Signed-off-by: Abhinav Mahadevan Link: https://patch.msgid.link/20260410143335.5974-1-abhi220204@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index b6dfe3b63c67..803e03d4d77b 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -2652,6 +2652,54 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, +{ + /* + * The AudioBox USB advertises S24_3LE as the only supported format + * for both playback and capture. It does not support S16_LE despite + * being a USB full-speed device. + */ + USB_DEVICE(0x194f, 0x0301), + QUIRK_DRIVER_INFO { + .vendor_name = "PreSonus", + .product_name = "AudioBox USB", + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_IGNORE(0) }, + { + QUIRK_DATA_AUDIOFORMAT(2) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 2, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x01, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + } + }, + { + QUIRK_DATA_AUDIOFORMAT(3) { + .formats = SNDRV_PCM_FMTBIT_S24_3LE, + .channels = 2, + .iface = 3, + .altsetting = 1, + .altset_idx = 1, + .attributes = 0, + .endpoint = 0x82, + .ep_attr = USB_ENDPOINT_XFER_ISOC, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + } + }, + QUIRK_COMPOSITE_END + } + } +}, #endif /* disabled */ { -- cgit v1.2.3 From 4f55a85cd4fc988712965f710ba1475e7ba3292a Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 11 Apr 2026 01:49:02 +0800 Subject: ALSA: usb-audio: Add error checks against get_min_max*() All callers of get_min_max*() ignore the latter's return code completely. This means to ignore temporary errors at the probe time. However, it is not optimal and leads to some maintenance burdens. Return -EAGAIN for temporary errors, and check against it in the callers of get_min_max*(). If any other error occurs, bail out of the caller early. Suggested-by: Takashi Iwai Link: https://lore.kernel.org/r/87ldewi4j8.wl-tiwai@suse.de Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-1-29d62717befd@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index a25e8145af67..e5993364c825 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1264,7 +1264,7 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, "%d:%d: cannot get min/max values for control %d (id %d)\n", cval->head.id, mixer_ctrl_intf(cval->head.mixer), cval->control, cval->head.id); - return -EINVAL; + return -EAGAIN; } if (get_ctl_value(cval, UAC_GET_RES, (cval->control << 8) | minchn, @@ -1388,6 +1388,7 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct usb_mixer_elem_info *cval = snd_kcontrol_chip(kcontrol); + int ret; if (cval->val_type == USB_MIXER_BOOLEAN || cval->val_type == USB_MIXER_INV_BOOLEAN) @@ -1398,8 +1399,9 @@ static int mixer_ctl_feature_info(struct snd_kcontrol *kcontrol, if (cval->val_type != USB_MIXER_BOOLEAN && cval->val_type != USB_MIXER_INV_BOOLEAN) { if (!cval->initialized) { - get_min_max_with_quirks(cval, 0, kcontrol); - if (cval->initialized && cval->dBmin >= cval->dBmax) { + ret = get_min_max_with_quirks(cval, 0, kcontrol); + if ((ret >= 0 || ret == -EAGAIN) && + cval->initialized && cval->dBmin >= cval->dBmax) { kcontrol->vd[0].access &= ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); @@ -1743,6 +1745,7 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl; struct usb_mixer_elem_info *cval; const struct usbmix_name_map *map; + int ret; if (control == UAC_FU_GRAPHIC_EQUALIZER) { /* FIXME: not supported yet */ @@ -1856,10 +1859,10 @@ static void __build_feature_ctl(struct usb_mixer_interface *mixer, } /* get min/max values */ - get_min_max_with_quirks(cval, 0, kctl); + ret = get_min_max_with_quirks(cval, 0, kctl); /* skip a bogus volume range */ - if (cval->max <= cval->min) { + if ((ret < 0 && ret != -EAGAIN) || cval->max <= cval->min) { usb_audio_dbg(mixer->chip, "[%d] FU [%s] skipped due to invalid volume\n", cval->head.id, kctl->id.name); @@ -2233,6 +2236,7 @@ static void build_mixer_unit_ctl(struct mixer_build *state, unsigned int i, len; struct snd_kcontrol *kctl; const struct usbmix_name_map *map; + int ret; map = find_map(state->map, unitid, 0); if (check_ignored_ctl(map)) @@ -2255,7 +2259,11 @@ static void build_mixer_unit_ctl(struct mixer_build *state, } /* get min/max values */ - get_min_max(cval, 0); + ret = get_min_max(cval, 0); + if (ret < 0 && ret != -EAGAIN) { + usb_mixer_elem_info_free(cval); + return; + } kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval); if (!kctl) { @@ -2627,7 +2635,7 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, break; } - get_min_max(cval, valinfo->min_value); + err = get_min_max(cval, valinfo->min_value); break; } case USB_XU_CLOCK_RATE: @@ -2639,11 +2647,16 @@ static int build_audio_procunit(struct mixer_build *state, int unitid, cval->max = 5; cval->res = 1; cval->initialized = 1; + err = 0; break; default: - get_min_max(cval, valinfo->min_value); + err = get_min_max(cval, valinfo->min_value); break; } + if (err < 0 && err != -EAGAIN) { + usb_mixer_elem_info_free(cval); + return err; + } err = get_cur_ctl_value(cval, cval->control << 8, &val); if (err < 0) { -- cgit v1.2.3 From e3ad86a82868fcde16f213240a60891c2d7bbec4 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 11 Apr 2026 01:49:03 +0800 Subject: ALSA: usb-audio: Move volume control resolution check into a function get_min_max_with_quirks() is too lengthy and hard to read. Move the volume control resolution check code into a function as it's relatively self-contained. Suggested-by: Takashi Iwai Link: https://lore.kernel.org/r/87o6jsk3vs.wl-tiwai@suse.de Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-2-29d62717befd@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 65 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e5993364c825..e77c2d78a782 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1232,6 +1232,38 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) snd_usb_set_cur_mix_value(cval, ch, idx, cval->min); } +/* + * Additional checks for the proper resolution + * + * Some devices report smaller resolutions than actually reacting. + * They don't return errors but simply clip to the lower aligned value. + */ +static void check_volume_control_res(struct usb_mixer_elem_info *cval, + int channel, int saved) +{ + int last_valid_res = cval->res; + int test, check; + + for (;;) { + test = saved; + if (test < cval->max) + test += cval->res; + else + test -= cval->res; + + if (test < cval->min || test > cval->max || + snd_usb_set_cur_mix_value(cval, channel, 0, test) || + get_cur_mix_raw(cval, channel, &check)) { + cval->res = last_valid_res; + break; + } + if (test == check) + break; + + cval->res *= 2; + } +} + /* * retrieve the minimum and maximum values for the specified control */ @@ -1287,37 +1319,18 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (cval->res == 0) cval->res = 1; - /* Additional checks for the proper resolution - * - * Some devices report smaller resolutions than actually - * reacting. They don't return errors but simply clip - * to the lower aligned value. - */ if (cval->min + cval->res < cval->max) { - int last_valid_res = cval->res; - int saved, test, check; + int saved; + if (get_cur_mix_raw(cval, minchn, &saved) < 0) - goto no_res_check; - for (;;) { - test = saved; - if (test < cval->max) - test += cval->res; - else - test -= cval->res; - if (test < cval->min || test > cval->max || - snd_usb_set_cur_mix_value(cval, minchn, 0, test) || - get_cur_mix_raw(cval, minchn, &check)) { - cval->res = last_valid_res; - break; - } - if (test == check) - break; - cval->res *= 2; - } + goto no_checks; + + check_volume_control_res(cval, minchn, saved); + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); } -no_res_check: +no_checks: cval->initialized = 1; } -- cgit v1.2.3 From 86aa1ea1f15ce6b56ac1b4c0d9b88a07a5b9bf03 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 11 Apr 2026 01:49:04 +0800 Subject: ALSA: usb-audio: Do not expose sticky mixers Some devices' mixers are sticky, which accept SET_CUR but do absolutely nothing. Registering these mixers confuses userspace and results in ineffective volume control. Check if a mixer is sticky by setting the volume to the maximum or minimum value and checking for effectiveness afterward. Prevent the mixer from being registered if it turns out to be sticky. Quirky device sample: usb 7-1: New USB device found, idVendor=0e0b, idProduct=fa01, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: Feaulle Rainbow usb 7-1: Manufacturer: Generic usb 7-1: SerialNumber: 20210726905926 (Mic Capture Volume) Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260411-uac-sticky-mixer-v1-3-29d62717befd@rong.moe Signed-off-by: Takashi Iwai --- sound/usb/mixer.c | 48 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c index e77c2d78a782..d4ef45bf53d7 100644 --- a/sound/usb/mixer.c +++ b/sound/usb/mixer.c @@ -1232,6 +1232,41 @@ static void init_cur_mix_raw(struct usb_mixer_elem_info *cval, int ch, int idx) snd_usb_set_cur_mix_value(cval, ch, idx, cval->min); } +/* + * Additional checks for sticky mixers + * + * Some devices' volume control mixers are sticky, which accept SET_CUR but + * do absolutely nothing. + * + * Prevent sticky mixers from being registered, otherwise they confuses + * userspace and results in ineffective volume control. + */ +static int check_sticky_volume_control(struct usb_mixer_elem_info *cval, + int channel, int saved) +{ + int sticky_test_values[] = { cval->min, cval->max }; + int test, check, i; + + for (i = 0; i < ARRAY_SIZE(sticky_test_values); i++) { + test = sticky_test_values[i]; + if (test == saved) + continue; + + /* Assume non-sticky on failure. */ + if (snd_usb_set_cur_mix_value(cval, channel, 0, test) || + get_cur_mix_raw(cval, channel, &check) || + check != saved) /* SET_CUR effective, non-sticky. */ + return 0; + } + + usb_audio_err(cval->head.mixer->chip, + "%d:%d: sticky mixer values (%d/%d/%d => %d), disabling\n", + cval->head.id, mixer_ctrl_intf(cval->head.mixer), + cval->min, cval->max, cval->res, saved); + + return -ENODEV; +} + /* * Additional checks for the proper resolution * @@ -1270,7 +1305,7 @@ static void check_volume_control_res(struct usb_mixer_elem_info *cval, static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, int default_min, struct snd_kcontrol *kctl) { - int i, idx; + int i, idx, ret; /* for failsafe */ cval->min = default_min; @@ -1319,13 +1354,20 @@ static int get_min_max_with_quirks(struct usb_mixer_elem_info *cval, if (cval->res == 0) cval->res = 1; - if (cval->min + cval->res < cval->max) { + if (cval->min < cval->max) { int saved; if (get_cur_mix_raw(cval, minchn, &saved) < 0) goto no_checks; - check_volume_control_res(cval, minchn, saved); + ret = check_sticky_volume_control(cval, minchn, saved); + if (ret < 0) { + snd_usb_set_cur_mix_value(cval, minchn, 0, saved); + return ret; + } + + if (cval->min + cval->res < cval->max) + check_volume_control_res(cval, minchn, saved); snd_usb_set_cur_mix_value(cval, minchn, 0, saved); } -- cgit v1.2.3 From f312f8b5988003a10d662904c58c8c6bc036782b Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Sat, 11 Apr 2026 15:14:40 -0300 Subject: ALSA: sscape: Cache per-card resources for board reinitialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SoundScape driver programs the gate-array directly from the global resource arrays during probe. That is sufficient for initial bring-up, but a PM resume path also needs the resolved per-card IRQ, DMA, MPU IRQ and joystick settings after probe has finished. Store the resolved resources in struct soundscape and move the board setup into a reusable helper. Also factor the MIDI state programming so the same sequence can be reused by a later PM resume path. This is preparatory work for suspend/resume support and is not intended to change runtime behaviour. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260411-alsa-sscape-pm-v2-1-aeb5682e14b0@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/sscape.c | 234 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 139 insertions(+), 95 deletions(-) diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c index a31ca75774a6..6e951b3c0080 100644 --- a/sound/isa/sscape.c +++ b/sound/isa/sscape.c @@ -131,6 +131,11 @@ enum card_type { struct soundscape { spinlock_t lock; unsigned io_base; + unsigned long wss_base; + int irq; + int mpu_irq; + int dma1; + int dma2; int ic_type; enum card_type type; struct resource *io_res; @@ -138,6 +143,7 @@ struct soundscape { struct snd_wss *chip; unsigned char midi_vol; + bool joystick; struct device *dev; }; @@ -149,6 +155,21 @@ static inline struct soundscape *get_card_soundscape(struct snd_card *c) return (struct soundscape *) (c->private_data); } +/* + * Store the resolved board settings in the per-card state so that + * the same configuration can be replayed later if necessary. + */ +static void sscape_store_settings(struct soundscape *sscape, int dev) +{ + sscape->io_base = port[dev]; + sscape->wss_base = wss_port[dev]; + sscape->irq = irq[dev]; + sscape->mpu_irq = mpu_irq[dev]; + sscape->dma1 = dma[dev]; + sscape->dma2 = dma2[dev]; + sscape->joystick = joystick[dev]; +} + /* * Allocates some kernel memory that we can use for DMA. * I think this means that the memory has to map to @@ -263,34 +284,36 @@ static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout) /* * Write to the SoundScape's host-mode control registers, but - * leave any locking issues to the caller ... + * leave any locking issues to the caller. Returns true if + * the write succeeded. */ -static inline int host_write_unsafe(unsigned io_base, unsigned char data) +static inline bool host_write_unsafe(unsigned int io_base, unsigned char data) { if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) { outb(data, HOST_DATA_IO(io_base)); - return 1; + return true; } - return 0; + return false; } /* * Write to the SoundScape's host-mode control registers, performing * a limited amount of busy-waiting if the register isn't ready. - * Also leaves all locking-issues to the caller ... + * Also leaves all locking-issues to the caller. Returns true if + * the write succeeded before timing out. */ -static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data, - unsigned timeout) +static bool host_write_ctrl_unsafe(unsigned int io_base, unsigned char data, + unsigned int timeout) { - int err; + bool written; - while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) { + while (!(written = host_write_unsafe(io_base, data)) && timeout != 0) { udelay(100); --timeout; } /* while */ - return err; + return written; } @@ -560,6 +583,30 @@ static int sscape_upload_microcode(struct snd_card *card, int version) return err; } +/* + * Restore the SoundScape's MIDI control state after the firmware + * upload has made the host interface available again. + */ +static int sscape_restore_midi_state(struct soundscape *sscape) +{ + bool success; + + guard(spinlock_irqsave)(&sscape->lock); + set_host_mode_unsafe(sscape->io_base); + + success = host_write_ctrl_unsafe(sscape->io_base, CMD_SET_MIDI_VOL, 100) && + host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100) && + host_write_ctrl_unsafe(sscape->io_base, CMD_XXX_MIDI_VOL, 100) && + host_write_ctrl_unsafe(sscape->io_base, sscape->midi_vol, 100) && + host_write_ctrl_unsafe(sscape->io_base, CMD_SET_EXTMIDI, 100) && + host_write_ctrl_unsafe(sscape->io_base, 0, 100) && + host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100); + + set_midi_mode_unsafe(sscape->io_base); + + return success ? 0 : -EIO; +} + /* * Mixer control for the SoundScape's MIDI device. */ @@ -660,6 +707,59 @@ static unsigned get_irq_config(int sscape_type, int irq) return INVALID_IRQ; } +/* + * Program the SoundScape's board-specific routing and enable the + * codec path using the resolved IRQ, DMA and joystick settings. + */ +static int sscape_configure_board(struct soundscape *sscape) +{ + unsigned int dma_cfg; + unsigned int irq_cfg; + unsigned int mpu_irq_cfg; + int val; + + irq_cfg = get_irq_config(sscape->type, sscape->irq); + if (irq_cfg == INVALID_IRQ) + return -ENXIO; + + mpu_irq_cfg = get_irq_config(sscape->type, sscape->mpu_irq); + if (mpu_irq_cfg == INVALID_IRQ) + return -ENXIO; + + scoped_guard(spinlock_irqsave, &sscape->lock) { + if (sscape->ic_type == IC_OPUS) + activate_ad1845_unsafe(sscape->io_base); + + sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e); + sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00); + + /* + * Enable and configure the DMA channels ... + */ + sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50); + dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70); + sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg); + sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20); + + mpu_irq_cfg |= mpu_irq_cfg << 2; + val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xf7; + if (sscape->joystick) + val |= 0x08; + sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0xd0); + sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, + 0xf0 | mpu_irq_cfg); + sscape_write_unsafe(sscape->io_base, GA_CDCFG_REG, + 0x09 | DMA_8BIT | + (sscape->dma1 << 4) | (irq_cfg << 1)); + /* + * Enable the master IRQ ... + */ + sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80); + } + + return 0; +} + /* * Perform certain arcane port-checks to see whether there * is a SoundScape board lurking behind the given ports. @@ -890,37 +990,33 @@ _error: /* * Create an ALSA soundcard entry for the SoundScape, using - * the given list of port, IRQ and DMA resources. + * the resolved port, IRQ and DMA resources. */ -static int create_sscape(int dev, struct snd_card *card) +static int create_sscape(struct snd_card *card) { struct soundscape *sscape = get_card_soundscape(card); - unsigned dma_cfg; - unsigned irq_cfg; - unsigned mpu_irq_cfg; struct resource *io_res; struct resource *wss_res; int err; - int val; const char *name; /* * Grab IO ports that we will need to probe so that we * can detect and control this hardware ... */ - io_res = devm_request_region(card->dev, port[dev], 8, "SoundScape"); + io_res = devm_request_region(card->dev, sscape->io_base, 8, "SoundScape"); if (!io_res) { dev_err(card->dev, - "sscape: can't grab port 0x%lx\n", port[dev]); + "sscape: can't grab port 0x%x\n", sscape->io_base); return -EBUSY; } wss_res = NULL; if (sscape->type == SSCAPE_VIVO) { - wss_res = devm_request_region(card->dev, wss_port[dev], 4, + wss_res = devm_request_region(card->dev, sscape->wss_base, 4, "SoundScape"); if (!wss_res) { dev_err(card->dev, "sscape: can't grab port 0x%lx\n", - wss_port[dev]); + sscape->wss_base); return -EBUSY; } } @@ -928,18 +1024,17 @@ static int create_sscape(int dev, struct snd_card *card) /* * Grab one DMA channel ... */ - err = snd_devm_request_dma(card->dev, dma[dev], "SoundScape"); + err = snd_devm_request_dma(card->dev, sscape->dma1, "SoundScape"); if (err < 0) { - dev_err(card->dev, "sscape: can't grab DMA %d\n", dma[dev]); + dev_err(card->dev, "sscape: can't grab DMA %d\n", sscape->dma1); return err; } spin_lock_init(&sscape->lock); sscape->io_res = io_res; sscape->wss_res = wss_res; - sscape->io_base = port[dev]; - if (!detect_sscape(sscape, wss_port[dev])) { + if (!detect_sscape(sscape, sscape->wss_base)) { dev_err(card->dev, "sscape: hardware not detected at 0x%x\n", sscape->io_base); return -ENODEV; @@ -964,66 +1059,28 @@ static int create_sscape(int dev, struct snd_card *card) } dev_info(card->dev, "sscape: %s card detected at 0x%x, using IRQ %d, DMA %d\n", - name, sscape->io_base, irq[dev], dma[dev]); - - /* - * Check that the user didn't pass us garbage data ... - */ - irq_cfg = get_irq_config(sscape->type, irq[dev]); - if (irq_cfg == INVALID_IRQ) { - dev_err(card->dev, "sscape: Invalid IRQ %d\n", irq[dev]); - return -ENXIO; - } - - mpu_irq_cfg = get_irq_config(sscape->type, mpu_irq[dev]); - if (mpu_irq_cfg == INVALID_IRQ) { - dev_err(card->dev, "sscape: Invalid IRQ %d\n", mpu_irq[dev]); - return -ENXIO; - } + name, sscape->io_base, sscape->irq, sscape->dma1); /* * Tell the on-board devices where their resources are (I think - * I can't be sure without a datasheet ... So many magic values!) */ - scoped_guard(spinlock_irqsave, &sscape->lock) { - - sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e); - sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00); - - /* - * Enable and configure the DMA channels ... - */ - sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50); - dma_cfg = (sscape->ic_type == IC_OPUS ? 0x40 : 0x70); - sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg); - sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20); - - mpu_irq_cfg |= mpu_irq_cfg << 2; - val = sscape_read_unsafe(sscape->io_base, GA_HMCTL_REG) & 0xF7; - if (joystick[dev]) - val |= 8; - sscape_write_unsafe(sscape->io_base, GA_HMCTL_REG, val | 0x10); - sscape_write_unsafe(sscape->io_base, GA_INTCFG_REG, 0xf0 | mpu_irq_cfg); - sscape_write_unsafe(sscape->io_base, - GA_CDCFG_REG, 0x09 | DMA_8BIT - | (dma[dev] << 4) | (irq_cfg << 1)); - /* - * Enable the master IRQ ... - */ - sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x80); - + err = sscape_configure_board(sscape); + if (err < 0) { + dev_err(card->dev, "sscape: Invalid IRQ configuration\n"); + return err; } /* * We have now enabled the codec chip, and so we should * detect the AD1845 device ... */ - err = create_ad1845(card, wss_port[dev], irq[dev], - dma[dev], dma2[dev]); + err = create_ad1845(card, sscape->wss_base, sscape->irq, + sscape->dma1, sscape->dma2); if (err < 0) { dev_err(card->dev, "sscape: No AD1845 device at 0x%lx, IRQ %d\n", - wss_port[dev], irq[dev]); + sscape->wss_base, sscape->irq); return err; } strscpy(card->driver, "SoundScape"); @@ -1040,35 +1097,21 @@ static int create_sscape(int dev, struct snd_card *card) err = sscape_upload_microcode(card, err); if (err == 0) { - err = create_mpu401(card, MIDI_DEVNUM, port[dev], - mpu_irq[dev]); + err = create_mpu401(card, MIDI_DEVNUM, sscape->io_base, + sscape->mpu_irq); if (err < 0) { dev_err(card->dev, "sscape: Failed to create MPU-401 device at 0x%lx\n", - port[dev]); + (unsigned long)sscape->io_base); return err; } - /* - * Initialize mixer - */ - guard(spinlock_irqsave)(&sscape->lock); sscape->midi_vol = 0; - host_write_ctrl_unsafe(sscape->io_base, - CMD_SET_MIDI_VOL, 100); - host_write_ctrl_unsafe(sscape->io_base, - sscape->midi_vol, 100); - host_write_ctrl_unsafe(sscape->io_base, - CMD_XXX_MIDI_VOL, 100); - host_write_ctrl_unsafe(sscape->io_base, - sscape->midi_vol, 100); - host_write_ctrl_unsafe(sscape->io_base, - CMD_SET_EXTMIDI, 100); - host_write_ctrl_unsafe(sscape->io_base, - 0, 100); - host_write_ctrl_unsafe(sscape->io_base, CMD_ACK, 100); - - set_midi_mode_unsafe(sscape->io_base); + err = sscape_restore_midi_state(sscape); + if (err < 0) + dev_warn(card->dev, + "sscape: MIDI init incomplete: %d\n", + err); } } @@ -1111,8 +1154,9 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev) sscape->type = SSCAPE; dma[dev] &= 0x03; + sscape_store_settings(sscape, dev); - ret = create_sscape(dev, card); + ret = create_sscape(card); if (ret < 0) return ret; @@ -1130,7 +1174,6 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev) static struct isa_driver snd_sscape_driver = { .match = snd_sscape_match, .probe = snd_sscape_probe, - /* FIXME: suspend/resume */ .driver = { .name = DEV_NAME }, @@ -1211,8 +1254,9 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard, wss_port[idx] = pnp_port_start(dev, 1); dma2[idx] = pnp_dma(dev, 1); } + sscape_store_settings(sscape, idx); - ret = create_sscape(idx, card); + ret = create_sscape(card); if (ret < 0) return ret; -- cgit v1.2.3 From 713e0f011178a2896e46db3244093454708066e2 Mon Sep 17 00:00:00 2001 From: Cássio Gabriel Date: Sat, 11 Apr 2026 15:14:41 -0300 Subject: ALSA: sscape: Add suspend and resume support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SoundScape ISA driver has lacked suspend and resume callbacks since commit 277e926c9b27 ("[ALSA] sscape - Use platform_device"). A plain snd_wss resume is not sufficient for SoundScape. Resume also needs to restore the board-specific gate-array routing, and non-VIVO boards need to reinitialize the probe-time MIDI firmware and MIDI control state when the MPU-401 side was enabled during probe. That firmware reload can be handled in-kernel because commit acd47100914b ("ALSA: sscape: convert to firmware loader framework") moved the driver to request_firmware(). Add ISA and ISA-PnP PM callbacks, reconfigure the board on resume, reload the non-VIVO MIDI firmware, restore the MIDI state, and then resume the WSS codec. If MIDI firmware reload fails, keep the WSS resume path alive and leave MIDI unavailable instead of failing the whole device resume. Signed-off-by: Cássio Gabriel Link: https://patch.msgid.link/20260411-alsa-sscape-pm-v2-2-aeb5682e14b0@gmail.com Signed-off-by: Takashi Iwai --- sound/isa/sscape.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c index 6e951b3c0080..553ceb92d298 100644 --- a/sound/isa/sscape.c +++ b/sound/isa/sscape.c @@ -144,6 +144,7 @@ struct soundscape { unsigned char midi_vol; bool joystick; + bool midi_enabled; struct device *dev; }; @@ -1107,6 +1108,7 @@ static int create_sscape(struct snd_card *card) } sscape->midi_vol = 0; + sscape->midi_enabled = true; err = sscape_restore_midi_state(sscape); if (err < 0) dev_warn(card->dev, @@ -1118,6 +1120,77 @@ static int create_sscape(struct snd_card *card) return 0; } +#ifdef CONFIG_PM +/* + * Reload the MIDI firmware and restore the saved MIDI state for + * boards whose MPU-401 side was enabled during probe. + */ +static int sscape_resume_midi(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + int err, version; + + if (!sscape->midi_enabled) + return 0; + + version = sscape_upload_bootblock(card); + if (version < 0) + return version; + + err = sscape_upload_microcode(card, version); + if (err < 0) + return err; + + outb(0, sscape->io_base); + + return sscape_restore_midi_state(sscape); +} + +/* + * Save the WSS codec state before the SoundScape is suspended. + */ +static int snd_sscape_suspend_card(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + sscape->chip->suspend(sscape->chip); + return 0; +} + +/* + * Restore the board-specific state before resuming the WSS codec. + */ +static int snd_sscape_resume_card(struct snd_card *card) +{ + struct soundscape *sscape = get_card_soundscape(card); + int err; + + err = sscape_configure_board(sscape); + if (err < 0) + return err; + + err = sscape_resume_midi(card); + if (err < 0) + dev_warn(card->dev, "sscape: MIDI restore failed: %d\n", err); + + sscape->chip->resume(sscape->chip); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int snd_sscape_suspend(struct device *dev, unsigned int n, + pm_message_t state) +{ + return snd_sscape_suspend_card(dev_get_drvdata(dev)); +} + +static int snd_sscape_resume(struct device *dev, unsigned int n) +{ + return snd_sscape_resume_card(dev_get_drvdata(dev)); +} +#endif + static int snd_sscape_match(struct device *pdev, unsigned int i) { @@ -1174,6 +1247,10 @@ static int snd_sscape_probe(struct device *pdev, unsigned int dev) static struct isa_driver snd_sscape_driver = { .match = snd_sscape_match, .probe = snd_sscape_probe, +#ifdef CONFIG_PM + .suspend = snd_sscape_suspend, + .resume = snd_sscape_resume, +#endif .driver = { .name = DEV_NAME }, @@ -1271,11 +1348,27 @@ static int sscape_pnp_detect(struct pnp_card_link *pcard, return 0; } +#ifdef CONFIG_PM +static int sscape_pnp_suspend(struct pnp_card_link *pcard, pm_message_t state) +{ + return snd_sscape_suspend_card(pnp_get_card_drvdata(pcard)); +} + +static int sscape_pnp_resume(struct pnp_card_link *pcard) +{ + return snd_sscape_resume_card(pnp_get_card_drvdata(pcard)); +} +#endif + static struct pnp_card_driver sscape_pnpc_driver = { .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, .name = "sscape", .id_table = sscape_pnpids, .probe = sscape_pnp_detect, +#ifdef CONFIG_PM + .suspend = sscape_pnp_suspend, + .resume = sscape_pnp_resume, +#endif }; #endif /* CONFIG_PNP */ -- cgit v1.2.3