Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flesniak/074ab23bbc833663b782f44174eae6a4 to your computer and use it in GitHub Desktop.
Save flesniak/074ab23bbc833663b782f44174eae6a4 to your computer and use it in GitHub Desktop.
ALSA patches for DJM-900NXS2
From bfaf7cdff533e44e485520c91ebe8575ce6a17f1 Mon Sep 17 00:00:00 2001
From: Fabian Lesniak <fabian@lesniak-it.de>
Date: Thu, 26 Sep 2019 23:37:28 +0200
Subject: [PATCH 1/3] ALSA: usb-audio: Add quirk table for Pioneer DJM 900NXS2
---
sound/usb/quirks-table.h | 68 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/sound/usb/quirks-table.h b/sound/usb/quirks-table.h
index e918ce346027..dbffa044ec62 100644
--- a/sound/usb/quirks-table.h
+++ b/sound/usb/quirks-table.h
@@ -3534,5 +3534,73 @@ AU0828_DEVICE(0x2040, 0x7270, "Hauppauge", "HVR-950Q"),
}
}
},
+{
+ USB_DEVICE_VENDOR_SPEC(0x2b73, 0x000a),
+ .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) {
+ /* .vendor_name = "Pioneer DJ Corporation", */
+ /* .product_name = "DJM-900NXS2", */
+ .ifnum = QUIRK_ANY_INTERFACE,
+ .type = QUIRK_COMPOSITE,
+ .data = & (const struct snd_usb_audio_quirk[]) {
+ /* interface 0 is vendor specific audio streaming
+ * setting sample rate only works for endpoint 0x82,
+ * the workaround for that is in clock.c:set_sample_rate_v1 */
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 10,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x01,
+ .ep_attr = USB_ENDPOINT_SYNC_ASYNC |
+ USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 3,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 96000
+ }
+ }
+ },
+ {
+ .ifnum = 0,
+ .type = QUIRK_AUDIO_FIXED_ENDPOINT,
+ .data = & (const struct audioformat) {
+ .formats = SNDRV_PCM_FMTBIT_S24_3LE,
+ .channels = 12,
+ .iface = 0,
+ .altsetting = 1,
+ .altset_idx = 1,
+ .attributes = UAC_EP_CS_ATTR_SAMPLE_RATE,
+ .endpoint = 0x82,
+ .ep_attr = USB_ENDPOINT_SYNC_ASYNC |
+ USB_ENDPOINT_XFER_ISOC,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_96000,
+ .rate_min = 44100,
+ .rate_max = 96000,
+ .nr_rates = 3,
+ .rate_table = (unsigned int[]) {
+ 44100, 48000, 96000
+ }
+ }
+ },
+ /* interface 1 (audio control) has no endpoints */
+ /* interface 2 (MIDI) is standard compliant */
+ /* interface 3 (HID) is standard compliant */
+ {
+ .ifnum = -1
+ },
+ }
+ }
+},
#undef USB_DEVICE_VENDOR_SPEC
--
2.24.0
From 57401a832846173a90a7afe75171735ed912dc33 Mon Sep 17 00:00:00 2001
From: Fabian Lesniak <fabian@lesniak-it.de>
Date: Sun, 29 Sep 2019 21:06:13 +0200
Subject: [PATCH 2/3] ALSA: usb-audio: sample rate quirk for Pioneer DJM
900NXS2
This adds a workaround for setting the samplerate on Pioneer DJM 900NXS2
mixing consoles. The device has both playback and capture endpoints on
the same interface:
I: If#= 0 Alt= 1 #EPs= 2 Cls=ff(vend.) Sub=00 Prot=00 Driver=snd-usb-audio
E: Ad=01(O) Atr=05(Isoc) MxPS=1024 Ivl=125us
E: Ad=82(I) Atr=05(Isoc) MxPS=1024 Ivl=125us
The device accepts sample rate change commands for the capture endpoint 0x82
only. Both endpoints are then configured to that sample rate. Thus the
quirk redirects all sample rate changes to the capture endpoint.
Playback and recording works fine individually, but not concurrently.
The second open request will try to reconfigure the interface again and
fail due to ep->use_count > 0, because the playback streams also tries
to start the capture endpoint as sync source.
---
sound/usb/clock.c | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/sound/usb/clock.c b/sound/usb/clock.c
index 72e9bdf76115..e5f877597c92 100644
--- a/sound/usb/clock.c
+++ b/sound/usb/clock.c
@@ -409,6 +409,33 @@ int snd_usb_clock_find_source(struct snd_usb_audio *chip, int protocol,
}
}
+#define SND_PIONEER_DJM_REC_EP 0x82
+
+static int pioneer_djm_sample_rate_fixup(struct snd_usb_audio *chip,
+ struct usb_host_interface *alts,
+ int rate, int *ep)
+{
+ const unsigned int original_ep = *ep;
+ struct snd_usb_endpoint *other_ep;
+
+ // find the other EP and check if it is running.
+ // if yes, check if rate is equal then reroute/ignore request, otherwise return EBUSY
+ // else, reroute request
+ list_for_each_entry(other_ep, &chip->ep_list, list) {
+ if (other_ep->ep_num != original_ep &&
+ other_ep->iface == alts->desc.bInterfaceNumber &&
+ other_ep->altsetting == alts->desc.bAlternateSetting &&
+ (other_ep->use_count > 0 && rate != (other_ep->freqn * 125 + 62) >> 10))
+ return -EBUSY; // EBUSY aborts the configuration but also resets the device
+ }
+
+ usb_audio_dbg(chip,
+ "Rerouting sample rate change to rate %d from ep %d to ep %d\n",
+ rate, original_ep, SND_PIONEER_DJM_REC_EP);
+ *ep = SND_PIONEER_DJM_REC_EP;
+ return -EINVAL; // case 3
+}
+
static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
struct usb_host_interface *alts,
struct audioformat *fmt, int rate)
@@ -420,7 +447,14 @@ static int set_sample_rate_v1(struct snd_usb_audio *chip, int iface,
if (get_iface_desc(alts)->bNumEndpoints < 1)
return -EINVAL;
- ep = get_endpoint(alts, 0)->bEndpointAddress;
+ ep = fmt->endpoint;
+
+ /* for Pioneer DJM devices always use the outgoing endpoint for sample rate */
+ if (chip->usb_id == USB_ID(0x2b73, 0x000a)) {
+ err = pioneer_djm_sample_rate_fixup(chip, alts, rate, &ep);
+ if (err != -EINVAL)
+ return err;
+ }
/* if endpoint doesn't have sampling rate control, bail out */
if (!(fmt->attributes & UAC_EP_CS_ATTR_SAMPLE_RATE))
--
2.24.0
From eb3381699dab62b3e1d6cf663144d712a282e0d7 Mon Sep 17 00:00:00 2001
From: Fabian Lesniak <fabian@lesniak-it.de>
Date: Sun, 29 Sep 2019 22:20:36 +0200
Subject: [PATCH 3/3] ALSA: usb-audio: mixer quirks for Pioneer DJM 900NXS2
This device allows to select the source of each of the five stereo pairs
on the capture interface. It also allows to set the capture gain in from
-19 to -5 dB. The TLV db range is declared but not being parsed by alsamixer.
---
sound/usb/mixer_quirks.c | 281 +++++++++++++++++++++++++++++++++++++++
1 file changed, 281 insertions(+)
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 199fa157a411..2c95aeca9831 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -1805,6 +1805,284 @@ static int dell_dock_mixer_init(struct usb_mixer_interface *mixer)
return 0;
}
+/* Pioneer DJM-900NXS2 controls */
+
+#define SND_PIONEER_DJM_INPUTSEL_READ 0x8002
+#define SND_PIONEER_DJM_SET_SOURCE 0x8002
+#define SND_PIONEER_DJM_SET_GAIN 0x8003
+
+static int snd_pioneer_djm_inputsel_read(struct snd_kcontrol *kcontrol,
+ u8 input_status[])
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ struct snd_usb_audio *chip = list->mixer->chip;
+ struct usb_device *dev = chip->dev;
+ int err, i;
+ u8 buf[5];
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return err;
+
+ err = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), 0,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0, SND_PIONEER_DJM_INPUTSEL_READ,
+ buf, sizeof(buf));
+ if (err < 0)
+ dev_err(&dev->dev,
+ "unable to read output status (ret = %d)", err);
+ else
+ for (i=0; i<4; i++)
+ input_status[i] = buf[i+1];
+
+ snd_usb_unlock_shutdown(chip);
+ return err;
+}
+
+static const char *const snd_pioneer_djm_inputsel_types[] = {
+ "", "Line", "Digital", "Phono", "", "", "USB A", "USB B", "Return Aux"
+};
+
+static int snd_pioneer_djm_inputsel_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ return snd_ctl_enum_info(uinfo, 1,
+ ARRAY_SIZE(snd_pioneer_djm_inputsel_types),
+ snd_pioneer_djm_inputsel_types);
+ return 0;
+}
+
+static int snd_pioneer_djm_inputsel_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ u8 input_status[4];
+ u8 idx = ucontrol->id.index-1;
+ int err;
+
+ err = snd_pioneer_djm_inputsel_read(kcontrol, input_status);
+ if (err < 0)
+ return err;
+
+ if (idx < 5 &&
+ input_status[idx] < ARRAY_SIZE(snd_pioneer_djm_inputsel_types))
+ ucontrol->value.enumerated.item[0] = input_status[idx];
+ else
+ ucontrol->value.enumerated.item[0] = 0;
+
+ return 0;
+}
+
+static const u8 snd_pioneer_djm_source_values[] = {
+ 0x00, 0x02, 0x03, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x11, 0x12, 0x13, 0x14
+};
+#define SND_PIONEER_DJM_SOURCE(ch,val) ((u16)((ch+1)<<8) | (val&0xff))
+
+/* stereo capture channels 1..4 allow sources 0..7, channel 5 allows 4..11 */
+static const char *const snd_pioneer_djm_source_types[] = {
+ "Line",
+ "Digital",
+ "Phono",
+ "Post Fader",
+ "Cross Fader A",
+ "Cross Fader B",
+ "Mic",
+ "Mix / Rec Out",
+ "Post Fader A",
+ "Post Fader B",
+ "Post Fader C",
+ "Post Fader D"
+};
+
+static int snd_pioneer_djm_source_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ const char* const* types = &snd_pioneer_djm_source_types[0];
+
+ /* channel 5 supports types 4 to 11 only */
+ if (uinfo->id.index == 5)
+ types += 4;
+
+ return snd_ctl_enum_info(uinfo, 1,
+ ARRAY_SIZE(snd_pioneer_djm_source_types)-4,
+ types);
+}
+
+static int snd_pioneer_djm_source_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ int channel = ucontrol->id.index;
+ u8 *values = (u8*)&kcontrol->private_value;
+
+ ucontrol->value.enumerated.item[0] = values[channel-1];
+ return 0;
+}
+
+static int snd_pioneer_djm_source_update(struct usb_mixer_interface *mixer,
+ u8 channel, u8 source)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+ u8 index = source;
+ u16 status;
+
+ if (channel == 5)
+ index += 4;
+ if (index >= ARRAY_SIZE(snd_pioneer_djm_source_values))
+ return -EINVAL;
+ status = SND_PIONEER_DJM_SOURCE(channel,
+ snd_pioneer_djm_source_values[index]);
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return err;
+ err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), 3,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ status, SND_PIONEER_DJM_SET_SOURCE, NULL, 0);
+ snd_usb_unlock_shutdown(chip);
+ return err;
+}
+
+static int snd_pioneer_djm_source_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ u8 channel = ucontrol->id.index;
+ u8 source = ucontrol->value.enumerated.item[0];
+ u8 *values = (u8*)&kcontrol->private_value;
+ int err;
+
+ if (source > 8 || channel > 5)
+ return -EINVAL;
+ values[channel-1] = source;
+
+ err = snd_pioneer_djm_source_update(list->mixer,
+ channel, source);
+ return err < 0 ? err : 1;
+}
+
+static int snd_pioneer_djm_source_resume(struct usb_mixer_elem_list *list)
+{
+ u8 channel, err;
+ u8 *values = (u8*)&list->kctl->private_value;
+ for (channel = 1; channel < 6; channel++) {
+ err = snd_pioneer_djm_source_update(list->mixer,
+ channel, values[channel-1]);
+ if (err < 0)
+ break;
+ }
+ return err;
+}
+
+static int snd_pioneer_djm_gain_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0; // -19 dB
+ uinfo->value.integer.max = 3; // -5 dB
+ uinfo->value.integer.step = 1;
+ return 0;
+}
+
+static int snd_pioneer_djm_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = kcontrol->private_value;
+ return 0;
+}
+
+static int snd_pioneer_djm_gain_update(struct usb_mixer_interface *mixer,
+ u16 value)
+{
+ struct snd_usb_audio *chip = mixer->chip;
+ int err;
+
+ err = snd_usb_lock_shutdown(chip);
+ if (err < 0)
+ return err;
+ err = snd_usb_ctl_msg(chip->dev, usb_sndctrlpipe(chip->dev, 0), 3,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ value, SND_PIONEER_DJM_SET_GAIN, NULL, 0);
+ snd_usb_unlock_shutdown(chip);
+ return err;
+}
+
+static int snd_pioneer_djm_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct usb_mixer_elem_list *list = snd_kcontrol_chip(kcontrol);
+ u16 gain = ucontrol->value.integer.value[0];
+ int err;
+
+ kcontrol->private_value = gain;
+ err = snd_pioneer_djm_gain_update(list->mixer, gain);
+ return err < 0 ? err : 1;
+}
+
+static int snd_pioneer_djm_gain_resume(struct usb_mixer_elem_list *list)
+{
+ return snd_pioneer_djm_gain_update(list->mixer,
+ list->kctl->private_value);
+}
+
+static const DECLARE_TLV_DB_RANGE(snd_pioneer_djm_db_scale, -1900, -1500, -1000, -500);
+
+static const struct snd_kcontrol_new snd_pioneer_djm_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Selected Input",
+ .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+ .info = snd_pioneer_djm_inputsel_info,
+ .get = snd_pioneer_djm_inputsel_get,
+ .index = 1,
+ .count = 4
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Channel",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_pioneer_djm_source_info,
+ .get = snd_pioneer_djm_source_get,
+ .put = snd_pioneer_djm_source_put,
+ .index = 1,
+ .count = 5
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Gain",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = snd_pioneer_djm_gain_info,
+ .get = snd_pioneer_djm_gain_get,
+ .put = snd_pioneer_djm_gain_put,
+ .private_value = 1,
+ .tlv = { .p = snd_pioneer_djm_db_scale }
+ },
+};
+
+static int snd_pioneer_djm_resume(struct usb_mixer_elem_list *list)
+{
+ if (list->kctl->info == snd_pioneer_djm_source_info)
+ return snd_pioneer_djm_source_resume(list);
+ else
+ return snd_pioneer_djm_gain_resume(list);
+}
+
+static int snd_pioneer_djm_controls_create(struct usb_mixer_interface *mixer)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_pioneer_djm_controls); ++i) {
+ err = add_single_ctl_with_resume(mixer, 0,
+ snd_pioneer_djm_resume,
+ &snd_pioneer_djm_controls[i],
+ NULL);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
/* RME Class Compliant device quirks */
#define SND_RME_GET_STATUS1 23
@@ -2264,6 +2542,9 @@ int snd_usb_mixer_apply_create_quirk(struct usb_mixer_interface *mixer)
case USB_ID(0x0bda, 0x4014): /* Dell WD15 dock */
err = dell_dock_mixer_init(mixer);
break;
+ case USB_ID(0x2b73, 0x000a): /* Pioneer DJM-900NXS2 */
+ err = snd_pioneer_djm_controls_create(mixer);
+ break;
case USB_ID(0x2a39, 0x3fd2): /* RME ADI-2 Pro */
case USB_ID(0x2a39, 0x3fd3): /* RME ADI-2 DAC */
--
2.24.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment