[RFC PATCH v5 1/8] ALSA: usb: add RME Babyface Pro driver core (probe, PCM stream)

Ismaïl Bahloul <[email protected]>
Newsgroups org.kernel.vger.linux-doc,org.kernel.vger.linux-kernel,org.kernel.vger.linux-sound,org.kernel.vger.linux-usb
Message-ID <[email protected]>
Minimal first step of the series: card lifecycle (probe/disconnect),
interrupt-URB PCM streaming, and the sample-rate/varispeed clock
mechanism the stream depends on. No mixer controls, front-panel
support, DSP EQ or suspend/resume yet - each is added by its own
later patch.

The sample rate is the family register (request 0x10, index 0x0030:
32k/44.1k/48k family) with SET_INTERFACE(5, alt) selecting the x1/x2/x4
speed on top of it; the 0x1B DDS quad is varispeed, a pitch ratio
applied independently of the family. A running stream now locks the
rate: a second client asking for a different one is refused at
hw_params rather than silently retuning the device out from under a
live stream, matching the vendor drivers' own behaviour.

Co-developed-by: David Fredman <[email protected]>
Signed-off-by: David Fredman <[email protected]>
Signed-off-by: Ismaïl Bahloul <[email protected]>
---
 MAINTAINERS                         |    6 +
 sound/usb/Kconfig                   |   20 +
 sound/usb/Makefile                  |    2 +-
 sound/usb/babyfacepro/Makefile      |    4 +
 sound/usb/babyfacepro/babyfacepro.c | 1200 +++++++++++++++++++++++++++
 sound/usb/babyfacepro/babyfacepro.h |  165 ++++
 6 files changed, 1396 insertions(+), 1 deletion(-)
 create mode 100644 sound/usb/babyfacepro/Makefile
 create mode 100644 sound/usb/babyfacepro/babyfacepro.c
 create mode 100644 sound/usb/babyfacepro/babyfacepro.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 207a6e2db..31ed00692 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23828,6 +23828,12 @@ F:	include/dt-bindings/power/thead,th1520-power.h
 F:	include/dt-bindings/reset/thead,th1520-reset.h
 F:	include/linux/firmware/thead/thead,th1520-aon.h
 
+RME BABYFACE PRO DRIVER (PROPRIETARY MODE)
+M:	Ismaïl Bahloul <[email protected]>
+L:	[email protected] (moderated for non-subscribers)
+S:	Maintained
+F:	sound/usb/babyfacepro/
+
 RNBD BLOCK DRIVERS
 M:	Md. Haris Iqbal <[email protected]>
 M:	Jack Wang <[email protected]>
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index b4588915e..f58eedf3d 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -204,6 +204,26 @@ config SND_USB_AUDIO_QMI
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-usb-audio-qmi.
 
+config SND_USB_BABYFACE_PRO
+	tristate "RME Babyface Pro / Pro FS (proprietary mode)"
+	select SND_PCM
+	help
+	  Say Y here to include support for the RME Babyface Pro and
+	  Babyface Pro FS in their proprietary mode (VID 0x2a39,
+	  PID 0x3fc0).  The two models share the same USB IDs and
+	  descriptors and are handled identically.
+
+	  The proprietary mode streams PCM over interrupt endpoints
+	  (interface 5, ep 0x01/0x82) instead of the class-compliant
+	  isochronous path handled by snd-usb-audio, so this driver is
+	  standalone (snd-usb-caiaq-style interrupt streaming).  This
+	  first patch covers probe, disconnect and the PCM stream only;
+	  later patches in the series add the mixer, front panel and
+	  DSP EQ as standard ALSA controls.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-babyface-pro.
+
 source "sound/usb/line6/Kconfig"
 
 endif	# SND_USB
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index e62794a87..2f83f5881 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -35,5 +35,5 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
 
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ qcom/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/ qcom/ babyfacepro/
 obj-$(CONFIG_SND_USB_LINE6)	+= line6/
diff --git a/sound/usb/babyfacepro/Makefile b/sound/usb/babyfacepro/Makefile
new file mode 100644
index 000000000..5adc6d474
--- /dev/null
+++ b/sound/usb/babyfacepro/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
+snd-usb-babyface-pro-y := babyfacepro.o
+
+obj-$(CONFIG_SND_USB_BABYFACE_PRO) += snd-usb-babyface-pro.o
diff --git a/sound/usb/babyfacepro/babyfacepro.c b/sound/usb/babyfacepro/babyfacepro.c
new file mode 100644
index 000000000..2d71286e3
--- /dev/null
+++ b/sound/usb/babyfacepro/babyfacepro.c
@@ -0,0 +1,1200 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RME Babyface Pro / Pro FS - proprietary-mode USB audio driver
+ *
+ * Core driver: USB vendor requests + cold init, interrupt-URB PCM
+ * streaming, and the card lifecycle (probe/disconnect/module entry).
+ * No mixer controls, front-panel support or suspend/resume yet - see
+ * babyfacepro.h and the cover letter for the rest of the series.
+ */
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include "babyfacepro.h"
+
+/* -- sample-rate / alt classes -------------------- */
+
+/* rate = family_base << (alt - 1): three rate families (32k / 44.1k /
+ * 48k), three interface speeds.  Measured on hardware, all nine rates,
+ * through the family register alone (2026-09-16).
+ *
+ * 64 kHz is the 32 kHz family at x2, not a 64 kHz base at x1.  Both clock
+ * 64 kHz at pitch 0, but a 64 kHz base on alt 1 needs 64000 x 56 B =
+ * 3584 kB/s, which is exactly alt 1's 448-byte packet ceiling: any
+ * positive varispeed then runs off the end of the bus, and the device
+ * delivers the mirrored rate instead (asked +5 %, got -5 %).  At x2 the
+ * frame is 40 B, so the same 64 kHz costs 2560 kB/s against a 5120 kB/s
+ * ceiling.  Same reasoning puts 128 kHz on alt 3 rather than alt 2.
+ */
+static const struct bf_rate bf_rates[] = {
+	{  32000, BF_ALT_1, 56,  8 },
+	{  44100, BF_ALT_1, 56,  8 },
+	{  48000, BF_ALT_1, 56,  8 },
+	{  64000, BF_ALT_2, 40, 16 },
+	{  88200, BF_ALT_2, 40, 16 },
+	{  96000, BF_ALT_2, 40, 16 },
+	{ 128000, BF_ALT_3, 32, 32 },
+	{ 176400, BF_ALT_3, 32, 32 },
+	{ 192000, BF_ALT_3, 32, 32 },
+};
+
+static const unsigned int bf_rate_list[ARRAY_SIZE(bf_rates)] = {
+	32000, 44100, 48000, 64000, 88200,
+	96000, 128000, 176400, 192000,
+};
+
+const struct snd_pcm_hw_constraint_list bf_rates_constraint = {
+	.count = ARRAY_SIZE(bf_rate_list),
+	.list = bf_rate_list,
+	.mask = 0,
+};
+
+const struct bf_rate *bf_rate_lookup(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bf_rates); i++)
+		if (bf_rates[i].rate == rate)
+			return &bf_rates[i];
+	return NULL;
+}
+
+unsigned int bf_rate_family(const struct bf_rate *r)
+{
+	switch (r->rate >> (r->alt - 1)) {
+	case 32000:
+		return 0;
+	case 44100:
+		return 1;
+	default:
+		return 2;
+	}
+}
+
+/* -- vendor requests ----------------------- */
+
+/* Timeout for every vendor control transfer (ms). */
+#define BF_CTL_TIMEOUT		1000
+
+int bf_vendor_write(struct snd_usb_babyface *chip, u8 req, u16 val, u16 idx)
+{
+	return usb_control_msg_send(chip->dev, 0, req,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE,
+				    val, idx, NULL, 0, BF_CTL_TIMEOUT, GFP_KERNEL);
+}
+
+int bf_vendor_read(struct snd_usb_babyface *chip, u8 req, u16 idx, u8 *buf)
+{
+	return usb_control_msg_recv(chip->dev, 0, req,
+				    USB_DIR_IN | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE,
+				    0, idx, buf, 4, BF_CTL_TIMEOUT, GFP_KERNEL);
+}
+
+/* Sends the BF_REG_KEEPALIVE_SETTINGS word (PROTOCOL.md: "keepalive
+ * 0x10 0x05CF wVal = host settings-state register" - a single shared
+ * word, not independent per-setting writes).  Hardcoded to Internal
+ * clock for now; the clock-source control and the chip->clock_optical
+ * bit it composes in land with a later patch in this series, at which
+ * point this grows the same way babyface_restore_state() does further
+ * down the series.
+ */
+int bf_settings_write(struct snd_usb_babyface *chip)
+{
+	return bf_vendor_write(chip, BF_REQ_KEEPALIVE, BF_SETTINGS_CLOCK_INTERNAL,
+			       BF_REG_KEEPALIVE_SETTINGS);
+}
+
+/* Set the sample rate: the family register.  The firmware derives its
+ * own DDS word from it; SET_INTERFACE(5, alt) (done in hw_params) is the
+ * x1/x2/x4 speed.  This is exactly what the RME Windows driver sends on
+ * a rate change (USBPcap, 2026-09-14, all fourteen changes): the family,
+ * then the settings word, nothing else.  Measured on this driver's
+ * target hardware for all nine rates with no quad in play (2026-09-16),
+ * and to -1 ppm at 44.1 kHz against an NTP-disciplined clock.
+ *
+ * An earlier version of this branch had this register and measured it as
+ * dead.  It was not: bf_cold_init()'s 0x0021 -> 0x05ff write is this
+ * same register (0x05ff = 0x05cf | 0x0030) hardcoded to the 48 kHz
+ * family, and it followed the write.  See bf_cold_init().
+ */
+int bf_clock_write(struct snd_usb_babyface *chip)
+{
+	const struct bf_rate *r = bf_rate_lookup(chip->rate);
+	int ret;
+
+	if (!r)
+		return -EINVAL;
+	ret = bf_vendor_write(chip, BF_REQ_KEEPALIVE,
+			      bf_rate_family(r) << 4, BF_REG_RATE_FAMILY);
+	if (ret < 0)
+		return ret;
+	return bf_settings_write(chip);
+}
+
+/* Varispeed: the 0x1B DDS quad.  It is a pitch RATIO applied on top of
+ * the family rate, not a clock, so it carries no base rate and is the
+ * same four words at every sample rate (measured 2026-09-16: a 48 kHz
+ * quad over the 44.1 kHz family gives 44.1 kHz; the same quad at +5 %
+ * gives 46305).  It is sticky: the device keeps the last ratio across
+ * family writes, so pitch 0 must send the x1.0 quad rather than nothing.
+ *
+ * Only bank 2 is read on the Babyface Pro (one bank at a time swapped
+ * between two quads: only bank 2 moved the clock).  It is the ratio's
+ * period as a 24-bit word, 1.0 = 2^14 x 25 MHz / 48000 = 8533333.33, and
+ * the firmware's constant is exact: sent exact it measures -1 ppm, sent
+ * as RME's own Q16-truncated word (0x8234d3) it measures +14 ppm.  So
+ * bank 2 is computed exactly.  Banks 0, 1 and 3 are sent as the RME
+ * host software computes them - bank 0 the x1.0 period 12800000 scaled
+ * by the pitch, banks 1 and 3 that value times fixed Q16 constants
+ * (0xb9c2, 0xa3d7), truncated - so that a unit which reads a different
+ * bank (the FS is not measured) sees exactly what Windows gives it.
+ * Together this reproduces the cold-plug capture's quad at pitch 0 in
+ * banks 0, 1 and 3, and improves on it in bank 2.
+ *
+ * Every quad must be followed by the settings keepalive or it does not
+ * apply.  pitch is in 0.1 % steps, -50..50.
+ */
+int bf_pitch_write(struct snd_usb_babyface *chip, int pitch)
+{
+	const u64 num0 = 12800000ULL * 1000;		/* bank 0: 12800000 / (1 + p) */
+	const u64 num2 = 409600000000000ULL;		/* bank 2: 4.096e11 * 1000 */
+	u32 den0 = 1000 + pitch;
+	u32 den2 = 48000 * den0;
+	u64 b0 = div_u64(num0 + den0 / 2, den0);
+	u64 b[4];
+	int k, ret;
+
+	b[0] = b0;
+	b[1] = (b0 * 47554) >> 16;
+	b[2] = div_u64(num2 + den2 / 2, den2);
+	b[3] = (b0 * 41943) >> 16;
+
+	for (k = 0; k < 4; k++) {
+		ret = bf_vendor_write(chip, BF_REQ_DDS, (b[k] >> 8) & 0xffff,
+				      ((b[k] & 0xff) << 8) | k);
+		if (ret < 0)
+			return ret;
+	}
+	return bf_settings_write(chip);
+}
+
+/* The cold-start session init (cap_coldplug.pcap), verbatim from the
+ * user-space reference (protocol::streaming_init).  Without it the
+ * firmware never validates a stream.
+ */
+int bf_cold_init(struct snd_usb_babyface *chip)
+{
+	const struct bf_rate *r;
+	int ret, i;
+
+	for (i = 0; i <= 0x3d; i++) {
+		if (i == 0x1e || i == 0x1f)
+			continue;
+		ret = bf_vendor_write(chip, BF_REQ_REG_CLEAR, 0x0000, i);
+		if (ret < 0)
+			return ret;
+	}
+	/* The varispeed quad, where the coldplug capture sends its x1.0
+	 * quad.  At pitch 0 banks 0, 1 and 3 match the capture; bank 2 is
+	 * the exact ratio (see bf_pitch_write).
+	 */
+	ret = bf_pitch_write(chip, chip->pitch);
+	if (ret < 0)
+		return ret;
+	/* 0x1C status - the hardware-validated reference (protocol::
+	 * streaming_init) sends it as an OUT write; Windows reads it.
+	 * Both are tolerated; match the validated path.
+	 */
+	ret = bf_vendor_write(chip, BF_REQ_STATUS_2, 0x0000, 0x0000);
+	if (ret < 0)
+		return ret;
+	/* The settings word and the family register in one write (0x05ff =
+	 * 0x05cf | 0x0030).  The coldplug capture hardcodes 0x0021 here:
+	 * family 48 kHz, clock internal.  Sent for the rate actually in use,
+	 * this IS the rate write; hardcoded, it was the thing that reset every
+	 * earlier family write to 48 kHz.
+	 */
+	r = bf_rate_lookup(chip->rate);
+	if (!r)
+		return -EINVAL;
+	ret = bf_vendor_write(chip, BF_REQ_KEEPALIVE,
+			      (bf_rate_family(r) << 4) | BF_SETTINGS_CLOCK_INTERNAL,
+			      BF_REG_KEEPALIVE_INIT);
+	if (ret < 0)
+		return ret;
+	/* 0x17 wIdx=0x0000 does NOT touch the preamp state (0x003F). */
+	ret = bf_vendor_write(chip, BF_REQ_PREAMP, 0x000c, 0x0000);
+	if (ret < 0)
+		return ret;
+	ret = bf_vendor_write(chip, BF_REQ_PREAMP_COMMIT, 0x0000, 0x0000);
+	if (ret < 0)
+		return ret;
+	for (i = 0; i < 2; i++) {
+		ret = bf_vendor_write(chip, BF_REQ_KEEPALIVE, 0x0000, 0x3000);
+		if (ret < 0)
+			return ret;
+	}
+	for (i = 0; i < 3; i++) {
+		ret = bf_vendor_write(chip, BF_REQ_KEEPALIVE, 0x0800, 0x0800);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+/* -- PCM data path ------------------------- */
+
+static bool babyface_capture_copy(struct snd_usb_babyface *chip,
+				  struct snd_pcm_substream *subs,
+				  u8 *data, unsigned int frames)
+{
+	struct snd_pcm_runtime *rt = subs->runtime;
+	unsigned int buf_frames = rt->buffer_size;
+	unsigned int words = chip->frame_bytes / 4;
+	unsigned int chans = rt->channels;
+	unsigned int pos, f, i;
+	unsigned long new_period;
+	bool crossed = false;
+	u8 *dst;
+
+	spin_lock(&chip->lock);
+	pos = chip->hw_ptr[SNDRV_PCM_STREAM_CAPTURE] % buf_frames;
+	for (f = 0; f < frames; f++) {
+		const __le32 *w = (const __le32 *)(data + f * chip->frame_bytes);
+
+		dst = rt->dma_area + frames_to_bytes(rt, pos);
+		for (i = 0; i < chans; i++) {
+			/* Channel map: app ch0-3 = device words 0-3 (AN1-4);
+			 * app ch4-9 = words 6-11 (ADAT/SPDIF); app ch10/11 =
+			 * words 12/13 = a FIXED-GAIN playback tap (observed
+			 * 2026-08-25: the playback echoes there at ~-27 dB,
+			 * independent of the output masters - NOT the output
+			 * bus; the ADAT/SPDIF range is words 6-11 only).  The
+			 * device words 4/5 are a fixed marker, not audio -
+			 * skipped.  At 96/192 kHz the frame has fewer words;
+			 * missing ones read as zero.
+			 */
+			static const u8 map[12] = { 0, 1, 2, 3, 6, 7, 8, 9,
+						   10, 11, 12, 13 };
+			u8 wi = i < 12 ? map[i] : 0xff;
+			s32 s = 0;
+
+			if (wi < words) {
+				/* 24-bit sample in bits 31-8 of the 32-bit word;
+				 * S32_LE with 24 msbits, so copy the word as-is.
+				 */
+				s = (s32)le32_to_cpu(w[wi]);
+			}
+			put_unaligned_le32((u32)s, dst + i * 4);
+		}
+		pos++;
+		if (pos >= buf_frames)
+			pos = 0;
+	}
+	chip->hw_ptr[SNDRV_PCM_STREAM_CAPTURE] += frames;
+	new_period = chip->hw_ptr[SNDRV_PCM_STREAM_CAPTURE] / rt->period_size;
+	if (new_period != chip->prev_period[SNDRV_PCM_STREAM_CAPTURE]) {
+		chip->prev_period[SNDRV_PCM_STREAM_CAPTURE] = new_period;
+		crossed = true;
+	}
+	spin_unlock(&chip->lock);
+
+	return crossed;
+}
+
+static bool babyface_playback_copy(struct snd_usb_babyface *chip,
+				   struct snd_pcm_substream *subs,
+				   u8 *data, unsigned int frames)
+{
+	struct snd_pcm_runtime *rt = subs->runtime;
+	unsigned int buf_frames = rt->buffer_size;
+	unsigned int words = chip->frame_bytes / 4;
+	unsigned int chans = rt->channels;
+	unsigned int pos, f, i;
+	unsigned long new_period;
+	bool crossed = false;
+	const u8 *src;
+
+	spin_lock(&chip->lock);
+	/* Never copy frames the app has not prepared.  With several URBs in
+	 * flight the device can transiently be ahead of the app ring at low
+	 * periods, so hw_ptr may reach past appl_ptr; reading beyond appl_ptr
+	 * would trip a spurious XRUN.  The device just repeats the last
+	 * frames instead.  (Both counters are unbounded, so clamp on the
+	 * signed difference.)
+	 */
+	{
+		snd_pcm_sframes_t avail =
+			(snd_pcm_sframes_t)(rt->control->appl_ptr -
+					    chip->hw_ptr[SNDRV_PCM_STREAM_PLAYBACK]);
+
+		if (avail < 0)
+			avail = 0;
+		else if (avail > (snd_pcm_sframes_t)buf_frames)
+			avail = buf_frames;
+		if ((snd_pcm_uframes_t)avail < frames)
+			frames = (unsigned int)avail;
+	}
+	pos = chip->hw_ptr[SNDRV_PCM_STREAM_PLAYBACK] % buf_frames;
+	for (f = 0; f < frames; f++) {
+		__le32 *w = (__le32 *)(data + f * chip->frame_bytes);
+
+		src = rt->dma_area + frames_to_bytes(rt, pos);
+		/* App ch n feeds the device word n (PB1-6 = words 0-11);
+		 * words 12/13 stay zero.  At 96/192 kHz the frame is
+		 * shorter - the extra app channels are dropped.
+		 */
+		for (i = 0; i < chans && i < words; i++) {
+			u32 s = get_unaligned_le32(src + i * 4);
+
+			/* S32_LE msbits=24: the app's 24 valid bits are already
+			 * left-justified (bits 31-8) - copy the word as-is.
+			 */
+			w[i] = cpu_to_le32(s);
+		}
+		for (; i < words; i++)
+			w[i] = 0;
+		pos++;
+		if (pos >= buf_frames)
+			pos = 0;
+	}
+	chip->hw_ptr[SNDRV_PCM_STREAM_PLAYBACK] += frames;
+	new_period = chip->hw_ptr[SNDRV_PCM_STREAM_PLAYBACK] / rt->period_size;
+	if (new_period != chip->prev_period[SNDRV_PCM_STREAM_PLAYBACK]) {
+		chip->prev_period[SNDRV_PCM_STREAM_PLAYBACK] = new_period;
+		crossed = true;
+	}
+	spin_unlock(&chip->lock);
+
+	return crossed;
+}
+
+static void babyface_complete_in(struct urb *urb)
+{
+	struct snd_usb_babyface *chip = urb->context;
+	struct snd_pcm_substream *subs;
+	unsigned long flags;
+	unsigned int frames;
+	bool crossed = false;
+	int ret;
+
+	if (urb->status < 0) {
+		if (urb->status == -ESHUTDOWN || urb->status == -ENOENT ||
+		    urb->status == -ECONNRESET)
+			return;		/* killed */
+		dev_dbg_ratelimited(&chip->dev->dev, "IN urb status %d\n",
+				    urb->status);
+		if (atomic_inc_return(&chip->urb_err) >= BF_URB_ERR_STOP)
+			schedule_work(&chip->stream_work);
+		goto resubmit;
+	}
+	atomic_set(&chip->urb_err, 0);
+
+	subs = READ_ONCE(chip->subs[SNDRV_PCM_STREAM_CAPTURE]);
+	if (subs) {
+		snd_pcm_stream_lock_irqsave(subs, flags);
+		if (snd_pcm_running(subs)) {
+			frames = urb->actual_length / chip->frame_bytes;
+			if (frames)
+				crossed = babyface_capture_copy(chip, subs,
+								urb->transfer_buffer,
+								frames);
+		}
+		snd_pcm_stream_unlock_irqrestore(subs, flags);
+		if (crossed)
+			snd_pcm_period_elapsed(subs);
+	}
+resubmit:
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret < 0) {
+		dev_err_ratelimited(&chip->dev->dev,
+				    "IN resubmit failed: %d\n", ret);
+		if (atomic_inc_return(&chip->urb_err) >= BF_URB_ERR_STOP)
+			schedule_work(&chip->stream_work);
+	}
+}
+
+static void babyface_complete_out(struct urb *urb)
+{
+	struct snd_usb_babyface *chip = urb->context;
+	struct snd_pcm_substream *subs;
+	unsigned long flags;
+	unsigned int frames;
+	bool crossed = false;
+	int ret;
+
+	if (urb->status < 0) {
+		if (urb->status == -ESHUTDOWN || urb->status == -ENOENT ||
+		    urb->status == -ECONNRESET)
+			return;		/* killed */
+		dev_dbg_ratelimited(&chip->dev->dev, "OUT urb status %d\n",
+				    urb->status);
+		if (atomic_inc_return(&chip->urb_err) >= BF_URB_ERR_STOP)
+			schedule_work(&chip->stream_work);
+		goto resubmit;
+	}
+	atomic_set(&chip->urb_err, 0);
+
+	subs = READ_ONCE(chip->subs[SNDRV_PCM_STREAM_PLAYBACK]);
+	if (subs) {
+		snd_pcm_stream_lock_irqsave(subs, flags);
+		if (snd_pcm_running(subs)) {
+			frames = chip->frames_per_urb;
+			crossed = babyface_playback_copy(chip, subs,
+							 urb->transfer_buffer, frames);
+		}
+		snd_pcm_stream_unlock_irqrestore(subs, flags);
+		if (crossed)
+			snd_pcm_period_elapsed(subs);
+	} else {
+		/* No consumer: silence the OUT frames. */
+		memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
+	}
+resubmit:
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret < 0) {
+		dev_err_ratelimited(&chip->dev->dev,
+				    "OUT resubmit failed: %d\n", ret);
+		if (atomic_inc_return(&chip->urb_err) >= BF_URB_ERR_STOP)
+			schedule_work(&chip->stream_work);
+	}
+}
+
+void babyface_stream_kill(struct snd_usb_babyface *chip)
+{
+	int i;
+
+	for (i = 0; i < chip->nurbs; i++) {
+		usb_kill_urb(chip->urbs_in[i]);
+		usb_kill_urb(chip->urbs_out[i]);
+	}
+	chip->streaming = false;
+}
+
+/* Stream start/stop run in process context (control transfers sleep).
+ * The trigger only toggles stream_users and schedules this work.
+ */
+
+/* Stop both PCM substreams (if running) so apps blocked in read/write
+ * wake with a clean error: XRUN for a recoverable stream error, or
+ * DISCONNECTED when the card is going away.
+ */
+void babyface_pcm_stop_both(struct snd_usb_babyface *chip, snd_pcm_state_t state)
+{
+	int s;
+
+	for (s = 0; s < 2; s++) {
+		struct snd_pcm_substream *subs = READ_ONCE(chip->subs[s]);
+
+		if (subs && snd_pcm_running(subs))
+			snd_pcm_stop(subs, state);
+	}
+}
+
+/* Re-count stream_users from the substream running states.  The apps
+ * can recover (re-prepare + trigger) while the stream work runs, so a
+ * hard `= 0` would wipe a fresh increment and leave a RUNNING
+ * substream with no URBs (hang).  Called on the error paths with the
+ * mutex held.
+ */
+static void bf_recount_users(struct snd_usb_babyface *chip)
+{
+	unsigned long flags;
+	int s, users = 0;
+
+	for (s = 0; s < 2; s++) {
+		struct snd_pcm_substream *subs = READ_ONCE(chip->subs[s]);
+
+		if (subs && snd_pcm_running(subs))
+			users++;
+	}
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->stream_users = users;
+	spin_unlock_irqrestore(&chip->lock, flags);
+}
+
+/* Stream model (big picture):
+ *
+ * The PCM stream is entirely asynchronous.  A trigger(START/STOP) does
+ * not touch the hardware; it only changes the shared stream_users
+ * counter (0..2, one per running substream, counted under chip->lock)
+ * and schedules stream_work.  The work runs in process context (the
+ * control transfers and usb_submit_urb() calls must sleep):
+ *
+ *  - 1st user (users 0 -> 1): cold-init + the session trigger pair,
+ *    then the interrupt URBs (nurbs in each direction) are submitted
+ *    and the session is armed.
+ *  - last user (users 1 -> 0): the URBs are killed and the session
+ *    disarmed.
+ *
+ * So the device has exactly one live stream regardless of how many
+ * substreams run, and a playback+capture pair shares it.  An app that
+ * triggers while the work is running just increments stream_users; the
+ * work's re-count on the error path (bf_recount_users) reflects the
+ * RUNNING state so a recovered app re-arms from a clean slate.
+ */
+void babyface_stream_work(struct work_struct *work)
+{
+	struct snd_usb_babyface *chip =
+		container_of(work, struct snd_usb_babyface, stream_work);
+	unsigned int urbsize;
+	unsigned long flags;
+	int i, ret;
+	int users;
+
+	mutex_lock(&chip->mutex);
+	urbsize = chip->frame_bytes * chip->frames_per_urb;
+
+	if (chip->shutdown) {
+		mutex_unlock(&chip->mutex);
+		return;
+	}
+
+	/* Persistent URB errors (bad link, device wedged): stop the stream
+	 * and wake the apps with -EPIPE.  stream_users is re-counted from
+	 * the (now stopped) substreams so an app recovery (prepare+start)
+	 * re-arms the session from a clean slate.
+	 */
+	if (atomic_read(&chip->urb_err) >= BF_URB_ERR_STOP) {
+		dev_err(&chip->dev->dev,
+			"stream error: %d consecutive bad URBs, stopping (apps re-arm)\n",
+			BF_URB_ERR_STOP);
+		babyface_pcm_stop_both(chip, SNDRV_PCM_STATE_XRUN);
+		if (chip->streaming)
+			babyface_stream_kill(chip);
+		bf_recount_users(chip);
+		atomic_set(&chip->urb_err, 0);
+		mutex_unlock(&chip->mutex);
+		return;
+	}
+
+	spin_lock_irqsave(&chip->lock, flags);
+	users = chip->stream_users;
+	spin_unlock_irqrestore(&chip->lock, flags);
+
+	if (users > 0 && !chip->streaming) {
+		/* The firmware only validates a stream session that is
+		 * preceded by the full cold-init (the user-space reference
+		 * sends streaming_init at every session start - without it
+		 * the outputs stay silent).
+		 */
+		ret = bf_cold_init(chip);
+		if (ret < 0)
+			goto err;
+
+		/* Stream trigger pair (cap_audio): 0x10 0x8000 + 0x1D. */
+		ret = bf_vendor_write(chip, BF_REQ_KEEPALIVE, 0x0000, 0x8000);
+		if (ret < 0)
+			goto err;
+		ret = bf_vendor_write(chip, BF_REQ_SESSION_START, 0x0000, 0x0000);
+		if (ret < 0)
+			goto err;
+
+		for (i = 0; i < chip->nurbs; i++) {
+			/* Do not replay data from the previous stream/format. */
+			memset(chip->buf_out[i], 0, urbsize);
+			usb_fill_int_urb(chip->urbs_in[i], chip->dev,
+					 usb_rcvintpipe(chip->dev, BF_EP_IN),
+					 chip->buf_in[i], urbsize,
+					 babyface_complete_in, chip, 1);
+			usb_fill_int_urb(chip->urbs_out[i], chip->dev,
+					 usb_sndintpipe(chip->dev, BF_EP_OUT),
+					 chip->buf_out[i], urbsize,
+					 babyface_complete_out, chip, 1);
+			/* The buffers come from usb_alloc_coherent(), so
+			 * they are already DMA-mapped: hand the HCD the
+			 * mapping instead of letting it map them again.
+			 * Without this, usb_hcd_map_urb_for_dma() calls
+			 * dma_map_single() on a coherent allocation, which
+			 * fails with -EAGAIN on any host where that
+			 * allocation is a vmap (IOMMU-backed dma-iommu,
+			 * e.g. amd_iommu in its default translated mode).
+			 */
+			chip->urbs_in[i]->transfer_dma = chip->dma_in[i];
+			chip->urbs_in[i]->transfer_flags |=
+				URB_NO_TRANSFER_DMA_MAP;
+			chip->urbs_out[i]->transfer_dma = chip->dma_out[i];
+			chip->urbs_out[i]->transfer_flags |=
+				URB_NO_TRANSFER_DMA_MAP;
+		}
+		for (i = 0; i < chip->nurbs; i++) {
+			ret = usb_submit_urb(chip->urbs_in[i], GFP_KERNEL);
+			if (ret < 0)
+				goto err;
+			ret = usb_submit_urb(chip->urbs_out[i], GFP_KERNEL);
+			if (ret < 0)
+				goto err;
+		}
+		/* Session arm (cap_audio frame 5829, after the URBs). */
+		ret = bf_vendor_write(chip, BF_REQ_SESSION_ARM, 0x0000, 0xc000);
+		if (ret < 0)
+			goto err;
+
+		chip->streaming = true;
+		dev_dbg(&chip->dev->dev, "stream started (%u frames/URB, %u URBs)\n",
+			chip->frames_per_urb, chip->nurbs);
+	} else if (users == 0 && chip->streaming) {
+		babyface_stream_kill(chip);
+		dev_dbg(&chip->dev->dev, "stream stopped\n");
+	}
+
+	mutex_unlock(&chip->mutex);
+	return;
+
+err:
+	dev_err(&chip->dev->dev, "failed to start stream: %d\n", ret);
+	babyface_stream_kill(chip);
+	/* The apps already got a successful trigger - wake them with an
+	 * XRUN so a failed start (device wedged, cold-init error) does not
+	 * leave them hung in read/write with no URBs in flight.
+	 */
+	babyface_pcm_stop_both(chip, SNDRV_PCM_STATE_XRUN);
+	bf_recount_users(chip);
+	mutex_unlock(&chip->mutex);
+}
+
+/* -- PCM --------------------------- */
+
+static const struct snd_pcm_hardware babyface_pcm_hw = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.rate_min = 32000,
+	.rate_max = 192000,
+	.channels_min = 2,
+	.channels_max = 12,
+	.buffer_bytes_max = 1 << 20,
+	.period_bytes_max = 1 << 18,
+	.periods_min = 2,
+	.periods_max = 16,
+};
+
+/* Is the OTHER direction actually transferring?  Not "is it open": a client
+ * that has the device open but has not started must still be able to pick the
+ * rate, so that an application opening playback and capture before starting
+ * either one can take both to 96 kHz.
+ */
+static bool bf_other_running(struct snd_usb_babyface *chip,
+			     struct snd_pcm_substream *subs)
+{
+	struct snd_pcm_substream *other = READ_ONCE(chip->subs[!subs->stream]);
+	unsigned long flags;
+	bool running;
+
+	if (!other)
+		return false;
+	snd_pcm_stream_lock_irqsave(other, flags);
+	running = snd_pcm_running(other);
+	snd_pcm_stream_unlock_irqrestore(other, flags);
+	return running;
+}
+
+static int babyface_pcm_open(struct snd_pcm_substream *subs)
+{
+	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *rt = subs->runtime;
+	unsigned int locked;
+	unsigned long flags;
+	int ret;
+
+	rt->hw = babyface_pcm_hw;
+	ret = snd_pcm_hw_constraint_list(rt, 0, SNDRV_PCM_HW_PARAM_RATE,
+					 &bf_rates_constraint);
+	if (ret < 0)
+		return ret;
+	/* The stream is 24-bit audio in a 32-bit container, left-justified
+	 * (the device word carries the sample in bits 31-8).  Declare the
+	 * valid-bit width so user-space knows to ignore the low 8 bits.
+	 */
+	ret = snd_pcm_hw_constraint_msbits(rt, 0, 32, 24);
+	if (ret < 0)
+		return ret;
+	/* One URB delivers frames_per_urb frames per interrupt; a period must
+	 * span at least one URB so a completion crosses at most one period
+	 * boundary.  Constrain in frames (not bytes) so the minimum period
+	 * does not balloon at low channel counts: 2 ch @ 48 kHz -> 256
+	 * frames (5.3 ms) instead of 1536 frames from a 12-ch byte clamp.
+	 */
+	ret = snd_pcm_hw_constraint_minmax(rt, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+					   chip->frames_per_urb, 1 << 18);
+	if (ret < 0)
+		return ret;
+
+	/* Both directions share one clock, so while audio is flowing the rate
+	 * belongs to whoever started it.  Offer that rate alone: a client
+	 * arriving later then negotiates down to it, and the sound server
+	 * resamples, rather than the device being retuned underneath a running
+	 * stream.  Advertising the constraint here rather than failing in
+	 * hw_params() is what keeps a PipeWire sink alive - it picks the rate
+	 * on offer instead of asking for one that has to be refused.
+	 *
+	 * This is what the vendor drivers do.  RME's settings panel greys the
+	 * sample rate out during record/playback, and their manual is explicit
+	 * that all active ASIO clients share one rate, with WDM (or CoreAudio)
+	 * doing any conversion - never the driver.
+	 */
+	mutex_lock(&chip->mutex);
+	locked = chip->streaming ? chip->rate : 0;
+	mutex_unlock(&chip->mutex);
+	if (locked) {
+		ret = snd_pcm_hw_constraint_minmax(rt, SNDRV_PCM_HW_PARAM_RATE,
+						   locked, locked);
+		if (ret < 0)
+			return ret;
+	}
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->subs[subs->stream] = subs;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int babyface_pcm_close(struct snd_pcm_substream *subs)
+{
+	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
+	unsigned long flags;
+
+	/* Wait for the stream stop work so the URB callbacks (which
+	 * touch subs) are done before the substream can be freed.
+	 */
+	flush_work(&chip->stream_work);
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->subs[subs->stream] = NULL;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int babyface_pcm_hw_params(struct snd_pcm_substream *subs,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
+	const struct bf_rate *r;
+	int ret = 0;
+
+	r = bf_rate_lookup(params_rate(params));
+	if (!r)
+		return -EINVAL;
+
+	/* The stream URBs must be at least one alt packet wide: the device
+	 * delivers its IN data in alt-sized packets (448/640/1024 B for
+	 * alt 1/2/3), and a smaller URB buffer makes the host controller
+	 * discard the transfer with -EOVERFLOW (babble) - seen at
+	 * 176.4/192 kHz with frames_per_urb below 32.  Return a clean
+	 * error instead of a silently dead capture stream.
+	 */
+	if (chip->frames_per_urb < r->min_fpu) {
+		dev_err(&chip->dev->dev,
+			"rate %u Hz needs frames_per_urb >= %u (module has %u)\n",
+			r->rate, r->min_fpu, chip->frames_per_urb);
+		return -EINVAL;
+	}
+
+	mutex_lock(&chip->mutex);
+	if (r->rate != chip->rate) {
+		/* The constraint in open() normally means nobody asks for a
+		 * rate other than the running one.  It can still happen, when
+		 * this substream was opened before the other one started: the
+		 * constraint is evaluated at open, so it did not apply then.
+		 * Refuse rather than retune - the alternative is a running
+		 * stream silently changing pitch, with no error and no xrun,
+		 * because a negotiated rate is never revised for a live
+		 * substream.
+		 */
+		if (bf_other_running(chip, subs)) {
+			dev_dbg(&chip->dev->dev,
+				"rate %u Hz refused: %u Hz stream running\n",
+				r->rate, chip->rate);
+			ret = -EBUSY;
+			goto out;
+		}
+		/* Nothing is transferring, so the clock is free.  Stop any
+		 * armed URBs, re-point the bandwidth class and let the stream
+		 * work restart the session at the new rate.
+		 */
+		if (chip->streaming)
+			babyface_stream_kill(chip);
+		ret = usb_set_interface(chip->dev, BF_IFACE, r->alt);
+		if (ret < 0) {
+			babyface_pcm_stop_both(chip, SNDRV_PCM_STATE_XRUN);
+			bf_recount_users(chip);
+			goto out;
+		}
+		chip->rate = r->rate;
+		chip->alt = r->alt;
+		chip->frame_bytes = r->frame_bytes;
+		/* Publish the new geometry before restarting either stream. */
+		schedule_work(&chip->stream_work);
+		dev_dbg(&chip->dev->dev, "rate %u Hz (alt %u)\n",
+			chip->rate, chip->alt);
+	}
+out:
+	mutex_unlock(&chip->mutex);
+	return ret;
+}
+
+static int babyface_pcm_hw_free(struct snd_pcm_substream *subs)
+{
+	/* The device buffer is host-side; nothing to release here. */
+	return 0;
+}
+
+static int babyface_pcm_prepare(struct snd_pcm_substream *subs)
+{
+	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	chip->hw_ptr[subs->stream] = 0;
+	chip->prev_period[subs->stream] = 0;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return 0;
+}
+
+static int babyface_pcm_trigger(struct snd_pcm_substream *subs, int cmd)
+{
+	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
+	unsigned long flags;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		spin_lock_irqsave(&chip->lock, flags);
+		chip->hw_ptr[subs->stream] = 0;
+		chip->prev_period[subs->stream] = 0;
+		/* stream_users is shared by the two substreams (separate
+		 * locks) - serialize the ++/-- so a concurrent trigger on
+		 * the other direction can't lose an increment (which would
+		 * stop the stream while a substream still runs).
+		 */
+		if (chip->stream_users++ == 0)
+			schedule_work(&chip->stream_work);
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		spin_lock_irqsave(&chip->lock, flags);
+		if (chip->stream_users > 0 && --chip->stream_users == 0)
+			schedule_work(&chip->stream_work);
+		spin_unlock_irqrestore(&chip->lock, flags);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static snd_pcm_uframes_t babyface_pcm_pointer(struct snd_pcm_substream *subs)
+{
+	struct snd_usb_babyface *chip = snd_pcm_substream_chip(subs);
+	unsigned long flags;
+	snd_pcm_uframes_t pos;
+
+	spin_lock_irqsave(&chip->lock, flags);
+	pos = chip->hw_ptr[subs->stream] % subs->runtime->buffer_size;
+	spin_unlock_irqrestore(&chip->lock, flags);
+	return pos;
+}
+
+static const struct snd_pcm_ops babyface_pcm_ops = {
+	.open = babyface_pcm_open,
+	.close = babyface_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = babyface_pcm_hw_params,
+	.hw_free = babyface_pcm_hw_free,
+	.prepare = babyface_pcm_prepare,
+	.trigger = babyface_pcm_trigger,
+	.pointer = babyface_pcm_pointer,
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int frames_per_urb = BF_FRAMES_PER_URB_DEFAULT;
+static int nurbs = BF_NURBS_DEFAULT;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the Babyface Pro sound card.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the Babyface Pro sound card.");
+module_param(frames_per_urb, int, 0644);
+MODULE_PARM_DESC(frames_per_urb, "Audio frames per URB, 8..1024 (16 = low-latency floor, 256 = default).");
+module_param(nurbs, int, 0644);
+MODULE_PARM_DESC(nurbs, "URBs in flight per direction, 1..16 (16 = low-latency).");
+
+/* -- USB driver ------------------------- */
+
+static void babyface_private_free(struct snd_card *card)
+{
+	struct snd_usb_babyface *chip = card->private_data;
+	unsigned int urbsize;
+	int i;
+
+	if (!chip)
+		return;
+
+	/* The URB arrays are NULL when the probe failed before allocating
+	 * them (snd_card_free runs private_free on any probe error).
+	 */
+	if (chip->urbs_in) {
+		urbsize = BF_WORDS_PER_FRAME * sizeof(u32) * chip->frames_per_urb;
+		for (i = 0; i < chip->nurbs; i++) {
+			if (chip->urbs_in[i]) {
+				usb_kill_urb(chip->urbs_in[i]);
+				usb_free_urb(chip->urbs_in[i]);
+			}
+			if (chip->urbs_out[i]) {
+				usb_kill_urb(chip->urbs_out[i]);
+				usb_free_urb(chip->urbs_out[i]);
+			}
+			usb_free_coherent(chip->dev, urbsize, chip->buf_in[i],
+					  chip->dma_in[i]);
+			usb_free_coherent(chip->dev, urbsize, chip->buf_out[i],
+					  chip->dma_out[i]);
+		}
+	}
+	kfree(chip->urbs_in);
+	kfree(chip->urbs_out);
+	kfree(chip->buf_in);
+	kfree(chip->buf_out);
+	kfree(chip->dma_in);
+	kfree(chip->dma_out);
+	usb_put_dev(chip->dev);
+}
+
+static int babyface_probe(struct usb_interface *intf,
+			  const struct usb_device_id *usb_id)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct snd_usb_babyface *chip;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	unsigned int urbsize;
+	int i, err;
+
+	if (intf->cur_altsetting->desc.bInterfaceNumber != BF_IFACE) {
+		/* Only the proprietary audio interface is ours; the MIDI
+		 * (standard class) and bulk interfaces stay unclaimed so
+		 * snd-usb-audio can take the MIDI one.
+		 */
+		return -ENODEV;
+	}
+
+	err = snd_card_new(&intf->dev, index[0], id[0], THIS_MODULE,
+			   sizeof(*chip), &card);
+	if (err < 0) {
+		dev_err(&intf->dev, "snd_card_new failed: %d\n", err);
+		return err;
+	}
+	chip = card->private_data;
+	chip->card = card;
+
+	chip->dev = usb_get_dev(dev);
+	/* USB autosuspend is untested: nothing in this driver holds a PM
+	 * reference while streaming, so an autosuspend request could race
+	 * a live stream.  Disable it explicitly rather than ship an
+	 * untested code path - full autosuspend support (correct
+	 * autopm_get/put pairing around the stream) is a deliberate
+	 * follow-up, not an oversight.
+	 */
+	usb_disable_autosuspend(chip->dev);
+	chip->iface = intf;
+	chip->nurbs = clamp(nurbs, 1, 16);
+	chip->frames_per_urb = clamp(frames_per_urb, 8, 1024) & ~7;
+	chip->rate = 48000;
+	chip->alt = BF_ALT_1;
+	chip->frame_bytes = 56;
+	mutex_init(&chip->mutex);
+	spin_lock_init(&chip->lock);
+	atomic_set(&chip->urb_err, 0);
+	INIT_WORK(&chip->stream_work, babyface_stream_work);
+	chip->card->private_free = babyface_private_free;
+
+	/* Model-neutral on purpose.  The FS and the original (2015)
+	 * Babyface Pro share VID:PID 2a39:3fc0, report the same bcdDevice
+	 * and the same iProduct shape, and neither says "FS" anywhere, so
+	 * there is nothing to tell them apart at probe time - and the
+	 * driver is reported to work unmodified on both.  card->driver in
+	 * particular is what alsa-lib configs and UCM profiles match on,
+	 * so it has to be right before this reaches a released kernel.
+	 */
+	strscpy(chip->card->driver, "BabyfacePro",
+		sizeof(chip->card->driver));
+	strscpy(chip->card->shortname, "Babyface Pro",
+		sizeof(chip->card->shortname));
+	snprintf(chip->card->longname, sizeof(chip->card->longname),
+		 "RME Babyface Pro (proprietary mode) at %s",
+		 dev_name(&dev->dev));
+
+	/* If the id was not passed as a module option, derive it from the
+	 * shortname with the whitespace removed, the way snd-usb-caiaq
+	 * does.  Letting the core derive it instead yields the last word
+	 * of the shortname ("Pro"), which is no use as an hw: name.
+	 */
+	if (*chip->card->id == '\0') {
+		char cid[sizeof(chip->card->id)];
+		const char *c;
+		size_t len;
+
+		memset(cid, 0, sizeof(cid));
+		for (c = chip->card->shortname, len = 0;
+		     *c && len < sizeof(cid) - 1; c++)
+			if (*c != ' ')
+				cid[len++] = *c;
+		snd_card_set_id(chip->card, cid);
+	}
+	strscpy(chip->card->mixername, "Babyface Pro",
+		sizeof(chip->card->mixername));
+
+	/* alt 1 = the default 48-kHz bandwidth class. */
+	err = usb_set_interface(dev, BF_IFACE, BF_ALT_1);
+	if (err < 0) {
+		dev_err(&intf->dev, "usb_set_interface failed: %d\n", err);
+		goto error;
+	}
+
+	err = bf_cold_init(chip);
+	if (err < 0) {
+		dev_err(&intf->dev, "cold init failed: %d\n", err);
+		goto error;
+	}
+
+	/* Keep the allocation size independent of the active USB mode. */
+	urbsize = BF_WORDS_PER_FRAME * sizeof(u32) * chip->frames_per_urb;
+	chip->urbs_in = kcalloc(chip->nurbs, sizeof(*chip->urbs_in), GFP_KERNEL);
+	chip->urbs_out = kcalloc(chip->nurbs, sizeof(*chip->urbs_out), GFP_KERNEL);
+	chip->buf_in = kcalloc(chip->nurbs, sizeof(*chip->buf_in), GFP_KERNEL);
+	chip->buf_out = kcalloc(chip->nurbs, sizeof(*chip->buf_out), GFP_KERNEL);
+	chip->dma_in = kcalloc(chip->nurbs, sizeof(*chip->dma_in), GFP_KERNEL);
+	chip->dma_out = kcalloc(chip->nurbs, sizeof(*chip->dma_out), GFP_KERNEL);
+	if (!chip->urbs_in || !chip->urbs_out || !chip->buf_in ||
+	    !chip->buf_out || !chip->dma_in || !chip->dma_out)
+		goto error;
+
+	for (i = 0; i < chip->nurbs; i++) {
+		chip->urbs_in[i] = usb_alloc_urb(0, GFP_KERNEL);
+		chip->urbs_out[i] = usb_alloc_urb(0, GFP_KERNEL);
+		chip->buf_in[i] = usb_alloc_coherent(dev, urbsize, GFP_KERNEL,
+						     &chip->dma_in[i]);
+		chip->buf_out[i] = usb_alloc_coherent(dev, urbsize, GFP_KERNEL,
+						      &chip->dma_out[i]);
+		if (!chip->urbs_in[i] || !chip->urbs_out[i] ||
+		    !chip->buf_in[i] || !chip->buf_out[i])
+			goto error;
+	}
+
+	err = snd_pcm_new(chip->card, "Babyface Pro", 0, 1, 1, &pcm);
+	if (err < 0) {
+		dev_err(&intf->dev, "snd_pcm_new failed: %d\n", err);
+		goto error;
+	}
+	pcm->private_data = chip;
+	strscpy(pcm->name, "Babyface Pro", sizeof(pcm->name));
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &babyface_pcm_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &babyface_pcm_ops);
+
+	/* The PCM buffer is host-side (the URB callbacks copy in/out of
+	 * it); vmalloc is the standard choice for that.
+	 */
+	err = snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC,
+					     NULL, 0, 1 << 20);
+	if (err < 0) {
+		dev_err(&intf->dev, "buffer allocation failed: %d\n", err);
+		goto error;
+	}
+
+	err = snd_card_register(chip->card);
+	if (err < 0) {
+		dev_err(&intf->dev, "snd_card_register failed: %d\n", err);
+		goto error;
+	}
+
+	usb_set_intfdata(intf, chip);
+	dev_info(&intf->dev,
+		 "Babyface Pro: card %i, %u frames/URB, %u URBs/direction\n",
+		 chip->card->number, chip->frames_per_urb, chip->nurbs);
+	return 0;
+
+error:
+	usb_set_intfdata(intf, NULL);
+	/* Balance the probe()-time usb_disable_autosuspend(): disconnect()
+	 * is never called for a failed probe, so the disable would leak and
+	 * leave autosuspend off on this usb_device until a physical unplug.
+	 */
+	usb_enable_autosuspend(chip->dev);
+	snd_card_free(chip->card);
+	return err;
+}
+
+static void babyface_disconnect(struct usb_interface *intf)
+{
+	struct snd_usb_babyface *chip = usb_get_intfdata(intf);
+
+	if (!chip)
+		return;
+
+	/* Idempotence guard: a disconnect can race a re-probe (usbfs
+	 * detach/re-attach) - tear the card down exactly once.
+	 */
+	usb_set_intfdata(intf, NULL);
+	if (chip->shutdown)
+		return;
+
+	chip->shutdown = true;
+	cancel_work_sync(&chip->stream_work);
+	/* Balance the probe()-time usb_disable_autosuspend(): the usb_device
+	 * outlives this interface claim (a usbfs detach re-probes without
+	 * the physical device ever disconnecting), so leaving autosuspend
+	 * disabled here would wrongly affect whatever claims the device next.
+	 */
+	usb_enable_autosuspend(chip->dev);
+	/* Wake apps blocked in read/write: the card is going away. */
+	dev_info(&chip->dev->dev, "disconnect: stopping PCM substreams\n");
+	babyface_pcm_stop_both(chip, SNDRV_PCM_STATE_DISCONNECTED);
+	mutex_lock(&chip->mutex);
+	if (chip->streaming)
+		babyface_stream_kill(chip);
+	mutex_unlock(&chip->mutex);
+
+	snd_card_disconnect(chip->card);
+	/* NEVER snd_card_free() here: it blocks until the last user
+	 * closes the card, and an open client (e.g. PipeWire) deadlocks
+	 * the disconnect (seen live: pipewire stuck in snd_card_free,
+	 * D state).  free_when_closed frees on the last close.
+	 */
+	snd_card_free_when_closed(chip->card);
+}
+
+static const struct usb_device_id babyface_ids[] = {
+	{ USB_DEVICE(USB_VENDOR_RME, USB_PRODUCT_BABYFACE_PRO_FS) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, babyface_ids);
+
+static struct usb_driver babyface_driver = {
+	.name = "snd-usb-babyface-pro",
+	.probe = babyface_probe,
+	.disconnect = babyface_disconnect,
+	.id_table = babyface_ids,
+};
+
+static int __init babyface_init(void)
+{
+	return usb_register(&babyface_driver);
+}
+
+static void __exit babyface_exit(void)
+{
+	usb_deregister(&babyface_driver);
+}
+
+module_init(babyface_init);
+module_exit(babyface_exit);
+
+MODULE_AUTHOR("Ismaïl Bahloul <[email protected]>");
+MODULE_DESCRIPTION("RME Babyface Pro / Pro FS (proprietary mode) USB audio driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/usb/babyfacepro/babyfacepro.h b/sound/usb/babyfacepro/babyfacepro.h
new file mode 100644
index 000000000..de0353a59
--- /dev/null
+++ b/sound/usb/babyfacepro/babyfacepro.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * RME Babyface Pro / Pro FS - proprietary-mode USB audio driver
+ *
+ * The Babyface Pro (and Pro FS) present two personalities on the USB
+ * bus: a class-compliant one (handled by snd-usb-audio) and a
+ * proprietary one (VID 0x2a39 / PID 0x3fc0) whose PCM stream runs on
+ * INTERRUPT endpoints (interface 5, ep 0x01 OUT / 0x82 IN).
+ * Isochronous transfers are rejected there with EINVAL, and
+ * snd-usb-audio has no interrupt-PCM path, so this driver is
+ * standalone (snd-usb-caiaq-style interrupt streaming) instead of an
+ * snd-usb-audio quirk.
+ *
+ * This file and babyfacepro.c cover the card lifecycle (probe,
+ * disconnect) and the PCM stream only - no mixer controls, no
+ * front-panel support, no suspend/resume yet.  Those are added by
+ * later patches in this series, each with its own register map and
+ * device-state additions; see the cover letter for the full series
+ * plan.
+ *
+ * The protocol (vendor requests + 14x32-bit frame layout) was
+ * reverse-engineered from Windows captures and validated on hardware -
+ * tools/usbdump/PROTOCOL.md in the driver's development repository is
+ * the authoritative reference.
+ *
+ * Stream notes (hardware-validated 2026-08):
+ *   - frames_per_urb is tunable 8..1024 (multiple of 8) but must be at
+ *     least one alt packet wide - the device delivers IN data in
+ *     alt-sized packets (448/640/1024 B for alt 1/2/3), smaller URBs
+ *     get -EOVERFLOW (babble).  So frames_per_urb >= 8/16/32 for
+ *     alt 1/2/3; the driver rejects violating rates in hw_params.
+ *   - The device only advances the stream while BOTH endpoints have a
+ *     pending URB - IN and OUT are always submitted as a pair.
+ *   - SET_INTERFACE(5, alt) selects single/double/quad speed and USB
+ *     packet capacity; the BASE rate is the family register (request
+ *     0x10, index 0x0030: 32k / 44.1k / 48k family), from which the
+ *     firmware derives its own DDS word.  Three families times three
+ *     speeds give all nine rates.  The 0x1B DDS quad is varispeed: a
+ *     pitch RATIO applied on top of the family rate, sticky until
+ *     rewritten, with the 48 kHz cold-plug quad meaning x1.0.  See
+ *     bf_clock_write / bf_pitch_write.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/unaligned.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#define USB_VENDOR_RME			0x2a39
+#define USB_PRODUCT_BABYFACE_PRO_FS	0x3fc0
+
+/* The proprietary audio interface (interface 5, interrupt endpoints). */
+#define BF_IFACE			5
+#define BF_EP_OUT			0x01
+#define BF_EP_IN			0x82
+
+#define BF_ALT_1			1	/* x1: 32/44.1/48 kHz, 448-B packets */
+#define BF_ALT_2			2	/* x2: 64/88.2/96 kHz, 640-B packets */
+#define BF_ALT_3			3	/* x4: 128/176.4/192 kHz, 1024-B packets */
+
+/* Default stream geometry - conservative, matches the RME TotalMix
+ * 256-sample buffer.  Both are tunable via module params; the
+ * low-latency profile (validated) is frames_per_urb=16 nurbs=16.
+ */
+#define BF_FRAMES_PER_URB_DEFAULT	256
+#define BF_NURBS_DEFAULT		8
+
+#define BF_WORDS_PER_FRAME		14	/* 14 x 32-bit words per frame */
+
+/* Consecutive URB errors (CRC/babble/protocol or a failed resubmit)
+ * before the stream is stopped and the apps get a clean -EPIPE.
+ */
+#define BF_URB_ERR_STOP			3
+
+/* Vendor requests (bmRequestType 0x40, value in wValue, no data phase).
+ * Only the subset the core probe/PCM path needs; the mixer, front
+ * panel and DSP EQ patches each add their own as those features land.
+ */
+#define BF_REQ_KEEPALIVE		0x10	/* settings word / stream trigger */
+#define BF_REQ_REG_CLEAR		0x16	/* cold-init register clear */
+#define BF_REQ_PREAMP			0x17	/* 48V/PAD state + readback */
+#define BF_REQ_DDS			0x1b	/* clock quads */
+#define BF_REQ_STATUS_2			0x1c	/* read 4 B */
+#define BF_REQ_SESSION_START		0x1d
+#define BF_REQ_SESSION_ARM		0x14
+#define BF_REQ_PREAMP_COMMIT		0x21	/* commit after 0x17 */
+
+#define BF_REG_RATE_FAMILY		0x0030	/* family << 4 (bReq 0x10) */
+/* 0x05ff = 0x05cf | 0x0030: the settings word and the family register
+ * written together (measured 2026-09-16: bits 4-5 of the value land in
+ * the family register).  The cold-plug capture writes 0x0021 here, i.e.
+ * family 48 kHz + clock internal - which is why replaying it after a
+ * family write used to reset the rate to 48 kHz.
+ */
+#define BF_REG_KEEPALIVE_INIT		0x05ff
+#define BF_REG_KEEPALIVE_SETTINGS	0x05cf
+
+/* Host settings-state word carried by the BF_REG_KEEPALIVE_SETTINGS
+ * keepalive (PROTOCOL.md "keepalive 0x10 0x05CF wVal = host settings-
+ * state register", hardware-verified 2026-08-22/23).  Only the
+ * Internal value is used until the clock-source control lands with a
+ * later patch in this series.
+ */
+#define BF_SETTINGS_CLOCK_INTERNAL	0x0001
+#define BF_SETTINGS_CLOCK_OPTICAL	0x0004
+
+struct snd_usb_babyface {
+	struct snd_card *card;
+	struct usb_device *dev;
+	struct usb_interface *iface;
+
+	struct mutex mutex;		/* controls + stream geometry */
+	spinlock_t lock;		/* hw_ptr / subs */
+
+	/* stream */
+	struct urb **urbs_in;
+	struct urb **urbs_out;
+	void **buf_in;
+	void **buf_out;
+	dma_addr_t *dma_in;
+	dma_addr_t *dma_out;
+	unsigned int nurbs;
+	unsigned int frames_per_urb;
+	unsigned int frame_bytes;	/* 56/40/32 for alt 1/2/3 */
+	unsigned int rate;
+	unsigned int alt;
+	int pitch;			/* varispeed in 0.1% (-500..+500) */
+	int stream_users;		/* PCM substreams sharing the stream */
+	bool streaming;			/* URBs actually in flight */
+	bool shutdown;
+	atomic_t urb_err;		/* consecutive bad URBs (stops the stream) */
+	struct work_struct stream_work;
+
+	struct snd_pcm_substream *subs[2];
+	unsigned long hw_ptr[2];
+	unsigned long prev_period[2];
+};
+
+struct bf_rate {
+	unsigned int rate;
+	unsigned int alt;
+	unsigned int frame_bytes;
+	unsigned int min_fpu;	/* frames/URB floor = one alt packet (448/640/1024 B) */
+};
+
+/* -- babyfacepro.c ------------------------ */
+const struct bf_rate *bf_rate_lookup(unsigned int rate);
+/* The family register value for a rate: 0 / 1 / 2 for the 32k / 44.1k /
+ * 48k family (rate >> (alt - 1)).
+ */
+unsigned int bf_rate_family(const struct bf_rate *r);
+int bf_vendor_write(struct snd_usb_babyface *chip, u8 req, u16 val, u16 idx);
+int bf_vendor_read(struct snd_usb_babyface *chip, u8 req, u16 idx, u8 *buf);
+int bf_settings_write(struct snd_usb_babyface *chip);
+int bf_clock_write(struct snd_usb_babyface *chip);
+int bf_pitch_write(struct snd_usb_babyface *chip, int pitch);
+int bf_cold_init(struct snd_usb_babyface *chip);
+void babyface_stream_kill(struct snd_usb_babyface *chip);
+void babyface_pcm_stop_both(struct snd_usb_babyface *chip, snd_pcm_state_t state);
+void babyface_stream_work(struct work_struct *work);
+extern const struct snd_pcm_hw_constraint_list bf_rates_constraint;
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.