ALSA: usb-audio: add Pioneer DJ DDJ-SZ support

The Pioneer DJ DDJ-SZ exposes its audio interface as USB vendor-specific
class (0xFF) rather than USB Audio Class, so it needs a quirks-table
entry like its sibling Pioneer devices (DJM-750, DJM-850, DJM-900NXS2,
DJM-450, DJM-V10) already have.

The device presents 10 channels of S24_3LE audio in both directions,
fixed at 44.1kHz, on interface 0 altsetting 1: playback on endpoint
0x01, capture on endpoint 0x82. The unit contains its own analog mixer,
and each playback channel pair feeds one of its physical channel strips:
0/1, 2/3, 4/5 and 6/7 feed strips 1-4 respectively, and 8/9 feed the
booth output. The master output is produced in analog by that mixer and
is not carried over USB at all. On the capture side, channels 8/9 are
the mic input; capture channels 0-7 are not yet mapped to specific
physical inputs.

Implicit feedback needs no quirk flag here: is_pioneer_implicit_fb() in
implicit.c already covers vendor 0x08e4 with a vendor-spec class
interface and two endpoints, and the driver duly reports endpoint 0x82
as the playback sync endpoint.

Activation reuses the existing pioneer_djm_set_format_quirk() used by
the DJM-750/850/900NXS2/450/V10 (SET_INTERFACE to altsetting 1, then a
UAC-shaped SET_CUR sample-rate control transfer) with this device's own
captured wIndex (0x0082). Unlike those devices, the DDJ-SZ additionally
needs a vendor "arm" sequence before its capture path produces real
audio -- without it, capture opens and runs with no USB/ALSA errors
but delivers silence (a hard zero on every channel) rather than any
error, so this is easy to miss. The arm sequence is six vendor control
transfers (bmRequestType=0x40, bRequest=3, varying wValue/wIndex, each
followed by a bmRequestType=0xc0, bRequest=0 status read), replicated
byte-for-byte from a USB capture of the official Windows driver.

All of the above -- endpoint numbers, format, channel mapping, and the
arm sequence bytes -- were determined by capturing and decoding real USB
traffic from the Windows driver (USBPcap + Wireshark) during
enumeration, playback, and mic recording, then verifying the format
hypothesis against actual de-interleaved payload data rather than
packet-size arithmetic alone. Both playback and capture have been
verified working with real audio, not just clean enumeration.

Signed-off-by: Hanh Kieu <hhkieu@gmail.com>
Link: https://patch.msgid.link/20260903231641.18536-1-hhkieu@gmail.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Hanh Kieu 2026-09-03 16:16:41 -07:00 committed by Takashi Iwai
parent a41da0bb08
commit eccc0dc233
2 changed files with 107 additions and 0 deletions

View File

@ -3731,6 +3731,53 @@ YAMAHA_DEVICE(0x7010, "UB99"),
}
}
},
{
/*
* Pioneer DJ DDJ-SZ
* 10 channels playback & 10 channels capture @ 44.1kHz S24LE
*/
USB_DEVICE_VENDOR_SPEC(0x08e4, 0x0191),
QUIRK_DRIVER_INFO {
QUIRK_DATA_COMPOSITE {
{
QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 10,
.iface = 0,
.altsetting = 1,
.altset_idx = 1,
.endpoint = 0x01,
.ep_attr = USB_ENDPOINT_XFER_ISOC|
USB_ENDPOINT_SYNC_ASYNC,
.rates = SNDRV_PCM_RATE_44100,
.rate_min = 44100,
.rate_max = 44100,
.nr_rates = 1,
.rate_table = (unsigned int[]) { 44100 }
}
},
{
QUIRK_DATA_AUDIOFORMAT(0) {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.channels = 10,
.iface = 0,
.altsetting = 1,
.altset_idx = 1,
.endpoint = 0x82,
.ep_idx = 1,
.ep_attr = USB_ENDPOINT_XFER_ISOC|
USB_ENDPOINT_SYNC_ASYNC,
.rates = SNDRV_PCM_RATE_44100,
.rate_min = 44100,
.rate_max = 44100,
.nr_rates = 1,
.rate_table = (unsigned int[]) { 44100 }
}
},
QUIRK_COMPOSITE_END
}
}
},
{
/*
* Pioneer DJ DJM-750MK2

View File

@ -1805,6 +1805,62 @@ static void set_format_emu_quirk(struct snd_usb_substream *subs,
subs->pkt_offset_adj = (emu_samplerate_id >= EMU_QUIRK_SR_176400HZ) ? 4 : 0;
}
/*
* The DDJ-SZ needs a vendor "arm" sequence before its capture path
* produces real audio; without it capture runs with no USB or ALSA error
* but delivers a hard zero on every channel. The sequence is replicated
* byte-for-byte from a USB capture of the Windows driver: each write is
* followed by a status read whose content is a fixed value regardless of
* what was written, but the read is replicated too, since it is unclear
* whether the device requires it to process the preceding write.
*
* This runs from snd_usb_set_format_quirk(), i.e. on every format setup
* rather than once per device. Re-arming is harmless in practice and
* keeps the device armed if it is reset behind our back.
*/
static void ddj_sz_arm_quirk(struct usb_device *dev)
{
static const struct {
u16 value;
u16 index;
u8 read_len;
} cmds[] = {
{ 0x0100, 0x8002, 6 },
{ 0x0200, 0x8002, 6 },
{ 0x0303, 0x8002, 6 },
{ 0x0403, 0x8002, 6 },
{ 0x050a, 0x8002, 6 },
{ 0x0000, 0x8003, 2 },
};
u8 buf[6];
unsigned int i;
int err;
for (i = 0; i < ARRAY_SIZE(cmds); i++) {
err = snd_usb_ctl_msg(dev, usb_sndctrlpipe(dev, 0), 3,
USB_DIR_OUT | USB_TYPE_VENDOR |
USB_RECIP_DEVICE,
cmds[i].value, cmds[i].index, NULL, 0);
if (err < 0)
goto err_out;
err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0,
USB_DIR_IN | USB_TYPE_VENDOR |
USB_RECIP_DEVICE,
0x0000, cmds[i].index, buf,
cmds[i].read_len);
if (err < 0)
goto err_out;
}
return;
err_out:
dev_warn(&dev->dev,
"DDJ-SZ: arm sequence step %u failed (%d), capture may be silent\n",
i, err);
}
static int pioneer_djm_set_format_quirk(struct snd_usb_substream *subs,
u16 windex)
{
@ -1986,6 +2042,10 @@ void snd_usb_set_format_quirk(struct snd_usb_substream *subs,
case USB_ID(0x08e4, 0x0163): /* Pioneer DJM-850 */
pioneer_djm_set_format_quirk(subs, 0x0086);
break;
case USB_ID(0x08e4, 0x0191): /* Pioneer DDJ-SZ */
ddj_sz_arm_quirk(subs->dev);
pioneer_djm_set_format_quirk(subs, 0x0082);
break;
case USB_ID(0x0dba, 0x5000):
mbox3_set_format_quirk(subs, fmt); /* Digidesign Mbox 3 */
break;