From 016fe9695e6dc1322ddfe8c7dbc7b973483b4a24 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Wed, 28 Oct 2026 10:44:41 -0400 Subject: [PATCH 001/186] ALSA: memalloc: Free the new pages when WC DMA mapping fails snd_dma_wc_alloc() keeps the newly allocated pages in a local variable, and snd_dma_alloc_dir_pages() stores them in dmab->area only after ->alloc() returns. If dma_map_single() fails, the error path passes dmab->area to do_free_pages() instead, and that is still NULL at this point for the usual callers. The pages are then leaked with their write-combining attribute still set, set_memory_wb() warns about the zero address, and for buffers larger than a page free_pages_exact() goes on to free the struct pages of the bogus addresses 4096, 8192 and so on. With CONFIG_DEBUG_VM that hits the VM_BUG_ON() in free_pages(): kernel BUG at mm/page_alloc.c:5600! RIP: 0010:free_pages_exact+0xcb/0xd0 Call Trace: snd_dma_wc_alloc+0x14f/0x160 [snd_pcm] snd_dma_alloc_dir_pages+0x4f/0x80 [snd_pcm] This was reproduced on an x86 guest without swiotlb by allocating SNDRV_DMA_TYPE_DEV_WC buffers for a device with a 24-bit streaming DMA mask, so that dma_map_single() fails. Free the pages that were just allocated. Fixes: c880a5146642 ("ALSA: memalloc: Use proper DMA mapping API for x86 WC buffer allocations") Signed-off-by: Sasha Levin Link: https://patch.msgid.link/20261028144441.992127-1-sashal@kernel.org Signed-off-by: Takashi Iwai --- sound/core/memalloc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/memalloc.c b/sound/core/memalloc.c index 5bc7e586b430f5..3118bb14231cec 100644 --- a/sound/core/memalloc.c +++ b/sound/core/memalloc.c @@ -506,7 +506,7 @@ static void *snd_dma_wc_alloc(struct snd_dma_buffer *dmab, size_t size) return NULL; dmab->addr = dma_map_single(dmab->dev.dev, p, size, DMA_BIDIRECTIONAL); if (dma_mapping_error(dmab->dev.dev, dmab->addr)) { - do_free_pages(dmab->area, size, true); + do_free_pages(p, size, true); return NULL; } return p; From 1886b8132cd37edbc28b55f30a885011aa0bbe8a Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Wed, 28 Oct 2026 10:44:43 -0400 Subject: [PATCH 002/186] ALSA: hda/core: Fix double free on stream page allocation failure Since commit f16eaa38ea64 ("ALSA: hda/core: add cleanup in snd_hdac_bus_alloc_stream_pages()"), snd_hdac_bus_alloc_stream_pages() frees the BDL and position buffers it has already allocated when a later allocation fails. snd_dma_free_pages() doesn't clear dmab->area, so the freed buffers still look allocated to snd_hdac_bus_free_stream_pages(), which the HDA controller drivers call from their destructors, and they are freed a second time when the device is removed. With fault injection failing the CORB/RIRB buffer allocation of snd-hda-intel on a QEMU ich9-intel-hda, unbinding the device then crashes: page dumped because: VM_BUG_ON_PAGE(page_ref_count(page) == 0) kernel BUG at include/linux/mm.h:1986! Call Trace: ___free_pages+0x9b/0xa0 free_pages_exact+0xa5/0xd0 snd_hdac_bus_free_stream_pages+0x43/0xb0 [snd_hda_core] azx_free+0xb3/0x1b0 [snd_hda_intel] azx_dev_free+0xd/0x20 [snd_hda_intel] Clear the buffer pointers after freeing them in the error path. Fixes: f16eaa38ea64 ("ALSA: hda/core: add cleanup in snd_hdac_bus_alloc_stream_pages()") Signed-off-by: Sasha Levin Link: https://patch.msgid.link/20261028144443.992569-1-sashal@kernel.org Signed-off-by: Takashi Iwai --- sound/hda/core/controller.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index a7acd3418ae8a2..32574dc899fad3 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -764,10 +764,13 @@ int snd_hdac_bus_alloc_stream_pages(struct hdac_bus *bus) error_posbuf: snd_dma_free_pages(&bus->posbuf); + bus->posbuf.area = NULL; error_bdl: list_for_each_entry(s, &bus->stream_list, list) { - if (s->bdl.area) + if (s->bdl.area) { snd_dma_free_pages(&s->bdl); + s->bdl.area = NULL; + } } return -ENOMEM; } From f82a447ceebd1012d4dcbc48a4404c7e85d55109 Mon Sep 17 00:00:00 2001 From: Cian Ruane Date: Wed, 28 Oct 2026 00:47:32 +0100 Subject: [PATCH 003/186] ALSA: hda/realtek: Add quirk for Huawei MateBook X Pro 2020 The Huawei MateBook X Pro 2020 (MACHC-WAX9) has a combined headphone and microphone jack, but the BIOS marks pin 0x19 as unconnected (0x411111f0). As a result the headset microphone is missing and only the internal microphone is available. The mic mute LED on the F7 key does not follow the capture mute state either. The machine uses the PCI subsystem vendor ID 0x1e83 (Huaqin) rather than 0x19e5, so the Huawei vendor-wide quirk that sets up the LED does not match. Add a fixup that configures pin 0x19 as a headset microphone without its own jack detection and chains to ALC269_FIXUP_HEADSET_MODE, which enables headset mode and the mic mute LED. This is the MACHR-WX9 fixup without its other pin overrides. The remaining BIOS pin defaults work, and pin 0x14, which the MACHR-WX9 uses for a second speaker pair, produces no sound on this machine. Configuring pin 0x19 with presence detection (0x04a11040) does not work either: the jack state drops to unplugged while the headset is still connected. Tested on: HUAWEI MACHC-WAX9 (MateBook X Pro 2020), BIOS 1.26, Ubuntu 26.04 (kernel 7.0.0-38-generic, with this patch backported). Signed-off-by: Cian Ruane Link: https://patch.msgid.link/20261027234732.6893-1-cian.ruane1@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 23329d5887a56e..5a4cb04aaf571c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4330,6 +4330,7 @@ enum { ALC255_FIXUP_DELL_HEADSET_MIC, ALC256_FIXUP_HUAWEI_MACH_WX9_PINS, ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS, + ALC256_FIXUP_HUAWEI_MACHC_WAX9_PINS, ALC298_FIXUP_HUAWEI_MBX_STEREO, ALC295_FIXUP_HP_X360, ALC295_FIXUP_HP_PAVILION_X360, @@ -5063,6 +5064,15 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MODE }, + [ALC256_FIXUP_HUAWEI_MACHC_WAX9_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x19, 0x04a11140 }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE + }, [ALC298_FIXUP_HUAWEI_MBX_STEREO] = { .type = HDA_FIXUP_FUNC, .v.func = alc298_fixup_huawei_mbx_stereo, @@ -8727,6 +8737,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1e50, 0x7007, "Positivo DN50E", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x1e50, 0x7036, "Acer Gadget E10 ETBook", ALC233_FIXUP_WUJIE_SPEAKERS), SND_PCI_QUIRK(0x1e50, 0x7038, "Positivo DN140", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), + SND_PCI_QUIRK(0x1e83, 0x3e21, "Huawei MACHC-WAX9", ALC256_FIXUP_HUAWEI_MACHC_WAX9_PINS), SND_PCI_QUIRK(0x1ee7, 0x204e, "HONOR DRA-XX M1020", ALC256_FIXUP_HONOR_DRA_XX_SPEAKERS), SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), From f5d3eb483fed59fc0f3b059b8b6a32cedc922360 Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Wed, 28 Oct 2026 12:25:25 +0800 Subject: [PATCH 004/186] ALSA: us122l: Add missing NULL check in usb_stream_hwdep_mmap() usb_stream_hwdep_mmap() reads us122l->sk.s and dereferences it without checking for NULL. The stream object is released by usb_stream_free(), which is reached through us122l_stop() both when the device is disconnected and when us122l_start() fails, and it leaves us122l->sk.s set to NULL. The hwdep file stays usable after that, so a later mmap() KASAN: null-ptr-deref in range [0x0000000000000010-0x0000000000000017] RIP: 0010:usb_stream_hwdep_mmap+0x107/0x890 sound/usb/usx2y/us122l.c:182 crashes while reading s->read_size, which sits at offset 0x10. That NULL is an expected state for this field: usb_stream_hwdep_vm_fault(), usb_stream_hwdep_poll(), usb_stream_hwdep_ioctl(), usb_stream_stop() and usb_stream_free() all test it before use. Only the mmap callback misses the check, so let it return -ENODEV, matching what the other hwdep mapping in usx2yhwdeppcm.c does when its shared buffer has not been allocated. Fixes: 030a07e441296c372f946cd4065b5d831d8dc40c ("ALSA: Add USB US122L driver") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261028042525.8737-1-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us122l.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index a5db0d044ef96c..27b6b0bd576810 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -179,6 +179,8 @@ static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, offset = area->vm_pgoff << PAGE_SHIFT; guard(mutex)(&us122l->mutex); s = us122l->sk.s; + if (!s) + return -ENODEV; read = offset < s->read_size; if (read) { if (area->vm_flags & VM_WRITE) From 820e0050866942b5498488496462d12c6ce1c7a9 Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Wed, 28 Oct 2026 15:41:43 +0800 Subject: [PATCH 005/186] ALSA: us122l: Fix NULL dereference at hwdep open and release usb_stream_hwdep_open() and usb_stream_hwdep_release() pass the result of usb_ifnum_to_if() straight to usb_autopm_get_interface() and usb_autopm_put_interface(), without checking it for NULL. usb_ifnum_to_if() returns NULL once the device is gone, since usb_disable_device() clears udev->actconfig on disconnect. The hwdep file can outlive the disconnect: the card is released with snd_card_free_when_closed(), so a process that keeps the character device open reaches the driver's release callback after the USB configuration has been dropped, and interface_to_usbdev() then dereferences the NULL interface at offsetof(struct device, parent). The open callback has the same problem when it races with the disconnect. Check both lookups and fail the open with -ENODEV; in release, skip the runtime PM put when the interface is already gone and still run the rest of the per-file cleanup. Move the us122l->first bookkeeping after the checks so that a failed open no longer leaves a stale file pointer behind. Fixes: 030a07e44129 ("ALSA: Add USB US122L driver") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261028074143.14440-1-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/usx2y/us122l.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c index 27b6b0bd576810..9b4ec9bdc1383c 100644 --- a/sound/usb/usx2y/us122l.c +++ b/sound/usb/usx2y/us122l.c @@ -129,19 +129,26 @@ static const struct vm_operations_struct usb_stream_hwdep_vm_ops = { static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file) { struct us122l *us122l = hw->private_data; - struct usb_interface *iface; + struct usb_interface *iface, *iface0 = NULL; if (hw->used >= 2) return -EBUSY; - if (!us122l->first) - us122l->first = file; - if (us122l->is_us144) { - iface = usb_ifnum_to_if(us122l->dev, 0); - usb_autopm_get_interface(iface); + iface0 = usb_ifnum_to_if(us122l->dev, 0); + if (!iface0) + return -ENODEV; } + iface = usb_ifnum_to_if(us122l->dev, 1); + if (!iface) + return -ENODEV; + + if (!us122l->first) + us122l->first = file; + + if (iface0) + usb_autopm_get_interface(iface0); usb_autopm_get_interface(iface); return 0; } @@ -153,10 +160,12 @@ static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file) if (us122l->is_us144) { iface = usb_ifnum_to_if(us122l->dev, 0); - usb_autopm_put_interface(iface); + if (iface) + usb_autopm_put_interface(iface); } iface = usb_ifnum_to_if(us122l->dev, 1); - usb_autopm_put_interface(iface); + if (iface) + usb_autopm_put_interface(iface); if (us122l->first == file) us122l->first = NULL; guard(mutex)(&us122l->mutex); From ceb0d8c22473933099b46cec70639d0f1f7c68ad Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Wed, 28 Oct 2026 18:31:58 +0800 Subject: [PATCH 006/186] ALSA: usx2y: Initialize idle URBs before submitting streams usb_stream_start() submits the first two capture and playback URBs and only afterwards assigns sk->idle_inurb, sk->idle_outurb, sk->completed_inurb and sk->completed_outurb to the remaining two URBs of each direction. A transfer can complete as soon as usb_submit_urb() has been called, so the completion handler i_capture_start() or i_playback_start() may run before those pointers are set. While the stream is still being synchronized (state below usb_stream_sync1), stream_start() then calls playback_prep_freqn(sk, sk->idle_outurb) with sk->idle_outurb still NULL, and the packet descriptors are written through that NULL pointer: Oops: general protection fault, probably for non-canonical address 0xdffffc0000000018: 0000 [#1] SMP KASAN KASAN: null-ptr-deref in range [0x00000000000000c0-0x00000000000000c7] RIP: 0010:playback_prep_freqn+0x11c/0x4a0 sound/usb/usx2y/usb_stream.c:33 Call Trace: stream_start+0x5dc/0x8d0 sound/usb/usx2y/usb_stream.c:596 i_capture_start+0x3da/0x710 sound/usb/usx2y/usb_stream.c:654 __usb_hcd_giveback_urb+0x238/0x550 drivers/usb/core/hcd.c:1657 usb_giveback_urb_bh+0x20b/0x530 drivers/usb/core/hcd.c:1691 tasklet_hi_action+0xa/0x20 kernel/softirq.c:994 handle_softirqs+0x16f/0x440 kernel/softirq.c:645 The four URBs referenced by these pointers are allocated long before and do not depend on any transfer state, so assign them before the submit loop. Keeping the assignment inside the retry path also restores a well-defined URB rotation when the start frame mismatch handling kills and resubmits the streams. Fixes: 030a07e441296c372f946cd4065b5d831d8dc40c ("ALSA: Add USB US122L driver") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261028103158.22996-1-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usb_stream.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c index 3122cf653273df..7d621280fba33d 100644 --- a/sound/usb/usx2y/usb_stream.c +++ b/sound/usb/usx2y/usb_stream.c @@ -682,6 +682,10 @@ int usb_stream_start(struct usb_stream_kernel *sk) s->sync_packet = -1; s->inpacket_head = -1; sk->iso_frame_balance = 0; + sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2]; + sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2]; + sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1]; + sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1]; ++try; for (u = 0; u < 2; u++) { struct urb *inurb = sk->inurb[u]; @@ -739,11 +743,6 @@ int usb_stream_start(struct usb_stream_kernel *sk) return -EFAULT; } - sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2]; - sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2]; - sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1]; - sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1]; - /* wait, check */ { int wait_ms = 3000; From c5acc61d8e4868881a1c5dba60a35abcae00bc49 Mon Sep 17 00:00:00 2001 From: Benjamin J Pryor Date: Wed, 28 Oct 2026 09:29:51 -0400 Subject: [PATCH 007/186] ALSA: hda/realtek: Extend ALC1316 fixup to MSI GE76 Raider The MSI GE76 Raider drives its speakers using the exact same ALC1316 smart amplifier as the GE66 Raider, but under a different hardware ID. Thus, the same speaker dropout fixup should apply to both devices. This patch extends the fixup that configures this amplifier's silence detection to apply to the hardware ID of the GE76 as well. Link: https://github.com/thesofproject/sof/issues/10475 Link: https://lore.kernel.org/all/20260909193104.26508-1-audvare@gmail.com Signed-off-by: Benjamin J Pryor Link: https://patch.msgid.link/20261028132951.1253841-1-programmer2514@gmail.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 5a4cb04aaf571c..71ad14a75833f5 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8352,8 +8352,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x144d, 0xc1cc, "Samsung Galaxy Book3 Ultra (NT960XFH)", ALC298_FIXUP_SAMSUNG_AMP_V2_4_AMPS), SND_PCI_QUIRK(0x1458, 0x900e, "Gigabyte G5 KF5 (2023)", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1458, 0xfa53, "Gigabyte BXBT-2807", ALC283_FIXUP_HEADSET_MIC), - SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE", - ALC298_FIXUP_MSI_GE66_ALC1316_AMP), + SND_PCI_QUIRK(0x1462, 0x12fb, "MSI GE66 Raider 11UE", ALC298_FIXUP_MSI_GE66_ALC1316_AMP), + SND_PCI_QUIRK(0x1462, 0x1305, "MSI GE76 Raider 11UG", ALC298_FIXUP_MSI_GE66_ALC1316_AMP), SND_PCI_QUIRK(0x1462, 0xb120, "MSI Cubi MS-B120", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1462, 0xb171, "Cubi N 8GL (MS-B171)", ALC283_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x152d, 0x1082, "Quanta NL3", ALC269_FIXUP_LIFEBOOK), From 3a2be9a1c44c14a6836a5c551c350c1b0a147dcf Mon Sep 17 00:00:00 2001 From: renpanpan Date: Tue, 27 Oct 2026 17:09:13 +0800 Subject: [PATCH 008/186] ALSA: usb-audio: Fix card name for CVT Touch Device (1ff7:0f33) The CVT Touch Device (1ff7:0f33) is a USB composite device (HID + UAC audio). Its iProduct string "Touch Device" is generic and does not identify the audio function. Windows names the audio function "MAXHUB Speaker" from the UAC OUTPUT_TERMINAL iTerminal descriptor. Add a quirk entry to override the card shortname to "MAXHUB Speaker" with QUIRK_NO_INTERFACE to preserve normal stream and mixer creation. Signed-off-by: renpanpan Link: https://patch.msgid.link/20261027090913.37647-1-renpanpan@kylinos.cn Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 4793fd1f85fef3..1544ad7365f56d 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -4437,6 +4437,27 @@ QUIRK_RME_DIGIFACE(0x3fa0), } }, +/* + * CVT Touch Device (1ff7:0f33) is a USB composite device with HID + UAC + * audio interfaces. The device iProduct is "Touch Device" but Windows + * displays the audio function as "MAXHUB Speaker" (from iTerminal). + * Override the card shortname to match the Windows display name, while + * keeping normal stream/mixer creation (ifnum = QUIRK_NO_INTERFACE). + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x1ff7, + .idProduct = 0x0f33, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .product_name = "MAXHUB Speaker", + .ifnum = QUIRK_NO_INTERFACE, + } +}, + #undef QUIRK_AF16RIG_CLOCK #undef QUIRK_AF16RIG From 7786d2f00871d9647c5c5fd883580a2b570ffbd1 Mon Sep 17 00:00:00 2001 From: Kuangyi Chiang Date: Thu, 29 Oct 2026 11:28:56 +0800 Subject: [PATCH 009/186] ALSA: usb-audio: Add delay quirk for HIK camera Audio control requests that set the volume sometimes time out on the HIK 1080P Camera (2bdf:0280). Adding a 1ms delay between control messages avoids the issue. Apply QUIRK_FLAG_CTL_MSG_DELAY_1M for this device. Signed-off-by: Kuangyi Chiang Link: https://patch.msgid.link/20261029032856.7100-1-ki.chiang65@gmail.com 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 4ec1f2a0b359ad..05a9f6727a3a59 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2809,6 +2809,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x2b73, 0x0047, /* AlphaTheta EUPHONIA */ QUIRK_FLAG_PLAYBACK_FIRST | QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x2bdf, 0x0280, /* HIK 1080P Camera */ + QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d95, 0x8011, /* VIVO USB-C HEADSET */ QUIRK_FLAG_CTL_MSG_DELAY_1M), DEVICE_FLG(0x2d95, 0x8021, /* VIVO USB-C-XE710 HEADSET */ From 7f5672dc187d2b14dbe06374011c4b9e10b04532 Mon Sep 17 00:00:00 2001 From: yinguobing Date: Thu, 29 Oct 2026 16:13:31 +0800 Subject: [PATCH 010/186] ALSA: hda/realtek: Add quirk for Huawei WRT-WX9 (ALC256, 19e5:320d) On the Huawei MateBook WRT-WX9 whose ALC256 codec reports subsystem ID 19e5:320d, headphone jack presence detection never works: the "Headphone Jack" control stays off, PipeWire keeps the speaker route active, and audio plays from the internal speakers and the headphones at the same time. Unplugging the headphones leaves the speakers working, so only insertion detection is broken. The existing quirk for this machine family is registered for 19e5:320f only, so this variant receives no fixup at all. ALC256 on this board needs ALC269_FIXUP_HEADSET_MODE; without it the codec's jack detection is never enabled and GET_PIN_SENSE on the headphone pin (0x21) always reads 0. Verified on the affected machine by booting with snd-hda-intel.model=headset-mode, which applies the same fixup: the "Headphone Jack" control then reports "on" when a plug is inserted, GET_PIN_SENSE on pin 0x21 returns 0x80000000, the "Headphones" port becomes available in PipeWire, and the speaker is muted automatically on insertion. HDMI audio profiles and the internal microphone keep working. Add a SND_PCI_QUIRK entry for 19e5:320d using ALC269_FIXUP_HEADSET_MODE. Signed-off-by: yinguobing Link: https://patch.msgid.link/CA+2LKoO7He4DciWnx7qcwndwpZNxA4R1FMTpXGSTVK_k9J6Svg@mail.gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 71ad14a75833f5..ee8d8385d1934c 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8693,6 +8693,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1854, 0x0490, "LG Gram Style 14 (14Z90RS)", ALC298_FIXUP_LG_GRAM_STYLE_14), SND_PCI_QUIRK(0x1854, 0x0554, "LG gram 16 (16Z90TR)", ALC298_FIXUP_SAMSUNG_AMP_V2_2_AMPS), SND_PCI_QUIRK(0x19e5, 0x3204, "Huawei MACH-WX9", ALC256_FIXUP_HUAWEI_MACH_WX9_PINS), + SND_PCI_QUIRK(0x19e5, 0x320d, "Huawei WRT-WX9", ALC269_FIXUP_HEADSET_MODE), SND_PCI_QUIRK(0x19e5, 0x320f, "Huawei WRT-WX9 ", ALC256_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x19e5, 0x3212, "Huawei KLV-WX9 ", ALC256_FIXUP_ACER_HEADSET_MIC), SND_PCI_QUIRK(0x19e5, 0x3e13, "Huawei MACHR-WX9", ALC256_FIXUP_HUAWEI_MACHR_WX9_PINS), From aee83c204f80d56b4d55f53acdb8594308ddcd4a Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Thu, 29 Oct 2026 16:48:09 +0800 Subject: [PATCH 011/186] ALSA: hwdep: Serialize DSP load requests The check of the loaded DSP indices and the call of the driver's dsp_load callback in snd_hwdep_dsp_load() are not serialized. A hwdep file descriptor can be shared among threads, or inherited across fork, so two SNDRV_HWDEP_IOCTL_DSP_LOAD requests for the same index can both observe the index as unloaded and enter the driver's callback concurrently, even though the device itself is opened exclusively. This breaks the assumption behind hw->dsp_loaded that a firmware index is loaded only once. For example, snd-usb-usx2y hits WARN_ON(usx2y->as04.buffer) in usx2y_async_seq04_init() and may allocate the US-428 sequence URBs twice. Serialize the check, the callback invocation and the bookkeeping with a per-hwdep mutex, so that the second request returns -EBUSY. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261029084810.21910-2-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- include/sound/hwdep.h | 1 + sound/core/hwdep.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/include/sound/hwdep.h b/include/sound/hwdep.h index b0da633184cde5..7357727f066033 100644 --- a/include/sound/hwdep.h +++ b/include/sound/hwdep.h @@ -57,6 +57,7 @@ struct snd_hwdep { struct mutex open_mutex; int used; /* reference counter */ + struct mutex dsp_mutex; /* serializes DSP load requests */ unsigned int dsp_loaded; /* bit fields of loaded dsp indices */ unsigned int exclusive:1; /* exclusive access mode */ }; diff --git a/sound/core/hwdep.c b/sound/core/hwdep.c index c6de5345e946f1..fb9034897c561b 100644 --- a/sound/core/hwdep.c +++ b/sound/core/hwdep.c @@ -212,6 +212,7 @@ static int snd_hwdep_dsp_load(struct snd_hwdep *hw, return -ENXIO; if (info->index >= 32) return -EINVAL; + guard(mutex)(&hw->dsp_mutex); /* check whether the dsp was already loaded */ if (hw->dsp_loaded & (1u << info->index)) return -EBUSY; @@ -380,6 +381,7 @@ int snd_hwdep_new(struct snd_card *card, char *id, int device, init_waitqueue_head(&hwdep->open_wait); mutex_init(&hwdep->open_mutex); + mutex_init(&hwdep->dsp_mutex); hwdep->card = card; hwdep->device = device; if (id) From 643b0be0c47a78712b0eb127654a068a521bf6d0 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Thu, 29 Oct 2026 11:51:57 +0100 Subject: [PATCH 012/186] ALSA: hda: aw88399: use auto-cleanup for ACPI subsystem ID string Use scope-based cleanup for the string returned by acpi_get_subsystem_id() instead of freeing it manually, consistent with the other cleanup annotations in aw88399_hda_acpi_probe() and aw88399_hda_init(). This is just a code cleanup, with no functional changes intended. Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB23035A048BCAF108507B59D0FC6E2@SN6PR19MB2303.namprd19.prod.outlook.com --- sound/hda/codecs/side-codecs/aw88399_hda.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 00b78299fa56fe..5d5acf87ef3c9e 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -184,7 +184,6 @@ static const struct aw88399_prop_model aw88399_prop_model_table[] = { static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) { struct acpi_device *adev; - const char *sub; const struct aw88399_prop_model *model; aw88399->index = aw88399_hda_index_from_i2c(aw88399); @@ -203,14 +202,13 @@ static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) if (!physdev) return -ENODEV; - sub = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); + const char *sub __free(kfree) = acpi_get_subsystem_id(ACPI_HANDLE(physdev)); if (IS_ERR_OR_NULL(sub)) { dev_err(aw88399->dev, "ACPI subsystem ID not available\n"); return -ENODEV; } aw88399->acpi_subsystem_id = devm_kstrdup(aw88399->dev, sub, GFP_KERNEL); - kfree(sub); if (!aw88399->acpi_subsystem_id) return -ENOMEM; From df7b4255d6751618511243ea7c9ccd7a23372030 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Thu, 29 Oct 2026 11:51:58 +0100 Subject: [PATCH 013/186] ALSA: hda: aw88399: add missing MODULE_AUTHOR to I2C driver The original AW88399 HDA driver submission accidentally didn't include my MODULE_AUTHOR() metadata in the I2C driver. Add it, matching the main HDA driver. Signed-off-by: Marco Giunta Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB230341A84A4CACD260883C2EFC6E2@SN6PR19MB2303.namprd19.prod.outlook.com --- sound/hda/codecs/side-codecs/aw88399_hda_i2c.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c index 186e13adbc6cf9..9443ca88743142 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c @@ -51,4 +51,5 @@ module_i2c_driver(aw88399_hda_i2c_driver); MODULE_DESCRIPTION("HDA AW88399 I2C driver"); MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); MODULE_AUTHOR("Yakov Till "); +MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); From 5067ce3ba8218a941344b76249bf185e1cda6051 Mon Sep 17 00:00:00 2001 From: Marco Giunta Date: Thu, 29 Oct 2026 11:51:59 +0100 Subject: [PATCH 014/186] ASoC: aw88399: move library exports to SND_SOC_AW88399_LIB namespace Replace every EXPORT_SYMBOL_GPL(x) in aw88399-lib.c with EXPORT_SYMBOL_NS_GPL(x, "SND_SOC_AW88399_LIB"), and add the corresponding MODULE_IMPORT_NS("SND_SOC_AW88399_LIB") metadata to the ASoC and HDA users of the library (aw88399.c, aw88399_hda.c, aw88399_hda_i2c.c). This makes module dependencies explicit, consistent with the namespaced exports in the HDA side-codec driver. Signed-off-by: Marco Giunta Acked-by: Mark Brown Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/SN6PR19MB2303BF7F0C1B1BE767351D98FC6E2@SN6PR19MB2303.namprd19.prod.outlook.com --- sound/hda/codecs/side-codecs/aw88399_hda.c | 1 + .../hda/codecs/side-codecs/aw88399_hda_i2c.c | 1 + sound/soc/codecs/aw88399-lib.c | 30 +++++++++---------- sound/soc/codecs/aw88399.c | 1 + 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 5d5acf87ef3c9e..94a4577cb2fecc 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -355,6 +355,7 @@ const struct dev_pm_ops aw88399_hda_pm_ops = { EXPORT_SYMBOL_NS_GPL(aw88399_hda_pm_ops, "SND_HDA_SCODEC_AW88399"); MODULE_DESCRIPTION("AW88399 HDA driver"); +MODULE_IMPORT_NS("SND_SOC_AW88399_LIB"); MODULE_AUTHOR("Yakov Till "); MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); diff --git a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c index 9443ca88743142..0b3fa75e09c78c 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda_i2c.c @@ -50,6 +50,7 @@ module_i2c_driver(aw88399_hda_i2c_driver); MODULE_DESCRIPTION("HDA AW88399 I2C driver"); MODULE_IMPORT_NS("SND_HDA_SCODEC_AW88399"); +MODULE_IMPORT_NS("SND_SOC_AW88399_LIB"); MODULE_AUTHOR("Yakov Till "); MODULE_AUTHOR("Marco Giunta "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/aw88399-lib.c b/sound/soc/codecs/aw88399-lib.c index b4e8aae56805e6..b08baacad87b5e 100644 --- a/sound/soc/codecs/aw88399-lib.c +++ b/sound/soc/codecs/aw88399-lib.c @@ -23,7 +23,7 @@ const struct regmap_config aw88399_remap_config = { .reg_format_endian = REGMAP_ENDIAN_LITTLE, .val_format_endian = REGMAP_ENDIAN_BIG, }; -EXPORT_SYMBOL_GPL(aw88399_remap_config); +EXPORT_SYMBOL_NS_GPL(aw88399_remap_config, "SND_SOC_AW88399_LIB"); static void aw_dev_pwd(struct aw_device *aw_dev, bool pwd) { @@ -165,7 +165,7 @@ int aw_dev_check_syspll(struct aw_device *aw_dev) return 0; } -EXPORT_SYMBOL_GPL(aw_dev_check_syspll); +EXPORT_SYMBOL_NS_GPL(aw_dev_check_syspll, "SND_SOC_AW88399_LIB"); static int aw_dev_check_sysst(struct aw_device *aw_dev) { @@ -241,7 +241,7 @@ void aw_dev_dsp_enable(struct aw_device *aw_dev, bool is_enable) if (ret) dev_dbg(aw_dev->dev, "%s failed\n", __func__); } -EXPORT_SYMBOL_GPL(aw_dev_dsp_enable); +EXPORT_SYMBOL_NS_GPL(aw_dev_dsp_enable, "SND_SOC_AW88399_LIB"); static int aw88399_dev_get_icalk(struct aw88399 *aw88399, int16_t *icalk) { @@ -418,7 +418,7 @@ int aw_dev_update_cali_re(struct aw_cali_desc *cali_desc) return ret; } -EXPORT_SYMBOL_GPL(aw_dev_update_cali_re); +EXPORT_SYMBOL_NS_GPL(aw_dev_update_cali_re, "SND_SOC_AW88399_LIB"); static int aw_dev_fw_crc_check(struct aw_device *aw_dev) { @@ -572,7 +572,7 @@ int aw_dev_get_dsp_status(struct aw_device *aw_dev) return 0; } -EXPORT_SYMBOL_GPL(aw_dev_get_dsp_status); +EXPORT_SYMBOL_NS_GPL(aw_dev_get_dsp_status, "SND_SOC_AW88399_LIB"); static int aw_dev_dsp_check(struct aw_device *aw_dev) { @@ -625,7 +625,7 @@ int aw_dev_set_volume(struct aw_device *aw_dev, unsigned int value) return ret; } -EXPORT_SYMBOL_GPL(aw_dev_set_volume); +EXPORT_SYMBOL_NS_GPL(aw_dev_set_volume, "SND_SOC_AW88399_LIB"); static void aw_dev_fade_in(struct aw_device *aw_dev) { @@ -682,7 +682,7 @@ void aw88399_dev_mute(struct aw_device *aw_dev, bool is_mute) aw_dev_fade_in(aw_dev); } } -EXPORT_SYMBOL_GPL(aw88399_dev_mute); +EXPORT_SYMBOL_NS_GPL(aw88399_dev_mute, "SND_SOC_AW88399_LIB"); static void aw88399_dev_set_dither(struct aw88399 *aw88399, bool dither) { @@ -1038,7 +1038,7 @@ int aw88399_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_n return 0; } -EXPORT_SYMBOL_GPL(aw88399_dev_get_prof_name); +EXPORT_SYMBOL_NS_GPL(aw88399_dev_get_prof_name, "SND_SOC_AW88399_LIB"); static int aw88399_dev_get_prof_data(struct aw_device *aw_dev, int index, struct aw_prof_desc **prof_desc) @@ -1163,7 +1163,7 @@ void aw88399_startup_work(struct work_struct *work) aw88399_start_pa(aw88399); mutex_unlock(&aw88399->lock); } -EXPORT_SYMBOL_GPL(aw88399_startup_work); +EXPORT_SYMBOL_NS_GPL(aw88399_startup_work, "SND_SOC_AW88399_LIB"); void aw88399_start(struct aw88399 *aw88399, bool sync_start) { @@ -1191,7 +1191,7 @@ void aw88399_start(struct aw88399 *aw88399, bool sync_start) &aw88399->start_work, AW88399_START_WORK_DELAY_MS); } -EXPORT_SYMBOL_GPL(aw88399_start); +EXPORT_SYMBOL_NS_GPL(aw88399_start, "SND_SOC_AW88399_LIB"); static int aw_dev_check_sysint(struct aw_device *aw_dev) { @@ -1244,7 +1244,7 @@ int aw88399_stop(struct aw_device *aw_dev) return 0; } -EXPORT_SYMBOL_GPL(aw88399_stop); +EXPORT_SYMBOL_NS_GPL(aw88399_stop, "SND_SOC_AW88399_LIB"); static int aw88399_dev_init(struct aw88399 *aw88399, struct aw_container *aw_cfg) { @@ -1323,7 +1323,7 @@ int aw88399_request_firmware_file(struct aw88399 *aw88399, const char *fw_name) return ret; } -EXPORT_SYMBOL_GPL(aw88399_request_firmware_file); +EXPORT_SYMBOL_NS_GPL(aw88399_request_firmware_file, "SND_SOC_AW88399_LIB"); void aw88399_hw_reset(struct aw88399 *aw88399) { @@ -1336,7 +1336,7 @@ void aw88399_hw_reset(struct aw88399 *aw88399) usleep_range(AW88399_1000_US, AW88399_1000_US + 10); } } -EXPORT_SYMBOL_GPL(aw88399_hw_reset); +EXPORT_SYMBOL_NS_GPL(aw88399_hw_reset, "SND_SOC_AW88399_LIB"); static void aw88399_parse_channel_dt(struct aw_device *aw_dev) { @@ -1401,13 +1401,13 @@ int aw88399_init(struct aw88399 *aw88399, struct i2c_client *i2c, struct regmap return 0; } -EXPORT_SYMBOL_GPL(aw88399_init); +EXPORT_SYMBOL_NS_GPL(aw88399_init, "SND_SOC_AW88399_LIB"); void aw88399_dev_set_channel(struct aw88399 *aw88399, int channel) { aw88399->aw_pa->channel = channel; } -EXPORT_SYMBOL_GPL(aw88399_dev_set_channel); +EXPORT_SYMBOL_NS_GPL(aw88399_dev_set_channel, "SND_SOC_AW88399_LIB"); MODULE_DESCRIPTION("AW88399 common device library"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/aw88399.c b/sound/soc/codecs/aw88399.c index 320dc1b87841c9..c398f66a526255 100644 --- a/sound/soc/codecs/aw88399.c +++ b/sound/soc/codecs/aw88399.c @@ -818,4 +818,5 @@ static struct i2c_driver aw88399_i2c_driver = { module_i2c_driver(aw88399_i2c_driver); MODULE_DESCRIPTION("ASoC AW88399 Smart PA Driver"); +MODULE_IMPORT_NS("SND_SOC_AW88399_LIB"); MODULE_LICENSE("GPL v2"); From 0334cc4c6e26917ca2fee6804923bbbce00ed8aa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 26 Oct 2026 23:32:07 +0000 Subject: [PATCH 015/186] regcache: Validate bounds of regcache drop requests It is not valid for a caller to try to drop a range of registers from the cache where the minimum register is greater than the maximum register, and doing so is likely to trigger bugs in the caches. Reject such invalid inputs. Link: https://patch.msgid.link/20261026-regmap-drop-region-min-max-v1-1-2c28637ede23@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 774e9c9bf3c49f..1447967ff00b87 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -589,6 +589,9 @@ int regcache_drop_region(struct regmap *map, unsigned int min, trace_regcache_drop_region(map, min, max); + if (min > max) + return -EINVAL; + return map->cache_ops->drop(map, min, max); } EXPORT_SYMBOL_GPL(regcache_drop_region); From 3547211537fdc81e04879f73bc543c5ea343b150 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 26 Oct 2026 23:32:08 +0000 Subject: [PATCH 016/186] regcache: Add test for bounds checking of regcache drop requests Validate that regcache_drop_region() rejects regions with a minimum greater than the maximum. Link: https://patch.msgid.link/20261026-regmap-drop-region-min-max-v1-2-2c28637ede23@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-kunit.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/base/regmap/regmap-kunit.c b/drivers/base/regmap/regmap-kunit.c index c78772b660d6c5..c948219f46a39d 100644 --- a/drivers/base/regmap/regmap-kunit.c +++ b/drivers/base/regmap/regmap-kunit.c @@ -1347,6 +1347,26 @@ static void cache_drop(struct kunit *test) KUNIT_EXPECT_MEMEQ(test, &data->vals[param->from_reg], rval, sizeof(rval)); } +static void cache_drop_min_max(struct kunit *test) +{ + const struct regmap_test_param *param = test->param_value; + struct regmap *map; + struct regmap_config config; + struct regmap_ram_data *data; + + config = test_regmap_config; + config.num_reg_defaults = BLOCK_TEST_SIZE; + + map = gen_regmap(test, &config, &data); + KUNIT_ASSERT_FALSE(test, IS_ERR(map)); + if (IS_ERR(map)) + return; + + /* Drop some registers */ + KUNIT_EXPECT_EQ(test, -EINVAL, regcache_drop_region(map, param->from_reg + 5, + param->from_reg + 3)); +} + static void cache_drop_with_non_contiguous_ranges(struct kunit *test) { const struct regmap_test_param *param = test->param_value; @@ -2253,6 +2273,7 @@ static struct kunit_case regmap_test_cases[] = { KUNIT_CASE_PARAM(cache_sync_readonly, real_cache_types_gen_params), KUNIT_CASE_PARAM(cache_sync_patch, real_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop, sparse_cache_types_gen_params), + KUNIT_CASE_PARAM(cache_drop_min_max, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop_with_non_contiguous_ranges, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop_all_and_sync_marked_dirty, sparse_cache_types_gen_params), KUNIT_CASE_PARAM(cache_drop_all_and_sync_no_defaults, sparse_cache_types_gen_params), From 477267f7f7547dd2056e90aa475c6b69497903ac Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 26 Oct 2026 21:43:29 +0000 Subject: [PATCH 017/186] regcache: rbtree: Restore async flag when exiting on error rbtree cache syncs run with asynchronous I/O enabled, this is indicated by setting the async flag on the regmap. During a normal exit we reset to synchronous mode but if we exit early due to an error this restore is missed and the regmap is left in async mode. Reset the flag on error as well. Link: https://patch.msgid.link/20261026-regcache-rbtree-restore-sync-v1-1-2ef0e9d57ec6@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index e2feed6dbecffd..26ac2c2a209508 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -494,8 +494,10 @@ static int regcache_rbtree_sync(struct regmap *map, unsigned int min, ret = regcache_sync_block(map, rbnode->block, rbnode->cache_present, rbnode->base_reg, start, end); - if (ret != 0) + if (ret != 0) { + map->async = false; return ret; + } } map->async = false; From e1b6fd745176ca77228884554274dcf191f268c6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 26 Oct 2026 16:57:00 +0000 Subject: [PATCH 018/186] regcache: maple: Don't overflow the register number when inserting When inserting a previously uncached register into the maple cache we check for adjacent blocks of registers above and below the register number we are inserting and merge there. Currently we check for reg - 1 and reg + 1 without worrying about if reg is 0 or UINT_MAX, meaning that the register number will overflow and we could consider a block at the other extremity of possible register numbers adjacent. Avoid doing this by clamping to 0 and UINT_MAX, if we do this then our search for an adjacent block will fail as we will be looking for the register number we are trying to insert. The current behaviour means that in the event that we do attempt to add 0 and merge it with a block at UINT_MAX kmalloc_array() rejects the allocation for being absurdly large, this results in us failing the write. Such register maps are very unlikely due to the normal patterns of register design and the fact that register numbers usually have less bits than an int. Link: https://patch.msgid.link/20261026-regmap-maple-bounds-v1-1-446f3eb8d406@kernel.org Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-maple.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/base/regmap/regcache-maple.c b/drivers/base/regmap/regcache-maple.c index 16e488da5f230c..9bc217b1e6f9ed 100644 --- a/drivers/base/regmap/regcache-maple.c +++ b/drivers/base/regmap/regcache-maple.c @@ -41,7 +41,7 @@ static int regcache_maple_write(struct regmap *map, unsigned int reg, struct maple_tree *mt = map->cache; MA_STATE(mas, mt, reg, reg); unsigned long *entry, *upper, *lower; - unsigned long index, last; + unsigned long index, last, lower_reg, upper_reg; size_t lower_sz, upper_sz; int ret; @@ -54,18 +54,33 @@ static int regcache_maple_write(struct regmap *map, unsigned int reg, return 0; } - /* Any adjacent entries to extend/merge? */ - mas_set_range(&mas, reg - 1, reg + 1); + /* Avoid overflowing and wrapping */ + if (reg != 0) + lower_reg = reg - 1; + else + lower_reg = reg; + + if (reg != UINT_MAX) + upper_reg = reg + 1; + else + upper_reg = reg; + + /* + * Any adjacent entries to extend/merge? If we are here then + * reg won't exist so if we clamped lower_reg or upper_reg we + * won't get anything from mas_find(). + */ + mas_set_range(&mas, lower_reg, upper_reg); index = reg; last = reg; - lower = mas_find(&mas, reg - 1); + lower = mas_find(&mas, lower_reg); if (lower) { index = mas.index; lower_sz = (mas.last - mas.index + 1) * sizeof(unsigned long); } - upper = mas_find(&mas, reg + 1); + upper = mas_find(&mas, upper_reg); if (upper) { last = mas.last; upper_sz = (mas.last - mas.index + 1) * sizeof(unsigned long); From afd88e7ad78170040612f608203734156b8a9eb1 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 20 Oct 2026 08:38:55 +0100 Subject: [PATCH 019/186] soundwire: stream: skip bps mismatch check for PDM streams PDM data-port terminals carry a wire word length defined by the stream payload, not by any host-side bps field. Skip the sdw_config_stream() bps mismatch check for SDW_STREAM_PDM. Behaviour for SDW_STREAM_PCM and SDW_STREAM_BPT is unchanged. Assisted-by: LLM Signed-off-by: Srinivas Kandagatla Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20261020073856.1177749-6-srinivas.kandagatla@oss.qualcomm.com Signed-off-by: Vinod Koul --- drivers/soundwire/stream.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 0b00fcdcab2d6d..31db368ba24b87 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1374,7 +1374,9 @@ static int sdw_config_stream(struct device *dev, return -EINVAL; } - if (stream->params.bps && + /* PDM streams have no stream-wide bps; skip the match for them. */ + if (stream_config->type != SDW_STREAM_PDM && + stream->params.bps && stream->params.bps != stream_config->bps) { dev_err(dev, "bps not matching, stream:%s\n", stream->name); return -EINVAL; From acebd8e4f16f6473676338b9a430c837dbe00315 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Mon, 2 Nov 2026 12:30:20 +0100 Subject: [PATCH 020/186] regmap: Annotate regmap_access_table members with __counted_by_ptr() Add __counted_by_ptr() to regmap_access_table::yes_ranges and regmap_access_table::no_ranges to improve access bounds checking via CONFIG_UBSAN_BOUNDS and CONFIG_FORTIFY_SOURCE. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261102113019.265822-2-blum@kernel.org Signed-off-by: Mark Brown --- include/linux/regmap.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/regmap.h b/include/linux/regmap.h index c8aebd148e08ec..a72f17ab6264ea 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -248,9 +248,9 @@ struct regmap_range { * check function will return true. "no_ranges" are searched first. */ struct regmap_access_table { - const struct regmap_range *yes_ranges; + const struct regmap_range *yes_ranges __counted_by_ptr(n_yes_ranges); unsigned int n_yes_ranges; - const struct regmap_range *no_ranges; + const struct regmap_range *no_ranges __counted_by_ptr(n_no_ranges); unsigned int n_no_ranges; }; From 740fb77b38380bfca0b253be17b310db18c0de79 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:55 +0000 Subject: [PATCH 021/186] ASoC: audio-graph-card2: fix device node leaks in graph_count_c2c() graph_count_c2c() takes two references that it never drops: the one from of_node_get(lnk) stored in port0, and the extra one added on top of the reference of_graph_get_next_port() already returns for port1. Both nodes are leaked once per codec2codec link parsed at probe time. Release port0 with the cleanup attribute and drop the redundant of_node_get() around the port1 lookup, so that the cleanup attribute balances the single reference of_graph_get_next_port() returns. This is the same ownership model audio_graph2_link_c2c() uses. Fixes: 5f281c3e82b1 ("ASoC: audio-graph-card2: use __free(device_node) for device node") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Acked-by: Kuninori Morimoto Link: https://patch.msgid.link/20261104024055.1508771-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/generic/audio-graph-card2.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/soc/generic/audio-graph-card2.c b/sound/soc/generic/audio-graph-card2.c index 503170578cff81..f20301c3a4412b 100644 --- a/sound/soc/generic/audio-graph-card2.c +++ b/sound/soc/generic/audio-graph-card2.c @@ -1199,8 +1199,9 @@ static int graph_count_c2c(struct simple_util_priv *priv, struct link_info *li) { struct device_node *ports __free(device_node) = port_to_ports(lnk); - struct device_node *port0 = of_node_get(lnk); - struct device_node *port1 = of_node_get(of_graph_get_next_port(ports, of_node_get(port0))); + struct device_node *port0 __free(device_node) = of_node_get(lnk); + struct device_node *port1 __free(device_node) = + of_graph_get_next_port(ports, of_node_get(port0)); struct device_node *ep0 __free(device_node) = of_graph_get_next_port_endpoint(port0, NULL); struct device_node *ep1 __free(device_node) = of_graph_get_next_port_endpoint(port1, NULL); struct device_node *codec0 __free(device_node) = of_graph_get_remote_port(ep0); From cf09d02dc0c49593fdd9e0ec2f17251cba39a94e Mon Sep 17 00:00:00 2001 From: Wei Lixiang Date: Wed, 4 Nov 2026 09:50:58 +0800 Subject: [PATCH 022/186] ASoC: amd: yc: drop duplicated DMI quirk entries Two entries of yc_acp_quirk_table[] are present twice, so the second copy of each can never match: dmi_first_match() returns the first hit and the rest of the table is only walked when that lookup fails. The LENOVO/21D0 entry was added twice in October 2022, once for the ThinkBook 14/16 Gen 4+ ARA (a450b5c87392) and again ten days later for the ThinkBook 14+ 2022 (a75481fa00cc). The ASUSTeK/BM1403CDA entry was added for the ExpertBook BM1403CDA (f200b2f9a810) and then duplicated when that fix was merged into the development branch by commit 5a306bef5988 ("ASoC: Merge up fixes"), which kept both copies while resolving the conflict. Removing the redundant copies keeps the table easy to audit when new quirks are added; the set of matched machines is unchanged. Signed-off-by: Wei Lixiang Reviewed-by: Zhang Heng Link: https://patch.msgid.link/20261104015058.6-1-weilixiang@kylinos.cn Signed-off-by: Mark Brown --- sound/soc/amd/yc/acp6x-mach.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sound/soc/amd/yc/acp6x-mach.c b/sound/soc/amd/yc/acp6x-mach.c index 4908f4cc147a34..7ab48e3629be8b 100644 --- a/sound/soc/amd/yc/acp6x-mach.c +++ b/sound/soc/amd/yc/acp6x-mach.c @@ -87,13 +87,6 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "21D0"), } }, - { - .driver_data = &acp6x_card, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_NAME, "21D0"), - } - }, { .driver_data = &acp6x_card, .matches = { @@ -899,13 +892,6 @@ static const struct dmi_system_id yc_acp_quirk_table[] = { DMI_MATCH(DMI_BOARD_NAME, "BM1403CDA"), } }, - { - .driver_data = &acp6x_card, - .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_BOARD_NAME, "BM1403CDA"), - } - }, { .driver_data = &acp6x_card, .matches = { From 1ba6a1d89d1119bb05f640ac27e23babb226efcc Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:28:14 +0000 Subject: [PATCH 023/186] ASoC: codecs: madera: release runtime PM ref on FLL config failure When the FLL is not already enabled, madera_fllhj_enable() takes a runtime PM reference and relies on madera_fllhj_disable() to drop it later. If madera_fllhj_apply() fails, the ENA bit is never set, so a subsequent disable sees no change and does not drop the reference, leaking it along with the device's active state. Drop the reference on the failure path and skip waiting for a lock that will never be asserted. Fixes: 6535e831b4c8 ("ASoC: cs47l92: Add codec driver for Cirrus Logic CS47L92") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Reviewed-by: Charles Keepax Link: https://patch.msgid.link/20261104022814.1484284-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/madera.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/madera.c b/sound/soc/codecs/madera.c index 58255844b4df37..2f6256f8e87b2a 100644 --- a/sound/soc/codecs/madera.c +++ b/sound/soc/codecs/madera.c @@ -4608,8 +4608,17 @@ static int madera_fllhj_enable(struct madera_fll *fll) MADERA_FLL1_HOLD_MASK, 0); - if (!already_enabled) - madera_wait_for_fll(fll, true); + /* + * If configuring the FLL failed it is left disabled, so a later + * madera_fllhj_disable() will not see the ENA bit set and will not + * drop the runtime PM reference taken above. + */ + if (!already_enabled) { + if (ret) + pm_runtime_put_autosuspend(madera->dev); + else + madera_wait_for_fll(fll, true); + } return 0; } From 53865f2d4974630cae996e7a390a3561749b5494 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:28:15 +0000 Subject: [PATCH 024/186] ASoC: fsl: imx-sgtl5000: drop codec device reference after probe imx_sgtl5000_probe() takes a reference on the codec i2c device, but the successful probe path returns without releasing it. Only the error paths drop the reference, so every successful probe leaks the i2c client device. Release the reference once the card has been registered. Fixes: 81e8e4926167 ("ASoC: fsl: add sgtl5000 clock support for imx-sgtl5000") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Reviewed-by: Valerio Setti Link: https://patch.msgid.link/20261104022815.1484320-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/fsl/imx-sgtl5000.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c index 8bcf54ef709e1f..8ce333eb47fe6e 100644 --- a/sound/soc/fsl/imx-sgtl5000.c +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -177,6 +177,8 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) goto put_device; } + put_device(&codec_dev->dev); + of_node_put(ssi_np); of_node_put(codec_np); From aa56acd476296a1b41925d30d4742e93dd69de03 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Wed, 4 Nov 2026 19:32:28 +0800 Subject: [PATCH 025/186] ASoC: imx-audmux: Convert to subsys_platform_driver() Replace the open-coded subsys_initcall()/module_exit() platform driver registration with subsys_platform_driver(). No functional change. Signed-off-by: Peng Fan Reviewed-by: Daniel Baluta Link: https://patch.msgid.link/20261104113228.1634005-1-peng.fan@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-audmux.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/sound/soc/fsl/imx-audmux.c b/sound/soc/fsl/imx-audmux.c index f8335a04595a76..d46f43ab66cf62 100644 --- a/sound/soc/fsl/imx-audmux.c +++ b/sound/soc/fsl/imx-audmux.c @@ -361,18 +361,7 @@ static struct platform_driver imx_audmux_driver = { .of_match_table = imx_audmux_dt_ids, } }; - -static int __init imx_audmux_init(void) -{ - return platform_driver_register(&imx_audmux_driver); -} -subsys_initcall(imx_audmux_init); - -static void __exit imx_audmux_exit(void) -{ - platform_driver_unregister(&imx_audmux_driver); -} -module_exit(imx_audmux_exit); +subsys_platform_driver(imx_audmux_driver); MODULE_DESCRIPTION("Freescale i.MX AUDMUX driver"); MODULE_AUTHOR("Sascha Hauer "); From ac1cac4c1f1b143650f7261ae78f64a3e22fa4d6 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 29 Oct 2026 22:05:37 +0900 Subject: [PATCH 026/186] ALSA: ump: Fix dropped data bytes at UMP System message conversion The cvt_ump_system_to_legacy() function switches on ump_message_status_code(), which returns only the upper 4 bits of the status. The case labels are the full 0xf1, 0xf2 and 0xf3 values, so they never match. MIDI Time Code, Song Position and Song Select are converted to the status byte alone, and their data bytes are lost. Switch on the full status byte in buf[0] instead. Both callers of snd_ump_convert_from_ump() pass the result on as is: the legacy rawmidi input of a UMP endpoint, and the MIDI 1.0 path of the f_midi2 USB gadget. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Cc: stable@vger.kernel.org Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261029130538.1985867-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- sound/core/ump_convert.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index 0a7972d91ec52c..ecbd894f4c83ae 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -69,7 +69,7 @@ static u32 upscale_14_to_32bit(u16 src) static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf) { buf[0] = ump_message_status_channel(data); - switch (ump_message_status_code(data)) { + switch (buf[0]) { case UMP_SYSTEM_STATUS_MIDI_TIME_CODE: case UMP_SYSTEM_STATUS_SONG_SELECT: buf[1] = (data >> 8) & 0x7f; From eb296114d5ba4d335a640dbbbbc9618fded0ffe7 Mon Sep 17 00:00:00 2001 From: Chris Simpson Date: Thu, 29 Oct 2026 16:49:10 -0600 Subject: [PATCH 027/186] ALSA: hda/realtek: Enable bass speakers on Alienware m17 R3 The Alienware m17 R3 (ALC289, PCI SSID 1028:09d1) has a second speaker pair, the bass speakers, on pin 0x17, which the BIOS leaves unconnected (0x411111f0). No PCI SSID quirk matches the machine, so it gets ALC269_FIXUP_DELL4_MIC_NO_PRESENCE from the fallback pin quirk table, and only the speakers on pin 0x14 play. Use ALC289_FIXUP_DUAL_SPK, as on the Alienware x16 R2 and Dell XPS 15 9510/9520: it sets pin 0x17 up as a speaker on DAC 0x02, so one volume control covers both pairs, and chains to ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, so the headset mic setup is unchanged. Tested on an Alienware m17 R3 (BIOS 1.38.0), Linux 7.3 with this change applied: the bass speakers play, and volume and mute cover both pairs, also after suspend and resume. Headphones and a headset mic were not tested. I used Claude Code (Claude Opus 5.5) to find the unused pin, pick the existing fixup and write this patch and changelog; I did the listening tests myself. Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Chris Simpson Link: https://patch.msgid.link/20261029-alc289-m17r3-v1-1-5d3eac605e7a@nilk.net Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index ee8d8385d1934c..5bc77f7f8f5375 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7604,6 +7604,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1028, 0x097e, "Dell Precision", ALC289_FIXUP_DUAL_SPK), SND_PCI_QUIRK(0x1028, 0x098d, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), SND_PCI_QUIRK(0x1028, 0x09bf, "Dell Precision", ALC233_FIXUP_ASUS_MIC_NO_PRESENCE), + SND_PCI_QUIRK(0x1028, 0x09d1, "Alienware m17 R3", ALC289_FIXUP_DUAL_SPK), SND_PCI_QUIRK(0x1028, 0x0a2e, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1028, 0x0a30, "Dell", ALC236_FIXUP_DELL_AIO_HEADSET_MIC), SND_PCI_QUIRK(0x1028, 0x0a38, "Dell Latitude 7520", ALC269_FIXUP_DELL4_MIC_NO_PRESENCE_QUIET), From e72d0658e9c1d7cbb12b2ba4813abacb1fb13372 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 30 Oct 2026 12:11:59 +0100 Subject: [PATCH 028/186] ALSA: usx2y: Use prepare_inurb() in usb_stream_start() Replace the open-coded packet count and transfer buffer length assignments with prepare_inurb(). Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261030111200.114945-3-blum@kernel.org Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usb_stream.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c index 7d621280fba33d..15e01c77567c61 100644 --- a/sound/usb/usx2y/usb_stream.c +++ b/sound/usb/usx2y/usb_stream.c @@ -692,10 +692,7 @@ int usb_stream_start(struct usb_stream_kernel *sk) struct urb *outurb = sk->outurb[u]; playback_prep_freqn(sk, outurb); - inurb->number_of_packets = outurb->number_of_packets; - inurb->transfer_buffer_length = - inurb->number_of_packets * - inurb->iso_frame_desc[0].length; + prepare_inurb(outurb->number_of_packets, inurb); if (u == 0) { int now; From 023d3e1176202339cd94a3d732e3aec74e36ba22 Mon Sep 17 00:00:00 2001 From: Alex Kazinsky Date: Fri, 30 Oct 2026 18:25:27 +0400 Subject: [PATCH 029/186] ALSA: cs46xx: Handle SCB creation failures before dereferencing With CONFIG_PM_SLEEP enabled, cs46xx_dsp_create_scb() can return NULL if kmemdup() fails to allocate the SCB data saved for resume. cs46xx_dsp_scb_and_task_init() and _dsp_create_generic_scb() dereference the returned pointer without checking it, causing a NULL pointer dereference on allocation failure. Check both return values before accessing the SCB descriptors. Return -ENOMEM when creating the null SCB fails and propagate NULL from _dsp_create_generic_scb(). Found by Linux Verification Center (linuxtesting.org) with SVACE static analysis tool. Fixes: 41116e926cb9 ("ALSA: cs46xx - Fix suspend/resume with new DSP") Cc: stable@vger.kernel.org Signed-off-by: Alex Kazinsky Link: https://patch.msgid.link/20261030142529.415697-1-a.kazinsky@ideco.ru Signed-off-by: Takashi Iwai --- sound/pci/cs46xx/dsp_spos.c | 2 ++ sound/pci/cs46xx/dsp_spos_scb_lib.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sound/pci/cs46xx/dsp_spos.c b/sound/pci/cs46xx/dsp_spos.c index a77e6f5a14ada1..57b0a6321e0b6b 100644 --- a/sound/pci/cs46xx/dsp_spos.c +++ b/sound/pci/cs46xx/dsp_spos.c @@ -1104,6 +1104,8 @@ int cs46xx_dsp_scb_and_task_init (struct snd_cs46xx *chip) null_scb.entry_point = null_algorithm->address; ins->the_null_scb = cs46xx_dsp_create_scb(chip, "nullSCB", (u32 *)&null_scb, NULL_SCB_ADDR); + if (!ins->the_null_scb) + return -ENOMEM; ins->the_null_scb->task_entry = null_algorithm; ins->the_null_scb->sub_list_ptr = ins->the_null_scb; ins->the_null_scb->next_scb_ptr = ins->the_null_scb; diff --git a/sound/pci/cs46xx/dsp_spos_scb_lib.c b/sound/pci/cs46xx/dsp_spos_scb_lib.c index 71eee5cb478f40..ac914bf784c816 100644 --- a/sound/pci/cs46xx/dsp_spos_scb_lib.c +++ b/sound/pci/cs46xx/dsp_spos_scb_lib.c @@ -274,7 +274,8 @@ _dsp_create_generic_scb (struct snd_cs46xx *chip, char * name, u32 * scb_data, u dev_dbg(chip->card->dev, "dsp_spos: creating SCB <%s>\n", name); scb = cs46xx_dsp_create_scb(chip,name,scb_data,dest); - + if (!scb) + return NULL; scb->sub_list_ptr = ins->the_null_scb; scb->next_scb_ptr = ins->the_null_scb; From 9bfbc1af07137f5d4792de282091d16ac36f6ea5 Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Fri, 30 Oct 2026 17:10:14 +0000 Subject: [PATCH 030/186] ALSA: hda: cs35l41: Add common wait_for_boot api and reset_and_boot api Both the ASoC CS35L41 and HDA CS35L41 drivers currently use regmap_read_poll_timeout() calls to wait for the chip to boot. However, this macro will exit immediately if one of the regmap_read() calls fail. This could be cause by the hardware not being ready yet after a reset, which then breaks the loop early and leads to probe and resume calls failing. This patch replaces the existing code in the HDA driver with a function with a loop which does not break on regmap_read() failure. Since both drivers need to do this, make a common function in cs35l41-lib.c to be called from both drivers. This patch only updates the HDA driver, a separate patch will update the ASoC driver. In addition, before the call to wait to boot, the HDA driver also does a software reset to ensure the device is in a known state before probe or resume (for example, if there is no reset gpio). Since this write could fail for the same reason, add a common api to do this with a retry mechanism. Signed-off-by: Stefan Binding Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261030171045.469944-2-sbinding@opensource.cirrus.com --- include/sound/cs35l41.h | 2 + sound/hda/codecs/side-codecs/cs35l41_hda.c | 33 +---------- sound/soc/codecs/cs35l41-lib.c | 67 ++++++++++++++++++++++ 3 files changed, 71 insertions(+), 31 deletions(-) diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h index 7542cabfa7266e..b6b338c55ff0bf 100644 --- a/include/sound/cs35l41.h +++ b/include/sound/cs35l41.h @@ -924,5 +924,7 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type); int cs35l41_mdsync_up(struct regmap *regmap); int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type, int enable, struct cs_dsp *dsp); +int cs35l41_wait_boot_done(struct device *dev, struct regmap *regmap); +int cs35l41_reset_and_boot(struct device *dev, struct regmap *regmap); #endif /* __CS35L41_H */ diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index d50c3d8f5303c7..6fbc194d334732 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -974,30 +974,6 @@ static int cs35l41_system_suspend(struct device *dev) return ret; } -static int cs35l41_wait_boot_done(struct cs35l41_hda *cs35l41) -{ - unsigned int int_status; - int ret; - - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status, - int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000); - if (ret) { - dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE\n"); - return ret; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); - if (ret || (int_status & CS35L41_OTP_BOOT_ERR)) { - dev_err(cs35l41->dev, "OTP Boot status %x error\n", - int_status & CS35L41_OTP_BOOT_ERR); - if (!ret) - ret = -EIO; - return ret; - } - - return 0; -} - static int cs35l41_system_resume(struct device *dev) { struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); @@ -1020,10 +996,7 @@ static int cs35l41_system_resume(struct device *dev) regcache_cache_only(cs35l41->regmap, false); - regmap_write(cs35l41->regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); - usleep_range(2000, 2100); - - ret = cs35l41_wait_boot_done(cs35l41); + ret = cs35l41_reset_and_boot(cs35l41->dev, cs35l41->regmap); if (ret) return ret; @@ -2004,11 +1977,9 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); } - usleep_range(2000, 2100); - regmap_write(cs35l41->regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); usleep_range(2000, 2100); - ret = cs35l41_wait_boot_done(cs35l41); + ret = cs35l41_reset_and_boot(cs35l41->dev, cs35l41->regmap); if (ret) goto err; diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c index b0fa80705be7d2..973713626ebd43 100644 --- a/sound/soc/codecs/cs35l41-lib.c +++ b/sound/soc/codecs/cs35l41-lib.c @@ -17,6 +17,8 @@ #include #define CS35L41_FIRMWARE_OLD_VERSION 0x001C00 /* v0.28.0 */ +#define CS35L41_GLOBAL_BOOT_DONE_TIMEOUT_US 100000 +#define CS35L41_GLOBAL_BOOT_DONE_SLEEP_US 2000 static const struct reg_default cs35l41_reg[] = { { CS35L41_PWR_CTRL1, 0x00000000 }, @@ -1586,6 +1588,71 @@ int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap) } EXPORT_SYMBOL_GPL(cs35l41_exit_hibernate); +int cs35l41_wait_boot_done(struct device *dev, struct regmap *regmap) +{ + ktime_t timeout; + unsigned int val = 0; + int ret = 0; + + /* + * The chip could NAK a read request while it is booting so ignore + * errors from regmap_read. + */ + timeout = ktime_add_us(ktime_get(), CS35L41_GLOBAL_BOOT_DONE_TIMEOUT_US); + regmap_read(regmap, CS35L41_IRQ1_STATUS4, &val); + while (!(val & CS35L41_OTP_BOOT_DONE) && !ktime_after(ktime_get(), timeout)) { + usleep_range(CS35L41_GLOBAL_BOOT_DONE_SLEEP_US / 2, + CS35L41_GLOBAL_BOOT_DONE_SLEEP_US); + regmap_read(regmap, CS35L41_IRQ1_STATUS4, &val); + } + + if (!(val & CS35L41_OTP_BOOT_DONE)) { + dev_err(dev, "Timeout waiting for OTP_BOOT_DONE\n"); + return -ETIMEDOUT; + } + + ret = regmap_read(regmap, CS35L41_IRQ1_STATUS3, &val); + if (ret || (val & CS35L41_OTP_BOOT_ERR)) { + dev_err(dev, "OTP Boot status %x error\n", + val & CS35L41_OTP_BOOT_ERR); + if (!ret) + ret = -EIO; + return ret; + } + return 0; +} +EXPORT_SYMBOL_GPL(cs35l41_wait_boot_done); + +int cs35l41_reset_and_boot(struct device *dev, struct regmap *regmap) +{ + int ret; + + ret = regmap_write(regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); + if (ret) { + dev_warn(dev, "Failed to write software reset: %d\n", ret); + + /* + * If the write failed, it is likely because the chip is still booting after a + * reset. Wait for boot, then retry the software reset. If the initial write failed + * for another reason, then either waiting for boot, or the second write will fail, + * So we can return the error then. + */ + ret = cs35l41_wait_boot_done(dev, regmap); + if (ret) + return ret; + + ret = regmap_write(regmap, CS35L41_SFT_RESET, CS35L41_SOFTWARE_RESET); + if (ret) { + dev_err(dev, "Failed to write software reset on retry: %d\n", ret); + return ret; + } + } + usleep_range(2000, 2100); + + return cs35l41_wait_boot_done(dev, regmap); +} +EXPORT_SYMBOL_GPL(cs35l41_reset_and_boot); + MODULE_DESCRIPTION("CS35L41 library"); MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, "); MODULE_AUTHOR("Lucas Tanure, Cirrus Logic Inc, "); From a04a1a820f3735fb9c8acc66ce1c05e354296ebe Mon Sep 17 00:00:00 2001 From: Stefan Binding Date: Fri, 30 Oct 2026 17:10:15 +0000 Subject: [PATCH 031/186] ASoC: cs35l41: Use common wait_for_boot and reset_and_boot apis Use the newly added common wait_for_boot and reset_and_boot apis on probe. This should prevent issues occurring if the chip is not ready on before it starts reading and writing to it. In addition, the driver now ensures the chip is reset using a software reset, when calling reset_and_boot(). Signed-off-by: Stefan Binding Acked-by: Mark Brown Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261030171045.469944-3-sbinding@opensource.cirrus.com --- sound/soc/codecs/cs35l41.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c index d286025183f466..0e40444a10034d 100644 --- a/sound/soc/codecs/cs35l41.c +++ b/sound/soc/codecs/cs35l41.c @@ -1211,7 +1211,7 @@ static int cs35l41_get_system_name(struct cs35l41_private *cs35l41) int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg) { - u32 regid, reg_revid, i, mtl_revid, int_status, chipid_match; + u32 regid, reg_revid, i, mtl_revid, chipid_match; int irq_pol = 0; int ret; @@ -1260,21 +1260,9 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * usleep_range(2000, 2100); - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, - int_status, int_status & CS35L41_OTP_BOOT_DONE, - 1000, 100000); - if (ret) { - dev_err_probe(cs35l41->dev, ret, - "Failed waiting for OTP_BOOT_DONE\n"); - goto err; - } - - regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); - if (int_status & CS35L41_OTP_BOOT_ERR) { - dev_err(cs35l41->dev, "OTP Boot error\n"); - ret = -EINVAL; + ret = cs35l41_reset_and_boot(cs35l41->dev, cs35l41->regmap); + if (ret) goto err; - } ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); if (ret < 0) { From 316768d7e3fd77855ccf460918152d0f4ca21e45 Mon Sep 17 00:00:00 2001 From: Stjepan Poljak Date: Thu, 29 Oct 2026 17:57:01 +0100 Subject: [PATCH 032/186] ALSA: usb-audio: don't resubmit MIDI URBs at once on -EPIPE When a USB MIDI device answers with a stall, as a bus-powered keyboard does while it is being switched off, snd_usbmidi_urb_error() falls through to the default case: it logs "urb status -32" and returns 0, so the completion handler resubmits the URB at once. That fails the same way, so the URBs loop, logging every failure, until the disconnect is processed. On a Raspberry Pi 3B+ with a keyboard behind the onboard hub, that disconnect is never processed. Printing to the framebuffer and serial consoles holds the loop to about 250 messages a second, and all interrupts are handled on CPU 0, so the hub's port change isn't handled: the keyboard stays silent until it is replugged. With loglevel=3, the same kernel processes the disconnect 0.04-0.24 s after the first error and the keyboard works again. Treat -EPIPE like the other errors seen while a device goes away: retry from the error timer after ERROR_DELAY_JIFFIES, without a message. An endpoint that is really halted wasn't cleared before either; it is now retried every 100 ms instead of continuously. Tested on the Raspberry Pi 3B+ (rpi-6.12.y, dwc_otg) with the default console settings: after each off/on the disconnect is processed, the keyboard re-enumerates and plays, and nothing is logged for the stall. Assisted-by: LLM Signed-off-by: Stjepan Poljak Link: https://patch.msgid.link/20261029165701.2004391-1-stjepan.poljak@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/midi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/usb/midi.c b/sound/usb/midi.c index e3d15755406138..5604c3fdbe3b75 100644 --- a/sound/usb/midi.c +++ b/sound/usb/midi.c @@ -194,10 +194,11 @@ static int snd_usbmidi_urb_error(const struct urb *urb) case -ESHUTDOWN: case -ENODEV: return -ENODEV; - /* errors that might occur during unplugging */ + /* errors that might occur during unplugging or switching off */ case -EPROTO: case -ETIME: case -EILSEQ: + case -EPIPE: return -EIO; default: dev_err(&urb->dev->dev, "urb status %d\n", urb->status); From 84f01b7c17d32ead07cffe83992014e681130f8e Mon Sep 17 00:00:00 2001 From: ahmad baghereslami Date: Fri, 30 Oct 2026 08:26:26 +0330 Subject: [PATCH 033/186] ALSA: hda/conexant: Add mute LED quirk for HP ZBook 17 G3 The HP ZBook 17 G3 (PCI SSID 103c:80d6, codec CX20724 with SSID 103c:80f8) has its speaker-mute LED on codec GPIO0 and its mic-mute LED on GPIO1, both active high, like the HP ZBook 15u G3 that already uses CXT_FIXUP_MUTE_LED_GPIO. HP's Windows driver agrees: chdrt.inf sets EnableGPIO0MuteSignal for this model, and MicTray's MasterMute section for SUBSYS_103C80D6/103C80F8 sets PlaybackGPIO = 0 and CaptureGPIO = 1. Use CXT_FIXUP_MUTE_LED_GPIO for it. Tested on an HP ZBook 17 G3 with Ubuntu's 7.0.0 kernel: with each GPIO held high and low, GPIO0 switches only the speaker-mute LED and GPIO1 only the mic-mute LED; GPIO2-6 switch neither. With this quirk applied, hda::mute and hda::micmute follow the speaker and mic mute, and sound and the microphone keep working. Finding the LEDs (the GPIO tests and reading HP's driver) and writing this patch were done with the help of an AI coding assistant (Claude); I checked the LEDs on the machine. Signed-off-by: ahmad baghereslami Link: https://patch.msgid.link/20261030045626.1608040-1-ahmad.b1995@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/conexant.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/conexant.c b/sound/hda/codecs/conexant.c index a141c94eee13b4..88651a36dd3372 100644 --- a/sound/hda/codecs/conexant.c +++ b/sound/hda/codecs/conexant.c @@ -1193,6 +1193,7 @@ static const struct hda_quirk cxt5066_fixups[] = { SND_PCI_QUIRK(0x1025, 0x1937, "Acer Aspire Go 16", CXT_FIXUP_ACER_ASPIRE_GO_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8079, "HP EliteBook 840 G3", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x807C, "HP EliteBook 820 G3", CXT_FIXUP_HP_DOCK), + SND_PCI_QUIRK(0x103c, 0x80d6, "HP ZBook 17 G3", CXT_FIXUP_MUTE_LED_GPIO), SND_PCI_QUIRK(0x103c, 0x80FD, "HP ProBook 640 G2", CXT_FIXUP_HP_DOCK), SND_PCI_QUIRK(0x103c, 0x8115, "HP Z1 Gen3", CXT_FIXUP_HP_GATE_MIC), SND_PCI_QUIRK(0x103c, 0x814f, "HP ZBook 15u G3", CXT_FIXUP_MUTE_LED_GPIO), From f6c7bb1f0799591a62be75b9bcf84245b4f208b7 Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Sun, 1 Nov 2026 03:09:36 +0800 Subject: [PATCH 034/186] ALSA: bcd2000: Fix use-after-free of MIDI URBs on file close bcd2000_disconnect() calls snd_card_disconnect(), which only prevents new opens, and then frees the MIDI URBs. A rawmidi file that was already open can still be closed afterwards, and its close callback, bcd2000_midi_output_close(), calls usb_kill_urb() on midi_out_urb. The URB pointers are cleared on disconnect and their accesses are serialized with midi_lock, but bcd2000_midi_output_close() reads midi_out_urb without that lock. The check can therefore pass just before the pointer is cleared, and usb_kill_urb() then runs on the URB that has already been freed: BUG: KASAN: slab-use-after-free in usb_kill_urb+0x74/0x80 Read of size 8 at addr ffff8880078e7d40 usb_kill_urb bcd2000_midi_output_close close_substream rawmidi_release_priv snd_rawmidi_release Allocated by: usb_alloc_urb <- bcd2000_init_midi <- bcd2000_probe Freed by: usb_free_urb <- bcd2000_midi_free <- bcd2000_disconnect Take the lock in bcd2000_midi_output_close() and reference the URB with usb_get_urb() before releasing it. The URB then stays valid until usb_put_urb() after usb_kill_urb() has returned, even if a disconnect frees it in the meantime. usb_kill_urb() itself cannot run under midi_lock, as the completion handler takes the same lock. Fixes: b47a22290d58 ("ALSA: MIDI driver for Behringer BCD2000 USB device") Cc: stable@vger.kernel.org Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261031190936.5696-1-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/bcd2000/bcd2000.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c index 2bd49bf8274899..0fd086cac9552d 100644 --- a/sound/usb/bcd2000/bcd2000.c +++ b/sound/usb/bcd2000/bcd2000.c @@ -183,10 +183,24 @@ static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream) static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream) { struct bcd2000 *bcd2k = substream->rmidi->private_data; + struct urb *urb = NULL; - if (bcd2k->midi_out_active && bcd2k->midi_out_urb) { - usb_kill_urb(bcd2k->midi_out_urb); - bcd2k->midi_out_active = 0; + /* + * Reference the URB under the lock so that it stays valid until + * usb_kill_urb() returns, even if a disconnect frees it in the + * meantime. usb_kill_urb() cannot run under the lock, as the + * completion handler takes the same lock. + */ + scoped_guard(spinlock_irqsave, &bcd2k->midi_lock) { + if (bcd2k->midi_out_active && bcd2k->midi_out_urb) { + urb = usb_get_urb(bcd2k->midi_out_urb); + bcd2k->midi_out_active = 0; + } + } + + if (urb) { + usb_kill_urb(urb); + usb_put_urb(urb); } return 0; From 1f8101b6adc87a3ddbd51fcb6609251591758026 Mon Sep 17 00:00:00 2001 From: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Date: Sun, 1 Nov 2026 03:15:57 +0800 Subject: [PATCH 035/186] ALSA: usx2y: Refuse a retried DSP load after a failure snd_usx2y_hwdep_dsp_load() sets up the asynchronous sequence URBs and the interrupt URB when the FPGA firmware image with index 1 is loaded, and then creates the ALSA devices. If usx2y_in04_init() or usx2y_create_alsa_devices() fails, the URBs that were already set up are left in place. The core keeps the index unloaded when the callback fails, so user space may issue the same request again. The second attempt then enters usx2y_async_seq04_init() or usx2y_in04_init() with the URBs still allocated and trips WARN_ON(usx2y->as04.buffer) or WARN_ON(usx2y->in04_urb). The URBs cannot be released at this point: the card may already have registered PCM devices that user space can still open, and the disconnect handler accesses the URBs without holding the DSP lock. Mark the chip as hung instead, so that a retried request is refused with -ENODEV and the assertions keep their meaning. The URBs are released by snd_usx2y_card_private_free() when the card is freed. A failing usx2y_async_seq04_init() still releases its own URBs, so that path remains retryable. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Co-developed-by: Bin Yu Signed-off-by: Bin Yu Co-developed-by: Cheng Wen Signed-off-by: Cheng Wen Signed-off-by: YuanZhe Liu <25031212351@stu.xidian.edu.cn> Link: https://patch.msgid.link/20261031191557.8460-1-25031212351@stu.xidian.edu.cn Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usX2Yhwdep.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c index 7f153a2fcdd813..3800c5b5887c81 100644 --- a/sound/usb/usx2y/usX2Yhwdep.c +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -179,6 +179,9 @@ static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, int lret, err; char *buf; + if (priv->chip_status & USX2Y_STAT_CHIP_HUP) + return -ENODEV; + buf = memdup_user(dsp->image, dsp->length); if (IS_ERR(buf)) return PTR_ERR(buf); @@ -201,11 +204,13 @@ static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, err = usx2y_in04_init(priv); if (err) { dev_err(&dev->dev, "usx2y_in04_init error\n"); + priv->chip_status |= USX2Y_STAT_CHIP_HUP; return err; } err = usx2y_create_alsa_devices(hw->card); if (err) { dev_err(&dev->dev, "usx2y_create_alsa_devices error %i\n", err); + priv->chip_status |= USX2Y_STAT_CHIP_HUP; return err; } priv->chip_status |= USX2Y_STAT_CHIP_INIT; From 5cdee541b37c6d29a6e05cb2a875a365cfbcf520 Mon Sep 17 00:00:00 2001 From: Shivam Madlani Date: Sun, 1 Nov 2026 20:08:18 +0530 Subject: [PATCH 036/186] ALSA: hda/realtek: Fix mute and mic-mute LEDs for HP OmniBook 7 The HP OmniBook 7 Laptop 14-fr0xxx (PCI SSID 103c:8e3b) uses an ALC245 codec with CS35L41 amplifiers. The existing quirk ALC287_FIXUP_CS35L41_I2C_2 does not configure the speaker mute or mic-mute LEDs, leaving them non-functional. Switch to ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED, which controls the speaker mute LED via COEF index 0x0b, the mic-mute LED via GPIO 0x04, and chains to the CS35L41 amplifier setup. Also update the model name from the internal board codename ("HP Agusta") to the retail product name. Fixes: 7150d57c370f ("ALSA: hda/realtek: Add support for HP Agusta using CS35L41 HDA") Signed-off-by: Shivam Madlani Link: https://patch.msgid.link/20261101143818.17714-1-shivammadlani5@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 5bc77f7f8f5375..a75204d3286565 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8078,7 +8078,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8e36, "HP 14 Enstrom OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e37, "HP 16 Piston OmniBook X", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8e3a, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8e3b, "HP Agusta", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8e3b, "HP OmniBook 7", ALC245_FIXUP_CS35L41_I2C_2_MUTE_MICMUTE_LED), SND_PCI_QUIRK(0x103c, 0x8e43, "HP Laptop 15-fd2xxx", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_GPIO), HDA_CODEC_QUIRK(0x103c, 0x84ee, "HP Pavilion AiO 24", ALC225_FIXUP_HP_AIO_SPK), SND_PCI_QUIRK(0x103c, 0x8e60, "HP OmniBook 7 Laptop 16-bh0xxx", ALC245_FIXUP_CS35L41_I2C_2_MUTE_LED), From 64d3a2c8e7bc0d3cf144d71ee2fb357c15b6c71f Mon Sep 17 00:00:00 2001 From: Takashi Sakamoto Date: Mon, 2 Nov 2026 12:36:52 +0900 Subject: [PATCH 037/186] ALSA: firewire: fix inverted streaming speed between tx and rx streams ALSA firewire-tascam driver uses inverted streaming speed when reserving isochronous resources between tx and rx streams. Correct the streaming speeds. This has no impact on existing functionality, as both streams are always reserved together when handling PCM substreams. Fixes: dbd0cf204fe6 ("ALSA: firewire-tascam: reserve resources for transferred isochronous packets at S400") Signed-off-by: Takashi Sakamoto Link: https://patch.msgid.link/20261102033652.1184893-1-o-takashi@sakamocchi.jp Signed-off-by: Takashi Iwai --- sound/firewire/tascam/tascam-stream.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index 4ecd151a46c193..462766946e94ef 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -287,10 +287,10 @@ static int keep_resources(struct snd_tscm *tscm, unsigned int rate, if (stream == &tscm->tx_stream) { resources = &tscm->tx_resources; - speed = fw_parent_device(tscm->unit)->max_speed; + speed = SCODE_400; } else { resources = &tscm->rx_resources; - speed = SCODE_400; + speed = fw_parent_device(tscm->unit)->max_speed; } err = amdtp_tscm_set_parameters(stream, rate); From a3cf5ebab5f1db9e6f751e2a08df05a84f37ea57 Mon Sep 17 00:00:00 2001 From: Yuqiang Sun Date: Mon, 2 Nov 2026 11:52:37 +0800 Subject: [PATCH 038/186] ALSA: usb-audio: qcom: Check the return value of qmi_handle_init() qc_usb_audio_probe() overwrites the return value of qmi_handle_init() with that of qmi_add_server() without checking it. When qmi_handle_init() fails, it has already freed the receive buffer and destroyed the workqueue, and qmi->sock may hold an error pointer. If QRTR is not available, qmi_handle_init() returns -EPROBE_DEFER and qmi->sock is ERR_PTR(-EAFNOSUPPORT), so qmi_add_server() passes it to kernel_sendmsg(): BUG: unable to handle page fault for address: ffffffffffffffb7 RIP: 0010:selinux_socket_sendmsg+0xa0/0x590 Call Trace: security_socket_sendmsg+0x29/0x50 __sock_sendmsg+0x43/0x150 kernel_sendmsg+0xd6/0x180 qmi_send_new_server+0x1b8/0x2b0 qmi_add_server+0x165/0x200 qc_usb_audio_probe+0xf4/0x2e0 really_probe+0x2b4/0x850 Had the probe gone on, the error and remove paths would call qmi_handle_release() on the half-initialized handle, which destroys the workqueue and frees the receive buffer a second time. Return the error from qmi_handle_init(), so that -EPROBE_DEFER defers the probe, and check qmi_add_server() as well. The bug was found with kbound, a static analysis prototype for ownership mismatches at kernel interfaces, on v7.2.12; it is present in mainline. Build-tested with x86_64 and arm64 allmodconfig (W=1, sparse): no new warnings. Run-tested on QEMU (x86_64, KVM) with KASAN and kmemleak, with the driver built in and a test module that registers the q6usb.qc-usb-audio-offload auxiliary device. Without QRTR, the probe crashes as above without this patch; with it the device is deferred and removing it again reports nothing. With QRTR the probe succeeds, and removal reports nothing either. Fixes: 326bbc348298 ("ALSA: usb-audio: qcom: Introduce QC USB SND offloading support") Cc: stable@vger.kernel.org Signed-off-by: Yuqiang Sun Link: https://patch.msgid.link/20261102035237.56253-1-izaiahsun@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/qcom/qc_audio_offload.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c index e4bfd43a2488b9..5ee568e0454cb4 100644 --- a/sound/usb/qcom/qc_audio_offload.c +++ b/sound/usb/qcom/qc_audio_offload.c @@ -1969,8 +1969,13 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, &uaudio_svc_ops_options, &uaudio_stream_req_handlers); + if (ret) + goto free_qmi_hdl; + ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, UAUDIO_STREAM_SERVICE_VERS_V01, 0); + if (ret) + goto release_qmi_hdl; uaudio_svc = svc; @@ -1987,7 +1992,9 @@ static int qc_usb_audio_probe(struct auxiliary_device *auxdev, release_qmi: qc_usb_audio_cleanup_qmi_dev(); +release_qmi_hdl: qmi_handle_release(svc->uaudio_svc_hdl); +free_qmi_hdl: kfree(svc->uaudio_svc_hdl); free_svc: kfree(svc); From ba39a31448857da3b848174c9130b466b3ccddba Mon Sep 17 00:00:00 2001 From: Antheas Kapenekakis Date: Mon, 2 Nov 2026 18:40:52 +0100 Subject: [PATCH 039/186] ALSA: hda/tas2781: Prevent throwing away custom calibration addresses The V2 UEFI spec for per-device calibration data in TAS allows for manufacturers to specify up to five custom calibration register addresses and values to optimize per-device in factory. A basic description of the spec is found in a comment in function tas2781_apply_calib(). However, currently, for the condition of p->dspbin_typ != TASDEV_BASIC, if custom calibration addresses have been provided by the manufacturer, they are ignored and the ones listed in firmware are used. In case the manufacturer chose to change different registers other than the default ones (r0, invr0, r0_low, pow, tlimit), this causes the UEFI calibration data to scratch different registers and cause the firmware to misbehave. This is true in the Xbox ROG Ally X, for one of the firmwares, where it pops and has audio dropouts after the calibration data is applied. Therefore, add a new bool to indicate that UEFI supplied custom calibration addresses, and if so, use them regardless of dspbin_typ. Closes: https://lore.kernel.org/all/179122346529.6.9250312386131579603.1671009695@8alias.com/ Co-developed-by: Matthew Schwartz Signed-off-by: Matthew Schwartz Signed-off-by: Antheas Kapenekakis Link: https://patch.msgid.link/20261102174052.1216670-1-lkml@antheas.dev Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/tas2781_hda.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda.c b/sound/hda/codecs/side-codecs/tas2781_hda.c index e0da299df780c2..70e5118a17a1bb 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda.c @@ -64,6 +64,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) TASDEVICE_REG(0, 0x18, 0x7c), }; unsigned int crc, oft, node_num; + bool custom_addr = false; unsigned char *buf; int i, j, k, l; @@ -104,6 +105,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) for (j = 0, k = 0; j < node_num; j++) { oft = j * 6 + 3; if (tmp_val[oft] == TASDEV_UEFI_CALI_REG_ADDR_FLG) { + custom_addr = true; for (i = 0; i < TASDEV_CALIB_N; i++) { buf = &data[(oft + i + 1) * 4]; cali_reg[i] = TASDEVICE_REG(buf[1], @@ -163,7 +165,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) } } - if (p->dspbin_typ == TASDEV_BASIC) { + if (custom_addr || p->dspbin_typ == TASDEV_BASIC) { r->r0_reg = cali_reg[0]; r->invr0_reg = cali_reg[1]; r->r0_low_reg = cali_reg[2]; From 459e02b74c51cd24ca437da2fa4668806150af1b Mon Sep 17 00:00:00 2001 From: Razvan Dinculescu Date: Mon, 2 Nov 2026 21:04:17 +0200 Subject: [PATCH 040/186] ALSA: hda/hdmi: Don't share an assigned converter on NVIDIA codecs All pins of the NVIDIA HDMI/DP codecs share one connection list, so a converter can be selected by several pins at once. Unlike the Intel codecs (intel_not_share_assigned_cvt()), the NVIDIA ones never move an idle pin off the converter that another pin streams on, and the display that should play stays silent. Seen on an RTX 5090 (codec 0x10de00aa) with a DP monitor on pin 0x04 and an HDMI TV on pin 0x05. PipeWire probes every PCM at start-up: the monitor's PCM got converter 0x08 and kept it selected after closing, then the TV's PCM got 0x08 as well when it was opened. With both pins on 0x08 the TV played nothing, although its PCM was running and its ELD was valid. Opening the monitor's PCM while the TV's was running moved the monitor to 0x09 and the TV played at once. Restarting PipeWire put both pins back on 0x08. Implement pin_cvt_fixup for the generic NVIDIA model as Intel does: restore the pin's own converter selection, which snd_hda_hdmi_generic_pcm_prepare() relies on after the hardware drops it, then move every other connected pin that selects the assigned converter to an unassigned one. Look the converter up in each pin's own connection list instead of assuming the same order on every pin. Tested on that system with the TV's PCM on 0x08 and the monitor's on 0x09, both pins then forced onto 0x08 with hda-verb: a prepare on the TV's PCM moved the monitor off 0x08, and a prepare on the monitor's PCM put it back on 0x09. Signed-off-by: Razvan Dinculescu Link: https://patch.msgid.link/20261102190417.2661176-1-razvandinculescu@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/hdmi/nvhdmi.c | 97 ++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/sound/hda/codecs/hdmi/nvhdmi.c b/sound/hda/codecs/hdmi/nvhdmi.c index 94671ad24b5ebe..e61ad1624dfba3 100644 --- a/sound/hda/codecs/hdmi/nvhdmi.c +++ b/sound/hda/codecs/hdmi/nvhdmi.c @@ -66,6 +66,102 @@ static const struct drm_audio_component_audio_ops nvhdmi_audio_ops = { .master_unbind = snd_hda_hdmi_acomp_master_unbind, }; +/* + * All pins of these codecs share one connection list, as on Intel. An idle + * pin left selecting the converter that another pin streams on takes that + * stream: the display that plays stays silent and the audio goes to the + * other display instead. Seen on GB202 (0x10de00aa) with an HDMI TV and a + * DP monitor: whenever the monitor's PCM was opened first (PipeWire probes + * every PCM at start-up), its pin kept the converter the TV got next. + * + * Move every other connected pin off the converter just assigned, to a + * converter nobody uses. + */ +static void nvhdmi_not_share_assigned_cvt(struct hda_codec *codec, + hda_nid_t pin_nid, int dev_id, + hda_nid_t cvt_nid) +{ + struct hdmi_spec *spec = codec->spec; + struct hdmi_spec_per_pin *per_pin; + struct hdmi_spec_per_cvt *per_cvt; + int pin_idx, cvt_idx, mux_idx, curr, dev_id_saved; + + if (!cvt_nid) + return; + + for (pin_idx = 0; pin_idx < spec->num_pins; pin_idx++) { + per_pin = get_pin(spec, pin_idx); + /* no display on this pin */ + if (!per_pin->pcm) + continue; + if (per_pin->pin_nid == pin_nid && per_pin->dev_id == dev_id) + continue; + if (per_pin->dev_id >= + snd_hda_get_num_devices(codec, per_pin->pin_nid) + 1) + continue; + + dev_id_saved = snd_hda_get_dev_select(codec, per_pin->pin_nid); + snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); + curr = snd_hda_codec_read(codec, per_pin->pin_nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (curr < 0 || curr >= per_pin->num_mux_nids || + per_pin->mux_nids[curr] != cvt_nid) + goto restore; + + for (cvt_idx = 0; cvt_idx < spec->num_cvts; cvt_idx++) { + per_cvt = get_cvt(spec, cvt_idx); + if (per_cvt->assigned || per_cvt->cvt_nid == cvt_nid) + continue; + for (mux_idx = 0; mux_idx < per_pin->num_mux_nids; mux_idx++) + if (per_pin->mux_nids[mux_idx] == per_cvt->cvt_nid) + break; + if (mux_idx == per_pin->num_mux_nids) + continue; + codec_dbg(codec, + "hdmi: move pin 0x%x off cvt 0x%x to cvt 0x%x\n", + per_pin->pin_nid, cvt_nid, per_cvt->cvt_nid); + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + mux_idx); + break; + } +restore: + snd_hda_set_dev_select(codec, per_pin->pin_nid, dev_id_saved); + } +} + +/* + * The hardware can drop a pin's converter selection, e.g. on a display + * reset. snd_hda_hdmi_generic_pcm_prepare() relies on pin_cvt_fixup() to + * restore it, as i915_pin_cvt_fixup() does on Intel. + */ +static void nvhdmi_verify_pin_cvt_connect(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin) +{ + int curr; + + curr = snd_hda_codec_read(codec, per_pin->pin_nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (curr != per_pin->mux_idx) + snd_hda_codec_write_cache(codec, per_pin->pin_nid, 0, + AC_VERB_SET_CONNECT_SEL, + per_pin->mux_idx); +} + +static void nvhdmi_pin_cvt_fixup(struct hda_codec *codec, + struct hdmi_spec_per_pin *per_pin, + hda_nid_t cvt_nid) +{ + if (per_pin) { + snd_hda_set_dev_select(codec, per_pin->pin_nid, per_pin->dev_id); + nvhdmi_verify_pin_cvt_connect(codec, per_pin); + nvhdmi_not_share_assigned_cvt(codec, per_pin->pin_nid, + per_pin->dev_id, per_pin->cvt_nid); + } else { + nvhdmi_not_share_assigned_cvt(codec, 0, 0, cvt_nid); + } +} + static int probe_generic(struct hda_codec *codec) { struct hdmi_spec *spec; @@ -77,6 +173,7 @@ static int probe_generic(struct hda_codec *codec) codec->dp_mst = true; spec = codec->spec; + spec->ops.pin_cvt_fixup = nvhdmi_pin_cvt_fixup; err = snd_hda_hdmi_parse_codec(codec); if (err < 0) { From 1b5e85fd6cd25825c39e4385df7fb55f64b4520f Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Mon, 2 Nov 2026 13:50:34 +0000 Subject: [PATCH 041/186] ALSA: control: Clear user-provided numid before adding a control Clear the user-provided numid before checking for duplicate controls. A non-existing numid can cause the duplicate control ID check to be skipped, allowing a control with the same name and index to be added. Since the kernel assigns the numid when adding a control, ignore the value provided by userspace. Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261102135034.2751-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/core/control.c b/sound/core/control.c index 535ceba294acf8..42f16d2d0e7988 100644 --- a/sound/core/control.c +++ b/sound/core/control.c @@ -1759,9 +1759,10 @@ static int snd_ctl_elem_add(struct snd_ctl_file *file, if (strnlen(info->id.name, sizeof(info->id.name)) >= sizeof(info->id.name)) return -EINVAL; + info->id.numid = 0; + /* Delete a control to replace them if needed. */ if (replace) { - info->id.numid = 0; err = snd_ctl_remove_user_ctl(file, &info->id); if (err) return err; From f65c917397c77daeb7e6d0893ebe89107ddd5692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Kope=C4=87?= Date: Mon, 2 Nov 2026 12:49:39 +0100 Subject: [PATCH 042/186] ALSA: hda/core: Stop capability walk on empty capability header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit snd_hdac_bus_parse_capabilities() walks the controller's linked capability list starting at LLCH. On some platforms one of the entries reads back as all zeros. Such a header does not describe a capability, but the walk still treats it as one: it falls into the default case and reports an error, printing the raw register value instead of the capability ID: snd_hda_intel 0000:00:1f.3: Unknown capability 0 The walk then stops anyway, because the next pointer of a zero header is 0, so the message is only noise. Treat an all-zero header as the end of the list, the same way a failed read (-1) is already handled, and log it at debug level only. Also print the capability ID in hex for a genuinely unknown capability, instead of the full header value. Fixes: 0b00a5615dc4 ("ALSA: hdac_ext: add hdac extended controller") Signed-off-by: MichaÅ‚ Kopeć Link: https://patch.msgid.link/20261102114939.2182131-1-michal.kopec@3mdeb.com Signed-off-by: Takashi Iwai --- sound/hda/core/controller.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sound/hda/core/controller.c b/sound/hda/core/controller.c index 32574dc899fad3..3929dd19501868 100644 --- a/sound/hda/core/controller.c +++ b/sound/hda/core/controller.c @@ -445,6 +445,13 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) break; } + /* An all-zero header means no capability is present here */ + if (!cur_cap) { + dev_dbg(bus->dev, "Empty capability header at offset 0x%x\n", + offset); + break; + } + switch ((cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF) { case AZX_ML_CAP_ID: dev_dbg(bus->dev, "Found ML capability\n"); @@ -475,7 +482,8 @@ int snd_hdac_bus_parse_capabilities(struct hdac_bus *bus) break; default: - dev_err(bus->dev, "Unknown capability %d\n", cur_cap); + dev_err(bus->dev, "Unknown capability ID 0x%x\n", + (cur_cap & AZX_CAP_HDR_ID_MASK) >> AZX_CAP_HDR_ID_OFF); cur_cap = 0; break; } From 9017bc3d13da0ea4e63be618c87143a7b69185e6 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 3 Nov 2026 16:58:40 +0100 Subject: [PATCH 043/186] ALSA: pcm: Prevent O_APPEND open on OSS streams ALSA PCM core accepts to re-use the existing stream when O_APPEND is passed, and it's been used for dmix, etc. However, this mode isn't supposed to be used for OSS: the OSS stream allows re-configuration via its hw_params/hw_refine procedure even while it's being mmapped, so it implies the usage of only a single stream. Because of this assumption, the sanity check is skipped for OSS streams, and if user may attach to an existing OSS stream with O_APPEND, it may result in a UAF of the mapped pages which could be used as a privilege escalation exploit. For addressing it, this patch simply prohibits the O_APPEND behavior on the OSS stream. O_APPEND is never used for OSS layer itself, so this can't break for the right applications. Fixes: 0df63e44c3e3 ("[ALSA] Add O_APPEND flag support to PCM") Cc: stable@vger.kernel.org Reported-by: Bo Cao Link: https://lore.kernel.org/tencent_E90BB4F086165FAA3313723567A5C675EA06@qq.com Link: https://patch.msgid.link/20261103155844.120838-1-tiwai@suse.de Signed-off-by: Takashi Iwai --- sound/core/pcm.c | 2 ++ sound/core/pcm_local.h | 6 ++++++ sound/core/pcm_native.c | 6 ------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 064df4cd3b50e6..6c8274414c2265 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -929,6 +929,8 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, return -ENODEV; if (! SUBSTREAM_BUSY(substream)) return -EBADFD; + if (is_oss_stream(substream)) + return -EINVAL; substream->ref_count++; *rsubstream = substream; return 0; diff --git a/sound/core/pcm_local.h b/sound/core/pcm_local.h index ecb21697ae3a4d..4b134d435c24f3 100644 --- a/sound/core/pcm_local.h +++ b/sound/core/pcm_local.h @@ -80,4 +80,10 @@ static inline void snd_pcm_dma_buffer_sync(struct snd_pcm_substream *substream, snd_dma_buffer_sync(snd_pcm_get_dma_buf(substream), mode); } +#if IS_ENABLED(CONFIG_SND_PCM_OSS) +#define is_oss_stream(substream) ((substream)->oss.oss) +#else +#define is_oss_stream(substream) false +#endif + #endif /* __SOUND_CORE_PCM_LOCAL_H */ diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 35152fae74ca2f..bec797398acf71 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -749,12 +749,6 @@ int snd_pcm_runtime_buffer_set_silence(struct snd_pcm_runtime *runtime) } EXPORT_SYMBOL_GPL(snd_pcm_runtime_buffer_set_silence); -#if IS_ENABLED(CONFIG_SND_PCM_OSS) -#define is_oss_stream(substream) ((substream)->oss.oss) -#else -#define is_oss_stream(substream) false -#endif - static void pcm_qos_req_clear(struct snd_pcm_substream *substream) { if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req)) From e7c66b3e249e2a1d2462703a6d3820a9af3eefc0 Mon Sep 17 00:00:00 2001 From: Riley Date: Wed, 4 Nov 2026 11:39:38 +1100 Subject: [PATCH 044/186] ALSA: usb-audio: Add quirk for Yamaha ZG02 The Yamaha ZG02 game streaming mixer (0499:1516) exposes UAC2 audio streaming interfaces with a vendor-specific interface class. Only its MIDI interfaces get bound today: the Yamaha catch-all QUIRK_AUTODETECT entry rejects the PCM interfaces because create_auto_pcm_quirk() expects a UAC1-sized FORMAT_TYPE descriptor (bLength >= 8), while the ZG02 has the 6-byte UAC2 one. As a result no PCM device is created. The interface layout matches the Steinberg UR22 (0499:1509): 0: UAC2 AudioControl (vendor class), clock source ID 1 1: playback, 10ch S32_LE 48kHz, async isochronous OUT 2: capture, 4ch, isochronous IN used as implicit feedback 3: Yamaha MIDI 4: vendor bulk interface Add the same composite quirk and QUIRK_FLAG_GENERIC_IMPLICIT_FB for it. The generic UAC2 implicit feedback detection does not apply here because it requires USB_CLASS_AUDIO. Tested on a 7.3 kernel with this patch applied (no quirk_alias): the card registers as USB0499:1516, implicit feedback is set up from EP 0x81, and playback (voice chat and game buses) and capture (microphone) work. The full channel mapping (playback 1-2 voice chat bus, 3-10 game bus; capture 1-2 streaming mix, 3-4 microphone) was verified with quirk_alias=04991516:04991509, which applies the same quirk and flag entries. Assisted-by: Claude Opus 5.5 Signed-off-by: Riley Link: https://patch.msgid.link/20261104-yamaha-zg02-v1-1-3fd61b8c564f@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 14 ++++++++++++++ sound/usb/quirks.c | 2 ++ 2 files changed, 16 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index 1544ad7365f56d..f912e1033cfada 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -404,6 +404,20 @@ YAMAHA_DEVICE(0x105d, NULL), } } }, +{ + USB_DEVICE(0x0499, 0x1516), + QUIRK_DRIVER_INFO { + /* .vendor_name = "Yamaha", */ + /* .product_name = "ZG02", */ + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + { QUIRK_DATA_IGNORE(4) }, + QUIRK_COMPOSITE_END + } + } +}, { USB_DEVICE(0x0499, 0x1718), QUIRK_DRIVER_INFO { diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c index 05a9f6727a3a59..7c581e283903ff 100644 --- a/sound/usb/quirks.c +++ b/sound/usb/quirks.c @@ -2556,6 +2556,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = { QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x1509, /* Steinberg UR22 */ QUIRK_FLAG_GENERIC_IMPLICIT_FB), + DEVICE_FLG(0x0499, 0x1516, /* Yamaha ZG02 */ + QUIRK_FLAG_GENERIC_IMPLICIT_FB), DEVICE_FLG(0x0499, 0x3108, /* Yamaha YIT-W12TX */ QUIRK_FLAG_GET_SAMPLE_RATE), DEVICE_FLG(0x04d8, 0xfeea, /* Benchmark DAC1 Pre */ From cf79cc4996bf936fe159af2a600519b3ebd4ccb7 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Tue, 3 Nov 2026 21:21:21 +0700 Subject: [PATCH 045/186] ALSA: hda/realtek: Enable mute and mic-mute LEDs on HP ENVY 17-cr0xxx The HP ENVY Laptop 17-cr0xxx (subsystem ID 103c:8a2e) has mute and mic-mute LEDs on the F8 and F5 keys that never light up on Linux: the existing quirk entry only sets up the CS35L41 amplifiers. The board uses the same LED wiring as the HP ENVY x360 15-ey0xxx (103c:8a31): the mute LED is driven by COEF 0x0b bits 3:2 and the mic-mute LED by GPIO2 with inverted polarity. Both were verified with hda-verb on the actual hardware. Switch the entry to ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS, which chains the amplifier setup with ALC245_FIXUP_HP_X360_MUTE_LEDS, and use the DMI product name of the board. Fixes: aa8e3ef4fe53 ("ALSA: hda/realtek: Add quirks for various HP ENVY models") Cc: Signed-off-by: Alexandr Link: https://patch.msgid.link/20261103142121.74032-1-youdoit@mail.ru Signed-off-by: Takashi Iwai --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index a75204d3286565..cac3018069948f 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -7878,7 +7878,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8a2b, "HP Envy 15", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a2c, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a2d, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8a2e, "HP Envy 16", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x103c, 0x8a2e, "HP ENVY Laptop 17-cr0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8a30, "HP Envy 17", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x103c, 0x8a31, "HP ENVY x360 2-in-1 Laptop 15-ey0xxx", ALC287_FIXUP_CS35L41_I2C_2_HP_MUTE_LEDS), SND_PCI_QUIRK(0x103c, 0x8a34, "HP Pavilion x360 2-in-1 Laptop 14-ek0xxx", ALC245_FIXUP_HP_MUTE_LED_COEFBIT), From 8dbab764f1e0a92c3fac0ce070ce30c0c33c2692 Mon Sep 17 00:00:00 2001 From: Mohamed Yassine Jebabli Date: Tue, 3 Nov 2026 10:27:12 +0100 Subject: [PATCH 046/186] ALSA: hda/tas2781: Only accept CRC-less V1 data if all entries are valid Commit 408249089c48 ("ALSA: hda/tas2781: Accept V1 calibration data without CRC") only checked the first device entry. On machines where the BIOS populates fewer entries than p->ndev, the remaining all-zero entries are applied and tas2781_cali_preproc() divides by zero on playback open. Accept CRC-less V1 data only if every expected entry is non-zero and self-consistent (R0 * 1/R0 close to 1.0), otherwise reject it as before. Fixes: 408249089c48 ("ALSA: hda/tas2781: Accept V1 calibration data without CRC") Reported-by: Cristian Castellano Closes: https://lore.kernel.org/all/4e500063-d985-46b6-a0a1-3feaa77c4b2f@leemhuis.info/ Cc: stable@vger.kernel.org Signed-off-by: Mohamed Yassine Jebabli Link: https://patch.msgid.link/20261103-tas2781-v1-nocrc-fix-v1-1-a769c27f21ce@gmail.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/tas2781_hda.c | 27 +++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda.c b/sound/hda/codecs/side-codecs/tas2781_hda.c index 70e5118a17a1bb..b9b6e44f8791af 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda.c @@ -50,6 +50,31 @@ static void cali_cnv(unsigned char *data, unsigned int base, int offset) (u32 *)®_data, TASDEV_CALIB_N); } +/* + * V1 data without CRC is only trusted if every expected device has a + * populated, self-consistent entry: R0 (Q27) * 1/R0 (Q31) must be + * close to 1.0 in Q58. + */ +static bool tas2781_v1_nocrc_valid(const unsigned int *v, int ndev) +{ + const u64 one = 1ULL << 58; + int i; + + if (ndev <= 0 || ndev > 4) + return false; + + for (i = 0; i < ndev; i++) { + const unsigned int *e = &v[i * 5]; + u64 prod = (u64)e[0] * e[1]; + + if (!e[0] || !e[1] || + prod < one - one / 10 || prod > one + one / 10) + return false; + } + + return true; +} + static void tas2781_apply_calib(struct tasdevice_priv *p) { struct calidata *cali_data = &p->cali_data; @@ -147,7 +172,7 @@ static void tas2781_apply_calib(struct tasdevice_priv *p) * but leave TimeStamp and CRC zeroed. */ if (!tmp_val[20] && !tmp_val[21] && - tmp_val[0] && tmp_val[1]) { + tas2781_v1_nocrc_valid(tmp_val, p->ndev)) { dev_warn(p->dev, "%s: V1 data without CRC, accepting\n", __func__); From f13964de5ee38be8af3cdcbe143cb17431ba3c0d Mon Sep 17 00:00:00 2001 From: Rafael Medina Date: Mon, 2 Nov 2026 16:57:23 -0600 Subject: [PATCH 047/186] ALSA: hda/ca0132: Restore AE-5 audio registers after LED strip send The AE-5 LED strip transport added by commit c3e35f355fad ("ALSA: hda/ca0132: Add Sound BlasterX AE-5 external RGB LED strip support") reprograms the CA0113 audio serializer block in BAR2 to clock out the WS2812 data, and at the end clears only the stream routing register. The format and channel enable register at 0x100 is left at 0x3000ff8f instead of the 0x00000700 it reads after a normal codec init, and the analog outputs stay silent until the codec is initialised again. The strip control is marked volatile, but alsactl still stores it, and the udev rule runs "alsactl restore" for every card probe. The saved state of a card that never had a strip attached contains "AE-5 LED Strip" and "AE-5 LED Strip Count" with access "read write volatile". ae5_strip_ctl_put() sent a frame even when the colors were unchanged, so every boot of an AE-5 or AE-5 Plus ended with a silent card. Save the registers that the transmit touches before programming them and write them back at the end of ae5_strip_send_frame(), and do not transmit from ae5_strip_ctl_put() when the colors did not change. Fixes: c3e35f355fad ("ALSA: hda/ca0132: Add Sound BlasterX AE-5 external RGB LED strip support") Cc: Devin Christensen Signed-off-by: Rafael Medina Link: https://patch.msgid.link/20261102225723.123266-1-rafael@gexdigital.net Signed-off-by: Takashi Iwai --- sound/hda/codecs/ca0132.c | 43 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c index 896091c1144ffb..b3e768be8a6956 100644 --- a/sound/hda/codecs/ca0132.c +++ b/sound/hda/codecs/ca0132.c @@ -8416,6 +8416,10 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, struct hdac_stream *hstr = NULL; unsigned int format, stream_tag; u32 enc_words[4]; + struct { + u32 r100, r104, r400, r408, r40c, r410, rc04; + u32 ser_base[4], ser_clk[4], ser_out[4]; + } saved; __le32 *dst; int word_idx = 0; int frame_words; @@ -8501,6 +8505,24 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, /* Ensure DMA buffer writes are committed before triggering the HDA stream */ wmb(); + /* + * Save every BAR2 register the strip transport reprograms, so that the + * audio serializer/clock/routing setup done at codec init can be put + * back afterwards. Leaving them modified silences the analog outputs. + */ + saved.r100 = readl(spec->mem_base + 0x100); + saved.r104 = readl(spec->mem_base + 0x104); + saved.r400 = readl(spec->mem_base + 0x400); + saved.r408 = readl(spec->mem_base + 0x408); + saved.r40c = readl(spec->mem_base + 0x40c); + saved.r410 = readl(spec->mem_base + 0x410); + saved.rc04 = readl(spec->mem_base + 0xc04); + for (i = 0; i < 4; i++) { + saved.ser_base[i] = readl(CA0113_SERIALIZER_BASE(spec, i)); + saved.ser_clk[i] = readl(CA0113_SERIALIZER_CLK(spec, i)); + saved.ser_out[i] = readl(CA0113_SERIALIZER_OUT(spec, i)); + } + /* 2. Configure CA0113 BAR2 hardware registers */ /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */ writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04); @@ -8559,8 +8581,19 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, snd_hdac_dsp_trigger(hstr, false); codec_dbg(codec, "AE5 strip: trigger complete\n"); - /* Teardown: clear 0x104 pin mux back to 0 */ - writel(0x00000000, spec->mem_base + 0x104); + /* Teardown: put back the registers exactly as the audio path left them */ + writel(saved.r104, spec->mem_base + 0x104); + for (i = 0; i < 4; i++) { + writel(saved.ser_out[i], CA0113_SERIALIZER_OUT(spec, i)); + writel(saved.ser_clk[i], CA0113_SERIALIZER_CLK(spec, i)); + writel(saved.ser_base[i], CA0113_SERIALIZER_BASE(spec, i)); + } + writel(saved.r100, spec->mem_base + 0x100); + writel(saved.r410, spec->mem_base + 0x410); + writel(saved.r40c, spec->mem_base + 0x40c); + writel(saved.r408, spec->mem_base + 0x408); + writel(saved.r400, spec->mem_base + 0x400); + writel(saved.rc04, spec->mem_base + 0xc04); snd_hdac_dsp_cleanup(hstr, &dmab); scoped_guard(spinlock_irq, &bus->reg_lock) @@ -8613,6 +8646,12 @@ static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol, } change = memcmp(spec->ae5_strip_cur_colors, colors, sizeof(spec->ae5_strip_cur_colors)) != 0; + /* + * alsactl restores this control at every card probe with the saved + * values; do not run the whole transmit routine when nothing changed. + */ + if (!change) + return 0; num_leds = spec->ae5_strip_cur_num_leds; ret = ae5_strip_send_frame(codec, colors, num_leds); if (ret < 0) From f14f5be3c9e1ca01dce882f43a6fd07afb39542c Mon Sep 17 00:00:00 2001 From: Vincent Cloutier Date: Tue, 3 Nov 2026 19:40:25 -0500 Subject: [PATCH 048/186] ALSA: pcm: Allow HW_REFINE queries with mapped buffers PulseAudio 16.1 queries supported formats after configuring and mapping a PCM buffer. Sharing HW_PARAMS' state checker with HW_REFINE causes these queries to fail with -EBADFD because a non-OSS stream has active buffer mappings. PulseAudio then fails to create its sinks and sources. Make the mmap restriction optional in the shared state checker. Keep it for HW_PARAMS, which can reconfigure the mapped buffer, but skip it for HW_REFINE, which only refines the caller's constraint structure. Keep the stream-state checks and buffer access locking for native, compat and legacy refinement ioctls rather than bypassing their wrapper. Leave the accepted stream states unchanged. On Librem 5, both L5 and Modem cards reproduce the failure for playback and capture: refinement succeeds before setup and after RW setup, but fails after MMAP setup in PREPARED state. With this change, all eight cases pass, PulseAudio starts normally and audible playback works. The reproducer does not start streams or transfer audio data. Fixes: ad28d2f607d1 ("ALSA: pcm: Serialize and check HW_REFINE ioctls") Link: https://lore.kernel.org/linux-sound/41a2e39a-5637-8afa-f84f-f79e63b3b56e@xenosoft.de/ Link: https://lore.kernel.org/linux-sound/874idyx9n6.wl-tiwai@suse.de/ Assisted-by: OpenCode:GPT-6.1 Sol Signed-off-by: Vincent Cloutier Link: https://patch.msgid.link/20261104004113.546712-1-vincent.cloutier@icloud.com Signed-off-by: Takashi Iwai --- sound/core/pcm_native.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index bec797398acf71..29c428cb6debd8 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -770,14 +770,15 @@ static void pcm_qos_req_update(struct snd_pcm_substream *substream, int usecs) usecs); } -static int hw_params_state_check(struct snd_pcm_substream *substream) +static int hw_params_state_check(struct snd_pcm_substream *substream, + bool check_mmap) { guard(pcm_stream_lock_irq)(substream); switch (substream->runtime->state) { case SNDRV_PCM_STATE_OPEN: case SNDRV_PCM_STATE_SETUP: case SNDRV_PCM_STATE_PREPARED: - if (!is_oss_stream(substream) && + if (check_mmap && !is_oss_stream(substream) && atomic_read(&substream->mmap_count)) return -EBADFD; return 0; @@ -800,7 +801,7 @@ static int snd_pcm_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - err = hw_params_state_check(substream); + err = hw_params_state_check(substream, true); if (err) goto unlock; @@ -938,7 +939,8 @@ static int snd_pcm_hw_refine_with_lock(struct snd_pcm_substream *substream, err = snd_pcm_buffer_access_lock(runtime); if (err < 0) return err; - err = hw_params_state_check(substream); + /* Refinement queries constraints without changing the mapped buffer. */ + err = hw_params_state_check(substream, false); if (err) goto unlock; From db51425c9eb038a8e40e09b837afde015ae9e3ec Mon Sep 17 00:00:00 2001 From: Sinan Turunc Date: Tue, 3 Nov 2026 13:22:44 +0000 Subject: [PATCH 049/186] ALSA: line6: Reject capture buffers smaller than two packets line6_capture_copy() splits the copy into the ALSA ring buffer once at the buffer end and wraps pos_done once, which is only correct when one packet holds at most buffer_size frames. The packet size comes from the device's endpoint, and the per-device hw structs allow 64-byte periods with periods_min 1, so a packet can be larger than the whole buffer. audio_in_callback() logs an oversized packet and then copies it anyway. The packet size is known at probe time, so constrain SNDRV_PCM_HW_PARAM_BUFFER_BYTES in snd_line6_capture_open() to at least twice the larger of the two endpoints' packet sizes. Two rather than one, because pos_done advances by the packet and wraps once, so a buffer of exactly one packet leaves the position unchanged after every packet. hw_params then rejects a buffer that cannot hold two packets, and the single wrap is valid for every configuration that gets through. Keep a guard in line6_capture_copy() that drops an oversized packet with a ratelimited error, for the case that should no longer be reachable. Reproduced on 7.3-rc7 with a USB/IP device presenting the GuitarPort interface layout and 1024-byte packets into a 128-byte buffer: ALSA logged "invalid position: pos = 224, buffer size = 32" and the driver logged "driver bug: len" on every following packet. With this patch a 1024-byte buffer is refused at hw_params, and a 2048-byte buffer streams for 30 s with no report and with the position moving inside the buffer. Fixes: 705ececd1c60 ("Staging: add line6 usb driver") Cc: stable@vger.kernel.org Signed-off-by: Sinan Turunc Link: https://patch.msgid.link/20261103132227.696-1-enscrpt@pm.me Signed-off-by: Takashi Iwai --- sound/usb/line6/capture.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/sound/usb/line6/capture.c b/sound/usb/line6/capture.c index 17f49f567efbf2..626274e503c7aa 100644 --- a/sound/usb/line6/capture.c +++ b/sound/usb/line6/capture.c @@ -94,6 +94,18 @@ void line6_capture_copy(struct snd_line6_pcm *line6pcm, char *fbuf, int fsize) if (runtime == NULL) return; + /* + * snd_line6_capture_open() constrains the buffer to hold at least + * two packets, so this cannot happen for a conforming device. Drop + * the packet rather than overrun the single wrap below. + */ + if (unlikely(frames > runtime->buffer_size)) { + dev_err_ratelimited(line6pcm->line6->ifcdev, + "packet of %d frames exceeds %lu-frame buffer, dropping\n", + frames, runtime->buffer_size); + return; + } + if (line6pcm->in.pos_done + frames > runtime->buffer_size) { /* The transferred area goes over buffer boundary, @@ -232,6 +244,23 @@ static int snd_line6_capture_open(struct snd_pcm_substream *substream) if (err < 0) return err; + /* + * line6_capture_copy() wraps the ring buffer once per packet and + * advances pos_done by the packet, which only works when the buffer + * holds more than one packet: with exactly one, pos_done gains a + * buffer and loses it again on every packet. The packet size comes + * from the device, so bound the buffer by two packets here. The + * impulse-response path copies a playback URB into this ring, so + * take the larger of the two endpoints. + */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 2 * max(line6pcm->max_packet_size_in, + line6pcm->max_packet_size_out), + UINT_MAX); + if (err < 0) + return err; + line6_pcm_acquire(line6pcm, LINE6_STREAM_CAPTURE_HELPER, false); runtime->hw = line6pcm->properties->capture_hw; From d21746bafdb8e1a5fc9bdcb86f603d827a500a62 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:53:41 +0000 Subject: [PATCH 050/186] ALSA: caiaq: free the EP4 URB when input setup fails The three controllers that use the EP4 input URB allocate it before checking the endpoint type and before registering the input device. On failure they jump to exit_free_idev, which clears cdev->input_dev but leaves cdev->ep4_in_urb set. The card teardown then calls snd_usb_caiaq_input_free(), which returns early because input_dev is NULL, so the URB is never freed. Release the URB before dropping the input device on the error path. Fixes: 6da7a2aa899f ("ALSA: usb/caiaq: Add support for Traktor Kontrol X1") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104025341.1527407-1-vulab@iscas.ac.cn Signed-off-by: Takashi Iwai --- sound/usb/caiaq/input.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/usb/caiaq/input.c b/sound/usb/caiaq/input.c index 2830a1da067da5..5a415d1acca4bf 100644 --- a/sound/usb/caiaq/input.c +++ b/sound/usb/caiaq/input.c @@ -863,6 +863,8 @@ int snd_usb_caiaq_input_init(struct snd_usb_caiaqdev *cdev) return 0; exit_free_idev: + usb_free_urb(cdev->ep4_in_urb); + cdev->ep4_in_urb = NULL; input_free_device(input); cdev->input_dev = NULL; return ret; From df6cc85923d96381a24f2a24ac2cb6ac92220a8e Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Wed, 4 Nov 2026 18:04:25 +0800 Subject: [PATCH 051/186] ALSA: hda/realtek: Fix DAC binding for Lenovo Legion Pro 7 16ARX8H The Lenovo Legion Pro 7 16ARX8H (SSID 17aa:38a7) uses the TAS2781 I2C DAC. The generic ALC287_FIXUP_TAS2781_I2C quirk assumes two speakers (0x14 and 0x17). It forcibly binds 0x14 to DAC 0x02, and binds 0x17 and 0x21 to DAC 0x03. However, this machine only has 0x17 as the speaker and lacks 0x14, which leads to incorrect DAC binding. To fix this, chain the alc295_fixup_asus_dacs() fixup before the generic quirk to ensure the correct DAC routing for this hardware layout. Tested-by: Roman Maidanovych via B4 Relay Link: https://bugzilla.kernel.org/show_bug.cgi?id=221735 Signed-off-by: Zhang Heng Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261104100427.474270-1-zhangheng@kylinos.cn --- sound/hda/codecs/realtek/alc269.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index cac3018069948f..a225c75230ae17 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -4553,6 +4553,7 @@ enum { ALC256_FIXUP_IPASON_SMARTBOOK_S1, ALC256_FIXUP_ASUS_SPEAKER_COEFS, ALC245_FIXUP_MINISFORUM_V3_MIC_NO_PRESENCE, + ALC287_FIXUP_TAS2781_I2C_DAC, }; /* A special fixup for Lenovo C940 and Yoga Duet 7; @@ -7061,6 +7062,12 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC285_FIXUP_THINKPAD_HEADSET_JACK, }, + [ALC287_FIXUP_TAS2781_I2C_DAC] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc295_fixup_asus_dacs, + .chained_before = true, + .chain_id = ALC287_FIXUP_TAS2781_I2C, + }, [ALC287_FIXUP_ASUS_ALLY_X] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -8568,7 +8575,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x386a, "Lenovo Yoga 7 16IAP7", ALC287_FIXUP_YOGA9_14IAP7_BASS_SPK_PIN), HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), - HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C_DAC), /* this must match before PCI SSID 17aa:386f below */ HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), From 994cd0af4dcd1ae34c990deb06079d7b1e30a1da Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Wed, 4 Nov 2026 18:04:26 +0800 Subject: [PATCH 052/186] ALSA: hda/realtek: Fix DAC binding for Legion Pro 7 16ARX8H variant Similar to SSID 17aa:38a7, the Lenovo Legion Pro 7 16ARX8H variant (SSID 17aa:38a8) also lacks the 0x14 speaker node. The generic ALC287_FIXUP_TAS2781_I2C quirk assumes the presence of 0x14 and will result in incorrect DAC bindings on this machine. Link: https://lore.kernel.org/all/CAGPQRHYXcbWTiKpCANEQ1mq9aUHM=v82T231fw7Ld=C-NM4+Bw@mail.gmail.com/ Signed-off-by: Zhang Heng Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261104100427.474270-2-zhangheng@kylinos.cn --- sound/hda/codecs/realtek/alc269.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index a225c75230ae17..0c678fb8144ab7 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8576,7 +8576,7 @@ static const struct hda_quirk alc269_fixup_tbl[] = { HDA_CODEC_QUIRK(0x17aa, 0x386e, "Legion Y9000X 2022 IAH7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x386e, "Yoga Pro 7 14ARP8", ALC285_FIXUP_YOGA_SPEAKER2_TO_DAC1), HDA_CODEC_QUIRK(0x17aa, 0x38a7, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C_DAC), /* this must match before PCI SSID 17aa:386f below */ - HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C), /* this must match before PCI SSID 17aa:386f below */ + HDA_CODEC_QUIRK(0x17aa, 0x38a8, "Legion Pro 7 16ARX8H", ALC287_FIXUP_TAS2781_I2C_DAC), /* this must match before PCI SSID 17aa:386f below */ SND_PCI_QUIRK(0x17aa, 0x386f, "Legion Pro 7i 16IAX7", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x17aa, 0x3870, "Lenovo Yoga 7 14ARB7", ALC287_FIXUP_YOGA7_14ARB7_I2C), SND_PCI_QUIRK(0x17aa, 0x3871, "Lenovo ThinkBook 16p Gen 3 ARH", ALC287_FIXUP_YOGA7_14ARB7_I2C), From 81c4b0e3545805f18334273d1d0f72ac50f633ef Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Wed, 4 Nov 2026 18:04:27 +0800 Subject: [PATCH 053/186] ALSA: hda/realtek: Fix pop noise on ASUS PRIME B650M-K The ASUS PRIME B650M-K motherboard with the ALC897 codec produces a loud "pop" noise from the audio outputs when the driver initializes the codec during boot. Add a specific fixup to toggle the EAPD pin (disable, wait 200ms, then re-enable) during the initialization phase to eliminate the pop. Tested-by: Catalonia_State Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222096 Signed-off-by: Zhang Heng Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261104100427.474270-3-zhangheng@kylinos.cn --- sound/hda/codecs/realtek/alc662.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sound/hda/codecs/realtek/alc662.c b/sound/hda/codecs/realtek/alc662.c index c1c7277b6754a8..a0efd4596c8807 100644 --- a/sound/hda/codecs/realtek/alc662.c +++ b/sound/hda/codecs/realtek/alc662.c @@ -284,6 +284,21 @@ static void alc662_fixup_csl_amp(struct hda_codec *codec, } } +static void alc897_fixup_asus_b650m_depop(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + if (action != HDA_FIXUP_ACT_INIT) + return; + + alc_auto_setup_eapd(codec, false); + snd_hda_codec_write(codec, 0x1b, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0); + msleep(200); + alc_auto_setup_eapd(codec, true); + snd_hda_codec_write(codec, 0x1b, 0, + AC_VERB_SET_EAPD_BTLENABLE, 2); +} + enum { ALC662_FIXUP_ASPIRE, ALC662_FIXUP_LED_GPIO1, @@ -345,6 +360,7 @@ enum { ALC662_FIXUP_CSL_GPIO, ALC897_FIXUP_BEELINK_SER6_AMP, ALC897_FIXUP_LIMIT_LINEIN_BOOST, + ALC897_FIXUP_ASUS_B650M_DEPOP, }; static const struct hda_fixup alc662_fixups[] = { @@ -810,6 +826,10 @@ static const struct hda_fixup alc662_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc897_fixup_limit_linein_boost, }, + [ALC897_FIXUP_ASUS_B650M_DEPOP] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc897_fixup_asus_b650m_depop, + }, }; static const struct hda_quirk alc662_fixup_tbl[] = { @@ -858,6 +878,7 @@ static const struct hda_quirk alc662_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x1b73, "ASUS N55SF", ALC662_FIXUP_BASS_16), SND_PCI_QUIRK(0x1043, 0x1bf3, "ASUS N76VZ", ALC662_FIXUP_BASS_MODE4_CHMAP), SND_PCI_QUIRK(0x1043, 0x8469, "ASUS mobo", ALC662_FIXUP_NO_JACK_DETECT), + SND_PCI_QUIRK(0x1043, 0x87fb, "ASUS PRIME B650M-K", ALC897_FIXUP_ASUS_B650M_DEPOP), SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_FIXUP_ASUS_MODE2), SND_PCI_QUIRK(0x144d, 0xc051, "Samsung R720", ALC662_FIXUP_IDEAPAD), SND_PCI_QUIRK(0x14cd, 0x5003, "USI", ALC662_FIXUP_USI_HEADSET_MODE), From 42bbdb212cb37d3653ef7542114b8cd3e6263657 Mon Sep 17 00:00:00 2001 From: Anupama Kunkulagunta Date: Wed, 4 Nov 2026 10:29:55 +0000 Subject: [PATCH 054/186] ALSA: hda/tegra: Add Tegra268 HDMI/DP codec ID The Tegra268 HDMI/DP HDA codec reports codec ID 0x10de0037, which is not present in the Tegra HDMI codec driver's device table. Add this ID so the driver can bind to the Tegra268 codec. Tegra268 uses the same HDMI/DP codec programming model as Tegra234. Reuse MODEL_TEGRA234 to enable DP MST, dynamic pin output assignment and HDMI interrupt trigger control for Tegra268. Signed-off-by: Anupama Kunkulagunta Signed-off-by: Sheetal Link: https://patch.msgid.link/20261104102955.1089991-1-sheetal@nvidia.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/hdmi/tegrahdmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/hda/codecs/hdmi/tegrahdmi.c b/sound/hda/codecs/hdmi/tegrahdmi.c index 171d1ee154d9cd..6baa98a3c627e6 100644 --- a/sound/hda/codecs/hdmi/tegrahdmi.c +++ b/sound/hda/codecs/hdmi/tegrahdmi.c @@ -338,6 +338,7 @@ static const struct hda_device_id snd_hda_id_tegrahdmi[] = { HDA_CODEC_ID_MODEL(0x10de0033, "SoC 33 HDMI/DP", MODEL_TEGRA234), HDA_CODEC_ID_MODEL(0x10de0034, "Tegra264 HDMI/DP", MODEL_TEGRA234), HDA_CODEC_ID_MODEL(0x10de0035, "SoC 35 HDMI/DP", MODEL_TEGRA234), + HDA_CODEC_ID_MODEL(0x10de0037, "Tegra268 HDMI/DP", MODEL_TEGRA234), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_tegrahdmi); From d2895945d37e3426191894a010f94db31c206e10 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 4 Nov 2026 11:11:21 +0000 Subject: [PATCH 055/186] ALSA: hda: cs35l56: Fix mixed cleanup and goto in cs35l56_hda_fw_load() Remove the gotos from cs35l56_hda_fw_load() to prevent mixing auto-cleanup and goto-cleanup in the same function. Replace them with returns and remove the goto cleanup code, as there's no need to "undo" a cs_dsp_power_up() with a cs_dsp_power_down() here. Two previous commits added auto-cleanup to cs35l56_hda_fw_load() but didn't remove the goto, which leaves mixed cleanup and goto. commit 0ff22680e51f4 ("ALSA: hda/cs35l56: Clean up with PM_RUNTIME_ACQUIRE*() macros") commit 185841c94accc ("ALSA: hda: cs35l56: Use auto-cleanup for firmware loading") There's actually no need to call cs_dsp_power_down() here. On the CS35L54/56/57 amps it doesn't control the DSP subsystem power (that is all managed by the firmware), so it's perfectly safe to just return if there are any errors after a successful cs_dsp_power_up(). As cs_dsp_power_up() was successful it is valid to leave cs_dsp in this state, which matches the state of the hardware. The two places that cs_dsp_power_down() is called conditionally have been changed to check the cs_dsp.booted flag, now the fw_patched flag is not a guaranteed proxy. The booted flag is directly indicating that there has been a successful cs_dsp_power_up() so this is more correct anyway. Fixes: 0ff22680e51f4 ("ALSA: hda/cs35l56: Clean up with PM_RUNTIME_ACQUIRE*() macros") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20261104111121.1054077-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l56_hda.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c index bc207ab5b020a5..148d760987fdd4 100644 --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c @@ -562,7 +562,7 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) * downloaded previously then it needs to be powered down so that it * can be updated. */ - if (cs35l56->base.fw_patched) + if (cs35l56->cs_dsp.booted) cs_dsp_power_down(&cs35l56->cs_dsp); cs35l56->base.fw_patched = false; @@ -634,7 +634,7 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) regcache_mark_dirty(cs35l56->base.regmap); ret = cs35l56_wait_for_firmware_boot(&cs35l56->base); if (ret) - goto err_powered_up; + return; regcache_cache_only(cs35l56->base.regmap, false); } @@ -642,7 +642,7 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) /* Disable auto-hibernate so that runtime_pm has control */ ret = cs35l56_mbox_send(&cs35l56->base, CS35L56_MBOX_CMD_PREVENT_AUTO_HIBERNATE); if (ret) - goto err_powered_up; + return; regcache_sync(cs35l56->base.regmap); @@ -663,10 +663,6 @@ static void cs35l56_hda_fw_load(struct cs35l56_hda *cs35l56) cs_dsp_stop(&cs35l56->cs_dsp); cs35l56_log_tuning(&cs35l56->base, &cs35l56->cs_dsp); - -err_powered_up: - if (!cs35l56->base.fw_patched) - cs_dsp_power_down(&cs35l56->cs_dsp); } static void cs35l56_hda_dsp_work(struct work_struct *work) @@ -814,7 +810,7 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * debugfs_remove_recursive(cs35l56->debugfs_root); #endif - if (cs35l56->base.fw_patched) + if (cs35l56->cs_dsp.booted) cs_dsp_power_down(&cs35l56->cs_dsp); comp = hda_component_from_index(parent, cs35l56->index); From aebc5461e2d0ef8f09920dfbc90e05b427aa5d35 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 4 Nov 2026 11:36:10 +0000 Subject: [PATCH 056/186] ALSA: doc: cs35l56: Update calibration write failure message info Update the descriptions of the "Failed to write calibration" and "Calibration disabled due to missing firmware controls" error messages. Originally "Failed to write calibration" was described as having the same cause as "Calibration disabled due to missing firmware controls". This was _typically_ true because the most likely cause of the write failure was missing controls (because both messages would be logged in that case). But it wasn't clear from the description that this was referring to the _typical_ root cause being the same. Reword the "Failed to write calibration" info to describe the possible other reasons for this error. Also slightly reword the description of "Calibration disabled due to missing firmware controls" to say "firmware wmfw file" instead of only "wmfw" and fix a missing "the" in the second bullet point. Also note that this is not necessarily an error, only information. Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20261104113610.1057194-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai --- Documentation/sound/codecs/cs35l56.rst | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Documentation/sound/codecs/cs35l56.rst b/Documentation/sound/codecs/cs35l56.rst index b3f8c1c2385185..ffbfbaed127144 100644 --- a/Documentation/sound/codecs/cs35l56.rst +++ b/Documentation/sound/codecs/cs35l56.rst @@ -235,19 +235,28 @@ No %s for algorithm %x HDA driver did not find a .bin file that matches this hardware. Calibration disabled due to missing firmware controls - Driver was not able to write EFI calibration data to firmware registers. - This typically means that either: - - * The driver did not find a suitable wmfw for this hardware, or + This isn't necessarily an error, only informative. The amplifier can + operate without calibration, as the firmware contains a default + calibration for the speakers, but it will tend to over-protect the + speakers. + + This message typically means that either: + * The driver did not find a suitable firmware .wmfw file for this + hardware, or * The amplifier has already been patched with firmware by something previously, and the driver does not have control of a hard RESET line to be able to reset the amplifier and download the firmware files it found. This situation is indicated by the device identification - string in the kernel log shows "(patched=1)" + string in the kernel log showing "(patched=1)" Failed to write calibration - Same meaning and cause as "Calibration disabled due to missing firmware - controls" + If the error code is -2 (ENOENT): + * The driver has been unbound and rebound without reprobing. + * The wrong firmware .wmfw file was loaded for this hardware so the + algorithm IDs in the firmware do not match what the driver is + looking for. + + Other error codes likely indicate a problem communicating with the hardware. Failed to read calibration data from EFI Factory calibration data in EFI is missing, empty or corrupt. From 8c63987ac7fe605c337edbf9052cf97e2a415558 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Wed, 4 Nov 2026 12:23:37 +0000 Subject: [PATCH 057/186] ALSA: hda: cs35l56: Stop cs_dsp before calling cs_dsp_power_down() In cs35l56_hda_unbind() call cs_dsp_stop() before cs_dsp_power_down() if cs_dsp is in a running state. cs35l56_hda_runtime_resume() calls cs_dsp_run() if cs_dsp_power_up() has previously run successfully. If cs35l56_hda_unbind() is then called while in this runtime-resumed state cs_dsp will be running. The code wasn't checking for this and was calling cs_dsp_power_down() while still in a running state, which is illegal. This is currently fairly harmless for the cs35l56 driver. But it doesn't comply with the API, so potentially could break with any future changes that expect the correct API use. Fixes: fb78d73dde2d4 ("ALSA: hda/cs35l56: Call cs_dsp_power_down() before calling cs_dsp_remove()") Signed-off-by: Richard Fitzgerald Link: https://patch.msgid.link/20261104122337.1072468-1-rf@opensource.cirrus.com Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l56_hda.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l56_hda.c b/sound/hda/codecs/side-codecs/cs35l56_hda.c index 148d760987fdd4..5982eebae15254 100644 --- a/sound/hda/codecs/side-codecs/cs35l56_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l56_hda.c @@ -809,6 +809,8 @@ static void cs35l56_hda_unbind(struct device *dev, struct device *master, void * cs_dsp_cleanup_debugfs(&cs35l56->cs_dsp); debugfs_remove_recursive(cs35l56->debugfs_root); #endif + if (cs35l56->cs_dsp.running) + cs_dsp_stop(&cs35l56->cs_dsp); if (cs35l56->cs_dsp.booted) cs_dsp_power_down(&cs35l56->cs_dsp); From 4cbb826b3b36df6c275454287ba1df4564a5370b Mon Sep 17 00:00:00 2001 From: Sinan Turunc Date: Wed, 4 Nov 2026 12:21:57 +0000 Subject: [PATCH 058/186] ALSA: usx2y: Require a buffer of two URBs usx2y_urb_capt_retire() wraps the ring buffer once per packet and usx2y_urb_play_prepare() once per URB of nr_of_packs() packets of up to 50 frames. Both assume that the data fits in the buffer, and the hw constraints only require about two 1 ms periods, so a packet larger than the buffer on capture, or a URB larger than the buffer on playback, violates that assumption. Constrain SNDRV_PCM_HW_PARAM_BUFFER_SIZE in snd_usx2y_pcm_open() to at least two URBs, 2 * nr_of_packs() * 50 frames, for both directions. The bound is in frames and derived from the driver's own packet limit, so it does not read the endpoint, whose altsetting is only selected in prepare. Two URBs rather than one, because the position advances by a URB on playback and only moves when a URB is less than the whole buffer. Keep a guard in usx2y_urb_capt_retire() that drops a packet larger than the buffer, for the case the constraint makes unreachable. With nrpacks above about 163 (S16_LE) or 109 (S24_3LE) the bound exceeds the 64 KiB buffer the substream preallocates (twice that with the preallocation raised to its 128 KiB maximum), so hw_params fails for those module settings. That is intended: the driver cannot be safe with a URB larger than half the largest buffer. Reproduced on 7.3-rc7 with a USB/IP device presenting the US-122 interface layout and 1024-byte packets into a 512-byte capture buffer: BUG: KASAN: out-of-bounds in i_usx2y_urb_complete+0x1285/0x1ac0 Read of size 18446744073709551104 at addr ffff88800e448800 by task vhci_rx/83 Tested on 7.3-rc7 with the same device, S16_LE at 44100 Hz, nrpacks 4, so the bound is 400 frames. A 128-frame capture buffer and a 200-frame playback buffer are refused at hw_params with EINVAL. A 400-frame buffer streams for 30 s in each direction with the position advancing inside the buffer and no KASAN report; the capture guard did not fire. The same 400-frame capture buffer with S24_3LE, which selects altsetting 2 in prepare, streams for 30 s with the same result. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Sinan Turunc Link: https://patch.msgid.link/20261104122136.1518-2-enscrpt@pm.me Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2yaudio.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index a3a0bc13632b8c..1e1d0cfbd167cc 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -51,6 +51,18 @@ static int usx2y_urb_capt_retire(struct snd_usx2y_substream *subs) dev_dbg(&usx2y->dev->dev, "%s: 0 == len ERROR!\n", __func__); continue; } + /* + * snd_usx2y_pcm_open() constrains the buffer to hold at least + * two URBs, so this cannot happen for a device that keeps its + * packets at 50 frames. Drop the packet rather than exceed the + * single wrap below. + */ + if (unlikely(len > runtime->buffer_size)) { + dev_err_ratelimited(&usx2y->dev->dev, + "%s: packet of %i frames exceeds %lu-frame buffer, dropping\n", + __func__, len, runtime->buffer_size); + continue; + } /* copy a data chunk */ if ((hwptr_done + len) > runtime->buffer_size) { @@ -888,6 +900,18 @@ static int snd_usx2y_pcm_open(struct snd_pcm_substream *substream) runtime->private_data = subs; subs->pcm_substream = substream; snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); + /* + * Capture wraps the ring buffer once per packet and playback once + * per URB of nr_of_packs() packets of at most 50 frames (see + * usx2y_urb_play_prepare()). Playback needs the buffer to hold more + * than one URB and capture more than one packet; use the URB bound + * for both, and two URBs rather than one, because the position only + * advances when a URB is less than the whole buffer. The bound is in + * frames and does not depend on the altsetting, which is selected + * in prepare. + */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + 2 * nr_of_packs() * 50, UINT_MAX); return 0; } From e2c6de52ffacb2c0f4076884ee3fdb744735a4cb Mon Sep 17 00:00:00 2001 From: Sinan Turunc Date: Wed, 4 Nov 2026 12:22:03 +0000 Subject: [PATCH 059/186] ALSA: usx2y: Guard the playback copy usx2y_urb_play_prepare() sums the packet lengths of a whole URB, copies that many frames with a single wrap, and sizes each playback packet from the capture packet's actual_length. Three things are unchecked: that the URB fits in the ring buffer, that a packet fits in the playback endpoint's maxpacksize, from which tmpbuf is allocated, and that actual_length is a whole number of frames. The ISO descriptors take the raw actual_length while transfer_buffer_length and the copies take count * stride, so a packet that is not a multiple of the stride makes the descriptors add up to more than the buffer, by up to stride - 1 bytes per packet. Return -EPIPE, as the existing packet-size check does, when the URB is larger than the buffer or a packet is larger than maxpacksize, and size each descriptor from whole frames, so the descriptors add up to transfer_buffer_length and each stays within maxpacksize. The buffer case is prevented by the previous patch and this is the guard for it; the other two depend only on the device. The first case is reachable on the unpatched driver with ordinary packets: on 7.3-rc7, a USB/IP device presenting the US-122 interface layout and sending 48-frame packets, with a 100-frame playback buffer (two 50-frame periods, inside the stock constraints), KASAN reports: BUG: KASAN: out-of-bounds in i_usx2y_urb_complete+0x79b/0x1ac0 Read of size 18446744073709551280 at addr ffff88800e6b02e0 by task vhci_rx/80 With both patches, the same device and a 200-frame playback buffer is refused at hw_params with EINVAL, and a 400-frame buffer plays for 30 s with the position advancing inside the buffer, no KASAN report and neither guard message. The maxpacksize check is not exercised: the emulated device reports the same wMaxPacketSize on both endpoints. The descriptor case is visible with capture packets of 195 bytes at stride 4 (48 frames and 3 bytes): without this change the four descriptors add up to 780 bytes for 768 bytes of data, and USB/IP's completion check refuses the URB ("total length of iso packets 780 not equal to actual length of buffer 768", -EPROTO), after which the driver stops the streams. With this patch every descriptor is 192 bytes, the descriptors add up to the data in every URB for 30 s, the position advances inside the buffer and KASAN reports nothing. The 48-frame playback run above is unchanged. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Sinan Turunc Link: https://patch.msgid.link/20261104122136.1518-3-enscrpt@pm.me Signed-off-by: Takashi Iwai --- sound/usb/usx2y/usbusx2yaudio.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c index 1e1d0cfbd167cc..798c9bb5386a2b 100644 --- a/sound/usb/usx2y/usbusx2yaudio.c +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -118,14 +118,31 @@ static int usx2y_urb_play_prepare(struct snd_usx2y_substream *subs, __func__, counts); return -EPIPE; } + if (counts * usx2y->stride > subs->maxpacksize) { + dev_err_ratelimited(&usx2y->dev->dev, + "%s: packet of %i bytes exceeds maxpacksize %u\n", + __func__, counts * usx2y->stride, + subs->maxpacksize); + return -EPIPE; + } /* set up descriptor */ urb->iso_frame_desc[pack].offset = pack ? urb->iso_frame_desc[pack - 1].offset + urb->iso_frame_desc[pack - 1].length : 0; - urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length; + urb->iso_frame_desc[pack].length = counts * usx2y->stride; } if (atomic_read(&subs->state) >= STATE_PRERUNNING) { + /* + * The copy below wraps once, which needs count <= buffer_size. + * snd_usx2y_pcm_open() guarantees it; guard anyway. + */ + if (unlikely(count > runtime->buffer_size)) { + dev_err_ratelimited(&usx2y->dev->dev, + "%s: URB of %i frames exceeds %lu-frame buffer, stopping\n", + __func__, count, runtime->buffer_size); + return -EPIPE; + } if (subs->hwptr + count > runtime->buffer_size) { /* err, the transferred area goes over buffer boundary. * copy the data to the temp buffer. From fd86c06ad6147c07fe6dff2d548a70bc9dce5c91 Mon Sep 17 00:00:00 2001 From: Zhang Heng Date: Thu, 5 Nov 2026 09:45:35 +0800 Subject: [PATCH 060/186] ALSA: hda: cs35l41: Add support for HP OmniBook 7 Laptop 16-ay0xxx Add a configuration entry for the HP OmniBook 7 Laptop 16-ay0xxx, which uses the HDA subsystem ID 103C8E13. The laptop has a malformed _DSD which causes the cirrus,dev-index property to fail to parse, so it needs to be supported using the configuration table. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=222202 Signed-off-by: Zhang Heng Link: https://patch.msgid.link/20261105014535.47907-1-zhangheng@kylinos.cn Signed-off-by: Takashi Iwai --- sound/hda/codecs/side-codecs/cs35l41_hda_property.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c index c50c7e68953369..95843205828543 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda_property.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda_property.c @@ -85,6 +85,7 @@ static const struct cs35l41_config cs35l41_config_table[] = { { "103C8C51", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDD", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4100, 24 }, { "103C8CDE", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, + { "103C8E13", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "103C8E60", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 4500, 24 }, { "103C8F07", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 0, 1, -1, 1000, 3900, 24 }, { "104312AF", 2, INTERNAL, { CS35L41_LEFT, CS35L41_RIGHT, 0, 0 }, 1, 2, 0, 1000, 4500, 24 }, @@ -543,6 +544,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { { "CSC3551", "103C8C6A", hp_i2c_int_2amp_dual_spkid }, { "CSC3551", "103C8CDD", generic_dsd_config }, { "CSC3551", "103C8CDE", generic_dsd_config }, + { "CSC3551", "103C8E13", generic_dsd_config }, { "CSC3551", "103C8E60", generic_dsd_config }, { "CSC3551", "103C8F07", generic_dsd_config }, { "CSC3551", "104312AF", generic_dsd_config }, From 022e9f8f094279ecd632b3846968790fad7d8ad6 Mon Sep 17 00:00:00 2001 From: Chris Chiu Date: Thu, 5 Nov 2026 10:56:47 +0800 Subject: [PATCH 061/186] ALSA: hda/realtek: Use inverted mute LED quirk for HP ZBook 8 G2a HP ZBook 8 G2a (SSIDs 0x103c:0x8f40, 0x103c:0x8f41, 0x103c:0x8f42, 0x103c:0x8f62) use the Realtek ALC245 codec with a TAS2781 amplifier via I2C. They share the same hardware configuration as the related 0x103c:0x8f94 / 0x103c:0x8f95 models but were left on ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED, which drives the speaker mute LED backwards: off when muted and on when unmuted. Reassign these SSIDs to ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED so the LED follows mute state correctly. Fixes: 9afbbf490322 ("ALSA: hda/realtek - Enable Mute LED and Tas2781 for HP platform") Signed-off-by: Chris Chiu Link: https://patch.msgid.link/20261105025647.1116662-1-chris.chiu@canonical.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 0c678fb8144ab7..873eb3d5ddbe8e 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -8124,11 +8124,11 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x8f3a, "HP EliteBook 6 G2i", ALC236_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x8f3c, "HP EliteBook 6 G2a", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), SND_PCI_QUIRK(0x103c, 0x8f3d, "HP EliteBook 6 G2a", ALC236_FIXUP_HP_MUTE_LED_MICMUTE_VREF), - 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), + SND_PCI_QUIRK(0x103c, 0x8f40, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f41, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), + SND_PCI_QUIRK(0x103c, 0x8f42, "HP ZBook 8 G2a 14W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), SND_PCI_QUIRK(0x103c, 0x8f57, "HP Trekker G7JC", ALC287_FIXUP_CS35L41_I2C_2), - SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), + SND_PCI_QUIRK(0x103c, 0x8f62, "HP ZBook 8 G2a 16W", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), SND_PCI_QUIRK(0x103c, 0x8f7e, "HP Messi", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x8f94, "HP ZBook 8 G2a 14", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), SND_PCI_QUIRK(0x103c, 0x8f95, "HP ZBook 8 G2a 16", ALC245_FIXUP_HP_TAS2781_I2C_MUTE_LED_INVERTED), From 2e17c72cc4db5d1e55788adbdeea0291b3c793dd Mon Sep 17 00:00:00 2001 From: renpanpan Date: Tue, 27 Oct 2026 17:09:13 +0800 Subject: [PATCH 062/186] ALSA: usb-audio: Fix card name for CVT Touch Device (1ff7:0f33) The CVT Touch Device (1ff7:0f33) is a USB composite device (HID + UAC audio). Its iProduct string "Touch Device" is generic and does not identify the audio function. Windows names the audio function "MAXHUB Speaker" from the UAC OUTPUT_TERMINAL iTerminal descriptor. Add a quirk entry to override the card shortname to "MAXHUB Speaker" with QUIRK_NO_INTERFACE to preserve normal stream and mixer creation. Signed-off-by: renpanpan Link: https://patch.msgid.link/20261027090913.37647-1-renpanpan@kylinos.cn Signed-off-by: Takashi Iwai --- sound/usb/quirks-table.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index f912e1033cfada..e85eeb536a0a2e 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -4472,6 +4472,27 @@ QUIRK_RME_DIGIFACE(0x3fa0), } }, +/* + * CVT Touch Device (1ff7:0f33) is a USB composite device with HID + UAC + * audio interfaces. The device iProduct is "Touch Device" but Windows + * displays the audio function as "MAXHUB Speaker" (from iTerminal). + * Override the card shortname to match the Windows display name, while + * keeping normal stream/mixer creation (ifnum = QUIRK_NO_INTERFACE). + */ +{ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_CLASS | + USB_DEVICE_ID_MATCH_INT_SUBCLASS, + .idVendor = 0x1ff7, + .idProduct = 0x0f33, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL, + .driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) { + .product_name = "MAXHUB Speaker", + .ifnum = QUIRK_NO_INTERFACE, + } +}, + #undef QUIRK_AF16RIG_CLOCK #undef QUIRK_AF16RIG From a102d018ed9107f8defe0a82745c6914e74f4f74 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 20 Oct 2026 04:18:22 +0800 Subject: [PATCH 063/186] ALSA: usb-audio: Unify dB scale override Currently, both snd_dragonfly_quirk_db_scale() and snd_usb_mixer_fu_quirk_linear_scale() override dB scale. Collect the code snippets for dB scale override into a helper function, with unified info and warning messages. Also deconflict playback dB scale quirks of DragonFly. Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261020-b4-uac-ya-audio-v1-1-2d6e053b7f50@rong.moe --- sound/usb/mixer_quirks.c | 107 ++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 34 deletions(-) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 01210cc3fae729..9bf68c2573c80d 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4651,6 +4651,72 @@ void snd_usb_mixer_rc_memory_change(struct usb_mixer_interface *mixer, } } +static void snd_usb_apply_db_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl, + const unsigned int *scale, const char *desc) +{ + if (!scale) { + usb_audio_info(mixer->chip, "clearing %s dB scale on [%s] (%d/%d/%d)\n", + desc, kctl->id.name, cval->min, cval->max, cval->res); + + kctl->tlv.p = NULL; + kctl->vd[0].access &= ~(SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK); + return; + } + + usb_audio_info(mixer->chip, "applying %s dB scale quirk on [%s] (%d/%d/%d)\n", + desc, kctl->id.name, cval->min, cval->max, cval->res); + + if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ && + !(kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK)) { + usb_audio_warn(mixer->chip, + "%s dB scale quirk overrides the previously applied one on [%s]\n", + desc, kctl->id.name); + } else if (cval->min_mute) { + /* + * We are clearing SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, + * resulting in min_mute becoming a no-op. + */ + usb_audio_warn(mixer->chip, "%s dB scale quirk overrides MIN_MUTE on [%s]\n", + desc, kctl->id.name); + } + + kctl->tlv.p = scale; + kctl->vd[0].access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; +} + +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); + + snd_usb_apply_db_scale(mixer, cval, kctl, scale, "linear volume"); +} + +/* Respect quirks set by module parameters or quirk flags table. */ +static inline bool deconflict_playback_tlv_quirks(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + const u64 playback_quirk_flags = (QUIRK_FLAG_MIXER_PLAYBACK_MIN_MUTE | + QUIRK_FLAG_MIXER_PLAYBACK_LINEAR_VOL); + + return (!(mixer->chip->quirk_flags & playback_quirk_flags) && + strstr(kctl->id.name, "Playback")); +} + +static inline bool deconflict_capture_tlv_quirks(struct usb_mixer_interface *mixer, + struct snd_kcontrol *kctl) +{ + const u64 capture_quirk_flags = (QUIRK_FLAG_MIXER_CAPTURE_MIN_MUTE | + QUIRK_FLAG_MIXER_CAPTURE_LINEAR_VOL); + + return (!(mixer->chip->quirk_flags & capture_quirk_flags) && + strstr(kctl->id.name, "Capture")); +} + static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) @@ -4671,18 +4737,16 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, 41, 50, TLV_DB_MINMAX_ITEM(-441, 0), ); - if (cval->min == 0 && cval->max == 50) { - usb_audio_info(mixer->chip, "applying DragonFly dB scale quirk (0-50 variant)\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; + if (!deconflict_playback_tlv_quirks(mixer, kctl)) + return; + if (cval->min == 0 && cval->max == 50) { + snd_usb_apply_db_scale(mixer, cval, kctl, scale, "DragonFly"); } else if (cval->min == 0 && cval->max <= 1000) { /* Some other clearly broken DragonFly variant. * At least a 0..53 variant (hw v1.0) exists. */ - usb_audio_info(mixer->chip, "ignoring too narrow dB range on a DragonFly device"); - kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; + snd_usb_apply_db_scale(mixer, cval, kctl, NULL, "DragonFly"); } } @@ -4750,25 +4814,6 @@ static void snd_fix_plt_name(struct snd_usb_audio *chip, 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) @@ -4801,18 +4846,12 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, } 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"); + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Playback")) 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"); + if (cval->control == UAC_FU_VOLUME && strstr(kctl->id.name, "Capture")) 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 && From d82b7b150c2eae1e5b21db5826b28d3f10dcbe9c Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 20 Oct 2026 04:18:23 +0800 Subject: [PATCH 064/186] ALSA: usb-audio: Apply MV-SILICON linear volume quirk per mixer The snd_usb_mv_silicon_quirks() tries to apply both MIXER_PLAYBACK_LINEAR_VOL and MIXER_CAPTURE_LINEAR_VOL on the device at the same time, as long as one of the mixers fulfills val == 0/4096/1. IOW, even if only one of them matches the control values, the linear volume quirk applies to all mixers. Apply the quirk by calling snd_usb_mixer_fu_quirk_linear_scale() directly, so that the MV-SILICON quirk becomes mixer-wise. The existing quirk flags are also checked to respect conflicting quirks. Fixes: dfd4b0d46e77 ("ALSA: usb-audio: Apply linear volume quirk on MV-SILICON devices") Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261020-b4-uac-ya-audio-v1-2-2d6e053b7f50@rong.moe --- sound/usb/mixer_quirks.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 9bf68c2573c80d..2e7a7f160f91a0 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4758,13 +4758,11 @@ static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, /* 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; + if (deconflict_playback_tlv_quirks(mixer, kctl) || + deconflict_capture_tlv_quirks(mixer, kctl)) + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); } else { - usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant"); + usb_audio_dbg(mixer->chip, "not applying MV-SILICON quirks on unknown variant\n"); } } From 804b99c463a67699211c202b7e09cf8afd492fd9 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 20 Oct 2026 04:18:24 +0800 Subject: [PATCH 065/186] ALSA: usb-audio: Add dB scale quirk for an AB13X variant I recently got an AB13X variant with insane volume control, so I tried to measure its volume mapping @ 1000Hz: +--------+--------+-----------+---------+----------+--------+ | hex | dec | dec => dB | mVrms * | mV => dB | res | +--------+--------+-----------+---------+----------+--------+ | 0xd300 | -11520 | -45.00 | | | | | 0xdb5a | -9382 | -36.65 | 1.285 | -26.68 | 2138 # | | 0xe2d0 | -7472 | -29.19 | 4.104 | -21.63 | 1910 # | | 0xe7cc | -6196 | -24.20 | 13.693 | -16.40 | 1276 # | | 0xeb8c | -5236 | -20.45 | 26.71 | -13.50 | 960 | | 0xee8d | -4467 | -17.45 | 42.29 | -11.50 | 769 | | 0xf10e | -3826 | -14.95 | 66.81 | -9.52 | 641 | | 0xf335 | -3275 | -12.79 | 94.43 | -8.01 | 551 | | 0xf517 | -2793 | -10.91 | 133.25 | -6.52 | 482 | | 0xf6c5 | -2363 | -9.23 | 168.73 | -5.49 | 430 | | 0xf847 | -1977 | -7.72 | 212.05 | -4.50 | 386 | | 0xf9a7 | -1625 | -6.35 | 267.1 | -3.50 | 352 | | 0xfae9 | -1303 | -5.09 | 336.3 | -2.50 | 322 | | 0xfc13 | -1005 | -3.93 | 377.4 | -2.00 | 298 | | 0xfd27 | -729 | -2.85 | 423.6 | -1.50 | 276 | | 0xfe29 | -471 | -1.84 | 475.3 | -1.00 | 258 | | 0xff69 | -151 | -0.59 | 533.1 | -0.50 | 320 | | 0x0000 | 0 | 0.00 | 597.7 | 0.00 | 151 | +--------+--------+-----------+---------+----------+--------+ Footnote *: measured through a power amplifier's loopback line out to ensure high input impedance (see #). The raw voltage at 0x0000 without going through a power amplifier is 677mVrms. Footnote #: low output power. Even the impedance of a multimeter is too low, causing a massive voltage drop (-10dB!), which means that they are inaudible when connected to earphones. Do not expose them. The volume control mixer has uneven resolutions in reality. Setting any value between [0xd300, 0xdb5a] results in the same audible volume as 0xdb5a, (0xdb5a, 0xe2d0] results in the same audible volume as 0xe2d0, and so on. The read back value always acts like res == 1. Carefully overrides cval->{min,max,res} so that volume steps with 0.5dB or 1.0dB resolution can be approximately represented togother. With that, we can expose a 19-step TLV range mapping with 7 items. The capture mixer is also quirky and requires linear volume. The AB13X seems to have a lot of variants, probably because it's a OEM product. I believe mine is one of them considering that its idVendor, idProduct, Manufacturer and SerialNumber are the same as the original AB13X. Only the Product string differs. Both quirks are applied only when the mixer values match the seen ones, so that they won't be accidentally applied on unaffected variants. Quirky device sample: usb 7-1: new full-speed USB device number 2 using xhci_hcd usb 7-1: not running at top speed; connect to a high speed hub usb 7-1: New USB device found, idVendor=001f, idProduct=0b21, bcdDevice= 1.00 usb 7-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 7-1: Product: YA USB Audio usb 7-1: Manufacturer: Generic usb 7-1: SerialNumber: 20210726905926 usb 7-1: From builtin table apply quirk flag IFACE_DELAY for device 001f:0b21 usb 7-1: From builtin table apply quirk flag FORCE_IFACE_RESET for device 001f:0b21 usb 7-1: Found last interface = 0 usb 7-1: 1:1: add audio endpoint 0x3 usb 7-1: Creating new data endpoint #3 usb 7-1: 1:1 Set sample rate 48000, clock 0 usb 7-1: 2:1: add audio endpoint 0x83 usb 7-1: Creating new data endpoint #83 usb 7-1: 2:1 Set sample rate 48000, clock 0 usb 7-1: [2] FU [PCM Playback Switch] ch = 1, val = 0/1/1 usb 7-1: [2] FU [PCM Playback Volume] ch = 1, val = -11520/0/1 usb 7-1: [5] FU [Mic Capture Switch] ch = 1, val = 0/1/1 usb 7-1: Warning! Unlikely big volume step count (=8191), linear volume or wrong cval->res? usb 7-1: [5] FU [Mic Capture Volume] ch = 1, val = 0/8191/1 input: Generic YA USB Audio as /devices/pci0000:00/0000:00:08.3/0000:67:00.4/usb7/7-1/7-1:1.3/0003:001F:0B21.000F/input/input41 hid-generic 0003:001F:0B21.000F: input,hidraw12: USB HID v2.01 Device [Generic YA USB Audio] on usb-0000:67:00.4-1/input3 Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261020-b4-uac-ya-audio-v1-3-2d6e053b7f50@rong.moe --- sound/usb/mixer_quirks.c | 99 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 2e7a7f160f91a0..31e1f9df94bd6b 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4687,6 +4687,26 @@ static void snd_usb_apply_db_scale(struct usb_mixer_interface *mixer, kctl->vd[0].access &= ~SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK; } +static void snd_usb_apply_db_scale_val(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl, + const unsigned int *scale, const char *desc, + int min, int max, int res) +{ + snd_usb_apply_db_scale(mixer, cval, kctl, scale, desc); + + usb_audio_info(mixer->chip, + "overriding [%s] val (%d/%d/%d => %d/%d/%d) to fit %s dB scale quirk\n", + kctl->id.name, cval->min, cval->max, cval->res, min, max, res, desc); + + /* + * We are not fixing the cached values, as get_relative_value() will + * ensure userspace always sees compliant ones. + */ + cval->min = min; + cval->max = max; + cval->res = res; +} + static void snd_usb_mixer_fu_quirk_linear_scale(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) @@ -4750,6 +4770,81 @@ static void snd_dragonfly_quirk_db_scale(struct usb_mixer_interface *mixer, } } +static void snd_ab13x_quirks(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + /* + * Volume mapping based on voltage level measurement @ 1000Hz: + * +--------+--------+-----------+---------+----------+--------+ + * | hex | dec | dec => dB | mVrms * | mV => dB | res | + * +--------+--------+-----------+---------+----------+--------+ + * | 0xd300 | -11520 | -45.00 | | | | + * | 0xdb5a | -9382 | -36.65 | 1.285 | -26.68 | 2138 # | + * | 0xe2d0 | -7472 | -29.19 | 4.104 | -21.63 | 1910 # | + * | 0xe7cc | -6196 | -24.20 | 13.693 | -16.40 | 1276 # | + * | 0xeb8c | -5236 | -20.45 | 26.71 | -13.50 | 960 | + * | 0xee8d | -4467 | -17.45 | 42.29 | -11.50 | 769 | + * | 0xf10e | -3826 | -14.95 | 66.81 | -9.52 | 641 | + * | 0xf335 | -3275 | -12.79 | 94.43 | -8.01 | 551 | + * | 0xf517 | -2793 | -10.91 | 133.25 | -6.52 | 482 | + * | 0xf6c5 | -2363 | -9.23 | 168.73 | -5.49 | 430 | + * | 0xf847 | -1977 | -7.72 | 212.05 | -4.50 | 386 | + * | 0xf9a7 | -1625 | -6.35 | 267.1 | -3.50 | 352 | + * | 0xfae9 | -1303 | -5.09 | 336.3 | -2.50 | 322 | + * | 0xfc13 | -1005 | -3.93 | 377.4 | -2.00 | 298 | + * | 0xfd27 | -729 | -2.85 | 423.6 | -1.50 | 276 | + * | 0xfe29 | -471 | -1.84 | 475.3 | -1.00 | 258 | + * | 0xff69 | -151 | -0.59 | 533.1 | -0.50 | 320 | + * | 0x0000 | 0 | 0.00 | 597.7 | 0.00 | 151 | + * +--------+--------+-----------+---------+----------+--------+ + * + * Footnote *: measured through a power amplifier's loopback line out + * to ensure high input impedance (see #). The raw voltage at 0x0000 + * without going through a power amplifier is 677mVrms. + * + * Footnote #: low output power. Even the impedance of a multimeter is + * too low, causing a massive voltage drop (-10dB!), which means that + * they are inaudible when connected to earphones. Do not expose them. + * + * The volume control mixer has uneven resolutions in reality. Setting + * any value between [0xd300, 0xdb5a] results in the same audible volume + * as 0xdb5a, (0xdb5a, 0xe2d0] results in the same audible volume as + * 0xe2d0, and so on. The read back value always acts like res == 1. + */ + static const DECLARE_TLV_DB_RANGE(scale, + 0, 0, TLV_DB_SCALE_ITEM(-1350, 0, 0), /* -5500 */ + 1, 3, TLV_DB_SCALE_ITEM(-1150, 0, 0), /* -5200, -4900, -4600 */ + 4, 5, TLV_DB_SCALE_ITEM( -952, 0, 0), /* -4300, -4000 */ + 6, 7, TLV_DB_SCALE_ITEM( -801, 0, 0), /* -3700, -3400 */ + 8, 9, TLV_DB_SCALE_ITEM( -652, 0, 0), /* -3100, -2800 */ + 10, 13, TLV_DB_MINMAX_ITEM(-550, -250), /* -2500, -2200, -1900, -1600 */ + 14, 18, TLV_DB_MINMAX_ITEM(-200, 0), /* -1300, -1000, -700, -400, -100 */ + ); + + if (cval->min == -11520 && cval->max == 0 && cval->res == 1) { + if (!deconflict_playback_tlv_quirks(mixer, kctl)) + return; + + /* + * These values are carefully chosen so that volume steps with + * 0.5dB or 1.0dB resolution can be approximately represented + * togother. + * + * It's literally a miracle that these values can flawlessly + * align to 100. + */ + snd_usb_apply_db_scale_val(mixer, cval, kctl, scale, "AB13X", -5500, -100, 300); + } else if (cval->min == 0 && cval->max == 8191 && cval->res == 1) { + /* The final effects will be printed later. */ + usb_audio_info(mixer->chip, "applying AB13X Capture quirk (0/8191/1 variant)\n"); + + if (deconflict_capture_tlv_quirks(mixer, kctl)) + snd_usb_mixer_fu_quirk_linear_scale(mixer, cval, kctl); + } else { + usb_audio_dbg(mixer->chip, "not applying AB13X quirk on unknown variant\n"); + } +} + static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) @@ -4817,6 +4912,10 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, struct snd_kcontrol *kctl) { switch (mixer->chip->usb_id) { + case USB_ID(0x001f, 0x0b21): /* AB13X Headset Adapter */ + if (cval->control == UAC_FU_VOLUME) + snd_ab13x_quirks(mixer, cval, kctl); + break; case USB_ID(0x21b4, 0x0081): /* AudioQuest DragonFly */ if (unitid == 7 && cval->control == UAC_FU_VOLUME) snd_dragonfly_quirk_db_scale(mixer, cval, kctl); From d4ada42c7a0a1d285fd4847c0da15254b56911bf Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Tue, 20 Oct 2026 04:18:25 +0800 Subject: [PATCH 066/186] ALSA: usb-audio: Add dB scale quirk for Xiaomi Speaker Mini I recently brought an Xiaomi Speaker Mini (USB Product string, marketing name: Xiaomi Bluetooth Speaker Mini), which is never advertised as UAC capable. By chance, I connected it to my PC, and a UAC device showed up. Unsurprisingly, it's yet another device with insane volume control, so I tried to measure its volume mapping @ 1000Hz with a microphone: +--------+--------+-----------+-------------+--------+ | hex | dec | dec => dB | measured dB | res | +--------+--------+-----------+-------------+--------+ | 0xd000 | -12288 | -48.00 | (mute) | | | 0xdd00 | -8960 | -35.00 | -68.6 | 3328 # | | 0xe540 | -6848 | -26.75 | -61.2 | 2112 | | 0xea40 | -5568 | -21.75 | -57.0 | 1280 | | 0xed80 | -4736 | -18.50 | -52.5 | 832 | | 0xf0a0 | -3936 | -15.38 | -48.3 | 800 | | 0xf2d0 | -3376 | -13.19 | -42.1 | 560 | | 0xf4d0 | -2864 | -11.19 | -36.1 | 512 | | 0xf6c0 | -2368 | -9.25 | -32.0 | 496 | | 0xf840 | -1984 | -7.75 | -24.0 | 384 | | 0xf9e0 | -1568 | -6.13 | -17.9 | 416 | | 0xfb30 | -1232 | -4.81 | -13.9 | 336 | | 0xfc80 | -896 | -3.50 | -10.0 | 336 | | 0xfd90 | -624 | -2.44 | -6.0 | 272 | | 0xfeb0 | -336 | -1.31 | -4.0 | 288 | | 0xffb0 | -80 | -0.31 | -2.0 | 256 | | 0x0000 | 0 | 0.00 | 0.0 | 80 | +--------+--------+-----------+-------------+--------+ Footnote #: running out of the 10-item limit of TLV ranges. The limitation is from alsa-lib, where MAX_TLV_RANGE_SIZE == 256 Bytes. The volume control mixer declares res == 256, but actually acts like res == 1 (i.e., accepts any value between min and max) and has uneven resolutions in reality. Setting any value between (0xd000, 0xdd00] results in the same audible volume as 0xdd00, and so on. Carefully overrides cval->{min,max,res} so that volume steps with 2dB or 4dB resolution can be approximately represented togother. With that, we can expose a 25-step TLV range mapping with 10 items. The quirk is applied only when the mixer values match the seen ones, so that it won't be accidentally applied to unseen variants. Quirky device sample: usb 3-1: new full-speed USB device number 14 using xhci_hcd usb 3-1: New USB device found, idVendor=2717, idProduct=5096, bcdDevice= 1.00 usb 3-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 usb 3-1: Product: Xiaomi Speaker Mini-**** usb 3-1: Manufacturer: bestechnic usb 3-1: SerialNumber: 20160406.1 usb 3-1: Found last interface = 0 usb 3-1: 1:1: add audio endpoint 0x3 usb 3-1: Creating new data endpoint #3 usb 3-1: 1:1 Set sample rate 48000, clock 0 usb 3-1: [5] FU [PCM Playback Switch] ch = 1, val = 0/1/1 usb 3-1: cannot set ctl value: req = 0x4, wValue = 0x200, wIndex = 0x500, type = 4, data = 0x80/0x0 usb 3-1: [5] FU [PCM Playback Volume] ch = 1, val = -12288/0/256 input: bestechnic Xiaomi Speaker Mini-2421 as /devices/pci0000:00/0000:00:08.3/0000:67:00.0/usb3/3-1/3-1:1.2/0003:2717:5096.002A/input/input69 hid-generic 0003:2717:5096.002A: input,hidraw10: USB HID v1.11 Device [bestechnic Xiaomi Speaker Mini-2421] on usb-0000:67:00.0-1/input2 (**** is the last four digits of the device's serial number) Signed-off-by: Rong Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261020-b4-uac-ya-audio-v1-4-2d6e053b7f50@rong.moe --- sound/usb/mixer_quirks.c | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 31e1f9df94bd6b..5eee501948fbc4 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4845,6 +4845,71 @@ static void snd_ab13x_quirks(struct usb_mixer_interface *mixer, struct usb_mixer } } +static void snd_xiaomi_speaker_mini_quirk_db_scale(struct usb_mixer_interface *mixer, + struct usb_mixer_elem_info *cval, + struct snd_kcontrol *kctl) +{ + /* + * Volume mapping based on normalized microphone measurement @ 1000Hz: + * +--------+--------+-----------+-------------+--------+ + * | hex | dec | dec => dB | measured dB | res | + * +--------+--------+-----------+-------------+--------+ + * | 0xd000 | -12288 | -48.00 | (mute) | | + * | 0xdd00 | -8960 | -35.00 | -68.6 | 3328 # | + * | 0xe540 | -6848 | -26.75 | -61.2 | 2112 | + * | 0xea40 | -5568 | -21.75 | -57.0 | 1280 | + * | 0xed80 | -4736 | -18.50 | -52.5 | 832 | + * | 0xf0a0 | -3936 | -15.38 | -48.3 | 800 | + * | 0xf2d0 | -3376 | -13.19 | -42.1 | 560 | + * | 0xf4d0 | -2864 | -11.19 | -36.1 | 512 | + * | 0xf6c0 | -2368 | -9.25 | -32.0 | 496 | + * | 0xf840 | -1984 | -7.75 | -24.0 | 384 | + * | 0xf9e0 | -1568 | -6.13 | -17.9 | 416 | + * | 0xfb30 | -1232 | -4.81 | -13.9 | 336 | + * | 0xfc80 | -896 | -3.50 | -10.0 | 336 | + * | 0xfd90 | -624 | -2.44 | -6.0 | 272 | + * | 0xfeb0 | -336 | -1.31 | -4.0 | 288 | + * | 0xffb0 | -80 | -0.31 | -2.0 | 256 | + * | 0x0000 | 0 | 0.00 | 0.0 | 80 | + * +--------+--------+-----------+-------------+--------+ + * + * The volume control mixer declares res == 256, but actually acts like + * res == 1 (i.e., accepts any value between min and max) and has uneven + * resolutions in reality. Setting any value between (0xd000, 0xdd00] + * results in the same audible volume as 0xdd00, and so on. + * + * Footnote #: running out of the 10-item limit of TLV ranges. + */ + static const DECLARE_TLV_DB_RANGE(scale, + 0, 0, TLV_DB_SCALE_ITEM( -6120, 0, 0), /* -7104 */ + 1, 5, TLV_DB_SCALE_ITEM( -5700, 0, 0), /* -6808, -6512, -6216, -5920, -5624 */ + 6, 8, TLV_DB_SCALE_ITEM( -5250, 0, 0), /* -5328, -5032, -4736 */ + 9, 10, TLV_DB_SCALE_ITEM( -4830, 0, 0), /* -4440, -4144 */ + 11, 12, TLV_DB_SCALE_ITEM( -4210, 0, 0), /* -3848, -3552 */ + 13, 14, TLV_DB_SCALE_ITEM( -3610, 0, 0), /* -3256, -2960 */ + 15, 16, TLV_DB_SCALE_ITEM( -3200, 0, 0), /* -2664, -2368 */ + 17, 18, TLV_DB_MINMAX_ITEM(-2400, -1790), /* -2072, -1776 */ + 19, 21, TLV_DB_MINMAX_ITEM(-1390, -600), /* -1480, -1184, -888 */ + 22, 24, TLV_DB_MINMAX_ITEM( -400, 0), /* -592, -296, 0 */ + ); + + if (cval->min == -12288 && cval->max == 0 && cval->res == 256) { + if (!deconflict_playback_tlv_quirks(mixer, kctl)) + return; + + /* + * These values are carefully chosen so that volume steps with + * 2dB or 4dB resolution can be approximately represented + * togother. + */ + snd_usb_apply_db_scale_val(mixer, cval, kctl, scale, + "Xiaomi Speaker Mini", -7104, 0, 296); + } else { + usb_audio_dbg(mixer->chip, + "not applying Xiaomi Speaker Mini quirk on unknown variant\n"); + } +} + static void snd_usb_mv_silicon_quirks(struct usb_mixer_interface *mixer, struct usb_mixer_elem_info *cval, struct snd_kcontrol *kctl) @@ -4920,6 +4985,10 @@ void snd_usb_mixer_fu_apply_quirk(struct usb_mixer_interface *mixer, if (unitid == 7 && cval->control == UAC_FU_VOLUME) snd_dragonfly_quirk_db_scale(mixer, cval, kctl); break; + case USB_ID(0x2717, 0x5096): /* Xiaomi Speaker Mini */ + if (cval->control == UAC_FU_VOLUME) + snd_xiaomi_speaker_mini_quirk_db_scale(mixer, cval, kctl); + break; } if (cval->control == UAC_FU_VOLUME && From c8eaa2d9f0cfa2e5f1e238ab39c33970be9b13a2 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 22 Oct 2026 20:12:42 +0900 Subject: [PATCH 067/186] selftests: ALSA: pcmtest: Move the capture data check into a helper The capture test checks the captured bytes against the patterns in an inline loop. A pause test added later needs the same check, so move the loop into find_capture_mismatch(). It returns the offset of the first wrong byte, or -1 when all bytes match. No functional change. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/2922e4e3021760ebed68b8e44b80fe0b2e3f1d9c.1792660433.git.sammiee5311@gmail.com --- .../selftests/alsa/test-pcmtest-driver.c | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index 18ee72d5d4c963..fc4d125428083f 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -82,6 +82,25 @@ static size_t get_sec_buf_len(unsigned int rate, unsigned long channels, snd_pcm return rate * channels * snd_pcm_format_physical_width(format) / 8; } +/* + * Check interleaved capture data against the patterns. Return the offset of the first wrong + * byte, or -1 if all bytes match. + */ +static long find_capture_mismatch(const unsigned char *it, struct pcmtest_test_params *params) +{ + size_t i, cur_ch, pos_in_ch; + + for (i = 0; i < params->sec_buf_len * params->time; i++) { + cur_ch = (i / params->sample_size) % CH_NUM; + pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size + + (i % params->sample_size); + if (it[i] != patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]) + return i; + } + + return -1; +} + static int setup_handle(snd_pcm_t **handle, snd_pcm_sw_params_t *swparams, snd_pcm_hw_params_t *hwparams, struct pcmtest_test_params *params, int card, snd_pcm_stream_t stream) @@ -207,9 +226,7 @@ TEST_F(pcmtest, playback) { */ TEST_F(pcmtest, capture) { snd_pcm_t *handle; - unsigned char *it; snd_pcm_sframes_t read_res; - int i, cur_ch, pos_in_ch; void *samples; struct pcmtest_test_params *params = &self->params; @@ -226,13 +243,7 @@ TEST_F(pcmtest, capture) { read_res = snd_pcm_readi(handle, samples, params->rate * params->time); ASSERT_EQ(read_res, params->rate * params->time); snd_pcm_close(handle); - it = (unsigned char *)samples; - for (i = 0; i < self->params.sec_buf_len * self->params.time; i++) { - cur_ch = (i / params->sample_size) % CH_NUM; - pos_in_ch = i / params->sample_size / CH_NUM * params->sample_size - + (i % params->sample_size); - ASSERT_EQ(it[i], patterns[cur_ch].buf[pos_in_ch % patterns[cur_ch].len]); - } + ASSERT_EQ(find_capture_mismatch(samples, params), -1); free(samples); } From 35d9f383973baa8bce70f6afeb033ce550a684cb Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 22 Oct 2026 20:12:43 +0900 Subject: [PATCH 068/186] selftests: ALSA: pcmtest: Test errors from the PCM callbacks The driver can return an error from its open, hw_params, prepare and trigger callbacks, controlled by the inject_*_err module parameters. No test uses them, so nothing checks that these errors reach user space. Add a test for each callback. It sets the parameter, checks that the matching call fails with the driver's error, then clears the parameter and checks that the call succeeds. The trigger test also checks that a failed start leaves the stream in the PREPARED state. The fixture teardown clears all parameters, so a failed test does not affect the next one. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/f1b98dad097550f09e38ee7e90ce30106d89a09d.1792660433.git.sammiee5311@gmail.com --- .../selftests/alsa/test-pcmtest-driver.c | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index fc4d125428083f..380364ee792415 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -5,6 +5,7 @@ * * Copyright 2023 Ivan Orlov */ +#include #include #include #include "kselftest_harness.h" @@ -58,6 +59,20 @@ static int read_patterns(void) return 0; } +static int set_param(const char *name, int value) +{ + char path[128]; + FILE *f; + + sprintf(path, "/sys/module/snd_pcmtest/parameters/%s", name); + f = fopen(path, "w"); + if (!f) + return -1; + fprintf(f, "%d", value); + + return fclose(f) ? -1 : 0; +} + static int get_test_results(char *debug_name) { int result; @@ -141,6 +156,10 @@ FIXTURE(pcmtest) { }; FIXTURE_TEARDOWN(pcmtest) { + set_param("inject_open_err", 0); + set_param("inject_hwpars_err", 0); + set_param("inject_prepare_err", 0); + set_param("inject_trigger_err", 0); } FIXTURE_SETUP(pcmtest) { @@ -343,4 +362,74 @@ TEST_F(pcmtest, reset_ioctl) { snd_pcm_close(handle); } +/* + * Here we check that an error from each PCM callback reaches user space. The driver returns + * the error while the matching 'inject_*_err' parameter is set, and works again once it is + * cleared. + */ +TEST_F(pcmtest, open_err) { + snd_pcm_t *handle; + char pcm_name[32]; + + sprintf(pcm_name, "hw:%d,0,0", self->card); + + ASSERT_EQ(set_param("inject_open_err", 1), 0); + ASSERT_EQ(snd_pcm_open(&handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0), -EBUSY); + + ASSERT_EQ(set_param("inject_open_err", 0), 0); + ASSERT_EQ(snd_pcm_open(&handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0), 0); + snd_pcm_close(handle); +} + +TEST_F(pcmtest, hw_params_err) { + snd_pcm_t *handle; + char pcm_name[32]; + + snd_pcm_hw_params_alloca(&self->hwparams); + sprintf(pcm_name, "hw:%d,0,0", self->card); + ASSERT_EQ(snd_pcm_open(&handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0), 0); + ASSERT_EQ(snd_pcm_hw_params_any(handle, self->hwparams), 0); + + ASSERT_EQ(set_param("inject_hwpars_err", 1), 0); + ASSERT_EQ(snd_pcm_hw_params(handle, self->hwparams), -EBUSY); + + ASSERT_EQ(set_param("inject_hwpars_err", 0), 0); + ASSERT_EQ(snd_pcm_hw_params(handle, self->hwparams), 0); + snd_pcm_close(handle); +} + +TEST_F(pcmtest, prepare_err) { + snd_pcm_t *handle; + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, &self->params, + self->card, SND_PCM_STREAM_PLAYBACK), 0); + + ASSERT_EQ(set_param("inject_prepare_err", 1), 0); + ASSERT_EQ(snd_pcm_prepare(handle), -EINVAL); + + ASSERT_EQ(set_param("inject_prepare_err", 0), 0); + ASSERT_EQ(snd_pcm_prepare(handle), 0); + snd_pcm_close(handle); +} + +TEST_F(pcmtest, trigger_err) { + snd_pcm_t *handle; + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, &self->params, + self->card, SND_PCM_STREAM_CAPTURE), 0); + + ASSERT_EQ(set_param("inject_trigger_err", 1), 0); + ASSERT_EQ(snd_pcm_start(handle), -EINVAL); + ASSERT_EQ(snd_pcm_state(handle), SND_PCM_STATE_PREPARED); + + ASSERT_EQ(set_param("inject_trigger_err", 0), 0); + ASSERT_EQ(snd_pcm_start(handle), 0); + ASSERT_EQ(snd_pcm_state(handle), SND_PCM_STATE_RUNNING); + snd_pcm_close(handle); +} + TEST_HARNESS_MAIN From e32df694d32340ff42ede40b644a06a6e17e9e2d Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Thu, 22 Oct 2026 20:12:44 +0900 Subject: [PATCH 069/186] selftests: ALSA: pcmtest: Test pausing a capture stream The driver supports pause, but no test uses it. Add a capture test that pauses the stream halfway for half a second. The core does not read the hardware pointer while the stream is paused, so a driver that keeps running shows up only at the release, as a jump of the pointer. Check that the jump is under a tenth of a second, and that the data carries on from where it stopped. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/906d65ff1cf78f5e173a5476eadcbca285aa2b17.1792660433.git.sammiee5311@gmail.com --- .../selftests/alsa/test-pcmtest-driver.c | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tools/testing/selftests/alsa/test-pcmtest-driver.c b/tools/testing/selftests/alsa/test-pcmtest-driver.c index 380364ee792415..b796a5613c6f9c 100644 --- a/tools/testing/selftests/alsa/test-pcmtest-driver.c +++ b/tools/testing/selftests/alsa/test-pcmtest-driver.c @@ -7,6 +7,7 @@ */ #include #include +#include #include #include "kselftest_harness.h" @@ -362,6 +363,54 @@ TEST_F(pcmtest, reset_ioctl) { snd_pcm_close(handle); } +/* + * Here we pause a capture stream halfway. The core does not read the hardware pointer while + * the stream is paused, so a driver that keeps running only shows up after the release, as a + * jump of the pointer. The data must also continue where it stopped, so the whole buffer still + * matches the patterns. + */ +TEST_F(pcmtest, capture_pause) { + snd_pcm_t *handle; + snd_pcm_sframes_t read_res, paused_avail, avail, frames, half; + unsigned char *samples; + struct pcmtest_test_params *params = &self->params; + + samples = calloc(params->sec_buf_len * params->time, 1); + ASSERT_NE(samples, NULL); + + snd_pcm_sw_params_alloca(&self->swparams); + snd_pcm_hw_params_alloca(&self->hwparams); + ASSERT_EQ(setup_handle(&handle, self->swparams, self->hwparams, params, + self->card, SND_PCM_STREAM_CAPTURE), 0); + + frames = params->rate * params->time; + half = frames / 2; + read_res = snd_pcm_readi(handle, samples, half); + ASSERT_EQ(read_res, half); + + ASSERT_EQ(snd_pcm_pause(handle, 1), 0); + ASSERT_EQ(snd_pcm_state(handle), SND_PCM_STATE_PAUSED); + paused_avail = snd_pcm_avail(handle); + ASSERT_GE(paused_avail, 0); + usleep(500000); + + /* + * At 8000 Hz the pause is 4000 frames long. Allow a tenth of a second (800 frames) for + * the release. + */ + ASSERT_EQ(snd_pcm_pause(handle, 0), 0); + avail = snd_pcm_avail(handle); + ASSERT_GE(avail, 0); + ASSERT_LT(avail - paused_avail, (snd_pcm_sframes_t)params->rate / 10); + read_res = snd_pcm_readi(handle, samples + snd_pcm_frames_to_bytes(handle, half), + frames - half); + ASSERT_EQ(read_res, frames - half); + snd_pcm_close(handle); + + ASSERT_EQ(find_capture_mismatch(samples, params), -1); + free(samples); +} + /* * Here we check that an error from each PCM callback reaches user space. The driver returns * the error while the matching 'inject_*_err' parameter is set, and works again once it is From 2992cc5b646f1375c0f9bef807da970fa85cba0b Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Thu, 29 Oct 2026 04:24:32 +1030 Subject: [PATCH 070/186] ALSA: scarlett2: Allow config sets to override port counts Firmware updates can change the number of mux ports. Let a config set override the device port counts. Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Geoffrey D. Bennett Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/da781a3cc3ffe2f9684586e874e118ec9ad01c20.1793207190.git.g@b4.vu --- sound/usb/mixer_scarlett2.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index ed5fe746d43856..2b4876abcecd09 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -616,6 +616,7 @@ struct scarlett2_config_set { const unsigned int *input_gain_tlv; const char *const *autogain_status_texts; const struct scarlett2_config items[SCARLETT2_CONFIG_COUNT]; + const int (*port_count)[SCARLETT2_PORT_DIRNS]; }; /* Map firmware versions to config sets per-device. @@ -1412,6 +1413,7 @@ struct scarlett2_data { struct delayed_work work; const struct scarlett2_device_info *info; const struct scarlett2_config_set *config_set; + const int (*port_count)[SCARLETT2_PORT_DIRNS]; const char *series_name; __u8 bInterfaceNumber; __u8 bEndpointAddress; @@ -3158,7 +3160,7 @@ static int scarlett2_usb_set_mix(struct usb_mixer_interface *mixer, NULL, 0); } -/* Convert a port number index (per info->port_count) to a hardware ID */ +/* Convert a port number index (per private->port_count) to a hardware ID */ static u32 scarlett2_mux_src_num_to_id( const int port_count[][SCARLETT2_PORT_DIRNS], int num) { @@ -3202,8 +3204,7 @@ static u32 scarlett2_mux_id_to_num( static void scarlett2_usb_populate_mux(struct scarlett2_data *private, u32 mux_entry) { - const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; int dst_idx, src_idx; @@ -3342,7 +3343,7 @@ static int scarlett2_usb_set_mux(struct usb_mixer_interface *mixer) { struct scarlett2_data *private = mixer->private_data; const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; int table; struct { @@ -7365,8 +7366,7 @@ static int scarlett2_mux_src_enum_ctl_info(struct snd_kcontrol *kctl, { struct usb_mixer_elem_info *elem = kctl->private_data; struct scarlett2_data *private = elem->head.mixer->private_data; - const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; unsigned int item = uinfo->value.enumerated.item; int items = private->num_mux_srcs; int port_type; @@ -7466,8 +7466,7 @@ static const struct snd_kcontrol_new scarlett2_mux_src_enum_ctl = { static int scarlett2_add_mux_enums(struct usb_mixer_interface *mixer) { struct scarlett2_data *private = mixer->private_data; - const struct scarlett2_device_info *info = private->info; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS] = private->port_count; int port_type, channel, i; for (i = 0, port_type = 0; @@ -8696,9 +8695,12 @@ static void scarlett2_count_io(struct scarlett2_data *private) { const struct scarlett2_device_info *info = private->info; const struct scarlett2_config_set *config_set = private->config_set; - const int (*port_count)[SCARLETT2_PORT_DIRNS] = info->port_count; + const int (*port_count)[SCARLETT2_PORT_DIRNS]; int port_type, srcs = 0, dsts = 0, i; + port_count = config_set->port_count ?: info->port_count; + private->port_count = port_count; + /* Count the number of mux sources and destinations */ for (port_type = 0; port_type < SCARLETT2_PORT_TYPE_COUNT; From 848a68e764a7ec6ac7d87b3193ed20dd1c351e58 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Thu, 29 Oct 2026 04:24:42 +1030 Subject: [PATCH 071/186] ALSA: scarlett2: Update offsets for Gen 4 firmware 2432 and 2449 Solo, 2i2, and 4i4 Gen 4 firmware 2449 and 4i4 firmware 2432 move the settings the driver uses, so it reads and writes the wrong ones, and the 2i2 locks up until it is rebooted. 2i2 firmware 2449 also adds two mixer outputs and the input combining switch that the Solo already has. Add config sets for them. Fixes: 4e809a299677 ("ALSA: scarlett2: Add support for Solo, 2i2, and 4i4 Gen 4") Closes: https://github.com/geoffreybennett/alsa-scarlett-gui/issues/270 Cc: stable@vger.kernel.org # ALSA: scarlett2: Allow config sets to override port counts Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Geoffrey D. Bennett Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/288b4cd4b10f0185de029f7327d14fdc413a740b.1793207190.git.g@b4.vu --- sound/usb/mixer_scarlett2.c | 267 ++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c index 2b4876abcecd09..2b916eddbbf3e0 100644 --- a/sound/usb/mixer_scarlett2.c +++ b/sound/usb/mixer_scarlett2.c @@ -453,6 +453,7 @@ static void scarlett2_notify_direct_monitor(struct usb_mixer_interface *mixer); static void scarlett2_notify_power_status(struct usb_mixer_interface *mixer); static void scarlett2_notify_pcm_input_switch( struct usb_mixer_interface *mixer); +static void scarlett2_notify_select_pcm_in(struct usb_mixer_interface *mixer); static void scarlett2_notify_bluetooth(struct usb_mixer_interface *mixer); /* Arrays of notification callback functions */ @@ -513,6 +514,23 @@ static const struct scarlett2_notification scarlett4_2i2_notifications[] = { { 0, NULL } }; +static const struct scarlett2_notification +scarlett4_2i2_2449_notifications[] = { + { 0x00000001, scarlett2_notify_ack }, + { 0x00000008, scarlett2_notify_sync }, + { 0x00200000, scarlett2_notify_input_safe }, + { 0x00400000, scarlett2_notify_autogain }, + { 0x00800000, scarlett2_notify_input_air }, + { 0x01000000, scarlett2_notify_direct_monitor }, + { 0x02000000, scarlett2_notify_select_pcm_in }, + { 0x04000000, scarlett2_notify_input_level }, + { 0x08000000, scarlett2_notify_input_phantom }, + { 0x10000000, NULL }, /* power status, ignored */ + { 0x40000000, scarlett2_notify_input_gain }, + { 0x80000000, NULL }, /* power status, ignored */ + { 0, NULL } +}; + static const struct scarlett2_notification scarlett4_4i4_notifications[] = { { 0x00000001, scarlett2_notify_ack }, { 0x00000008, scarlett2_notify_sync }, @@ -927,6 +945,42 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_solo_2417 = { } }; +/* Solo Gen 4, firmware version 2449 and above */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_solo_2449 = { + .notifications = scarlett4_solo_notifications, + .param_buf_addr = 0xe0, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x4f, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x110, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x4e, .size = 8, .activate = 9, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x45, .size = 8, .activate = 10, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x46, .size = 8, .activate = 11, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PCM_INPUT_SWITCH] = { + .offset = 0x20e, .size = 8, .activate = 25, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x23a, .size = 16, .activate = 26 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x24b, .size = 8, .activate = 27, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x250, .size = 32, .activate = 29 } + } +}; + /* 2i2 Gen 4 */ static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2 = { .notifications = scarlett4_2i2_notifications, @@ -1044,6 +1098,76 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2417 = { } }; +/* 2i2 Gen 4, firmware version 2449 and above */ +static const int scarlett2_gen4_2i2_2449_port_count[][SCARLETT2_PORT_DIRNS] = { + [SCARLETT2_PORT_TYPE_NONE] = { 1, 0 }, + [SCARLETT2_PORT_TYPE_ANALOGUE] = { 2, 2 }, + [SCARLETT2_PORT_TYPE_MIX] = { 8, 6 }, + [SCARLETT2_PORT_TYPE_PCM] = { 2, 4 }, +}; + +static const struct scarlett2_config_set scarlett2_config_set_gen4_2i2_2449 = { + .notifications = scarlett4_2i2_2449_notifications, + .port_count = scarlett2_gen4_2i2_2449_port_count, + .param_buf_addr = 0x114, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x61, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR] = { + .offset = 0x162, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x14d, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x14f, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x149, .size = 8, .activate = 29, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x14a, .size = 8, .activate = 30, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x60, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x63, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x54, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x15f, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x56, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x163, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x166, .size = 8, .activate = 18, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PCM_INPUT_SWITCH] = { + .offset = 0x4, .size = 8, .activate = 40, .pbuf = 1 }, + + [SCARLETT2_CONFIG_DIRECT_MONITOR_GAIN] = { + .offset = 0x2b0, .size = 16, .activate = 36 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x2d3, .size = 8, .activate = 37, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x2d8, .size = 32, .activate = 39 } + } +}; + /* 4i4 Gen 4 */ static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4 = { .notifications = scarlett4_4i4_notifications, @@ -1168,6 +1292,136 @@ static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4_2417 = { } }; +/* 4i4 Gen 4, firmware version 2432 and above */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4_2432 = { + .notifications = scarlett4_4i4_notifications, + .param_buf_addr = 0x130, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x5c, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x13e, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x140, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x13a, .size = 8, .activate = 23, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x13b, .size = 8, .activate = 24, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x5a, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x5e, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x4e, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x150, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x50, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x153, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x156, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x32, .size = 16 }, + + [SCARLETT2_CONFIG_HEADPHONE_VOLUME] = { + .offset = 0x3a, .size = 16 }, + + [SCARLETT2_CONFIG_POWER_EXT] = { + .offset = 0x168, .size = 8 }, + + [SCARLETT2_CONFIG_POWER_LOW] = { + .offset = 0x16f, .size = 8 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x3c5, .size = 8, .activate = 36, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x3c8, .size = 32, .activate = 38 } + } +}; + +/* 4i4 Gen 4, firmware version 2449 and above */ +static const struct scarlett2_config_set scarlett2_config_set_gen4_4i4_2449 = { + .notifications = scarlett4_4i4_notifications, + .param_buf_addr = 0x138, + .input_gain_tlv = db_scale_gen4_gain, + .autogain_status_texts = scarlett2_autogain_status_gen4, + .items = { + [SCARLETT2_CONFIG_MSD_SWITCH] = { + .offset = 0x64, .size = 8, .activate = 4 }, + + [SCARLETT2_CONFIG_AUTOGAIN_SWITCH] = { + .offset = 0x146, .size = 8, .activate = 10, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AUTOGAIN_STATUS] = { + .offset = 0x148, .size = 8 }, + + [SCARLETT2_CONFIG_AG_MEAN_TARGET] = { + .offset = 0x142, .size = 8, .activate = 23, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AG_PEAK_TARGET] = { + .offset = 0x143, .size = 8, .activate = 24, .pbuf = 1 }, + + [SCARLETT2_CONFIG_PHANTOM_SWITCH] = { + .offset = 0x62, .size = 8, .activate = 11, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_INPUT_GAIN] = { + .offset = 0x66, .size = 8, .activate = 12, .pbuf = 1 }, + + [SCARLETT2_CONFIG_LEVEL_SWITCH] = { + .offset = 0x56, .size = 8, .activate = 13, .pbuf = 1, + .mute = 1 }, + + [SCARLETT2_CONFIG_SAFE_SWITCH] = { + .offset = 0x158, .size = 8, .activate = 14, .pbuf = 1 }, + + [SCARLETT2_CONFIG_AIR_SWITCH] = { + .offset = 0x58, .size = 8, .activate = 15, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_SELECT_SWITCH] = { + .offset = 0x15b, .size = 8, .activate = 16, .pbuf = 1 }, + + [SCARLETT2_CONFIG_INPUT_LINK_SWITCH] = { + .offset = 0x15e, .size = 8, .activate = 17, .pbuf = 1 }, + + [SCARLETT2_CONFIG_MASTER_VOLUME] = { + .offset = 0x3a, .size = 16 }, + + [SCARLETT2_CONFIG_HEADPHONE_VOLUME] = { + .offset = 0x42, .size = 16 }, + + [SCARLETT2_CONFIG_POWER_EXT] = { + .offset = 0x170, .size = 8 }, + + [SCARLETT2_CONFIG_POWER_LOW] = { + .offset = 0x177, .size = 8 }, + + [SCARLETT2_CONFIG_FP_BRIGHTNESS] = { + .offset = 0x3cd, .size = 8, .activate = 36, .pbuf = 1 }, + + [SCARLETT2_CONFIG_FP_SLEEP_TIME] = { + .offset = 0x3d0, .size = 32, .activate = 38 } + } +}; + /* Clarett USB and Clarett+ devices: 2Pre, 4Pre, 8Pre */ static const struct scarlett2_config_set scarlett2_config_set_clarett = { .notifications = scarlett2_notifications, @@ -2113,6 +2367,7 @@ static const struct scarlett2_device_info solo_gen4_info = { .config_sets = (const struct scarlett2_config_set_entry[]) { { 2115, &scarlett2_config_set_gen4_solo }, { 2417, &scarlett2_config_set_gen4_solo_2417 }, + { 2449, &scarlett2_config_set_gen4_solo_2449 }, { } }, .has_devmap = 1, @@ -2171,6 +2426,7 @@ static const struct scarlett2_device_info s2i2_gen4_info = { .config_sets = (const struct scarlett2_config_set_entry[]) { { 2115, &scarlett2_config_set_gen4_2i2 }, { 2417, &scarlett2_config_set_gen4_2i2_2417 }, + { 2449, &scarlett2_config_set_gen4_2i2_2449 }, { } }, .has_devmap = 1, @@ -2229,6 +2485,8 @@ static const struct scarlett2_device_info s4i4_gen4_info = { .config_sets = (const struct scarlett2_config_set_entry[]) { { 2089, &scarlett2_config_set_gen4_4i4 }, { 2417, &scarlett2_config_set_gen4_4i4_2417 }, + { 2432, &scarlett2_config_set_gen4_4i4_2432 }, + { 2449, &scarlett2_config_set_gen4_4i4_2449 }, { } }, .has_devmap = 1, @@ -8515,6 +8773,15 @@ static void scarlett2_notify_pcm_input_switch(struct usb_mixer_interface *mixer) scarlett2_notify_mux(mixer); } +/* Notify on input select or PCM input switch change; 2i2 Gen 4 + * firmware 2449 uses one notification for both + */ +static void scarlett2_notify_select_pcm_in(struct usb_mixer_interface *mixer) +{ + scarlett2_notify_input_select(mixer); + scarlett2_notify_pcm_input_switch(mixer); +} + /* Notify on Bluetooth change */ static void scarlett2_notify_bluetooth(struct usb_mixer_interface *mixer) { From 4dd75414525b3c991e48a5df0e442800a2cefbc9 Mon Sep 17 00:00:00 2001 From: Davide Gironi Date: Wed, 28 Oct 2026 19:24:09 +0100 Subject: [PATCH 072/186] ALSA: ice1712: add a board-specific EEPROM initialization hook Add an eeprom_init callback to card info and call it after reading the EEPROM but before generic chip setup. Move the existing DMX6Fire fixup into the EWS driver. Assisted-by: Pi:gpt-6-luna Signed-off-by: Davide Gironi Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261028183155.787599-2-davide.gironi@gmail.com --- sound/pci/ice1712/ews.c | 14 ++++++++++++++ sound/pci/ice1712/ice1712.c | 31 ++++++++++++++++++++++--------- sound/pci/ice1712/ice1712.h | 1 + 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c index 26f5d5528e27f3..d7c1e5430d96b0 100644 --- a/sound/pci/ice1712/ews.c +++ b/sound/pci/ice1712/ews.c @@ -1012,6 +1012,19 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice) } +static int snd_ice1712_ews_eeprom_init(struct snd_ice1712 *ice) +{ + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE && + !ice->dxr_enable) { + /* Set EEPROM value to limit active ADCs and DACs to 6; + * also disable AC'97 as no hardware in standard 6fire card/box. + * DXR extensions are not currently supported. + */ + ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; + } + return 0; +} + /* entry point */ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { { @@ -1060,6 +1073,7 @@ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { .subvendor = ICE1712_SUBDEVICE_DMX6FIRE, .name = "TerraTec DMX6Fire", .model = "dmx6fire", + .eeprom_init = snd_ice1712_ews_eeprom_init, .chip_init = snd_ice1712_ews_init, .build_controls = snd_ice1712_ews_add_controls, .mpu401_1_name = "MIDI-Front DMX6fire", diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index 1fad8c418132c9..5481720a17ed6d 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2283,19 +2283,28 @@ static int snd_ice1712_read_eeprom(struct snd_ice1712 *ice, +static int snd_ice1712_init_eeprom(struct snd_ice1712 *ice) +{ + const struct snd_ice1712_card_info * const *tbl, *c; + + for (tbl = card_tables; *tbl; tbl++) { + for (c = *tbl; c->subvendor; c++) { + if (c->subvendor != ice->eeprom.subvendor) + continue; + if (c->eeprom_init) + return c->eeprom_init(ice); + return 0; + } + } + return 0; +} + static int snd_ice1712_chip_init(struct snd_ice1712 *ice) { outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL)); udelay(200); outb(ICE1712_NATIVE, ICEREG(ice, CONTROL)); udelay(200); - if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE && - !ice->dxr_enable) - /* Set eeprom value to limit active ADCs and DACs to 6; - * Also disable AC97 as no hardware in standard 6fire card/box - * Note: DXR extensions are not currently supported - */ - ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; pci_write_config_byte(ice->pci, 0x60, ice->eeprom.data[ICE_EEP1_CODEC]); pci_write_config_byte(ice->pci, 0x61, ice->eeprom.data[ICE_EEP1_ACLINK]); pci_write_config_byte(ice->pci, 0x62, ice->eeprom.data[ICE_EEP1_I2SID]); @@ -2502,8 +2511,12 @@ static int snd_ice1712_create(struct snd_card *card, card->sync_irq = ice->irq; card->private_free = snd_ice1712_free; - if (snd_ice1712_read_eeprom(ice, modelname) < 0) - return -EIO; + err = snd_ice1712_read_eeprom(ice, modelname); + if (err < 0) + return err; + err = snd_ice1712_init_eeprom(ice); + if (err < 0) + return err; if (snd_ice1712_chip_init(ice) < 0) return -EIO; diff --git a/sound/pci/ice1712/ice1712.h b/sound/pci/ice1712/ice1712.h index cd02710d827153..6e9c13b9a5282d 100644 --- a/sound/pci/ice1712/ice1712.h +++ b/sound/pci/ice1712/ice1712.h @@ -509,6 +509,7 @@ struct snd_ice1712_card_info { const char *name; const char *model; const char *driver; + int (*eeprom_init)(struct snd_ice1712 *ice); int (*chip_init)(struct snd_ice1712 *); void (*chip_exit)(struct snd_ice1712 *); int (*build_controls)(struct snd_ice1712 *); From cdbe81f0b1509777cc9ed3140a7136d6c49b53f3 Mon Sep 17 00:00:00 2001 From: Davide Gironi Date: Wed, 28 Oct 2026 19:24:10 +0100 Subject: [PATCH 073/186] ALSA: ice1712: add TerraTec PHASE 88 support Handle PHASE 88-specific expander, EEPROM, control, and MIDI behavior in the EWS driver. Use a distinct TS88 model name and preserve the expander state when a write fails. Assisted-by: Pi:gpt-6-luna Signed-off-by: Davide Gironi Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261028183155.787599-3-davide.gironi@gmail.com --- sound/pci/ice1712/ews.c | 141 +++++++++++++++++++++++++++++++++++++--- sound/pci/ice1712/ews.h | 7 ++ 2 files changed, 138 insertions(+), 10 deletions(-) diff --git a/sound/pci/ice1712/ews.c b/sound/pci/ice1712/ews.c index d7c1e5430d96b0..e3c88d19017088 100644 --- a/sound/pci/ice1712/ews.c +++ b/sound/pci/ice1712/ews.c @@ -32,6 +32,8 @@ enum { /* additional i2c devices for EWS boards */ struct ews_spec { struct snd_i2c_device *i2cdevs[3]; + unsigned char pcf8575_data; /* PHASE 88: shadow of the chip select byte */ + unsigned char pcf8575_input; /* PHASE 88: shadow of the input select byte */ }; /* @@ -128,12 +130,94 @@ static struct snd_i2c_bit_ops snd_ice1712_ewx_cs8427_bit_ops = { * AK4524 access */ +/* The PHASE 88 chip-select expander is write-only; keep a shadow value. */ +/* The bit-banged bus takes gpio_mutex internally; don't hold it here. */ +static int snd_ice1712_phase88_write(struct snd_ice1712 *ice, + unsigned char page, unsigned char value) +{ + struct ews_spec *spec = ice->spec; + unsigned char bytes[2]; + int err; + + bytes[0] = page; + bytes[1] = value; + snd_i2c_lock(ice->i2c); + err = snd_i2c_sendbytes(spec->i2cdevs[EWS_I2C_PCF2], bytes, 2); + snd_i2c_unlock(ice->i2c); + if (err != 2) { + dev_err(ice->card->dev, + "PHASE 88 expander write failed (page 0x%02x, addr 0x%02x, err %d)\n", + page, spec->i2cdevs[EWS_I2C_PCF2]->addr, err); + return -EIO; + } + return 0; +} + +static int snd_ice1712_phase88_chip_select(struct snd_ice1712 *ice, int chip_mask) +{ + struct ews_spec *spec = ice->spec; + unsigned char data; + int err; + + if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f)) + return -EINVAL; + data = (spec->pcf8575_data & 0xf0) | chip_mask; + err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_OUTPUT_PAGE, + data); + if (err >= 0) + spec->pcf8575_data = data; + return err; +} + +/* PHASE 88 Line/Mic bits are ordered 7/8 to 1/2 (0x10 to 0x80). */ +static int snd_ice1712_phase88_linemic_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + + ucontrol->value.integer.value[0] = + !!(spec->pcf8575_input & (0x80 >> kcontrol->private_value)); + return 0; +} + +static int snd_ice1712_phase88_linemic_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_ice1712 *ice = snd_kcontrol_chip(kcontrol); + struct ews_spec *spec = ice->spec; + unsigned char bit = 0x80 >> kcontrol->private_value; + unsigned char val = ucontrol->value.integer.value[0] ? + spec->pcf8575_input | bit : spec->pcf8575_input & ~bit; + int err; + + if (val == spec->pcf8575_input) + return 0; + err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_INPUT_PAGE, val); + if (err < 0) + return err; + spec->pcf8575_input = val; + return 1; +} + +static const struct snd_kcontrol_new snd_ice1712_phase88_linemic_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line/Mic 7/8 Switch", + .info = snd_ctl_boolean_mono_info, + .get = snd_ice1712_phase88_linemic_get, + .put = snd_ice1712_phase88_linemic_put, + .private_value = 3, /* bit 0x10: the pair nothing else drives */ +}; + /* AK4524 chip select; address 0x48 bit 0-3 */ static int snd_ice1712_ews88mt_chip_select(struct snd_ice1712 *ice, int chip_mask) { struct ews_spec *spec = ice->spec; unsigned char data, ndata; + if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_PHASE88) + return snd_ice1712_phase88_chip_select(ice, chip_mask); + if (snd_BUG_ON(chip_mask < 0 || chip_mask > 0x0f)) return -EINVAL; snd_i2c_lock(ice->i2c); @@ -458,9 +542,31 @@ static int snd_ice1712_ews_init(struct snd_ice1712 *ice) } snd_ice1712_6fire_write_pca(ice, PCF9554_REG_CONFIG, 0x80); break; + case ICE1712_SUBDEVICE_PHASE88: + err = snd_i2c_device_create(ice->i2c, "CS8404", + ICE1712_EWS88MT_CS8404_ADDR, + &spec->i2cdevs[EWS_I2C_CS8404]); + if (err < 0) + return err; + err = snd_i2c_device_create(ice->i2c, "Output expander", + ICE1712_PHASE88_OUTPUT_ADDR, + &spec->i2cdevs[EWS_I2C_PCF2]); + if (err < 0) + return err; + spec->pcf8575_data = ICE1712_PHASE88_OUTPUT_INIT; + /* Check if the chip select expander is present */ + err = snd_ice1712_ews88mt_chip_select(ice, 0x0f); + if (err < 0) + return err; + /* Clear the input register before codec setup. */ + spec->pcf8575_input = ICE1712_PHASE88_INPUT_INIT; + err = snd_ice1712_phase88_write(ice, ICE1712_PHASE88_INPUT_PAGE, + spec->pcf8575_input); + if (err < 0) + return err; + break; case ICE1712_SUBDEVICE_EWS88MT: case ICE1712_SUBDEVICE_EWS88MT_NEW: - case ICE1712_SUBDEVICE_PHASE88: case ICE1712_SUBDEVICE_TS88: err = snd_i2c_device_create(ice->i2c, "CS8404", @@ -982,9 +1088,15 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice) return err; } break; + case ICE1712_SUBDEVICE_PHASE88: + /* The PHASE 88 has no EWS88MT sensitivity controls. */ + err = snd_ctl_add(ice->card, + snd_ctl_new1(&snd_ice1712_phase88_linemic_ctl, ice)); + if (err < 0) + return err; + break; case ICE1712_SUBDEVICE_EWS88MT: case ICE1712_SUBDEVICE_EWS88MT_NEW: - case ICE1712_SUBDEVICE_PHASE88: case ICE1712_SUBDEVICE_TS88: err = snd_ctl_add(ice->card, snd_ctl_new1(&snd_ice1712_ews88mt_input_sense, ice)); if (err < 0) @@ -1014,13 +1126,21 @@ static int snd_ice1712_ews_add_controls(struct snd_ice1712 *ice) static int snd_ice1712_ews_eeprom_init(struct snd_ice1712 *ice) { - if (ice->eeprom.subvendor == ICE1712_SUBDEVICE_DMX6FIRE && - !ice->dxr_enable) { - /* Set EEPROM value to limit active ADCs and DACs to 6; - * also disable AC'97 as no hardware in standard 6fire card/box. - * DXR extensions are not currently supported. - */ - ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; + switch (ice->eeprom.subvendor) { + case ICE1712_SUBDEVICE_DMX6FIRE: + if (!ice->dxr_enable) { + /* Limit active ADCs and DACs to 6, and disable AC'97 + * as the standard 6fire card/box has no hardware for it. + */ + ice->eeprom.data[ICE_EEP1_CODEC] = 0x3a; + } + break; + case ICE1712_SUBDEVICE_PHASE88: + /* No consumer AC'97 codec on PHASE 88. */ + ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_NO_CON_AC97; + /* Enable the rear MIDI UART. */ + ice->eeprom.data[ICE_EEP1_CODEC] |= ICE1712_CFG_2xMPU401; + break; } return 0; } @@ -1052,13 +1172,14 @@ struct snd_ice1712_card_info snd_ice1712_ews_cards[] = { .subvendor = ICE1712_SUBDEVICE_PHASE88, .name = "TerraTec Phase88", .model = "phase88", + .eeprom_init = snd_ice1712_ews_eeprom_init, .chip_init = snd_ice1712_ews_init, .build_controls = snd_ice1712_ews_add_controls, }, { .subvendor = ICE1712_SUBDEVICE_TS88, .name = "terrasoniq TS88", - .model = "phase88", + .model = "ts88", .chip_init = snd_ice1712_ews_init, .build_controls = snd_ice1712_ews_add_controls, }, diff --git a/sound/pci/ice1712/ews.h b/sound/pci/ice1712/ews.h index aec8dc69a9eadf..064675adf80191 100644 --- a/sound/pci/ice1712/ews.h +++ b/sound/pci/ice1712/ews.h @@ -58,6 +58,13 @@ extern struct snd_ice1712_card_info snd_ice1712_ews_cards[]; #define ICE1712_EWS88MT_OUTPUT_SENSE 0x40 /* mask */ #define ICE1712_EWS88D_PCF_ADDR (0x40>>1) +/* TerraTec PHASE 88 configuration definitions */ +#define ICE1712_PHASE88_OUTPUT_ADDR (0x42>>1) +#define ICE1712_PHASE88_OUTPUT_PAGE 0x01 /* output register */ +#define ICE1712_PHASE88_OUTPUT_INIT 0x20 /* initial output state */ +#define ICE1712_PHASE88_INPUT_PAGE 0x03 /* input register */ +#define ICE1712_PHASE88_INPUT_INIT 0x00 /* line inputs */ + /* TerraTec DMX 6Fire configuration definitions */ #define ICE1712_6FIRE_AK4524_CS_MASK 0x07 /* AK4524 chip select #1-#3 */ #define ICE1712_6FIRE_RW 0x08 /* read/write switch for i2c; high = write */ From 3afeddc22974a57beb1fed57e3a0ea510f8ba8a9 Mon Sep 17 00:00:00 2001 From: Ingyu Jang Date: Mon, 2 Nov 2026 02:21:30 +0900 Subject: [PATCH 074/186] ALSA: hda: Make snd_hda_multi_out_analog_{prepare,cleanup}() return void snd_hda_multi_out_analog_prepare() and snd_hda_multi_out_analog_cleanup() always return 0, so the error checks on their return values in playback_pcm_prepare() and playback_pcm_cleanup() never trigger. Make both functions return void, and call the playback hooks unconditionally. Signed-off-by: Ingyu Jang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261101172134.3451066-2-ingyujang25@korea.ac.kr --- sound/hda/codecs/generic.c | 22 +++++++++------------- sound/hda/common/codec.c | 16 +++++++--------- sound/hda/common/hda_local.h | 14 +++++++------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c index 6120f797b45a54..ca780b7f688f6f 100644 --- a/sound/hda/codecs/generic.c +++ b/sound/hda/codecs/generic.c @@ -5324,14 +5324,12 @@ static int playback_pcm_prepare(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream) { struct hda_gen_spec *spec = codec->spec; - int err; - err = snd_hda_multi_out_analog_prepare(codec, &spec->multiout, - stream_tag, format, substream); - if (!err) - call_pcm_playback_hook(hinfo, codec, substream, - HDA_GEN_PCM_ACT_PREPARE); - return err; + snd_hda_multi_out_analog_prepare(codec, &spec->multiout, + stream_tag, format, substream); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_PREPARE); + return 0; } static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo, @@ -5339,13 +5337,11 @@ static int playback_pcm_cleanup(struct hda_pcm_stream *hinfo, struct snd_pcm_substream *substream) { struct hda_gen_spec *spec = codec->spec; - int err; - err = snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); - if (!err) - call_pcm_playback_hook(hinfo, codec, substream, - HDA_GEN_PCM_ACT_CLEANUP); - return err; + snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); + call_pcm_playback_hook(hinfo, codec, substream, + HDA_GEN_PCM_ACT_CLEANUP); + return 0; } static int playback_pcm_close(struct hda_pcm_stream *hinfo, diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index e367cc1c6e78f2..51189e305ece67 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -3798,11 +3798,11 @@ EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_open); * Set up the i/o for analog out. * When the digital out is available, copy the front out to digital out, too. */ -int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, - struct hda_multi_out *mout, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +void snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) { const hda_nid_t *nids = mout->dac_nids; int chs = substream->runtime->channels; @@ -3865,7 +3865,6 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, stream_tag, ch, format); } - return 0; } EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare); @@ -3874,8 +3873,8 @@ EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_prepare); * @codec: the HDA codec * @mout: hda_multi_out object */ -int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, - struct hda_multi_out *mout) +void snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout) { const hda_nid_t *nids = mout->dac_nids; int i; @@ -3897,7 +3896,6 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, cleanup_dig_out_stream(codec, mout->dig_out_nid); mout->dig_out_used = 0; } - return 0; } EXPORT_SYMBOL_GPL(snd_hda_multi_out_analog_cleanup); diff --git a/sound/hda/common/hda_local.h b/sound/hda/common/hda_local.h index 947a3152fdb530..8bd4497a6b0909 100644 --- a/sound/hda/common/hda_local.h +++ b/sound/hda/common/hda_local.h @@ -241,13 +241,13 @@ int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_multi_out *mout, struct snd_pcm_substream *substream, struct hda_pcm_stream *hinfo); -int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, - struct hda_multi_out *mout, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream); -int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, - struct hda_multi_out *mout); +void snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream); +void snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout); /* * generic proc interface From 5c54e6df663c28c791ea0462c0d75e9508e7185f Mon Sep 17 00:00:00 2001 From: Ingyu Jang Date: Mon, 2 Nov 2026 02:21:32 +0900 Subject: [PATCH 075/186] ALSA: cs4236: Make snd_cs423x_pnp_init_mpu() return void snd_cs423x_pnp_init_mpu() handles a pnp_activate_dev() failure itself by falling back to SNDRV_AUTO_PORT and always returns 0, so the error check in snd_card_cs423x_pnp() never triggers. Change the function's return type to void and remove the dead check. Signed-off-by: Ingyu Jang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261101172134.3451066-4-ingyujang25@korea.ac.kr --- sound/isa/cs423x/cs4236.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c index ecce31b348b021..167a198fa0fb9c 100644 --- a/sound/isa/cs423x/cs4236.c +++ b/sound/isa/cs423x/cs4236.c @@ -236,7 +236,7 @@ static int snd_cs423x_pnp_init_ctrl(int dev, struct pnp_dev *pdev) } /* MPU initialization */ -static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) +static void snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) { if (pnp_activate_dev(pdev) < 0) { dev_err(&pdev->dev, IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n"); @@ -253,7 +253,6 @@ static int snd_cs423x_pnp_init_mpu(int dev, struct pnp_dev *pdev) } } dev_dbg(&pdev->dev, "isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]); - return 0; } static int snd_card_cs423x_pnp(int dev, struct snd_card_cs4236 *acard, @@ -296,10 +295,8 @@ static int snd_card_cs423x_pnpc(int dev, struct snd_card_cs4236 *acard, return -EBUSY; } /* MPU initialization */ - if (acard->mpu && mpu_port[dev] > 0) { - if (snd_cs423x_pnp_init_mpu(dev, acard->mpu) < 0) - return -EBUSY; - } + if (acard->mpu && mpu_port[dev] > 0) + snd_cs423x_pnp_init_mpu(dev, acard->mpu); return 0; } #endif /* CONFIG_PNP */ From 5ff4fd858813264d8465bc544995a756894080b5 Mon Sep 17 00:00:00 2001 From: Ingyu Jang Date: Mon, 2 Nov 2026 02:21:33 +0900 Subject: [PATCH 076/186] ALSA: vx222: Make put_xilinx_data() return void put_xilinx_data() always returns 0, so the error check on its return value in vx2_load_xilinx_binary() never triggers. Change the function's return type to void and remove the dead check. Signed-off-by: Ingyu Jang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261101172134.3451066-5-ingyujang25@korea.ac.kr --- sound/pci/vx222/vx222_ops.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sound/pci/vx222/vx222_ops.c b/sound/pci/vx222/vx222_ops.c index 40f3716c478f49..348666b422f5c6 100644 --- a/sound/pci/vx222/vx222_ops.c +++ b/sound/pci/vx222/vx222_ops.c @@ -321,7 +321,8 @@ static void vx2_dma_read(struct vx_core *chip, struct snd_pcm_runtime *runtime, /* * transfer counts bits to PLX */ -static int put_xilinx_data(struct vx_core *chip, unsigned int port, unsigned int counts, unsigned char data) +static void put_xilinx_data(struct vx_core *chip, unsigned int port, + unsigned int counts, unsigned char data) { unsigned int i; @@ -347,7 +348,6 @@ static int put_xilinx_data(struct vx_core *chip, unsigned int port, unsigned int vx2_inl(chip, port); udelay(1); } - return 0; } /* @@ -374,8 +374,7 @@ static int vx2_load_xilinx_binary(struct vx_core *chip, const struct firmware *x image = xilinx->data; for (i = 0; i < xilinx->size; i++, image++) { - if (put_xilinx_data(chip, port, 8, *image) < 0) - return -EINVAL; + put_xilinx_data(chip, port, 8, *image); /* don't take too much time in this loop... */ cond_resched(); } From 9f4237359691a8fd23ed20825e48e518e9d384a8 Mon Sep 17 00:00:00 2001 From: Ingyu Jang Date: Mon, 2 Nov 2026 02:21:34 +0900 Subject: [PATCH 077/186] ALSA: ali5451: Make snd_ali_reset_5451() return void snd_ali_reset_5451() always returns 0, as a reset timeout is treated as non-fatal, so the error check in snd_ali_chip_init() never triggers. Change the function's return type to void and remove the dead check. Signed-off-by: Ingyu Jang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261101172134.3451066-6-ingyujang25@korea.ac.kr --- sound/pci/ali5451/ali5451.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/sound/pci/ali5451/ali5451.c b/sound/pci/ali5451/ali5451.c index 8295eee9b977f9..b86b9212964271 100644 --- a/sound/pci/ali5451/ali5451.c +++ b/sound/pci/ali5451/ali5451.c @@ -414,7 +414,7 @@ static unsigned short snd_ali_codec_read(struct snd_ac97 *ac97, * AC97 Reset */ -static int snd_ali_reset_5451(struct snd_ali *codec) +static void snd_ali_reset_5451(struct snd_ali *codec) { struct pci_dev *pci_dev; unsigned short wCount, wReg; @@ -442,13 +442,12 @@ static int snd_ali_reset_5451(struct snd_ali *codec) while(wCount--) { wReg = snd_ali_codec_peek(codec, 0, AC97_POWERDOWN); if ((wReg & 0x000f) == 0x000f) - return 0; + return; mdelay(5); } /* non-fatal if you have a non PM capable codec */ /* dev_warn(codec->card->dev, "ali5451: reset time out\n"); */ - return 0; } /* @@ -1901,10 +1900,7 @@ static int snd_ali_chip_init(struct snd_ali *codec) dev_dbg(codec->card->dev, "chip initializing ...\n"); - if (snd_ali_reset_5451(codec)) { - dev_err(codec->card->dev, "ali_chip_init: reset 5451 error.\n"); - return -1; - } + snd_ali_reset_5451(codec); if (codec->revision == ALI_5451_V02) { pci_dev = codec->pci_m1533; From dcc62c6452b90010e67dbb0bb4e6a4898c1a6fc4 Mon Sep 17 00:00:00 2001 From: Ingyu Jang Date: Mon, 2 Nov 2026 18:38:24 +0900 Subject: [PATCH 078/186] ALSA: ice1712: Make chip_init helpers return void snd_ice1712_chip_init() and snd_vt1724_chip_init() always return 0, so the error checks on their return values in the probe and resume paths never trigger. Change both functions to return void and remove the dead checks. Signed-off-by: Ingyu Jang Link: https://patch.msgid.link/20261102093824.3123528-1-ingyujang25@korea.ac.kr Signed-off-by: Takashi Iwai --- sound/pci/ice1712/ice1712.c | 12 +++--------- sound/pci/ice1712/ice1724.c | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/sound/pci/ice1712/ice1712.c b/sound/pci/ice1712/ice1712.c index 5481720a17ed6d..2d4c9bdfb62f2a 100644 --- a/sound/pci/ice1712/ice1712.c +++ b/sound/pci/ice1712/ice1712.c @@ -2299,7 +2299,7 @@ static int snd_ice1712_init_eeprom(struct snd_ice1712 *ice) return 0; } -static int snd_ice1712_chip_init(struct snd_ice1712 *ice) +static void snd_ice1712_chip_init(struct snd_ice1712 *ice) { outb(ICE1712_RESET | ICE1712_NATIVE, ICEREG(ice, CONTROL)); udelay(200); @@ -2343,8 +2343,6 @@ static int snd_ice1712_chip_init(struct snd_ice1712 *ice) ICE1712_IRQ_PBKDS | ICE1712_IRQ_CONCAP | ICE1712_IRQ_CONPBK : 0), ICEREG(ice, IRQMASK)); outb(0x00, ICEMT(ice, IRQ)); - - return 0; } int snd_ice1712_spdif_build_controls(struct snd_ice1712 *ice) @@ -2517,8 +2515,7 @@ static int snd_ice1712_create(struct snd_card *card, err = snd_ice1712_init_eeprom(ice); if (err < 0) return err; - if (snd_ice1712_chip_init(ice) < 0) - return -EIO; + snd_ice1712_chip_init(ice); return 0; } @@ -2696,10 +2693,7 @@ static int snd_ice1712_resume(struct device *dev) else rate = PRO_RATE_DEFAULT; - if (snd_ice1712_chip_init(ice) < 0) { - snd_card_disconnect(card); - return -EIO; - } + snd_ice1712_chip_init(ice); ice->cur_rate = rate; diff --git a/sound/pci/ice1712/ice1724.c b/sound/pci/ice1712/ice1724.c index 651d9266356751..d4ed7050c533ae 100644 --- a/sound/pci/ice1712/ice1724.c +++ b/sound/pci/ice1712/ice1724.c @@ -2342,7 +2342,7 @@ static void snd_vt1724_chip_reset(struct snd_ice1712 *ice) msleep(10); } -static int snd_vt1724_chip_init(struct snd_ice1712 *ice) +static void snd_vt1724_chip_init(struct snd_ice1712 *ice) { outb(ice->eeprom.data[ICE_EEP2_SYSCONF], ICEREG1724(ice, SYS_CFG)); outb(ice->eeprom.data[ICE_EEP2_ACLINK], ICEREG1724(ice, AC97_CFG)); @@ -2364,8 +2364,6 @@ static int snd_vt1724_chip_init(struct snd_ice1712 *ice) * since they cause machine lockups */ outb(VT1724_MULTI_FIFO_ERR, ICEMT1724(ice, DMA_INT_MASK)); - - return 0; } static int snd_vt1724_spdif_build_controls(struct snd_ice1712 *ice) @@ -2511,8 +2509,7 @@ static int snd_vt1724_create(struct snd_card *card, snd_vt1724_chip_reset(ice); if (snd_vt1724_read_eeprom(ice, modelname) < 0) return -EIO; - if (snd_vt1724_chip_init(ice) < 0) - return -EIO; + snd_vt1724_chip_init(ice); return 0; } @@ -2703,10 +2700,7 @@ static int snd_vt1724_resume(struct device *dev) snd_vt1724_chip_reset(ice); - if (snd_vt1724_chip_init(ice) < 0) { - snd_card_disconnect(card); - return -EIO; - } + snd_vt1724_chip_init(ice); if (ice->pm_resume) ice->pm_resume(ice); From 553e8de72bf9f4c9da15d8f17db8044807fb0409 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Mon, 2 Nov 2026 14:16:47 +0000 Subject: [PATCH 079/186] ALSA: pcm: Fix source frame count calculation Pass the updated frame count to the "src_frames()" callback. When the next plugin handles fewer frames than requested, use the actual frame count instead of the original requested count. This ensures the correct number of source frames is calculated. [ Note: this code path isn't actually reached by the current code, as all client_channels callbacks return the given frame as-is without modification. So this change is only for hypothetical issues for now, not really fixing any actual problem. ] Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261102141647.3133-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/oss/pcm_plugin.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/oss/pcm_plugin.c b/sound/core/oss/pcm_plugin.c index acf5ca663ba8ca..3de71377999367 100644 --- a/sound/core/oss/pcm_plugin.c +++ b/sound/core/oss/pcm_plugin.c @@ -604,7 +604,7 @@ snd_pcm_sframes_t snd_pcm_plug_write_transfer(struct snd_pcm_substream *plug, st if (err != frames1) { frames = err; if (plugin->src_frames) { - frames = plugin->src_frames(plugin, frames1); + frames = plugin->src_frames(plugin, err); if (frames <= 0) return frames; } From afdbc45618fa46eb9e922ba6f785120818e0fccc Mon Sep 17 00:00:00 2001 From: Bob Song Date: Wed, 4 Nov 2026 15:56:42 +0800 Subject: [PATCH 080/186] ALSA: mixer: oss: Fix uninitialized index in recsrc ioctls snd_mixer_oss_get_recsrc2() returns success without setting active_index when the current Capture Source does not match any OSS mixer slot. snd_mixer_oss_get_recsrc() then uses that uninitialized index for "1 << index", and snd_mixer_oss_set_recsrc() does the same while ignoring the callback's return value. Make get_recsrc2() return -ENOENT if no slot matches, only use the returned index in set_recsrc() when the lookup succeeds, and initialize the index variables as a defensive measure. Signed-off-by: Bob Song Link: https://patch.msgid.link/20261104075642.1790966-1-songxiebing@kylinos.cn Signed-off-by: Takashi Iwai --- sound/core/oss/mixer_oss.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sound/core/oss/mixer_oss.c b/sound/core/oss/mixer_oss.c index a51bc68473e63e..de17a132a6a55c 100644 --- a/sound/core/oss/mixer_oss.c +++ b/sound/core/oss/mixer_oss.c @@ -185,7 +185,7 @@ static int snd_mixer_oss_get_recsrc(struct snd_mixer_oss_file *fmixer) return -EIO; guard(mutex)(&mixer->reg_mutex); if (mixer->put_recsrc && mixer->get_recsrc) { /* exclusive */ - unsigned int index; + unsigned int index = 0; result = mixer->get_recsrc(fmixer, &index); if (result < 0) return result; @@ -212,7 +212,7 @@ static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsr struct snd_mixer_oss *mixer = fmixer->mixer; struct snd_mixer_oss_slot *pslot; int chn, active; - unsigned int index; + unsigned int index = 0; int result = 0; if (mixer == NULL) @@ -222,8 +222,8 @@ static int snd_mixer_oss_set_recsrc(struct snd_mixer_oss_file *fmixer, int recsr if (recsrc & ~mixer->oss_recsrc) recsrc &= ~mixer->oss_recsrc; mixer->put_recsrc(fmixer, ffz(~recsrc)); - mixer->get_recsrc(fmixer, &index); - result = 1 << index; + if (mixer->get_recsrc(fmixer, &index) == 0) + result = 1 << index; } for (chn = 0; chn < 31; chn++) { pslot = &mixer->slots[chn]; @@ -826,6 +826,8 @@ static int snd_mixer_oss_get_recsrc2(struct snd_mixer_oss_file *fmixer, unsigned break; } } + if (idx == 32) + return -ENOENT; return 0; } From 449fbf2b404980ffd304aec6d60c2f7440f1711e Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Wed, 4 Nov 2026 16:33:04 +0800 Subject: [PATCH 081/186] ALSA: cs8427: read the PCM stream IEC958 status from its own buffer The IEC958 Playback PCM Stream control writes pcm_status, but its get callback always returns def_status. As a result, userspace reads back the default status instead of the PCM stream status after writing the stream control. Rate changes also update pcm_status without being reflected in the value returned by the control. Select the status buffer using the same private_value as the put callback. The default control continues to return def_status, while the PCM stream control now returns its own current status. Signed-off-by: Xu Rao Link: https://patch.msgid.link/7A8A7F3C30522F89+20261104083304.4000520-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/i2c/cs8427.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/i2c/cs8427.c b/sound/i2c/cs8427.c index 1de985ba85d9bf..773383471f17f3 100644 --- a/sound/i2c/cs8427.c +++ b/sound/i2c/cs8427.c @@ -424,9 +424,11 @@ static int snd_cs8427_spdif_get(struct snd_kcontrol *kcontrol, { struct snd_i2c_device *device = snd_kcontrol_chip(kcontrol); struct cs8427 *chip = device->private_data; + const char *status = kcontrol->private_value ? + chip->playback.pcm_status : chip->playback.def_status; snd_i2c_lock(device->bus); - memcpy(ucontrol->value.iec958.status, chip->playback.def_status, 24); + memcpy(ucontrol->value.iec958.status, status, 24); snd_i2c_unlock(device->bus); return 0; } From dddc85c161758963d7d9aced4882e3cd6f2e8c61 Mon Sep 17 00:00:00 2001 From: Joseph Telaak Date: Mon, 2 Nov 2026 18:55:23 -0500 Subject: [PATCH 082/186] ALSA: hda/generic: Add indep_dig_outs hint The generic parser gives a codec one digital playback PCM. Any further digital output pin is set up as a follower of the first: it gets the same stream tag, so every digital jack plays the same stream. That is the right default for a codec with, say, a coax and an optical jack. Some boards wire two digital converters to different places instead. On the Control4 HC-800, an ALC888 has converter 0x10 driving pin 0x11, which feeds an HDMI transmitter, and converter 0x06 driving pin 0x1e, the S/PDIF coax jack. These are two outputs that should play different streams at the same time. The follower setup cannot express that, and userspace cannot split one stream tag in two. Add an "indep_dig_outs" hint. Like indep_hp does for the headphone DAC, it gives the second digital output pin its own stream: its own hda_multi_out and its own PCM, "Alt Digital", typed from its pin and with its own IEC958 controls. The digital playback ops pick the multiout by the stream's NID. The analog "IEC958 Default PCM" duplication stays on the first digital output only. Unlike indep_hp, the split is fixed at parse time; there is no mixer switch. A codec driver can also set spec->indep_dig_outs itself. Without the hint or the flag nothing changes. The new PCM takes the next free device number of its type. With an HDMI-typed first output and an S/PDIF-typed second one, as on the HC-800, the HDMI output stays the "Digital" PCM (device 3) and the coax output becomes "Alt Digital" (device 1), with its IEC958 controls at index 16. Assisted-by: LLM Signed-off-by: Joseph Telaak Link: https://patch.msgid.link/20261102-indep-dig-outs-v1-1-b468f76bdd2e@telaak.dev Signed-off-by: Takashi Iwai --- Documentation/sound/hd-audio/notes.rst | 5 ++ sound/hda/codecs/generic.c | 63 ++++++++++++++++++++++---- sound/hda/codecs/generic.h | 7 ++- 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/Documentation/sound/hd-audio/notes.rst b/Documentation/sound/hd-audio/notes.rst index c16f83875b800c..c618bb2d7d3caf 100644 --- a/Documentation/sound/hd-audio/notes.rst +++ b/Documentation/sound/hd-audio/notes.rst @@ -486,6 +486,11 @@ inv_dmic_split (bool) indep_hp (bool) provide the independent headphone PCM stream and the corresponding mixer control, if available +indep_dig_outs (bool) + give the second digital output pin (in parsing order) its own PCM + stream, "Alt Digital", instead of making it follow the first one's + stream. The PCM takes the next free device number of its type, so + two S/PDIF outputs need dynamic minors. Default false add_stereo_mix_input (bool) add the stereo mix (analog-loopback mix) to the input mux if available diff --git a/sound/hda/codecs/generic.c b/sound/hda/codecs/generic.c index ca780b7f688f6f..12e40c382c16ae 100644 --- a/sound/hda/codecs/generic.c +++ b/sound/hda/codecs/generic.c @@ -168,6 +168,9 @@ static void parse_user_hints(struct hda_codec *codec) val = snd_hda_get_bool_hint(codec, "indep_hp"); if (val >= 0) spec->indep_hp = !!val; + val = snd_hda_get_bool_hint(codec, "indep_dig_outs"); + if (val >= 0) + spec->indep_dig_outs = !!val; val = snd_hda_get_bool_hint(codec, "add_stereo_mix_input"); if (val >= 0) spec->add_stereo_mix_input = !!val; @@ -4000,7 +4003,9 @@ static void parse_digital(struct hda_codec *codec) int i, nums; hda_nid_t dig_nid, pin; - /* support multiple SPDIFs; the secondary is set up as a follower */ + /* support multiple SPDIFs; the secondary is set up as a follower, + * or as an independent PCM with the indep_dig_outs hint + */ nums = 0; for (i = 0; i < spec->autocfg.dig_outs; i++) { pin = spec->autocfg.dig_out_pins[i]; @@ -4018,6 +4023,10 @@ static void parse_digital(struct hda_codec *codec) if (!nums) { spec->multiout.dig_out_nid = dig_nid; spec->dig_out_type = spec->autocfg.dig_out_type[0]; + } else if (spec->indep_dig_outs) { + /* autocfg gives at most two digital outs */ + spec->alt_dig_multiout.dig_out_nid = dig_nid; + spec->alt_dig_out_type = spec->autocfg.dig_out_type[i]; } else { spec->multiout.follower_dig_outs = spec->follower_dig_outs; if (nums >= ARRAY_SIZE(spec->follower_dig_outs) - 1) @@ -5228,6 +5237,14 @@ int snd_hda_gen_build_controls(struct hda_codec *codec) spec->multiout.share_spdif = 1; } } + if (spec->alt_dig_multiout.dig_out_nid) { + err = snd_hda_create_dig_out_ctls(codec, + spec->alt_dig_multiout.dig_out_nid, + spec->alt_dig_multiout.dig_out_nid, + spec->pcm_rec[3]->pcm_type); + if (err < 0) + return err; + } if (spec->dig_in_nid) { err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); if (err < 0) @@ -5450,12 +5467,24 @@ static int alt_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, /* * Digital out */ + +/* the independent second digital out has its own multiout */ +static struct hda_multi_out *dig_multiout(struct hda_codec *codec, + struct hda_pcm_stream *hinfo) +{ + struct hda_gen_spec *spec = codec->spec; + + if (spec->alt_dig_multiout.dig_out_nid && + hinfo->nid == spec->alt_dig_multiout.dig_out_nid) + return &spec->alt_dig_multiout; + return &spec->multiout; +} + static int dig_playback_pcm_open(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_open(codec, &spec->multiout); + return snd_hda_multi_out_dig_open(codec, dig_multiout(codec, hinfo)); } static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, @@ -5464,8 +5493,7 @@ static int dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, unsigned int format, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + return snd_hda_multi_out_dig_prepare(codec, dig_multiout(codec, hinfo), stream_tag, format, substream); } @@ -5473,16 +5501,14 @@ static int dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_cleanup(codec, &spec->multiout); + return snd_hda_multi_out_dig_cleanup(codec, dig_multiout(codec, hinfo)); } static int dig_playback_pcm_close(struct hda_pcm_stream *hinfo, struct hda_codec *codec, struct snd_pcm_substream *substream) { - struct hda_gen_spec *spec = codec->spec; - return snd_hda_multi_out_dig_close(codec, &spec->multiout); + return snd_hda_multi_out_dig_close(codec, dig_multiout(codec, hinfo)); } /* @@ -5775,6 +5801,25 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec) spec->dig_in_nid); } + /* independent second digital out, stream index #3 */ + if (spec->alt_dig_multiout.dig_out_nid) { + fill_pcm_stream_name(spec->stream_name_alt_digital, + sizeof(spec->stream_name_alt_digital), + " Alt Digital", codec->core.chip_name); + info = snd_hda_codec_pcm_new(codec, "%s", + spec->stream_name_alt_digital); + if (!info) + return -ENOMEM; + spec->pcm_rec[3] = info; + if (spec->alt_dig_out_type) + info->pcm_type = spec->alt_dig_out_type; + else + info->pcm_type = HDA_PCM_TYPE_SPDIF; + setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK], + &pcm_digital_playback, NULL, + spec->alt_dig_multiout.dig_out_nid); + } + if (spec->no_analog) return 0; diff --git a/sound/hda/codecs/generic.h b/sound/hda/codecs/generic.h index 524591821f8c98..e6971ff66ec18a 100644 --- a/sound/hda/codecs/generic.h +++ b/sound/hda/codecs/generic.h @@ -93,6 +93,7 @@ struct hda_gen_spec { const struct hda_pcm_stream *stream_analog_capture; char stream_name_alt_analog[32]; /* alternative analog PCM stream */ + char stream_name_alt_digital[32]; /* independent 2nd digital PCM */ const struct hda_pcm_stream *stream_analog_alt_playback; const struct hda_pcm_stream *stream_analog_alt_capture; @@ -112,6 +113,9 @@ struct hda_gen_spec { hda_nid_t alt_dac_nid; hda_nid_t follower_dig_outs[3]; /* optional - for auto-parsing */ int dig_out_type; + /* the 2nd digital out with the indep_dig_outs hint */ + struct hda_multi_out alt_dig_multiout; + int alt_dig_out_type; /* capture */ unsigned int num_adc_nids; @@ -148,7 +152,7 @@ struct hda_gen_spec { int const_channel_count; /* channel count for all */ /* PCM information */ - struct hda_pcm *pcm_rec[3]; /* used in build_pcms() */ + struct hda_pcm *pcm_rec[4]; /* used in build_pcms() */ /* dynamic controls, init_verbs and input_mux */ struct auto_pin_cfg autocfg; @@ -226,6 +230,7 @@ struct hda_gen_spec { unsigned int vmaster_mute_led:1; /* add SPK-LED flag to vmaster mute switch */ unsigned int mic_mute_led:1; /* add MIC-LED flag to capture mute switch */ unsigned int indep_hp:1; /* independent HP supported */ + unsigned int indep_dig_outs:1; /* 2nd digital out has its own PCM */ unsigned int prefer_hp_amp:1; /* enable HP amp for speaker if any */ unsigned int add_stereo_mix_input:2; /* add aamix as a capture src */ unsigned int add_jack_modes:1; /* add i/o jack mode enum ctls */ From 3461b38f7c4c0cbed4d797756d5dd7068e622507 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Wed, 4 Nov 2026 15:52:05 +0000 Subject: [PATCH 083/186] ALSA: core: Validate device type for dynamic minors Reject unsupported device types when allocating dynamic minors. This prevents invalid device types from being registered and avoids unnecessarily consuming available minor numbers. Return -EINVAL for unsupported device types. Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261104155205.3378-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/sound.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/core/sound.c b/sound/core/sound.c index 24f40763a20c39..32637c40df05cb 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -184,6 +184,18 @@ static int snd_find_free_minor(int type, struct snd_card *card, int dev) if (type == SNDRV_DEVICE_TYPE_TIMER) return SNDRV_MINOR_TIMER; + switch (type) { + case SNDRV_DEVICE_TYPE_CONTROL: + case SNDRV_DEVICE_TYPE_HWDEP: + case SNDRV_DEVICE_TYPE_RAWMIDI: + case SNDRV_DEVICE_TYPE_PCM_PLAYBACK: + case SNDRV_DEVICE_TYPE_PCM_CAPTURE: + case SNDRV_DEVICE_TYPE_COMPRESS: + break; + default: + return -EINVAL; + } + for (minor = 0; minor < ARRAY_SIZE(snd_minors); ++minor) { /* skip static minors still used for module auto loading */ if (SNDRV_MINOR_DEVICE(minor) == SNDRV_MINOR_CONTROL) From 12bb41f20b71cfb70061faf54f92a20d6ad6b7f7 Mon Sep 17 00:00:00 2001 From: Xu Rao Date: Thu, 5 Nov 2026 09:46:42 +0800 Subject: [PATCH 084/186] ALSA: intel8x0: keep AC97 PCM descriptors for ICH4 capture When S/PDIF uses the AC-link, spdif_idx is negative and the AC97 PCM assignment drops its last descriptor. On ICH4 this leaves five entries, but PCM2IN and MIC2 still use entries four and five. Opening MIC2 then reads beyond the allocated descriptor array. Keep all six descriptors on ICH4 so the capture stream indices stay valid. The S/PDIF descriptor occupies its fixed position but does not create a separate hardware PCM when S/PDIF uses the AC-link. Other controller types retain their existing descriptor counts. Signed-off-by: Xu Rao Link: https://patch.msgid.link/5387AE07DB9C7930+20261105014642.73877-1-raoxu@uniontech.com Signed-off-by: Takashi Iwai --- sound/pci/intel8x0.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/pci/intel8x0.c b/sound/pci/intel8x0.c index 339024182a26eb..69fe4274775000 100644 --- a/sound/pci/intel8x0.c +++ b/sound/pci/intel8x0.c @@ -2289,10 +2289,11 @@ static int snd_intel8x0_mixer(struct intel8x0 *chip, int ac97_clock, pbus->isdin = 1; /* find the available PCM streams */ i = ARRAY_SIZE(ac97_pcm_defs); - if (chip->device_type != DEVICE_INTEL_ICH4) + if (chip->device_type != DEVICE_INTEL_ICH4) { i -= 2; /* do not allocate PCM2IN and MIC2 */ - if (chip->spdif_idx < 0) - i--; /* do not allocate S/PDIF */ + if (chip->spdif_idx < 0) + i--; /* do not allocate S/PDIF */ + } err = snd_ac97_pcm_assign(pbus, i, ac97_pcm_defs); if (err < 0) goto __err; From d51f9833022b1f14afe77e6d3fb0347a20fbf360 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Thu, 5 Nov 2026 09:42:25 +0100 Subject: [PATCH 085/186] ASoC: codecs: pcm3168a: do not request the reset GPIO as non-exclusive The GPIOD_FLAGS_BIT_NONEXCLUSIVE flag is deprecated and users should rely on the GPIO shared proxy. All users of this codec driver are either ARM64 - this architecture now enables shared GPIOs automatically for everyone - or x86 and don't share pins. The driver no longer has to care about the sharing so drop the flag. Signed-off-by: Bartosz Golaszewski Reviewed-by: Cezary Rojewski Link: https://patch.msgid.link/20261105-pcm3168a-non-exclusive-gpios-v2-1-982577248ed6@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm3168a.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c index 6f45ea4869a75d..c1a263216ea6d8 100644 --- a/sound/soc/codecs/pcm3168a.c +++ b/sound/soc/codecs/pcm3168a.c @@ -725,16 +725,11 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap) dev_set_drvdata(dev, pcm3168a); /* - * Request the reset (connected to RST pin) gpio line as non exclusive - * as the same reset line might be connected to multiple pcm3168a codec - * * The RST is low active, we want the GPIO line to be high initially, so * request the initial level to LOW which in practice means DEASSERTED: * The deasserted level of GPIO_ACTIVE_LOW is HIGH. */ - pcm3168a->gpio_rst = devm_gpiod_get_optional(dev, "reset", - GPIOD_OUT_LOW | - GPIOD_FLAGS_BIT_NONEXCLUSIVE); + pcm3168a->gpio_rst = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(pcm3168a->gpio_rst)) return dev_err_probe(dev, PTR_ERR(pcm3168a->gpio_rst), "failed to acquire RST gpio\n"); From 4ab9e4ccfaf7d1c70e110e0fae94893722f22ee2 Mon Sep 17 00:00:00 2001 From: Valerio Setti Date: Wed, 4 Nov 2026 11:48:35 +0100 Subject: [PATCH 086/186] ASoC: fsl: imx-sgtl5000: release codec clock right after reading its rate The 'codec_clk' is only used in 'probe' to read its rate, which is then cached in clk_frequency and used by 'imx_sgtl5000_dai_init()'. Holding the clk until 'remove()' serves no purpose. Release the clock right after reading its rate. This allows turning 'codec_clk' into a local variable, simplifying the probe error path and dropping the 'remove()' callback, which has nothing left to do. Signed-off-by: Valerio Setti Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261104-imx-sgtl5000-cleanup-v1-1-62739fbb4a25@baylibre.com Signed-off-by: Mark Brown --- sound/soc/fsl/imx-sgtl5000.c | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/sound/soc/fsl/imx-sgtl5000.c b/sound/soc/fsl/imx-sgtl5000.c index 8ce333eb47fe6e..ac568210b4e649 100644 --- a/sound/soc/fsl/imx-sgtl5000.c +++ b/sound/soc/fsl/imx-sgtl5000.c @@ -20,7 +20,6 @@ struct imx_sgtl5000_data { struct snd_soc_card card; char codec_dai_name[DAI_NAME_SIZE]; char platform_name[DAI_NAME_SIZE]; - struct clk *codec_clk; unsigned int clk_frequency; }; @@ -54,8 +53,9 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) struct device_node *ssi_np, *codec_np; struct platform_device *ssi_pdev; struct i2c_client *codec_dev; - struct imx_sgtl5000_data *data = NULL; + struct imx_sgtl5000_data *data; struct snd_soc_dai_link_component *comp; + struct clk *codec_clk; int int_port, ext_port; int ret; @@ -129,13 +129,14 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) goto put_device; } - data->codec_clk = clk_get(&codec_dev->dev, NULL); - if (IS_ERR(data->codec_clk)) { - ret = PTR_ERR(data->codec_clk); + codec_clk = clk_get(&codec_dev->dev, NULL); + if (IS_ERR(codec_clk)) { + ret = PTR_ERR(codec_clk); goto put_device; } - data->clk_frequency = clk_get_rate(data->codec_clk); + data->clk_frequency = clk_get_rate(codec_clk); + clk_put(codec_clk); data->dai.cpus = &comp[0]; data->dai.codecs = &comp[1]; @@ -187,22 +188,12 @@ static int imx_sgtl5000_probe(struct platform_device *pdev) put_device: put_device(&codec_dev->dev); fail: - if (data && !IS_ERR(data->codec_clk)) - clk_put(data->codec_clk); of_node_put(ssi_np); of_node_put(codec_np); return ret; } -static void imx_sgtl5000_remove(struct platform_device *pdev) -{ - struct snd_soc_card *card = platform_get_drvdata(pdev); - struct imx_sgtl5000_data *data = snd_soc_card_get_drvdata(card); - - clk_put(data->codec_clk); -} - static const struct of_device_id imx_sgtl5000_dt_ids[] = { { .compatible = "fsl,imx-audio-sgtl5000", }, { /* sentinel */ } @@ -216,7 +207,6 @@ static struct platform_driver imx_sgtl5000_driver = { .of_match_table = imx_sgtl5000_dt_ids, }, .probe = imx_sgtl5000_probe, - .remove = imx_sgtl5000_remove, }; module_platform_driver(imx_sgtl5000_driver); From 82f733e54a919ef46e8ac7265413b05f9c2ab3ce Mon Sep 17 00:00:00 2001 From: Rafael Gago Date: Thu, 5 Nov 2026 10:22:50 +0100 Subject: [PATCH 087/186] ALSA: hdsp: allow sample rate changes from hw_params hdsp_set_rate() refuses to switch between single, double and quad speed whenever capture_pid or playback_pid is set. Both are set in the PCM open callbacks, so the substream that asks for the new rate in hw_params always blocks itself: cannot change speed mode (capture PID = -1, playback PID = 4242) As a result no application can move an HDSP from 48 kHz to 96 kHz (or to 192 kHz on the H9632) through the normal ALSA API. Rate changes within one speed mode work, and a speed mode change only works through the "Sample Clock Source" control while no PCM is open, which is what users have to script around today. The restriction exists because a speed mode change moves the channels to other DMA buffer slots (hdsp->channel_map) and changes the channel count, which must not happen under a substream that was already set up with the old layout. The substream calling hw_params is about to be set up anyway, so it is not such a substream. Pass the requesting substream to hdsp_set_rate() in place of the called_internally flag, and from hw_params only refuse the speed mode change if the other direction has hw_params of its own. A substream of another process is still turned away by the existing check in snd_hdsp_hw_params(), and requests from the controls and from card initialization (substream == NULL) keep the old rule of no open substreams at all. A duplex client therefore opens both directions, sets hw_params on the first one (which may change the speed mode) and then on the second one at the same rate. To change the speed mode later it has to hw_free or close the other direction first. Which directions have hw_params is tracked in a new setup_streams mask under hdsp->lock: hw_params sets the bit, and a new hw_free callback and close clear it. The other substream's runtime state cannot be used for this, because the PCM core only moves it to SETUP after the driver's hw_params has returned and dropped hdsp->lock. Two threads calling hw_params on the two directions at once could then both change the speed mode. Tested on an HDSP 9632 (v7.0 based kernel, same hdsp.c as v7.0) with aplay/arecord -M on hw:DSP: one stream moves the clock between 44.1k, 48k, 88.2k, 96k and 192k in both directions; alsaloop runs duplex in one process at 48k/12ch, 96k/8ch and 192k/4ch; while another process plays at 96k, a capture at 96k works and one at 48k or 192k is still refused; the "Sample Clock Source" control is still refused while a stream is open. REAPER on raw ALSA now changes the project rate between speed modes without presetting the clock first. Also built with W=1 on current mainline without warnings. Only the HDSP 9632 was available for testing; the Digiface, Multiface, RPM and 9652 were not. The change is not model specific: the speed mode switch itself is the same code the "Sample Clock Source" control already runs on every model, and it still only happens while no stream is transferring data. The per-model channel counts and channel maps come from the driver's existing tables. A Claude AI coding assistant (Claude Code) read the driver, wrote this change, the changelog and the tests at the request of the submitter, who reviewed them. Signed-off-by: Rafael Gago Link: https://patch.msgid.link/20261105-hdsp-sound-v2-1-458a3cfafbcd@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/rme9652/hdsp.c | 65 +++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index 7dce548b9f363e..c6ddad82dcf773 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -478,6 +478,7 @@ struct hdsp { pid_t capture_pid; pid_t playback_pid; + unsigned int setup_streams; /* 1 << stream after hw_params */ int running; int system_sample_rate; const signed char *channel_map; @@ -1095,7 +1096,40 @@ static void hdsp_set_dds_value(struct hdsp *hdsp, int rate) hdsp_write(hdsp, HDSP_freqReg, hdsp->dds_value); } -static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) +/* + * A speed mode change moves the channels to different DMA buffer slots + * (hdsp->channel_map) and changes the channel count. It is only safe if + * no substream other than the one asking for it (if any) was set up with + * the old layout. A substream of another process is already turned away + * by snd_hdsp_hw_params(). + * + * hdsp->setup_streams is used rather than the other substream's runtime + * state, because the PCM core only moves a substream to SETUP after + * snd_hdsp_hw_params() has returned and dropped hdsp->lock. It is read + * and updated under hdsp->lock. + */ +static bool hdsp_speed_change_blocked(struct hdsp *hdsp, + struct snd_pcm_substream *substream) +{ + int other; + + if (!substream) + return hdsp->capture_pid >= 0 || hdsp->playback_pid >= 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + other = SNDRV_PCM_STREAM_CAPTURE; + else + other = SNDRV_PCM_STREAM_PLAYBACK; + + return hdsp->setup_streams & (1 << other); +} + +/* + * substream is the stream asking for the rate in hw_params, or NULL for a + * request from a control or from card initialization. + */ +static int hdsp_set_rate(struct hdsp *hdsp, int rate, + struct snd_pcm_substream *substream) { int reject_if_open = 0; int current_rate; @@ -1107,7 +1141,7 @@ static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) */ if (!(hdsp->control_register & HDSP_ClockModeMaster)) { - if (called_internally) { + if (!substream) { /* request from ctl or card initialization */ dev_err(hdsp->card->dev, "device is not running as a clock master: cannot set sample rate.\n"); @@ -1134,7 +1168,7 @@ static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) current_rate = hdsp->system_sample_rate; /* Changing from a "single speed" to a "double speed" rate is - not allowed if any substreams are open. This is because + not allowed while another substream is set up. This is because such a change causes a shift in the location of the DMA buffers and a reduction in the number of available buffers. @@ -1196,7 +1230,7 @@ static int hdsp_set_rate(struct hdsp *hdsp, int rate, int called_internally) return -EINVAL; } - if (reject_if_open && (hdsp->capture_pid >= 0 || hdsp->playback_pid >= 0)) { + if (reject_if_open && hdsp_speed_change_blocked(hdsp, substream)) { dev_warn(hdsp->card->dev, "cannot change speed mode (capture PID = %d, playback PID = %d)\n", hdsp->capture_pid, @@ -1939,7 +1973,7 @@ static int hdsp_set_clock_source(struct hdsp *hdsp, int mode) switch (mode) { case HDSP_CLOCK_SOURCE_AUTOSYNC: if (hdsp_external_sample_rate(hdsp) != 0) { - if (!hdsp_set_rate(hdsp, hdsp_external_sample_rate(hdsp), 1)) { + if (!hdsp_set_rate(hdsp, hdsp_external_sample_rate(hdsp), NULL)) { hdsp->control_register &= ~HDSP_ClockModeMaster; hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); return 0; @@ -1978,7 +2012,7 @@ static int hdsp_set_clock_source(struct hdsp *hdsp, int mode) } hdsp->control_register |= HDSP_ClockModeMaster; hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); - hdsp_set_rate(hdsp, rate, 1); + hdsp_set_rate(hdsp, rate, NULL); return 0; } @@ -3815,7 +3849,7 @@ static int snd_hdsp_set_defaults(struct hdsp *hdsp) /* set a default rate so that the channel map is set up. */ - hdsp_set_rate(hdsp, 48000, 1); + hdsp_set_rate(hdsp, 48000, NULL); return 0; } @@ -4040,6 +4074,7 @@ static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, /* We're fine. */ + hdsp->setup_streams |= 1 << substream->stream; return 0; } @@ -4048,7 +4083,7 @@ static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, */ if (! hdsp->clock_source_locked) { - err = hdsp_set_rate(hdsp, params_rate(params), 0); + err = hdsp_set_rate(hdsp, params_rate(params), substream); if (err < 0) { _snd_pcm_hw_param_setempty(params, SNDRV_PCM_HW_PARAM_RATE); return err; @@ -4061,6 +4096,16 @@ static int snd_hdsp_hw_params(struct snd_pcm_substream *substream, return err; } + hdsp->setup_streams |= 1 << substream->stream; + return 0; +} + +static int snd_hdsp_hw_free(struct snd_pcm_substream *substream) +{ + struct hdsp *hdsp = snd_pcm_substream_chip(substream); + + guard(spinlock_irq)(&hdsp->lock); + hdsp->setup_streams &= ~(1 << substream->stream); return 0; } @@ -4471,6 +4516,7 @@ static int snd_hdsp_playback_release(struct snd_pcm_substream *substream) scoped_guard(spinlock_irq, &hdsp->lock) { hdsp->playback_pid = -1; hdsp->playback_substream = NULL; + hdsp->setup_streams &= ~(1 << SNDRV_PCM_STREAM_PLAYBACK); } if (RPM != hdsp->io_type) { @@ -4533,6 +4579,7 @@ static int snd_hdsp_capture_release(struct snd_pcm_substream *substream) hdsp->capture_pid = -1; hdsp->capture_substream = NULL; + hdsp->setup_streams &= ~(1 << SNDRV_PCM_STREAM_CAPTURE); return 0; } @@ -4854,6 +4901,7 @@ static const struct snd_pcm_ops snd_hdsp_playback_ops = { .close = snd_hdsp_playback_release, .ioctl = snd_hdsp_ioctl, .hw_params = snd_hdsp_hw_params, + .hw_free = snd_hdsp_hw_free, .prepare = snd_hdsp_prepare, .trigger = snd_hdsp_trigger, .pointer = snd_hdsp_hw_pointer, @@ -4866,6 +4914,7 @@ static const struct snd_pcm_ops snd_hdsp_capture_ops = { .close = snd_hdsp_capture_release, .ioctl = snd_hdsp_ioctl, .hw_params = snd_hdsp_hw_params, + .hw_free = snd_hdsp_hw_free, .prepare = snd_hdsp_prepare, .trigger = snd_hdsp_trigger, .pointer = snd_hdsp_hw_pointer, From 20f1752b9241d300b42cd45a07e22d063bccba45 Mon Sep 17 00:00:00 2001 From: Tanmay Jagdale Date: Fri, 16 Oct 2026 12:29:11 +0200 Subject: [PATCH 088/186] spi: spi-mem: Add support for panic mem writes Add a 'panic_write' flag to the controller's capabilities struct. When set, the SPI controller indicates that it will be able to send commands to a connected NOR or NAND memory in the context of a kernel panic, which means without sleeping and without relying on IRQs. Whether or not a given memory operation is a panic mem write is given by the spi_mem_op.panic_write flag. Signed-off-by: Tanmay Jagdale Co-developed-by: Paul Cercueil Signed-off-by: Paul Cercueil Link: https://patch.msgid.link/20261016102917.171095-2-paul.cercueil@bootlin.com Signed-off-by: Mark Brown --- drivers/spi/spi-mem.c | 42 +++++++++++++++++++++++++++++++++---- include/linux/spi/spi-mem.h | 5 +++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c index b94680b60af958..9ad42dd9de7053 100644 --- a/drivers/spi/spi-mem.c +++ b/drivers/spi/spi-mem.c @@ -398,6 +398,31 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) u8 *tmpbuf; int ret; + if (op->panic_write) { + if (!spi_mem_controller_is_capable(ctlr, panic_write)) + return -EOPNOTSUPP; + + /* + * Only support panic writes if we'll go through .exec_op(). + * The regular SPI path (spi_sync(), etc.) explicitly mentions + * that it can only be used from a context that may sleep, which + * is not the case on a kernel panic. + * If this changes in the future, this could be reworked, but + * for now this will have to do. + */ + if (WARN_ON_ONCE(!ctlr->mem_ops || !ctlr->mem_ops->exec_op)) + return -EOPNOTSUPP; + + if (spi_get_csgpiod(mem->spi, 0)) + return -EPERM; + + /* On panic IRQs are disabled so we cannot wait on locks */ + if (mutex_is_locked(&ctlr->bus_lock_mutex) || + mutex_is_locked(&ctlr->io_mutex)) { + return -EPERM; + } + } + /* Make sure the operation frequency is correct before going futher */ spi_mem_adjust_op_freq(mem, (struct spi_mem_op *)op); @@ -420,15 +445,20 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) return -EOPNOTSUPP; if (ctlr->mem_ops && ctlr->mem_ops->exec_op && !spi_get_csgpiod(mem->spi, 0)) { - ret = spi_mem_access_start(mem); - if (ret) - return ret; + if (!op->panic_write) { + ret = spi_mem_access_start(mem); + if (ret) + return ret; + } else if (!pm_runtime_active(ctlr->dev.parent)) { + return -EPERM; + } trace_spi_mem_start_op(mem, op); ret = ctlr->mem_ops->exec_op(mem, op); trace_spi_mem_stop_op(mem, op); - spi_mem_access_end(mem); + if (!op->panic_write) + spi_mem_access_end(mem); /* * Some controllers only optimize specific paths (typically the @@ -441,6 +471,10 @@ int spi_mem_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) return ret; } + + /* On panic write we can't fall back to the fallback SPI path */ + if (op->panic_write) + return ret; } tmpbufsize = op->cmd.nbytes + op->addr.nbytes + op->dummy.nbytes; diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h index 0cce6b57242ae0..2633307bfad50f 100644 --- a/include/linux/spi/spi-mem.h +++ b/include/linux/spi/spi-mem.h @@ -169,6 +169,7 @@ enum spi_mem_data_dir { * operation does not involve transferring data * @data.buf.in: input buffer (must be DMA-able) * @data.buf.out: output buffer (must be DMA-able) + * @panic_write: The command is executed in the context of a kernel panic * @max_freq: frequency limitation wrt this operation. 0 means there is no * specific constraint and the highest achievable frequency can be * attempted. @@ -211,6 +212,7 @@ struct spi_mem_op { } buf; } data; + bool panic_write; unsigned int max_freq; }; @@ -392,6 +394,8 @@ struct spi_controller_mem_ops { * is a pause in the transfer (eg. internal bus contention or * DMA arbitration on an interconnect). Features such as NAND * continuous reads shall not be leveraged. + * @panic_write: Set to true if the controller driver supports writing to a + * connected NAND/NOR memory in a kernel panic context */ struct spi_controller_mem_caps { bool dtr; @@ -400,6 +404,7 @@ struct spi_controller_mem_caps { bool per_op_freq; bool secondary_op_tmpl; bool no_cs_assertion; + bool panic_write; }; #define spi_mem_controller_is_capable(ctlr, cap) \ From 007f91d0fdb80ae16feae3c1a55f5f114c8197ac Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:16 +0530 Subject: [PATCH 089/186] ASoC: amd: acp: fix missing cleanup on devm_kasprintf() failure in mc_probe() On devm_kasprintf() failure for the cfg-amp components string, mc_probe() returned -ENOMEM directly without calling asoc_sdw_mc_dailink_exit_loop(), leaving device references acquired during sof_card_dai_links_create() unreleased. Call asoc_sdw_mc_dailink_exit_loop() before returning -ENOMEM to release those references, consistent with the cleanup already done in the devm_snd_soc_register_card() failure path. Fixes: cb8ea62e6402 ("ASoC: amd/sdw_utils: add sof based soundwire generic machine driver") Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index ec3e1f5f1052c5..979048e42424d2 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -425,8 +425,10 @@ static int mc_probe(struct platform_device *pdev) card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, " cfg-amp:%d", amp_num); - if (!card->components) + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); return -ENOMEM; + } /* Register the card */ ret = devm_snd_soc_register_card(&pdev->dev, card); From 2f107878e7a20b2069b503c26f34b2ee95fbf17d Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:17 +0530 Subject: [PATCH 090/186] ASoC: amd: acp: fix missing cleanup on devm_kasprintf() failure in legacy SDW machine driver On devm_kasprintf() failure for the cfg-amp and DMIC components strings in mc_probe(), the legacy SoundWire machine driver returned -ENOMEM directly without calling asoc_sdw_mc_dailink_exit_loop(), leaving device references acquired during soc_card_dai_links_create() unreleased. Call asoc_sdw_mc_dailink_exit_loop() before returning -ENOMEM on both failure paths, consistent with the cleanup already done in the devm_snd_soc_register_card() failure path. Fixes: 2981d9b0789c ("ASoC: amd: acp: add soundwire machine driver for legacy stack") Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-legacy-mach.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-legacy-mach.c b/sound/soc/amd/acp/acp-sdw-legacy-mach.c index a8b62817b2344e..bd03789c784954 100644 --- a/sound/soc/amd/acp/acp-sdw-legacy-mach.c +++ b/sound/soc/amd/acp/acp-sdw-legacy-mach.c @@ -590,15 +590,19 @@ static int mc_probe(struct platform_device *pdev) card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, " cfg-amp:%d", amp_num); - if (!card->components) + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); return -ENOMEM; + } if (soc_sdw_quirk & ASOC_SDW_ACP_DMIC) { card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s mic:acp-dmic cfg-mics:%d", card->components, 1); - if (!card->components) + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); return -ENOMEM; + } } /* Register the card */ From be810ddf1be97428b370cb49ef37998f0e71daf1 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:18 +0530 Subject: [PATCH 091/186] ASoC: amd: sdw: remove unused ACP63_SDW_MAX_CPU_DAIS define ACP63_SDW_MAX_CPU_DAIS is defined but never referenced anywhere in the tree. Remove it. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-4-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/soc_amd_sdw_common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 034c9ab6e4ea67..0ae85165573dde 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -15,7 +15,6 @@ #include #include "acp-mach-common.h" -#define ACP63_SDW_MAX_CPU_DAIS 8 #define ACP63_SDW_MAX_LINKS 2 #define AMD_SDW_MAX_GROUPS 9 From 7de8faa0fb6ed39fc69d458c1a167a2bab46a62f Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:19 +0530 Subject: [PATCH 092/186] ASoC: amd: acp: add ACP7.B/7.F SoundWire link count for machine driver ACP7.B/7.F platforms expose four SoundWire managers, whereas earlier platforms use two. Add PCI revision constants and ACP7X_SDW_MAX_LINKS to soc_amd_sdw_common.h, and set max_sdw_links in mc_probe() based on PCI revision so the machine driver validates DAI links against the correct manager count. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-5-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 10 +++++++++- sound/soc/amd/acp/soc_amd_sdw_common.h | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 979048e42424d2..46c3815a777fce 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -382,7 +382,15 @@ static int mc_probe(struct platform_device *pdev) return -ENOMEM; amd_ctx->acp_rev = mach->mach_params.subsystem_rev; - amd_ctx->max_sdw_links = ACP63_SDW_MAX_LINKS; + switch (amd_ctx->acp_rev) { + case ACP7B_PCI_REV: + case ACP7F_PCI_REV: + amd_ctx->max_sdw_links = ACP7X_SDW_MAX_LINKS; + break; + default: + amd_ctx->max_sdw_links = ACP63_SDW_MAX_LINKS; + break; + } ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 0ae85165573dde..c98d70a0d6ce65 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -16,12 +16,15 @@ #include "acp-mach-common.h" #define ACP63_SDW_MAX_LINKS 2 +#define ACP7X_SDW_MAX_LINKS 4 #define AMD_SDW_MAX_GROUPS 9 #define ACP63_PCI_REV 0x63 #define ACP70_PCI_REV 0x70 #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 +#define ACP7B_PCI_REV 0x7b +#define ACP7F_PCI_REV 0x7f #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) From b6fd467a4be07578f40f4a1a8d1a237ed4b011d6 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:20 +0530 Subject: [PATCH 093/186] ASoC: amd: acp: add ACP7.B/7.F SoundWire DAI link support Add CPU pin ID lookup and SoundWire DAI link support for ACP7.B/7.F platforms with up to 4 SoundWire managers. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-6-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-mach-common.c | 48 +++++++++++++++++++++++++ sound/soc/amd/acp/acp-sdw-sof-mach.c | 7 ++++ sound/soc/amd/acp/soc_amd_sdw_common.h | 38 ++++++++++++++++++++ 3 files changed, 93 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-mach-common.c b/sound/soc/amd/acp/acp-sdw-mach-common.c index e5f394dc2f4c02..40184674dc8060 100644 --- a/sound/soc/amd/acp/acp-sdw-mach-common.c +++ b/sound/soc/amd/acp/acp-sdw-mach-common.c @@ -93,6 +93,54 @@ int get_acp70_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct dev } EXPORT_SYMBOL_NS_GPL(get_acp70_cpu_pin_id, "SND_SOC_AMD_SDW_MACH"); +/* + * On ACP7x, each SoundWire data port has a dedicated DMA channel + * hardwired in hardware. This function selects only the ports required + * by the currently supported ACP7x machine topologies: + * AUDIO0_TX (0) - headset/jack playback + * AUDIO4_TX (4) - amplifier/speaker playback + * AUDIO0_RX (11) - headset/jack capture + * AUDIO4_RX (15) - amplifier/speaker capture (feedback) + * AUDIO5_RX (16) - DMIC capture + * The remaining port IDs are defined to reflect the full hardware + * data port layout. + */ +int get_acp7x_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev) +{ + switch (sdw_link_id) { + case AMD_SDW0: + case AMD_SDW1: + case AMD_SDW2: + case AMD_SDW3: + switch (be_id) { + case SOC_SDW_JACK_OUT_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO0_TX; + break; + case SOC_SDW_JACK_IN_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO0_RX; + break; + case SOC_SDW_AMP_OUT_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO4_TX; + break; + case SOC_SDW_AMP_IN_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO4_RX; + break; + case SOC_SDW_DMIC_DAI_ID: + *cpu_pin_id = ACP7X_SW_AUDIO5_RX; + break; + default: + dev_err(dev, "Invalid be id:%d\n", be_id); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + dev_dbg(dev, "sdw_link_id:%d, be_id:%d, cpu_pin_id:%d\n", sdw_link_id, be_id, *cpu_pin_id); + return 0; +} +EXPORT_SYMBOL_NS_GPL(get_acp7x_cpu_pin_id, "SND_SOC_AMD_SDW_MACH"); + MODULE_DESCRIPTION("AMD SoundWire Common Machine driver"); MODULE_AUTHOR("Vijendar Mukunda "); MODULE_LICENSE("GPL"); diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 46c3815a777fce..97b2e224492f4b 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -145,6 +145,13 @@ static int create_sdw_dailink(struct snd_soc_card *card, if (ret) return ret; break; + case ACP7B_PCI_REV: + case ACP7F_PCI_REV: + ret = get_acp7x_cpu_pin_id(ffs(sof_end->link_mask) - 1, + *be_id, &cpu_pin_id, dev); + if (ret) + return ret; + break; default: return -EINVAL; } diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index c98d70a0d6ce65..c979adf4e978b6 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -33,6 +33,8 @@ #define AMD_SDW0 0 #define AMD_SDW1 1 +#define AMD_SDW2 2 +#define AMD_SDW3 3 #define ACP63_SW0_AUDIO0_TX 0 #define ACP63_SW0_AUDIO1_TX 1 #define ACP63_SW0_AUDIO2_TX 2 @@ -54,6 +56,41 @@ #define ACP70_SW_AUDIO1_RX 4 #define ACP70_SW_AUDIO2_RX 5 +/* + * ACP7.B/7.F SoundWire CPU pin IDs. + * + * ACP7x hardware exposes 11 TX and 11 RX data ports per SoundWire + * instance, each with a dedicated DMA channel hardwired in hardware. + * The CPU pin IDs use a flat linear numbering shared across all four + * SoundWire links (SW0-SW3): TX ports are numbered 0-10 and RX ports + * are numbered 11-21. All port IDs are defined here to represent the + * ACP7x hardware data port layout. The ID numbering differs from + * ACP70 due to hardware differences between ACP70 and ACP7x. + */ +#define ACP7X_SW_AUDIO0_TX 0 +#define ACP7X_SW_AUDIO1_TX 1 +#define ACP7X_SW_AUDIO2_TX 2 +#define ACP7X_SW_AUDIO3_TX 3 +#define ACP7X_SW_AUDIO4_TX 4 +#define ACP7X_SW_AUDIO5_TX 5 +#define ACP7X_SW_AUDIO6_TX 6 +#define ACP7X_SW_AUDIO7_TX 7 +#define ACP7X_SW_AUDIO8_TX 8 +#define ACP7X_SW_AUDIO9_TX 9 +#define ACP7X_SW_AUDIO10_TX 10 + +#define ACP7X_SW_AUDIO0_RX 11 +#define ACP7X_SW_AUDIO1_RX 12 +#define ACP7X_SW_AUDIO2_RX 13 +#define ACP7X_SW_AUDIO3_RX 14 +#define ACP7X_SW_AUDIO4_RX 15 +#define ACP7X_SW_AUDIO5_RX 16 +#define ACP7X_SW_AUDIO6_RX 17 +#define ACP7X_SW_AUDIO7_RX 18 +#define ACP7X_SW_AUDIO8_RX 19 +#define ACP7X_SW_AUDIO9_RX 20 +#define ACP7X_SW_AUDIO10_RX 21 + struct amd_mc_ctx { unsigned int acp_rev; unsigned int max_sdw_links; @@ -61,5 +98,6 @@ struct amd_mc_ctx { int get_acp63_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); int get_acp70_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); +int get_acp7x_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); #endif From f6a58ddb3284ee0a1749947201ff3fa0cf0a2e76 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:21 +0530 Subject: [PATCH 094/186] ASoC: amd: acp: add PDM controller selection for ACP7.B/7.F SoundWire machine driver Add ASOC_SDW_ACP_DMIC1 quirk bit (BIT(6)) and pdm_sel field to amd_mc_ctx to support per-controller DMIC DAI selection on ACP7.B/7.F platforms. In mc_probe(), read the active PDM controller from mach->pdata (populated via ACPI acp-audio-ep-port _DSD). When ACPI does not set pdm_sel, derive it from the quirk bits after dmi_check_system() and quirk_override are applied: ASOC_SDW_ACP_DMIC1 selects DMIC1; ASOC_SDW_ACP_DMIC selects DMIC0. Since only one PDM controller is active at a time, DMIC1 is checked first. In create_dmic_dailinks(), select the SOF CPU DAI name based on pdm_sel: "acp-sof-dmic1" for ACP7X_PDM_DMIC1, "acp-sof-dmic0" otherwise on ACP7.B/7.F; all other platforms use "acp-sof-dmic". Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-7-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 44 +++++++++++++++++++++++++- sound/soc/amd/acp/soc_amd_sdw_common.h | 3 ++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 97b2e224492f4b..34a71f7a8c5452 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -265,11 +265,31 @@ static int create_dmic_dailinks(struct snd_soc_card *card, struct snd_soc_dai_link **dai_links, int *be_id, int no_pcm) { struct device *dev = card->dev; + struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct amd_mc_ctx *amd_ctx = (struct amd_mc_ctx *)ctx->private; + const char *cpu_dai_name; int ret; + /* + * For ACP7.B/7.F platforms the PDM controller is selected from ACPI + * acp-audio-ep-port and stored in amd_ctx->pdm_sel. Use the + * matching SOF DAI name ("acp-sof-dmic0" or "acp-sof-dmic1"). + * All other platforms use the generic "acp-sof-dmic" DAI. + */ + switch (amd_ctx->acp_rev) { + case ACP7B_PCI_REV: + case ACP7F_PCI_REV: + cpu_dai_name = (amd_ctx->pdm_sel == ACP7X_PDM_DMIC1) ? + "acp-sof-dmic1" : "acp-sof-dmic0"; + break; + default: + cpu_dai_name = "acp-sof-dmic"; + break; + } + ret = asoc_sdw_init_simple_dai_link(dev, *dai_links, be_id, "acp-dmic-codec", 0, 1, // DMIC only supports capture - "acp-sof-dmic", platform_component->name, + cpu_dai_name, platform_component->name, "dmic-codec", "dmic-hifi", no_pcm, asoc_sdw_dmic_init, NULL); if (ret) @@ -378,6 +398,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) static int mc_probe(struct platform_device *pdev) { struct snd_soc_acpi_mach *mach = dev_get_platdata(&pdev->dev); + struct amd_pdm_pdata *pdm_pdata; struct snd_soc_card *card; struct amd_mc_ctx *amd_ctx; struct asoc_sdw_mc_private *ctx; @@ -398,6 +419,10 @@ static int mc_probe(struct platform_device *pdev) amd_ctx->max_sdw_links = ACP63_SDW_MAX_LINKS; break; } + if (mach->pdata) { + pdm_pdata = mach->pdata; + amd_ctx->pdm_sel = pdm_pdata->pdm_sel; + } ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; @@ -419,6 +444,23 @@ static int mc_probe(struct platform_device *pdev) sof_sdw_quirk = quirk_override; } + /* + * If ACPI did not set pdm_sel (no acp-audio-ep-port in _DSD), derive + * it from the quirk bits. ACPI takes priority: quirks are only + * consulted when pdm_sel is still zero. PDM controller selection is + * only meaningful on ACP7B/7F platforms. + * ASOC_SDW_ACP_DMIC selects DMIC0 (default); ASOC_SDW_ACP_DMIC1 + * overrides to DMIC1. + */ + if (!amd_ctx->pdm_sel && + (amd_ctx->acp_rev == ACP7B_PCI_REV || + amd_ctx->acp_rev == ACP7F_PCI_REV)) { + if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC1) + amd_ctx->pdm_sel = ACP7X_PDM_DMIC1; + else if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC) + amd_ctx->pdm_sel = ACP7X_PDM_DMIC0; + } + log_quirks(&pdev->dev); ctx->mc_quirk = sof_sdw_quirk; diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index c979adf4e978b6..07837a2cfa5405 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -29,6 +29,7 @@ #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) +#define ASOC_SDW_ACP_DMIC1 BIT(6) /* select DMIC1 controller for ACP7.B/7.F */ #define ASOC_SDW_CODEC_SPKR BIT(15) #define AMD_SDW0 0 @@ -94,6 +95,8 @@ struct amd_mc_ctx { unsigned int acp_rev; unsigned int max_sdw_links; + /* PDM controller selection, copied from amd_pdm_pdata at probe time */ + unsigned int pdm_sel; }; int get_acp63_cpu_pin_id(u32 sdw_link_id, int be_id, int *cpu_pin_id, struct device *dev); From 5b597730d199dd7d9929b20e0e8fcc34628169e7 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:22 +0530 Subject: [PATCH 095/186] ASoC: amd: acp: log ASOC_SDW_ACP_DMIC1 quirk in SOF SoundWire machine driver Add ASOC_SDW_ACP_DMIC1 to log_quirks() so the active PDM controller selection is visible in boot-time diagnostics alongside the other ACP quirk bits. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-8-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 34a71f7a8c5452..18603e552deed8 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -28,6 +28,8 @@ static void log_quirks(struct device *dev) SOC_JACK_JDSRC(sof_sdw_quirk)); if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC) dev_dbg(dev, "quirk SOC_SDW_ACP_DMIC enabled\n"); + if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC1) + dev_dbg(dev, "quirk ASOC_SDW_ACP_DMIC1 enabled\n"); } static int sof_sdw_quirk_cb(const struct dmi_system_id *id) From eb5a7c98ca1b16ea54228037cbf06ac8ea2264ef Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:23 +0530 Subject: [PATCH 096/186] ASoC: amd: acp: enable DMIC DAI link based on PDM controller selection Extend the DMIC DAI link enable condition in sof_card_dai_links_create() to also check amd_ctx->pdm_sel. On ACP7.B/7.F platforms the PDM controller is selected via ACPI rather than a DMI quirk, so ASOC_SDW_ACP_DMIC may not be set. Enabling the DMIC link when amd_ctx->pdm_sel is non-zero ensures the link is created whenever a valid PDM controller has been identified from ACPI _DSD. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-9-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 18603e552deed8..9024e789d746b0 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -308,6 +308,7 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) struct snd_soc_acpi_mach *mach = dev_get_platdata(card->dev); int sdw_be_num = 0, dmic_num = 0; struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card); + struct amd_mc_ctx *amd_ctx = (struct amd_mc_ctx *)ctx->private; struct snd_soc_acpi_mach_params *mach_params = &mach->mach_params; struct snd_soc_aux_dev *sof_aux; struct snd_soc_codec_conf *codec_conf; @@ -350,7 +351,8 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) sdw_be_num = ret; /* enable dmic */ - if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach_params->dmic_num) + if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach_params->dmic_num || + amd_ctx->pdm_sel) dmic_num = 1; dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); From 5ad81cbb83bdf385561dd8372d3147b808176eb7 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:24 +0530 Subject: [PATCH 097/186] ASoC: amd: acp: move ignore_internal_dmic gate to DMIC enable condition Move the ignore_internal_dmic check from inside the dmic_num > 0 block to the DMIC enable condition itself. This eliminates the dead if/else inside the dmic_num > 0 block - when dmic_num is set, create_dmic_dailinks() is always called. Demote the suppression message from dev_warn to dev_dbg since ignoring the ACP DMIC in favour of a SoundWire DMIC codec is expected behaviour, not a warning condition. For platforms where ignore_internal_dmic is true, the behaviour changes from emitting a dev_warn inside the dmic_num > 0 block to silent suppression at the gate where dmic_num stays zero. This is intentional: ignoring the ACP DMIC in favour of a SoundWire DMIC codec is expected behaviour, not a warning condition. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-10-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 9024e789d746b0..3366f668d819b9 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -352,8 +352,12 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) /* enable dmic */ if (sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach_params->dmic_num || - amd_ctx->pdm_sel) - dmic_num = 1; + amd_ctx->pdm_sel) { + if (ctx->ignore_internal_dmic) + dev_dbg(dev, "ACP DMIC suppressed by SoundWire DMIC\n"); + else + dmic_num = 1; + } dev_dbg(dev, "sdw %d, dmic %d", sdw_be_num, dmic_num); @@ -384,13 +388,9 @@ static int sof_card_dai_links_create(struct snd_soc_card *card) /* dmic */ if (dmic_num > 0) { - if (ctx->ignore_internal_dmic) { - dev_warn(dev, "Ignoring ACP DMIC\n"); - } else { - ret = create_dmic_dailinks(card, &dai_links, &be_id, 1); - if (ret) - return ret; - } + ret = create_dmic_dailinks(card, &dai_links, &be_id, 1); + if (ret) + return ret; } WARN_ON(codec_conf != card->codec_conf + card->num_configs); From 2ced313a0605a72eb23c87c45cffd28ffd7cf3ab Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:25 +0530 Subject: [PATCH 098/186] ASoC: amd: acp: add DMIC components string for SOF SoundWire machine driver Append "mic:acp-dmic cfg-mics:N" to card->components in mc_probe() when a DMIC DAI link will be registered, keeping the components string consistent with what sof_card_dai_links_create() actually creates. Gate on the same three-way condition used in sof_card_dai_links_create() (ASOC_SDW_ACP_DMIC quirk, mach_params->dmic_num, or amd_ctx->pdm_sel) and suppress when ctx->ignore_internal_dmic is set. Use mach_params->dmic_num for the mic count when platform-reported; fall back to 1 for quirk and pdm_sel paths. Call asoc_sdw_mc_dailink_exit_loop() before returning -ENOMEM on devm_kasprintf() failure to release device references acquired during sof_card_dai_links_create(). Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-11-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 3366f668d819b9..664b9e960cf77c 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -406,7 +406,7 @@ static int mc_probe(struct platform_device *pdev) struct snd_soc_card *card; struct amd_mc_ctx *amd_ctx; struct asoc_sdw_mc_private *ctx; - int amp_num = 0, i; + int amp_num = 0, i, dmic_num; int ret; amd_ctx = devm_kzalloc(&pdev->dev, sizeof(*amd_ctx), GFP_KERNEL); @@ -491,6 +491,19 @@ static int mc_probe(struct platform_device *pdev) return -ENOMEM; } + if ((sof_sdw_quirk & ASOC_SDW_ACP_DMIC || mach->mach_params.dmic_num || + amd_ctx->pdm_sel) && !ctx->ignore_internal_dmic) { + dmic_num = mach->mach_params.dmic_num ? mach->mach_params.dmic_num : 1; + card->components = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s mic:acp-dmic cfg-mics:%d", + card->components, + dmic_num); + if (!card->components) { + asoc_sdw_mc_dailink_exit_loop(card); + return -ENOMEM; + } + } + /* Register the card */ ret = devm_snd_soc_register_card(&pdev->dev, card); if (ret) { From 90a52a8f23fab004e513908772d9d5242a3c1e3b Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:26 +0530 Subject: [PATCH 099/186] ASoC: amd: acp: set PCI subsystem ID for SOF SoundWire machine driver Call snd_soc_card_set_pci_ssid() when subsystem_id_set is indicated in the machine params, propagating the PCI subsystem vendor and device IDs to the ALSA card for correct device identification on ACP SoundWire platforms. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-12-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 664b9e960cf77c..11cd0feaa16c45 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -440,6 +440,11 @@ static int mc_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, ctx); + if (mach->mach_params.subsystem_id_set) + snd_soc_card_set_pci_ssid(card, + mach->mach_params.subsystem_vendor, + mach->mach_params.subsystem_device); + dmi_check_system(sof_sdw_quirk_table); if (quirk_override != -1) { From faa5a77479ad0121dc9ab244514f276c1ef1942c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:27 +0530 Subject: [PATCH 100/186] ASoC: amd: acp: fix MODULE_AUTHOR missing closing angle bracket The MODULE_AUTHOR macro call was missing the closing '>' for the email address angle bracket notation. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-13-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/acp-sdw-sof-mach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp-sdw-sof-mach.c b/sound/soc/amd/acp/acp-sdw-sof-mach.c index 11cd0feaa16c45..1d7487c8726575 100644 --- a/sound/soc/amd/acp/acp-sdw-sof-mach.c +++ b/sound/soc/amd/acp/acp-sdw-sof-mach.c @@ -548,7 +548,7 @@ static struct platform_driver sof_sdw_driver = { module_platform_driver(sof_sdw_driver); MODULE_DESCRIPTION("ASoC AMD SoundWire Generic Machine driver"); -MODULE_AUTHOR("Vijendar Mukunda "); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS("SND_SOC_SDW_UTILS"); MODULE_IMPORT_NS("SND_SOC_AMD_SDW_MACH"); From 48b1ff3a75d9348635f0e5064a8bc1f1a76d79af Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:28 +0530 Subject: [PATCH 101/186] ASoC: amd: acp: add ACPI machine tables for ACP7.F SoundWire Add amd-acp7x-acpi-match.c with four snd_soc_acpi_mach entries for ACP7.F SoundWire configurations: - RT721 + RT1320 on SoundWire links L0+L3 (jack+dmic on RT721, spk on RT1320) - RT721 + RT1320 on SoundWire links L0+L1 (jack+dmic on RT721, spk on RT1320) - Standalone RT721 on L0 (jack, amp and dmic endpoints) - Standalone RT722 on L0 (jack, amp and dmic endpoints) RT721+RT1320 combo configurations use jack_dmic_endpoints for RT721, skipping the internal amp since RT1320 provides amplification on a dedicated SoundWire link. ACP7.B machine table will be added in a follow-up patch. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-14-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/Makefile | 2 +- sound/soc/amd/acp/amd-acp7x-acpi-match.c | 186 +++++++++++++++++++++++ sound/soc/amd/mach-config.h | 1 + 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 sound/soc/amd/acp/amd-acp7x-acpi-match.c diff --git a/sound/soc/amd/acp/Makefile b/sound/soc/amd/acp/Makefile index ab5f9dc871a677..42e2bd34da0608 100644 --- a/sound/soc/amd/acp/Makefile +++ b/sound/soc/amd/acp/Makefile @@ -23,7 +23,7 @@ snd-acp70-y := acp70.o snd-acp-mach-y := acp-mach-common.o snd-acp-legacy-mach-y := acp-legacy-mach.o acp3x-es83xx/acp3x-es83xx.o snd-acp-sof-mach-y := acp-sof-mach.o -snd-soc-acpi-amd-match-y := amd-acp63-acpi-match.o amd-acp70-acpi-match.o +snd-soc-acpi-amd-match-y := amd-acp63-acpi-match.o amd-acp70-acpi-match.o amd-acp7x-acpi-match.o snd-soc-acpi-amd-match-$(CONFIG_SND_SOC_ACPI_AMD_SDCA_QUIRKS) += soc-acpi-amd-sdca-quirks.o snd-acp-sdw-mach-y := acp-sdw-mach-common.o snd-acp-sdw-sof-mach-y += acp-sdw-sof-mach.o diff --git a/sound/soc/amd/acp/amd-acp7x-acpi-match.c b/sound/soc/amd/acp/amd-acp7x-acpi-match.c new file mode 100644 index 00000000000000..2f5e060ec79b48 --- /dev/null +++ b/sound/soc/amd/acp/amd-acp7x-acpi-match.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * amd-acp7x-acpi-match.c - tables and support for ACP7.x(7.B/7.F) platforms + * ACPI enumeration. + * + * Copyright 2026 Advanced Micro Devices, Inc. + */ + +#include +#include "../mach-config.h" + +static const struct snd_soc_acpi_endpoint single_endpoint = { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0 +}; + +static const struct snd_soc_acpi_endpoint jack_dmic_endpoints[] = { + /* Jack Endpoint */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* DMIC Endpoint */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_endpoint jack_amp_dmic_endpoints[] = { + /* Jack Endpoint */ + { + .num = 0, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* Amp Endpoint */ + { + .num = 1, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, + /* DMIC Endpoint */ + { + .num = 2, + .aggregated = 0, + .group_position = 0, + .group_id = 0, + }, +}; + +static const struct snd_soc_acpi_adr_device rt721_l0_single_adr[] = { + { + .adr = 0x000030025d072101ull, + .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), + .endpoints = jack_dmic_endpoints, + .name_prefix = "rt721" + } +}; + +static const struct snd_soc_acpi_adr_device rt721_l0_only[] = { + { + .adr = 0x000030025d072101ull, + .num_endpoints = ARRAY_SIZE(jack_amp_dmic_endpoints), + .endpoints = jack_amp_dmic_endpoints, + .name_prefix = "rt721" + } +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt721_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt721_l0_only), + .adr_d = rt721_l0_only, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device rt722_l0_single_adr[] = { + { + .adr = 0x000030025d072201ull, + .num_endpoints = ARRAY_SIZE(jack_amp_dmic_endpoints), + .endpoints = jack_amp_dmic_endpoints, + .name_prefix = "rt722" + } +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt722_only[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt722_l0_single_adr), + .adr_d = rt722_l0_single_adr, + }, + {} +}; + +static const struct snd_soc_acpi_adr_device rt1320_l1_single_adr[] = { + { + .adr = 0x000130025d132001ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1320-1" + }, +}; + +static const struct snd_soc_acpi_adr_device rt1320_l3_single_adr[] = { + { + .adr = 0x000330025d132001ull, + .num_endpoints = 1, + .endpoints = &single_endpoint, + .name_prefix = "rt1320-1" + }, +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l1[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt721_l0_single_adr), + .adr_d = rt721_l0_single_adr, + }, + { + .mask = BIT(1), + .num_adr = ARRAY_SIZE(rt1320_l1_single_adr), + .adr_d = rt1320_l1_single_adr, + }, + {} +}; + +static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l3[] = { + { + .mask = BIT(0), + .num_adr = ARRAY_SIZE(rt721_l0_single_adr), + .adr_d = rt721_l0_single_adr, + }, + { + .mask = BIT(3), + .num_adr = ARRAY_SIZE(rt1320_l3_single_adr), + .adr_d = rt1320_l3_single_adr, + }, + {} +}; + +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_sdw_machines[] = { + { + .link_mask = BIT(0) | BIT(3), + .links = acp7x_rt721_l0_rt1320_l3, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt721-l0-rt1320-l3.tplg", + .fw_filename = "sof-acp7f.ri", + }, + { + .link_mask = BIT(0) | BIT(1), + .links = acp7x_rt721_l0_rt1320_l1, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt721-l0-rt1320-l1.tplg", + .fw_filename = "sof-acp7f.ri", + }, + { + .link_mask = BIT(0), + .links = acp7x_rt721_only, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt721-l0.tplg", + .fw_filename = "sof-acp7f.ri", + }, + { + .link_mask = BIT(0), + .links = acp7x_rt722_only, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7f-rt722-l0.tplg", + .fw_filename = "sof-acp7f.ri", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7f_sof_sdw_machines); + +MODULE_DESCRIPTION("AMD ACP7.x SoundWire machine tables for ACPI enumeration"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vijendar Mukunda "); diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index 02ce044bcbf722..321af4682fd30c 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -30,6 +30,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_sdw_machines[]; struct config_entry { u32 flags; From c9ff86300abe8ec1b892b0ce767fc44a1347dc12 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:29 +0530 Subject: [PATCH 102/186] ASoC: SOF: amd: add alt_machines for ACP7.F SOF SoundWire Register snd_soc_acpi_amd_acp7f_sof_sdw_machines as alt_machines in the ACP7.F SOF device descriptor so that the SOF core selects the SoundWire machine driver when ADR devices are detected on ACP7.F platforms. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-15-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/pci-acp7x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 5a21ab8ebb4b9b..2999dc6d72a636 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -89,6 +89,7 @@ static const struct sof_amd_acp_desc acp7f_chip_info = { static const struct sof_dev_desc acp7f_desc = { .machines = snd_soc_acpi_amd_acp7f_sof_machines, + .alt_machines = snd_soc_acpi_amd_acp7f_sof_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, From e6f48837d135f0e3273a509e74e60adc84727e29 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:30 +0530 Subject: [PATCH 103/186] ASoC: amd: acp: add SoundWire machine table for ACP7.B platform Add snd_soc_acpi_amd_acp7b_sof_sdw_machines with one entry for the RT721 (L0: jack + DMIC) + RT1320 (L3: speaker) SoundWire configuration on ACP7.B. Export the table for use by the SOF descriptor. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-16-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/amd-acp7x-acpi-match.c | 12 ++++++++++++ sound/soc/amd/mach-config.h | 1 + 2 files changed, 13 insertions(+) diff --git a/sound/soc/amd/acp/amd-acp7x-acpi-match.c b/sound/soc/amd/acp/amd-acp7x-acpi-match.c index 2f5e060ec79b48..830c59966a3569 100644 --- a/sound/soc/amd/acp/amd-acp7x-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp7x-acpi-match.c @@ -148,6 +148,18 @@ static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l3[] = { {} }; +struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_sdw_machines[] = { + { + .link_mask = BIT(0) | BIT(3), + .links = acp7x_rt721_l0_rt1320_l3, + .drv_name = "amd_sof_sdw", + .sof_tplg_filename = "sof-acp7b-rt721-l0-rt1320-l3.tplg", + .fw_filename = "sof-acp7b.ri", + }, + {}, +}; +EXPORT_SYMBOL(snd_soc_acpi_amd_acp7b_sof_sdw_machines); + struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_sdw_machines[] = { { .link_mask = BIT(0) | BIT(3), diff --git a/sound/soc/amd/mach-config.h b/sound/soc/amd/mach-config.h index 321af4682fd30c..09ea9f5259557c 100644 --- a/sound/soc/amd/mach-config.h +++ b/sound/soc/amd/mach-config.h @@ -29,6 +29,7 @@ extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp70_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_machines[]; +extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7b_sof_sdw_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_machines[]; extern struct snd_soc_acpi_mach snd_soc_acpi_amd_acp7f_sof_sdw_machines[]; From 4384043e78be740e888af650daf1191be4e54ff9 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 30 Oct 2026 15:15:31 +0530 Subject: [PATCH 104/186] ASoC: SOF: amd: register SoundWire alt_machines for ACP7.B Register snd_soc_acpi_amd_acp7b_sof_sdw_machines as alt_machines in the ACP7.B SOF device descriptor so that the SOF core selects the SoundWire machine driver when ADR devices are detected on ACP7.B platforms. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261030094714.1123396-17-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/sof/amd/pci-acp7x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 2999dc6d72a636..6aa16d1a12b952 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -47,6 +47,7 @@ static const struct sof_amd_acp_desc acp7b_chip_info = { static const struct sof_dev_desc acp7b_desc = { .machines = snd_soc_acpi_amd_acp7b_sof_machines, + .alt_machines = snd_soc_acpi_amd_acp7b_sof_sdw_machines, .resindex_lpe_base = 0, .resindex_pcicfg_base = -1, .resindex_imr_base = -1, From 8f70d5f9161116d8daddf8d0758a079adb32a62e Mon Sep 17 00:00:00 2001 From: Ravulapati Vishnuvardhan Rao Date: Tue, 3 Nov 2026 16:22:20 +0530 Subject: [PATCH 105/186] ASoC: soc-core: Increase the channel map size As per the need for new OEMs it is needed to increase the number of channels size as some of the OEM have ten mics and one speaker. So for this it is important to make sure that channel map needs to be increased. Signed-off-by: Ravulapati Vishnuvardhan Rao Link: https://patch.msgid.link/20261103-for-next-v1-1-ecb1348f5941@oss.qualcomm.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index f33bece378b617..51d74f967c18f2 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -859,7 +859,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, return -EINVAL; } -#define MAX_DEFAULT_CH_MAP_SIZE 8 +#define MAX_DEFAULT_CH_MAP_SIZE 12 static struct snd_soc_dai_link_ch_map default_ch_map_sync[MAX_DEFAULT_CH_MAP_SIZE] = { { .cpu = 0, .codec = 0 }, { .cpu = 1, .codec = 1 }, @@ -869,6 +869,10 @@ static struct snd_soc_dai_link_ch_map default_ch_map_sync[MAX_DEFAULT_CH_MAP_SIZ { .cpu = 5, .codec = 5 }, { .cpu = 6, .codec = 6 }, { .cpu = 7, .codec = 7 }, + { .cpu = 8, .codec = 8 }, + { .cpu = 9, .codec = 9 }, + { .cpu = 10, .codec = 10 }, + { .cpu = 11, .codec = 11 }, }; static struct snd_soc_dai_link_ch_map default_ch_map_1cpu[MAX_DEFAULT_CH_MAP_SIZE] = { { .cpu = 0, .codec = 0 }, @@ -879,6 +883,10 @@ static struct snd_soc_dai_link_ch_map default_ch_map_1cpu[MAX_DEFAULT_CH_MAP_SIZ { .cpu = 0, .codec = 5 }, { .cpu = 0, .codec = 6 }, { .cpu = 0, .codec = 7 }, + { .cpu = 0, .codec = 8 }, + { .cpu = 0, .codec = 9 }, + { .cpu = 0, .codec = 10 }, + { .cpu = 0, .codec = 11 }, }; static struct snd_soc_dai_link_ch_map default_ch_map_1codec[MAX_DEFAULT_CH_MAP_SIZE] = { { .cpu = 0, .codec = 0 }, @@ -889,6 +897,10 @@ static struct snd_soc_dai_link_ch_map default_ch_map_1codec[MAX_DEFAULT_CH_MAP_S { .cpu = 5, .codec = 0 }, { .cpu = 6, .codec = 0 }, { .cpu = 7, .codec = 0 }, + { .cpu = 8, .codec = 0 }, + { .cpu = 9, .codec = 0 }, + { .cpu = 10, .codec = 0 }, + { .cpu = 11, .codec = 0 }, }; static int snd_soc_compensate_channel_connection_map(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link) From 28c0e86dc8f3b9e4bc17f1f5d197f5a4d06252cb Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 2 Nov 2026 14:52:27 -0500 Subject: [PATCH 106/186] ASoC: fsl_asrc_m2m: use dma_chan directly for dma_async_issue_pending() Use pair->dma_chan[] directly instead of pair->desc[]->chan when calling dma_async_issue_pending(), which is more straightforward and avoids dereferencing the descriptor to get the channel. Prepare to make struct dma_async_tx_descriptor private. Signed-off-by: Frank Li Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261102195228.4084438-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_m2m.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 58101ce07c9f9f..9009d7a33e35b1 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -258,12 +258,12 @@ static int asrc_m2m_device_run(struct fsl_asrc_pair *pair, struct snd_compr_task /* Submit DMA request */ dmaengine_submit(pair->desc[IN]); - dma_async_issue_pending(pair->desc[IN]->chan); + dma_async_issue_pending(pair->dma_chan[IN]); if (out_dma_len > 0) { if (asrc->start_before_dma && asrc->m2m_output_ready) asrc->m2m_output_ready(pair); dmaengine_submit(pair->desc[OUT]); - dma_async_issue_pending(pair->desc[OUT]->chan); + dma_async_issue_pending(pair->dma_chan[OUT]); } if (!asrc->start_before_dma) From c64fd9e2efeb6cb4cdc3f98fde96ad543e1356ab Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 31 Oct 2026 13:14:26 +0800 Subject: [PATCH 107/186] ALSA: hda: Serialize codec probe and remove on the same bus The probe and remove of different codecs on one HD-audio bus can run at the same time, for example when two codecs are bound and unbound through sysfs. A codec probe on a registered card calls snd_card_register(), which walks card->devices in snd_device_register_all(), while the remove of another codec frees its PCMs and deletes them from the same list. The list has no lock of its own. The controller probe and the reconfig sysfs knob also call snd_card_register() after the codecs are bound, so a codec bound or unbound through sysfs at that time changes the list as well. Add a mutex to struct hdac_bus and hold it in hda_codec_driver_probe() and hda_codec_driver_remove(). Add snd_hda_card_register(), which registers the card of a bus under the same mutex, and use it in the controller drivers and in reconfig_codec(). The ASoC ext_ops paths are unchanged. Fixes: bcd96557bd0a ("ALSA: hda - Build PCMs and controls at codec driver probe") Suggested-by: Takashi Iwai Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-a4703edca61d34aa679b-v12-b75145090f6bcd7cf145-1@gmail.com --- include/sound/hda_codec.h | 1 + include/sound/hdaudio.h | 1 + sound/hda/common/bind.c | 26 ++++++++++++++++++++++++++ sound/hda/common/sysfs.c | 2 +- sound/hda/controllers/acpi.c | 2 +- sound/hda/controllers/cix-ipbloq.c | 2 +- sound/hda/controllers/intel.c | 2 +- sound/hda/controllers/tegra.c | 2 +- sound/hda/core/bus.c | 1 + 9 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index c7f29ad64e02c8..084f06488a95f6 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -318,6 +318,7 @@ int snd_hda_codec_device_new(struct hda_bus *bus, struct snd_card *card, int snd_hda_codec_configure(struct hda_codec *codec); int snd_hda_codec_update_widgets(struct hda_codec *codec); void snd_hda_codec_register(struct hda_codec *codec); +int snd_hda_card_register(struct hda_bus *bus); void snd_hda_codec_unregister(struct hda_codec *codec); void snd_hda_codec_cleanup_for_unbind(struct hda_codec *codec); diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h index 04fd4794bceee5..682e10a0f6dbaa 100644 --- a/include/sound/hdaudio.h +++ b/include/sound/hdaudio.h @@ -367,6 +367,7 @@ struct hdac_bus { spinlock_t reg_lock; struct mutex cmd_mutex; struct mutex lock; + struct mutex codec_bind_lock; /* codec driver bind, card register */ /* DRM component interface */ struct drm_audio_component *audio_component; diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index 957feace0f58ee..de867f34f2906b 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -104,6 +104,12 @@ static int hda_codec_driver_probe(struct device *dev) if (WARN_ON(!codec->preset)) return -EINVAL; + /* + * snd_card_register() below walks the device list of the card, which + * the removal of another codec changes when it frees its PCMs. + */ + mutex_lock(&codec->bus->core.codec_bind_lock); + err = snd_hda_codec_set_name(codec, codec->preset->name); if (err < 0) goto error; @@ -140,6 +146,7 @@ static int hda_codec_driver_probe(struct device *dev) } codec->core.lazy_cache = true; + mutex_unlock(&codec->bus->core.codec_bind_lock); return 0; error_module: @@ -153,6 +160,7 @@ static int hda_codec_driver_probe(struct device *dev) error: snd_hda_codec_cleanup_for_unbind(codec); codec->preset = NULL; + mutex_unlock(&codec->bus->core.codec_bind_lock); return err; } @@ -171,6 +179,7 @@ static int hda_codec_driver_remove(struct device *dev) return codec->bus->core.ext_ops->hdev_detach(&codec->core); } + guard(mutex)(&codec->bus->core.codec_bind_lock); snd_hda_codec_disconnect_pcms(codec); snd_hda_jack_tbl_disconnect(codec); snd_refcount_sync(&codec->pcm_ref); @@ -184,6 +193,23 @@ static int hda_codec_driver_remove(struct device *dev) return 0; } +/** + * snd_hda_card_register - register the card of an HD-audio bus + * @bus: HD-audio bus + * + * snd_card_register() walks the device list of the card, which codec driver + * probe and remove on the same bus change. Register the card with them + * excluded. + * + * Return: zero or a negative error code. + */ +int snd_hda_card_register(struct hda_bus *bus) +{ + guard(mutex)(&bus->core.codec_bind_lock); + return snd_card_register(bus->card); +} +EXPORT_SYMBOL_GPL(snd_hda_card_register); + static void hda_codec_driver_shutdown(struct device *dev) { snd_hda_codec_shutdown(dev_to_hda_codec(dev)); diff --git a/sound/hda/common/sysfs.c b/sound/hda/common/sysfs.c index bf65223fde10da..ac08ee6f9c10e0 100644 --- a/sound/hda/common/sysfs.c +++ b/sound/hda/common/sysfs.c @@ -144,7 +144,7 @@ static int reconfig_codec(struct hda_codec *codec) err = device_reprobe(hda_codec_dev(codec)); if (err < 0) return err; - return snd_card_register(codec->card); + return snd_hda_card_register(codec->bus); } /* diff --git a/sound/hda/controllers/acpi.c b/sound/hda/controllers/acpi.c index 04b261dde22eaf..87e5f3f14a2b92 100644 --- a/sound/hda/controllers/acpi.c +++ b/sound/hda/controllers/acpi.c @@ -166,7 +166,7 @@ static void hda_acpi_probe_work(struct work_struct *work) if (err < 0) return; - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) return; diff --git a/sound/hda/controllers/cix-ipbloq.c b/sound/hda/controllers/cix-ipbloq.c index c1084a915c1363..c193dc1428c24d 100644 --- a/sound/hda/controllers/cix-ipbloq.c +++ b/sound/hda/controllers/cix-ipbloq.c @@ -80,7 +80,7 @@ static int cix_ipbloq_hda_probe_codec(struct cix_ipbloq_hda *hda) return err; } - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) { dev_err(hda->dev, "card register failed: %d\n", err); return err; diff --git a/sound/hda/controllers/intel.c b/sound/hda/controllers/intel.c index 29679fe7f40132..0a62723b345496 100644 --- a/sound/hda/controllers/intel.c +++ b/sound/hda/controllers/intel.c @@ -2434,7 +2434,7 @@ static int azx_probe_continue(struct azx *chip, bool free_on_error) } } - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) goto out_free; diff --git a/sound/hda/controllers/tegra.c b/sound/hda/controllers/tegra.c index 674cc0f79c3c88..d601aa3db8b414 100644 --- a/sound/hda/controllers/tegra.c +++ b/sound/hda/controllers/tegra.c @@ -607,7 +607,7 @@ static void hda_tegra_probe_work(struct work_struct *work) if (err < 0) return; - err = snd_card_register(chip->card); + err = snd_hda_card_register(&chip->bus); if (err < 0) return; diff --git a/sound/hda/core/bus.c b/sound/hda/core/bus.c index 02462bd035e731..304110c9a6a70a 100644 --- a/sound/hda/core/bus.c +++ b/sound/hda/core/bus.c @@ -44,6 +44,7 @@ int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev, spin_lock_init(&bus->reg_lock); mutex_init(&bus->cmd_mutex); mutex_init(&bus->lock); + mutex_init(&bus->codec_bind_lock); INIT_LIST_HEAD(&bus->hlink_list); init_waitqueue_head(&bus->rirb_wq); bus->irq = -1; From 1ad267d71c3503b59b1f2bcd587b86ac6db29ed8 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 31 Oct 2026 13:14:27 +0800 Subject: [PATCH 108/186] ALSA: core: Add the card file while looking up a minor An open looks up its device with snd_lookup_minor_data(), which takes a card reference, and adds its file to the card with snd_card_file_add() only afterwards. The card reference does not keep alive a device that its driver frees while the card stays registered, as the HD-audio codec remove does with its PCMs. A remove that waits for the files of the card can miss an open between the lookup and snd_card_file_add(). Add snd_lookup_minor_data_file() and snd_lookup_oss_minor_data_file(), which add the file to the card while the minor lock is still held, and record the device data of the minor with the file. After the minor has been unregistered, an open either has its file on the card already or does not find the device. Add snd_card_file_sync() to wait until the files opened for a device are gone, and wake its waiters in snd_card_file_remove(). Waiting for the files of one device instead of all files of the card matters, because the card files include the control device, which a sound server keeps open for as long as it runs. Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-a4703edca61d34aa679b-v12-b75145090f6bcd7cf145-2@gmail.com --- include/sound/core.h | 7 +++++ sound/core/init.c | 61 +++++++++++++++++++++++++++++++++++++----- sound/core/sound.c | 34 +++++++++++++++++++++++ sound/core/sound_oss.c | 21 +++++++++++++++ 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/include/sound/core.h b/include/sound/core.h index d6584967fd491b..4cecf04204f406 100644 --- a/include/sound/core.h +++ b/include/sound/core.h @@ -271,12 +271,16 @@ int snd_register_device(int type, struct snd_card *card, int dev, void *private_data, struct device *device); int snd_unregister_device(struct device *dev); void *snd_lookup_minor_data(unsigned int minor, int type); +void *snd_lookup_minor_data_file(unsigned int minor, int type, + struct file *file); #ifdef CONFIG_SND_OSSEMUL int snd_register_oss_device(int type, struct snd_card *card, int dev, const struct file_operations *f_ops, void *private_data); int snd_unregister_oss_device(int type, struct snd_card *card, int dev); void *snd_lookup_oss_minor_data(unsigned int minor, int type); +void *snd_lookup_oss_minor_data_file(unsigned int minor, int type, + struct file *file); #endif int snd_minor_info_init(void); @@ -323,7 +327,10 @@ int snd_card_add_dev_attr(struct snd_card *card, const struct attribute_group *group); int snd_component_add(struct snd_card *card, const char *component); int snd_card_file_add(struct snd_card *card, struct file *file); +int snd_card_file_add_data(struct snd_card *card, struct file *file, + const void *data); int snd_card_file_remove(struct snd_card *card, struct file *file); +void snd_card_file_sync(struct snd_card *card, const void *data); struct snd_card *snd_card_ref(int card); diff --git a/sound/core/init.c b/sound/core/init.c index d518cb83877711..ee660dc4098b0f 100644 --- a/sound/core/init.c +++ b/sound/core/init.c @@ -24,6 +24,7 @@ /* monitor files for graceful shutdown (hotplug) */ struct snd_monitor_file { struct file *file; + const void *data; /* device data from the minor lookup, or NULL */ const struct file_operations *disconnected_f_op; struct list_head shutdown_list; /* still need to shutdown */ struct list_head list; /* link of monitor files */ @@ -1075,17 +1076,18 @@ int snd_component_add(struct snd_card *card, const char *component) EXPORT_SYMBOL(snd_component_add); /** - * snd_card_file_add - add the file to the file list of the card + * snd_card_file_add_data - add the file of a device to the file list * @card: soundcard structure * @file: file pointer + * @data: the device data the file is opened for * - * This function adds the file to the file linked-list of the card. - * This linked-list is used to keep tracking the connection state, - * and to avoid the release of busy resources by hotplug. + * Like snd_card_file_add(), but also records @data for + * snd_card_file_sync(). * * Return: zero or a negative error code. */ -int snd_card_file_add(struct snd_card *card, struct file *file) +int snd_card_file_add_data(struct snd_card *card, struct file *file, + const void *data) { struct snd_monitor_file *mfile; @@ -1093,6 +1095,7 @@ int snd_card_file_add(struct snd_card *card, struct file *file) if (mfile == NULL) return -ENOMEM; mfile->file = file; + mfile->data = data; mfile->disconnected_f_op = NULL; INIT_LIST_HEAD(&mfile->shutdown_list); guard(spinlock)(&card->files_lock); @@ -1104,6 +1107,22 @@ int snd_card_file_add(struct snd_card *card, struct file *file) get_device(&card->card_dev); return 0; } + +/** + * snd_card_file_add - add the file to the file list of the card + * @card: soundcard structure + * @file: file pointer + * + * This function adds the file to the file linked-list of the card. + * This linked-list is used to keep tracking the connection state, + * and to avoid the release of busy resources by hotplug. + * + * Return: zero or a negative error code. + */ +int snd_card_file_add(struct snd_card *card, struct file *file) +{ + return snd_card_file_add_data(card, file, NULL); +} EXPORT_SYMBOL(snd_card_file_add); /** @@ -1135,7 +1154,7 @@ int snd_card_file_remove(struct snd_card *card, struct file *file) break; } } - if (list_empty(&card->files_list)) + if (list_empty(&card->files_list) || (found && found->data)) wake_up_all(&card->remove_sleep); } if (!found) { @@ -1148,6 +1167,36 @@ int snd_card_file_remove(struct snd_card *card, struct file *file) } EXPORT_SYMBOL(snd_card_file_remove); +static bool snd_card_has_data_file(struct snd_card *card, const void *data) +{ + struct snd_monitor_file *mfile; + + list_for_each_entry(mfile, &card->files_list, list) { + if (mfile->data == data) + return true; + } + return false; +} + +/** + * snd_card_file_sync - wait until the files opened for a device are closed + * @card: soundcard structure + * @data: the device data registered for the minors of the device + * + * Waits until every file added for @data by snd_lookup_minor_data_file() + * or snd_lookup_oss_minor_data_file() has been removed from @card. The + * minors of the device must already be unregistered, so that no new file + * can be added for it. + */ +void snd_card_file_sync(struct snd_card *card, const void *data) +{ + guard(spinlock_irq)(&card->files_lock); + wait_event_lock_irq(card->remove_sleep, + !snd_card_has_data_file(card, data), + card->files_lock); +} +EXPORT_SYMBOL_GPL(snd_card_file_sync); + #ifdef CONFIG_PM /** * snd_power_ref_and_wait - wait until the card gets powered up diff --git a/sound/core/sound.c b/sound/core/sound.c index 32637c40df05cb..ba226aa7affd3c 100644 --- a/sound/core/sound.c +++ b/sound/core/sound.c @@ -115,6 +115,40 @@ void *snd_lookup_minor_data(unsigned int minor, int type) } EXPORT_SYMBOL(snd_lookup_minor_data); +/** + * snd_lookup_minor_data_file - get user data of a device and add the file + * @minor: the minor number + * @type: device type (SNDRV_DEVICE_TYPE_XXX) + * @file: the file being opened + * + * Like snd_lookup_minor_data(), but instead of taking a reference to the + * card, adds @file to the file list of the card while the minor is still + * registered, and records the user data for snd_card_file_sync(). The + * caller must call snd_card_file_remove() when it is done with the file. + * + * Return: The user data pointer if the specified device is found, %NULL if + * it is not found, or an error pointer if the file cannot be added. + */ +void *snd_lookup_minor_data_file(unsigned int minor, int type, + struct file *file) +{ + struct snd_minor *mreg; + int err; + + if (minor >= ARRAY_SIZE(snd_minors)) + return NULL; + guard(mutex)(&sound_mutex); + mreg = snd_minors[minor]; + if (!mreg || mreg->type != type || !mreg->private_data || + !mreg->card_ptr) + return NULL; + err = snd_card_file_add_data(mreg->card_ptr, file, mreg->private_data); + if (err < 0) + return ERR_PTR(err); + return mreg->private_data; +} +EXPORT_SYMBOL(snd_lookup_minor_data_file); + #ifdef CONFIG_MODULES static struct snd_minor *autoload_device(unsigned int minor) { diff --git a/sound/core/sound_oss.c b/sound/core/sound_oss.c index 7eb49f2cada5d0..2f0bc481a46d40 100644 --- a/sound/core/sound_oss.c +++ b/sound/core/sound_oss.c @@ -41,6 +41,27 @@ void *snd_lookup_oss_minor_data(unsigned int minor, int type) } EXPORT_SYMBOL(snd_lookup_oss_minor_data); +/* Like snd_lookup_minor_data_file(), for OSS minors */ +void *snd_lookup_oss_minor_data_file(unsigned int minor, int type, + struct file *file) +{ + struct snd_minor *mreg; + int err; + + if (minor >= ARRAY_SIZE(snd_oss_minors)) + return NULL; + guard(mutex)(&sound_oss_mutex); + mreg = snd_oss_minors[minor]; + if (!mreg || mreg->type != type || !mreg->private_data || + !mreg->card_ptr) + return NULL; + err = snd_card_file_add_data(mreg->card_ptr, file, mreg->private_data); + if (err < 0) + return ERR_PTR(err); + return mreg->private_data; +} +EXPORT_SYMBOL(snd_lookup_oss_minor_data_file); + static int snd_oss_kernel_minor(int type, struct snd_card *card, int dev) { int minor; From 02c816a7162007c0a89dd930568906507aee74ba Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 31 Oct 2026 13:14:28 +0800 Subject: [PATCH 109/186] ALSA: pcm: Add the file to the card while looking up the minor Use snd_lookup_minor_data_file() and snd_lookup_oss_minor_data_file() in the native and OSS PCM opens, so that the file is on the card before the PCM minor can be unregistered. The opens then no longer need the extra card reference from the lookup. Without that reference, a failed open also no longer reads pcm->card after it has removed its file, which would race with a remove that waits for the files before freeing the PCM. Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-a4703edca61d34aa679b-v12-b75145090f6bcd7cf145-3@gmail.com --- sound/core/oss/pcm_oss.c | 19 ++++++------------- sound/core/pcm_native.c | 37 +++++++++++++++---------------------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c index e8df27d09b9710..2d99df900f76dc 100644 --- a/sound/core/oss/pcm_oss.c +++ b/sound/core/oss/pcm_oss.c @@ -2497,15 +2497,12 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) nonseekable_open(inode, file); - pcm = snd_lookup_oss_minor_data(iminor(inode), - SNDRV_OSS_DEVICE_TYPE_PCM); - if (pcm == NULL) { - err = -ENODEV; - goto __error1; - } - err = snd_card_file_add(pcm->card, file); - if (err < 0) - goto __error1; + pcm = snd_lookup_oss_minor_data_file(iminor(inode), + SNDRV_OSS_DEVICE_TYPE_PCM, file); + if (IS_ERR(pcm)) + return PTR_ERR(pcm); + if (!pcm) + return -ENODEV; if (!try_module_get(pcm->card->module)) { err = -ENODEV; goto __error2; @@ -2558,16 +2555,12 @@ static int snd_pcm_oss_open(struct inode *inode, struct file *file) mutex_unlock(&pcm->open_mutex); if (err < 0) goto __error; - snd_card_unref(pcm->card); return err; __error: module_put(pcm->card->module); __error2: snd_card_file_remove(pcm->card, file); - __error1: - if (pcm) - snd_card_unref(pcm->card); return err; } diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c index 29c428cb6debd8..50dbc73a66aa82 100644 --- a/sound/core/pcm_native.c +++ b/sound/core/pcm_native.c @@ -2934,45 +2934,39 @@ static int snd_pcm_open_file(struct file *file, static int snd_pcm_playback_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; - int err; nonseekable_open(inode, file); - pcm = snd_lookup_minor_data(iminor(inode), - SNDRV_DEVICE_TYPE_PCM_PLAYBACK); - err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); - if (pcm) - snd_card_unref(pcm->card); - return err; + pcm = snd_lookup_minor_data_file(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_PLAYBACK, file); + if (IS_ERR(pcm)) + return PTR_ERR(pcm); + if (!pcm) + return -ENODEV; + return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_PLAYBACK); } static int snd_pcm_capture_open(struct inode *inode, struct file *file) { struct snd_pcm *pcm; - int err; nonseekable_open(inode, file); - pcm = snd_lookup_minor_data(iminor(inode), - SNDRV_DEVICE_TYPE_PCM_CAPTURE); - err = snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); - if (pcm) - snd_card_unref(pcm->card); - return err; + pcm = snd_lookup_minor_data_file(iminor(inode), + SNDRV_DEVICE_TYPE_PCM_CAPTURE, file); + if (IS_ERR(pcm)) + return PTR_ERR(pcm); + if (!pcm) + return -ENODEV; + return snd_pcm_open(file, pcm, SNDRV_PCM_STREAM_CAPTURE); } +/* called with the file added to the card by snd_lookup_minor_data_file() */ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) { int err; wait_queue_entry_t wait; - if (pcm == NULL) { - err = -ENODEV; - goto __error1; - } - err = snd_card_file_add(pcm->card, file); - if (err < 0) - goto __error1; if (!try_module_get(pcm->card->module)) { err = -ENODEV; goto __error2; @@ -3014,7 +3008,6 @@ static int snd_pcm_open(struct file *file, struct snd_pcm *pcm, int stream) module_put(pcm->card->module); __error2: snd_card_file_remove(pcm->card, file); - __error1: return err; } From 1fde88bd72ad7a3c4a855d3ce2dcc597738eb5b1 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 31 Oct 2026 13:14:29 +0800 Subject: [PATCH 110/186] ALSA: pcm: Refuse to open a disconnected PCM An open that found the PCM before snd_pcm_dev_disconnect() can still attach a substream after it, because nothing in the open path checks whether the PCM was disconnected. When only the PCM is disconnected, as on an HD-audio codec unbind, the open then succeeds on a device that is going away, and its file delays the remove until it is closed. Mark the PCM as disconnected under open_mutex in snd_pcm_dev_disconnect() and make snd_pcm_attach_substream() fail with -ENODEV for it. Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-a4703edca61d34aa679b-v12-b75145090f6bcd7cf145-4@gmail.com --- include/sound/pcm.h | 1 + sound/core/pcm.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/include/sound/pcm.h b/include/sound/pcm.h index 1cf511f8d880ea..04f58c2ec5c623 100644 --- a/include/sound/pcm.h +++ b/include/sound/pcm.h @@ -548,6 +548,7 @@ struct snd_pcm { bool internal; /* pcm is for internal use only */ bool nonatomic; /* whole PCM operations are in non-atomic context */ bool no_device_suspend; /* don't invoke device PM suspend */ + bool disconnected; /* disconnected, no new open */ #if IS_ENABLED(CONFIG_SND_PCM_OSS) struct snd_pcm_oss oss; #endif diff --git a/sound/core/pcm.c b/sound/core/pcm.c index 6c8274414c2265..3549f0b8ffd2d5 100644 --- a/sound/core/pcm.c +++ b/sound/core/pcm.c @@ -897,6 +897,8 @@ int snd_pcm_attach_substream(struct snd_pcm *pcm, int stream, stream != SNDRV_PCM_STREAM_CAPTURE)) return -EINVAL; *rsubstream = NULL; + if (pcm->disconnected) + return -ENODEV; pstr = &pcm->streams[stream]; if (pstr->substream == NULL || pstr->substream_count == 0) return -ENODEV; @@ -1103,6 +1105,7 @@ static int snd_pcm_dev_disconnect(struct snd_device *device) guard(mutex)(®ister_mutex); scoped_guard(mutex, &pcm->open_mutex) { + pcm->disconnected = true; wake_up(&pcm->open_wait); list_del_init(&pcm->list); From a52a60c0cea453992676b05d95a8668497c48a85 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Sat, 31 Oct 2026 13:14:30 +0800 Subject: [PATCH 111/186] ALSA: hda: Wait for the files of the codec PCMs before freeing them The codec remove waits for codec->pcm_ref, which only counts the streams opened through the HD-audio PCM ops. A file uses the PCM before that: snd_pcm_open() takes pcm->open_mutex, and an OSS open walks the setup list of the PCM, before a substream is opened. snd_pcm_release() also still takes pcm->open_mutex after the close callback has dropped the reference. When a codec is unbound through sysfs while the card stays registered, the remove can free the PCMs under such a file: PCM open codec unbind -------- ------------ snd_lookup_minor_data() snd_card_file_add() snd_hda_codec_disconnect_pcms() snd_refcount_sync(&codec->pcm_ref) snd_hda_codec_cleanup_for_unbind() snd_pcm_free() mutex_lock(&pcm->open_mutex) Opening the PCM while the codec is unbound and bound in a loop triggers it: BUG: KASAN: slab-use-after-free in mutex_spin_on_owner+0x94/0x2b0 Read of size 8 at addr ffff888005d52a58 by task hdatest/90 Call Trace: __mutex_lock+0x16a4/0x24a0 snd_pcm_open+0x230/0x600 snd_pcm_playback_open+0x5e/0xb0 Freed by task 94: kfree+0x2e5/0x510 snd_pcm_free+0x9d/0x150 __snd_device_free+0x121/0x370 snd_hda_codec_cleanup_for_unbind+0x219/0x920 hda_codec_driver_remove+0x1a7/0x2b0 unbind_store+0xd1/0xf0 An open can also get past the wait and call into the codec driver after the remove has cleared its state, which ends in a NULL pointer dereference in playback_pcm_open(). After disconnecting the PCMs, wait until the files opened on them are closed. Do the same on the probe error path, which frees PCMs that a late probe may already have registered. Fixes: bcd96557bd0a ("ALSA: hda - Build PCMs and controls at codec driver probe") Suggested-by: Takashi Iwai Signed-off-by: Cen Zhang Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/pm-pmb-a4703edca61d34aa679b-v12-b75145090f6bcd7cf145-5@gmail.com --- sound/hda/common/bind.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/sound/hda/common/bind.c b/sound/hda/common/bind.c index de867f34f2906b..66949c4ff21ff7 100644 --- a/sound/hda/common/bind.c +++ b/sound/hda/common/bind.c @@ -84,6 +84,20 @@ int snd_hda_codec_set_name(struct hda_codec *codec, const char *name) } EXPORT_SYMBOL_GPL(snd_hda_codec_set_name); +/* + * A file opened on a codec PCM can use the PCM until it is closed, also + * after the PCM was disconnected. Wait for those files. + */ +static void hda_codec_sync_pcm_files(struct hda_codec *codec) +{ + struct hda_pcm *cpcm; + + list_for_each_entry(cpcm, &codec->pcm_list_head, list) { + if (cpcm->pcm) + snd_card_file_sync(codec->card, cpcm->pcm); + } +} + static int hda_codec_driver_probe(struct device *dev) { struct hda_codec *codec = dev_to_hda_codec(dev); @@ -152,6 +166,8 @@ static int hda_codec_driver_probe(struct device *dev) error_module: snd_hdac_device_disable_unsol(&codec->core); cancel_delayed_work_sync(&codec->jackpoll_work); + snd_hda_codec_disconnect_pcms(codec); + hda_codec_sync_pcm_files(codec); if (probed && driver->ops->remove) driver->ops->remove(codec); error_module_put: @@ -182,6 +198,7 @@ static int hda_codec_driver_remove(struct device *dev) guard(mutex)(&codec->bus->core.codec_bind_lock); snd_hda_codec_disconnect_pcms(codec); snd_hda_jack_tbl_disconnect(codec); + hda_codec_sync_pcm_files(codec); snd_refcount_sync(&codec->pcm_ref); snd_power_sync_ref(codec->bus->card); From f804ad135529a2cec95dad9551e72263dfde543e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:28 +0700 Subject: [PATCH 112/186] ASoC: fsl_sai: Fix PM QoS cleanup on resume failure If regcache_sync() fails during runtime resume, the CPU latency QoS request remains active even though the resume operation failed. Switch the regmap back to cache-only mode and remove the PM QoS request before disabling the clocks, ensuring that the resources enabled earlier in the resume path are properly cleaned up. Fixes: 907e0cdebc1f ("ASoC: fsl_sai: Add pm qos cpu latency support") Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index d232c8f5306154..042885ba415e6f 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1981,7 +1981,7 @@ static int fsl_sai_runtime_resume(struct device *dev) ret = regcache_sync(sai->regmap); if (ret) - goto disable_rx_clk; + goto remove_qos; if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output) regmap_update_bits(sai->regmap, FSL_SAI_TCSR(ofs), @@ -1989,7 +1989,12 @@ static int fsl_sai_runtime_resume(struct device *dev) return 0; -disable_rx_clk: +remove_qos: + regcache_cache_only(sai->regmap, true); + + if (sai->soc_data->flags & PMQOS_CPU_LATENCY) + cpu_latency_qos_remove_request(&sai->pm_qos_req); + if (sai->mclk_streams & BIT(SNDRV_PCM_STREAM_CAPTURE)) clk_disable_unprepare(sai->mclk_clk[sai->mclk_id[0]]); disable_tx_clk: From ee95db53352231a59ac4faee6b639eed7b813c16 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:29 +0700 Subject: [PATCH 113/186] ASoC: fsl_sai: Use optional clock getter for MCLKs The current code uses devm_clk_get() and sets the clock pointer to NULL when it returns an error. However, this silently ignores errors such as -EPROBE_DEFER, allowing the driver to continue probing without the requested clock. Use devm_clk_get_optional() to handle missing optional clocks automatically, and dev_err_probe() to properly propagate other errors, including -EPROBE_DEFER. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 042885ba415e6f..44a34f48a0cf87 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1589,12 +1589,10 @@ static int fsl_sai_probe(struct platform_device *pdev) for (i = 1; i < FSL_SAI_MCLK_MAX; i++) { sprintf(tmp, "mclk%d", i); - sai->mclk_clk[i] = devm_clk_get(dev, tmp); - if (IS_ERR(sai->mclk_clk[i])) { - dev_err(dev, "failed to get mclk%d clock: %ld\n", - i, PTR_ERR(sai->mclk_clk[i])); - sai->mclk_clk[i] = NULL; - } + sai->mclk_clk[i] = devm_clk_get_optional(dev, tmp); + if (IS_ERR(sai->mclk_clk[i])) + return dev_err_probe(dev, PTR_ERR(sai->mclk_clk[i]), + "failed to get mclk%d clock\n", i); } if (sai->soc_data->mclk0_is_mclk1) From b73124cd7b91b46ea9c97935178813930f6d21ef Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:30 +0700 Subject: [PATCH 114/186] ASoC: fsl_sai: Remove redundant error messages The errors are either reported directly by these functions or already reported deeper in the call chain, so additional dev_err() and dev_warn() calls are unnecessary. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 44a34f48a0cf87..5532fab80be0a3 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1615,10 +1615,8 @@ static int fsl_sai_probe(struct platform_device *pdev) /* read dataline mask for rx and tx*/ ret = fsl_sai_read_dlcfg(sai); - if (ret < 0) { - dev_err(dev, "failed to read dlcfg %d\n", ret); + if (ret < 0) return ret; - } irq = platform_get_irq(pdev, 0); if (irq < 0) @@ -1698,9 +1696,7 @@ static int fsl_sai_probe(struct platform_device *pdev) if (ret < 0) dev_warn(dev, "Error reading SAI version: %d\n", ret); - ret = fsl_sai_reset_hw(dev); - if (ret < 0) - dev_warn(dev, "Failed to reset hardware: %d\n", ret); + fsl_sai_reset_hw(dev); /* Select MCLK direction */ if (sai->mclk_direction_output && @@ -1715,10 +1711,8 @@ static int fsl_sai_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, fsl_sai_isr, IRQF_SHARED, np->name, sai); - if (ret) { - dev_err(dev, "failed to claim irq %u\n", irq); + if (ret) goto err_pm_get_sync; - } if (of_device_is_compatible(np, "fsl,imx952-sai") && !of_property_read_string(np, "fsl,sai-amix-mode", &str)) { From 702a9978d58007c25c265e8595d822dcff66d949 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:31 +0700 Subject: [PATCH 115/186] ASoC: fsl_sai: Propagate device tree property read errors Return the original error codes from of_property_read_u32_index() instead of replacing them with -EINVAL. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-5-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 5532fab80be0a3..cb07e7d1313257 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1495,15 +1495,15 @@ static int fsl_sai_read_dlcfg(struct fsl_sai *sai) */ ret = of_property_read_u32_index(np, propname, index++, &type); if (ret) - return -EINVAL; + return ret; ret = of_property_read_u32_index(np, propname, index++, &rx); if (ret) - return -EINVAL; + return ret; ret = of_property_read_u32_index(np, propname, index++, &tx); if (ret) - return -EINVAL; + return ret; if ((rx & ~soc_dl) || (tx & ~soc_dl)) { dev_err(dev, "dataline cfg[%d] setting error, mask is 0x%x\n", i, soc_dl); From a11ee78a0b250e85f585e60489aebf05dcd80370 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:32 +0700 Subject: [PATCH 116/186] ASoC: fsl_sai: Drop redundant dataline mask AND The rx/tx masks with bits outside soc_dl are already rejected just above, so masking them with soc_dl again is no-op. Remove it. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-6-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index cb07e7d1313257..8315a0d8893ba0 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -1510,9 +1510,6 @@ static int fsl_sai_read_dlcfg(struct fsl_sai *sai) return -EINVAL; } - rx = rx & soc_dl; - tx = tx & soc_dl; - cfg[i].type = type; cfg[i].pins[0] = hweight8(rx); cfg[i].mask[0] = rx; From db36f0b28b1179a5be7337d7075a879b74707624 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:33 +0700 Subject: [PATCH 117/186] ASoC: fsl_sai: Warn if TE/RE does not clear on disable fsl_sai_config_disable() polls TE/RE with an open-coded loop and continues silently if the bit does not clear within 1ms. Also, if regmap_read() fails, the loop condition evaluates xcsr uninitialized. Use regmap_read_poll_timeout_atomic() and warn on timeout. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-7-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 8315a0d8893ba0..f75a1d61d7b720 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -849,7 +849,8 @@ static void fsl_sai_config_disable(struct fsl_sai *sai, int dir) { unsigned int ofs = sai->soc_data->reg_offset; bool tx = dir == TX; - u32 xcsr, count = 100, mask; + u32 xcsr, mask; + int ret; if (sai->soc_data->mclk_with_tere && sai->mclk_direction_output) mask = FSL_SAI_CSR_TERE; @@ -860,10 +861,15 @@ static void fsl_sai_config_disable(struct fsl_sai *sai, int dir) mask, 0); /* TERE will remain set till the end of current frame */ - do { - udelay(10); - regmap_read(sai->regmap, FSL_SAI_xCSR(tx, ofs), &xcsr); - } while (--count && xcsr & FSL_SAI_CSR_TERE); + ret = regmap_read_poll_timeout_atomic(sai->regmap, FSL_SAI_xCSR(tx, ofs), + xcsr, !(xcsr & FSL_SAI_CSR_TERE), + 10, 1000); + if (ret == -ETIMEDOUT) + dev_warn(&sai->pdev->dev, "%cx not disabled at frame end\n", + tx ? 'T' : 'R'); + else if (ret) + dev_err(&sai->pdev->dev, "failed to read %cSCR : %d\n", + tx ? 'T' : 'R', ret); regmap_update_bits(sai->regmap, FSL_SAI_xCSR(tx, ofs), FSL_SAI_CSR_FR, FSL_SAI_CSR_FR); From be10761cad310a0e59c238ae566c46fab0566d63 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:34 +0700 Subject: [PATCH 118/186] ASoC: fsl_sai: Clarify fsl_sai_dir_is_synced() documentation The summary line of fsl_sai_dir_is_synced() reads as if @dir follows the clocks of the opposite stream, while the function returns true when @dir provides the clocks and the opposite stream is synchronous. Reword the description to match the behavior and document the return value. No functional change. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-8-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index f75a1d61d7b720..25243b6859d6c5 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -88,14 +88,16 @@ static const struct snd_kcontrol_new fsl_sai_timestamp_ctrls[] = { }; /** - * fsl_sai_dir_is_synced - Check if stream is synced by the opposite stream + * fsl_sai_dir_is_synced - Check if the opposite stream is synced to @dir + * @sai: SAI context + * @dir: stream direction, TX or RX * - * SAI supports synchronous mode using bit/frame clocks of either Transmitter's - * or Receiver's for both streams. This function is used to check if clocks of - * the stream's are synced by the opposite stream. + * SAI supports synchronous mode, where one direction uses the bit clock and + * frame sync of the other. This function checks whether @dir provides the + * clocks, i.e. @dir runs in asynchronous mode while the opposite direction + * runs in synchronous mode. * - * @sai: SAI context - * @dir: stream direction + * Return: true if the opposite direction uses the clocks of @dir. */ static inline bool fsl_sai_dir_is_synced(struct fsl_sai *sai, int dir) { From b485bfcb12a8e01e8524e6fd0be5d4d43e19b72d Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:35 +0700 Subject: [PATCH 119/186] ASoC: fsl_sai: Check regmap_read() return value in ISR The IRQ is shared, so fsl_sai_isr() may run while the SAI is runtime suspended. The regmap is then in cache-only mode, so regmap_read() fails and xcsr is used uninitialized. Check the return value of regmap_read() for both TCSR and RCSR, and skip the handling of the corresponding direction on failure. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-9-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 25243b6859d6c5..5c50ed7c2258c8 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -148,7 +148,8 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) mask = (FSL_SAI_FLAGS >> FSL_SAI_CSR_xIE_SHIFT) << FSL_SAI_CSR_xF_SHIFT; /* Tx IRQ */ - regmap_read(sai->regmap, FSL_SAI_TCSR(ofs), &xcsr); + if (regmap_read(sai->regmap, FSL_SAI_TCSR(ofs), &xcsr)) + return IRQ_NONE; flags = xcsr & mask; if (flags) @@ -179,7 +180,8 @@ static irqreturn_t fsl_sai_isr(int irq, void *devid) irq_rx: /* Rx IRQ */ - regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr); + if (regmap_read(sai->regmap, FSL_SAI_RCSR(ofs), &xcsr)) + goto out; flags = xcsr & mask; if (flags) From 279ea69e6df5de9d2ee9c03fd903e8a285ad2f3e Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:36 +0700 Subject: [PATCH 120/186] ASoC: fsl_sai: Reject bclk ratio smaller than slot width In hw_params(), when a bclk ratio is set, the number of slots is computed as bclk_ratio / slot_width. If the ratio is smaller than the slot width, slots becomes zero and the following DIV_ROUND_UP(channels, slots) divides by zero. Return -EINVAL in this case. Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-10-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index 5c50ed7c2258c8..c61703ddb6f8e5 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -632,8 +632,14 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, if (sai->slots[tx]) slots = sai->slots[tx]; - else if (sai->bclk_ratio) + else if (sai->bclk_ratio) { + if (sai->bclk_ratio < slot_width) { + dev_err(cpu_dai->dev, "bclk ratio %u < slot width %u\n", + sai->bclk_ratio, slot_width); + return -EINVAL; + } slots = sai->bclk_ratio / slot_width; + } pins = DIV_ROUND_UP(channels, slots); From 9580e6ecce6364b9370c391593c7d973d2726464 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Thu, 5 Nov 2026 17:42:37 +0700 Subject: [PATCH 121/186] ASoC: fsl_sai: Don't write CHMOD to the other direction's xCR4 When the current direction is synced to the other one, hw_params() also writes the frame configuration to the other direction's xCR4. The mask includes CHMOD, whose value is set by the current direction (Tx only) but written to the other one, so it is wrong: - TCR4[CHMOD] is cleared, while Tx needs it set (output mode). - RCR4 bit 5 is set, while it is reserved and must be written as 0. CHMOD is not part of the frame configuration, so drop it from the mask. Fixes: f4c4b1bb2f5a ("ASoC: fsl_sai: Set SAI Channel Mode to Output Mode") Signed-off-by: bui duc phuc Reviewed-by: Shengjiu Wang Link: https://patch.msgid.link/20261105104237.38542-11-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_sai.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c index c61703ddb6f8e5..430e896d064e3b 100644 --- a/sound/soc/fsl/fsl_sai.c +++ b/sound/soc/fsl/fsl_sai.c @@ -720,8 +720,7 @@ static int fsl_sai_hw_params(struct snd_pcm_substream *substream, if (fsl_sai_dir_is_synced(sai, adir)) { regmap_update_bits(sai->regmap, FSL_SAI_xCR4(!tx, ofs), - FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK | - FSL_SAI_CR4_CHMOD_MASK, + FSL_SAI_CR4_SYWD_MASK | FSL_SAI_CR4_FRSZ_MASK, val_cr4); regmap_update_bits(sai->regmap, FSL_SAI_xCR5(!tx, ofs), FSL_SAI_CR5_WNW_MASK | FSL_SAI_CR5_W0W_MASK | From 2e2f6b2a7cd440c86e65f727b19952abd2e49b5c Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 6 Nov 2026 18:37:28 +0530 Subject: [PATCH 122/186] ASoC: amd: acp: fix adr_device naming in ACP7.x ACPI match table Rename the three rt721/rt722 adr_device arrays to encode their endpoint set rather than their structural role: rt721_l0_single_adr -> rt721_l0_jack_dmic_adr rt721_l0_only -> rt721_l0_jack_amp_dmic_adr rt722_l0_single_adr -> rt722_l0_jack_amp_dmic_adr The old names (_single_adr vs _only) did not indicate which endpoint set each entry uses. The new names make it immediately clear that rt721_l0_jack_dmic_adr carries jack+dmic endpoints (used in combo configs where RT1320 provides amplification) while both *_jack_amp_dmic_adr variants carry the full jack+amp+dmic set for standalone configurations. Update all four call sites accordingly. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261106130801.506026-2-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/amd-acp7x-acpi-match.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/sound/soc/amd/acp/amd-acp7x-acpi-match.c b/sound/soc/amd/acp/amd-acp7x-acpi-match.c index 830c59966a3569..bf11ceb7478b52 100644 --- a/sound/soc/amd/acp/amd-acp7x-acpi-match.c +++ b/sound/soc/amd/acp/amd-acp7x-acpi-match.c @@ -57,7 +57,7 @@ static const struct snd_soc_acpi_endpoint jack_amp_dmic_endpoints[] = { }, }; -static const struct snd_soc_acpi_adr_device rt721_l0_single_adr[] = { +static const struct snd_soc_acpi_adr_device rt721_l0_jack_dmic_adr[] = { { .adr = 0x000030025d072101ull, .num_endpoints = ARRAY_SIZE(jack_dmic_endpoints), @@ -66,7 +66,7 @@ static const struct snd_soc_acpi_adr_device rt721_l0_single_adr[] = { } }; -static const struct snd_soc_acpi_adr_device rt721_l0_only[] = { +static const struct snd_soc_acpi_adr_device rt721_l0_jack_amp_dmic_adr[] = { { .adr = 0x000030025d072101ull, .num_endpoints = ARRAY_SIZE(jack_amp_dmic_endpoints), @@ -78,13 +78,13 @@ static const struct snd_soc_acpi_adr_device rt721_l0_only[] = { static const struct snd_soc_acpi_link_adr acp7x_rt721_only[] = { { .mask = BIT(0), - .num_adr = ARRAY_SIZE(rt721_l0_only), - .adr_d = rt721_l0_only, + .num_adr = ARRAY_SIZE(rt721_l0_jack_amp_dmic_adr), + .adr_d = rt721_l0_jack_amp_dmic_adr, }, {} }; -static const struct snd_soc_acpi_adr_device rt722_l0_single_adr[] = { +static const struct snd_soc_acpi_adr_device rt722_l0_jack_amp_dmic_adr[] = { { .adr = 0x000030025d072201ull, .num_endpoints = ARRAY_SIZE(jack_amp_dmic_endpoints), @@ -96,8 +96,8 @@ static const struct snd_soc_acpi_adr_device rt722_l0_single_adr[] = { static const struct snd_soc_acpi_link_adr acp7x_rt722_only[] = { { .mask = BIT(0), - .num_adr = ARRAY_SIZE(rt722_l0_single_adr), - .adr_d = rt722_l0_single_adr, + .num_adr = ARRAY_SIZE(rt722_l0_jack_amp_dmic_adr), + .adr_d = rt722_l0_jack_amp_dmic_adr, }, {} }; @@ -123,8 +123,8 @@ static const struct snd_soc_acpi_adr_device rt1320_l3_single_adr[] = { static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l1[] = { { .mask = BIT(0), - .num_adr = ARRAY_SIZE(rt721_l0_single_adr), - .adr_d = rt721_l0_single_adr, + .num_adr = ARRAY_SIZE(rt721_l0_jack_dmic_adr), + .adr_d = rt721_l0_jack_dmic_adr, }, { .mask = BIT(1), @@ -137,8 +137,8 @@ static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l1[] = { static const struct snd_soc_acpi_link_adr acp7x_rt721_l0_rt1320_l3[] = { { .mask = BIT(0), - .num_adr = ARRAY_SIZE(rt721_l0_single_adr), - .adr_d = rt721_l0_single_adr, + .num_adr = ARRAY_SIZE(rt721_l0_jack_dmic_adr), + .adr_d = rt721_l0_jack_dmic_adr, }, { .mask = BIT(3), From a9a7c22d3e678e3b97775d0993a4323f38d38065 Mon Sep 17 00:00:00 2001 From: Vijendar Mukunda Date: Fri, 6 Nov 2026 18:37:29 +0530 Subject: [PATCH 123/186] ASoC: amd: acp: document PDM controller topology for ACP7.B/7.F Add a block comment above the ASOC_SDW_ACP_DMIC / ASOC_SDW_ACP_DMIC1 quirk bits and annotate each bit with its role. ASOC_SDW_ACP_DMIC (BIT(5)) is a generic DMIC-enable flag used across all ACP platforms; on ACP7.B/7.F it additionally identifies the PDM0 controller. ASOC_SDW_ACP_DMIC1 (BIT(6)) selects the PDM1 controller and is ACP7.B/7.F-specific. Both ACP7.B/7.F controllers support a 2-channel stereo capture path, and BIOS wires exactly one per platform. Signed-off-by: Vijendar Mukunda Reviewed-by: Pierre-Louis Bossart Link: https://patch.msgid.link/20261106130801.506026-3-Vijendar.Mukunda@amd.com Signed-off-by: Mark Brown --- sound/soc/amd/acp/soc_amd_sdw_common.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 07837a2cfa5405..21b059b7462671 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -28,8 +28,15 @@ #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) -#define ASOC_SDW_ACP_DMIC BIT(5) -#define ASOC_SDW_ACP_DMIC1 BIT(6) /* select DMIC1 controller for ACP7.B/7.F */ +/* + * ASOC_SDW_ACP_DMIC is a generic DMIC-enable flag used across all ACP + * platforms. On ACP7.B/7.F it additionally identifies the PDM0 controller; + * ASOC_SDW_ACP_DMIC1 selects PDM1 on ACP7.B/7.F instead. Both ACP7.B/7.F + * controllers support a 2-channel stereo capture path, and BIOS wires exactly + * one per platform. + */ +#define ASOC_SDW_ACP_DMIC BIT(5) /* enable DMIC; PDM0 on ACP7.B/7.F */ +#define ASOC_SDW_ACP_DMIC1 BIT(6) /* PDM1 / DMIC1, ACP7.B/7.F only */ #define ASOC_SDW_CODEC_SPKR BIT(15) #define AMD_SDW0 0 From df67373b0eec5a614b4139974e53f6b61181b47d Mon Sep 17 00:00:00 2001 From: Yige Jiang Date: Thu, 5 Nov 2026 20:32:28 +0800 Subject: [PATCH 124/186] ASoC: cpcap: use the platform OF node setter cpcap_codec_probe() gets the audio-codec child node and assigns it only to dev.of_node. Platform device release now puts dev.fwnode, so the acquired node reference is never returned. Use platform_device_set_of_node() to synchronize both fields and give the device its own reference. Use __free(device_node) to release the local lookup reference when probe returns. The device keeps the node until it is replaced or the platform device is released; a probe retry also drops the old device-owned reference. Fixes: 7b5b51b2b05c ("driver core: platform: count references to all kinds of firmware nodes") Signed-off-by: Yige Jiang Reviewed-by: Bartosz Golaszewski Link: https://patch.msgid.link/rc-review-20261105-v2-28-d7877b3f9c24@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/cpcap.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index eaaf141dea57e3..0e7c05f5bab607 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1754,14 +1754,14 @@ static const struct snd_soc_component_driver soc_codec_dev_cpcap = { static int cpcap_codec_probe(struct platform_device *pdev) { - struct device_node *codec_node = + struct device_node *codec_node __free(device_node) = of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); struct cpcap_audio *cpcap; if (!codec_node) return -ENODEV; - pdev->dev.of_node = codec_node; + platform_device_set_of_node(pdev, codec_node); cpcap = devm_kzalloc(&pdev->dev, sizeof(*cpcap), GFP_KERNEL); if (!cpcap) From 148a32166601ce00d614fde7648ceb156cd5fa27 Mon Sep 17 00:00:00 2001 From: Deukjung Kim Date: Thu, 5 Nov 2026 20:29:03 +0900 Subject: [PATCH 125/186] ALSA: usb-audio: Add Arturia AudioFuse 48V/Main mute mixer controls Add mixer quirk controls for Arturia AudioFuse (0x1c75:0xaf02): - Input1 48V LED Switch (wValue 0x0001) - Main Speaker Mute LED Switch (wValue 0x0500) Values are written with a class MEM request to entity 0x46 and the initial state is read from a 178-byte status block of entity 0x47. Protocol reverse engineered from Arturia AudioFuse Control Center. Signed-off-by: Deukjung Kim Link: https://patch.msgid.link/20261105112903.536150-1-mentalese@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_quirks.c | 187 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 5eee501948fbc4..5839719444f8b8 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -4419,6 +4420,189 @@ static int snd_djm_controls_create(struct usb_mixer_interface *mixer, return 0; } +/* + * Arturia AudioFuse (Rev2) + * + * The protocol was reverse engineered from Arturia AudioFuse Control Center. + * A parameter is written with a UAC2 class MEM request to the vendor entity + * 0x46 (wValue = parameter id, 1 byte payload), and the current device state + * can be read back as a 178 byte block from the vendor entity 0x47. + * Both entities live on the audio control interface 0. + * + * Layout of kcontrol->private_value: (parameter id << 8) | current value + */ +#define SND_AF_CTRL_IFNUM 0 +#define SND_AF_ENTITY_SET 0x46 +#define SND_AF_ENTITY_STATUS 0x47 +#define SND_AF_STATUS_LEN 178 + +#define SND_AF_PARAM_SHIFT 8 +#define SND_AF_VAL_MASK 0xff + +struct snd_audiofuse_ctl { + const char *name; + u16 param; /* wValue of the set request */ + u8 status_offset; /* offset of the value in the status block */ +}; + +static const struct snd_audiofuse_ctl snd_audiofuse_ctls[] = { + { + .name = "Input1 48V LED Switch", + .param = 0x0001, + .status_offset = 0x3e, + }, + { + .name = "Main Speaker Mute LED Switch", + .param = 0x0500, + .status_offset = 0x5c, + }, +}; + +static u16 snd_audiofuse_windex(struct usb_mixer_interface *mixer, u8 entity) +{ + return (entity << 8) | mixer->hostif->desc.bInterfaceNumber; +} + +static int snd_audiofuse_ctl_write(struct usb_mixer_interface *mixer, + u16 param, u8 val) +{ + struct snd_usb_audio *chip = mixer->chip; + int err; + + err = snd_usb_lock_shutdown(chip); + if (err < 0) + return err; + + /* snd_usb_ctl_msg() bounces the buffer, so a stack variable is fine */ + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + param, + snd_audiofuse_windex(mixer, SND_AF_ENTITY_SET), + &val, sizeof(val)); + + snd_usb_unlock_shutdown(chip); + return err; +} + +/* Returns the number of bytes read, or a negative error code */ +static int snd_audiofuse_read_status(struct usb_mixer_interface *mixer, + u8 *buf) +{ + struct snd_usb_audio *chip = mixer->chip; + int len; + + len = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC2_CS_MEM, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, + snd_audiofuse_windex(mixer, SND_AF_ENTITY_STATUS), + buf, SND_AF_STATUS_LEN); + if (len < 0) + return len; + + /* enable with dynamic debug: file mixer_quirks.c +p */ + print_hex_dump_debug("AudioFuse status: ", DUMP_PREFIX_OFFSET, 16, 1, + buf, len, false); + return len; +} + +static int snd_audiofuse_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = + kcontrol->private_value & SND_AF_VAL_MASK; + return 0; +} + +static int snd_audiofuse_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol); + unsigned long pval = kcontrol->private_value; + u16 param = pval >> SND_AF_PARAM_SHIFT; + u8 oldval = pval & SND_AF_VAL_MASK; + long newval = ucontrol->value.integer.value[0]; + int err; + + if (newval < 0 || newval > 1) + return -EINVAL; + if (newval == oldval) + return 0; + + err = snd_audiofuse_ctl_write(list->mixer, param, newval); + if (err < 0) + return err; + + /* update the cache only after the device accepted the value */ + kcontrol->private_value = ((unsigned long)param << SND_AF_PARAM_SHIFT) | + newval; + return 1; +} + +static int snd_audiofuse_ctl_resume(struct usb_mixer_elem_list *list) +{ + unsigned long pval = list->kctl->private_value; + + return snd_audiofuse_ctl_write(list->mixer, + pval >> SND_AF_PARAM_SHIFT, + pval & SND_AF_VAL_MASK); +} + +static int snd_audiofuse_controls_create(struct usb_mixer_interface *mixer) +{ + struct snd_kcontrol_new knew = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = snd_ctl_boolean_mono_info, + .get = snd_audiofuse_ctl_get, + .put = snd_audiofuse_ctl_put, + }; + u8 *status; + int len, i, err = 0; + + /* + * The quirk is applied for every mixer of the card, but the vendor + * entities are only reachable through the control interface 0. + * Checking this per mixer (instead of a global flag) keeps the + * controls working after re-plugging and with multiple devices. + */ + if (mixer->hostif->desc.bInterfaceNumber != SND_AF_CTRL_IFNUM) + return 0; + + status = kmalloc(SND_AF_STATUS_LEN, GFP_KERNEL); + if (!status) + return -ENOMEM; + + /* a failed read is not fatal: start from "off" and keep audio working */ + len = snd_audiofuse_read_status(mixer, status); + if (len < 0) + usb_audio_warn(mixer->chip, + "AudioFuse: cannot read device status (%d)\n", + len); + + for (i = 0; i < ARRAY_SIZE(snd_audiofuse_ctls); i++) { + const struct snd_audiofuse_ctl *ctl = &snd_audiofuse_ctls[i]; + u8 val = 0; + + if (ctl->status_offset < len) + val = !!status[ctl->status_offset]; + + knew.name = ctl->name; + knew.private_value = + ((unsigned long)ctl->param << SND_AF_PARAM_SHIFT) | val; + + err = add_single_ctl_with_resume(mixer, 0, + snd_audiofuse_ctl_resume, + &knew, NULL); + if (err < 0) + break; + } + + kfree(status); + return err; +} + int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err = 0; @@ -4609,6 +4793,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x03f0, 0x0269): /* HP TB Dock G2 */ err = hp_dock_mixer_create(mixer); break; + case USB_ID(0x1c75, 0xaf02): /* Arturia AudioFuse */ + err = snd_audiofuse_controls_create(mixer); + break; } return err; From 0b6513cb5b7c2f1c810c022e80cd88cfc64979d3 Mon Sep 17 00:00:00 2001 From: Tang Bin Date: Wed, 4 Nov 2026 16:20:00 +0800 Subject: [PATCH 126/186] ASoC: pcm512x: fix SCLK/supplies leak on resume failure pcm512x_resume() enabled SCLK then supplies, but each later failing step (regcache_sync, regmap_update_bits) returned directly without undoing the already-enabled resources, and the matching suspend() is not called by the PM core on a failed resume. Use goto cleanup labels that disable supplies and SCLK in reverse order. Fixes: 5a3af1293194 ("ASoC: pcm512x: Add PCM512x driver") Signed-off-by: Tang Bin Link: https://patch.msgid.link/20261104082000.1230-1-tangbin@cmss.chinamobile.com Signed-off-by: Mark Brown --- sound/soc/codecs/pcm512x.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c index f3e20919042b4e..24194a8ec1a9f7 100644 --- a/sound/soc/codecs/pcm512x.c +++ b/sound/soc/codecs/pcm512x.c @@ -1772,24 +1772,31 @@ static int pcm512x_resume(struct device *dev) pcm512x->supplies); if (ret != 0) { dev_err(dev, "Failed to enable supplies: %d\n", ret); - return ret; + goto err_sclk; } regcache_cache_only(pcm512x->regmap, false); ret = regcache_sync(pcm512x->regmap); if (ret != 0) { dev_err(dev, "Failed to sync cache: %d\n", ret); - return ret; + goto err_supplies; } ret = regmap_update_bits(pcm512x->regmap, PCM512x_POWER, PCM512x_RQPD, 0); if (ret != 0) { dev_err(dev, "Failed to remove power down: %d\n", ret); - return ret; + goto err_supplies; } return 0; + +err_supplies: + regulator_bulk_disable(ARRAY_SIZE(pcm512x->supplies), pcm512x->supplies); +err_sclk: + if (!IS_ERR(pcm512x->sclk)) + clk_disable_unprepare(pcm512x->sclk); + return ret; } EXPORT_GPL_DEV_PM_OPS(pcm512x_pm_ops) = { From 86778727e320229755c855c57ea24bcc2677a5d0 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 2 Jun 2026 18:31:36 +0700 Subject: [PATCH 127/186] ASoC: sunxi: sun50i-dmic: Reorder clock enable sequence Reorder the runtime resume clock enable sequence to match the hardware dependency and ensure symmetry with the suspend path. The bus clock drives the register interface and must be enabled before the DMIC module clock. This aligns the resume sequence to be the exact reverse of the suspend sequence. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260602113136.49494-1-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/sunxi/sun50i-dmic.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/sunxi/sun50i-dmic.c b/sound/soc/sunxi/sun50i-dmic.c index 5c784b1f68461a..e5a02812d6be9e 100644 --- a/sound/soc/sunxi/sun50i-dmic.c +++ b/sound/soc/sunxi/sun50i-dmic.c @@ -322,16 +322,16 @@ static int sun50i_dmic_runtime_suspend(struct device *dev) static int sun50i_dmic_runtime_resume(struct device *dev) { - struct sun50i_dmic_dev *host = dev_get_drvdata(dev); + struct sun50i_dmic_dev *host = dev_get_drvdata(dev); int ret; - ret = clk_prepare_enable(host->dmic_clk); + ret = clk_prepare_enable(host->bus_clk); if (ret) return ret; - ret = clk_prepare_enable(host->bus_clk); + ret = clk_prepare_enable(host->dmic_clk); if (ret) { - clk_disable_unprepare(host->dmic_clk); + clk_disable_unprepare(host->bus_clk); return ret; } From b72f6bb43dec5e89c26adf049056e24e85e5e65e Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 15 Oct 2026 22:39:12 -0400 Subject: [PATCH 128/186] ASoC: fsl_esai: Disable reset work before suspending on remove XRUN recovery was introduced as deferred reset and later converted to a work item. The reset reads ESAI registers and can restart the transmit and receive paths. fsl_esai_remove() currently suspends the device before cancelling the work, so a running reset can overlap regcache and clock shutdown. The managed IRQ can also queue another reset after cancellation. Disable and drain the work before runtime PM teardown. This lets an already running reset finish while the device is active and rejects later queue attempts until the managed IRQ is released. The IRQ handler's own register accesses during teardown are a separate issue. This issue was identified as part of our ongoing research while reviewing kernel code. Fixes: 7ccafa2b3879 ("ASoC: fsl_esai: recover the channel swap after xrun") Assisted-by: LLM Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20261016023912.3658714-1-mhun512@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_esai.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sound/soc/fsl/fsl_esai.c b/sound/soc/fsl/fsl_esai.c index 4a530a6c33f07b..83a996377135c7 100644 --- a/sound/soc/fsl/fsl_esai.c +++ b/sound/soc/fsl/fsl_esai.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -1099,11 +1100,11 @@ static void fsl_esai_remove(struct platform_device *pdev) { struct fsl_esai *esai_priv = platform_get_drvdata(pdev); + disable_work_sync(&esai_priv->work); + pm_runtime_disable(&pdev->dev); if (!pm_runtime_status_suspended(&pdev->dev)) fsl_esai_runtime_suspend(&pdev->dev); - - cancel_work_sync(&esai_priv->work); } static const struct of_device_id fsl_esai_dt_ids[] = { From 9626eb83c6f0f6813a5a56944a9eccd800041cc1 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Fri, 6 Nov 2026 17:26:25 +0000 Subject: [PATCH 129/186] ALSA: control: Trim whitespace from sysfs input The attach and detach sysfs interfaces receive input from userspace. When userspace writes a value using echo, the input normally ends with a newline character. The parser does not remove this newline in all input formats. For example, a bare numeric ID can be treated as a control name, and an unquoted name= field can include the newline. Trim leading and trailing whitespace before parsing the input. This keeps spaces inside mixer control names unchanged. Fixes: a135dfb5de15 ("ALSA: led control - add sysfs kcontrol LED marking layer") Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261106172625.4622-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control_led.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 501387287ae693..a640e7835ce262 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -518,6 +518,11 @@ static ssize_t set_led_id(struct snd_ctl_led_card *led_card, const char *buf, si if (strscpy(buf2, buf, sizeof(buf2)) < 0) return -E2BIG; + + s = strim(buf2); + if (s != buf2) + memmove(buf2, s, strlen(s) + 1); + memset(&id, 0, sizeof(id)); id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; s = buf2; From 988eb45d29808f9d9c1b3ea41019d15e75203840 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Fri, 6 Nov 2026 18:39:53 +0000 Subject: [PATCH 130/186] ALSA: control: Synchronize LED mode accesses mode_store() updates led->mode while holding snd_ctl_led_mutex. However, snd_ctl_led_set_state() and mode_show() read this field without holding the same mutex. This can cause a data race when userspace changes the mode while another path reads it. Read the mode under snd_ctl_led_mutex in snd_ctl_led_set_state() and use the local value after releasing the mutex. Also protect the mode read in mode_show() with the mutex. Fixes: cb17fe0045aa ("ALSA: control - add sysfs support to the LED trigger module") Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261106183953.2762-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control_led.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index a640e7835ce262..3d55da3af2a056 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -140,6 +140,7 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, { struct snd_ctl_led *led; struct snd_ctl_led_ctl *lctl; + enum snd_ctl_led_mode mode; int route; bool found; @@ -168,8 +169,9 @@ static void snd_ctl_led_set_state(struct snd_card *card, unsigned int access, UPDATE_ROUTE(route, snd_ctl_led_get(lctl)); } } + mode = led->mode; } - switch (led->mode) { + switch (mode) { case MODE_OFF: route = 1; break; case MODE_ON: route = 0; break; case MODE_FOLLOW_ROUTE: if (route >= 0) route ^= 1; break; @@ -382,6 +384,8 @@ static ssize_t mode_show(struct device *dev, struct snd_ctl_led *led = container_of(dev, struct snd_ctl_led, dev); const char *str = NULL; + guard(mutex)(&snd_ctl_led_mutex); + switch (led->mode) { case MODE_FOLLOW_MUTE: str = "follow-mute"; break; case MODE_FOLLOW_ROUTE: str = "follow-route"; break; From 80b8c4a92a2750183293708ef0fbf648ac10f13d Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Fri, 6 Nov 2026 20:50:46 +0000 Subject: [PATCH 131/186] ALSA: hdsp: Serialize control_register updates in MIDI input paths hdsp->control_register is a software copy of a hardware register. It is updated read-modify-write under hdsp->lock, except in two MIDI paths: snd_hdsp_midi_input_read() holds only the per-port hmidi->lock, and snd_hdsp_interrupt() holds no lock at all. Lock holders disable interrupts only on the local CPU, so updates can be lost across CPUs. A lost MIDI interrupt enable bit stops MIDI input on that port. Other lost updates revert control or PCM settings. Take hdsp->lock in both places. In snd_hdsp_midi_input_read(), drop hmidi->lock before taking it, as commit c0da00145f9a ("ALSA: hdspm - Fix locking in snd_hdspm_midi_input_read") did for hdspm. Found by static analysis. Compile-tested only; no hardware available. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Yifei Gao Link: https://patch.msgid.link/20261106205050.8048-2-gyf161023@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/rme9652/hdsp.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/sound/pci/rme9652/hdsp.c b/sound/pci/rme9652/hdsp.c index c6ddad82dcf773..00295930ce6f6f 100644 --- a/sound/pci/rme9652/hdsp.c +++ b/sound/pci/rme9652/hdsp.c @@ -1381,6 +1381,10 @@ static int snd_hdsp_midi_input_read (struct hdsp_midi *hmidi) } } hmidi->pending = 0; + } + + /* control_register is shared by all users, serialize on hdsp->lock */ + scoped_guard(spinlock_irqsave, &hmidi->hdsp->lock) { if (hmidi->id) hmidi->hdsp->control_register |= HDSP_Midi1InterruptEnable; else @@ -3903,8 +3907,10 @@ static irqreturn_t snd_hdsp_interrupt(int irq, void *dev_id) if (midi0 && midi0status) { if (hdsp->use_midi_work) { /* we disable interrupts for this input until processing is done */ - hdsp->control_register &= ~HDSP_Midi0InterruptEnable; - hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + scoped_guard(spinlock, &hdsp->lock) { + hdsp->control_register &= ~HDSP_Midi0InterruptEnable; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + } hdsp->midi[0].pending = 1; schedule = 1; } else { @@ -3914,8 +3920,10 @@ static irqreturn_t snd_hdsp_interrupt(int irq, void *dev_id) if (hdsp->io_type != Multiface && hdsp->io_type != RPM && hdsp->io_type != H9632 && midi1 && midi1status) { if (hdsp->use_midi_work) { /* we disable interrupts for this input until processing is done */ - hdsp->control_register &= ~HDSP_Midi1InterruptEnable; - hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + scoped_guard(spinlock, &hdsp->lock) { + hdsp->control_register &= ~HDSP_Midi1InterruptEnable; + hdsp_write(hdsp, HDSP_controlRegister, hdsp->control_register); + } hdsp->midi[1].pending = 1; schedule = 1; } else { From 46b6ece07e74aef8273e9e181676f3ddb6493315 Mon Sep 17 00:00:00 2001 From: Yifei Gao Date: Fri, 6 Nov 2026 20:50:47 +0000 Subject: [PATCH 132/186] ALSA: hdspm: Take hdspm->lock when masking MIDI interrupts in IRQ handler snd_hdspm_interrupt() clears a MIDI interrupt enable bit in hdspm->control_register without hdspm->lock. Every other update holds that lock. This includes snd_hdspm_midi_input_read(), which sets the bit again, since commit c0da00145f9a ("ALSA: hdspm - Fix locking in snd_hdspm_midi_input_read"). Updates racing with the handler on another CPU can be lost. This can stall MIDI input or revert a control setting. Take hdspm->lock in the handler. All other users of the lock already run with interrupts disabled. Found by static analysis. Compile-tested only; no hardware available. Fixes: 763f356cd8de ("[ALSA] Add HDSP MADI driver") Assisted-by: Claude:claude-opus-5-5 Signed-off-by: Yifei Gao Link: https://patch.msgid.link/20261106205050.8048-3-gyf161023@gmail.com Signed-off-by: Takashi Iwai --- sound/pci/rme9652/hdspm.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sound/pci/rme9652/hdspm.c b/sound/pci/rme9652/hdspm.c index cc4cf43e5a5b6a..135761278556c7 100644 --- a/sound/pci/rme9652/hdspm.c +++ b/sound/pci/rme9652/hdspm.c @@ -5383,9 +5383,11 @@ static irqreturn_t snd_hdspm_interrupt(int irq, void *dev_id) /* we disable interrupts for this input until * processing is done */ - hdspm->control_register &= ~hdspm->midi[i].ie; - hdspm_write(hdspm, HDSPM_controlRegister, - hdspm->control_register); + scoped_guard(spinlock, &hdspm->lock) { + hdspm->control_register &= ~hdspm->midi[i].ie; + hdspm_write(hdspm, HDSPM_controlRegister, + hdspm->control_register); + } hdspm->midi[i].pending = 1; schedule = 1; } From ba9bccdf711ac6fec13240c9812bd859ce1f7743 Mon Sep 17 00:00:00 2001 From: Cristiano Souza Date: Fri, 6 Nov 2026 23:26:43 -0300 Subject: [PATCH 133/186] ALSA: usb-audio: Skip broken playback feature unit on XiiSound chips Weltrend XiiSound-based devices (040b:0897, e.g. Redragon H878 wireless headset) expose a UAC1 playback feature unit whose volume and mute controls are unusable in practice: the reported per-channel dB range is only 0..+0.39dB, yet setting any volume below the maximum mutes one or even both channels, and the mapping between logical and physical channels is unreliable. Any attempt by userspace to drive the hardware volume therefore results in muted or one-sided audio. Ignore the playback feature unit via a usbmix_name_map so that userspace falls back to software volume, and pin the unit's master and per-channel volumes to the reported maximum with mutes cleared when the mixer is created, so the signal path always stays fully open. The capture path (microphone feature unit) is not affected and keeps working. Tested with a Redragon H878 wireless headset on PipeWire/PulseAudio: stereo output works on both channels at any volume, software volume applies smoothly, and mute/unmute works. Cc: stable@vger.kernel.org Signed-off-by: Cristiano Souza Link: https://patch.msgid.link/20261107022643.135168-1-cristianocps@gmail.com Signed-off-by: Takashi Iwai --- sound/usb/mixer_maps.c | 19 +++++++++++++ sound/usb/mixer_quirks.c | 59 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/sound/usb/mixer_maps.c b/sound/usb/mixer_maps.c index 98b4146216ae7c..9218ca6314ad79 100644 --- a/sound/usb/mixer_maps.c +++ b/sound/usb/mixer_maps.c @@ -374,6 +374,20 @@ static const struct usbmix_name_map dell_alc4020_map[] = { { 0 } }; +/* + * Weltrend XiiSound chips (040b:0897, e.g. Redragon H878): + * the playback feature unit is broken; its volume controls act like + * mute switches below the maximum value and the channel mapping is + * unreliable, so any hardware volume change mutes one or both + * channels. Skip the unit entirely and let userspace fall back to + * software volume; the unit is pinned to maximum/unmuted by the + * mixer quirk in mixer_quirks.c. + */ +static const struct usbmix_name_map xiisound_map[] = { + { 5, NULL }, /* playback feature unit */ + { 0 } +}; + /* * Corsair Virtuoso calls everything "Headset" without this, leading to * applications moving the sidetone control instead of the main one. @@ -790,6 +804,11 @@ static const struct usbmix_ctl_map usbmix_ctl_maps[] = { .id = USB_ID(0x1395, 0x0025), .map = sennheiser_pc8_map, }, + { + /* Weltrend XiiSound (Redragon H878) */ + .id = USB_ID(0x040b, 0x0897), + .map = xiisound_map, + }, { /* Microsoft USB Link headset */ .id = USB_ID(0x045e, 0x083c), diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c index 5839719444f8b8..4c7d68311f2048 100644 --- a/sound/usb/mixer_quirks.c +++ b/sound/usb/mixer_quirks.c @@ -4603,6 +4603,59 @@ static int snd_audiofuse_controls_create(struct usb_mixer_interface *mixer) return err; } +/* + * Weltrend XiiSound chips (040b:0897, e.g. Redragon H878): + * the playback feature unit (id 5) is broken; its volume controls act like + * mute switches below the maximum value and the channel mapping is + * unreliable, so the unit is hidden from userspace entirely (see the + * quirk in mixer_maps.c) and userspace falls back to software volume. + * Pin the unit's master and channel volumes to the reported maximum + * and unmute them, so the audio path stays fully open. + */ +static int snd_xiisound_init_fu_pins(struct usb_mixer_interface *mixer) +{ + struct snd_usb_audio *chip = mixer->chip; + unsigned char buf[2]; + int ifnum, ch, err; + + if (mixer->protocol != UAC_VERSION_1) + return 0; + + ifnum = snd_usb_ctrl_intf(chip->ctrl_intf); + + /* unit 5 is the playback feature unit on these chips */ + for (ch = 0; ch <= 2; ch++) { + err = snd_usb_ctl_msg(chip->dev, usb_rcvctrlpipe(chip->dev, 0), + UAC_GET_MAX, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (UAC_FU_VOLUME << 8) | ch, + (5 << 8) | ifnum, buf, 2); + if (err < 2) + return 0; /* no such unit/channels, nothing to pin */ + + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (UAC_FU_VOLUME << 8) | ch, + (5 << 8) | ifnum, buf, 2); + if (err < 0) + return err; + + buf[0] = 0; /* unmute */ + err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), + UAC_SET_CUR, + USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, + (UAC_FU_MUTE << 8) | ch, + (5 << 8) | ifnum, buf, 1); + if (err < 0) + return err; + } + return 0; +} + int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) { int err = 0; @@ -4616,6 +4669,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x0644, 0x8047): err = snd_us16x08_controls_create(mixer); break; + case USB_ID(0x040b, 0x0897): + err = snd_xiisound_init_fu_pins(mixer); + break; case USB_ID(0x041e, 0x3020): case USB_ID(0x041e, 0x3040): case USB_ID(0x041e, 0x3042): @@ -4807,6 +4863,9 @@ void snd_usb_mixer_resume_quirk(struct usb_mixer_interface *mixer) case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */ dell_dock_mixer_init(mixer); break; + case USB_ID(0x040b, 0x0897): /* Weltrend XiiSound (Redragon H878) */ + snd_xiisound_init_fu_pins(mixer); + break; } } From 73ade6a51c7974444d2f3e2f7dd91d295e7578c4 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Wed, 28 Oct 2026 13:06:26 +0800 Subject: [PATCH 134/186] ASoC: sunxi: sun50i-dmic: resume device for control access The capture volume controls use regmap directly while runtime PM may have suspended the DMIC and disabled its clocks. Since the regmap has no cache, reading or writing a control while suspended accesses unpowered hardware. Use control callbacks which resume the device around the regmap access and release the runtime PM reference afterwards. Fixes: 9a8cadddd930 ("ASoC: sunxi: DMIC: Add controls for adjusting the mic gains") Cc: stable@vger.kernel.org Signed-off-by: Jiale Yao Link: https://patch.msgid.link/20261028050626.133421-1-yaojiale02@163.com Signed-off-by: Mark Brown --- sound/soc/sunxi/sun50i-dmic.c | 59 +++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/sound/soc/sunxi/sun50i-dmic.c b/sound/soc/sunxi/sun50i-dmic.c index e5a02812d6be9e..be61990415b01c 100644 --- a/sound/soc/sunxi/sun50i-dmic.c +++ b/sound/soc/sunxi/sun50i-dmic.c @@ -286,22 +286,55 @@ MODULE_DEVICE_TABLE(of, sun50i_dmic_of_match); static const SNDRV_CTL_TLVD_DECLARE_DB_SCALE(sun50i_dmic_vol_scale, -12000, 75, 1); -static const struct snd_kcontrol_new sun50i_dmic_controls[] = { +static int sun50i_dmic_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret; - SOC_DOUBLE_TLV("DMIC Channel 0 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, - SUN50I_DMIC_D0D1_VOL_CTR_0L, SUN50I_DMIC_D0D1_VOL_CTR_0R, - 0xFF, 0, sun50i_dmic_vol_scale), - SOC_DOUBLE_TLV("DMIC Channel 1 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, - SUN50I_DMIC_D0D1_VOL_CTR_1L, SUN50I_DMIC_D0D1_VOL_CTR_1R, - 0xFF, 0, sun50i_dmic_vol_scale), - SOC_DOUBLE_TLV("DMIC Channel 2 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, - SUN50I_DMIC_D2D3_VOL_CTR_2L, SUN50I_DMIC_D2D3_VOL_CTR_2R, - 0xFF, 0, sun50i_dmic_vol_scale), - SOC_DOUBLE_TLV("DMIC Channel 3 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, - SUN50I_DMIC_D2D3_VOL_CTR_3L, SUN50I_DMIC_D2D3_VOL_CTR_3R, - 0xFF, 0, sun50i_dmic_vol_scale), + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0) + return ret; + ret = snd_soc_get_volsw(kcontrol, ucontrol); + pm_runtime_put(component->dev); + return ret; +} + +static int sun50i_dmic_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + int ret; + + ret = pm_runtime_resume_and_get(component->dev); + if (ret < 0) + return ret; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + pm_runtime_put(component->dev); + + return ret; +} + +static const struct snd_kcontrol_new sun50i_dmic_controls[] = { + SOC_DOUBLE_EXT_TLV("DMIC Channel 0 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, + SUN50I_DMIC_D0D1_VOL_CTR_0L, SUN50I_DMIC_D0D1_VOL_CTR_0R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), + SOC_DOUBLE_EXT_TLV("DMIC Channel 1 Capture Volume", SUN50I_DMIC_D0D1_VOL_CTR, + SUN50I_DMIC_D0D1_VOL_CTR_1L, SUN50I_DMIC_D0D1_VOL_CTR_1R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), + SOC_DOUBLE_EXT_TLV("DMIC Channel 2 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, + SUN50I_DMIC_D2D3_VOL_CTR_2L, SUN50I_DMIC_D2D3_VOL_CTR_2R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), + SOC_DOUBLE_EXT_TLV("DMIC Channel 3 Capture Volume", SUN50I_DMIC_D2D3_VOL_CTR, + SUN50I_DMIC_D2D3_VOL_CTR_3L, SUN50I_DMIC_D2D3_VOL_CTR_3R, + 0xFF, 0, sun50i_dmic_get_volsw, sun50i_dmic_put_volsw, + sun50i_dmic_vol_scale), }; static const struct snd_soc_component_driver sun50i_dmic_component = { From 347e02073b08bfa66e5e200e5f4a0f28da45bfe5 Mon Sep 17 00:00:00 2001 From: Birk Skyum Date: Sun, 18 Oct 2026 22:10:41 +0000 Subject: [PATCH 135/186] ASoC: qcom: q6apm-lpass-dais: Reset the codec DMA channel map in hw_params Since commit c663014e452d ("ASoC: qcom: audioreach: compute active channel maps from channel_map"), the codec DMA active channel mask is built from the port's channel map instead of its channel count. q6dma_hw_params() fills that map with audioreach_set_default_channel_mapping(), but the map lives in the DAI's module config for as long as the device exists and is never cleared, and the default mapping only covers one, two and four channels. Entries left from an earlier stream, or entries the default mapping does not fill, give a mask that does not match the channel count, and the DSP refuses to start the port. On the Lenovo Yoga Slim 7x every capture from the digital microphones fails with q6apm-lpass-dais ...: Failed to start APM port 110 Clear the map before filling it, and map any channel the default mapping leaves out. With this, capture through PipeWire and direct captures with two, four and two channels in a row work again. Fixes: c663014e452d ("ASoC: qcom: audioreach: compute active channel maps from channel_map") Signed-off-by: Birk Skyum Link: https://patch.msgid.link/20261018220857.16819-1-birk.skyum@pm.me Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-lpass-dais.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c index 83f2711bceeaaf..9f6d17f80de357 100644 --- a/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c +++ b/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c @@ -173,11 +173,16 @@ static int q6dma_hw_params(struct snd_pcm_substream *substream, struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; int channels = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max; + int i; cfg->bit_width = params_width(params); cfg->sample_rate = params_rate(params); cfg->num_channels = channels; + memset(cfg->channel_map, 0, sizeof(cfg->channel_map)); audioreach_set_default_channel_mapping(cfg->channel_map, channels); + for (i = 0; i < channels && i < AR_PCM_MAX_NUM_CHANNEL; i++) + if (!cfg->channel_map[i]) + cfg->channel_map[i] = i + 1; return 0; } From c5f9ddc7d2ece6a82317a284463c047c10828220 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 2 Jun 2026 18:44:35 +0700 Subject: [PATCH 136/186] ASoC: sunxi: sun4i-spdif: Use guard() for spin locks Clean up the code using guard() for spin locks. Merely code refactoring, and no behavior change. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260602114437.50324-2-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 62 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index d979bb00312fb3..ec05600a04adf0 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -427,24 +427,21 @@ static int sun4i_spdif_get_status(struct snd_kcontrol *kcontrol, struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); u8 *status = ucontrol->value.iec958.status; - unsigned long flags; unsigned int reg; - spin_lock_irqsave(&host->lock, flags); + scoped_guard(spinlock_irqsave, &host->lock) { + regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA0, ®); - regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA0, ®); + status[0] = reg & 0xff; + status[1] = (reg >> 8) & 0xff; + status[2] = (reg >> 16) & 0xff; + status[3] = (reg >> 24) & 0xff; - status[0] = reg & 0xff; - status[1] = (reg >> 8) & 0xff; - status[2] = (reg >> 16) & 0xff; - status[3] = (reg >> 24) & 0xff; + regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA1, ®); - regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA1, ®); - - status[4] = reg & 0xff; - status[5] = (reg >> 8) & 0x3; - - spin_unlock_irqrestore(&host->lock, flags); + status[4] = reg & 0xff; + status[5] = (reg >> 8) & 0x3; + } return 0; } @@ -455,35 +452,32 @@ static int sun4i_spdif_set_status(struct snd_kcontrol *kcontrol, struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); u8 *status = ucontrol->value.iec958.status; - unsigned long flags; unsigned int reg; bool chg0, chg1; - spin_lock_irqsave(&host->lock, flags); - - reg = (u32)status[3] << 24; - reg |= (u32)status[2] << 16; - reg |= (u32)status[1] << 8; - reg |= (u32)status[0]; + scoped_guard(spinlock_irqsave, &host->lock) { + reg = (u32)status[3] << 24; + reg |= (u32)status[2] << 16; + reg |= (u32)status[1] << 8; + reg |= (u32)status[0]; - regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA0, - GENMASK(31,0), reg, &chg0); + regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA0, + GENMASK(31, 0), reg, &chg0); - reg = (u32)status[5] << 8; - reg |= (u32)status[4]; + reg = (u32)status[5] << 8; + reg |= (u32)status[4]; - regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA1, - GENMASK(9,0), reg, &chg1); + regmap_update_bits_check(host->regmap, SUN4I_SPDIF_TXCHSTA1, + GENMASK(9, 0), reg, &chg1); - reg = SUN4I_SPDIF_TXCFG_CHSTMODE; - if (status[0] & IEC958_AES0_NONAUDIO) - reg |= SUN4I_SPDIF_TXCFG_NONAUDIO; + reg = SUN4I_SPDIF_TXCFG_CHSTMODE; + if (status[0] & IEC958_AES0_NONAUDIO) + reg |= SUN4I_SPDIF_TXCFG_NONAUDIO; - regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, - SUN4I_SPDIF_TXCFG_CHSTMODE | - SUN4I_SPDIF_TXCFG_NONAUDIO, reg); - - spin_unlock_irqrestore(&host->lock, flags); + regmap_update_bits(host->regmap, SUN4I_SPDIF_TXCFG, + SUN4I_SPDIF_TXCFG_CHSTMODE | + SUN4I_SPDIF_TXCFG_NONAUDIO, reg); + } return chg0 || chg1; } From f003dfe0842ad6e418dd90a5675f036f4821fb56 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 2 Jun 2026 18:44:36 +0700 Subject: [PATCH 137/186] ASoC: sunxi: sun4i-spdif: Resume device before kcontrol register access The IEC958 status kcontrols access hardware registers directly. Take a runtime PM reference while accessing the registers so that the device remains active for the duration of the operation. Reported-by: Sashiko AI Review Closes: https://lore.kernel.org/all/20260514043314.62222C2BCB7@smtp.kernel.org/ Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260602114437.50324-3-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index ec05600a04adf0..d924f497e9dbc6 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -428,6 +428,11 @@ static int sun4i_spdif_get_status(struct snd_kcontrol *kcontrol, struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); u8 *status = ucontrol->value.iec958.status; unsigned int reg; + int ret; + + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; scoped_guard(spinlock_irqsave, &host->lock) { regmap_read(host->regmap, SUN4I_SPDIF_TXCHSTA0, ®); @@ -443,6 +448,8 @@ static int sun4i_spdif_get_status(struct snd_kcontrol *kcontrol, status[5] = (reg >> 8) & 0x3; } + pm_runtime_put(cpu_dai->dev); + return 0; } @@ -453,8 +460,13 @@ static int sun4i_spdif_set_status(struct snd_kcontrol *kcontrol, struct sun4i_spdif_dev *host = snd_soc_dai_get_drvdata(cpu_dai); u8 *status = ucontrol->value.iec958.status; unsigned int reg; + int ret; bool chg0, chg1; + ret = pm_runtime_resume_and_get(cpu_dai->dev); + if (ret) + return ret; + scoped_guard(spinlock_irqsave, &host->lock) { reg = (u32)status[3] << 24; reg |= (u32)status[2] << 16; @@ -479,6 +491,8 @@ static int sun4i_spdif_set_status(struct snd_kcontrol *kcontrol, SUN4I_SPDIF_TXCFG_NONAUDIO, reg); } + pm_runtime_put(cpu_dai->dev); + return chg0 || chg1; } From a4a9384fdf2c7f19448530d808b8b6edb6b0d1ed Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Tue, 2 Jun 2026 18:44:37 +0700 Subject: [PATCH 138/186] ASoC: sunxi: sun4i-spdif: Reorder clock enable sequence Reorder the runtime resume clock enable sequence to match the hardware dependency and ensure symmetry with the suspend path. The APB bus clock drives the register interface and must be enabled before the functional module clock. This aligns the resume sequence to be the exact reverse of the suspend sequence. Signed-off-by: bui duc phuc Link: https://patch.msgid.link/20260602114437.50324-4-phucduc.bui@gmail.com Signed-off-by: Mark Brown --- sound/soc/sunxi/sun4i-spdif.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/sunxi/sun4i-spdif.c b/sound/soc/sunxi/sun4i-spdif.c index d924f497e9dbc6..747ecd43dd2665 100644 --- a/sound/soc/sunxi/sun4i-spdif.c +++ b/sound/soc/sunxi/sun4i-spdif.c @@ -643,15 +643,15 @@ static int sun4i_spdif_runtime_suspend(struct device *dev) static int sun4i_spdif_runtime_resume(struct device *dev) { - struct sun4i_spdif_dev *host = dev_get_drvdata(dev); + struct sun4i_spdif_dev *host = dev_get_drvdata(dev); int ret; - ret = clk_prepare_enable(host->spdif_clk); + ret = clk_prepare_enable(host->apb_clk); if (ret) return ret; - ret = clk_prepare_enable(host->apb_clk); + ret = clk_prepare_enable(host->spdif_clk); if (ret) - clk_disable_unprepare(host->spdif_clk); + clk_disable_unprepare(host->apb_clk); return ret; } From bd9d1df5a85694adba1ddc36a836483b9112a773 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 2 Nov 2026 13:09:08 -0500 Subject: [PATCH 139/186] ASoC/sound: use dmaengine_desc_set_callback() Replace open-coded assignments to callback and callback_param fields of struct dma_async_tx_descriptor with calls to the dmaengine_desc_set_callback() helper across sound drivers. No functional change intended. Prepare to make struct dma_async_tx_descriptor private to the DMA engine. Signed-off-by: Frank Li Acked-by: Kuninori Morimoto Acked-by: Shengjiu Wang Link: https://patch.msgid.link/20261102180915.3981805-1-Frank.Li@oss.nxp.com Signed-off-by: Mark Brown --- sound/core/pcm_dmaengine.c | 3 +-- sound/soc/fsl/fsl_asrc_dma.c | 3 +-- sound/soc/fsl/fsl_asrc_m2m.c | 3 +-- sound/soc/renesas/fsi.c | 3 +-- sound/soc/renesas/siu_pcm.c | 6 ++---- sound/soc/sprd/sprd-pcm-compress.c | 5 +++-- sound/soc/sprd/sprd-pcm-dma.c | 5 +++-- sound/soc/stm/stm32_spdifrx.c | 3 +-- 8 files changed, 13 insertions(+), 18 deletions(-) diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c index 1306b04be171ae..41feb5312ffece 100644 --- a/sound/core/pcm_dmaengine.c +++ b/sound/core/pcm_dmaengine.c @@ -169,8 +169,7 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream) if (!desc) return -ENOMEM; - desc->callback = dmaengine_pcm_dma_complete; - desc->callback_param = substream; + dmaengine_desc_set_callback(desc, dmaengine_pcm_dma_complete, NULL, substream); prtd->cookie = dmaengine_submit(desc); return 0; diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index b417de6abebe4a..cd3bf6340b6506 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -76,8 +76,7 @@ static int fsl_asrc_dma_prepare_and_submit(struct snd_pcm_substream *substream, return -ENOMEM; } - pair->desc[!dir]->callback = fsl_asrc_dma_complete; - pair->desc[!dir]->callback_param = substream; + dmaengine_desc_set_callback(pair->desc[!dir], fsl_asrc_dma_complete, NULL, substream); dmaengine_submit(pair->desc[!dir]); diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c index 9009d7a33e35b1..796a78124765f4 100644 --- a/sound/soc/fsl/fsl_asrc_m2m.c +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -176,8 +176,7 @@ static int asrc_dmaconfig(struct fsl_asrc_pair *pair, return -EINVAL; } - pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); - pair->desc[dir]->callback_param = pair; + dmaengine_desc_set_callback(pair->desc[dir], ASRC_xPUT_DMA_CALLBACK(dir), NULL, pair); return 0; } diff --git a/sound/soc/renesas/fsi.c b/sound/soc/renesas/fsi.c index 74e2ea002fbb1d..fa21cf47befffd 100644 --- a/sound/soc/renesas/fsi.c +++ b/sound/soc/renesas/fsi.c @@ -1375,8 +1375,7 @@ static int fsi_dma_transfer(struct fsi_priv *fsi, struct fsi_stream *io) goto fsi_dma_transfer_err; } - desc->callback = fsi_dma_complete; - desc->callback_param = io; + dmaengine_desc_set_callback(desc, fsi_dma_complete, NULL, io); if (dmaengine_submit(desc) < 0) { dev_err(dai->dev, "tx_submit() fail\n"); diff --git a/sound/soc/renesas/siu_pcm.c b/sound/soc/renesas/siu_pcm.c index fd5b20b469a4ad..7208514e16a43d 100644 --- a/sound/soc/renesas/siu_pcm.c +++ b/sound/soc/renesas/siu_pcm.c @@ -125,8 +125,7 @@ static int siu_pcm_wr_set(struct siu_port *port_info, return -ENOMEM; } - desc->callback = siu_dma_tx_complete; - desc->callback_param = siu_stream; + dmaengine_desc_set_callback(desc, siu_dma_tx_complete, NULL, siu_stream); cookie = dmaengine_submit(desc); if (cookie < 0) { dev_err(dev, "Failed to submit a dma transfer\n"); @@ -175,8 +174,7 @@ static int siu_pcm_rd_set(struct siu_port *port_info, return -ENOMEM; } - desc->callback = siu_dma_tx_complete; - desc->callback_param = siu_stream; + dmaengine_desc_set_callback(desc, siu_dma_tx_complete, NULL, siu_stream); cookie = dmaengine_submit(desc); if (cookie < 0) { dev_err(dev, "Failed to submit dma descriptor\n"); diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c index f790b959cd4e47..a810c9c8a0b5b1 100644 --- a/sound/soc/sprd/sprd-pcm-compress.c +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -247,8 +247,9 @@ static int sprd_platform_compr_dma_config(struct snd_soc_component *component, /* Only channel 1 transfer can wake up the AP system. */ if (!params->no_wake_mode && channel == 1) { - dma->desc->callback = sprd_platform_compr_dma_complete; - dma->desc->callback_param = cstream; + dmaengine_desc_set_callback(dma->desc, + sprd_platform_compr_dma_complete, + NULL, cstream); } kfree(sg); diff --git a/sound/soc/sprd/sprd-pcm-dma.c b/sound/soc/sprd/sprd-pcm-dma.c index bbd7516f81954e..968901ae4c335e 100644 --- a/sound/soc/sprd/sprd-pcm-dma.c +++ b/sound/soc/sprd/sprd-pcm-dma.c @@ -287,8 +287,9 @@ static int sprd_pcm_hw_params(struct snd_soc_component *component, } if (!runtime->no_period_wakeup) { - data->desc->callback = sprd_pcm_dma_complete; - data->desc->callback_param = dma_private; + dmaengine_desc_set_callback(data->desc, + sprd_pcm_dma_complete, + NULL, dma_private); } } diff --git a/sound/soc/stm/stm32_spdifrx.c b/sound/soc/stm/stm32_spdifrx.c index 88863dbd05d9bd..44467a25fb69a4 100644 --- a/sound/soc/stm/stm32_spdifrx.c +++ b/sound/soc/stm/stm32_spdifrx.c @@ -302,8 +302,7 @@ static int stm32_spdifrx_dma_ctrl_start(struct stm32_spdifrx_data *spdifrx) if (!spdifrx->desc) return -EINVAL; - spdifrx->desc->callback = stm32_spdifrx_dma_complete; - spdifrx->desc->callback_param = spdifrx; + dmaengine_desc_set_callback(spdifrx->desc, stm32_spdifrx_dma_complete, NULL, spdifrx); cookie = dmaengine_submit(spdifrx->desc); err = dma_submit_error(cookie); if (err) From 861f35761dc873514f198904dd994072ba3c2515 Mon Sep 17 00:00:00 2001 From: Tang Bin Date: Wed, 28 Oct 2026 09:57:31 +0800 Subject: [PATCH 140/186] ASoC: qdsp6: q6routing: Fix memory leak on probe failure routing_data is allocated with kzalloc() and stored in a global static pointer. If devm_snd_soc_register_component() fails, the probe function returns directly without freeing routing_data. Since the remove callback is not invoked on probe failure, this leads to a memory leak. On a probe retry, a new routing_data overwrites the global pointer, permanently losing the previous allocation. Fix this by switching to devm_kzalloc_obj() for the allocation. This ensures the memory is automatically freed by the device core if probe fails or when the driver is unbound. Consequently, the manual kfree() in the remove callback is no longer needed and must be removed to avoid a double-free. Fixes: e3a33673e845 ("ASoC: qdsp6: q6routing: Add q6routing driver") Signed-off-by: Tang Bin Link: https://patch.msgid.link/20261028015732.1985-1-tangbin@cmss.chinamobile.com Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6routing.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c index ea511200c8d72c..0a55d1f384d4d8 100644 --- a/sound/soc/qcom/qdsp6/q6routing.c +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -1220,7 +1220,7 @@ static int q6pcm_routing_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - routing_data = kzalloc_obj(*routing_data); + routing_data = devm_kzalloc_obj(dev, *routing_data); if (!routing_data) return -ENOMEM; @@ -1235,7 +1235,6 @@ static int q6pcm_routing_probe(struct platform_device *pdev) static void q6pcm_routing_remove(struct platform_device *pdev) { - kfree(routing_data); routing_data = NULL; } From afe7120e273153b983616307e9f4bdf697453b8e Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:45 +0000 Subject: [PATCH 141/186] ASoC: Intel: sof_cirrus_common: release the physical device reference cs35l41_compute_codec_conf() looks up the physical device of each ACPI CS35L41 node with acpi_bus_get_primary_device(), which takes a reference on the returned device, but the reference is never dropped because only the device name is kept in the static codec configuration. Copy the name into a per-amplifier buffer that lives as long as the static configuration, and release the reference right after the lookup. Card setup no longer leaks one device reference per amplifier. Fixes: 1ca726424a12 ("ASoC: Intel: cirrus-common: Use UID to map correct amp to prefix") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024045.1508525-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/intel/boards/sof_cirrus_common.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index c7e77e20a09645..12124917b5526b 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -45,6 +45,8 @@ static struct snd_soc_dai_link_component cs35l41_components[CS35L41_MAX_AMPS]; */ static struct snd_soc_codec_conf cs35l41_codec_conf[CS35L41_MAX_AMPS]; +static char cs35l41_physdev_names[CS35L41_MAX_AMPS][64]; + static int cs35l41_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_card *card = rtd->card; @@ -175,9 +177,11 @@ static int cs35l41_compute_codec_conf(void) uid, cs35l41_name_prefixes[uid]); return 0; } - cs35l41_components[sz].name = dev_name(physdev); + strscpy(cs35l41_physdev_names[sz], dev_name(physdev)); + put_device(physdev); + cs35l41_components[sz].name = cs35l41_physdev_names[sz]; cs35l41_components[sz].dai_name = CS35L41_CODEC_DAI; - cs35l41_codec_conf[sz].dlc.name = dev_name(physdev); + cs35l41_codec_conf[sz].dlc.name = cs35l41_physdev_names[sz]; cs35l41_codec_conf[sz].name_prefix = cs35l41_name_prefixes[uid]; sz++; } From 2f85d1d2779e85ae3a415f48cbaa153caf242355 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:46 +0000 Subject: [PATCH 142/186] ASoC: qcom: q6apm-dai: close the graph when PCM open fails q6apm_dai_open() opens the AudioReach graph and then installs the PCM runtime constraints. If a constraint cannot be installed, the error path only frees the private data and leaves the graph open, leaking the graph and the reference it holds on the AudioReach graph. Close the graph before freeing the private data. A failure from q6apm_graph_open() itself still only needs the private data to be freed. Fixes: 9b4fe0f1cd79 ("ASoC: qdsp6: audioreach: add q6apm-dai support") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024046.1508566-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 58beaf906b9ffd..6c0245e92d3a3a 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -562,6 +562,8 @@ static int q6apm_dai_open(struct snd_soc_component *component, return 0; err: + if (!IS_ERR_OR_NULL(prtd->graph)) + q6apm_graph_close(prtd->graph); kfree(prtd); return ret; From 63364dc9f1018098d87731645f10b892115839d2 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:47 +0000 Subject: [PATCH 143/186] ASoC: qcom: q6apm-dai: close the graph when compress open fails q6apm_dai_compr_open() opens the AudioReach graph before allocating the DMA buffer and handing it to the DSP. If the allocation or the SCM assignment fails, the function returns without closing the graph. The compress core does not call the free callback for a stream that failed to open, so the graph, its AudioReach reference and the private data are all leaked. Unwind the graph and the private data on both failure paths. Fixes: 88b60bf047fd ("ASoC: q6dsp: q6apm-dai: Add open/free compress DAI callbacks") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024047.1508584-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6apm-dai.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6apm-dai.c b/sound/soc/qcom/qdsp6/q6apm-dai.c index 6c0245e92d3a3a..94af3b79c61dc1 100644 --- a/sound/soc/qcom/qdsp6/q6apm-dai.c +++ b/sound/soc/qcom/qdsp6/q6apm-dai.c @@ -834,7 +834,7 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, &prtd->dma_buffer); if (ret) - return ret; + goto err_close_graph; if (pdata->use_scm_assign && !pdata->has_reserved_mem) { ret = q6apm_dai_scm_assign(pdata, prtd->dma_buffer.addr, @@ -843,7 +843,7 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, dev_err(dev, "SCM assign compr buffer failed: %d\n", ret); snd_dma_free_pages(&prtd->dma_buffer); - return ret; + goto err_close_graph; } } @@ -857,6 +857,13 @@ static int q6apm_dai_compr_open(struct snd_soc_component *component, q6apm_enable_compress_module(dev, prtd->graph, true); return 0; + +err_close_graph: + runtime->private_data = NULL; + q6apm_graph_close(prtd->graph); + kfree(prtd); + + return ret; } static int q6apm_dai_compr_free(struct snd_soc_component *component, From 57199400abbfc4ef3339b7a9a6f9ee42fb6cd2b8 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:48 +0000 Subject: [PATCH 144/186] ASoC: qcom: q6usb: put the AFE port after sending the USB parameters q6afe_port_get_from_id() returns a port with a reference held, either because q6afe_find_port() took a kref on an existing port or because a new port was created with an initial kref. q6usb_hw_params() sends the USB device parameters and then returns without dropping that reference, so each hw_params call leaks a port reference. Drop the reference once the parameters have been sent. Fixes: 72b0b8b29980 ("ASoC: qcom: qdsp6: Add USB backend ASoC driver for Q6") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024048.1508635-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/qcom/qdsp6/q6usb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c index e226dd1263643a..5e051a23868a7f 100644 --- a/sound/soc/qcom/qdsp6/q6usb.c +++ b/sound/soc/qcom/qdsp6/q6usb.c @@ -82,6 +82,7 @@ static int q6usb_hw_params(struct snd_pcm_substream *substream, /* Notify audio DSP about the devices being offloaded */ ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, sdev->ppcm_idx[sdev->num_playback - 1]); + q6afe_port_put(q6usb_afe); out: mutex_unlock(&data->mutex); From 5d2a3da8b792581101cdcb0d1ce31c9b0ae57487 Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Fri, 6 Nov 2026 18:52:05 +0800 Subject: [PATCH 145/186] ASoC: dt-bindings: Add Cadence I2S-SC controller Add the DT binding for the Cadence I2S-SC controller integrated in the CIX SKY1 audio subsystem. I2S-SC is a full-duplex stereo controller supporting standard I2S, left/right-justified and DSP formats, plus TDM operation with up to 16 slots. Signed-off-by: Joakim Zhang Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20261106105208.1246554-2-joakim.zhang@cixtech.com Signed-off-by: Mark Brown --- .../bindings/sound/cix,sky1-i2s-sc.yaml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml diff --git a/Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml new file mode 100644 index 00000000000000..9ef11272d0fd89 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-sc.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cix,sky1-i2s-sc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cadence I2S-SC controller + +maintainers: + - Joakim Zhang + +description: | + The Cadence I2S-SC controller integrated in the CIX SKY1 audio subsystem. + It supports full-duplex stereo playback and capture in standard I2S, + left/right-justified and DSP modes, as well as TDM operation with up to + 16 slots. The transmitter and receiver are started and stopped together + by the controller. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: cix,sky1-i2s-sc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: hst + - const: i2s + - const: audio-clk0 + - const: audio-clk2 + + resets: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 2 + description: + One DMA channel for instances wired for a single direction, either + "tx" or "rx", or two channels for both transmit and receive. + + dma-names: + minItems: 1 + items: + - enum: [ tx, rx ] + - const: rx + + "#sound-dai-cells": + const: 0 + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - dmas + - dma-names + - "#sound-dai-cells" + +unevaluatedProperties: false + +examples: + - | + #include + + i2s@7020000 { + compatible = "cix,sky1-i2s-sc"; + reg = <0x7020000 0x10000>; + interrupts = ; + clocks = <&audss_cru 16>, <&audss_cru 26>, + <&scmi_clk 100>, <&scmi_clk 101>; + clock-names = "hst", "i2s", "audio-clk0", "audio-clk2"; + resets = <&audss_cru 0>; + dmas = <&audss_dmac 0>, <&audss_dmac 1>; + dma-names = "tx", "rx"; + #sound-dai-cells = <0>; + }; From 81bb3e5fda49b871b535814d941368900dd22b11 Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Fri, 6 Nov 2026 18:52:06 +0800 Subject: [PATCH 146/186] ASoC: cdns: Add Cadence I2S-SC controller driver Add support for the Cadence I2S-SC controller found in the CIX SKY1 audio subsystem. The controller provides full-duplex stereo playback and capture in standard I2S, left/right-justified and DSP modes, with mono operation mapped onto the left audio channel. It also supports TDM operation with up to 16 slots per frame, where the PCM channels map onto the active slots of the stream direction while the bit clock covers the whole frame. The transmitter and receiver share the transceiver enable sequence, so the start/stop state machine is serialized with a spinlock against independently triggered playback and capture PCMs. The driver registers the dmaengine PCM helper, selects the audio reference clock parent for the 8 kHz or 11.025 kHz sample rate family and keeps the minimum functional-clock to bit-clock ratio required for safe clock domain crossing. Runtime and system suspend/resume restore the registers through the regcache. Signed-off-by: Joakim Zhang Link: https://patch.msgid.link/20261106105208.1246554-3-joakim.zhang@cixtech.com Signed-off-by: Mark Brown --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/cdns/Kconfig | 12 + sound/soc/cdns/Makefile | 4 + sound/soc/cdns/cdns-i2s-sc.c | 1171 ++++++++++++++++++++++++++++++++++ 5 files changed, 1189 insertions(+) create mode 100644 sound/soc/cdns/Kconfig create mode 100644 sound/soc/cdns/Makefile create mode 100644 sound/soc/cdns/cdns-i2s-sc.c diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index edfdcbf734fe69..d9b746ffd701ca 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -108,6 +108,7 @@ source "sound/soc/apple/Kconfig" source "sound/soc/atmel/Kconfig" source "sound/soc/au1x/Kconfig" source "sound/soc/bcm/Kconfig" +source "sound/soc/cdns/Kconfig" source "sound/soc/cirrus/Kconfig" source "sound/soc/dwc/Kconfig" source "sound/soc/fsl/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 21d8406767fcbc..ba3cf6eb937aa8 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -50,6 +50,7 @@ obj-$(CONFIG_SND_SOC) += amd/ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ obj-$(CONFIG_SND_SOC) += bcm/ +obj-$(CONFIG_SND_SOC) += cdns/ obj-$(CONFIG_SND_SOC) += cirrus/ obj-$(CONFIG_SND_SOC) += dwc/ obj-$(CONFIG_SND_SOC) += fsl/ diff --git a/sound/soc/cdns/Kconfig b/sound/soc/cdns/Kconfig new file mode 100644 index 00000000000000..06fd8fd6cb540a --- /dev/null +++ b/sound/soc/cdns/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 + +config SND_SOC_CDNS_I2S_SC + tristate "Cadence I2S-SC controller support" + depends on ARCH_CIX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for the Cadence I2S-SC + controller found in the CIX SKY1 audio subsystem. It provides + full-duplex stereo playback and capture in standard I2S and + TDM modes with up to 16 slots. diff --git a/sound/soc/cdns/Makefile b/sound/soc/cdns/Makefile new file mode 100644 index 00000000000000..6abfeef29e438d --- /dev/null +++ b/sound/soc/cdns/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +snd-soc-cdns-i2s-sc-y := cdns-i2s-sc.o +obj-$(CONFIG_SND_SOC_CDNS_I2S_SC) += snd-soc-cdns-i2s-sc.o diff --git a/sound/soc/cdns/cdns-i2s-sc.c b/sound/soc/cdns/cdns-i2s-sc.c new file mode 100644 index 00000000000000..cbc514db1e325f --- /dev/null +++ b/sound/soc/cdns/cdns-i2s-sc.c @@ -0,0 +1,1171 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2026 Cix Technology Group Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Special Function Registers */ +/* Control Register */ +#define I2S_CTRL 0x00 +#define I2S_CTRL_LR_PACK BIT(31) +#define I2S_CTRL_FIFO_AFULL_MASK BIT(30) +#define I2S_CTRL_FIFO_FULL_MASK BIT(29) +#define I2S_CTRL_FIFO_AEMPTY_MASK BIT(28) +#define I2S_CTRL_FIFO_EMPTY_MASK BIT(27) +#define I2S_CTRL_I2S_MASK BIT(26) +#define I2S_CTRL_INTREQ_MASK BIT(25) +#define I2S_CTRL_I2S_STB BIT(24) +#define I2S_CTRL_HOST_DATA_ALIGN BIT(23) +#define I2S_CTRL_DATA_ORDER BIT(22) +#define I2S_CTRL_DATA_ALIGN BIT(21) +#define I2S_CTRL_DATA_WS_DEL GENMASK(20, 16) +#define I2S_CTRL_WS_POLAR BIT(15) +#define I2S_CTRL_SCK_POLAR BIT(14) +#define I2S_CTRL_AUDIO_MODE BIT(13) +#define I2S_CTRL_MONO_MODE BIT(12) +#define I2S_CTRL_WS_MODE GENMASK(11, 8) +#define I2S_CTRL_CHN_WIDTH GENMASK(7, 5) +#define I2S_CTRL_CHN_WIDTH_8 0 +#define I2S_CTRL_CHN_WIDTH_12 1 +#define I2S_CTRL_CHN_WIDTH_16 2 +#define I2S_CTRL_CHN_WIDTH_18 3 +#define I2S_CTRL_CHN_WIDTH_20 4 +#define I2S_CTRL_CHN_WIDTH_24 5 +#define I2S_CTRL_CHN_WIDTH_28 6 +#define I2S_CTRL_CHN_WIDTH_32 7 +#define I2S_CTRL_FIFO_RST BIT(4) +#define I2S_CTRL_SFR_RST BIT(3) +#define I2S_CTRL_MS_CFG BIT(2) +#define I2S_CTRL_DIR_CFG BIT(1) +#define I2S_CTRL_I2S_EN BIT(0) + +/* Full-Duplex Mode Control Register */ +#define I2S_CTRL_FDX 0x04 +#define I2S_CTRL_FDX_RFIFO_AFULL_MASK BIT(30) +#define I2S_CTRL_FDX_RFIFO_FULL_MASK BIT(29) +#define I2S_CTRL_FDX_RFIFO_AEMPTY_MASK BIT(28) +#define I2S_CTRL_FDX_RFIFO_EMPTY_MASK BIT(27) +#define I2S_CTRL_FDX_RI2S_MASK BIT(26) +#define I2S_CTRL_FDX_RFIFO_RST BIT(4) +#define I2S_CTRL_FDX_I2S_FRX_EN BIT(2) +#define I2S_CTRL_FDX_I2S_FTX_EN BIT(1) +#define I2S_CTRL_FDX_FULL_DUPLEX BIT(0) + +/* Sample Resolution Register */ +#define I2S_SRES 0x08 +#define I2S_SRES_RESOLUTION GENMASK(4, 0) + +/* Full-Duplex Mode Receive Sample Resolution Register */ +#define I2S_SRES_FDR 0x0c +#define I2S_SRES_FDR_RRESOLUTION GENMASK(4, 0) + +/* Transceiver Sample Rate Register */ +#define I2S_SRATE 0x10 +#define I2S_SRATE_SAMPLE_RATE GENMASK(19, 0) + +/* Status Flags Register */ +#define I2S_STAT 0x14 +#define I2S_STAT_RFIFO_AFULL BIT(19) +#define I2S_STAT_RFIFO_FULL BIT(18) +#define I2S_STAT_RFIFO_AEMPTY BIT(17) +#define I2S_STAT_RFIFO_EMPTY BIT(16) +#define I2S_STAT_FIFO_AFULL BIT(5) +#define I2S_STAT_FIFO_FULL BIT(4) +#define I2S_STAT_FIFO_AEMPTY BIT(3) +#define I2S_STAT_FIFO_EMPTY BIT(2) +#define I2S_STAT_RDATA_OVERR BIT(1) +#define I2S_STAT_TDATA_UNDERR BIT(0) + +/* FIFO Level Register (read only) */ +#define I2S_FIFO_LEVEL 0x18 + +/* FIFO Almost Empty Level Register */ +#define I2S_FIFO_AEMPTY 0x1c + +/* FIFO Almost Full Level Register */ +#define I2S_FIFO_AFULL 0x20 + +/* Full-Duplex Mode Receiver FIFO Level Register (read only) */ +#define I2S_FIFO_LEVEL_FDR 0x24 + +/* Full-Duplex Mode Receiver FIFO Almost Empty Level Register */ +#define I2S_FIFO_AEMPTY_FDR 0x28 + +/* Full-Duplex Mode Receiver FIFO Almost Full Level Register */ +#define I2S_FIFO_AFULL_FDR 0x2c + +/* Time Division Multiplexing Control Register */ +#define I2S_TDM_CTRL 0x30 +#define I2S_TDM_CTRL_CHN_EN GENMASK(31, 16) +#define I2S_TDM_CTRL_CHN_NO GENMASK(4, 1) +#define I2S_TDM_CTRL_TDM_EN BIT(0) + +/* Time Division Multiplexing Full-Duplex Mode Channels Direction Register */ +#define I2S_TDM_FD_DIR 0x34 +#define I2S_TDM_FD_DIR_CHN_RXEN GENMASK(31, 16) +#define I2S_TDM_FD_DIR_CHN_TXEN GENMASK(15, 0) + +/* Transmit And Receive FIFOs Address */ +#define I2S_FIFO_ADDRESS 0x40 + +/* + * The I2S functional clock must run at a minimum ratio above the audio + * bit clock to safely cross clock domains, regardless of whether the + * bit clock is generated internally or externally. See the controller + * reference manual, section 3.3 "Clocks and Clock Domain Crossing". + */ +#define I2S_CLK_RATIO_MIN 6 + +#define DRV_NAME "cdns-i2s-sc" + +enum { + AUDIO_CLK0, + AUDIO_CLK2, + AUDIO_CLK_NUM, +}; + +static const char *cdns_i2s_sc_clk_pll_names[AUDIO_CLK_NUM] = { + [AUDIO_CLK0] = "audio-clk0", + [AUDIO_CLK2] = "audio-clk2", +}; + +struct cdns_i2s_sc_devtype_data { + u32 rx_fifo_aempty_threshold; + u32 rx_fifo_afull_threshold; + u32 tx_fifo_aempty_threshold; + u32 tx_fifo_afull_threshold; +}; + +struct cdns_i2s_sc_tdm_config { + unsigned int tx_mask; + unsigned int rx_mask; + int slots; + int slot_width; +}; + +struct cdns_i2s_sc_priv { + struct device *dev; + struct regmap *regmap; + struct reset_control *i2s_rst; + + int irq; + + struct clk *clk_hst; + struct clk *clk_i2s; + struct clk *clks[AUDIO_CLK_NUM]; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + const struct cdns_i2s_sc_devtype_data *devtype_data; + struct cdns_i2s_sc_tdm_config tdm_config; + + bool is_master_mode; + bool is_tdm_mode; + bool is_mono_in_i2s_mode; + + /* Serializes the transmitter/receiver start/stop state machine below */ + spinlock_t lock; + + /* + * NOTE: + * I2S_CTRL_FDX[I2S_FTX_EN] = 1 && I2S_CTRL[I2S_EN] = 1 to enable transmitter + * I2S_CTRL_FDX[I2S_FRX_EN] = 1 && I2S_CTRL[I2S_EN] = 1 to enable receiver + * We can dynamically switch of the transmitter and receiver enable separately, + * but need always enable I2S transceiver, so I2S-SC controller hopes to start + * the transmitter and receiver together, also to stop them when they are both + * try to stop. + * + * The playback and capture PCMs trigger independently (the PCM core holds + * only each substream's own lock), so tx_start/rx_start and the shared + * transceiver registers must be accessed under &lock. + */ + bool rx_start; + bool tx_start; +}; + +static int cdns_i2s_sc_clks_enable(struct cdns_i2s_sc_priv *i2s_sc_priv) +{ + int ret; + + ret = clk_prepare_enable(i2s_sc_priv->clk_hst); + if (ret) + return ret; + + ret = clk_prepare_enable(i2s_sc_priv->clk_i2s); + if (ret) + clk_disable_unprepare(i2s_sc_priv->clk_hst); + + return ret; +} + +static void cdns_i2s_sc_clks_disable(struct cdns_i2s_sc_priv *i2s_sc_priv) +{ + clk_disable_unprepare(i2s_sc_priv->clk_i2s); + clk_disable_unprepare(i2s_sc_priv->clk_hst); +} + +static void cdns_i2s_sc_rxtx_common_config(struct cdns_i2s_sc_priv *i2s_sc_priv, bool on) +{ + if (on) { + /* Full-duplex mode enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_FULL_DUPLEX, I2S_CTRL_FDX_FULL_DUPLEX); + + if (i2s_sc_priv->is_tdm_mode) { + /* TDM mode enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_TDM_EN, I2S_TDM_CTRL_TDM_EN); + + /* Number of supported audio channels in TDM mode */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_CHN_NO, + FIELD_PREP(I2S_TDM_CTRL_CHN_NO, + i2s_sc_priv->tdm_config.slots - 1)); + + /* TDM mode channels enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, I2S_TDM_CTRL_CHN_EN, + FIELD_PREP(I2S_TDM_CTRL_CHN_EN, + i2s_sc_priv->tdm_config.rx_mask | + i2s_sc_priv->tdm_config.tx_mask)); + } + + /* Transceiver clock enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_STB, 0); + + /* All interrupt requests unmask */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_INTREQ_MASK, I2S_CTRL_INTREQ_MASK); + } else { + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_INTREQ_MASK, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_STB, I2S_CTRL_I2S_STB); + + if (i2s_sc_priv->is_tdm_mode) { + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_TDM_EN, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_CHN_NO, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, + I2S_TDM_CTRL_CHN_EN, 0); + } + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_FULL_DUPLEX, 0); + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN, 0); + } +} + +static void cdns_i2s_sc_tx_config(struct cdns_i2s_sc_priv *i2s_sc_priv, bool on) +{ + u32 irq_mask = 0; + + irq_mask |= I2S_CTRL_I2S_MASK; + + if (on) { + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + + /* Transmitter data underrun interrupt unmask */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, irq_mask, irq_mask); + + if (i2s_sc_priv->is_tdm_mode) { + /* TDM mode channels transmit enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_TXEN, + FIELD_PREP(I2S_TDM_FD_DIR_CHN_TXEN, + i2s_sc_priv->tdm_config.tx_mask)); + } else { + /* + * I2S mode mono/stereo selection + * audio_mode: HIGH = only one channel active + * mono_mode: LOW = left channel, HIGH = right channel + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_AUDIO_MODE | I2S_CTRL_MONO_MODE, + i2s_sc_priv->is_mono_in_i2s_mode ? + I2S_CTRL_AUDIO_MODE : 0); + } + + /* Full-duplex mode transmitter enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FTX_EN, I2S_CTRL_FDX_I2S_FTX_EN); + + /* Transceiver enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN, I2S_CTRL_I2S_EN); + + i2s_sc_priv->tx_start = true; + } else { + i2s_sc_priv->tx_start = false; + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FTX_EN, 0); + + if (i2s_sc_priv->is_tdm_mode) + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_TXEN, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, irq_mask, 0); + + if (!i2s_sc_priv->rx_start) + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + } +} + +static void cdns_i2s_sc_rx_config(struct cdns_i2s_sc_priv *i2s_sc_priv, bool on) +{ + u32 irq_mask = 0; + + irq_mask |= I2S_CTRL_FDX_RI2S_MASK; + + if (on) { + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + + /* Receiver data overrun interrupt unmask */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, irq_mask, irq_mask); + + if (i2s_sc_priv->is_tdm_mode) { + /* TDM mode channels receive enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_RXEN, + FIELD_PREP(I2S_TDM_FD_DIR_CHN_RXEN, + i2s_sc_priv->tdm_config.rx_mask)); + } else { + /* + * I2S mode mono/stereo selection + * audio_mode: HIGH = only one channel active + * mono_mode: LOW = left channel, HIGH = right channel + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_AUDIO_MODE | I2S_CTRL_MONO_MODE, + i2s_sc_priv->is_mono_in_i2s_mode ? + I2S_CTRL_AUDIO_MODE : 0); + } + + /* Full-duplex mode receiver enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FRX_EN, I2S_CTRL_FDX_I2S_FRX_EN); + + /* Transceiver enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN, I2S_CTRL_I2S_EN); + + i2s_sc_priv->rx_start = true; + } else { + i2s_sc_priv->rx_start = false; + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_I2S_FRX_EN, 0); + + if (i2s_sc_priv->is_tdm_mode) + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_FD_DIR, + I2S_TDM_FD_DIR_CHN_RXEN, 0); + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, irq_mask, 0); + + if (!i2s_sc_priv->tx_start) + cdns_i2s_sc_rxtx_common_config(i2s_sc_priv, on); + } +} + +static irqreturn_t cdns_i2s_sc_isr(int irq, void *devid) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = (struct cdns_i2s_sc_priv *)devid; + struct device *dev = i2s_sc_priv->dev; + u32 stat; + + if (regmap_read(i2s_sc_priv->regmap, I2S_STAT, &stat)) + return IRQ_NONE; + + stat &= I2S_STAT_TDATA_UNDERR | I2S_STAT_RDATA_OVERR; + if (!stat) + return IRQ_NONE; + + /* Clear the status */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_STAT, stat, 0); + + /* Transmitter status */ + if (stat & I2S_STAT_TDATA_UNDERR) + dev_dbg(dev, "isr: tx data underrun\n"); + + /* Receiver status */ + if (stat & I2S_STAT_RDATA_OVERR) + dev_dbg(dev, "isr: rx data overrun\n"); + + return IRQ_HANDLED; +} + +static int cdns_i2s_sc_set_tdm_slot(struct snd_soc_dai *cpu_dai, unsigned int tx_mask, + unsigned int rx_mask, int slots, int slot_width) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_sc_priv->dev; + + dev_dbg(dev, "tx_mask = 0x%x, rx_mask = 0x%x, slots = %d, slot_width = %d\n", + tx_mask, rx_mask, slots, slot_width); + + if (!slots) { + i2s_sc_priv->is_tdm_mode = false; + return 0; + } + + if (slots < 0 || slots > 16) { + dev_err(i2s_sc_priv->dev, "TDM mode supports 1 to 16 slots\n"); + return -EINVAL; + } + + if (slot_width < 8 || slot_width > 32) { + dev_err(i2s_sc_priv->dev, "TDM slot width must be 8 to 32 bits\n"); + return -EINVAL; + } + + if ((tx_mask >> slots) || (rx_mask >> slots)) { + dev_err(i2s_sc_priv->dev, "TDM slot mask has bits outside the slots\n"); + return -EINVAL; + } + + i2s_sc_priv->tdm_config.slots = slots; + i2s_sc_priv->tdm_config.slot_width = slot_width; + i2s_sc_priv->tdm_config.rx_mask = rx_mask; + i2s_sc_priv->tdm_config.tx_mask = tx_mask; + + i2s_sc_priv->is_tdm_mode = true; + + return 0; +} + +static int cdns_i2s_sc_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_sc_priv->dev; + u32 ctrl = 0, ctrl_mask = 0; + + dev_dbg(dev, "format = 0x%x\n", fmt); + + /* DAI hardware audio formats */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 1) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 1); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + case SND_SOC_DAIFMT_RIGHT_J: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 1) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 0) | + I2S_CTRL_DATA_ALIGN; + ctrl &= ~I2S_CTRL_DATA_ORDER; + break; + case SND_SOC_DAIFMT_LEFT_J: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 1) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 0); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + case SND_SOC_DAIFMT_DSP_A: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 0) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 1); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + case SND_SOC_DAIFMT_DSP_B: + ctrl |= FIELD_PREP(I2S_CTRL_WS_MODE, 0) | + FIELD_PREP(I2S_CTRL_DATA_WS_DEL, 0); + ctrl &= ~(I2S_CTRL_DATA_ALIGN | I2S_CTRL_DATA_ORDER); + break; + default: + return -EINVAL; + } + + /* DAI hardware signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* both normal clocks */ + ctrl |= I2S_CTRL_SCK_POLAR; + ctrl &= ~I2S_CTRL_WS_POLAR; + break; + case SND_SOC_DAIFMT_NB_IF: + /* invert frame clock */ + ctrl |= I2S_CTRL_SCK_POLAR; + ctrl |= I2S_CTRL_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_NF: + /* invert bit clock */ + ctrl &= ~I2S_CTRL_SCK_POLAR; + ctrl &= ~I2S_CTRL_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_IF: + /* invert both clocks */ + ctrl &= ~I2S_CTRL_SCK_POLAR; + ctrl |= I2S_CTRL_WS_POLAR; + break; + default: + return -EINVAL; + } + + /* DAI hardware clock masters */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + ctrl &= ~I2S_CTRL_MS_CFG; + i2s_sc_priv->is_master_mode = false; + break; + case SND_SOC_DAIFMT_CBP_CFP: + ctrl |= I2S_CTRL_MS_CFG; + i2s_sc_priv->is_master_mode = true; + break; + default: + return -EINVAL; + } + + ctrl_mask |= I2S_CTRL_WS_MODE | I2S_CTRL_DATA_WS_DEL | I2S_CTRL_DATA_ALIGN | + I2S_CTRL_DATA_ORDER | I2S_CTRL_SCK_POLAR | I2S_CTRL_WS_POLAR | + I2S_CTRL_MS_CFG; + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, ctrl_mask, ctrl); + + return 0; +} + +static int cdns_i2s_sc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + /* + * The transceiver clock must stay on across the FIFO reset below, + * while the trigger() of the other stream may gate it through the + * shared start/stop state machine. Serialize against it with &lock. + */ + scoped_guard(spinlock_irqsave, &i2s_sc_priv->lock) { + /* Transceiver clock enable */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, I2S_CTRL_I2S_STB, 0); + + /* + * FIFO reset should meet two requirements + * 1) after i2s_stb clock on + * 2) before dma ready to receive trigger + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* + * Transmitter FIFO reset + * When LOW, FIFO pointer is reset to zero. Threshold levels for + * FIFO are unchanged. This bit is automatically set to HIGH after + * one clock cycle. + * Deassert then assert this bit here, since I2S_CTRL register is not + * volatile, would not read from hardware any longer. If not, it would + * clear tx fifo every time when write this register. + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, I2S_CTRL_FIFO_RST, 0); + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_FIFO_RST, I2S_CTRL_FIFO_RST); + + /* Transmitter FIFO threshold set */ + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AEMPTY, + i2s_sc_priv->devtype_data->tx_fifo_aempty_threshold); + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AFULL, + i2s_sc_priv->devtype_data->tx_fifo_afull_threshold); + } else { + /* + * Receiver FIFO reset + * When '0', RFIFO pointer is reset to zero. Threshold levels for RFIFO + * are unchanged. The bit is automatically set to '1' after one clock cycle. + * Deassert then assert this bit here, since I2S_CTRL_FDX register is not + * volatile, would not read from hardware any longer. If not, it would + * clear rx fifo every time when write this register. + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_RFIFO_RST, 0); + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_RFIFO_RST, I2S_CTRL_FDX_RFIFO_RST); + + /* Receiver FIFO threshold set */ + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AEMPTY_FDR, + i2s_sc_priv->devtype_data->rx_fifo_aempty_threshold); + regmap_write(i2s_sc_priv->regmap, I2S_FIFO_AFULL_FDR, + i2s_sc_priv->devtype_data->rx_fifo_afull_threshold); + } + } + + return 0; +} + +static int cdns_i2s_sc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_sc_priv->dev; + u32 slots, slot_width, resolution, val; + u32 rate, sample_rate, frame_slots; + unsigned long i2s_clk_rate; + struct clk *clk_parent; + int ret; + + /* Set sample slots, slot width and resolution */ + slots = params_channels(params); + resolution = params_width(params); + dev_dbg(dev, "slots = %d, resolution = %d\n", slots, resolution); + + if (i2s_sc_priv->is_tdm_mode) { + unsigned int mask, nch; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mask = i2s_sc_priv->tdm_config.tx_mask; + else + mask = i2s_sc_priv->tdm_config.rx_mask; + + nch = hweight32(mask); + if (slots != nch) { + dev_err(i2s_sc_priv->dev, + "Channel number does not match active tdm slots: %d\n", nch); + return -EINVAL; + } + + if (resolution != i2s_sc_priv->tdm_config.slot_width) { + dev_err(i2s_sc_priv->dev, + "Sample width does not match with specified tdm slot width\n"); + return -EINVAL; + } + + slot_width = i2s_sc_priv->tdm_config.slot_width; + + frame_slots = i2s_sc_priv->tdm_config.slots; + } else { + /* + * I2S mode: mono maps onto the left audio channel, while + * the frame still carries two slots. + */ + if (slots == 1) { + i2s_sc_priv->is_mono_in_i2s_mode = true; + slots = 2; + } else if (slots == 2) { + i2s_sc_priv->is_mono_in_i2s_mode = false; + } else { + dev_warn(i2s_sc_priv->dev, + "I2S mode supports up to two channels\n"); + return -EOPNOTSUPP; + } + + slot_width = 32; + + frame_slots = slots; + } + + dev_dbg(dev, "slot_width = %d\n", slot_width); + + if (slot_width == 8) { + val = I2S_CTRL_CHN_WIDTH_8; + } else if (slot_width == 12) { + val = I2S_CTRL_CHN_WIDTH_12; + } else if (slot_width == 16) { + val = I2S_CTRL_CHN_WIDTH_16; + } else if (slot_width == 18) { + val = I2S_CTRL_CHN_WIDTH_18; + } else if (slot_width == 20) { + val = I2S_CTRL_CHN_WIDTH_20; + } else if (slot_width == 24) { + val = I2S_CTRL_CHN_WIDTH_24; + } else if (slot_width == 28) { + val = I2S_CTRL_CHN_WIDTH_28; + } else if (slot_width == 32) { + val = I2S_CTRL_CHN_WIDTH_32; + } else { + dev_err(i2s_sc_priv->dev, "Slot width is invalid value\n"); + return -EINVAL; + } + + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, I2S_CTRL_CHN_WIDTH, + FIELD_PREP(I2S_CTRL_CHN_WIDTH, val)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_write(i2s_sc_priv->regmap, I2S_SRES, + FIELD_PREP(I2S_SRES_RESOLUTION, (resolution - 1))); + else + regmap_write(i2s_sc_priv->regmap, I2S_SRES_FDR, + FIELD_PREP(I2S_SRES_FDR_RRESOLUTION, (resolution - 1))); + + rate = params_rate(params); + dev_dbg(dev, "rate = %d\n", rate); + + /* switch clk mux to select the appropriate clk parent */ + if (rate % 8000 == 0) { + /* Sampling rate is a multiple of 8KHz, select "audio-clk0" */ + clk_parent = i2s_sc_priv->clks[AUDIO_CLK0]; + + ret = clk_set_parent(i2s_sc_priv->clk_i2s, clk_parent); + } else if (rate % 11025 == 0) { + /* Sampling rate is a multiple of 11.025KHz, select "audio-clk2" */ + clk_parent = i2s_sc_priv->clks[AUDIO_CLK2]; + + ret = clk_set_parent(i2s_sc_priv->clk_i2s, clk_parent); + } else { + dev_err(i2s_sc_priv->dev, "Invalid sample rate\n"); + return -EINVAL; + } + if (ret) { + dev_err(i2s_sc_priv->dev, "Failed to set i2s clock parent\n"); + return ret; + } + + i2s_clk_rate = clk_get_rate(i2s_sc_priv->clk_i2s); + dev_dbg(dev, "i2s clk rate = %lu\n", i2s_clk_rate); + + if (i2s_clk_rate < rate * slot_width * frame_slots * I2S_CLK_RATIO_MIN) { + dev_err(i2s_sc_priv->dev, + "clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n", + i2s_clk_rate, rate, slot_width, frame_slots, I2S_CLK_RATIO_MIN, + rate * slot_width * frame_slots * I2S_CLK_RATIO_MIN); + return -EINVAL; + } + + /* Set sample rate */ + if (i2s_sc_priv->is_master_mode) { + sample_rate = DIV_ROUND_CLOSEST(i2s_clk_rate, (rate * frame_slots * slot_width)); + regmap_write(i2s_sc_priv->regmap, I2S_SRATE, + FIELD_PREP(I2S_SRATE_SAMPLE_RATE, sample_rate)); + } + + return 0; +} + +static int cdns_i2s_sc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + scoped_guard(spinlock_irqsave, &i2s_sc_priv->lock) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_sc_tx_config(i2s_sc_priv, true); + else + cdns_i2s_sc_rx_config(i2s_sc_priv, true); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + scoped_guard(spinlock_irqsave, &i2s_sc_priv->lock) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_sc_tx_config(i2s_sc_priv, false); + else + cdns_i2s_sc_rx_config(i2s_sc_priv, false); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cdns_i2s_sc_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 ctrl = 0; + + /* + * Transceiver disable + * Transceiver clock disable + * All interrupts masked + */ + ctrl &= ~(I2S_CTRL_I2S_EN | I2S_CTRL_INTREQ_MASK); + ctrl |= I2S_CTRL_I2S_STB; + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN | I2S_CTRL_INTREQ_MASK | + I2S_CTRL_LR_PACK | I2S_CTRL_I2S_STB, + ctrl); + + /* + * Full-duplex mode disable + * Full-duplex mode transmitter disable + * Full-duplex mode receiver disable + */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_CTRL_FDX, + I2S_CTRL_FDX_FULL_DUPLEX | I2S_CTRL_FDX_I2S_FTX_EN | + I2S_CTRL_FDX_I2S_FRX_EN, + 0); + + /* TDM mode disable, default works in standard stereo I2S mode */ + regmap_update_bits(i2s_sc_priv->regmap, I2S_TDM_CTRL, I2S_TDM_CTRL_TDM_EN, 0); + + snd_soc_dai_init_dma_data(cpu_dai, &i2s_sc_priv->playback_dma_data, + &i2s_sc_priv->capture_dma_data); + + snd_soc_dai_set_drvdata(cpu_dai, i2s_sc_priv); + + return 0; +} + +static const struct snd_soc_dai_ops cdns_i2s_sc_dai_ops = { + .probe = cdns_i2s_sc_dai_probe, + .set_tdm_slot = cdns_i2s_sc_set_tdm_slot, + .set_fmt = cdns_i2s_sc_set_fmt, + .hw_params = cdns_i2s_sc_hw_params, + .prepare = cdns_i2s_sc_prepare, + .trigger = cdns_i2s_sc_trigger, +}; + +static struct snd_soc_dai_driver cdns_i2s_sc_dai = { + .name = "i2s-sc-aif", + .playback = { + .stream_name = "I2S-SC-Playback", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .capture = { + .stream_name = "I2S-SC-Capture", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &cdns_i2s_sc_dai_ops, + .symmetric_rate = 1, + .symmetric_channels = 1, +}; + +static const struct snd_soc_component_driver cdns_i2s_sc_component = { + .name = DRV_NAME, + .legacy_dai_naming = 1, +}; + +static const struct reg_default cdns_i2s_sc_reg_defaults[] = { + { I2S_CTRL, 0x000001b8 }, + { I2S_CTRL_FDX, 0x00000010 }, + { I2S_SRES, 0x00000000 }, + { I2S_SRES_FDR, 0x00000000 }, + { I2S_SRATE, 0x00000000 }, + { I2S_STAT, 0x0003000c }, + { I2S_FIFO_LEVEL, 0x00000000 }, + { I2S_FIFO_AEMPTY, 0x00000000 }, + { I2S_FIFO_AFULL, 0x0000000f }, + { I2S_FIFO_LEVEL_FDR, 0x00000000 }, + { I2S_FIFO_AEMPTY_FDR, 0x00000000 }, + { I2S_FIFO_AFULL_FDR, 0x0000000f }, + { I2S_TDM_CTRL, 0xffff0000 }, + { I2S_TDM_FD_DIR, 0x0000ffff }, +}; + +static bool cdns_i2s_sc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_CTRL_FDX: + case I2S_SRES: + case I2S_SRES_FDR: + case I2S_SRATE: + case I2S_STAT: + case I2S_FIFO_LEVEL: + case I2S_FIFO_AEMPTY: + case I2S_FIFO_AFULL: + case I2S_FIFO_LEVEL_FDR: + case I2S_FIFO_AEMPTY_FDR: + case I2S_FIFO_AFULL_FDR: + case I2S_TDM_CTRL: + case I2S_TDM_FD_DIR: + return true; + default: + return false; + } +} + +static bool cdns_i2s_sc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_CTRL_FDX: + case I2S_SRES: + case I2S_SRES_FDR: + case I2S_SRATE: + case I2S_STAT: + case I2S_FIFO_AEMPTY: + case I2S_FIFO_AFULL: + case I2S_FIFO_AEMPTY_FDR: + case I2S_FIFO_AFULL_FDR: + case I2S_TDM_CTRL: + case I2S_TDM_FD_DIR: + return true; + default: + return false; + } +} + +static bool cdns_i2s_sc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_STAT: + case I2S_FIFO_LEVEL: + case I2S_FIFO_LEVEL_FDR: + return true; + default: + return false; + } +} + +static const struct regmap_config cdns_i2s_sc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = I2S_TDM_FD_DIR, + .reg_defaults = cdns_i2s_sc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cdns_i2s_sc_reg_defaults), + .readable_reg = cdns_i2s_sc_readable_reg, + .writeable_reg = cdns_i2s_sc_writeable_reg, + .volatile_reg = cdns_i2s_sc_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static void cdns_i2s_sc_rst(struct cdns_i2s_sc_priv *i2s_sc_priv) +{ + /* reset */ + reset_control_assert(i2s_sc_priv->i2s_rst); + + usleep_range(1, 2); + + /* release reset */ + reset_control_deassert(i2s_sc_priv->i2s_rst); +} + +static int cdns_i2s_sc_runtime_suspend(struct device *dev); + +static int cdns_i2s_sc_probe(struct platform_device *pdev) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv; + struct resource *res; + void __iomem *base; + int i, irq, ret; + + i2s_sc_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_sc_priv), GFP_KERNEL); + if (!i2s_sc_priv) + return -ENOMEM; + + spin_lock_init(&i2s_sc_priv->lock); + + platform_set_drvdata(pdev, i2s_sc_priv); + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s_sc_priv->dev = &pdev->dev; + i2s_sc_priv->devtype_data = device_get_match_data(&pdev->dev); + if (!i2s_sc_priv->devtype_data) + return -ENODEV; + + i2s_sc_priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &cdns_i2s_sc_regmap_config); + if (IS_ERR(i2s_sc_priv->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->regmap), + "Failed to initialize managed register map\n"); + + i2s_sc_priv->clk_hst = devm_clk_get(&pdev->dev, "hst"); + if (IS_ERR(i2s_sc_priv->clk_hst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->clk_hst), + "Failed to get hst clock\n"); + + i2s_sc_priv->clk_i2s = devm_clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s_sc_priv->clk_i2s)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->clk_i2s), + "Failed to get i2s clock\n"); + + for (i = 0; i < AUDIO_CLK_NUM; i++) { + i2s_sc_priv->clks[i] = devm_clk_get(&pdev->dev, + cdns_i2s_sc_clk_pll_names[i]); + if (IS_ERR(i2s_sc_priv->clks[i])) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->clks[i]), + "Failed to get %s clock\n", + cdns_i2s_sc_clk_pll_names[i]); + } + + i2s_sc_priv->i2s_rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s_sc_priv->i2s_rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_sc_priv->i2s_rst), + "Failed to get reset control\n"); + + i2s_sc_priv->playback_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_sc_priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_sc_priv->playback_dma_data.maxburst = 4; + + i2s_sc_priv->capture_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_sc_priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_sc_priv->capture_dma_data.maxburst = 4; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register dmaengine component\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + i2s_sc_priv->irq = irq; + + ret = cdns_i2s_sc_clks_enable(i2s_sc_priv); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to enable clocks\n"); + + cdns_i2s_sc_rst(i2s_sc_priv); + + ret = devm_request_irq(&pdev->dev, irq, cdns_i2s_sc_isr, 0, + dev_name(&pdev->dev), i2s_sc_priv); + if (ret) { + cdns_i2s_sc_clks_disable(i2s_sc_priv); + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); + } + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &cdns_i2s_sc_component, + &cdns_i2s_sc_dai, 1); + if (ret) + goto err_pm; + + pm_runtime_put_sync(&pdev->dev); + + return 0; + +err_pm: + cdns_i2s_sc_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, "Failed to register soc component\n"); +} + +static int cdns_i2s_sc_runtime_suspend(struct device *dev) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = dev_get_drvdata(dev); + + /* + * Disable the IRQ before switching the regcache to cache-only, so an + * in-flight handler can never access the registers through a + * cache-only regmap. disable_irq() waits for the handler to finish. + */ + disable_irq(i2s_sc_priv->irq); + regcache_cache_only(i2s_sc_priv->regmap, true); + cdns_i2s_sc_clks_disable(i2s_sc_priv); + + return 0; +} + +static int cdns_i2s_sc_runtime_resume(struct device *dev) +{ + struct cdns_i2s_sc_priv *i2s_sc_priv = dev_get_drvdata(dev); + int ret; + + ret = cdns_i2s_sc_clks_enable(i2s_sc_priv); + if (ret) { + dev_err(dev, "Failed to enable clocks:%d\n", ret); + return ret; + } + + cdns_i2s_sc_rst(i2s_sc_priv); + + regcache_cache_only(i2s_sc_priv->regmap, false); + regcache_mark_dirty(i2s_sc_priv->regmap); + + ret = regcache_sync(i2s_sc_priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regcache: %d\n", ret); + regcache_cache_only(i2s_sc_priv->regmap, true); + cdns_i2s_sc_clks_disable(i2s_sc_priv); + return ret; + } + + /* Enable the IRQ only after the regcache is back in sync */ + enable_irq(i2s_sc_priv->irq); + + return 0; +} + +static int cdns_i2s_sc_system_suspend(struct device *dev) +{ + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int cdns_i2s_sc_system_resume(struct device *dev) +{ + int ret; + + pinctrl_pm_select_default_state(dev); + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops cdns_i2s_sc_pm_ops = { + RUNTIME_PM_OPS(cdns_i2s_sc_runtime_suspend, cdns_i2s_sc_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(cdns_i2s_sc_system_suspend, cdns_i2s_sc_system_resume) +}; + +static const struct cdns_i2s_sc_devtype_data sky1_devtype_data = { + .rx_fifo_aempty_threshold = 4, + .rx_fifo_afull_threshold = 12, + .tx_fifo_aempty_threshold = 4, + .tx_fifo_afull_threshold = 12, +}; + +static const struct of_device_id cdns_i2s_sc_of_match[] = { + { .compatible = "cix,sky1-i2s-sc", .data = &sky1_devtype_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cdns_i2s_sc_of_match); + +static void cdns_i2s_sc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + cdns_i2s_sc_runtime_suspend(&pdev->dev); +} + +static struct platform_driver cdns_i2s_sc_driver = { + .probe = cdns_i2s_sc_probe, + .remove = cdns_i2s_sc_remove, + .driver = { + .name = DRV_NAME, + .pm = pm_ptr(&cdns_i2s_sc_pm_ops), + .of_match_table = cdns_i2s_sc_of_match, + }, +}; +module_platform_driver(cdns_i2s_sc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cadence I2S-SC Controller Driver"); From fe1cecba2f2a4774d9866305a4869ef7b0bc77bf Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Fri, 6 Nov 2026 18:52:07 +0800 Subject: [PATCH 147/186] ASoC: dt-bindings: Add Cadence I2S-MC controller Add the DT binding for the Cadence I2S-MC multi-channel controller integrated in the CIX SKY1 audio subsystem. The controller exposes a transmit DAI and a receive DAI and moves audio through a configurable set of its 8 data pins. Which pins are wired out and in which direction is fixed by the board design and described by the cix,pin-out-num, cix,pin-rx-mask and cix,pin-tx-mask properties. Signed-off-by: Joakim Zhang Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20261106105208.1246554-4-joakim.zhang@cixtech.com Signed-off-by: Mark Brown --- .../bindings/sound/cix,sky1-i2s-mc.yaml | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml diff --git a/Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml new file mode 100644 index 00000000000000..e750009edbdb4b --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cix,sky1-i2s-mc.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/cix,sky1-i2s-mc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Cadence I2S-MC controller + +maintainers: + - Joakim Zhang + +description: | + The Cadence I2S-MC multi-channel controller integrated in the CIX SKY1 + audio subsystem. It provides up to 8-channel playback and capture through + a configurable number of I2S data pins, and exposes two DAIs: a transmit + DAI and a receive DAI. + +allOf: + - $ref: dai-common.yaml# + +properties: + compatible: + const: cix,sky1-i2s-mc + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 4 + + clock-names: + items: + - const: hst + - const: i2s + - const: audio-clk0 + - const: audio-clk2 + + resets: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 2 + description: + One DMA channel for instances wired for a single direction, either + "tx" or "rx", or two channels for both transmit and receive. + + dma-names: + minItems: 1 + items: + - enum: [ tx, rx ] + - const: rx + + "#sound-dai-cells": + const: 1 + description: + Cell selects the DAI, 0 for the transmit DAI (i2s-mc-aif1) and + 1 for the receive DAI (i2s-mc-aif2). + + cix,pin-out-num: + $ref: /schemas/types.yaml#/definitions/uint8 + minimum: 1 + maximum: 8 + description: + Number of I2S data output pins wired on this instance. Which of + the 8 controller data pins are wired out is fixed by the board + design and can be any subset. + + cix,pin-rx-mask: + $ref: /schemas/types.yaml#/definitions/uint8 + description: + Bit mask of I2S data pins used for capture, bit N selects data + pin N. + + cix,pin-tx-mask: + $ref: /schemas/types.yaml#/definitions/uint8 + description: + Bit mask of I2S data pins used for playback, bit N selects data + pin N. The rx and tx masks must not overlap, and the number of + set bits must not exceed cix,pin-out-num. + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - resets + - dmas + - dma-names + - "#sound-dai-cells" + - cix,pin-out-num + - cix,pin-rx-mask + - cix,pin-tx-mask + +unevaluatedProperties: false + +examples: + - | + #include + + i2s@7050000 { + compatible = "cix,sky1-i2s-mc"; + reg = <0x7050000 0x10000>; + interrupts = ; + clocks = <&audss_cru 17>, <&audss_cru 27>, + <&scmi_clk 100>, <&scmi_clk 101>; + clock-names = "hst", "i2s", "audio-clk0", "audio-clk2"; + resets = <&audss_cru 1>; + dmas = <&audss_dmac 2>, <&audss_dmac 3>; + dma-names = "tx", "rx"; + #sound-dai-cells = <1>; + cix,pin-out-num = /bits/ 8 <2>; + cix,pin-rx-mask = /bits/ 8 <0x1>; + cix,pin-tx-mask = /bits/ 8 <0x2>; + }; From a6feafc3bbed014b1231e9bd5f5984af21a9db44 Mon Sep 17 00:00:00 2001 From: Joakim Zhang Date: Fri, 6 Nov 2026 18:52:08 +0800 Subject: [PATCH 148/186] ASoC: cdns: Add Cadence I2S-MC controller driver Add support for the Cadence I2S-MC multi-channel controller found in the CIX SKY1 audio subsystem. The controller moves up to 16 channels through a configurable set of its 8 data pins, each pin carrying two slots. Wired-out pins may sit at any pin index and are described by cix,pin-out-num, cix,pin-rx-mask and cix,pin-tx-mask. The driver exposes a transmit DAI and a receive DAI which can act as clock provider or consumer independently, and streams with fewer channels than the wired pins narrow the enabled pin mask at runtime. The driver registers the dmaengine PCM helper, selects the audio reference clock parent for the 8 kHz or 11.025 kHz sample rate family and keeps the minimum functional-clock to bit-clock ratio required for safe clock domain crossing, with an extra margin cycle when the bit clock is sourced externally. Runtime and system suspend/resume restore the registers through the regcache. Signed-off-by: Joakim Zhang Link: https://patch.msgid.link/20261106105208.1246554-5-joakim.zhang@cixtech.com Signed-off-by: Mark Brown --- sound/soc/cdns/Kconfig | 11 + sound/soc/cdns/Makefile | 3 + sound/soc/cdns/cdns-i2s-mc.c | 1152 ++++++++++++++++++++++++++++++++++ 3 files changed, 1166 insertions(+) create mode 100644 sound/soc/cdns/cdns-i2s-mc.c diff --git a/sound/soc/cdns/Kconfig b/sound/soc/cdns/Kconfig index 06fd8fd6cb540a..d0ef3ced06b2b8 100644 --- a/sound/soc/cdns/Kconfig +++ b/sound/soc/cdns/Kconfig @@ -10,3 +10,14 @@ config SND_SOC_CDNS_I2S_SC controller found in the CIX SKY1 audio subsystem. It provides full-duplex stereo playback and capture in standard I2S and TDM modes with up to 16 slots. + +config SND_SOC_CDNS_I2S_MC + tristate "Cadence I2S-MC controller support" + depends on ARCH_CIX || COMPILE_TEST + select SND_SOC_GENERIC_DMAENGINE_PCM + select REGMAP_MMIO + help + Say Y or M if you want to add support for the Cadence I2S-MC + multi-channel controller found in the CIX SKY1 audio subsystem. + It provides multi-channel playback and capture through a + configurable number of I2S data pins. diff --git a/sound/soc/cdns/Makefile b/sound/soc/cdns/Makefile index 6abfeef29e438d..e440bb2bb5ca44 100644 --- a/sound/soc/cdns/Makefile +++ b/sound/soc/cdns/Makefile @@ -2,3 +2,6 @@ snd-soc-cdns-i2s-sc-y := cdns-i2s-sc.o obj-$(CONFIG_SND_SOC_CDNS_I2S_SC) += snd-soc-cdns-i2s-sc.o + +snd-soc-cdns-i2s-mc-y := cdns-i2s-mc.o +obj-$(CONFIG_SND_SOC_CDNS_I2S_MC) += snd-soc-cdns-i2s-mc.o diff --git a/sound/soc/cdns/cdns-i2s-mc.c b/sound/soc/cdns/cdns-i2s-mc.c new file mode 100644 index 00000000000000..5bbac98101449d --- /dev/null +++ b/sound/soc/cdns/cdns-i2s-mc.c @@ -0,0 +1,1152 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright 2026 Cix Technology Group Co., Ltd. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Special Function Registers */ +/* Control Register */ +#define I2S_CTRL 0x00 +#define I2S_CTRL_RSYNC_LOOP_BACK BIT(28) +#define I2S_CTRL_TSYNC_LOOP_BACK BIT(27) +#define I2S_CTRL_RSYNC_RST BIT(26) +#define I2S_CTRL_TSYNC_RST BIT(25) +#define I2S_CTRL_RFIFO_RST BIT(24) +#define I2S_CTRL_TFIFO_RST BIT(23) +#define I2S_CTRL_R_MS BIT(22) +#define I2S_CTRL_T_MS BIT(21) +#define I2S_CTRL_SFR_RST BIT(20) +#define I2S_CTRL_LOOP_BACK_6_7 BIT(19) +#define I2S_CTRL_LOOP_BACK_4_5 BIT(18) +#define I2S_CTRL_LOOP_BACK_2_3 BIT(17) +#define I2S_CTRL_LOOP_BACK_0_1 BIT(16) +#define I2S_CTRL_TR_CFG GENMASK(15, 8) +#define I2S_CTRL_I2S_EN GENMASK(7, 0) + +/* Interrupt Status Register */ +#define I2S_INTR_STAT 0x04 +#define I2S_INTR_STAT_RFIFO_AFULL BIT(15) +#define I2S_INTR_STAT_RFIFO_FULL BIT(14) +#define I2S_INTR_STAT_RFIFO_AEMPTY BIT(13) +#define I2S_INTR_STAT_RFIFO_EMPTY BIT(12) +#define I2S_INTR_STAT_TFIFO_AFULL BIT(11) +#define I2S_INTR_STAT_TFIFO_FULL BIT(10) +#define I2S_INTR_STAT_TFIFO_AEMPTY BIT(9) +#define I2S_INTR_STAT_TFIFO_EMPTY BIT(8) +#define I2S_INTR_STAT_OVERR_CODE GENMASK(7, 5) +#define I2S_INTR_STAT_RDATA_OVERR BIT(4) +#define I2S_INTR_STAT_UNDERR_CODE GENMASK(3, 1) +#define I2S_INTR_STAT_TDATA_UNDERR BIT(0) + +/* Sample Rate And Resolution Control Register */ +#define I2S_SRR 0x08 +#define I2S_SRR_RRESOLUTION GENMASK(31, 27) +#define I2S_SRR_RSAMPLE_RATE GENMASK(26, 16) +#define I2S_SRR_TRESOLUTION GENMASK(15, 11) +#define I2S_SRR_TSAMPLE_RATE GENMASK(10, 0) + +/* Clock Strobes And Interrupt Masks Control Register */ +#define I2S_CID_CTRL 0x0c +#define I2S_CID_CTRL_RFIFO_AFULL_MASK BIT(31) +#define I2S_CID_CTRL_RFIFO_FULL_MASK BIT(30) +#define I2S_CID_CTRL_RFIFO_AEMPTY_MASK BIT(29) +#define I2S_CID_CTRL_RFIFO_EMPTY_MASK BIT(28) +#define I2S_CID_CTRL_TFIFO_AFULL_MASK BIT(27) +#define I2S_CID_CTRL_TFIFO_FULL_MASK BIT(26) +#define I2S_CID_CTRL_TFIFO_AEMPTY_MASK BIT(25) +#define I2S_CID_CTRL_TFIFO_EMPTY_MASK BIT(24) +#define I2S_CID_CTRL_I2S_MASK GENMASK(23, 16) +#define I2S_CID_CTRL_INTREQ_MASK BIT(15) +#define I2S_CID_CTRL_STROBE_RS BIT(9) +#define I2S_CID_CTRL_STROBE_TS BIT(8) +#define I2S_CID_CTRL_I2S_STROBE GENMASK(7, 0) + +/* Transmit FIFO Level Status Register, read only */ +#define I2S_TFIFO_STAT 0x10 + +/* Receive FIFO Level Status Register, read only */ +#define I2S_RFIFO_STAT 0x14 + +/* Transmit FIFO Thresholds Control Register */ +#define I2S_TFIFO_CTRL 0x18 +#define I2S_TFIFO_CTRL_TAFULL_THRESHOLD GENMASK(31, 16) +#define I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD GENMASK(15, 0) + +/* Receive FIFO Thresholds Control Register */ +#define I2S_RFIFO_CTRL 0x1c +#define I2S_RFIFO_CTRL_RAFULL_THRESHOLD GENMASK(31, 16) +#define I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD GENMASK(15, 0) + +/* Device Configuration Register */ +#define I2S_DEV_CONF 0x20 +#define I2S_DEV_CONF_REC_WS_DSP_MODE BIT(11) +#define I2S_DEV_CONF_REC_DATA_WS_DEL BIT(10) +#define I2S_DEV_CONF_REC_I2S_ALIGN_LR BIT(9) +#define I2S_DEV_CONF_REC_APB_ALIGN_LR BIT(8) +#define I2S_DEV_CONF_REC_WS_POLAR BIT(7) +#define I2S_DEV_CONF_REC_SCK_POLAR BIT(6) +#define I2S_DEV_CONF_TRAN_WS_DSP_MODE BIT(5) +#define I2S_DEV_CONF_TRAN_DATA_WS_DEL BIT(4) +#define I2S_DEV_CONF_TRAN_I2S_ALIGN_LR BIT(3) +#define I2S_DEV_CONF_TRAN_APB_ALIGN_LR BIT(2) +#define I2S_DEV_CONF_TRAN_WS_POLAR BIT(1) +#define I2S_DEV_CONF_TRAN_SCK_POLAR BIT(0) + +/* Status Register, read only */ +#define I2S_POLL_STAT 0x24 +#define I2S_POLL_STAT_RX_OVERRUN BIT(6) +#define I2S_POLL_STAT_RX_AFULL BIT(5) +#define I2S_POLL_STAT_RX_FULL BIT(4) +#define I2S_POLL_STAT_TX_UNDERRUN BIT(2) +#define I2S_POLL_STAT_TX_AEMPTY BIT(1) +#define I2S_POLL_STAT_TX_EMPTY BIT(0) + +/* Transmit And Recevie FIFOs Address */ +#define I2S_FIFO_ADDRESS 0x3c + +/* + * The I2S functional clock must run at a minimum ratio above the audio + * bit clock to safely cross clock domains; a bit clock sourced + * externally (slave mode) needs one more cycle of margin. See the + * controller reference manual, section 3.3 "Clocks and Clock Domain + * Crossing". + */ +#define I2S_CLK_RATIO_MIN_MASTER 6 +#define I2S_CLK_RATIO_MIN_SLAVE 7 + +#define DRV_NAME "cdns-i2s-mc" + +enum { + AUDIO_CLK0, + AUDIO_CLK2, + AUDIO_CLK_NUM, +}; + +static const char *cdns_i2s_mc_clk_pll_names[AUDIO_CLK_NUM] = { + [AUDIO_CLK0] = "audio-clk0", + [AUDIO_CLK2] = "audio-clk2", +}; + +enum { + I2S_MC_AIF1, + I2S_MC_AIF2, +}; + +struct cdns_i2s_mc_devtype_data { + u32 data_width; + u32 rfifo_aempty_threshold; + u32 rfifo_afull_threshold; + u32 tfifo_aempty_threshold; + u32 tfifo_afull_threshold; +}; + +struct cdns_i2s_mc_priv { + struct device *dev; + struct regmap *regmap; + struct reset_control *i2s_rst; + + int irq; + + struct clk *clk_hst; + struct clk *clk_i2s; + struct clk *clks[AUDIO_CLK_NUM]; + + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + + const struct cdns_i2s_mc_devtype_data *devtype_data; + + u8 pin_out_num; + u8 pin_rx_mask; + u8 pin_rx_mask_adjust; + u8 pin_tx_mask; + u8 pin_tx_mask_adjust; +}; + +static int cdns_i2s_mc_clks_enable(struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + int ret; + + ret = clk_prepare_enable(i2s_mc_priv->clk_hst); + if (ret) + return ret; + + ret = clk_prepare_enable(i2s_mc_priv->clk_i2s); + if (ret) + clk_disable_unprepare(i2s_mc_priv->clk_hst); + + return ret; +} + +static void cdns_i2s_mc_clks_disable(struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + clk_disable_unprepare(i2s_mc_priv->clk_i2s); + clk_disable_unprepare(i2s_mc_priv->clk_hst); +} + +static void cdns_i2s_mc_adjust_pin_config(u8 *pin_mask, u32 slots) +{ + u8 mask = 0, num = 0; + int i; + + /* + * Wired-out pins may sit at any index among the 8 data pins, so + * scan the whole mask and keep the lowest pins until enough slots + * are covered. + */ + for (i = 0; i < BITS_PER_BYTE; i++) { + if (*pin_mask & (0x1 << i)) { + mask |= (0x1 << i); + if (++num == slots / 2) { + *pin_mask = mask; + break; + } + } + } +} + +static void cdns_i2s_mc_rxtx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on, + u8 pin_mask, u32 strobe, u32 sync_rst) +{ + u32 irq_mask = 0, clk_mask = 0, i2s_mask = 0; + + irq_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_MASK, pin_mask); + + clk_mask |= FIELD_PREP(I2S_CID_CTRL_I2S_STROBE, pin_mask) | strobe; + + i2s_mask |= FIELD_PREP(I2S_CTRL_I2S_EN, pin_mask); + + if (on) { + /* Data underrun/overrun interrupt unmask */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, irq_mask); + + /* Data pins clock enable */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, 0); + + /* + * Transmitter or receiver enable + * Synchronizing unit out of reset + */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + i2s_mask | sync_rst, + i2s_mask | sync_rst); + } else { + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + i2s_mask | sync_rst, 0); + + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, clk_mask, clk_mask); + + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, irq_mask, 0); + } +} + +static void cdns_i2s_mc_tx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on) +{ + cdns_i2s_mc_rxtx_config(i2s_mc_priv, on, i2s_mc_priv->pin_tx_mask_adjust, + I2S_CID_CTRL_STROBE_TS, I2S_CTRL_TSYNC_RST); +} + +static void cdns_i2s_mc_rx_config(struct cdns_i2s_mc_priv *i2s_mc_priv, bool on) +{ + cdns_i2s_mc_rxtx_config(i2s_mc_priv, on, i2s_mc_priv->pin_rx_mask_adjust, + I2S_CID_CTRL_STROBE_RS, I2S_CTRL_RSYNC_RST); +} + +static irqreturn_t cdns_i2s_mc_isr(int irq, void *devid) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = (struct cdns_i2s_mc_priv *)devid; + struct device *dev = i2s_mc_priv->dev; + u32 stat; + + if (regmap_read(i2s_mc_priv->regmap, I2S_INTR_STAT, &stat)) + return IRQ_NONE; + + stat &= I2S_INTR_STAT_TDATA_UNDERR | I2S_INTR_STAT_RDATA_OVERR; + if (!stat) + return IRQ_NONE; + + /* Clear the status */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_INTR_STAT, stat, 0); + + /* Transmitter status */ + if (stat & I2S_INTR_STAT_TDATA_UNDERR) + dev_dbg(dev, "isr: tx data underrun\n"); + + /* Receiver status */ + if (stat & I2S_INTR_STAT_RDATA_OVERR) + dev_dbg(dev, "isr: rx data overrun\n"); + + return IRQ_HANDLED; +} + +static int cdns_i2s_mc_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_mc_priv->dev; + u32 ctrl = 0, dev_conf = 0; + + dev_dbg(dev, "fmt = 0x%x\n", fmt); + + /* + * Configure the data pin channel directions, shared by both DAIs: + * a set TR_CFG bit marks the pin as a transmitter channel, a clear + * one as a receiver channel. Program the combined value so the + * write is idempotent and independent of the set_fmt call order. + */ + ctrl |= FIELD_PREP(I2S_CTRL_TR_CFG, + i2s_mc_priv->pin_tx_mask | + (u8)~i2s_mc_priv->pin_rx_mask); + + if (cpu_dai->id == I2S_MC_AIF1) { + /* DAI hardware signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* both normal clocks */ + dev_conf |= I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_TRAN_WS_POLAR; + break; + case SND_SOC_DAIFMT_NB_IF: + /* invert frame clock */ + dev_conf |= I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_TRAN_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_NF: + /* invert bit clock */ + dev_conf &= ~I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_TRAN_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_IF: + /* invert both clocks */ + dev_conf &= ~I2S_DEV_CONF_TRAN_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_TRAN_WS_POLAR; + break; + default: + return -EINVAL; + } + + /* DAI hardware audio formats */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR; + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL | + I2S_DEV_CONF_TRAN_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_RIGHT_J: + dev_conf |= I2S_DEV_CONF_TRAN_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_LEFT_J: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | I2S_DEV_CONF_TRAN_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_DSP_A: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_WS_DSP_MODE; + dev_conf &= ~(I2S_DEV_CONF_TRAN_APB_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL); + break; + case SND_SOC_DAIFMT_DSP_B: + dev_conf |= I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL | + I2S_DEV_CONF_TRAN_WS_DSP_MODE; + dev_conf &= ~I2S_DEV_CONF_TRAN_APB_ALIGN_LR; + break; + default: + return -EINVAL; + } + + /* DAI hardware clock masters */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + ctrl &= ~I2S_CTRL_T_MS; + break; + case SND_SOC_DAIFMT_CBP_CFP: + ctrl |= I2S_CTRL_T_MS; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_DEV_CONF, + I2S_DEV_CONF_TRAN_SCK_POLAR | I2S_DEV_CONF_TRAN_WS_POLAR | + I2S_DEV_CONF_TRAN_APB_ALIGN_LR | I2S_DEV_CONF_TRAN_I2S_ALIGN_LR | + I2S_DEV_CONF_TRAN_DATA_WS_DEL | + I2S_DEV_CONF_TRAN_WS_DSP_MODE, dev_conf); + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_T_MS | I2S_CTRL_TR_CFG, ctrl); + } else if (cpu_dai->id == I2S_MC_AIF2) { + /* DAI hardware signal polarity */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* both normal clocks */ + dev_conf |= I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_REC_WS_POLAR; + break; + case SND_SOC_DAIFMT_NB_IF: + /* invert frame clock */ + dev_conf |= I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_REC_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_NF: + /* invert bit clock */ + dev_conf &= ~I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf &= ~I2S_DEV_CONF_REC_WS_POLAR; + break; + case SND_SOC_DAIFMT_IB_IF: + /* invert both clocks */ + dev_conf &= ~I2S_DEV_CONF_REC_SCK_POLAR; + dev_conf |= I2S_DEV_CONF_REC_WS_POLAR; + break; + default: + return -EINVAL; + } + + /* DAI hardware audio formats */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR; + dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL | + I2S_DEV_CONF_REC_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_RIGHT_J: + dev_conf |= I2S_DEV_CONF_REC_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_APB_ALIGN_LR | + I2S_DEV_CONF_REC_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_LEFT_J: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | I2S_DEV_CONF_REC_DATA_WS_DEL; + dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_WS_DSP_MODE); + break; + case SND_SOC_DAIFMT_DSP_A: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_WS_DSP_MODE; + dev_conf &= ~(I2S_DEV_CONF_REC_APB_ALIGN_LR | + I2S_DEV_CONF_REC_DATA_WS_DEL); + break; + case SND_SOC_DAIFMT_DSP_B: + dev_conf |= I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_DATA_WS_DEL | + I2S_DEV_CONF_REC_WS_DSP_MODE; + dev_conf &= ~I2S_DEV_CONF_REC_APB_ALIGN_LR; + break; + default: + return -EINVAL; + } + + /* DAI hardware clock masters */ + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + ctrl &= ~I2S_CTRL_R_MS; + break; + case SND_SOC_DAIFMT_CBP_CFP: + ctrl |= I2S_CTRL_R_MS; + break; + default: + return -EINVAL; + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_DEV_CONF, + I2S_DEV_CONF_REC_SCK_POLAR | I2S_DEV_CONF_REC_WS_POLAR | + I2S_DEV_CONF_REC_APB_ALIGN_LR | I2S_DEV_CONF_REC_I2S_ALIGN_LR | + I2S_DEV_CONF_REC_DATA_WS_DEL | + I2S_DEV_CONF_REC_WS_DSP_MODE, dev_conf); + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_R_MS | I2S_CTRL_TR_CFG, ctrl); + } else { + dev_err(i2s_mc_priv->dev, "Invalid dai id\n"); + return -EINVAL; + } + + return 0; +} + +static int cdns_i2s_mc_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 ctrl = 0, fifo = 0; + + /* + * FIFO reset should meet two requirements + * 1) after strobe_ts and strobe_rs clock on + * 2) before dma ready to receive trigger + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* Transmitter clock enable */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_STROBE_TS, 0); + + /* + * Transmitter FIFO reset + * When '0', transmit FIFO pointers are reset to zero. Threshold level for + * this FIFO is unchanged. This bit is automatically set to '1' after one + * clock cycle if TX FIFO reset has been acknowledged. + * Deassert then assert this bit here, since I2S_CTRL register is not + * volatile, would not read from hardware any longer. If not, it would + * clear tx fifo every time when write this register. + */ + ctrl &= ~I2S_CTRL_TFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_TFIFO_RST, ctrl); + ctrl |= I2S_CTRL_TFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_TFIFO_RST, ctrl); + + /* Transmitter FIFO threshold set */ + fifo |= FIELD_PREP(I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD, + i2s_mc_priv->devtype_data->tfifo_aempty_threshold) | + FIELD_PREP(I2S_TFIFO_CTRL_TAFULL_THRESHOLD, + i2s_mc_priv->devtype_data->tfifo_afull_threshold); + regmap_update_bits(i2s_mc_priv->regmap, I2S_TFIFO_CTRL, + I2S_TFIFO_CTRL_TAEMPTY_THRESHOLD | + I2S_TFIFO_CTRL_TAFULL_THRESHOLD, fifo); + } else { + /* Receiver clock enable */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_STROBE_RS, 0); + + /* + * Receiver FIFO reset + * When '0', receive FIFO pointers are reset to zero. Threshold level for + * this FIFO is unchanged. This bit is automatically set to '1' after one + * clock cycle if RX FIFO reset has been acknowledged. + * Deassert then assert this bit here, since I2S_CTRL register is not + * volatile, would not read from hardware any longer. If not, it would + * clear rx fifo every time when write this register. + */ + ctrl &= ~I2S_CTRL_RFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_RFIFO_RST, ctrl); + ctrl |= I2S_CTRL_RFIFO_RST; + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_RFIFO_RST, ctrl); + + /* Receiver FIFO threshold set */ + fifo |= FIELD_PREP(I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD, + i2s_mc_priv->devtype_data->rfifo_aempty_threshold) | + FIELD_PREP(I2S_RFIFO_CTRL_RAFULL_THRESHOLD, + i2s_mc_priv->devtype_data->rfifo_afull_threshold); + regmap_update_bits(i2s_mc_priv->regmap, I2S_RFIFO_CTRL, + I2S_RFIFO_CTRL_RAEMPTY_THRESHOLD | + I2S_RFIFO_CTRL_RAFULL_THRESHOLD, fifo); + } + + /* + * Enable global interrupt mask, for both transmitter and receiver, use + * individual interrupt masks + */ + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_INTREQ_MASK, I2S_CID_CTRL_INTREQ_MASK); + + return 0; +} + +static bool cdns_i2s_mc_other_dai_active(struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_dai *dai; + + for_each_component_dais(cpu_dai->component, dai) { + if (dai == cpu_dai) + continue; + if (snd_soc_dai_active(dai)) + return true; + } + + return false; +} + +static int cdns_i2s_mc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct device *dev = i2s_mc_priv->dev; + u32 rate, sample_rate = 0; + u32 slots, slot_width, resolution, ctrl; + unsigned long i2s_clk_rate; + u8 pin_tx_num, pin_rx_num; + struct clk *clk_parent; + bool is_master_mode; + int ret; + + rate = params_rate(params); + slot_width = i2s_mc_priv->devtype_data->data_width; + dev_dbg(dev, "rate = %d, slot_width = %d\n", rate, slot_width); + + /* switch clk mux to select the appropriate clk parent */ + if (rate % 8000 == 0) { + /* Sampling rate is a multiple of 8KHz, select "audio-clk0" */ + clk_parent = i2s_mc_priv->clks[AUDIO_CLK0]; + } else if (rate % 11025 == 0) { + /* Sampling rate is a multiple of 11.025KHz, select "audio-clk2" */ + clk_parent = i2s_mc_priv->clks[AUDIO_CLK2]; + } else { + dev_err(i2s_mc_priv->dev, "Invalid sample rate\n"); + return -EINVAL; + } + + if (cdns_i2s_mc_other_dai_active(cpu_dai) && + !clk_is_match(clk_get_parent(i2s_mc_priv->clk_i2s), clk_parent)) { + dev_err(dev, + "i2s clock parent conflicts with active stream on the other DAI\n"); + return -EBUSY; + } + + ret = clk_set_parent(i2s_mc_priv->clk_i2s, clk_parent); + if (ret) { + dev_err(i2s_mc_priv->dev, "Failed to set i2s clock parent\n"); + return ret; + } + + i2s_clk_rate = clk_get_rate(i2s_mc_priv->clk_i2s); + dev_dbg(dev, "i2s clk rate = %lu\n", i2s_clk_rate); + + regmap_read(i2s_mc_priv->regmap, I2S_CTRL, &ctrl); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + is_master_mode = ctrl & I2S_CTRL_T_MS; + else + is_master_mode = ctrl & I2S_CTRL_R_MS; + + if (is_master_mode) { + if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_MASTER) { + dev_err(i2s_mc_priv->dev, + "clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n", + i2s_clk_rate, rate, slot_width, 2, I2S_CLK_RATIO_MIN_MASTER, + rate * slot_width * 2 * I2S_CLK_RATIO_MIN_MASTER); + return -EINVAL; + } + + sample_rate = DIV_ROUND_CLOSEST(i2s_clk_rate, (rate * 2 * slot_width)); + } else { + if (i2s_clk_rate < rate * 2 * slot_width * I2S_CLK_RATIO_MIN_SLAVE) { + dev_err(i2s_mc_priv->dev, + "clk freq %lu is too low, must >= (%d * %d * %d * %d = %d)\n", + i2s_clk_rate, rate, slot_width, 2, I2S_CLK_RATIO_MIN_SLAVE, + rate * slot_width * 2 * I2S_CLK_RATIO_MIN_SLAVE); + return -EINVAL; + } + } + + slots = params_channels(params); + resolution = params_width(params); + dev_dbg(dev, "slots = %d, resolution = %d\n", slots, resolution); + + /* Each data pin carries two slots, so the channel count must be even */ + if (slots % 2) { + dev_err(i2s_mc_priv->dev, "Channel number must be a multiple of two\n"); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_mc_priv->pin_tx_mask_adjust = i2s_mc_priv->pin_tx_mask; + + pin_tx_num = hweight8(i2s_mc_priv->pin_tx_mask); + if (slots > 2 * pin_tx_num) { + dev_warn(i2s_mc_priv->dev, + "Transmit channel number is larger than hardware config\n"); + return -EOPNOTSUPP; + } else if (slots < 2 * pin_tx_num) { + cdns_i2s_mc_adjust_pin_config(&i2s_mc_priv->pin_tx_mask_adjust, slots); + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_TSAMPLE_RATE, + FIELD_PREP(I2S_SRR_TSAMPLE_RATE, sample_rate)); + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_TRESOLUTION, + FIELD_PREP(I2S_SRR_TRESOLUTION, (resolution - 1))); + } else { + i2s_mc_priv->pin_rx_mask_adjust = i2s_mc_priv->pin_rx_mask; + + pin_rx_num = hweight8(i2s_mc_priv->pin_rx_mask); + if (slots > 2 * pin_rx_num) { + dev_warn(i2s_mc_priv->dev, + "Receive channel number is larger than hardware config\n"); + return -EOPNOTSUPP; + } else if (slots < 2 * pin_rx_num) { + cdns_i2s_mc_adjust_pin_config(&i2s_mc_priv->pin_rx_mask_adjust, slots); + } + + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_RSAMPLE_RATE, + FIELD_PREP(I2S_SRR_RSAMPLE_RATE, sample_rate)); + regmap_update_bits(i2s_mc_priv->regmap, I2S_SRR, I2S_SRR_RRESOLUTION, + FIELD_PREP(I2S_SRR_RRESOLUTION, (resolution - 1))); + } + + return 0; +} + +static int cdns_i2s_mc_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_mc_tx_config(i2s_mc_priv, true); + else + cdns_i2s_mc_rx_config(i2s_mc_priv, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + cdns_i2s_mc_tx_config(i2s_mc_priv, false); + else + cdns_i2s_mc_rx_config(i2s_mc_priv, false); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cdns_i2s_mc_dai_probe(struct snd_soc_dai *cpu_dai) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = snd_soc_dai_get_drvdata(cpu_dai); + u32 ctrl = 0, cid_ctrl = 0; + + /* + * Transceiver disable + * Transceiver clock disable + * All interrupts masked + * Reset for transmitter synchronizing unit + * Reset for receiver synchronizing unit + */ + ctrl |= FIELD_PREP(I2S_CTRL_I2S_EN, 0); + ctrl &= ~(I2S_CTRL_TSYNC_RST | I2S_CTRL_RSYNC_RST); + cid_ctrl |= I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS; + cid_ctrl &= ~I2S_CID_CTRL_INTREQ_MASK; + + regmap_update_bits(i2s_mc_priv->regmap, I2S_CTRL, + I2S_CTRL_I2S_EN | I2S_CTRL_TSYNC_RST | I2S_CTRL_RSYNC_RST, ctrl); + regmap_update_bits(i2s_mc_priv->regmap, I2S_CID_CTRL, + I2S_CID_CTRL_STROBE_TS | I2S_CID_CTRL_STROBE_RS | + I2S_CID_CTRL_INTREQ_MASK, cid_ctrl); + + snd_soc_dai_init_dma_data(cpu_dai, &i2s_mc_priv->playback_dma_data, + &i2s_mc_priv->capture_dma_data); + + snd_soc_dai_set_drvdata(cpu_dai, i2s_mc_priv); + + return 0; +} + +static const struct snd_soc_dai_ops cdns_i2s_mc_dai_ops = { + .probe = cdns_i2s_mc_dai_probe, + .set_fmt = cdns_i2s_mc_set_fmt, + .hw_params = cdns_i2s_mc_hw_params, + .prepare = cdns_i2s_mc_prepare, + .trigger = cdns_i2s_mc_trigger, +}; + +static struct snd_soc_dai_driver cdns_i2s_mc_dai[] = { + { + .name = "i2s-mc-aif1", + .id = I2S_MC_AIF1, + .playback = { + .stream_name = "I2S-MC-Playback", + .channels_min = 2, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &cdns_i2s_mc_dai_ops, + }, + { + .name = "i2s-mc-aif2", + .id = I2S_MC_AIF2, + .capture = { + .stream_name = "I2S-MC-Capture", + .channels_min = 2, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE), + }, + .ops = &cdns_i2s_mc_dai_ops, + }, +}; + +static const struct snd_soc_component_driver cdns_i2s_mc_component = { + .name = DRV_NAME, + .legacy_dai_naming = 1, +}; + +static const struct reg_default cdns_i2s_mc_reg_defaults[] = { + { I2S_CTRL, 0x01900000 }, + { I2S_INTR_STAT, 0x00003300 }, + { I2S_SRR, 0x00000000 }, + { I2S_CID_CTRL, 0x00000000 }, + { I2S_TFIFO_STAT, 0x00000000 }, + { I2S_RFIFO_STAT, 0x00000000 }, + { I2S_TFIFO_CTRL, 0x000f0000 }, + { I2S_RFIFO_CTRL, 0x000f0000 }, + { I2S_DEV_CONF, 0x00000208 }, + { I2S_POLL_STAT, 0x00000003 }, +}; + +static bool cdns_i2s_mc_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_INTR_STAT: + case I2S_SRR: + case I2S_CID_CTRL: + case I2S_TFIFO_STAT: + case I2S_RFIFO_STAT: + case I2S_TFIFO_CTRL: + case I2S_RFIFO_CTRL: + case I2S_DEV_CONF: + case I2S_POLL_STAT: + return true; + default: + return false; + } +} + +static bool cdns_i2s_mc_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_CTRL: + case I2S_INTR_STAT: + case I2S_SRR: + case I2S_CID_CTRL: + case I2S_TFIFO_CTRL: + case I2S_RFIFO_CTRL: + case I2S_DEV_CONF: + return true; + default: + return false; + } +} + +static bool cdns_i2s_mc_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_INTR_STAT: + case I2S_TFIFO_STAT: + case I2S_RFIFO_STAT: + case I2S_POLL_STAT: + return true; + default: + return false; + } +} + +static const struct regmap_config cdns_i2s_mc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + + .max_register = I2S_POLL_STAT, + .reg_defaults = cdns_i2s_mc_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(cdns_i2s_mc_reg_defaults), + .readable_reg = cdns_i2s_mc_readable_reg, + .writeable_reg = cdns_i2s_mc_writeable_reg, + .volatile_reg = cdns_i2s_mc_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int cdns_i2s_get_pin_config(struct platform_device *pdev, + struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + u8 rxtx_mask; + int ret; + + ret = device_property_read_u8(&pdev->dev, + "cix,pin-out-num", &i2s_mc_priv->pin_out_num); + if (ret) { + dev_err(&pdev->dev, "Failed to get pin-out-num: %d\n", ret); + return ret; + } + + if (!i2s_mc_priv->pin_out_num || i2s_mc_priv->pin_out_num > BITS_PER_BYTE) { + dev_err(&pdev->dev, "Pin out number should be in range [1, 8]\n"); + return -EINVAL; + } + + ret = device_property_read_u8(&pdev->dev, + "cix,pin-rx-mask", &i2s_mc_priv->pin_rx_mask); + if (ret) { + dev_err(&pdev->dev, "Failed to get pin-rx-mask: %d\n", ret); + return ret; + } + + ret = device_property_read_u8(&pdev->dev, + "cix,pin-tx-mask", &i2s_mc_priv->pin_tx_mask); + if (ret) { + dev_err(&pdev->dev, "Failed to get pin-tx-mask: %d\n", ret); + return ret; + } + + rxtx_mask = i2s_mc_priv->pin_rx_mask & i2s_mc_priv->pin_tx_mask; + if (rxtx_mask) { + dev_err(&pdev->dev, "Pin configuration for transmitter and receiver is conflict\n"); + return -EINVAL; + } + + rxtx_mask = i2s_mc_priv->pin_rx_mask | i2s_mc_priv->pin_tx_mask; + if (hweight8(rxtx_mask) > i2s_mc_priv->pin_out_num) { + dev_err(&pdev->dev, "Pin configuration is out of range\n"); + return -EINVAL; + } + + return 0; +} + +static void cdns_i2s_mc_rst(struct cdns_i2s_mc_priv *i2s_mc_priv) +{ + /* reset */ + reset_control_assert(i2s_mc_priv->i2s_rst); + + usleep_range(1, 2); + + /* release reset */ + reset_control_deassert(i2s_mc_priv->i2s_rst); +} + +static int cdns_i2s_mc_runtime_suspend(struct device *dev); + +static int cdns_i2s_mc_probe(struct platform_device *pdev) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv; + struct resource *res; + void __iomem *base; + int i, irq, ret; + + i2s_mc_priv = devm_kzalloc(&pdev->dev, sizeof(*i2s_mc_priv), GFP_KERNEL); + if (!i2s_mc_priv) + return -ENOMEM; + + platform_set_drvdata(pdev, i2s_mc_priv); + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + i2s_mc_priv->dev = &pdev->dev; + i2s_mc_priv->devtype_data = device_get_match_data(&pdev->dev); + if (!i2s_mc_priv->devtype_data) + return -ENODEV; + + i2s_mc_priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &cdns_i2s_mc_regmap_config); + if (IS_ERR(i2s_mc_priv->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->regmap), + "Failed to initialize managed register map\n"); + + i2s_mc_priv->clk_hst = devm_clk_get(&pdev->dev, "hst"); + if (IS_ERR(i2s_mc_priv->clk_hst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clk_hst), + "Failed to get hst clock\n"); + + i2s_mc_priv->clk_i2s = devm_clk_get(&pdev->dev, "i2s"); + if (IS_ERR(i2s_mc_priv->clk_i2s)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clk_i2s), + "Failed to get i2s clock\n"); + + for (i = 0; i < AUDIO_CLK_NUM; i++) { + i2s_mc_priv->clks[i] = devm_clk_get(&pdev->dev, + cdns_i2s_mc_clk_pll_names[i]); + if (IS_ERR(i2s_mc_priv->clks[i])) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->clks[i]), + "Failed to get %s clock\n", + cdns_i2s_mc_clk_pll_names[i]); + } + + i2s_mc_priv->i2s_rst = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(i2s_mc_priv->i2s_rst)) + return dev_err_probe(&pdev->dev, PTR_ERR(i2s_mc_priv->i2s_rst), + "Failed to get reset control\n"); + + ret = cdns_i2s_get_pin_config(pdev, i2s_mc_priv); + if (ret) + return ret; + + i2s_mc_priv->playback_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_mc_priv->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_mc_priv->playback_dma_data.maxburst = 4; + + i2s_mc_priv->capture_dma_data.addr = res->start + I2S_FIFO_ADDRESS; + /* Buswidth will be set by framework at runtime */ + i2s_mc_priv->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_UNDEFINED; + i2s_mc_priv->capture_dma_data.maxburst = 4; + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register dmaengine component\n"); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + i2s_mc_priv->irq = irq; + + ret = cdns_i2s_mc_clks_enable(i2s_mc_priv); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed to enable clocks\n"); + + cdns_i2s_mc_rst(i2s_mc_priv); + + ret = devm_request_irq(&pdev->dev, irq, cdns_i2s_mc_isr, 0, + dev_name(&pdev->dev), i2s_mc_priv); + if (ret) { + cdns_i2s_mc_clks_disable(i2s_mc_priv); + return dev_err_probe(&pdev->dev, ret, "Failed to request irq\n"); + } + + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = devm_snd_soc_register_component(&pdev->dev, &cdns_i2s_mc_component, + cdns_i2s_mc_dai, ARRAY_SIZE(cdns_i2s_mc_dai)); + if (ret) + goto err_pm; + + pm_runtime_put_sync(&pdev->dev); + + return 0; + +err_pm: + cdns_i2s_mc_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + return dev_err_probe(&pdev->dev, ret, "Failed to register soc component\n"); +} + +static int cdns_i2s_mc_runtime_suspend(struct device *dev) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev); + + /* + * Disable the IRQ before switching the regcache to cache-only, so an + * in-flight handler can never access the registers through a + * cache-only regmap. disable_irq() waits for the handler to finish. + */ + disable_irq(i2s_mc_priv->irq); + regcache_cache_only(i2s_mc_priv->regmap, true); + cdns_i2s_mc_clks_disable(i2s_mc_priv); + + return 0; +} + +static int cdns_i2s_mc_runtime_resume(struct device *dev) +{ + struct cdns_i2s_mc_priv *i2s_mc_priv = dev_get_drvdata(dev); + int ret; + + ret = cdns_i2s_mc_clks_enable(i2s_mc_priv); + if (ret) { + dev_err(dev, "Failed to enable clocks:%d\n", ret); + return ret; + } + + cdns_i2s_mc_rst(i2s_mc_priv); + + regcache_cache_only(i2s_mc_priv->regmap, false); + regcache_mark_dirty(i2s_mc_priv->regmap); + + ret = regcache_sync(i2s_mc_priv->regmap); + if (ret) { + dev_err(dev, "Failed to sync regcache: %d\n", ret); + regcache_cache_only(i2s_mc_priv->regmap, true); + cdns_i2s_mc_clks_disable(i2s_mc_priv); + return ret; + } + + /* Enable the IRQ only after the regcache is back in sync */ + enable_irq(i2s_mc_priv->irq); + + return 0; +} + +static int cdns_i2s_mc_system_suspend(struct device *dev) +{ + int ret; + + ret = pm_runtime_force_suspend(dev); + if (ret) + return ret; + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int cdns_i2s_mc_system_resume(struct device *dev) +{ + int ret; + + pinctrl_pm_select_default_state(dev); + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops cdns_i2s_mc_pm_ops = { + RUNTIME_PM_OPS(cdns_i2s_mc_runtime_suspend, cdns_i2s_mc_runtime_resume, NULL) + SYSTEM_SLEEP_PM_OPS(cdns_i2s_mc_system_suspend, cdns_i2s_mc_system_resume) +}; + +static const struct cdns_i2s_mc_devtype_data sky1_devtype_data = { + .data_width = 32, + .rfifo_aempty_threshold = 4, + .rfifo_afull_threshold = 12, + .tfifo_aempty_threshold = 4, + .tfifo_afull_threshold = 12, +}; + +static const struct of_device_id cdns_i2s_mc_of_match[] = { + { .compatible = "cix,sky1-i2s-mc", .data = &sky1_devtype_data }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, cdns_i2s_mc_of_match); + +static void cdns_i2s_mc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + cdns_i2s_mc_runtime_suspend(&pdev->dev); +} + +static struct platform_driver cdns_i2s_mc_driver = { + .probe = cdns_i2s_mc_probe, + .remove = cdns_i2s_mc_remove, + .driver = { + .name = DRV_NAME, + .pm = pm_ptr(&cdns_i2s_mc_pm_ops), + .of_match_table = cdns_i2s_mc_of_match, + }, +}; +module_platform_driver(cdns_i2s_mc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Joakim Zhang "); +MODULE_DESCRIPTION("Cadence I2S-MC Controller Driver"); From 0e9a49186621709b67e3a4b5343de17aa1601567 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:02:50 +0000 Subject: [PATCH 149/186] ASoC: soc-core: fixup snd_soc_dai_compress_new() return MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Original snd_soc_dai_compress_new() return was below. Here, snd_soc_dai_compress_new() will return -ENOTSUPP if driver doesn't support .compress_new() callback. Will return handled value if callback was supported. ret = snd_soc_dai_compress_new(...); if (ret != -ENOTSUPP) { if (ret < 0) dev_err(...); return ret; } So this means, it will return here anyway if driver supports the callback even though it was success/failed. This was updated by commit 62462e018220 ("ASoC: don't indicate error message for snd_soc_[pcm_]dai_xxx()") like below ret = snd_soc_dai_compress_new(...); if (ret != -ENOTSUPP) return ret; It still return here if driver supports callback. But commit dd9f9cc1e6b9 ("ASoC: core: Do not call link_exit() on uninitialized rtd objects") updated to like below to call snd_soc_link_exit() if error case. ret = snd_soc_dai_compress_new(...); if (ret != -ENOTSUPP) => goto err; But this looks like error, but is not. This is very confusable. Let's tidyup them. Fixes: dd9f9cc1e6b9 ("ASoC: core: Do not call link_exit() on uninitialized rtd objects") Cc: Amadeusz SÅ‚awiÅ„ski Signed-off-by: Kuninori Morimoto Reported-by: Sashiko Link: https://patch.msgid.link/87a4nyttlx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 97269b4ee4ca5c..1c3ed2975cac76 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1184,10 +1184,13 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card, /* add DPCM sysfs entries */ soc_dpcm_debugfs_add(rtd); - /* create compress_device if possible */ + /* create compress_device if driver is supporting */ ret = snd_soc_dai_compress_new(cpu_dai, rtd); - if (ret != -ENOTSUPP) - goto err; + if (ret != -ENOTSUPP) { /* driver is supporting */ + if (ret < 0) + goto err; + goto end; + } /* create the pcm */ ret = soc_new_pcm(rtd); @@ -1200,12 +1203,13 @@ static int soc_init_pcm_runtime(struct snd_soc_card *card, ret = snd_soc_pcm_dai_new(rtd); if (ret < 0) goto err; - +end: rtd->initialized = true; return 0; err: snd_soc_link_exit(rtd); + return ret; } From 930c66f40da1fb97cc0fe8abb034ada8a5f2053f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:15 +0000 Subject: [PATCH 150/186] ASoC: soc-card: move snd_soc_add_card_controls() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It renames function name - snd_soc_add_card_controls() + snd_soc_card_add_controls() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/878q3ittl8.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- include/sound/soc-card.h | 2 ++ include/sound/soc.h | 3 +-- sound/soc/soc-card.c | 20 ++++++++++++++++++++ sound/soc/soc-core.c | 20 -------------------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/include/sound/soc-card.h b/include/sound/soc-card.h index 1f1e7d45f55335..ce2b8653f67d37 100644 --- a/include/sound/soc-card.h +++ b/include/sound/soc-card.h @@ -28,6 +28,8 @@ static inline void snd_soc_card_mutex_unlock(struct snd_soc_card *card) mutex_unlock(&card->mutex); } +int snd_soc_card_add_controls(struct snd_soc_card *soc_card, + const struct snd_kcontrol_new *controls, int num_controls); struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name); int snd_soc_card_jack_new(struct snd_soc_card *card, const char *id, int type, diff --git a/include/sound/soc.h b/include/sound/soc.h index f6b98324f3464b..338662645b42fa 100644 --- a/include/sound/soc.h +++ b/include/sound/soc.h @@ -540,8 +540,6 @@ static inline int snd_soc_set_ac97_ops(struct snd_ac97_bus_ops *ops) struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template, void *data, const char *long_name, const char *prefix); -int snd_soc_add_card_controls(struct snd_soc_card *soc_card, - const struct snd_kcontrol_new *controls, int num_controls); int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo); int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, @@ -1539,5 +1537,6 @@ static inline void _snd_soc_dpcm_mutex_assert_held_r(struct snd_soc_pcm_runtime #define snd_soc_register_dai snd_soc_dai_register #define snd_soc_unregister_dai snd_soc_dai_unregister #define snd_soc_add_component_controls snd_soc_component_add_controls +#define snd_soc_add_card_controls snd_soc_card_add_controls #endif diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 282d666dae9ec5..7dafe22d8a7896 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -10,6 +10,7 @@ #include #include #include +#include "soc-internal.h" #define soc_card_ret(dai, ret) _soc_card_ret(dai, __func__, ret) static inline int _soc_card_ret(struct snd_soc_card *card, @@ -259,3 +260,22 @@ void snd_soc_card_set_topology_name(struct snd_soc_card *card, const char *prefi card->name = card->topology_shortname; } EXPORT_SYMBOL_GPL(snd_soc_card_set_topology_name); + +/** + * snd_soc_card_add_controls - add an array of controls to a SoC card. + * Convenience function to add a list of controls. + * + * @soc_card: SoC card to add controls to + * @controls: array of controls to add + * @num_controls: number of elements in the array + * + * Return 0 for success, else error. + */ +int snd_soc_card_add_controls(struct snd_soc_card *soc_card, + const struct snd_kcontrol_new *controls, int num_controls) +{ + struct snd_card *card = soc_card->snd_card; + + return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, NULL, soc_card); +} +EXPORT_SYMBOL_GPL(snd_soc_card_add_controls); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 1c3ed2975cac76..de6d9746aac6ef 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2045,26 +2045,6 @@ int snd_soc_add_controls(struct snd_card *card, struct device *dev, return 0; } -/** - * snd_soc_add_card_controls - add an array of controls to a SoC card. - * Convenience function to add a list of controls. - * - * @soc_card: SoC card to add controls to - * @controls: array of controls to add - * @num_controls: number of elements in the array - * - * Return 0 for success, else error. - */ -int snd_soc_add_card_controls(struct snd_soc_card *soc_card, - const struct snd_kcontrol_new *controls, int num_controls) -{ - struct snd_card *card = soc_card->snd_card; - - return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, - NULL, soc_card); -} -EXPORT_SYMBOL_GPL(snd_soc_add_card_controls); - /** * snd_soc_register_card - Register a card with the ASoC core * From fca7ed8ae2a9a6450b54d87f007415a5041e709d Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:19 +0000 Subject: [PATCH 151/186] ASoC: soc-card: move snd_soc_card_debugfs_xxx() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - soc_init_card_debugfs() - soc_cleanup_card_debugfs() + snd_soc_card_debugfs_init() + snd_soc_card_debugfs_cleanup() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/877bj2ttl4.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 20 ++++++++++++++++++++ sound/soc/soc-core.c | 20 ++------------------ sound/soc/soc-internal.h | 7 +++++++ 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 7dafe22d8a7896..58aa75163045de 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -6,6 +6,7 @@ // Kuninori Morimoto // +#include #include #include #include @@ -20,6 +21,25 @@ static inline int _soc_card_ret(struct snd_soc_card *card, "at %s() on %s\n", func, card->name); } +#ifdef CONFIG_DEBUG_FS +void snd_soc_card_debugfs_init(struct snd_soc_card *card) +{ + card->debugfs_card_root = debugfs_create_dir(card->name, + snd_soc_debugfs_root); + + snd_soc_dapm_debugfs_init(snd_soc_card_to_dapm(card), card->debugfs_card_root); +} + +void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) +{ + debugfs_remove_recursive(card->debugfs_card_root); + card->debugfs_card_root = NULL; +} +#else +void snd_soc_card_debugfs_init(struct snd_soc_card *card) { } +void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) { } +#endif /* CONFIG_DEBUG_FS */ + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index de6d9746aac6ef..91e2eea30cb343 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -154,20 +154,6 @@ static int component_list_show(struct seq_file *m, void *v) } DEFINE_SHOW_ATTRIBUTE(component_list); -static void soc_init_card_debugfs(struct snd_soc_card *card) -{ - card->debugfs_card_root = debugfs_create_dir(card->name, - snd_soc_debugfs_root); - - snd_soc_dapm_debugfs_init(snd_soc_card_to_dapm(card), card->debugfs_card_root); -} - -static void soc_cleanup_card_debugfs(struct snd_soc_card *card) -{ - debugfs_remove_recursive(card->debugfs_card_root); - card->debugfs_card_root = NULL; -} - static void snd_soc_debugfs_init(void) { snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); @@ -188,8 +174,6 @@ static void snd_soc_debugfs_exit(void) #else -static inline void soc_init_card_debugfs(struct snd_soc_card *card) { } -static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) { } static inline void snd_soc_debugfs_init(void) { } static inline void snd_soc_debugfs_exit(void) { } @@ -1651,7 +1635,7 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card) soc_unbind_aux_dev(card); snd_soc_dapm_free(snd_soc_card_to_dapm(card)); - soc_cleanup_card_debugfs(card); + snd_soc_card_debugfs_cleanup(card); /* remove the card */ snd_soc_card_remove(card); @@ -1722,7 +1706,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) goto probe_end; } - soc_init_card_debugfs(card); + snd_soc_card_debugfs_init(card); soc_resume_init(card); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index b844b27574c8aa..550fc1c7ef05fb 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -13,6 +13,13 @@ * as snd_soc_xxx(); */ +/* + * REMOVE ME + * Temporary definition + */ +void snd_soc_card_debugfs_init(struct snd_soc_card *card); +void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); + /* * In soc-core */ From d19bd1bc5af414a73c53b82a0878e38a8d908f02 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:24 +0000 Subject: [PATCH 152/186] ASoC: soc-card: move snd_soc_card_resume_init() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - void soc_resume_deferred - void soc_card_resume_init() + void snd_soc_card_resume_deferred + void snd_soc_card_resume_init() Below functions are used on above functions. Add them at soc-internal.h - void soc_playback_digital_mute() - void soc_dapm_suspend_resume() + void snd_soc_playback_digital_mute() + void snd_soc_dapm_suspend_resume() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/875wymttl0.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 55 ++++++++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 61 ++++------------------------------------ sound/soc/soc-internal.h | 5 ++++ 3 files changed, 65 insertions(+), 56 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 58aa75163045de..bccd47cc136e61 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -40,6 +40,61 @@ void snd_soc_card_debugfs_init(struct snd_soc_card *card) { } void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) { } #endif /* CONFIG_DEBUG_FS */ +#ifdef CONFIG_PM_SLEEP +/* + * deferred resume work, so resume can complete before we finished + * setting our codec back up, which can be very slow on I2C + */ +static void snd_soc_card_resume_deferred(struct work_struct *work) +{ + struct snd_soc_card *card = + container_of(work, struct snd_soc_card, + deferred_resume_work); + struct snd_soc_component *component; + + /* + * our power state is still SNDRV_CTL_POWER_D3hot from suspend time, + * so userspace apps are blocked from touching us + */ + + dev_dbg(card->dev, "ASoC: starting resume work\n"); + + /* Bring us up into D2 so that DAPM starts enabling things */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2); + + snd_soc_card_resume_pre(card); + + for_each_card_components(card, component) { + if (snd_soc_component_is_suspended(component)) + snd_soc_component_resume(component); + } + + snd_soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_RESUME); + + /* unmute any active DACs */ + snd_soc_playback_digital_mute(card, 0); + + snd_soc_card_resume_post(card); + + dev_dbg(card->dev, "ASoC: resume work completed\n"); + + /* Recheck all endpoints too, their state is affected by suspend */ + snd_soc_dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(snd_soc_card_to_dapm(card)); + + /* userspace can access us now we are back as we were before */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); +} + +void snd_soc_card_resume_init(struct snd_soc_card *card) +{ + /* deferred resume work */ + INIT_WORK(&card->deferred_resume_work, snd_soc_card_resume_deferred); +} +#else +void snd_soc_card_resume_init(struct snd_soc_card *card) { } +#endif /* CONFIG_PM_SLEEP */ + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 91e2eea30cb343..901fddbbe25e8b 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -457,7 +457,7 @@ static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) } #ifdef CONFIG_PM_SLEEP -static void soc_playback_digital_mute(struct snd_soc_card *card, int mute) +void snd_soc_playback_digital_mute(struct snd_soc_card *card, int mute) { struct snd_soc_pcm_runtime *rtd; struct snd_soc_dai *dai; @@ -476,7 +476,7 @@ static void soc_playback_digital_mute(struct snd_soc_card *card, int mute) } } -static void soc_dapm_suspend_resume(struct snd_soc_card *card, int event) +void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event) { struct snd_soc_pcm_runtime *rtd; int stream; @@ -513,7 +513,7 @@ int snd_soc_suspend(struct device *dev) snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot); /* mute any active DACs */ - soc_playback_digital_mute(card, 1); + snd_soc_playback_digital_mute(card, 1); /* suspend all pcms */ for_each_card_rtds(card, rtd) { @@ -528,7 +528,7 @@ int snd_soc_suspend(struct device *dev) /* close any waiting streams */ snd_soc_flush_all_delayed_work(card); - soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_SUSPEND); + snd_soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_SUSPEND); /* Recheck all endpoints too, their state is affected by suspend */ snd_soc_dapm_mark_endpoints_dirty(card); @@ -589,51 +589,6 @@ int snd_soc_suspend(struct device *dev) } EXPORT_SYMBOL_GPL(snd_soc_suspend); -/* - * deferred resume work, so resume can complete before we finished - * setting our codec back up, which can be very slow on I2C - */ -static void soc_resume_deferred(struct work_struct *work) -{ - struct snd_soc_card *card = - container_of(work, struct snd_soc_card, - deferred_resume_work); - struct snd_soc_component *component; - - /* - * our power state is still SNDRV_CTL_POWER_D3hot from suspend time, - * so userspace apps are blocked from touching us - */ - - dev_dbg(card->dev, "ASoC: starting resume work\n"); - - /* Bring us up into D2 so that DAPM starts enabling things */ - snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2); - - snd_soc_card_resume_pre(card); - - for_each_card_components(card, component) { - if (snd_soc_component_is_suspended(component)) - snd_soc_component_resume(component); - } - - soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_RESUME); - - /* unmute any active DACs */ - soc_playback_digital_mute(card, 0); - - snd_soc_card_resume_post(card); - - dev_dbg(card->dev, "ASoC: resume work completed\n"); - - /* Recheck all endpoints too, their state is affected by suspend */ - snd_soc_dapm_mark_endpoints_dirty(card); - snd_soc_dapm_sync(snd_soc_card_to_dapm(card)); - - /* userspace can access us now we are back as we were before */ - snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); -} - /* powers up audio subsystem after a suspend */ int snd_soc_resume(struct device *dev) { @@ -657,15 +612,9 @@ int snd_soc_resume(struct device *dev) } EXPORT_SYMBOL_GPL(snd_soc_resume); -static void soc_resume_init(struct snd_soc_card *card) -{ - /* deferred resume work */ - INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); -} #else #define snd_soc_suspend NULL #define snd_soc_resume NULL -static inline void soc_resume_init(struct snd_soc_card *card) { } #endif struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, @@ -1708,7 +1657,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) snd_soc_card_debugfs_init(card); - soc_resume_init(card); + snd_soc_card_resume_init(card); ret = snd_soc_dapm_new_controls(dapm, card->dapm_widgets, card->num_dapm_widgets); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 550fc1c7ef05fb..3d3703e58711d6 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -19,6 +19,7 @@ */ void snd_soc_card_debugfs_init(struct snd_soc_card *card); void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); +void snd_soc_card_resume_init(struct snd_soc_card *card); /* * In soc-core @@ -30,6 +31,10 @@ char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *d int snd_soc_add_controls(struct snd_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix, void *data); +#ifdef CONFIG_PM_SLEEP +void snd_soc_playback_digital_mute(struct snd_soc_card *card, int mute); +void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event); +#endif /* * In soc-dai From bffafc899cc18faa5937f139fbbc4537c58ac338 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:27 +0000 Subject: [PATCH 153/186] ASoC: soc-card: move snd_soc_card_fill_dummy_dai() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - snd_soc_fill_dummy_dai() + snd_soc_card_fill_dummy_dai() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/874ie6ttkw.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 22 ++++++++++++++++++++++ sound/soc/soc-core.c | 24 +----------------------- sound/soc/soc-internal.h | 1 + 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index bccd47cc136e61..6e2b8a86f4587a 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -95,6 +95,28 @@ void snd_soc_card_resume_init(struct snd_soc_card *card) void snd_soc_card_resume_init(struct snd_soc_card *card) { } #endif /* CONFIG_PM_SLEEP */ +void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link; + int i; + + /* + * COMP_DUMMY() creates size 0 array on dai_link. + * Fill it as dummy DAI in case of CPU/Codec here. + * Do nothing for Platform. + */ + for_each_card_prelinks(card, i, dai_link) { + if (dai_link->num_cpus == 0 && dai_link->cpus) { + dai_link->num_cpus = 1; + dai_link->cpus = &snd_soc_dummy_dlc; + } + if (dai_link->num_codecs == 0 && dai_link->codecs) { + dai_link->num_codecs = 1; + dai_link->codecs = &snd_soc_dummy_dlc; + } + } +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 901fddbbe25e8b..9a0e058f34271a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -426,28 +426,6 @@ static struct snd_soc_pcm_runtime *soc_new_pcm_runtime( return NULL; } -static void snd_soc_fill_dummy_dai(struct snd_soc_card *card) -{ - struct snd_soc_dai_link *dai_link; - int i; - - /* - * COMP_DUMMY() creates size 0 array on dai_link. - * Fill it as dummy DAI in case of CPU/Codec here. - * Do nothing for Platform. - */ - for_each_card_prelinks(card, i, dai_link) { - if (dai_link->num_cpus == 0 && dai_link->cpus) { - dai_link->num_cpus = 1; - dai_link->cpus = &snd_soc_dummy_dlc; - } - if (dai_link->num_codecs == 0 && dai_link->codecs) { - dai_link->num_codecs = 1; - dai_link->codecs = &snd_soc_dummy_dlc; - } - } -} - static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -1626,7 +1604,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) int ret; snd_soc_card_mutex_lock_root(card); - snd_soc_fill_dummy_dai(card); + snd_soc_card_fill_dummy_dai(card); snd_soc_dapm_init(dapm, card, NULL); list_del_init(&card->list); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 3d3703e58711d6..1ec7aec68cb9c8 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -20,6 +20,7 @@ void snd_soc_card_debugfs_init(struct snd_soc_card *card); void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); void snd_soc_card_resume_init(struct snd_soc_card *card); +void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card); /* * In soc-core From 82126f901f86cdbc03e06ae6bb514b81cd5e6196 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:30 +0000 Subject: [PATCH 154/186] ASoC: soc-card: move snd_soc_card_init_pcm_runtime() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - soc_init_pcm_runtime() + snd_soc_card_init_pcm_runtime() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/8733tqttkt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 49 +++++++++++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 50 +--------------------------------------- sound/soc/soc-internal.h | 2 ++ 3 files changed, 52 insertions(+), 49 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 6e2b8a86f4587a..405d88e57fcbc6 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "soc-internal.h" @@ -117,6 +118,54 @@ void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card) } } +int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai_link *dai_link = rtd->dai_link; + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); + int ret; + + /* do machine specific initialization */ + ret = snd_soc_link_init(rtd); + if (ret < 0) + return ret; + + ret = snd_soc_runtime_set_dai_fmt(rtd, snd_soc_dai_auto_select_format(rtd)); + if (ret) + goto err; + + /* add DPCM sysfs entries */ + soc_dpcm_debugfs_add(rtd); + + /* create compress_device if driver is supporting */ + ret = snd_soc_dai_compress_new(cpu_dai, rtd); + if (ret != -ENOTSUPP) { /* driver is supporting */ + if (ret < 0) + goto err; + goto end; + } + + /* create the pcm */ + ret = soc_new_pcm(rtd); + if (ret < 0) { + dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", + dai_link->stream_name, ret); + goto err; + } + + ret = snd_soc_pcm_dai_new(rtd); + if (ret < 0) + goto err; +end: + rtd->initialized = true; + + return 0; +err: + snd_soc_link_exit(rtd); + + return ret; +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 9a0e058f34271a..c1d64892f3876d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1076,54 +1076,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static int soc_init_pcm_runtime(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd) -{ - struct snd_soc_dai_link *dai_link = rtd->dai_link; - struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); - int ret; - - /* do machine specific initialization */ - ret = snd_soc_link_init(rtd); - if (ret < 0) - return ret; - - ret = snd_soc_runtime_set_dai_fmt(rtd, snd_soc_dai_auto_select_format(rtd)); - if (ret) - goto err; - - /* add DPCM sysfs entries */ - soc_dpcm_debugfs_add(rtd); - - /* create compress_device if driver is supporting */ - ret = snd_soc_dai_compress_new(cpu_dai, rtd); - if (ret != -ENOTSUPP) { /* driver is supporting */ - if (ret < 0) - goto err; - goto end; - } - - /* create the pcm */ - ret = soc_new_pcm(rtd); - if (ret < 0) { - dev_err(card->dev, "ASoC: can't create pcm %s :%d\n", - dai_link->stream_name, ret); - goto err; - } - - ret = snd_soc_pcm_dai_new(rtd); - if (ret < 0) - goto err; -end: - rtd->initialized = true; - - return 0; -err: - snd_soc_link_exit(rtd); - - return ret; -} - static void soc_remove_link_dais(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -1679,7 +1631,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) } for_each_card_rtds(card, rtd) { - ret = soc_init_pcm_runtime(card, rtd); + ret = snd_soc_card_init_pcm_runtime(card, rtd); if (ret < 0) goto probe_end; } diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 1ec7aec68cb9c8..2b4beca5f9e5b5 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -21,6 +21,8 @@ void snd_soc_card_debugfs_init(struct snd_soc_card *card); void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); void snd_soc_card_resume_init(struct snd_soc_card *card); void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card); +int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd); /* * In soc-core From 21db4448e39577b94202432b43fb7a108f972706 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:34 +0000 Subject: [PATCH 155/186] ASoC: soc-card: move snd_soc_card_link_dais_xxx() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - soc_remove_link_dais() - soc_probe_link_dais() + snd_soc_card_link_dais_remove() + snd_soc_card_link_dais_probe() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/871p9attkq.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 30 ++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 34 ++-------------------------------- sound/soc/soc-internal.h | 2 ++ 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 405d88e57fcbc6..1d16cd74eee903 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -166,6 +166,36 @@ int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, return ret; } +void snd_soc_card_link_dais_remove(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + int order; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + /* remove all rtd connected DAIs in good order */ + snd_soc_pcm_dai_remove(rtd, order); + } + } +} + +int snd_soc_card_link_dais_probe(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + int order, ret; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + /* probe all rtd connected DAIs in good order */ + ret = snd_soc_pcm_dai_probe(rtd, order); + if (ret) + return ret; + } + } + + return 0; +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c1d64892f3876d..e3664cbe670590 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1076,36 +1076,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static void soc_remove_link_dais(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - int order; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - /* remove all rtd connected DAIs in good order */ - snd_soc_pcm_dai_remove(rtd, order); - } - } -} - -static int soc_probe_link_dais(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - int order, ret; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - /* probe all rtd connected DAIs in good order */ - ret = snd_soc_pcm_dai_probe(rtd, order); - if (ret) - return ret; - } - } - - return 0; -} - static void soc_remove_link_components(struct snd_soc_card *card) { struct snd_soc_component *component; @@ -1503,7 +1473,7 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card) snd_soc_flush_all_delayed_work(card); /* remove and free each DAI */ - soc_remove_link_dais(card); + snd_soc_card_link_dais_remove(card); soc_remove_link_components(card); for_each_card_rtds_safe(card, rtd, n) @@ -1623,7 +1593,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) } /* probe all DAI links on this card */ - ret = soc_probe_link_dais(card); + ret = snd_soc_card_link_dais_probe(card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to instantiate card %d\n", ret); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 2b4beca5f9e5b5..e95b34a7869b48 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -23,6 +23,8 @@ void snd_soc_card_resume_init(struct snd_soc_card *card); void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card); int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd); +void snd_soc_card_link_dais_remove(struct snd_soc_card *card); +int snd_soc_card_link_dais_probe(struct snd_soc_card *card); /* * In soc-core From 2d459fc0eddeb184c77f34af03c220cf5e78f23a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:37 +0000 Subject: [PATCH 156/186] ASoC: soc-card: move snd_soc_card_link_components_xxx() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - soc_remove_link_components() - soc_probe_link_components() + snd_soc_card_link_components_remove() + snd_soc_card_link_components_probe() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87zevysf07.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 40 ++++++++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 44 ++-------------------------------------- sound/soc/soc-internal.h | 2 ++ 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 1d16cd74eee903..65217ac4f699fd 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -196,6 +196,46 @@ int snd_soc_card_link_dais_probe(struct snd_soc_card *card) return 0; } +void snd_soc_card_link_components_remove(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i, order; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + for_each_rtd_components(rtd, i, component) { + if (component->driver->remove_order != order) + continue; + + snd_soc_component_remove(component, 1); + } + } + } +} + +int snd_soc_card_link_components_probe(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_pcm_runtime *rtd; + int i, ret, order; + + for_each_comp_order(order) { + for_each_card_rtds(card, rtd) { + for_each_rtd_components(rtd, i, component) { + if (component->driver->probe_order != order) + continue; + + ret = snd_soc_component_probe(component, card); + if (ret < 0) + return ret; + } + } + } + + return 0; +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e3664cbe670590..e1779b2b91476c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1076,46 +1076,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static void soc_remove_link_components(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_pcm_runtime *rtd; - int i, order; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - for_each_rtd_components(rtd, i, component) { - if (component->driver->remove_order != order) - continue; - - snd_soc_component_remove(component, 1); - } - } - } -} - -static int soc_probe_link_components(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_pcm_runtime *rtd; - int i, ret, order; - - for_each_comp_order(order) { - for_each_card_rtds(card, rtd) { - for_each_rtd_components(rtd, i, component) { - if (component->driver->probe_order != order) - continue; - - ret = snd_soc_component_probe(component, card); - if (ret < 0) - return ret; - } - } - } - - return 0; -} - static void soc_unbind_aux_dev(struct snd_soc_card *card) { struct snd_soc_component *component, *_component; @@ -1474,7 +1434,7 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card) /* remove and free each DAI */ snd_soc_card_link_dais_remove(card); - soc_remove_link_components(card); + snd_soc_card_link_components_remove(card); for_each_card_rtds_safe(card, rtd, n) snd_soc_remove_pcm_runtime(card, rtd); @@ -1575,7 +1535,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) goto probe_end; /* probe all components used by DAI links on this card */ - ret = soc_probe_link_components(card); + ret = snd_soc_card_link_components_probe(card); if (ret < 0) { if (ret != -EPROBE_DEFER) { dev_err(card->dev, diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index e95b34a7869b48..c441268fb848d9 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -25,6 +25,8 @@ int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, struct snd_soc_pcm_runtime *rtd); void snd_soc_card_link_dais_remove(struct snd_soc_card *card); int snd_soc_card_link_dais_probe(struct snd_soc_card *card); +void snd_soc_card_link_components_remove(struct snd_soc_card *card); +int snd_soc_card_link_components_probe(struct snd_soc_card *card); /* * In soc-core From 73f44c64b093c78d318c71c8d4939e71d223643f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:40 +0000 Subject: [PATCH 157/186] ASoC: soc-card: move snd_soc_card_aux_[un]bind() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - soc_unbind_aux_dev() - soc_bind_aux_dev() + snd_soc_card_aux_unbind() + snd_soc_card_aux_bind() Below function is used on above functions. Add them at soc-internal.h Align with existing snd_soc_find_dai() - soc_find_component() + snd_soc_find_component() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87y0bisf03.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 31 +++++++++++++++++++++++++++ sound/soc/soc-core.c | 46 ++++++---------------------------------- sound/soc/soc-internal.h | 3 +++ 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 65217ac4f699fd..dcfecec4865709 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -236,6 +236,37 @@ int snd_soc_card_link_components_probe(struct snd_soc_card *card) return 0; } +void snd_soc_card_aux_unbind(struct snd_soc_card *card) +{ + struct snd_soc_component *component, *_component; + + for_each_card_auxs_safe(card, component, _component) { + /* for snd_soc_component_init() */ + snd_soc_component_set_aux(component, NULL); + list_del(&component->card_aux_list); + } +} + +int snd_soc_card_aux_bind(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_aux_dev *aux; + int i; + + for_each_card_pre_auxs(card, i, aux) { + /* codecs, usually analog devices */ + component = snd_soc_find_component(&aux->dlc); + if (!component) + return -EPROBE_DEFER; + + /* for snd_soc_component_init() */ + snd_soc_component_set_aux(component, aux); + /* see for_each_card_auxs */ + list_add(&component->card_aux_list, &card->aux_comp_list); + } + return 0; +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e1779b2b91476c..0e257006886962 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -609,8 +609,7 @@ struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, } EXPORT_SYMBOL_GPL(snd_soc_copy_dai_args); -static struct snd_soc_component *soc_find_component( - const struct snd_soc_dai_link_component *dlc) +struct snd_soc_component *snd_soc_find_component(const struct snd_soc_dai_link_component *dlc) { struct snd_soc_component *component; @@ -696,7 +695,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, * Defer card registration if codec component is not added to * component list. */ - if (!soc_find_component(dlc)) + if (!snd_soc_find_component(dlc)) goto component_not_found; } @@ -717,7 +716,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, * Defer card registration if platform component is not added to * component list. */ - if (!soc_find_component(dlc)) + if (!snd_soc_find_component(dlc)) goto component_not_found; } @@ -742,7 +741,7 @@ static int soc_dai_link_sanity_check(struct snd_soc_card *card, /* * Defer card registration if Component is not added */ - if (!soc_find_component(dlc)) + if (!snd_soc_find_component(dlc)) goto component_not_found; } } @@ -1076,37 +1075,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static void soc_unbind_aux_dev(struct snd_soc_card *card) -{ - struct snd_soc_component *component, *_component; - - for_each_card_auxs_safe(card, component, _component) { - /* for snd_soc_component_init() */ - snd_soc_component_set_aux(component, NULL); - list_del(&component->card_aux_list); - } -} - -static int soc_bind_aux_dev(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_aux_dev *aux; - int i; - - for_each_card_pre_auxs(card, i, aux) { - /* codecs, usually analog devices */ - component = soc_find_component(&aux->dlc); - if (!component) - return -EPROBE_DEFER; - - /* for snd_soc_component_init() */ - snd_soc_component_set_aux(component, aux); - /* see for_each_card_auxs */ - list_add(&component->card_aux_list, &card->aux_comp_list); - } - return 0; -} - static int soc_probe_aux_devices(struct snd_soc_card *card) { struct snd_soc_component *component; @@ -1441,7 +1409,7 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card) /* remove auxiliary devices */ soc_remove_aux_devices(card); - soc_unbind_aux_dev(card); + snd_soc_card_aux_unbind(card); snd_soc_dapm_free(snd_soc_card_to_dapm(card)); snd_soc_card_debugfs_cleanup(card); @@ -1495,7 +1463,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) soc_check_tplg_fes(card); /* bind aux_devs too */ - ret = soc_bind_aux_dev(card); + ret = snd_soc_card_aux_bind(card); if (ret < 0) goto probe_end; @@ -2795,7 +2763,7 @@ int snd_soc_get_dai_id(struct device_node *ep) ret = -ENOTSUPP; scoped_guard(mutex, &client_mutex) { - struct snd_soc_component *component = soc_find_component(&dlc); + struct snd_soc_component *component = snd_soc_find_component(&dlc); if (component) ret = snd_soc_component_of_xlate_dai_id(component, ep); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index c441268fb848d9..df3c3cb783fded 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -27,6 +27,8 @@ void snd_soc_card_link_dais_remove(struct snd_soc_card *card); int snd_soc_card_link_dais_probe(struct snd_soc_card *card); void snd_soc_card_link_components_remove(struct snd_soc_card *card); int snd_soc_card_link_components_probe(struct snd_soc_card *card); +void snd_soc_card_aux_unbind(struct snd_soc_card *card); +int snd_soc_card_aux_bind(struct snd_soc_card *card); /* * In soc-core @@ -38,6 +40,7 @@ char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *d int snd_soc_add_controls(struct snd_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix, void *data); +struct snd_soc_component *snd_soc_find_component(const struct snd_soc_dai_link_component *dlc); #ifdef CONFIG_PM_SLEEP void snd_soc_playback_digital_mute(struct snd_soc_card *card, int mute); void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event); From c453991fe8efcdc1dc96e5d9b0e79722fe01c9b3 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:43 +0000 Subject: [PATCH 158/186] ASoC: soc-card: move snd_soc_card_aux_{remove/probe}() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - soc_remove_aux_devices() - soc_probe_aux_devices() + snd_soc_card_aux_remove() + snd_soc_card_aux_probe() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87wlr2sf00.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 33 +++++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 37 ++----------------------------------- sound/soc/soc-internal.h | 2 ++ 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index dcfecec4865709..432882471537cc 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -267,6 +267,39 @@ int snd_soc_card_aux_bind(struct snd_soc_card *card) return 0; } +int snd_soc_card_aux_probe(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + int order; + int ret; + + for_each_comp_order(order) { + for_each_card_auxs(card, component) { + if (component->driver->probe_order != order) + continue; + + ret = snd_soc_component_probe(component, card); + if (ret < 0) + return ret; + } + } + + return 0; +} + +void snd_soc_card_aux_remove(struct snd_soc_card *card) +{ + struct snd_soc_component *comp, *_comp; + int order; + + for_each_comp_order(order) { + for_each_card_auxs_safe(card, comp, _comp) { + if (comp->driver->remove_order == order) + snd_soc_component_remove(comp, 1); + } + } +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 0e257006886962..2b47186640a1b4 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1075,39 +1075,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static int soc_probe_aux_devices(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - int order; - int ret; - - for_each_comp_order(order) { - for_each_card_auxs(card, component) { - if (component->driver->probe_order != order) - continue; - - ret = snd_soc_component_probe(component, card); - if (ret < 0) - return ret; - } - } - - return 0; -} - -static void soc_remove_aux_devices(struct snd_soc_card *card) -{ - struct snd_soc_component *comp, *_comp; - int order; - - for_each_comp_order(order) { - for_each_card_auxs_safe(card, comp, _comp) { - if (comp->driver->remove_order == order) - snd_soc_component_remove(comp, 1); - } - } -} - #ifdef CONFIG_DMI /* * If a DMI filed contain strings in this blacklist (e.g. @@ -1408,7 +1375,7 @@ static void soc_cleanup_card_resources(struct snd_soc_card *card) snd_soc_remove_pcm_runtime(card, rtd); /* remove auxiliary devices */ - soc_remove_aux_devices(card); + snd_soc_card_aux_remove(card); snd_soc_card_aux_unbind(card); snd_soc_dapm_free(snd_soc_card_to_dapm(card)); @@ -1513,7 +1480,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) } /* probe auxiliary components */ - ret = soc_probe_aux_devices(card); + ret = snd_soc_card_aux_probe(card); if (ret < 0) { dev_err(card->dev, "ASoC: failed to probe aux component %d\n", ret); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index df3c3cb783fded..ffe055e1b2de26 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -29,6 +29,8 @@ void snd_soc_card_link_components_remove(struct snd_soc_card *card); int snd_soc_card_link_components_probe(struct snd_soc_card *card); void snd_soc_card_aux_unbind(struct snd_soc_card *card); int snd_soc_card_aux_bind(struct snd_soc_card *card); +int snd_soc_card_aux_probe(struct snd_soc_card *card); +void snd_soc_card_aux_remove(struct snd_soc_card *card); /* * In soc-core From 2acf786bf6dcb2789727b941305b04eddf1a149f Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:47 +0000 Subject: [PATCH 159/186] ASoC: soc-card: move snd_soc_card_set_dmi_name() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - snd_soc_set_dmi_name() + snd_soc_card_set_dmi_name() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87v76msezx.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 165 ++++++++++++++++++++++++++++++++++++++ sound/soc/soc-core.c | 167 +-------------------------------------- sound/soc/soc-internal.h | 1 + 3 files changed, 167 insertions(+), 166 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 432882471537cc..c64c9d425bcf93 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -7,6 +7,7 @@ // #include +#include #include #include #include @@ -300,6 +301,170 @@ void snd_soc_card_aux_remove(struct snd_soc_card *card) } } +#ifdef CONFIG_DMI +/* + * If a DMI filed contain strings in this blacklist (e.g. + * "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken + * as invalid and dropped when setting the card long name from DMI info. + */ +static const char * const dmi_blacklist[] = { + "To be filled by OEM", + "TBD by OEM", + "Default String", + "Board Manufacturer", + "Board Vendor Name", + "Board Product Name", + NULL, /* terminator */ +}; + +/* + * Trim special characters, and replace '-' with '_' since '-' is used to + * separate different DMI fields in the card long name. Only number and + * alphabet characters and a few separator characters are kept. + */ +static void cleanup_dmi_name(char *name) +{ + int i, j = 0; + + for (i = 0; name[i]; i++) { + if (isalnum(name[i]) || (name[i] == '.') + || (name[i] == '_')) + name[j++] = name[i]; + else if (name[i] == '-') + name[j++] = '_'; + } + + name[j] = '\0'; +} + +/* + * Check if a DMI field is valid, i.e. not containing any string + * in the black list and not the empty string. + */ +static int is_dmi_valid(const char *field) +{ + int i = 0; + + if (!field[0]) + return 0; + + while (dmi_blacklist[i]) { + if (strstr(field, dmi_blacklist[i])) + return 0; + i++; + } + + return 1; +} + +/* + * Append a string to dmi_longname with character cleanups. + */ +#define DMI_LONGNAME_LEN 80 +static void append_dmi_string(char *dst, const char *str) +{ + size_t dst_len = DMI_LONGNAME_LEN; + size_t len; + + len = strlen(dst); + snprintf(dst + len, dst_len - len, "-%s", str); + + len++; /* skip the separator "-" */ + if (len < dst_len) + cleanup_dmi_name(dst + len); +} + +/** + * snd_soc_card_set_dmi_name() - Register DMI names to card + * @card: The card to register DMI names + * + * An Intel machine driver may be used by many different devices but are + * difficult for userspace to differentiate, since machine drivers usually + * use their own name as the card short name and leave the card long name + * blank. To differentiate such devices and fix bugs due to lack of + * device-specific configurations, this function allows DMI info to be used + * as the sound card long name, in the format of + * "vendor-product-version-board" + * (Character '-' is used to separate different DMI fields here). + * This will help the user space to load the device-specific Use Case Manager + * (UCM) configurations for the card. + * + * Possible card long names may be: + * DellInc.-XPS139343-01-0310JH + * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA + * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX + * + * This function also supports flavoring the card longname to provide + * the extra differentiation, like "vendor-product-version-board-flavor". + * + * We only keep number and alphabet characters and a few separator characters + * in the card long name since UCM in the user space uses the card long names + * as card configuration directory names and AudoConf cannot support special + * characters like SPACE. + * + * Returns 0 on success, otherwise a negative error code. + */ +int snd_soc_card_set_dmi_name(struct snd_soc_card *card) +{ + const char *vendor, *product, *board; + char *dmi_longname; + + if (card->long_name) + return 0; /* long name already set by driver or from DMI */ + + if (!dmi_available) + return 0; + + /* make up dmi long name as: vendor-product-version-board */ + vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + if (!vendor || !is_dmi_valid(vendor)) { + dev_warn(card->dev, "ASoC: no DMI vendor name!\n"); + return 0; + } + + dmi_longname = devm_kzalloc(card->dev, DMI_LONGNAME_LEN, GFP_KERNEL); + if (!dmi_longname) + return -ENOMEM; + + snprintf(dmi_longname, DMI_LONGNAME_LEN, "%s", vendor); + cleanup_dmi_name(dmi_longname); + + product = dmi_get_system_info(DMI_PRODUCT_NAME); + if (product && is_dmi_valid(product)) { + const char *product_version = dmi_get_system_info(DMI_PRODUCT_VERSION); + + append_dmi_string(dmi_longname, product); + + /* + * some vendors like Lenovo may only put a self-explanatory + * name in the product version field + */ + if (product_version && is_dmi_valid(product_version)) + append_dmi_string(dmi_longname, product_version); + } + + board = dmi_get_system_info(DMI_BOARD_NAME); + if (board && is_dmi_valid(board)) { + if (!product || strcasecmp(board, product)) + append_dmi_string(dmi_longname, board); + } else if (!product) { + /* fall back to using legacy name */ + dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); + return 0; + } + + /* set the card long name */ + card->long_name = dmi_longname; + + return 0; +} +#else +int snd_soc_card_set_dmi_name(struct snd_soc_card *card) +{ + return 0; +} +#endif /* CONFIG_DMI */ + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 2b47186640a1b4..12b8bcb93ceeb1 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -1075,170 +1074,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -#ifdef CONFIG_DMI -/* - * If a DMI filed contain strings in this blacklist (e.g. - * "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken - * as invalid and dropped when setting the card long name from DMI info. - */ -static const char * const dmi_blacklist[] = { - "To be filled by OEM", - "TBD by OEM", - "Default String", - "Board Manufacturer", - "Board Vendor Name", - "Board Product Name", - NULL, /* terminator */ -}; - -/* - * Trim special characters, and replace '-' with '_' since '-' is used to - * separate different DMI fields in the card long name. Only number and - * alphabet characters and a few separator characters are kept. - */ -static void cleanup_dmi_name(char *name) -{ - int i, j = 0; - - for (i = 0; name[i]; i++) { - if (isalnum(name[i]) || (name[i] == '.') - || (name[i] == '_')) - name[j++] = name[i]; - else if (name[i] == '-') - name[j++] = '_'; - } - - name[j] = '\0'; -} - -/* - * Check if a DMI field is valid, i.e. not containing any string - * in the black list and not the empty string. - */ -static int is_dmi_valid(const char *field) -{ - int i = 0; - - if (!field[0]) - return 0; - - while (dmi_blacklist[i]) { - if (strstr(field, dmi_blacklist[i])) - return 0; - i++; - } - - return 1; -} - -/* - * Append a string to dmi_longname with character cleanups. - */ -#define DMI_LONGNAME_LEN 80 -static void append_dmi_string(char *dst, const char *str) -{ - size_t dst_len = DMI_LONGNAME_LEN; - size_t len; - - len = strlen(dst); - snprintf(dst + len, dst_len - len, "-%s", str); - - len++; /* skip the separator "-" */ - if (len < dst_len) - cleanup_dmi_name(dst + len); -} - -/** - * snd_soc_set_dmi_name() - Register DMI names to card - * @card: The card to register DMI names - * - * An Intel machine driver may be used by many different devices but are - * difficult for userspace to differentiate, since machine drivers usually - * use their own name as the card short name and leave the card long name - * blank. To differentiate such devices and fix bugs due to lack of - * device-specific configurations, this function allows DMI info to be used - * as the sound card long name, in the format of - * "vendor-product-version-board" - * (Character '-' is used to separate different DMI fields here). - * This will help the user space to load the device-specific Use Case Manager - * (UCM) configurations for the card. - * - * Possible card long names may be: - * DellInc.-XPS139343-01-0310JH - * ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA - * Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX - * - * This function also supports flavoring the card longname to provide - * the extra differentiation, like "vendor-product-version-board-flavor". - * - * We only keep number and alphabet characters and a few separator characters - * in the card long name since UCM in the user space uses the card long names - * as card configuration directory names and AudoConf cannot support special - * characters like SPACE. - * - * Returns 0 on success, otherwise a negative error code. - */ -static int snd_soc_set_dmi_name(struct snd_soc_card *card) -{ - const char *vendor, *product, *board; - char *dmi_longname; - - if (card->long_name) - return 0; /* long name already set by driver or from DMI */ - - if (!dmi_available) - return 0; - - /* make up dmi long name as: vendor-product-version-board */ - vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - if (!vendor || !is_dmi_valid(vendor)) { - dev_warn(card->dev, "ASoC: no DMI vendor name!\n"); - return 0; - } - - dmi_longname = devm_kzalloc(card->dev, DMI_LONGNAME_LEN, GFP_KERNEL); - if (!dmi_longname) - return -ENOMEM; - - snprintf(dmi_longname, DMI_LONGNAME_LEN, "%s", vendor); - cleanup_dmi_name(dmi_longname); - - product = dmi_get_system_info(DMI_PRODUCT_NAME); - if (product && is_dmi_valid(product)) { - const char *product_version = dmi_get_system_info(DMI_PRODUCT_VERSION); - - append_dmi_string(dmi_longname, product); - - /* - * some vendors like Lenovo may only put a self-explanatory - * name in the product version field - */ - if (product_version && is_dmi_valid(product_version)) - append_dmi_string(dmi_longname, product_version); - } - - board = dmi_get_system_info(DMI_BOARD_NAME); - if (board && is_dmi_valid(board)) { - if (!product || strcasecmp(board, product)) - append_dmi_string(dmi_longname, board); - } else if (!product) { - /* fall back to using legacy name */ - dev_warn(card->dev, "ASoC: no DMI board/product name!\n"); - return 0; - } - - /* set the card long name */ - card->long_name = dmi_longname; - - return 0; -} -#else -static inline int snd_soc_set_dmi_name(struct snd_soc_card *card) -{ - return 0; -} -#endif /* CONFIG_DMI */ - static void soc_check_tplg_fes(struct snd_soc_card *card) { struct snd_soc_component *component; @@ -1520,7 +1355,7 @@ static int snd_soc_bind_card(struct snd_soc_card *card) goto probe_end; /* try to set some sane longname if DMI is available */ - snd_soc_set_dmi_name(card); + snd_soc_card_set_dmi_name(card); soc_setup_card_name(card, card->snd_card->shortname, card->name, NULL); diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index ffe055e1b2de26..e1f456e222b63e 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -31,6 +31,7 @@ void snd_soc_card_aux_unbind(struct snd_soc_card *card); int snd_soc_card_aux_bind(struct snd_soc_card *card); int snd_soc_card_aux_probe(struct snd_soc_card *card); void snd_soc_card_aux_remove(struct snd_soc_card *card); +int snd_soc_card_set_dmi_name(struct snd_soc_card *card); /* * In soc-core From 956918f7f1c0704de78435a6babf09e77f1f3937 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:50 +0000 Subject: [PATCH 160/186] ASoC: soc-card: move snd_soc_card_bind_call() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. It has too many local function dependencies. This patch uses temporary funciton define at soc-internal.h. It will be removed all funciton were moved. It renames function name - call_soc_bind_card() - snd_soc_bind_card() - snd_soc_unbind_card() + snd_soc_card_bind_call() + snd_soc_card_bind() + snd_soc_card_unbind() Below function is used on above functions. Add them at soc-internal.h + snd_soc_flush_all_delayed_work() Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87tsm6sezt.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 411 +++++++++++++++++++++++++++++++++++++- sound/soc/soc-core.c | 419 +-------------------------------------- sound/soc/soc-internal.h | 8 + 3 files changed, 422 insertions(+), 416 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index c64c9d425bcf93..72b40f842bd436 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -9,12 +9,16 @@ #include #include #include +#include #include +#include #include #include #include #include "soc-internal.h" +LIST_HEAD(unbind_card_list); + #define soc_card_ret(dai, ret) _soc_card_ret(dai, __func__, ret) static inline int _soc_card_ret(struct snd_soc_card *card, const char *func, int ret) @@ -465,6 +469,157 @@ int snd_soc_card_set_dmi_name(struct snd_soc_card *card) } #endif /* CONFIG_DMI */ +static void soc_card_check_tplg_fes(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + struct snd_soc_dai_link *dai_link; + int i; + + for_each_component(component) { + + /* does this component override BEs ? */ + if (!component->driver->ignore_machine) + continue; + + /* for this machine ? */ + if (!strcmp(component->driver->ignore_machine, + card->dev->driver->name)) + goto match; + if (strcmp(component->driver->ignore_machine, + dev_name(card->dev))) + continue; +match: + /* machine matches, so override the rtd data */ + for_each_card_prelinks(card, i, dai_link) { + + /* ignore this FE */ + if (dai_link->dynamic) { + dai_link->ignore = true; + continue; + } + + dev_dbg(card->dev, "info: override BE DAI link %s\n", + dai_link->name); + + /* override platform component */ + if (!dai_link->platforms) { + dev_err(card->dev, "init platform error"); + continue; + } + + if (component->dev->of_node) + dai_link->platforms->of_node = component->dev->of_node; + else + dai_link->platforms->name = component->name; + + /* convert non BE into BE */ + dai_link->no_pcm = 1; + + /* + * override any BE fixups + * see + * snd_soc_link_be_hw_params_fixup() + */ + dai_link->be_hw_params_fixup = + component->driver->be_hw_params_fixup; + + /* + * most BE links don't set stream name, so set it to + * dai link name if it's NULL to help bind widgets. + */ + if (!dai_link->stream_name) + dai_link->stream_name = dai_link->name; + } + + /* Inform userspace we are using alternate topology */ + snd_soc_card_set_topology_name(card, component->driver->topology_name_prefix); + } +} + +#define soc_card_setup_name(card, name, name1, name2) \ + __soc_card_setup_name(card, name, sizeof(name), name1, name2) +static void __soc_card_setup_name(struct snd_soc_card *card, + char *name, int len, + const char *name1, const char *name2) +{ + const char *src = name1 ? name1 : name2; + int i; + + snprintf(name, len, "%s", src); + + if (name != card->snd_card->driver) + return; + + /* + * Name normalization (driver field) + * + * The driver name is somewhat special, as it's used as a key for + * searches in the user-space. + * + * ex) + * "abcd??efg" -> "abcd__efg" + */ + for (i = 0; i < len; i++) { + switch (name[i]) { + case '_': + case '-': + case '\0': + break; + default: + if (!isalnum(name[i])) + name[i] = '_'; + break; + } + } + + /* + * The driver field should contain a valid string from the user view. + * The wrapping usually does not work so well here. Set a smaller string + * in the specific ASoC driver. + */ + if (strlen(src) > len - 1) + dev_err(card->dev, "ASoC: driver name too long '%s' -> '%s'\n", src, name); +} + +static void snd_soc_card_cleanup_resources(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd, *n; + + if (card->snd_card) + snd_card_disconnect_sync(card->snd_card); + + snd_soc_dapm_shutdown(card); + + /* release machine specific resources */ + for_each_card_rtds(card, rtd) + if (rtd->initialized) + snd_soc_link_exit(rtd); + /* flush delayed work before removing DAIs and DAPM widgets */ + snd_soc_flush_all_delayed_work(card); + + /* remove and free each DAI */ + snd_soc_card_link_dais_remove(card); + snd_soc_card_link_components_remove(card); + + for_each_card_rtds_safe(card, rtd, n) + snd_soc_remove_pcm_runtime(card, rtd); + + /* remove auxiliary devices */ + snd_soc_card_aux_remove(card); + snd_soc_card_aux_unbind(card); + + snd_soc_dapm_free(snd_soc_card_to_dapm(card)); + snd_soc_card_debugfs_cleanup(card); + + /* remove the card */ + snd_soc_card_remove(card); + + if (card->snd_card) { + snd_card_free(card->snd_card); + card->snd_card = NULL; + } +} + struct snd_kcontrol *snd_soc_card_get_kcontrol(struct snd_soc_card *soc_card, const char *name) { @@ -597,7 +752,7 @@ int snd_soc_card_probe(struct snd_soc_card *card) * about "late_probe". * * see - * snd_soc_bind_card() + * snd_soc_card_bind() * snd_soc_card_late_probe() */ card->probed = 1; @@ -622,7 +777,7 @@ int snd_soc_card_late_probe(struct snd_soc_card *card) * for all cases. * * see - * snd_soc_bind_card() + * snd_soc_card_bind() * snd_soc_card_probe() */ card->probed = 1; @@ -724,3 +879,255 @@ int snd_soc_card_add_controls(struct snd_soc_card *soc_card, return snd_soc_add_controls(card, soc_card->dev, controls, num_controls, NULL, soc_card); } EXPORT_SYMBOL_GPL(snd_soc_card_add_controls); + +static void snd_soc_remove_device_links(struct snd_soc_card *card) +{ + struct snd_soc_component *component; + + for_each_card_components(card, component) { + if (component->card_device_link) { + device_link_del(component->card_device_link); + component->card_device_link = NULL; + } + } +} + +void snd_soc_card_unbind(struct snd_soc_card *card) +{ + if (snd_soc_card_is_instantiated(card)) { + card->instantiated = false; + + snd_soc_remove_device_links(card); + + snd_soc_card_cleanup_resources(card); + } +} + +static int snd_soc_card_bind(struct snd_soc_card *card) +{ + struct snd_soc_pcm_runtime *rtd; + struct snd_soc_component *component; + struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); + int ret; + + snd_soc_card_mutex_lock_root(card); + snd_soc_card_fill_dummy_dai(card); + + snd_soc_dapm_init(dapm, card, NULL); + list_del_init(&card->list); + + /* check whether any platform is ignore machine FE and using topology */ + soc_card_check_tplg_fes(card); + + /* bind aux_devs too */ + ret = snd_soc_card_aux_bind(card); + if (ret < 0) + goto probe_end; + + /* add predefined DAI links to the list */ + card->num_rtd = 0; + ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links); + if (ret < 0) + goto probe_end; + + /* card bind complete so register a sound card */ + ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + card->owner, 0, &card->snd_card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: can't create sound card for card %s: %d\n", + card->name, ret); + goto probe_end; + } + + snd_soc_card_debugfs_init(card); + + snd_soc_card_resume_init(card); + + ret = snd_soc_dapm_new_controls(dapm, card->dapm_widgets, + card->num_dapm_widgets); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_new_controls(dapm, card->of_dapm_widgets, + card->num_of_dapm_widgets); + if (ret < 0) + goto probe_end; + + /* initialise the sound card only once */ + ret = snd_soc_card_probe(card); + if (ret < 0) + goto probe_end; + + /* probe all components used by DAI links on this card */ + ret = snd_soc_card_link_components_probe(card); + if (ret < 0) { + if (ret != -EPROBE_DEFER) { + dev_err(card->dev, + "ASoC: failed to instantiate card %d\n", ret); + } + goto probe_end; + } + + /* probe auxiliary components */ + ret = snd_soc_card_aux_probe(card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to probe aux component %d\n", ret); + goto probe_end; + } + + /* probe all DAI links on this card */ + ret = snd_soc_card_link_dais_probe(card); + if (ret < 0) { + dev_err(card->dev, + "ASoC: failed to instantiate card %d\n", ret); + goto probe_end; + } + + for_each_card_rtds(card, rtd) { + ret = snd_soc_card_init_pcm_runtime(card, rtd); + if (ret < 0) + goto probe_end; + } + + snd_soc_dapm_link_dai_widgets(card); + snd_soc_dapm_connect_dai_link_widgets(card); + + ret = snd_soc_add_card_controls(card, card->controls, + card->num_controls); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_add_routes(dapm, card->dapm_routes, + card->num_dapm_routes); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_add_routes(dapm, card->of_dapm_routes, + card->num_of_dapm_routes); + if (ret < 0) + goto probe_end; + + /* try to set some sane longname if DMI is available */ + snd_soc_card_set_dmi_name(card); + + soc_card_setup_name(card, card->snd_card->shortname, card->name, NULL); + soc_card_setup_name(card, card->snd_card->longname, card->long_name, card->name); + soc_card_setup_name(card, card->snd_card->driver, card->driver_name, card->name); + + if (card->components) { + /* the current implementation of snd_component_add() accepts */ + /* multiple components in the string separated by space, */ + /* but the string collision (identical string) check might */ + /* not work correctly */ + ret = snd_component_add(card->snd_card, card->components); + if (ret < 0) { + dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n", + card->name, ret); + goto probe_end; + } + } + + /* + * Add device_link from card to component so that system_suspend + * will be done in the correct order. The card must suspend first + * to stop audio activity before the components suspend. + * + * If a driver pair already have a link in the opposite direction + * they must manage their own suspend order. + */ + for_each_card_components(card, component) { + if (card->dev == component->dev) + continue; + + component->card_device_link = device_link_add(card->dev, + component->dev, + DL_FLAG_STATELESS); + if (!component->card_device_link) { + dev_warn(card->dev, "Could not create device link to %s\n", + dev_name(component->dev)); + } + } + + ret = snd_soc_card_late_probe(card); + if (ret < 0) + goto probe_end; + + ret = snd_soc_dapm_ignore_suspend_widgets(card); + if (ret < 0) + goto probe_end; + + snd_soc_dapm_new_widgets(card); + for_each_card_components(card, component) { + ret = snd_soc_component_fixup_controls(component); + if (ret < 0) + goto probe_end; + } + snd_soc_card_fixup_controls(card); + + ret = snd_card_register(card->snd_card); + if (ret < 0) { + dev_err(card->dev, "ASoC: failed to register soundcard %d\n", + ret); + goto probe_end; + } + + card->instantiated = 1; + snd_soc_dapm_mark_endpoints_dirty(card); + snd_soc_dapm_sync(dapm); + + /* deactivate pins to sleep state */ + for_each_card_components(card, component) + if (!snd_soc_component_active(component)) + pinctrl_pm_select_sleep_state(component->dev); + +probe_end: + if (ret < 0) { + snd_soc_remove_device_links(card); + snd_soc_card_cleanup_resources(card); + } + + if (ret == -EPROBE_DEFER) { + list_add(&card->list, &unbind_card_list); + ret = 0; + } + snd_soc_card_mutex_unlock(card); + + return ret; +} + +static void devm_snd_soc_card_bind_release(struct device *dev, void *res) +{ + snd_soc_unregister_card(*(struct snd_soc_card **)res); +} + +static int devm_snd_soc_card_bind(struct device *dev, struct snd_soc_card *card) +{ + struct snd_soc_card **ptr; + int ret; + + /* The procedure may be called many times during the lifetime of the card. */ + devres_destroy(dev, devm_snd_soc_card_bind_release, NULL, NULL); + + ptr = devres_alloc(devm_snd_soc_card_bind_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_soc_card_bind(card); + if (ret == 0) { + *ptr = card; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} + +int snd_soc_card_bind_call(struct snd_soc_card *card) +{ + if (card->devres_dev) + return devm_snd_soc_card_bind(card->devres_dev, card); + return snd_soc_card_bind(card); +} diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 12b8bcb93ceeb1..301089259a8d41 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -38,15 +38,12 @@ #include #include #include -#include -#include #include "soc-internal.h" #define CREATE_TRACE_POINTS #include DEFINE_MUTEX(client_mutex); -static LIST_HEAD(unbind_card_list); /* * This is used if driver don't need to have CPU/Codec/Platform @@ -425,7 +422,7 @@ static struct snd_soc_pcm_runtime *soc_new_pcm_runtime( return NULL; } -static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) +void snd_soc_flush_all_delayed_work(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; @@ -1074,412 +1071,6 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd, } EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt); -static void soc_check_tplg_fes(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - struct snd_soc_dai_link *dai_link; - int i; - - for_each_component(component) { - - /* does this component override BEs ? */ - if (!component->driver->ignore_machine) - continue; - - /* for this machine ? */ - if (!strcmp(component->driver->ignore_machine, - card->dev->driver->name)) - goto match; - if (strcmp(component->driver->ignore_machine, - dev_name(card->dev))) - continue; -match: - /* machine matches, so override the rtd data */ - for_each_card_prelinks(card, i, dai_link) { - - /* ignore this FE */ - if (dai_link->dynamic) { - dai_link->ignore = true; - continue; - } - - dev_dbg(card->dev, "info: override BE DAI link %s\n", - dai_link->name); - - /* override platform component */ - if (!dai_link->platforms) { - dev_err(card->dev, "init platform error"); - continue; - } - - if (component->dev->of_node) - dai_link->platforms->of_node = component->dev->of_node; - else - dai_link->platforms->name = component->name; - - /* convert non BE into BE */ - dai_link->no_pcm = 1; - - /* - * override any BE fixups - * see - * snd_soc_link_be_hw_params_fixup() - */ - dai_link->be_hw_params_fixup = - component->driver->be_hw_params_fixup; - - /* - * most BE links don't set stream name, so set it to - * dai link name if it's NULL to help bind widgets. - */ - if (!dai_link->stream_name) - dai_link->stream_name = dai_link->name; - } - - /* Inform userspace we are using alternate topology */ - snd_soc_card_set_topology_name(card, component->driver->topology_name_prefix); - } -} - -#define soc_setup_card_name(card, name, name1, name2) \ - __soc_setup_card_name(card, name, sizeof(name), name1, name2) -static void __soc_setup_card_name(struct snd_soc_card *card, - char *name, int len, - const char *name1, const char *name2) -{ - const char *src = name1 ? name1 : name2; - int i; - - snprintf(name, len, "%s", src); - - if (name != card->snd_card->driver) - return; - - /* - * Name normalization (driver field) - * - * The driver name is somewhat special, as it's used as a key for - * searches in the user-space. - * - * ex) - * "abcd??efg" -> "abcd__efg" - */ - for (i = 0; i < len; i++) { - switch (name[i]) { - case '_': - case '-': - case '\0': - break; - default: - if (!isalnum(name[i])) - name[i] = '_'; - break; - } - } - - /* - * The driver field should contain a valid string from the user view. - * The wrapping usually does not work so well here. Set a smaller string - * in the specific ASoC driver. - */ - if (strlen(src) > len - 1) - dev_err(card->dev, "ASoC: driver name too long '%s' -> '%s'\n", src, name); -} - -static void soc_cleanup_card_resources(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd, *n; - - if (card->snd_card) - snd_card_disconnect_sync(card->snd_card); - - snd_soc_dapm_shutdown(card); - - /* release machine specific resources */ - for_each_card_rtds(card, rtd) - if (rtd->initialized) - snd_soc_link_exit(rtd); - /* flush delayed work before removing DAIs and DAPM widgets */ - snd_soc_flush_all_delayed_work(card); - - /* remove and free each DAI */ - snd_soc_card_link_dais_remove(card); - snd_soc_card_link_components_remove(card); - - for_each_card_rtds_safe(card, rtd, n) - snd_soc_remove_pcm_runtime(card, rtd); - - /* remove auxiliary devices */ - snd_soc_card_aux_remove(card); - snd_soc_card_aux_unbind(card); - - snd_soc_dapm_free(snd_soc_card_to_dapm(card)); - snd_soc_card_debugfs_cleanup(card); - - /* remove the card */ - snd_soc_card_remove(card); - - if (card->snd_card) { - snd_card_free(card->snd_card); - card->snd_card = NULL; - } -} - -static void snd_soc_remove_device_links(struct snd_soc_card *card) -{ - struct snd_soc_component *component; - - for_each_card_components(card, component) { - if (component->card_device_link) { - device_link_del(component->card_device_link); - component->card_device_link = NULL; - } - } -} - -static void snd_soc_unbind_card(struct snd_soc_card *card) -{ - if (snd_soc_card_is_instantiated(card)) { - card->instantiated = false; - - snd_soc_remove_device_links(card); - - soc_cleanup_card_resources(card); - } -} - -static int snd_soc_bind_card(struct snd_soc_card *card) -{ - struct snd_soc_pcm_runtime *rtd; - struct snd_soc_component *component; - struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card); - int ret; - - snd_soc_card_mutex_lock_root(card); - snd_soc_card_fill_dummy_dai(card); - - snd_soc_dapm_init(dapm, card, NULL); - list_del_init(&card->list); - - /* check whether any platform is ignore machine FE and using topology */ - soc_check_tplg_fes(card); - - /* bind aux_devs too */ - ret = snd_soc_card_aux_bind(card); - if (ret < 0) - goto probe_end; - - /* add predefined DAI links to the list */ - card->num_rtd = 0; - ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links); - if (ret < 0) - goto probe_end; - - /* card bind complete so register a sound card */ - ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, - card->owner, 0, &card->snd_card); - if (ret < 0) { - dev_err(card->dev, - "ASoC: can't create sound card for card %s: %d\n", - card->name, ret); - goto probe_end; - } - - snd_soc_card_debugfs_init(card); - - snd_soc_card_resume_init(card); - - ret = snd_soc_dapm_new_controls(dapm, card->dapm_widgets, - card->num_dapm_widgets); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_new_controls(dapm, card->of_dapm_widgets, - card->num_of_dapm_widgets); - if (ret < 0) - goto probe_end; - - /* initialise the sound card only once */ - ret = snd_soc_card_probe(card); - if (ret < 0) - goto probe_end; - - /* probe all components used by DAI links on this card */ - ret = snd_soc_card_link_components_probe(card); - if (ret < 0) { - if (ret != -EPROBE_DEFER) { - dev_err(card->dev, - "ASoC: failed to instantiate card %d\n", ret); - } - goto probe_end; - } - - /* probe auxiliary components */ - ret = snd_soc_card_aux_probe(card); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to probe aux component %d\n", ret); - goto probe_end; - } - - /* probe all DAI links on this card */ - ret = snd_soc_card_link_dais_probe(card); - if (ret < 0) { - dev_err(card->dev, - "ASoC: failed to instantiate card %d\n", ret); - goto probe_end; - } - - for_each_card_rtds(card, rtd) { - ret = snd_soc_card_init_pcm_runtime(card, rtd); - if (ret < 0) - goto probe_end; - } - - snd_soc_dapm_link_dai_widgets(card); - snd_soc_dapm_connect_dai_link_widgets(card); - - ret = snd_soc_add_card_controls(card, card->controls, - card->num_controls); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_add_routes(dapm, card->dapm_routes, - card->num_dapm_routes); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_add_routes(dapm, card->of_dapm_routes, - card->num_of_dapm_routes); - if (ret < 0) - goto probe_end; - - /* try to set some sane longname if DMI is available */ - snd_soc_card_set_dmi_name(card); - - soc_setup_card_name(card, card->snd_card->shortname, - card->name, NULL); - soc_setup_card_name(card, card->snd_card->longname, - card->long_name, card->name); - soc_setup_card_name(card, card->snd_card->driver, - card->driver_name, card->name); - - if (card->components) { - /* the current implementation of snd_component_add() accepts */ - /* multiple components in the string separated by space, */ - /* but the string collision (identical string) check might */ - /* not work correctly */ - ret = snd_component_add(card->snd_card, card->components); - if (ret < 0) { - dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n", - card->name, ret); - goto probe_end; - } - } - - /* - * Add device_link from card to component so that system_suspend - * will be done in the correct order. The card must suspend first - * to stop audio activity before the components suspend. - * - * If a driver pair already have a link in the opposite direction - * they must manage their own suspend order. - */ - for_each_card_components(card, component) { - if (card->dev == component->dev) - continue; - - component->card_device_link = device_link_add(card->dev, - component->dev, - DL_FLAG_STATELESS); - if (!component->card_device_link) { - dev_warn(card->dev, "Could not create device link to %s\n", - dev_name(component->dev)); - } - } - - ret = snd_soc_card_late_probe(card); - if (ret < 0) - goto probe_end; - - ret = snd_soc_dapm_ignore_suspend_widgets(card); - if (ret < 0) - goto probe_end; - - snd_soc_dapm_new_widgets(card); - for_each_card_components(card, component) { - ret = snd_soc_component_fixup_controls(component); - if (ret < 0) - goto probe_end; - } - snd_soc_card_fixup_controls(card); - - ret = snd_card_register(card->snd_card); - if (ret < 0) { - dev_err(card->dev, "ASoC: failed to register soundcard %d\n", - ret); - goto probe_end; - } - - card->instantiated = 1; - snd_soc_dapm_mark_endpoints_dirty(card); - snd_soc_dapm_sync(dapm); - - /* deactivate pins to sleep state */ - for_each_card_components(card, component) - if (!snd_soc_component_active(component)) - pinctrl_pm_select_sleep_state(component->dev); - -probe_end: - if (ret < 0) { - snd_soc_remove_device_links(card); - soc_cleanup_card_resources(card); - } - - if (ret == -EPROBE_DEFER) { - list_add(&card->list, &unbind_card_list); - ret = 0; - } - snd_soc_card_mutex_unlock(card); - - return ret; -} - -static void devm_card_bind_release(struct device *dev, void *res) -{ - snd_soc_unregister_card(*(struct snd_soc_card **)res); -} - -static int devm_snd_soc_bind_card(struct device *dev, struct snd_soc_card *card) -{ - struct snd_soc_card **ptr; - int ret; - - /* The procedure may be called many times during the lifetime of the card. */ - devres_destroy(dev, devm_card_bind_release, NULL, NULL); - - ptr = devres_alloc(devm_card_bind_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return -ENOMEM; - - ret = snd_soc_bind_card(card); - if (ret == 0) { - *ptr = card; - devres_add(dev, ptr); - } else { - devres_free(ptr); - } - - return ret; -} - -static int call_soc_bind_card(struct snd_soc_card *card) -{ - if (card->devres_dev) - return devm_snd_soc_bind_card(card->devres_dev, card); - return snd_soc_bind_card(card); -} - /* probes a new socdev */ static int soc_probe(struct platform_device *pdev) { @@ -1641,7 +1232,7 @@ int snd_soc_register_card(struct snd_soc_card *card) guard(mutex)(&client_mutex); - return call_soc_bind_card(card); + return snd_soc_card_bind_call(card); } EXPORT_SYMBOL_GPL(snd_soc_register_card); @@ -1655,7 +1246,7 @@ void snd_soc_unregister_card(struct snd_soc_card *card) { guard(mutex)(&client_mutex); - snd_soc_unbind_card(card); + snd_soc_card_unbind(card); list_del(&card->list); dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name); @@ -1816,7 +1407,7 @@ static void snd_soc_del_component_unlocked(struct snd_soc_component *component) if (card) { instantiated = card->instantiated; - snd_soc_unbind_card(card); + snd_soc_card_unbind(card); if (instantiated) list_add(&card->list, &unbind_card_list); } @@ -1893,7 +1484,7 @@ int snd_soc_component_register(struct snd_soc_component *component, list_add(&component->list, snd_soc_component_get_list_head()); list_for_each_entry_safe(card, c, &unbind_card_list, list) - call_soc_bind_card(card); + snd_soc_card_bind_call(card); err_cleanup: if (ret < 0) diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index e1f456e222b63e..97891bf68b8823 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -17,6 +17,7 @@ * REMOVE ME * Temporary definition */ +extern struct list_head unbind_card_list; void snd_soc_card_debugfs_init(struct snd_soc_card *card); void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); void snd_soc_card_resume_init(struct snd_soc_card *card); @@ -43,12 +44,19 @@ char *snd_soc_fmt_multiple_name(struct device *dev, struct snd_soc_dai_driver *d int snd_soc_add_controls(struct snd_card *card, struct device *dev, const struct snd_kcontrol_new *controls, int num_controls, const char *prefix, void *data); +void snd_soc_flush_all_delayed_work(struct snd_soc_card *card); struct snd_soc_component *snd_soc_find_component(const struct snd_soc_dai_link_component *dlc); #ifdef CONFIG_PM_SLEEP void snd_soc_playback_digital_mute(struct snd_soc_card *card, int mute); void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event); #endif +/* + * In soc-card + */ +void snd_soc_card_unbind(struct snd_soc_card *card); +int snd_soc_card_bind_call(struct snd_soc_card *card); + /* * In soc-dai */ From eda0bcca774c788d33e53c3e2797b79ce766950a Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:53 +0000 Subject: [PATCH 161/186] ASoC: soc-card: add snd_soc_card_rebind() struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. add snd_soc_card_rebind() and capsuled card relarted operation Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87se1qsezq.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 8 ++++++++ sound/soc/soc-core.c | 4 +--- sound/soc/soc-internal.h | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 72b40f842bd436..f6ff99bf8a6016 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -1097,6 +1097,14 @@ static int snd_soc_card_bind(struct snd_soc_card *card) return ret; } +void snd_soc_card_rebind(void) +{ + struct snd_soc_card *card, *c; + + list_for_each_entry_safe(card, c, &unbind_card_list, list) + snd_soc_card_bind_call(card); +} + static void devm_snd_soc_card_bind_release(struct device *dev, void *res) { snd_soc_unregister_card(*(struct snd_soc_card **)res); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 301089259a8d41..fd46906cbca96d 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1455,7 +1455,6 @@ int snd_soc_component_register(struct snd_soc_component *component, struct snd_soc_dai_driver *dai_drv, int num_dai) { - struct snd_soc_card *card, *c; int ret; int i; guard(mutex)(&client_mutex); @@ -1483,8 +1482,7 @@ int snd_soc_component_register(struct snd_soc_component *component, /* see for_each_component */ list_add(&component->list, snd_soc_component_get_list_head()); - list_for_each_entry_safe(card, c, &unbind_card_list, list) - snd_soc_card_bind_call(card); + snd_soc_card_rebind(); err_cleanup: if (ret < 0) diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 97891bf68b8823..ccd6a7baa4f3fb 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -55,6 +55,7 @@ void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event); * In soc-card */ void snd_soc_card_unbind(struct snd_soc_card *card); +void snd_soc_card_rebind(void); int snd_soc_card_bind_call(struct snd_soc_card *card); /* From b703abddbc1fb08ed239f959c36fbe742304bd67 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:03:57 +0000 Subject: [PATCH 162/186] ASoC: soc-card: make unbind_card_list static struct snd_soc_card will be capsuled soon, its member will not be able to access from non soc-card.c. Update snd_soc_card_unbind() and makes unbind_card_list static in soc-card.c Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87qzhasezm.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 10 ++++++++-- sound/soc/soc-core.c | 12 +++--------- sound/soc/soc-internal.h | 3 +-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index f6ff99bf8a6016..959b56be0eedff 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -17,7 +17,7 @@ #include #include "soc-internal.h" -LIST_HEAD(unbind_card_list); +static LIST_HEAD(unbind_card_list); #define soc_card_ret(dai, ret) _soc_card_ret(dai, __func__, ret) static inline int _soc_card_ret(struct snd_soc_card *card, @@ -892,7 +892,7 @@ static void snd_soc_remove_device_links(struct snd_soc_card *card) } } -void snd_soc_card_unbind(struct snd_soc_card *card) +void snd_soc_card_unbind(struct snd_soc_card *card, bool reuse) { if (snd_soc_card_is_instantiated(card)) { card->instantiated = false; @@ -900,7 +900,13 @@ void snd_soc_card_unbind(struct snd_soc_card *card) snd_soc_remove_device_links(card); snd_soc_card_cleanup_resources(card); + + if (reuse) + list_add(&card->list, &unbind_card_list); } + + if (!reuse) + list_del(&card->list); } static int snd_soc_card_bind(struct snd_soc_card *card) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index fd46906cbca96d..ba02f244f79541 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1246,8 +1246,7 @@ void snd_soc_unregister_card(struct snd_soc_card *card) { guard(mutex)(&client_mutex); - snd_soc_card_unbind(card); - list_del(&card->list); + snd_soc_card_unbind(card, false); dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name); } @@ -1401,16 +1400,11 @@ static void convert_endianness_formats(struct snd_soc_pcm_stream *stream) static void snd_soc_del_component_unlocked(struct snd_soc_component *component) { struct snd_soc_card *card = component->card; - bool instantiated; snd_soc_unregister_dais(component); - if (card) { - instantiated = card->instantiated; - snd_soc_card_unbind(card); - if (instantiated) - list_add(&card->list, &unbind_card_list); - } + if (card) + snd_soc_card_unbind(card, true); list_del(&component->list); } diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index ccd6a7baa4f3fb..255e06466e925e 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -17,7 +17,6 @@ * REMOVE ME * Temporary definition */ -extern struct list_head unbind_card_list; void snd_soc_card_debugfs_init(struct snd_soc_card *card); void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); void snd_soc_card_resume_init(struct snd_soc_card *card); @@ -54,7 +53,7 @@ void snd_soc_dapm_suspend_resume(struct snd_soc_card *card, int event); /* * In soc-card */ -void snd_soc_card_unbind(struct snd_soc_card *card); +void snd_soc_card_unbind(struct snd_soc_card *card, bool reuse); void snd_soc_card_rebind(void); int snd_soc_card_bind_call(struct snd_soc_card *card); From d32126bec1be6dca8f9974e06afe368485d13ed1 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Wed, 28 Oct 2026 03:04:00 +0000 Subject: [PATCH 163/186] ASoC: soc-internal: remove temporary funciton defines There were too many local function dependencies to move Card related function from soc-core.c to soc-card.c. Now, all functions were moved, let's remove temporary funciton defines. Signed-off-by: Kuninori Morimoto Link: https://patch.msgid.link/87pkwusezj.wl-kuninori.morimoto.gx@renesas.com Signed-off-by: Mark Brown --- sound/soc/soc-card.c | 38 +++++++++++++++++++------------------- sound/soc/soc-internal.h | 20 -------------------- 2 files changed, 19 insertions(+), 39 deletions(-) diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c index 959b56be0eedff..6544dbf2ee2607 100644 --- a/sound/soc/soc-card.c +++ b/sound/soc/soc-card.c @@ -28,7 +28,7 @@ static inline int _soc_card_ret(struct snd_soc_card *card, } #ifdef CONFIG_DEBUG_FS -void snd_soc_card_debugfs_init(struct snd_soc_card *card) +static void snd_soc_card_debugfs_init(struct snd_soc_card *card) { card->debugfs_card_root = debugfs_create_dir(card->name, snd_soc_debugfs_root); @@ -36,14 +36,14 @@ void snd_soc_card_debugfs_init(struct snd_soc_card *card) snd_soc_dapm_debugfs_init(snd_soc_card_to_dapm(card), card->debugfs_card_root); } -void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) +static void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) { debugfs_remove_recursive(card->debugfs_card_root); card->debugfs_card_root = NULL; } #else -void snd_soc_card_debugfs_init(struct snd_soc_card *card) { } -void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) { } +static inline void snd_soc_card_debugfs_init(struct snd_soc_card *card) { } +static inline void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card) { } #endif /* CONFIG_DEBUG_FS */ #ifdef CONFIG_PM_SLEEP @@ -92,16 +92,16 @@ static void snd_soc_card_resume_deferred(struct work_struct *work) snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0); } -void snd_soc_card_resume_init(struct snd_soc_card *card) +static void snd_soc_card_resume_init(struct snd_soc_card *card) { /* deferred resume work */ INIT_WORK(&card->deferred_resume_work, snd_soc_card_resume_deferred); } #else -void snd_soc_card_resume_init(struct snd_soc_card *card) { } +static inline void snd_soc_card_resume_init(struct snd_soc_card *card) { } #endif /* CONFIG_PM_SLEEP */ -void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card) +static void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card) { struct snd_soc_dai_link *dai_link; int i; @@ -123,8 +123,8 @@ void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card) } } -int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd) +static int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, + struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai_link *dai_link = rtd->dai_link; struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); @@ -171,7 +171,7 @@ int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, return ret; } -void snd_soc_card_link_dais_remove(struct snd_soc_card *card) +static void snd_soc_card_link_dais_remove(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; int order; @@ -184,7 +184,7 @@ void snd_soc_card_link_dais_remove(struct snd_soc_card *card) } } -int snd_soc_card_link_dais_probe(struct snd_soc_card *card) +static int snd_soc_card_link_dais_probe(struct snd_soc_card *card) { struct snd_soc_pcm_runtime *rtd; int order, ret; @@ -201,7 +201,7 @@ int snd_soc_card_link_dais_probe(struct snd_soc_card *card) return 0; } -void snd_soc_card_link_components_remove(struct snd_soc_card *card) +static void snd_soc_card_link_components_remove(struct snd_soc_card *card) { struct snd_soc_component *component; struct snd_soc_pcm_runtime *rtd; @@ -219,7 +219,7 @@ void snd_soc_card_link_components_remove(struct snd_soc_card *card) } } -int snd_soc_card_link_components_probe(struct snd_soc_card *card) +static int snd_soc_card_link_components_probe(struct snd_soc_card *card) { struct snd_soc_component *component; struct snd_soc_pcm_runtime *rtd; @@ -241,7 +241,7 @@ int snd_soc_card_link_components_probe(struct snd_soc_card *card) return 0; } -void snd_soc_card_aux_unbind(struct snd_soc_card *card) +static void snd_soc_card_aux_unbind(struct snd_soc_card *card) { struct snd_soc_component *component, *_component; @@ -252,7 +252,7 @@ void snd_soc_card_aux_unbind(struct snd_soc_card *card) } } -int snd_soc_card_aux_bind(struct snd_soc_card *card) +static int snd_soc_card_aux_bind(struct snd_soc_card *card) { struct snd_soc_component *component; struct snd_soc_aux_dev *aux; @@ -272,7 +272,7 @@ int snd_soc_card_aux_bind(struct snd_soc_card *card) return 0; } -int snd_soc_card_aux_probe(struct snd_soc_card *card) +static int snd_soc_card_aux_probe(struct snd_soc_card *card) { struct snd_soc_component *component; int order; @@ -292,7 +292,7 @@ int snd_soc_card_aux_probe(struct snd_soc_card *card) return 0; } -void snd_soc_card_aux_remove(struct snd_soc_card *card) +static void snd_soc_card_aux_remove(struct snd_soc_card *card) { struct snd_soc_component *comp, *_comp; int order; @@ -408,7 +408,7 @@ static void append_dmi_string(char *dst, const char *str) * * Returns 0 on success, otherwise a negative error code. */ -int snd_soc_card_set_dmi_name(struct snd_soc_card *card) +static int snd_soc_card_set_dmi_name(struct snd_soc_card *card) { const char *vendor, *product, *board; char *dmi_longname; @@ -463,7 +463,7 @@ int snd_soc_card_set_dmi_name(struct snd_soc_card *card) return 0; } #else -int snd_soc_card_set_dmi_name(struct snd_soc_card *card) +static inline int snd_soc_card_set_dmi_name(struct snd_soc_card *card) { return 0; } diff --git a/sound/soc/soc-internal.h b/sound/soc/soc-internal.h index 255e06466e925e..858a071798d0fe 100644 --- a/sound/soc/soc-internal.h +++ b/sound/soc/soc-internal.h @@ -13,26 +13,6 @@ * as snd_soc_xxx(); */ -/* - * REMOVE ME - * Temporary definition - */ -void snd_soc_card_debugfs_init(struct snd_soc_card *card); -void snd_soc_card_debugfs_cleanup(struct snd_soc_card *card); -void snd_soc_card_resume_init(struct snd_soc_card *card); -void snd_soc_card_fill_dummy_dai(struct snd_soc_card *card); -int snd_soc_card_init_pcm_runtime(struct snd_soc_card *card, - struct snd_soc_pcm_runtime *rtd); -void snd_soc_card_link_dais_remove(struct snd_soc_card *card); -int snd_soc_card_link_dais_probe(struct snd_soc_card *card); -void snd_soc_card_link_components_remove(struct snd_soc_card *card); -int snd_soc_card_link_components_probe(struct snd_soc_card *card); -void snd_soc_card_aux_unbind(struct snd_soc_card *card); -int snd_soc_card_aux_bind(struct snd_soc_card *card); -int snd_soc_card_aux_probe(struct snd_soc_card *card); -void snd_soc_card_aux_remove(struct snd_soc_card *card); -int snd_soc_card_set_dmi_name(struct snd_soc_card *card); - /* * In soc-core */ From 304f08895e44b8d8f97d553770c2a64d94d14c6f Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:55 +0000 Subject: [PATCH 164/186] ASoC: sti: fix the player clock reference leak uni_player_init() takes a reference on the player clock with of_clk_get() and stores it in player->clk, but nothing in the driver ever calls clk_put(). The reference is lost when probe fails after that point, and it also outlives unbinding of the device, because the struct uniperif holding the pointer is devres-managed. Use devm_clk_get() so that devres drops the reference on probe failure and on driver detach, like the other resources this function acquires. Fixes: 76c2145ded6b ("ASoC: sti: Add CPU DAI driver for playback") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024055.1508788-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/sti/uniperif_player.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/sti/uniperif_player.c b/sound/soc/sti/uniperif_player.c index 844ddcfc421a7f..23c6f9e513c121 100644 --- a/sound/soc/sti/uniperif_player.c +++ b/sound/soc/sti/uniperif_player.c @@ -1078,7 +1078,7 @@ int uni_player_init(struct platform_device *pdev, player->hw = &uni_player_pcm_hw; /* Get uniperif resource */ - player->clk = of_clk_get(pdev->dev.of_node, 0); + player->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(player->clk)) { dev_err(player->dev, "Failed to get clock\n"); return PTR_ERR(player->clk); From e7366e6b65f3c772c01facf5993de606265c3388 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:49 +0000 Subject: [PATCH 165/186] ASoC: rt5677: register the GPIO chip through devm rt5677_init_gpio() registers the GPIO chip with gpiochip_add_data(), and rt5677_i2c_remove() unregisters it. If devm_snd_soc_register_component() fails at the end of rt5677_i2c_probe(), the probe returns an error and the driver core never calls .remove, so the GPIO chip stays registered and keeps its reference to the parent I2C device. Register the chip with devm_gpiochip_add_data() so that it is torn down on probe failure as well as on driver unbind, and drop the now redundant manual cleanup. Fixes: 44caf7648064 ("ASoC: rt5677: Add the GPIO function") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024049.1508652-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/codecs/rt5677.c | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c index fd1f840d6794ea..91316537d68ee8 100644 --- a/sound/soc/codecs/rt5677.c +++ b/sound/soc/codecs/rt5677.c @@ -4864,17 +4864,10 @@ static void rt5677_init_gpio(struct i2c_client *i2c) rt5677->gpio_chip.parent = &i2c->dev; rt5677->gpio_chip.base = -1; - ret = gpiochip_add_data(&rt5677->gpio_chip, rt5677); + ret = devm_gpiochip_add_data(&i2c->dev, &rt5677->gpio_chip, rt5677); if (ret != 0) dev_err(&i2c->dev, "Failed to add GPIOs: %d\n", ret); } - -static void rt5677_free_gpio(struct i2c_client *i2c) -{ - struct rt5677_priv *rt5677 = i2c_get_clientdata(i2c); - - gpiochip_remove(&rt5677->gpio_chip); -} #else static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, int value) @@ -4884,10 +4877,6 @@ static void rt5677_gpio_config(struct rt5677_priv *rt5677, unsigned offset, static void rt5677_init_gpio(struct i2c_client *i2c) { } - -static void rt5677_free_gpio(struct i2c_client *i2c) -{ -} #endif static int rt5677_probe(struct snd_soc_component *component) @@ -5730,11 +5719,6 @@ static int rt5677_i2c_probe(struct i2c_client *i2c) rt5677_dai, ARRAY_SIZE(rt5677_dai)); } -static void rt5677_i2c_remove(struct i2c_client *i2c) -{ - rt5677_free_gpio(i2c); -} - static struct i2c_driver rt5677_i2c_driver = { .driver = { .name = RT5677_DRV_NAME, @@ -5743,7 +5727,6 @@ static struct i2c_driver rt5677_i2c_driver = { }, .id_table = rt5677_i2c_id, .probe = rt5677_i2c_probe, - .remove = rt5677_i2c_remove, }; module_i2c_driver(rt5677_i2c_driver); From f8fff272681bc9403d2f1bc6f8467ed4895c1b77 Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:54 +0000 Subject: [PATCH 166/186] ASoC: SOF: amd: fix leaks when SoundWire probe fails When amd_sof_sdw_probe() fails, amd_sof_acp_probe() only frees the IPC IRQ and returns, leaving the "dmic-codec" platform device registered by platform_device_register_data() behind. The SoundWire context that sdw_amd_probe() allocated before the startup step failed is leaked as well. The probe never succeeds for this device, so neither amd_sof_acp_remove() nor the devres cleanup releases them. Jump to the existing free_ipc_irq label instead, which also calls amd_sof_sdw_exit() and unregisters the DMIC device, as the ACP7.x probe path already does. Fixes: d948218424bf ("ASoC: SOF: amd: add code for invoking soundwire manager helper functions") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024054.1508754-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/sof/amd/acp.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index 86e2402b8ffd26..120dde4ab7773b 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -1278,8 +1278,7 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) ret = amd_sof_sdw_probe(sdev); if (ret < 0) { dev_err(sdev->dev, "error: SoundWire probe error\n"); - free_irq(sdev->ipc_irq, sdev); - return ret; + goto free_ipc_irq; } skip_soundwire: From ad6111b192c5c38d3d93df608d5b0129693cdd2e Mon Sep 17 00:00:00 2001 From: Svyatoslav Ryhel Date: Thu, 15 Oct 2026 16:06:28 +0300 Subject: [PATCH 167/186] ASoC: dt-bindings: nvidia,tegra-audio: Consolidate all codec support into a single schema The number of codecs supported by the NVIDIA Tegra audio complex is increasing, which leads to an increasing number of bindings with minimal changes. The main differences between codecs are the compatible strings and possible pin configurations. With the introduction of auxiliary devices, a definitive description of possible pin configurations becomes nearly impossible. Remove all existing per-codec schemas and consolidate everything into a single schema. Compatibles are grouped into common codecs followed by unique codecs, all sorted alphabetically. Added examples for each of the common codecs and for each Tegra generation except Tegra114. Schema check passes for all in-kernel Tegra device trees. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20261015130628.184279-2-clamor95@gmail.com Signed-off-by: Mark Brown --- .../sound/nvidia,tegra-audio-alc5632.yaml | 74 ------ .../sound/nvidia,tegra-audio-common.yaml | 92 ------- .../sound/nvidia,tegra-audio-complex.yaml | 235 ++++++++++++++++++ .../sound/nvidia,tegra-audio-cpcap.yaml | 90 ------- .../sound/nvidia,tegra-audio-max9808x.yaml | 95 ------- .../sound/nvidia,tegra-audio-max98090.yaml | 97 -------- .../sound/nvidia,tegra-audio-rt5631.yaml | 85 ------- .../sound/nvidia,tegra-audio-rt5640.yaml | 84 ------- .../sound/nvidia,tegra-audio-rt5677.yaml | 100 -------- .../sound/nvidia,tegra-audio-sgtl5000.yaml | 67 ----- .../sound/nvidia,tegra-audio-trimslice.yaml | 33 --- .../sound/nvidia,tegra-audio-wm8753.yaml | 79 ------ .../sound/nvidia,tegra-audio-wm8903.yaml | 93 ------- .../sound/nvidia,tegra-audio-wm8962.yaml | 88 ------- .../sound/nvidia,tegra-audio-wm8994.yaml | 110 -------- .../sound/nvidia,tegra-audio-wm9712.yaml | 76 ------ 16 files changed, 235 insertions(+), 1263 deletions(-) delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml create mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml delete mode 100644 Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml deleted file mode 100644 index 96f2f927a6f591..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-alc5632.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-alc5632.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with ALC5632 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-alc5632(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-alc5632 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headset Stereophone - - Int Spk - - Headset Mic - - Digital Mic - - # CODEC Pins - - SPKOUT - - SPKOUTN - - MICBIAS1 - - MIC1 - - HPR - - HPL - - DMICDAT - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-alc5632-paz00", - "nvidia,tegra-audio-alc5632"; - - nvidia,model = "Compal PAZ00"; - - nvidia,audio-routing = "Int Spk", "SPKOUT", - "Int Spk", "SPKOUTN", - "Headset Mic", "MICBIAS1", - "MIC1", "Headset Mic", - "Headset Stereophone", "HPR", - "Headset Stereophone", "HPL", - "DMICDAT", "Digital Mic"; - - nvidia,i2s-controller = <&i2s>; - nvidia,audio-codec = <&codec>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml deleted file mode 100644 index 316806fc7346ee..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-common.yaml +++ /dev/null @@ -1,92 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-common.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: Common properties for NVIDIA Tegra audio complexes - -maintainers: - - Jon Hunter - - Thierry Reding - -properties: - clocks: - items: - - description: PLL A clock - - description: PLL A OUT0 clock - - description: The Tegra cdev1/extern1 clock, which feeds the card's mclk - - clock-names: - items: - - const: pll_a - - const: pll_a_out0 - - const: mclk - - nvidia,model: - $ref: /schemas/types.yaml#/definitions/string - description: The user-visible name of this sound complex. - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - - nvidia,ac97-controller: - $ref: /schemas/types.yaml#/definitions/phandle - description: The phandle of the AC97 controller - - nvidia,i2s-controller: - $ref: /schemas/types.yaml#/definitions/phandle - description: The phandle of the Tegra I2S controller - - nvidia,audio-codec: - $ref: /schemas/types.yaml#/definitions/phandle - description: The phandle of audio codec - - nvidia,aux-devs: - $ref: /schemas/types.yaml#/definitions/phandle-array - description: List of phandles pointing to auxiliary devices, - such as amplifiers or dsp, to be added to the sound card. - - nvidia,spkr-en-gpios: - maxItems: 1 - description: The GPIO that enables the speakers - - nvidia,hp-mute-gpios: - maxItems: 1 - description: The GPIO that mutes the headphones - - nvidia,hp-det-gpios: - maxItems: 1 - description: The GPIO that detect headphones are plugged in - - nvidia,mic-det-gpios: - maxItems: 1 - description: The GPIO that detect microphone is plugged in - - nvidia,ear-sel-gpios: - maxItems: 1 - description: The GPIO that switch between the microphones - - nvidia,int-mic-en-gpios: - maxItems: 1 - description: The GPIO that enables the internal microphone - - nvidia,ext-mic-en-gpios: - maxItems: 1 - description: The GPIO that enables the external microphone - - nvidia,headset: - type: boolean - description: The Mic Jack represents state of the headset microphone pin - - nvidia,coupled-mic-hp-det: - type: boolean - description: The Mic detect GPIO is viable only if HP detect GPIO is active - -additionalProperties: true diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml new file mode 100644 index 00000000000000..d5506ff4a4c698 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-complex.yaml @@ -0,0 +1,235 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-complex.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NVIDIA Tegra audio complex + +maintainers: + - Jon Hunter + - Thierry Reding + - Svyatoslav Ryhel + +properties: + compatible: + oneOf: + - items: + - pattern: '^[a-z0-9]+,tegra-audio-max(98088|98089|98090)([-_][a-z0-9]+)+$' + - enum: + - nvidia,tegra-audio-max98088 + - nvidia,tegra-audio-max98089 + - nvidia,tegra-audio-max98090 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-rt(5631|5639|5640|5677)([-_][a-z0-9]+)+$' + - enum: + - nvidia,tegra-audio-rt5631 + - nvidia,tegra-audio-rt5640 + - nvidia,tegra-audio-rt5677 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-wm(8753|8903|8962|8994|9712)([-_][a-z0-9]+)+$' + - enum: + - nvidia,tegra-audio-wm8753 + - nvidia,tegra-audio-wm8903 + - nvidia,tegra-audio-wm8962 + - nvidia,tegra-audio-wm8994 + - nvidia,tegra-audio-wm9712 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-alc5632(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-alc5632 + - items: + - pattern: '^motorola,tegra-audio-cpcap(-[a-z0-9]+)+$' + - const: nvidia,tegra-audio-cpcap + - items: + - pattern: '^nvidia,tegra-audio-max98090-nyan-(big|blaze)+$' + - const: nvidia,tegra-audio-max98090-nyan + - const: nvidia,tegra-audio-max98090 + - items: + - pattern: '^[a-z0-9]+,tegra-audio-sgtl5000([-_][a-z0-9]+)+$' + - const: nvidia,tegra-audio-sgtl5000 + - items: + const: nvidia,tegra-audio-trimslice + - items: + - pattern: ad,tegra-audio-plutux + - const: nvidia,tegra-audio-wm8903 + + clocks: + items: + - description: PLL A clock + - description: PLL A OUT0 clock + - description: The Tegra cdev1/extern1 clock, which feeds the card's mclk + + clock-names: + items: + - const: pll_a + - const: pll_a_out0 + - const: mclk + + nvidia,model: + $ref: /schemas/types.yaml#/definitions/string + description: The user-visible name of this sound complex. + + nvidia,audio-routing: + $ref: /schemas/types.yaml#/definitions/non-unique-string-array + description: | + A list of the connections between audio components. + Each entry is a pair of strings, the first being the connection's sink, + the second being the connection's source. Valid names for sources and + sinks are the codec pins and the jacks on the board. + + nvidia,ac97-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the AC97 controller + + nvidia,i2s-controller: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of the Tegra I2S controller + + nvidia,audio-codec: + $ref: /schemas/types.yaml#/definitions/phandle + description: The phandle of audio codec + + nvidia,aux-devs: + $ref: /schemas/types.yaml#/definitions/phandle-array + description: List of phandles pointing to auxiliary devices, + such as amplifiers or dsp, to be added to the sound card. + + nvidia,spkr-en-gpios: + maxItems: 1 + description: The GPIO that enables the speakers + + nvidia,hp-mute-gpios: + maxItems: 1 + description: The GPIO that mutes the headphones + + nvidia,hp-det-gpios: + maxItems: 1 + description: The GPIO that detect headphones are plugged in + + nvidia,mic-det-gpios: + maxItems: 1 + description: The GPIO that detect microphone is plugged in + + nvidia,ear-sel-gpios: + maxItems: 1 + description: The GPIO that switch between the microphones + + nvidia,int-mic-en-gpios: + maxItems: 1 + description: The GPIO that enables the internal microphone + + nvidia,ext-mic-en-gpios: + maxItems: 1 + description: The GPIO that enables the external microphone + + nvidia,headset: + type: boolean + description: The Mic Jack represents state of the headset microphone pin + + nvidia,coupled-mic-hp-det: + type: boolean + description: The Mic detect GPIO is viable only if HP detect GPIO is active + +oneOf: + - required: + - nvidia,i2s-controller + - required: + - nvidia,ac97-controller + +additionalProperties: false + +examples: + - | + #include + #include + + sound { + compatible = "lg,tegra-audio-wm8994-star", + "nvidia,tegra-audio-wm8994"; + nvidia,model = "LG Optimus 2X WM8994"; + + nvidia,audio-routing = + "Headphone Jack", "HPOUT1L", + "Headphone Jack", "HPOUT1R", + "Int Spk", "SPKOUTLN", + "Int Spk", "SPKOUTLP", + "Earpiece", "HPOUT2N", + "Earpiece", "HPOUT2P", + "IN2RN", "MICBIAS2", + "IN2RN", "Mic Jack", + "IN1LN", "MICBIAS1", + "IN1LN", "Internal Mic 1", + "IN1RN", "MICBIAS1", + "IN1RN", "Internal Mic 2"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&wm8994>; + + nvidia,hp-det-gpios = <&gpio 51 GPIO_ACTIVE_HIGH>; + nvidia,mic-det-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; + nvidia,ext-mic-en-gpios = <&gpio 57 GPIO_ACTIVE_HIGH>; + nvidia,coupled-mic-hp-det; + + clocks = <&tegra_car TEGRA20_CLK_PLL_A>, + <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, + <&tegra_car TEGRA20_CLK_CDEV1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; + - | + #include + #include + #include + + sound { + compatible = "asus,tegra-audio-rt5631-tf700t", + "nvidia,tegra-audio-rt5631"; + nvidia,model = "Asus Transformer Infinity TF700T RT5631"; + + nvidia,audio-routing = + "Headphone Jack", "HPOL", + "Headphone Jack", "HPOR", + "Int Spk", "SPOL", + "Int Spk", "SPOR", + "MIC1", "MIC Bias1", + "MIC1", "DSP Analog Mic", + "DSP AMIC", "Mic Jack", + "DMIC", "DSP Digital Mic", + "DSP DMIC", "Int Mic"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&rt5631>; + nvidia,aux-devs = <&fm34ne>; + + nvidia,hp-det-gpios = <&gpio 178 GPIO_ACTIVE_LOW>; + nvidia,mic-det-gpios = <&gpio 186 GPIO_ACTIVE_LOW>; + nvidia,coupled-mic-hp-det; + + clocks = <&tegra_car TEGRA30_CLK_PLL_A>, + <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, + <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; + - | + #include + + sound { + compatible = "nvidia,tegra-audio-max98090-venice2", + "nvidia,tegra-audio-max98090"; + nvidia,model = "NVIDIA Tegra Venice2"; + + nvidia,audio-routing = + "Headphones", "HPR", + "Headphones", "HPL", + "Speakers", "SPKR", + "Speakers", "SPKL", + "Mic Jack", "MICBIAS", + "IN34", "Mic Jack"; + + nvidia,i2s-controller = <&tegra_i2s1>; + nvidia,audio-codec = <&acodec>; + + clocks = <&tegra_car TEGRA124_CLK_PLL_A>, + <&tegra_car TEGRA124_CLK_PLL_A_OUT0>, + <&tegra_car TEGRA124_CLK_EXTERN1>; + clock-names = "pll_a", "pll_a_out0", "mclk"; + }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml deleted file mode 100644 index 69af2022d0fabf..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-cpcap.yaml +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-cpcap.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with CPCAP CODEC - -maintainers: - - Svyatoslav Ryhel - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^motorola,tegra-audio-cpcap(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-cpcap - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: - A list of the connections between audio components. Each entry is a - pair of strings, the first being the connection's sink, the second - being the connection's source. Valid names for sources and sinks are - the pins (documented in the binding document), and the jacks on the - board. - minItems: 2 - items: - enum: - # Board Connectors - - Speakers - - Int Spk - - Earpiece - - Int Mic - - Headset Mic - - Internal Mic 1 - - Internal Mic 2 - - Headphone - - Headphones - - Headphone Jack - - Mic Jack - - # CODEC Pins - - MICR - - HSMIC - - EMUMIC - - MICL - - EXTR - - EXTL - - EP - - SPKR - - SPKL - - LINER - - LINEL - - HSR - - HSL - - EMUR - - EMUL - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "motorola,tegra-audio-cpcap-olympus", - "nvidia,tegra-audio-cpcap"; - nvidia,model = "Motorola Atrix 4G (MB860) CPCAP"; - - nvidia,audio-routing = - "Headphones", "HSR", - "Headphones", "HSL", - "Int Spk", "SPKR", - "Int Spk", "SPKL", - "Earpiece", "EP", - "HSMIC", "Mic Jack", - "MICR", "Internal Mic 1", - "MICL", "Internal Mic 2"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&cpcap_audio>; - - clocks = <&tegra_car TEGRA20_CLK_PLL_A>, - <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA20_CLK_CDEV1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml deleted file mode 100644 index 4957645a8e0358..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max9808x.yaml +++ /dev/null @@ -1,95 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-max9808x.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with MAX9808x CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - oneOf: - - items: - - pattern: '^[a-z0-9]+,tegra-audio-max98088(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-max98088 - - items: - - pattern: '^[a-z0-9]+,tegra-audio-max98089(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-max98089 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Speakers - - Int Spk - - Headphone - - Headphones - - Headphone Jack - - Earpiece - - Headset Mic - - Mic Jack - - Int Mic - - Internal Mic 1 - - Internal Mic 2 - - # CODEC Pins - - HPL - - HPR - - SPKL - - SPKR - - RECL - - RECR - - INA1 - - INA2 - - INB1 - - INB2 - - MIC1 - - MIC2 - - MICBIAS - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "lg,tegra-audio-max98089-p895", - "nvidia,tegra-audio-max98089"; - nvidia,model = "LG Optimus Vu MAX98089"; - - nvidia,audio-routing = - "Headphone Jack", "HPL", - "Headphone Jack", "HPR", - "Int Spk", "SPKL", - "Int Spk", "SPKR", - "Earpiece", "RECL", - "Earpiece", "RECR", - "INA1", "Headset Mic", - "MIC1", "MICBIAS", - "MICBIAS", "Internal Mic 1", - "MIC2", "Internal Mic 2"; - - nvidia,i2s-controller = <&tegra_i2s0>; - nvidia,audio-codec = <&codec>; - - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml deleted file mode 100644 index 4d912458b18ba0..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-max98090.yaml +++ /dev/null @@ -1,97 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-max98090.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with MAX98090 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - oneOf: - - items: - - pattern: '^[a-z0-9]+,tegra-audio-max98090(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-max98090 - - items: - - enum: - - nvidia,tegra-audio-max98090-nyan-big - - nvidia,tegra-audio-max98090-nyan-blaze - - const: nvidia,tegra-audio-max98090-nyan - - const: nvidia,tegra-audio-max98090 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphones - - Speakers - - Mic Jack - - Int Mic - - # CODEC Pins - - MIC1 - - MIC2 - - DMICL - - DMICR - - IN1 - - IN2 - - IN3 - - IN4 - - IN5 - - IN6 - - IN12 - - IN34 - - IN56 - - HPL - - HPR - - SPKL - - SPKR - - RCVL - - RCVR - - MICBIAS - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - - sound { - compatible = "nvidia,tegra-audio-max98090-venice2", - "nvidia,tegra-audio-max98090"; - nvidia,model = "NVIDIA Tegra Venice2"; - - nvidia,audio-routing = - "Headphones", "HPR", - "Headphones", "HPL", - "Speakers", "SPKR", - "Speakers", "SPKL", - "Mic Jack", "MICBIAS", - "IN34", "Mic Jack"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&acodec>; - - clocks = <&tegra_car TEGRA124_CLK_PLL_A>, - <&tegra_car TEGRA124_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA124_CLK_EXTERN1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml deleted file mode 100644 index 0c8067c3b05618..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5631.yaml +++ /dev/null @@ -1,85 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5631.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with RT5631 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-rt5631(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-rt5631 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Int Spk - - Headphone Jack - - Mic Jack - - Int Mic - - # CODEC Pins - - MIC1 - - MIC2 - - AXIL - - AXIR - - MONOIN_RXN - - MONOIN_RXP - - DMIC - - MIC Bias1 - - MIC Bias2 - - MONO_IN - - AUXO1 - - AUXO2 - - SPOL - - SPOR - - HPOL - - HPOR - - MONO - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "asus,tegra-audio-rt5631-tf700t", - "nvidia,tegra-audio-rt5631"; - nvidia,model = "Asus Transformer Infinity TF700T RT5631"; - - nvidia,audio-routing = - "Headphone Jack", "HPOL", - "Headphone Jack", "HPOR", - "Int Spk", "SPOL", - "Int Spk", "SPOR", - "MIC1", "MIC Bias1", - "MIC Bias1", "Mic Jack", - "DMIC", "Int Mic"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&rt5631>; - - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml deleted file mode 100644 index 2638592435b218..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5640.yaml +++ /dev/null @@ -1,84 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5640.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with RT5639 or RT5640 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-rt56(39|40)(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-rt5640 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphones - - Speakers - - Mic Jack - - # CODEC Pins - - DMIC1 - - DMIC2 - - MICBIAS1 - - IN1P - - IN1R - - IN2P - - IN2R - - HPOL - - HPOR - - LOUTL - - LOUTR - - MONOP - - MONON - - SPOLP - - SPOLN - - SPORP - - SPORN - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-rt5640-dalmore", - "nvidia,tegra-audio-rt5640"; - nvidia,model = "NVIDIA Tegra Dalmore"; - - nvidia,audio-routing = - "Headphones", "HPOR", - "Headphones", "HPOL", - "Speakers", "SPORP", - "Speakers", "SPORN", - "Speakers", "SPOLP", - "Speakers", "SPOLN"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&rt5640>; - - nvidia,hp-det-gpios = <&gpio 143 0>; - - clocks = <&clk 216>, <&clk 217>, <&clk 120>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml deleted file mode 100644 index 09e1d0b18d276b..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-rt5677.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-rt5677.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with RT5677 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-rt5677(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-rt5677 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone - - Speaker - - Headset Mic - - Internal Mic 1 - - Internal Mic 2 - - # CODEC Pins - - IN1P - - IN1N - - IN2P - - IN2N - - MICBIAS1 - - DMIC1 - - DMIC2 - - DMIC3 - - DMIC4 - - DMIC L1 - - DMIC L2 - - DMIC L3 - - DMIC L4 - - DMIC R1 - - DMIC R2 - - DMIC R3 - - DMIC R4 - - LOUT1 - - LOUT2 - - LOUT3 - - PDM1L - - PDM1R - - PDM2L - - PDM2R - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-rt5677-ryu", - "nvidia,tegra-audio-rt5677"; - nvidia,model = "NVIDIA Tegra Ryu"; - - nvidia,audio-routing = - "Headphone", "LOUT2", - "Headphone", "LOUT1", - "Headset Mic", "MICBIAS1", - "IN1P", "Headset Mic", - "IN1N", "Headset Mic", - "DMIC L1", "Internal Mic 1", - "DMIC R1", "Internal Mic 1", - "DMIC L2", "Internal Mic 2", - "DMIC R2", "Internal Mic 2", - "Speaker", "PDM1L", - "Speaker", "PDM1R"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&rt5677>; - - nvidia,hp-det-gpios = <&gpio 143 0>; - - clocks = <&clk 216>, - <&clk 217>, - <&clk 121>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml deleted file mode 100644 index e5bc6a6ade2468..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-sgtl5000.yaml +++ /dev/null @@ -1,67 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-sgtl5000.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with SGTL5000 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-sgtl5000([-_][a-z0-9]+)+$' - - const: nvidia,tegra-audio-sgtl5000 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Line In Jack - - Mic Jack - - # CODEC Pins - - HP_OUT - - LINE_OUT - - LINE_IN - - MIC_IN - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - - sound { - compatible = "toradex,tegra-audio-sgtl5000-apalis_t30", - "nvidia,tegra-audio-sgtl5000"; - nvidia,model = "Toradex Apalis T30 SGTL5000"; - nvidia,audio-routing = - "Headphone Jack", "HP_OUT", - "LINE_IN", "Line In Jack", - "MIC_IN", "Mic Jack"; - nvidia,i2s-controller = <&tegra_i2s2>; - nvidia,audio-codec = <&codec>; - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA30_CLK_EXTERN1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml deleted file mode 100644 index 8c87cd166238fe..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-trimslice.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-trimslice.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with TrimSlice CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - const: nvidia,tegra-audio-trimslice - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-trimslice"; - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&codec>; - clocks = <&tegra_car 112>, <&tegra_car 113>, <&tegra_car 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml deleted file mode 100644 index 3323d6a438f5ce..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8753.yaml +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8753.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8753 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8753(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8753 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Mic Jack - - # CODEC Pins - - LOUT1 - - LOUT2 - - ROUT1 - - ROUT2 - - MONO1 - - MONO2 - - OUT3 - - OUT4 - - LINE1 - - LINE2 - - RXP - - RXN - - ACIN - - ACOP - - MIC1N - - MIC1 - - MIC2N - - MIC2 - - Mic Bias - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-wm8753-whistler", - "nvidia,tegra-audio-wm8753"; - nvidia,model = "tegra-wm8753-harmony"; - - nvidia,audio-routing = - "Headphone Jack", "LOUT1", - "Headphone Jack", "ROUT1"; - - nvidia,i2s-controller = <&i2s1>; - nvidia,audio-codec = <&wm8753>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml deleted file mode 100644 index 1be25ce4514b7a..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8903.yaml +++ /dev/null @@ -1,93 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8903.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8903 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - oneOf: - - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8903(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8903 - - items: - - pattern: ad,tegra-audio-plutux - - const: nvidia,tegra-audio-wm8903 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Int Spk - - Mic Jack - - Int Mic - - # CODEC Pins - - IN1L - - IN1R - - IN2L - - IN2R - - IN3L - - IN3R - - DMICDAT - - HPOUTL - - HPOUTR - - LINEOUTL - - LINEOUTR - - LOP - - LON - - ROP - - RON - - MICBIAS - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-wm8903-harmony", - "nvidia,tegra-audio-wm8903"; - nvidia,model = "tegra-wm8903-harmony"; - - nvidia,audio-routing = - "Headphone Jack", "HPOUTR", - "Headphone Jack", "HPOUTL", - "Int Spk", "ROP", - "Int Spk", "RON", - "Int Spk", "LOP", - "Int Spk", "LON", - "Mic Jack", "MICBIAS", - "IN1L", "Mic Jack"; - - nvidia,i2s-controller = <&i2s1>; - nvidia,audio-codec = <&wm8903>; - - nvidia,spkr-en-gpios = <&codec 2 0>; - nvidia,hp-det-gpios = <&gpio 178 0>; - nvidia,int-mic-en-gpios = <&gpio 184 0>; - nvidia,ext-mic-en-gpios = <&gpio 185 0>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml deleted file mode 100644 index 2c3bf5a02a34fa..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8962.yaml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8962.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8962 CODEC - -maintainers: - - Svyatoslav Ryhel - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8962(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8962 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: - A list of the connections between audio components. Each entry is a - pair of strings, the first being the connection's sink, the second - being the connection's source. Valid names for sources and sinks are - the pins (documented in the binding document), and the jacks on the - board. - minItems: 2 - items: - enum: - # Board Connectors - - Speakers - - Int Spk - - Earpiece - - Int Mic - - Headset Mic - - Internal Mic 1 - - Internal Mic 2 - - Headphone - - Headphones - - Headphone Jack - - Mic Jack - - # CODEC Pins - - IN1L - - IN1R - - IN2L - - IN2R - - IN3L - - IN3R - - IN4L - - IN4R - - DMICDAT - - HPOUTL - - HPOUTR - - SPKOUT - - SPKOUTL - - SPKOUTR - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - #include - sound { - compatible = "microsoft,tegra-audio-wm8962-surface-rt", - "nvidia,tegra-audio-wm8962"; - nvidia,model = "Microsoft Surface RT WM8962"; - - nvidia,audio-routing = - "Headphone Jack", "HPOUTR", - "Headphone Jack", "HPOUTL", - "Int Spk", "SPKOUTR", - "Int Spk", "SPKOUTL"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&wm8962>; - - clocks = <&tegra_car TEGRA30_CLK_PLL_A>, - <&tegra_car TEGRA30_CLK_PLL_A_OUT0>, - <&tegra_pmc TEGRA_PMC_CLK_OUT_1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml deleted file mode 100644 index d2c317f831ced1..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm8994.yaml +++ /dev/null @@ -1,110 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm8994.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM8994 CODEC - -maintainers: - - Svyatoslav Ryhel - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm8994(-[a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm8994 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone Jack - - Int Spk - - Earpiece - - Line Out - - Mic Jack - - Int Mic - - Internal Mic 1 - - Internal Mic 2 - - FM Radio - - # CODEC Pins - - SPKOUTLP - - SPKOUTLN - - SPKOUTRP - - SPKOUTRN - - HPOUT1L - - HPOUT1R - - HPOUT2P - - HPOUT2N - - LINEOUT1P - - LINEOUT1N - - LINEOUT2P - - LINEOUT2N - - IN1LN - - IN1LP - - IN2LN - - IN2LP:VXRN - - IN1RN - - IN1RP - - IN2RN - - IN2RP:VXRP - - DMIC1DAT - - DMIC2DAT - - MICBIAS1 - - MICBIAS2 - -required: - - nvidia,i2s-controller - -unevaluatedProperties: false - -examples: - - | - #include - #include - - sound { - compatible = "lg,tegra-audio-wm8994-star", - "nvidia,tegra-audio-wm8994"; - nvidia,model = "LG Optimus 2X WM8994"; - - nvidia,audio-routing = - "Headphone Jack", "HPOUT1L", - "Headphone Jack", "HPOUT1R", - "Int Spk", "SPKOUTLN", - "Int Spk", "SPKOUTLP", - "Earpiece", "HPOUT2N", - "Earpiece", "HPOUT2P", - "IN2RN", "MICBIAS2", - "IN2RN", "Mic Jack", - "IN1LN", "MICBIAS1", - "IN1LN", "Internal Mic 1", - "IN1RN", "MICBIAS1", - "IN1RN", "Internal Mic 2"; - - nvidia,i2s-controller = <&tegra_i2s1>; - nvidia,audio-codec = <&wm8994>; - - nvidia,hp-det-gpios = <&gpio 51 GPIO_ACTIVE_HIGH>; - nvidia,mic-det-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>; - nvidia,ext-mic-en-gpios = <&gpio 57 GPIO_ACTIVE_HIGH>; - nvidia,coupled-mic-hp-det; - - clocks = <&tegra_car TEGRA20_CLK_PLL_A>, - <&tegra_car TEGRA20_CLK_PLL_A_OUT0>, - <&tegra_car TEGRA20_CLK_CDEV1>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; diff --git a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml b/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml deleted file mode 100644 index 397306b8800d48..00000000000000 --- a/Documentation/devicetree/bindings/sound/nvidia,tegra-audio-wm9712.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -%YAML 1.2 ---- -$id: http://devicetree.org/schemas/sound/nvidia,tegra-audio-wm9712.yaml# -$schema: http://devicetree.org/meta-schemas/core.yaml# - -title: NVIDIA Tegra audio complex with WM9712 CODEC - -maintainers: - - Jon Hunter - - Thierry Reding - -allOf: - - $ref: nvidia,tegra-audio-common.yaml# - -properties: - compatible: - items: - - pattern: '^[a-z0-9]+,tegra-audio-wm9712([-_][a-z0-9]+)+$' - - const: nvidia,tegra-audio-wm9712 - - nvidia,audio-routing: - $ref: /schemas/types.yaml#/definitions/non-unique-string-array - description: | - A list of the connections between audio components. - Each entry is a pair of strings, the first being the connection's sink, - the second being the connection's source. Valid names for sources and - sinks are the pins (documented in the binding document), - and the jacks on the board. - minItems: 2 - items: - enum: - # Board Connectors - - Headphone - - LineIn - - Mic - - # CODEC Pins - - MONOOUT - - HPOUTL - - HPOUTR - - LOUT2 - - ROUT2 - - OUT3 - - LINEINL - - LINEINR - - PHONE - - PCBEEP - - MIC1 - - MIC2 - - Mic Bias - -required: - - nvidia,ac97-controller - -unevaluatedProperties: false - -examples: - - | - sound { - compatible = "nvidia,tegra-audio-wm9712-colibri_t20", - "nvidia,tegra-audio-wm9712"; - nvidia,model = "Toradex Colibri T20"; - - nvidia,audio-routing = - "Headphone", "HPOUTL", - "Headphone", "HPOUTR", - "LineIn", "LINEINL", - "LineIn", "LINEINR", - "Mic", "MIC1"; - - nvidia,ac97-controller = <&ac97>; - - clocks = <&clk 112>, <&clk 113>, <&clk 93>; - clock-names = "pll_a", "pll_a_out0", "mclk"; - }; From 9d12f5640132759e73f05454e4b8e19f5ff16765 Mon Sep 17 00:00:00 2001 From: Aryan Sinha Date: Wed, 4 Nov 2026 20:31:54 +0530 Subject: [PATCH 168/186] ASoC: sn624x: use dev_err_probe() for the regmap failure dev_err_probe() prints the symbolic errno and stays quiet on -EPROBE_DEFER. The sn624x prefix goes too, dev_err_probe() already prints the device name.. Signed-off-by: Aryan Sinha Acked-by: Qianghua Wang Link: https://patch.msgid.link/20261104150155.33157-1-sinha.aryan03@gmail.com Signed-off-by: Mark Brown --- sound/soc/codecs/sn624x-sdca-sdw.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/sn624x-sdca-sdw.c b/sound/soc/codecs/sn624x-sdca-sdw.c index c9e690482e8ec9..1a83f0a11afe41 100644 --- a/sound/soc/codecs/sn624x-sdca-sdw.c +++ b/sound/soc/codecs/sn624x-sdca-sdw.c @@ -1736,11 +1736,9 @@ static int sn624x_sdca_sdw_probe(struct sdw_slave *slave, regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave, &sn624x_sdca_regmap, &sn624x_sdca_mbq_cfg); - if (IS_ERR(regmap)) { - dev_err(&slave->dev, "sn624x: devm_regmap_init_sdw_mbq_cfg failed (%ld)\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } + if (IS_ERR(regmap)) + return dev_err_probe(&slave->dev, PTR_ERR(regmap), + "devm_regmap_init_sdw_mbq_cfg failed\n"); ret = sn624x_sdca_init(&slave->dev, regmap, slave); if (ret < 0) From 1f18d4babdf42a5354247e2c6f367163d269c698 Mon Sep 17 00:00:00 2001 From: Shengjiu Wang Date: Wed, 28 Oct 2026 10:59:21 +0800 Subject: [PATCH 169/186] ASoC: fsl_asrc_dma: fix error handling for DMA channel retrieval The original check used `if (!tmp_chan)`, which only catches NULL pointers. However `asrc->get_dma_channel()` returns an error pointer. The NULL check will always succeed, so replace it with `IS_ERR(tmp_chan)` to correctly detect errors. Fixes: b4136c0d69ea ("ASoC: fsl_asrc: check the second front-end DMA channel") Signed-off-by: Shengjiu Wang Reviewed-by: Chancel Liu Link: https://patch.msgid.link/20261028025921.4155551-1-shengjiu.wang@oss.nxp.com Signed-off-by: Mark Brown --- sound/soc/fsl/fsl_asrc_dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/fsl/fsl_asrc_dma.c b/sound/soc/fsl/fsl_asrc_dma.c index cd3bf6340b6506..e5b7b283c16b7b 100644 --- a/sound/soc/fsl/fsl_asrc_dma.c +++ b/sound/soc/fsl/fsl_asrc_dma.c @@ -250,7 +250,7 @@ static int fsl_asrc_dma_hw_params(struct snd_soc_component *component, /* Get DMA request of Front-End */ tmp_chan = asrc->get_dma_channel(pair, dir); - if (!tmp_chan) { + if (IS_ERR(tmp_chan)) { dma_release_channel(pair->dma_chan[!dir]); pair->dma_chan[!dir] = NULL; return -EINVAL; From 9913e4bc5a2ff8a58abad61b05ff619a158e63ba Mon Sep 17 00:00:00 2001 From: Wentao Liang Date: Wed, 4 Nov 2026 02:40:52 +0000 Subject: [PATCH 170/186] ASoC: samsung: snow: release the I2S bus clock if registration fails snow_probe() takes a reference on the I2S bus clock with of_clk_get_by_name(), but the only place that drops it is snow_remove(), which is not called when the probe fails. An error from devm_snd_soc_register_card() therefore leaks that reference, and since a deferred probe runs the probe again, every retry leaks another one. Drop the clock reference before returning the registration error. Fixes: aff8d2bed9e5 ("ASoC: samsung: Add HDMI audio support for Snow") Cc: stable@vger.kernel.org Assisted-by: DeepSeek-V4.1-Flash and OpenAI Codex Signed-off-by: Wentao Liang Link: https://patch.msgid.link/20261104024052.1508720-1-vulab@iscas.ac.cn Signed-off-by: Mark Brown --- sound/soc/samsung/snow.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sound/soc/samsung/snow.c b/sound/soc/samsung/snow.c index 66ef49dff1ba37..ad99c153e9f8c7 100644 --- a/sound/soc/samsung/snow.c +++ b/sound/soc/samsung/snow.c @@ -211,9 +211,11 @@ static int snow_probe(struct platform_device *pdev) snd_soc_card_set_drvdata(card, priv); ret = devm_snd_soc_register_card(dev, card); - if (ret) + if (ret) { + clk_put(priv->clk_i2s_bus); return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card failed\n"); + } return 0; } From 2225ef4e51c4ddfaff1b2e227607ed04413bd0c6 Mon Sep 17 00:00:00 2001 From: Yuehong Wu Date: Thu, 5 Nov 2026 19:05:00 +0800 Subject: [PATCH 171/186] ASoC: tlv320adcx140: zero-initialize the GPI configuration array adcx140_codec_probe() accepts any "ti,gpi-config" array with 1 to 4 entries (the DT binding has minItems: 1, maxItems: 4) but then always reads all four elements to build the GPI_CFG0 and GPI_CFG1 register values. With fewer than four entries the remaining elements of the on-stack gpi_inputs[] array are uninitialized and the garbage is shifted and written into the codec's GPI configuration registers. The binding documents a default of <0 0 0 0>, i.e. unspecified GPI pins are disabled, so zero-initialize the array and keep accepting short arrays. Found by code review with the help of an AI coding assistant. I do not have TLV320ADCX140 hardware, so this was only compile-tested (CONFIG_SND_SOC_TLV320ADCX140=m). Fixes: 3c35e79cead3 ("ASoC: tlv320adcx140: Add support for configuring GPI pins") Signed-off-by: Yuehong Wu Link: https://patch.msgid.link/20261105-tlv320adcx140-gpi-init-1-yuehongwu@cmss.chinamobile.com Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320adcx140.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/tlv320adcx140.c b/sound/soc/codecs/tlv320adcx140.c index c29584d77649b6..511d691923f2da 100644 --- a/sound/soc/codecs/tlv320adcx140.c +++ b/sound/soc/codecs/tlv320adcx140.c @@ -1018,7 +1018,7 @@ static int adcx140_codec_probe(struct snd_soc_component *component) u32 pdm_edges[ADCX140_NUM_PDM_EDGES]; u32 pdm_edge_val = 0; int gpi_count; - u32 gpi_inputs[ADCX140_NUM_GPI_PINS]; + u32 gpi_inputs[ADCX140_NUM_GPI_PINS] = {}; u32 gpi_input_val = 0; int i; int ret; From 94dd534d2eb96ceea4e36f3d3f0651a1abaa9b99 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 7 Nov 2026 15:09:48 +0100 Subject: [PATCH 172/186] ALSA: info: Use scnprintf() in snd_info_card_create() While the current code works correctly, replace the unbounded sprintf() with scnprintf() to bound the formatted string to the str buffer. Signed-off-by: Thorsten Blum Link: https://patch.msgid.link/20261107140949.350482-2-blum@kernel.org Signed-off-by: Takashi Iwai --- sound/core/info.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/core/info.c b/sound/core/info.c index 023b4736aa6deb..2c2a80a7d0d2ba 100644 --- a/sound/core/info.c +++ b/sound/core/info.c @@ -484,7 +484,7 @@ int snd_info_card_create(struct snd_card *card) if (snd_BUG_ON(!card)) return -ENXIO; - sprintf(str, "card%i", card->number); + scnprintf(str, sizeof(str), "card%i", card->number); entry = create_subdir(card->module, str); if (!entry) return -ENOMEM; From 4267121a564eb6a492150b9e19be3e91b042c578 Mon Sep 17 00:00:00 2001 From: Jude Gyimah Date: Wed, 4 Nov 2026 14:53:00 +0100 Subject: [PATCH 173/186] ASoC: sunxi: sun50i-dmic: select REGMAP_MMIO sun50i-dmic calls __devm_regmap_init_mmio_clk, which drivers/base/regmap/regmap-mmio.c provides under REGMAP_MMIO. sun50i-dmic.c:(.text+0x142bce): undefined reference to `__devm_regmap_init_mmio_clk' The driver can be built into vmlinux while REGMAP_MMIO is a module, in which case the symbol is not there at link time. Select rather than depend on: REGMAP_MMIO has no prompt of its own and is only ever enabled by a select, so a depends on would leave SND_SUN50I_DMIC unselectable. Reproduced on v7.3, x86_64, with CONFIG_SND_SUN50I_DMIC=y and CONFIG_REGMAP_MMIO=m. Found with kreach, a static check that compares what Kconfig allows against the symbols the compiled objects actually reference. Fixes: 9fc2c8ed923d ("ASoC: sunxi: Add Allwinner H6 Digital MIC driver") Signed-off-by: Jude Gyimah Link: https://patch.msgid.link/20261104135300.3723057-1-jude.gyimah87@gmail.com Signed-off-by: Mark Brown --- sound/soc/sunxi/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig index 205f422d9ded19..c8a626396e9a49 100644 --- a/sound/soc/sunxi/Kconfig +++ b/sound/soc/sunxi/Kconfig @@ -58,6 +58,7 @@ config SND_SUN4I_SPDIF config SND_SUN50I_DMIC tristate "Allwinner H6 DMIC Support" + select REGMAP_MMIO select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M to add support for the DMIC audio block in the Allwinner From c29a99ab4b11c7858cef35c1aad1464a6e3d15b7 Mon Sep 17 00:00:00 2001 From: Jude Gyimah Date: Wed, 4 Nov 2026 14:49:58 +0100 Subject: [PATCH 174/186] ASoC: fsl_aud2htx: select missing dependencies This driver is short two selects, not one: its configuration fails to link on a symbol from each. fsl_aud2htx calls __devm_regmap_init_mmio_clk, which drivers/base/regmap/regmap-mmio.c provides under REGMAP_MMIO. fsl_aud2htx calls devm_snd_dmaengine_pcm_register, which sound/soc/soc-generic-dmaengine-pcm.c provides under SND_SOC_GENERIC_DMAENGINE_PCM. fsl_aud2htx.c:(.text+0x30c): undefined reference to `__devm_regmap_init_mmio_clk' The driver can be built into vmlinux while REGMAP_MMIO is a module, in which case the symbol is not there at link time. Select rather than depend on: REGMAP_MMIO has no prompt of its own and is only ever enabled by a select, so a depends on would leave SND_SOC_FSL_AUD2HTX unselectable. Reproduced on v7.3, x86_64, with CONFIG_SND_SOC_FSL_AUD2HTX=y and CONFIG_REGMAP_MMIO=m. Found with kreach, a static check that compares what Kconfig allows against the symbols the compiled objects actually reference. Fixes: 8a24c834c053 ("ASoC: fsl_aud2htx: Add aud2htx module driver") Signed-off-by: Jude Gyimah Link: https://patch.msgid.link/20261104135000.3723017-2-jude.gyimah87@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 101bc952aa1446..af5deb31dc0830 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -116,6 +116,8 @@ config SND_SOC_FSL_XCVR config SND_SOC_FSL_AUD2HTX tristate "AUDIO TO HDMI TX module support" depends on ARCH_MXC || COMPILE_TEST + select REGMAP_MMIO + select SND_SOC_GENERIC_DMAENGINE_PCM select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n help Say Y if you want to add AUDIO TO HDMI TX support for NXP. From b5c35242c515b633b22d2af2e74165c7960c560c Mon Sep 17 00:00:00 2001 From: Jude Gyimah Date: Wed, 4 Nov 2026 14:49:59 +0100 Subject: [PATCH 175/186] ASoC: fsl: lpc3xxx-i2s: select REGMAP_MMIO lpc3xxx-i2s calls __devm_regmap_init_mmio_clk, which drivers/base/regmap/regmap-mmio.c provides under REGMAP_MMIO. lpc3xxx-i2s.c:320:(.text+0x13b8f6): undefined reference to `__devm_regmap_init_mmio_clk' The driver can be built into vmlinux while REGMAP_MMIO is a module, in which case the symbol is not there at link time. Select rather than depend on: REGMAP_MMIO has no prompt of its own and is only ever enabled by a select, so a depends on would leave SND_SOC_FSL_LPC3XXX unselectable. Reproduced on v7.3, x86_64, with CONFIG_SND_SOC_FSL_LPC3XXX=y and CONFIG_REGMAP_MMIO=m. Found with kreach, a static check that compares what Kconfig allows against the symbols the compiled objects actually reference. Fixes: 0959de657a10 ("ASoC: fsl: Add i2s and pcm drivers for LPC32xx CPUs") Signed-off-by: Jude Gyimah Link: https://patch.msgid.link/20261104135000.3723017-3-jude.gyimah87@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index af5deb31dc0830..f1783ee8f2966f 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -140,6 +140,7 @@ config SND_SOC_FSL_RPMSG config SND_SOC_FSL_LPC3XXX tristate "SoC Audio for NXP LPC32XX CPUs" depends on ARCH_LPC32XX || COMPILE_TEST + select REGMAP_MMIO select SND_SOC_GENERIC_DMAENGINE_PCM help Say Y or M if you want to add support for the LPC3XXX I2S interface. From 99c9016f6bbd5c4811faa354876dcf79c28691e4 Mon Sep 17 00:00:00 2001 From: Jude Gyimah Date: Wed, 4 Nov 2026 14:50:00 +0100 Subject: [PATCH 176/186] ASoC: fsl_xcvr: select BITREVERSE SND_SOC_FSL_SPDIF, a few stanzas up in the same file, already selects BITREVERSE for the same reason. SND_SOC_FSL_XCVR is missing it. fsl_xcvr calls byte_rev_table, which lib/bitrev.c provides under BITREVERSE. fsl_xcvr.c:(.text+0x1634): undefined reference to `byte_rev_table' The driver can be built into vmlinux while GENERIC_BITREVERSE is a module, in which case the symbol is not there at link time. Select rather than depend on: BITREVERSE has no prompt of its own and is only ever enabled by a select, so a depends on would leave SND_SOC_FSL_XCVR unselectable. BITREVERSE is the symbol to select rather than GENERIC_BITREVERSE: it selects GENERIC_BITREVERSE only where the architecture has no native implementation, so this stays correct on the architectures that do. Reproduced on v7.3, x86_64, with CONFIG_SND_SOC_FSL_XCVR=y and CONFIG_GENERIC_BITREVERSE=m. Found with kreach, a static check that compares what Kconfig allows against the symbols the compiled objects actually reference. Fixes: 28564486866f ("ASoC: fsl_xcvr: Add XCVR ASoC CPU DAI driver") Signed-off-by: Jude Gyimah Link: https://patch.msgid.link/20261104135000.3723017-4-jude.gyimah87@gmail.com Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index f1783ee8f2966f..72feb0ca427a0a 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -104,6 +104,7 @@ config SND_SOC_FSL_EASRC config SND_SOC_FSL_XCVR tristate "NXP Audio Transceiver (XCVR) module support" + select BITREVERSE select REGMAP_MMIO select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n select SND_SOC_GENERIC_DMAENGINE_PCM From cbd74ec34c06b9051b2fa17a2c853cac79956f0b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Nov 2026 11:34:54 +0100 Subject: [PATCH 177/186] regmap: Add REG_FIELD_GENMASK() helper macro One time in regmap and already a few times over the whole kernel the users want to extract a mask from the regmap field data type. Provide a helper and convert regmap core to use it. Others may follow later on. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20261105103533.1426533-1-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 2 +- include/linux/regmap.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 83f1f59421356f..48e96bc9e7b657 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1215,7 +1215,7 @@ static void regmap_field_init(struct regmap_field *rm_field, rm_field->regmap = regmap; rm_field->reg = reg_field.reg; rm_field->shift = reg_field.lsb; - rm_field->mask = GENMASK(reg_field.msb, reg_field.lsb); + rm_field->mask = REG_FIELD_GENMASK(reg_field); WARN_ONCE(rm_field->mask == 0, "invalid empty mask defined\n"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a72f17ab6264ea..8f3034881f8a2e 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -10,6 +10,7 @@ * Author: Mark Brown */ +#include #include #include #include @@ -1477,6 +1478,8 @@ struct reg_field { .id_offset = _offset, \ } +#define REG_FIELD_GENMASK(_reg_field) GENMASK(_reg_field.msb, _reg_field.lsb) + struct regmap_field *regmap_field_alloc(struct regmap *regmap, struct reg_field reg_field); void regmap_field_free(struct regmap_field *field); From 42b56b71b92c0483ef19d936880d2e9fac0be369 Mon Sep 17 00:00:00 2001 From: Xinyu Wei Date: Sun, 8 Nov 2026 21:03:02 +0800 Subject: [PATCH 178/186] ALSA: usb-audio: name the interfaces of the Steinberg UR22C Configuration 2 of the Steinberg UR22C (0499:172f) exposes 6 playback and 8 capture channels over standard UAC2 descriptors with bInterfaceClass set to 0xFF. Because the interfaces are vendor-class, they fall into the Yamaha catch-all entry and QUIRK_AUTODETECT fails to set them up: create_auto_pcm_quirk() rejects 6-byte UAC2 format descriptors, and interface 0 (audiocontrol, 1 altsetting, 0 endpoints) is rejected by both auto-PCM and auto-MIDI paths, leaving interfaces 0-2 unbound with no PCM registered. Add a USB_DEVICE_VENDOR_SPEC composite entry before the Yamaha catch-all: - interface 0: QUIRK_DATA_STANDARD_MIXER - interfaces 1, 2: QUIRK_DATA_STANDARD_AUDIO (6-ch playback, 8-ch capture) - interfaces 3, 4: QUIRK_DATA_MIDI_YAMAHA (DIN MIDI and DSP mixer control; both carry Yamaha-style CS_INTERFACE jack descriptors with no CS_ENDPOINT descriptor) Configuration 1 uses bInterfaceClass 0x01 and is unaffected. The existing IMPLICIT_FB_FIXED_DEV(0x0499, 0x172f, 0x81, 2) entry in implicit.c applies to both configurations unchanged. Signed-off-by: Xinyu Wei Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261108130303.387288-2-xinyuwei1024@gmail.com --- sound/usb/quirks-table.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h index e85eeb536a0a2e..b6da5bcb15e400 100644 --- a/sound/usb/quirks-table.h +++ b/sound/usb/quirks-table.h @@ -455,6 +455,20 @@ YAMAHA_DEVICE(0x7000, "DTX"), YAMAHA_DEVICE(0x7010, "UB99"), #undef YAMAHA_DEVICE #undef YAMAHA_INTERFACE +/* Steinberg UR22C (configuration 2) */ +{ + USB_DEVICE_VENDOR_SPEC(0x0499, 0x172f), + QUIRK_DRIVER_INFO { + QUIRK_DATA_COMPOSITE { + { QUIRK_DATA_STANDARD_MIXER(0) }, + { QUIRK_DATA_STANDARD_AUDIO(1) }, + { QUIRK_DATA_STANDARD_AUDIO(2) }, + { QUIRK_DATA_MIDI_YAMAHA(3) }, + { QUIRK_DATA_MIDI_YAMAHA(4) }, + QUIRK_COMPOSITE_END + } + } +}, /* this catches most recent vendor-specific Yamaha devices */ { .match_flags = USB_DEVICE_ID_MATCH_VENDOR | From b606b54b2752beb94a62902430287ec8dec4a048 Mon Sep 17 00:00:00 2001 From: Xinyu Wei Date: Sun, 8 Nov 2026 21:03:03 +0800 Subject: [PATCH 179/186] ALSA: usb-audio: fix the format bits of the Steinberg UR22C In configuration 2 of the Steinberg UR22C (0499:172f), the capture AS_GENERAL descriptor reports bmFormats = 0xffffffff, causing the driver to advertise U8, S32_LE, FLOAT_LE, MU_LAW, A_LAW and SPECIAL even though the hardware only supports 32-bit PCM (S32_LE). Because playback is implicit-feedback synced to the capture endpoint (0x81 on interface 2), hw_rule_format() restricts playback to the active capture stream's format. If capture is opened first with a non-S32_LE format such as FLOAT_LE, the intersection with playback's S32_LE-only format mask is empty and playback fails to open with -EINVAL. Clamp bmFormats to UAC2 PCM (1) in parse_audio_format_i_type() when chip->usb_id is 0499:172f and format == 0xffffffff, matching the existing DS4201 workaround below it. Signed-off-by: Xinyu Wei Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/20261108130303.387288-3-xinyuwei1024@gmail.com --- sound/usb/format.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sound/usb/format.c b/sound/usb/format.c index 4830f9f93ad77e..cf0da3ca3eaddc 100644 --- a/sound/usb/format.c +++ b/sound/usb/format.c @@ -57,6 +57,11 @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, sample_width = fmt->bBitResolution; sample_bytes = fmt->bSubslotSize; + /* Steinberg UR22C workaround: config 2 capture sets 0xffffffff */ + if (chip->usb_id == USB_ID(0x0499, 0x172f) && + format == 0xffffffff) + format = 1; + if (format & UAC2_FORMAT_TYPE_I_RAW_DATA) { pcm_formats |= SNDRV_PCM_FMTBIT_SPECIAL; /* flag potentially raw DSD capable altsettings */ From ad40aa59a6ca9bf343346191f069f1153bb06aa3 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Mon, 9 Nov 2026 16:35:31 +0000 Subject: [PATCH 180/186] ALSA: control: Fix parsing of numeric fields parse_uint() passes the remaining input string to kstrtoull(). When the input contains comma-separated fields, the string contains both the numeric value and the following fields. For example, with: device=1,subdevice=0,name=Master kstrtoull() receives "1,subdevice=0,name=Master" and fails to parse the numeric value. Stop parsing at the next comma and pass only the numeric field to kstrtouint(). Make all parse_*() functions return the end of the current substring, leaving delimiter handling to set_led_id(). Temporarily terminate the numeric substring in parse_uint() before calling kstrtouint(). This allows numeric fields to be parsed correctly in comma-separated input. Fixes: a135dfb5de15 ("ALSA: led control - add sysfs kcontrol LED marking layer") Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261109163531.2639-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control_led.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sound/core/control_led.c b/sound/core/control_led.c index 3d55da3af2a056..f9a776d1fef510 100644 --- a/sound/core/control_led.c +++ b/sound/core/control_led.c @@ -451,22 +451,23 @@ static const struct attribute_group *snd_ctl_led_dev_attr_groups[] = { NULL, }; +/* return the end of substring pointing to either comma or NUL */ static char *find_eos(char *s) { while (*s && *s != ',') s++; - if (*s) - s++; return s; } static char *parse_uint(char *s, unsigned int *val) { - unsigned long long res; - if (kstrtoull(s, 10, &res)) - res = 0; - *val = res; - return find_eos(s); + char *end = find_eos(s); + char delimiter = *end; + *end = '\0'; + if (kstrtouint(s, 10, val)) + *val = 0; + *end = delimiter; + return end; } static char *parse_string(char *s, char *val, size_t val_size) @@ -481,6 +482,8 @@ static char *parse_string(char *s, char *val, size_t val_size) } s++; } + if (*s) + s++; } else { while (*s && *s != ',') { if (val_size > 1) { @@ -491,8 +494,6 @@ static char *parse_string(char *s, char *val, size_t val_size) } } *val = '\0'; - if (*s) - s++; return s; } From ba8dcbf4a664be81790675f6e3af7ca3ec76788e Mon Sep 17 00:00:00 2001 From: Alyandra Zahran Nadhir Date: Sun, 8 Nov 2026 21:47:48 +0900 Subject: [PATCH 181/186] ALSA: ump: Clear stale RPN selector on NRPN change The legacy-to-UMP converter keeps separate RPN and NRPN selector flags. When a stream changes selector type without sending data first, both flags remain set and fill_rpn() gives RPN priority, so subsequent NRPN data is emitted as RPN. Clear the previous selector flag after flushing any pending parameter data and before recording the newly selected parameter type. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Cc: stable@vger.kernel.org Signed-off-by: Alyandra Zahran Nadhir [HyeongJun An: capitalize the subject, add Fixes and stable Cc] Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/3ed25a99e07b8cd803f9bd6eb0f88ef00ae23f4b.1794141913.git.sammiee5311@gmail.com --- sound/core/ump_convert.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index ecbd894f4c83ae..fe7d3ee3c6b1f6 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -378,6 +378,7 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, switch (buf[1]) { case UMP_CC_RPN_MSB: ret = fill_rpn(cc, midi2, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_msb = buf[2]; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -385,6 +386,7 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, return ret; case UMP_CC_RPN_LSB: ret = fill_rpn(cc, midi2, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_lsb = buf[2]; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -392,11 +394,13 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, return ret; case UMP_CC_NRPN_MSB: ret = fill_rpn(cc, midi2, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_msb = buf[2]; return ret; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, midi2, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_lsb = buf[2]; return ret; From c1aa230682650ec3ebd28e271523dc0aaa149baa Mon Sep 17 00:00:00 2001 From: Alyandra Zahran Nadhir Date: Sun, 8 Nov 2026 21:47:49 +0900 Subject: [PATCH 182/186] ALSA: seq: ump: Clear stale selectors in sequencer conversion The sequencer MIDI 2.0 converter tracks RPN and NRPN selection flags separately. When the controller stream switches selector type, the previous flag remains set; fill_rpn() checks RPN first and can encode subsequent NRPN data as RPN. Flush pending parameter data with the old selector, then clear the opposite selector before recording new state in both CC and Control14 event paths. Fixes: e9e02819a98a ("ALSA: seq: Automatic conversion of UMP events") Cc: stable@vger.kernel.org Signed-off-by: Alyandra Zahran Nadhir [HyeongJun An: drop the KUnit test, fix the subject, add Fixes and stable Cc] Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/3c5cb0ef0023c4d2e835805498a14751a92feda0.1794141913.git.sammiee5311@gmail.com --- sound/core/seq/seq_ump_convert.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index ff4ee26adad1fc..bac37d4ef43cb0 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -847,6 +847,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, switch (index) { case UMP_CC_RPN_MSB: ret = fill_rpn(cc, data, channel, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_msb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -854,6 +855,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, break; case UMP_CC_RPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->nrpn_set = 0; cc->rpn_set = 1; cc->cc_rpn_lsb = val; if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f) @@ -861,11 +863,13 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, break; case UMP_CC_NRPN_MSB: ret = fill_rpn(cc, data, channel, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_msb = val; break; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_lsb = val; break; @@ -975,6 +979,7 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, case UMP_CC_RPN_MSB: case UMP_CC_RPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->nrpn_set = 0; cc->cc_rpn_msb = msb; cc->cc_rpn_lsb = lsb; cc->rpn_set = 1; @@ -984,6 +989,7 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, case UMP_CC_NRPN_MSB: case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); + cc->rpn_set = 0; cc->cc_nrpn_msb = msb; cc->nrpn_set = 1; cc->cc_nrpn_lsb = lsb; From d7a9cc2e5bdb4a2a3222bf5306ddabbc06a71f06 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 8 Nov 2026 21:47:50 +0900 Subject: [PATCH 183/186] ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion The MIDI 1.0 to UMP converter keeps the RPN/NRPN selection and the Data Entry values per channel. Since commit e6ce8a28c768 ("ALSA: ump: Transmit RPN/NRPN message at each MSB/LSB data reception"), a CC 98 to 101 also sends the pending data. The state handling has a few problems: - Data Entry after a Null RPN, or before any selection, stays pending. The next RPN/NRPN selection then sends it, with the number only half updated. - The fill_rpn() clears the selection after each send. A second Data Entry for the same RPN is not sent and ends up as above. - A Null NRPN is not handled, and its data goes to NRPN 127/127. Keep the selection after a send and clear only the data, and drop the data when nothing is selected. On a Null RPN or NRPN, clear the selection and the data but keep the 7f/7f numbers, as a MIDI 1.0 receiver does. Commit 50a6dd19dca9 ("ALSA: ump: Explicitly reset RPN with Null RPN") reset them to 0, so when the LSB was already 127, the second message of a Null selected RPN 0/127 again. This follows the UMP spec (M2-104-UM D.3.3), which holds the latest RPN/NRPN numbers and does not translate an RPN/NRPN Null. Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support") Fixes: e6ce8a28c768 ("ALSA: ump: Transmit RPN/NRPN message at each MSB/LSB data reception") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20261104121508.3972314-1-sammiee5311@gmail.com/ Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/22377a352b928cf3db750c32daf54250fb9477ff.1794141913.git.sammiee5311@gmail.com --- sound/core/ump_convert.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c index fe7d3ee3c6b1f6..2f19efdc31fc28 100644 --- a/sound/core/ump_convert.c +++ b/sound/core/ump_convert.c @@ -288,13 +288,22 @@ static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt, return 4; } +static void reset_rpn_data(struct ump_cvt_to_ump_bank *cc) +{ + cc->cc_data_msb = cc->cc_data_lsb = 0; + cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; +} + +/* + * Deselect on a Null RPN/NRPN, but keep the 7f/7f numbers as a MIDI 1.0 + * receiver does. Otherwise, when the LSB was already 127, the second + * message of a Null would select RPN or NRPN 0/127. + */ static void reset_rpn(struct ump_cvt_to_ump_bank *cc) { cc->rpn_set = 0; cc->nrpn_set = 0; - cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; - cc->cc_data_msb = cc->cc_data_lsb = 0; - cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; + reset_rpn_data(cc); } static int fill_rpn(struct ump_cvt_to_ump_bank *cc, @@ -316,13 +325,16 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, midi2->rpn.bank = cc->cc_nrpn_msb; midi2->rpn.index = cc->cc_nrpn_lsb; } else { + /* no RPN/NRPN selected (e.g. after a Null), drop the data */ + reset_rpn_data(cc); return 0; // skip } midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) | cc->cc_data_lsb); - reset_rpn(cc); + /* keep the RPN/NRPN selection for a following Data Entry */ + reset_rpn_data(cc); return 8; } @@ -397,12 +409,16 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt, cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_msb = buf[2]; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); return ret; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, midi2, true); cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_lsb = buf[2]; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); return ret; case UMP_CC_DATA: cc->cc_data_msb_set = 1; From 58394f47b4382bb89c023e3ffabda37b5edc5f67 Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Sun, 8 Nov 2026 21:47:51 +0900 Subject: [PATCH 184/186] ALSA: seq: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion The sequencer converts MIDI 1.0 CC and CONTROL14 events to MIDI 2.0 UMP with the same RPN/NRPN state handling as the UMP core helper. It has the same problems: - Data Entry without a selection is sent at the next RPN/NRPN selection, with the number only half updated. - A repeated Data Entry for the same RPN is not sent right away and ends up as above. - A Null NRPN is not handled. Apply the same fix as for the UMP core helper to both the CC and the CONTROL14 paths. Fixes: e9e02819a98a ("ALSA: seq: Automatic conversion of UMP events") Fixes: a4ff92ff0bdd ("ALSA: seq: ump: Transmit RPN/NRPN message at each MSB/LSB data reception") Cc: stable@vger.kernel.org Link: https://lore.kernel.org/all/20261104121508.3972314-1-sammiee5311@gmail.com/ Signed-off-by: HyeongJun An Signed-off-by: Takashi Iwai Link: https://patch.msgid.link/81e86fb562df29953314f754b1daace962e85566.1794141913.git.sammiee5311@gmail.com --- sound/core/seq/seq_ump_convert.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c index bac37d4ef43cb0..62bdef6dca58c9 100644 --- a/sound/core/seq/seq_ump_convert.c +++ b/sound/core/seq/seq_ump_convert.c @@ -790,13 +790,22 @@ static int paf_ev_to_ump_midi2(const struct snd_seq_event *event, return 1; } +static void reset_rpn_data(struct ump_cvt_to_ump_bank *cc) +{ + cc->cc_data_msb = cc->cc_data_lsb = 0; + cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; +} + +/* + * Deselect on a Null RPN/NRPN, but keep the 7f/7f numbers as a MIDI 1.0 + * receiver does. Otherwise, when the LSB was already 127, the second + * message of a Null would select RPN or NRPN 0/127. + */ static void reset_rpn(struct ump_cvt_to_ump_bank *cc) { cc->rpn_set = 0; cc->nrpn_set = 0; - cc->cc_rpn_msb = cc->cc_rpn_lsb = 0; - cc->cc_data_msb = cc->cc_data_lsb = 0; - cc->cc_data_msb_set = cc->cc_data_lsb_set = 0; + reset_rpn_data(cc); } /* set up the MIDI2 RPN/NRPN packet data from the parsed info */ @@ -820,6 +829,8 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, data->rpn.bank = cc->cc_nrpn_msb; data->rpn.index = cc->cc_nrpn_lsb; } else { + /* no RPN/NRPN selected (e.g. after a Null), drop the data */ + reset_rpn_data(cc); return 0; // skip } @@ -827,7 +838,8 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc, cc->cc_data_lsb); data->rpn.channel = channel; - reset_rpn(cc); + /* keep the RPN/NRPN selection for a following Data Entry */ + reset_rpn_data(cc); return 1; } @@ -866,12 +878,16 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event, cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_msb = val; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); break; case UMP_CC_NRPN_LSB: ret = fill_rpn(cc, data, channel, true); cc->rpn_set = 0; cc->nrpn_set = 1; cc->cc_nrpn_lsb = val; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); break; case UMP_CC_DATA: cc->cc_data_msb_set = 1; @@ -993,6 +1009,8 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event, cc->cc_nrpn_msb = msb; cc->nrpn_set = 1; cc->cc_nrpn_lsb = lsb; + if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f) + reset_rpn(cc); return ret; case UMP_CC_DATA: case UMP_CC_DATA_LSB: From c89ed856f3acbb0148aa0ee07cdbae98b4b65d8b Mon Sep 17 00:00:00 2001 From: HyeongJun An Date: Tue, 10 Nov 2026 10:22:13 +0900 Subject: [PATCH 185/186] selftests/alsa: Add a test for the sequencer UMP conversion of RPN/NRPN The sequencer converts MIDI 1.0 CC and CONTROL14 events to UMP for UMP clients. Add a test that sends such events from a legacy client to a UMP MIDI 2.0 client and checks the RPN/NRPN packets it receives, and one that checks that a UMP MIDI 1.0 client gets the CCs as they are. The cases cover switching between RPN and NRPN, Data Entry sent with nothing selected, repeated Data Entry, Null RPN and NRPN, and the CONTROL14 path. Some of them come from the KUnit test in Alyandra Zahran Nadhir's patch. The test skips when /dev/snd/seq can't be opened or the kernel has no UMP support in the sequencer. Link: https://lore.kernel.org/all/20261107111056.667-1-alyandra5b@gmail.com/ Signed-off-by: HyeongJun An Link: https://patch.msgid.link/20261110012213.740288-1-sammiee5311@gmail.com Signed-off-by: Takashi Iwai --- tools/testing/selftests/alsa/.gitignore | 1 + tools/testing/selftests/alsa/Makefile | 3 +- tools/testing/selftests/alsa/seq-ump-test.c | 452 ++++++++++++++++++++ 3 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/alsa/seq-ump-test.c diff --git a/tools/testing/selftests/alsa/.gitignore b/tools/testing/selftests/alsa/.gitignore index 7b0e1e9ebf1b1e..a0762c3c52e6e9 100644 --- a/tools/testing/selftests/alsa/.gitignore +++ b/tools/testing/selftests/alsa/.gitignore @@ -2,5 +2,6 @@ aloop-test global-timer mixer-test pcm-test +seq-ump-test test-pcmtest-driver utimer-test diff --git a/tools/testing/selftests/alsa/Makefile b/tools/testing/selftests/alsa/Makefile index afd64a679dc1f6..92de58287e7786 100644 --- a/tools/testing/selftests/alsa/Makefile +++ b/tools/testing/selftests/alsa/Makefile @@ -16,7 +16,8 @@ LDLIBS+=-lpthread OVERRIDE_TARGETS = 1 -TEST_GEN_PROGS := aloop-test mixer-test pcm-test test-pcmtest-driver utimer-test +TEST_GEN_PROGS := aloop-test mixer-test pcm-test seq-ump-test test-pcmtest-driver \ + utimer-test TEST_GEN_PROGS_EXTENDED := libatest.so global-timer diff --git a/tools/testing/selftests/alsa/seq-ump-test.c b/tools/testing/selftests/alsa/seq-ump-test.c new file mode 100644 index 00000000000000..d1eca920cfc8e1 --- /dev/null +++ b/tools/testing/selftests/alsa/seq-ump-test.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test the conversion of MIDI 1.0 sequencer events to UMP. + * + * A legacy client sends CC and CONTROL14 events to a UMP client, and the + * test checks the packets that the UMP client receives. The converter keeps + * its RPN/NRPN state per destination port and channel, so each test uses + * new clients. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "kselftest_harness.h" + +#define MAX_PACKETS 8 +#define CAP_IN (SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE) +#define CAP_OUT (SNDRV_SEQ_PORT_CAP_READ | SNDRV_SEQ_PORT_CAP_SUBS_READ) +#define READ_TIMEOUT_MS 100 + +/* first word of a MIDI 2.0 RPN/NRPN packet on group 0 */ +#define RPN(ch, bank, index) \ + (0x40200000u | ((ch) << 16) | ((bank) << 8) | (index)) +#define NRPN(ch, bank, index) \ + (0x40300000u | ((ch) << 16) | ((bank) << 8) | (index)) + +struct packet { + unsigned int w0; + unsigned int w1; +}; + +struct seq_client { + int fd; + int id; + int port; +}; + +/* the same scaling as the kernel's upscale_14_to_32bit() */ +static unsigned int data14(unsigned int msb, unsigned int lsb) +{ + unsigned int src = (msb << 7) | lsb; + unsigned int val = src << 18; + unsigned int repeat; + + if (src <= 0x2000) + return val; + repeat = src & 0x1fff; + return val | (repeat << 5) | (repeat >> 8); +} + +/* returns 0, or a negative errno with *skip set when UMP is unavailable */ +static int open_client(struct seq_client *c, int midi_version, + unsigned int cap, int *skip) +{ + struct snd_seq_client_info info; + struct snd_seq_port_info port; + int ver = SNDRV_SEQ_VERSION; + + c->fd = open("/dev/snd/seq", O_RDWR | O_NONBLOCK); + if (c->fd < 0) { + *skip = 1; + return -errno; + } + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_CLIENT_ID, &c->id) < 0) + return -errno; + + if (midi_version) { + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_USER_PVERSION, &ver) < 0) { + *skip = 1; + return -errno; + } + memset(&info, 0, sizeof(info)); + info.client = c->id; + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_GET_CLIENT_INFO, &info) < 0) + return -errno; + info.midi_version = midi_version; + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, &info) < 0) { + *skip = 1; + return -errno; + } + } + + memset(&port, 0, sizeof(port)); + port.addr.client = c->id; + strcpy(port.name, "seq-ump-test"); + port.capability = cap; + port.type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC | + SNDRV_SEQ_PORT_TYPE_APPLICATION; + if (ioctl(c->fd, SNDRV_SEQ_IOCTL_CREATE_PORT, &port) < 0) + return -errno; + c->port = port.addr.port; + return 0; +} + +static void close_client(struct seq_client *c) +{ + if (c->fd >= 0) + close(c->fd); + c->fd = -1; +} + +static int send_event(struct seq_client *src, struct seq_client *dst, + int type, int ch, int param, int value) +{ + struct snd_seq_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = type; + ev.flags = SNDRV_SEQ_TIME_STAMP_TICK | SNDRV_SEQ_EVENT_LENGTH_FIXED; + ev.queue = SNDRV_SEQ_QUEUE_DIRECT; + ev.source.client = src->id; + ev.source.port = src->port; + ev.dest.client = dst->id; + ev.dest.port = dst->port; + ev.data.control.channel = ch; + ev.data.control.param = param; + ev.data.control.value = value; + if (write(src->fd, &ev, sizeof(ev)) != sizeof(ev)) + return -errno; + return 0; +} + +/* + * Read what the UMP client received until it is quiet, and keep the + * packets of message type @mt. Returns the number of packets kept. + */ +static int read_packets(struct seq_client *dst, unsigned int mt, + struct packet *out) +{ + struct pollfd pfd = { .fd = dst->fd, .events = POLLIN }; + struct snd_seq_ump_event ev; + int n = 0; + + while (poll(&pfd, 1, READ_TIMEOUT_MS) > 0) { + if (read(dst->fd, &ev, sizeof(ev)) != sizeof(ev)) + break; + if (!(ev.flags & SNDRV_SEQ_EVENT_UMP) || ev.ump[0] >> 28 != mt) + continue; + if (n < MAX_PACKETS) { + out[n].w0 = ev.ump[0]; + out[n].w1 = ev.ump[1]; + } + n++; + } + return n; +} + +FIXTURE(seq_ump) { + struct seq_client src; + struct seq_client dst; +}; + +FIXTURE_SETUP(seq_ump) +{ + int skip = 0; + int err; + + self->src.fd = -1; + self->dst.fd = -1; + + err = open_client(&self->dst, SNDRV_SEQ_CLIENT_UMP_MIDI_2_0, + CAP_IN, &skip); + if (skip) + SKIP(return, "UMP sequencer client unavailable: %s", + strerror(-err)); + ASSERT_EQ(0, err); + + err = open_client(&self->src, 0, CAP_OUT, &skip); + ASSERT_EQ(0, err); +} + +FIXTURE_TEARDOWN(seq_ump) +{ + close_client(&self->src); + close_client(&self->dst); +} + +struct event { + int type; + int ch; + int param; + int value; +}; + +#define CC(ch, param, value) \ + { SNDRV_SEQ_EVENT_CONTROLLER, ch, param, value } +#define C14(ch, param, value) \ + { SNDRV_SEQ_EVENT_CONTROL14, ch, param, value } + +static int send_events(struct seq_client *src, struct seq_client *dst, + const struct event *ev, int n) +{ + int i, err; + + for (i = 0; i < n; i++) { + err = send_event(src, dst, ev[i].type, ev[i].ch, + ev[i].param, ev[i].value); + if (err) + return err; + } + return 0; +} + +#define SEND(...) do { \ + const struct event ev[] = { __VA_ARGS__ }; \ + \ + ASSERT_EQ(0, send_events(&self->src, &self->dst, ev, \ + ARRAY_SIZE(ev))); \ +} while (0) + +/* compare the RPN/NRPN packets received with the expected ones */ +#define EXPECT_PACKETS(...) do { \ + const struct packet want[] = { __VA_ARGS__ }; \ + struct packet got[MAX_PACKETS]; \ + int n = read_packets(&self->dst, 4, got); \ + int i; \ + \ + ASSERT_EQ((int)ARRAY_SIZE(want), n); \ + for (i = 0; i < n; i++) { \ + EXPECT_EQ(want[i].w0, got[i].w0); \ + EXPECT_EQ(want[i].w1, got[i].w1); \ + } \ +} while (0) + +#define EXPECT_NO_PACKETS() do { \ + struct packet got[MAX_PACKETS]; \ + \ + EXPECT_EQ(0, read_packets(&self->dst, 4, got)); \ +} while (0) + +/* RPN 0/1 with Data Entry */ +TEST_F(seq_ump, rpn) +{ + SEND(CC(0, 101, 0), CC(0, 100, 1), CC(0, 6, 0x40), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x40, 0) }); +} + +/* Data Entry after a Null RPN is not sent at the next selection */ +TEST_F(seq_ump, data_after_null_rpn) +{ + SEND(CC(0, 101, 0x7f), CC(0, 100, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 101, 0), CC(0, 100, 1)); + EXPECT_NO_PACKETS(); +} + +/* Data Entry before any selection is not sent at the next selection */ +TEST_F(seq_ump, data_before_selection) +{ + SEND(CC(0, 6, 0x40), CC(0, 38, 0), CC(0, 101, 0), CC(0, 100, 1)); + EXPECT_NO_PACKETS(); +} + +/* the same for an NRPN selected after a Null RPN */ +TEST_F(seq_ump, data_after_null_rpn_to_nrpn) +{ + SEND(CC(0, 101, 0x7f), CC(0, 100, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 99, 0), CC(0, 98, 5)); + EXPECT_NO_PACKETS(); +} + +/* a repeated Data Entry goes to the selected RPN */ +TEST_F(seq_ump, repeated_data) +{ + SEND(CC(0, 101, 0), CC(0, 100, 2), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0), CC(0, 101, 0), CC(0, 100, 1)); + EXPECT_PACKETS({ RPN(0, 0, 2), data14(0x40, 0) }, + { RPN(0, 0, 2), data14(0x10, 0) }); +} + +/* two values for one RPN, then another RPN */ +TEST_F(seq_ump, two_rpns) +{ + SEND(CC(0, 101, 0), CC(0, 100, 0), CC(0, 6, 2), CC(0, 38, 0)); + SEND(CC(0, 6, 12), CC(0, 38, 0)); + SEND(CC(0, 101, 0), CC(0, 100, 1), CC(0, 6, 0x40), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 0), data14(2, 0) }, + { RPN(0, 0, 0), data14(12, 0) }, + { RPN(0, 0, 1), data14(0x40, 0) }); +} + +/* the RPN number is held after a send, like the NRPN number */ +TEST_F(seq_ump, rpn_msb_held) +{ + SEND(CC(0, 101, 1), CC(0, 100, 1), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 100, 2), CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 1, 1), data14(0x40, 0) }, + { RPN(0, 1, 2), data14(0x10, 0) }); +} + +/* selecting an NRPN replaces an RPN selection, and the reverse */ +TEST_F(seq_ump, selector_switch) +{ + SEND(CC(0, 101, 0x11), CC(0, 100, 0x12), CC(0, 99, 0x21), + CC(0, 98, 0x22)); + SEND(CC(0, 6, 0x31), CC(0, 38, 0x32)); + SEND(CC(1, 99, 0x41), CC(1, 98, 0x42), CC(1, 101, 0x51), + CC(1, 100, 0x52)); + SEND(CC(1, 6, 0x61), CC(1, 38, 0x62)); + EXPECT_PACKETS({ NRPN(0, 0x21, 0x22), data14(0x31, 0x32) }, + { RPN(1, 0x51, 0x52), data14(0x61, 0x62) }); +} + +/* + * A pending Data Entry MSB is sent for the old selection when another + * one is made. Other channels keep their own selection. + */ +TEST_F(seq_ump, pending_flush_and_channels) +{ + SEND(CC(2, 101, 0x09), CC(2, 100, 0x08)); + SEND(CC(3, 99, 0x17), CC(3, 98, 0x18)); + SEND(CC(2, 6, 0x25), CC(2, 99, 0x29)); + SEND(CC(3, 6, 0x33), CC(3, 38, 0)); + EXPECT_PACKETS({ RPN(2, 0x09, 0x08), data14(0x25, 0) }, + { NRPN(3, 0x17, 0x18), data14(0x33, 0) }); +} + +/* a Null NRPN deselects, and its data is not sent */ +TEST_F(seq_ump, null_nrpn) +{ + SEND(CC(0, 99, 0x7f), CC(0, 98, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + EXPECT_NO_PACKETS(); +} + +/* a Null RPN also deselects an NRPN */ +TEST_F(seq_ump, null_rpn_after_nrpn) +{ + SEND(CC(0, 99, 1), CC(0, 98, 2), CC(0, 101, 0x7f), CC(0, 100, 0x7f)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_NO_PACKETS(); +} + +/* a Null NRPN does not touch the RPN number */ +TEST_F(seq_ump, null_nrpn_keeps_rpn_number) +{ + SEND(CC(0, 100, 1), CC(0, 99, 0x7f), CC(0, 98, 0x7f), CC(0, 101, 0)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x10, 0) }); +} + +/* a Null RPN sent around a Null NRPN still deselects */ +TEST_F(seq_ump, null_rpn_split_by_null_nrpn) +{ + SEND(CC(0, 100, 0x7f), CC(0, 99, 0x7f), CC(0, 98, 0x7f), + CC(0, 101, 0x7f)); + SEND(CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_NO_PACKETS(); +} + +/* a Null RPN after an RPN with LSB 127 does not select RPN 0/127 */ +TEST_F(seq_ump, null_rpn_after_lsb_127) +{ + SEND(CC(0, 101, 0), CC(0, 100, 0x7f), CC(0, 6, 0x40), CC(0, 38, 0)); + SEND(CC(0, 101, 0x7f), CC(0, 100, 0x7f), CC(0, 6, 0x10), CC(0, 38, 0)); + EXPECT_PACKETS({ RPN(0, 0, 0x7f), data14(0x40, 0) }); +} + +/* CONTROL14: RPN 0/1 with Data Entry */ +TEST_F(seq_ump, c14_rpn) +{ + SEND(C14(0, 101, 1), C14(0, 6, 0x40 << 7)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x40, 0) }); +} + +/* CONTROL14: Data Entry before any selection is not sent */ +TEST_F(seq_ump, c14_data_before_selection) +{ + SEND(C14(0, 6, 0x40 << 7), C14(0, 101, 1), C14(0, 101, 2)); + EXPECT_NO_PACKETS(); +} + +/* CONTROL14: a repeated Data Entry goes to the selected RPN */ +TEST_F(seq_ump, c14_repeated_data) +{ + SEND(C14(0, 101, 1), C14(0, 6, 0x40 << 7), C14(0, 6, 0x10 << 7)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x40, 0) }, + { RPN(0, 0, 1), data14(0x10, 0) }); +} + +/* CONTROL14: selecting an NRPN replaces an RPN selection, and the reverse */ +TEST_F(seq_ump, c14_selector_switch) +{ + SEND(C14(4, 101, (1 << 7) | 2), C14(4, 99, (3 << 7) | 4)); + SEND(C14(4, 6, (5 << 7) | 6)); + SEND(C14(5, 99, (7 << 7) | 8), C14(5, 101, (9 << 7) | 10)); + SEND(C14(5, 6, (11 << 7) | 12)); + EXPECT_PACKETS({ NRPN(4, 3, 4), data14(5, 6) }, + { RPN(5, 9, 10), data14(11, 12) }); +} + +/* CONTROL14: a pending Data Entry is sent for the old selection */ +TEST_F(seq_ump, c14_pending_flush) +{ + SEND(C14(6, 99, (2 << 7) | 3), CC(6, 6, 0x35), + C14(6, 101, (4 << 7) | 5)); + EXPECT_PACKETS({ NRPN(6, 2, 3), data14(0x35, 0) }); +} + +/* CONTROL14: a Null RPN deselects an NRPN, and so does a Null NRPN */ +TEST_F(seq_ump, c14_null) +{ + SEND(C14(0, 99, (3 << 7) | 4), C14(0, 101, 0x3fff)); + SEND(C14(0, 6, 0x10 << 7)); + SEND(C14(1, 101, 1), C14(1, 99, 0x3fff), C14(1, 6, 0x10 << 7)); + EXPECT_NO_PACKETS(); +} + +/* CONTROL14: a Null NRPN does not touch the RPN number */ +TEST_F(seq_ump, c14_null_nrpn_keeps_rpn_number) +{ + SEND(CC(0, 100, 1), C14(0, 99, 0x3fff), CC(0, 101, 0)); + SEND(C14(0, 6, 0x10 << 7)); + EXPECT_PACKETS({ RPN(0, 0, 1), data14(0x10, 0) }); +} + +/* a UMP MIDI 1.0 client gets the CCs as they are, RPN selection included */ +TEST(midi1_passthrough) +{ + struct seq_client src = { .fd = -1 }, dst = { .fd = -1 }; + struct packet got[MAX_PACKETS]; + int skip = 0; + int err; + + err = open_client(&dst, SNDRV_SEQ_CLIENT_UMP_MIDI_1_0, + CAP_IN, &skip); + if (skip) { + close_client(&dst); + SKIP(return, "UMP sequencer client unavailable: %s", + strerror(-err)); + } + ASSERT_EQ(0, err); + ASSERT_EQ(0, open_client(&src, 0, CAP_OUT, &skip)); + + ASSERT_EQ(0, send_event(&src, &dst, SNDRV_SEQ_EVENT_CONTROLLER, + 9, 0x40, 0x22)); + ASSERT_EQ(0, send_event(&src, &dst, SNDRV_SEQ_EVENT_CONTROLLER, + 3, 101, 0x12)); + ASSERT_EQ(0, send_event(&src, &dst, SNDRV_SEQ_EVENT_CONTROLLER, + 3, 99, 0x34)); + ASSERT_EQ(3, read_packets(&dst, 2, got)); + EXPECT_EQ(0x20b94022u, got[0].w0); + EXPECT_EQ(0x20b36512u, got[1].w0); + EXPECT_EQ(0x20b36334u, got[2].w0); + + close_client(&src); + close_client(&dst); +} + +TEST_HARNESS_MAIN From 291d8707fa439ed4ec72552e7770177f6b6514e4 Mon Sep 17 00:00:00 2001 From: Sreeraj S Kurup Date: Sun, 8 Nov 2026 14:12:19 +0000 Subject: [PATCH 186/186] ALSA: control: Validate element info in compat ioctl The compat ioctl code calls the control's info callback directly in get_ctl_type() to get the element type and count. Unlike the normal control info path, this code does not validate the information returned by the callback. The compat read and write handlers use the returned type and count to access fixed-size value buffers and calculate copy sizes. An invalid type or an element count larger than the supported limit can cause these handlers to access memory outside the expected buffer boundaries. Call snd_ctl_check_elem_info() after the info callback to validate the returned type and count before using them. This keeps the compat ioctl path from using invalid control metadata when accessing its value buffers. Signed-off-by: Sreeraj S Kurup Link: https://patch.msgid.link/20261108141219.7655-1-sreekuttan2156239@gmail.com Signed-off-by: Takashi Iwai --- sound/core/control_compat.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sound/core/control_compat.c b/sound/core/control_compat.c index 87f7ad5ab73911..cad00d4aa2048b 100644 --- a/sound/core/control_compat.c +++ b/sound/core/control_compat.c @@ -183,6 +183,10 @@ static int get_ctl_type(struct snd_card *card, struct snd_ctl_elem_id *id, info->id = *id; err = kctl->info(kctl, info); if (err >= 0) { + err = snd_ctl_check_elem_info(card, info); + if (err < 0) + return err; + err = info->type; *countp = info->count; }