summaryrefslogtreecommitdiffstats
path: root/sound/firewire/bebob
diff options
context:
space:
mode:
authorSteven Rostedt <rostedt@goodmis.org>2026-09-02 09:55:01 -0400
committerSteven Rostedt <rostedt@goodmis.org>2026-09-02 11:08:21 -0400
commit4617721c502b2ddaa4e324e86da4997edf738fa5 (patch)
tree350aa83b47da0b574a814795392b20b0bed83cbc /sound/firewire/bebob
downloadlinux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz
linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'sound/firewire/bebob')
-rw-r--r--sound/firewire/bebob/Makefile6
-rw-r--r--sound/firewire/bebob/bebob.c510
-rw-r--r--sound/firewire/bebob/bebob.h255
-rw-r--r--sound/firewire/bebob/bebob_command.c331
-rw-r--r--sound/firewire/bebob/bebob_focusrite.c322
-rw-r--r--sound/firewire/bebob/bebob_hwdep.c177
-rw-r--r--sound/firewire/bebob/bebob_maudio.c785
-rw-r--r--sound/firewire/bebob/bebob_midi.c140
-rw-r--r--sound/firewire/bebob/bebob_pcm.c368
-rw-r--r--sound/firewire/bebob/bebob_proc.c189
-rw-r--r--sound/firewire/bebob/bebob_stream.c987
-rw-r--r--sound/firewire/bebob/bebob_terratec.c53
-rw-r--r--sound/firewire/bebob/bebob_yamaha_terratec.c64
13 files changed, 4187 insertions, 0 deletions
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile
new file mode 100644
index 000000000..b913e805b
--- /dev/null
+++ b/sound/firewire/bebob/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+snd-bebob-y := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
+ bebob_pcm.o bebob_hwdep.o bebob_terratec.o \
+ bebob_yamaha_terratec.o bebob_focusrite.o bebob_maudio.o \
+ bebob.o
+obj-$(CONFIG_SND_BEBOB) += snd-bebob.o
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c
new file mode 100644
index 000000000..01e2c4cc0
--- /dev/null
+++ b/sound/firewire/bebob/bebob.c
@@ -0,0 +1,510 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+/*
+ * BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire
+ * devices with DM1000/DM1100/DM1500 chipset. It gives common way for host
+ * system to handle BeBoB based devices.
+ */
+
+#include "bebob.h"
+
+MODULE_DESCRIPTION("BridgeCo BeBoB driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable BeBoB sound card");
+
+static DEFINE_MUTEX(devices_mutex);
+static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+
+/* Offsets from information register. */
+#define INFO_OFFSET_BEBOB_VERSION 0x08
+#define INFO_OFFSET_GUID 0x10
+#define INFO_OFFSET_HW_MODEL_ID 0x18
+#define INFO_OFFSET_HW_MODEL_REVISION 0x1c
+
+#define VEN_EDIROL 0x000040ab
+#define VEN_PRESONUS 0x00000a92
+#define VEN_BRIDGECO 0x000007f5
+#define VEN_MACKIE 0x00000ff2
+#define VEN_STANTON 0x00001260
+#define VEN_TASCAM 0x0000022e
+#define VEN_BEHRINGER 0x00001564
+#define VEN_APOGEE 0x000003db
+#define VEN_ESI 0x00000f1b
+#define VEN_CME 0x0000000a
+#define VEN_PHONIC 0x00001496
+#define VEN_LYNX 0x000019e5
+#define VEN_ICON 0x00001a9e
+#define VEN_PRISMSOUND 0x00001198
+#define VEN_TERRATEC 0x00000aac
+#define VEN_YAMAHA 0x0000a0de
+#define VEN_FOCUSRITE 0x0000130e
+#define VEN_MAUDIO 0x00000d6c
+#define VEN_DIGIDESIGN 0x00a07e
+#define OUI_SHOUYO 0x002327
+
+#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
+#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060
+#define MODEL_MAUDIO_FW1814 0x00010071
+#define MODEL_MAUDIO_PROJECTMIX 0x00010091
+#define MODEL_MAUDIO_PROFIRELIGHTBRIDGE 0x000100a1
+
+static int
+name_device(struct snd_bebob *bebob)
+{
+ struct fw_device *fw_dev = fw_parent_device(bebob->unit);
+ char vendor[24] = {0};
+ char model[32] = {0};
+ u32 hw_id;
+ u32 data[2] = {0};
+ u32 revision;
+ int err;
+
+ /* get vendor name from root directory */
+ err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR,
+ vendor, sizeof(vendor));
+ if (err < 0)
+ goto end;
+
+ /* get model name from unit directory */
+ err = fw_csr_string(bebob->unit->directory, CSR_MODEL,
+ model, sizeof(model));
+ if (err < 0)
+ goto end;
+
+ /* get hardware id */
+ err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID,
+ &hw_id);
+ if (err < 0)
+ goto end;
+
+ /* get hardware revision */
+ err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION,
+ &revision);
+ if (err < 0)
+ goto end;
+
+ /* get GUID */
+ err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID,
+ data, sizeof(data));
+ if (err < 0)
+ goto end;
+
+ strscpy(bebob->card->driver, "BeBoB");
+ strscpy(bebob->card->shortname, model);
+ strscpy(bebob->card->mixername, model);
+ snprintf(bebob->card->longname, sizeof(bebob->card->longname),
+ "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d",
+ vendor, model, hw_id, revision,
+ data[0], data[1], dev_name(&bebob->unit->device),
+ 100 << fw_dev->max_speed);
+end:
+ return err;
+}
+
+static void
+bebob_card_free(struct snd_card *card)
+{
+ struct snd_bebob *bebob = card->private_data;
+
+ scoped_guard(mutex, &devices_mutex) {
+ clear_bit(bebob->card_index, devices_used);
+ }
+
+ snd_bebob_stream_destroy_duplex(bebob);
+
+ mutex_destroy(&bebob->mutex);
+ fw_unit_put(bebob->unit);
+}
+
+static const struct snd_bebob_spec *
+get_saffire_spec(struct fw_unit *unit)
+{
+ char name[24] = {0};
+
+ if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
+ return NULL;
+
+ if (strcmp(name, "SaffireLE") == 0)
+ return &saffire_le_spec;
+ else
+ return &saffire_spec;
+}
+
+static bool
+check_audiophile_booted(struct fw_unit *unit)
+{
+ char name[28] = {0};
+
+ if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
+ return false;
+
+ return strncmp(name, "FW Audiophile Bootloader", 24) != 0;
+}
+
+static int detect_quirks(struct snd_bebob *bebob, const struct ieee1394_device_id *entry)
+{
+ if (entry->vendor_id == VEN_MAUDIO) {
+ switch (entry->model_id) {
+ case MODEL_MAUDIO_PROFIRELIGHTBRIDGE:
+ // M-Audio ProFire Lightbridge has a quirk to transfer packets with
+ // discontinuous cycle or data block counter in early stage of packet
+ // streaming. The cycle span from the first packet with event is variable.
+ bebob->quirks |= SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC;
+ break;
+ case MODEL_MAUDIO_FW1814:
+ case MODEL_MAUDIO_PROJECTMIX:
+ // At high sampling rate, M-Audio special firmware transmits empty packet
+ // with the value of dbc incremented by 8.
+ bebob->quirks |= SND_BEBOB_QUIRK_WRONG_DBC;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
+{
+ unsigned int card_index;
+ struct snd_card *card;
+ struct snd_bebob *bebob;
+ const struct snd_bebob_spec *spec;
+ int err;
+
+ if (entry->vendor_id == VEN_FOCUSRITE &&
+ entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)
+ spec = get_saffire_spec(unit);
+ else if (entry->vendor_id == VEN_MAUDIO &&
+ entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH &&
+ !check_audiophile_booted(unit))
+ spec = NULL;
+ else
+ spec = (const struct snd_bebob_spec *)entry->driver_data;
+
+ if (spec == NULL) {
+ // To boot up M-Audio models.
+ if (entry->vendor_id == VEN_MAUDIO || entry->vendor_id == VEN_BRIDGECO)
+ return snd_bebob_maudio_load_firmware(unit);
+ else
+ return -ENODEV;
+ }
+
+ scoped_guard(mutex, &devices_mutex) {
+ for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
+ if (!test_bit(card_index, devices_used) && enable[card_index])
+ break;
+ }
+ if (card_index >= SNDRV_CARDS)
+ return -ENOENT;
+
+ err = snd_card_new(&unit->device, index[card_index], id[card_index], THIS_MODULE,
+ sizeof(*bebob), &card);
+ if (err < 0)
+ return err;
+ card->private_free = bebob_card_free;
+ set_bit(card_index, devices_used);
+ }
+
+ bebob = card->private_data;
+ bebob->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, bebob);
+ bebob->card = card;
+ bebob->card_index = card_index;
+
+ bebob->spec = spec;
+ mutex_init(&bebob->mutex);
+ spin_lock_init(&bebob->lock);
+ init_waitqueue_head(&bebob->hwdep_wait);
+
+ err = name_device(bebob);
+ if (err < 0)
+ goto error;
+
+ err = detect_quirks(bebob, entry);
+ if (err < 0)
+ goto error;
+
+ if (bebob->spec == &maudio_special_spec) {
+ if (entry->model_id == MODEL_MAUDIO_FW1814)
+ err = snd_bebob_maudio_special_discover(bebob, true);
+ else
+ err = snd_bebob_maudio_special_discover(bebob, false);
+ } else {
+ err = snd_bebob_stream_discover(bebob);
+ }
+ if (err < 0)
+ goto error;
+
+ err = snd_bebob_stream_init_duplex(bebob);
+ if (err < 0)
+ goto error;
+
+ snd_bebob_proc_init(bebob);
+
+ if (bebob->midi_input_ports > 0 || bebob->midi_output_ports > 0) {
+ err = snd_bebob_create_midi_devices(bebob);
+ if (err < 0)
+ goto error;
+ }
+
+ err = snd_bebob_create_pcm_devices(bebob);
+ if (err < 0)
+ goto error;
+
+ err = snd_bebob_create_hwdep_device(bebob);
+ if (err < 0)
+ goto error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto error;
+
+ if (entry->vendor_id == VEN_MAUDIO &&
+ (entry->model_id == MODEL_MAUDIO_FW1814 || entry->model_id == MODEL_MAUDIO_PROJECTMIX)) {
+ // This is a workaround. This bus reset seems to have an effect to make devices
+ // correctly handling transactions. Without this, the devices have gap_count
+ // mismatch. This causes much failure of transaction.
+ //
+ // Just after registration, user-land application receive signals from dbus and
+ // starts I/Os. To avoid I/Os till the future bus reset, registration is done in
+ // next update().
+ fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, false, true);
+ }
+
+ return 0;
+error:
+ snd_card_free(card);
+ return err;
+}
+
+/*
+ * This driver doesn't update streams in bus reset handler.
+ *
+ * DM1000/ DM1100/DM1500 chipsets with BeBoB firmware transfer packets with
+ * discontinued counter at bus reset. This discontinuity is immediately
+ * detected in packet streaming layer, then it sets XRUN to PCM substream.
+ *
+ * ALSA PCM applications can know the XRUN by getting -EPIPE from PCM operation.
+ * Then, they can recover the PCM substream by executing ioctl(2) with
+ * SNDRV_PCM_IOCTL_PREPARE. 'struct snd_pcm_ops.prepare' is called and drivers
+ * restart packet streaming.
+ *
+ * The above processing may be executed before this bus-reset handler is
+ * executed. When this handler updates streams with current isochronous
+ * channels, the streams already have the current ones.
+ */
+static void
+bebob_update(struct fw_unit *unit)
+{
+ struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
+
+ if (bebob == NULL)
+ return;
+
+ fcp_bus_reset(bebob->unit);
+}
+
+static void bebob_remove(struct fw_unit *unit)
+{
+ struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
+
+ if (bebob == NULL)
+ return;
+
+ // Block till all of ALSA character devices are released.
+ snd_card_free(bebob->card);
+}
+
+static const struct snd_bebob_rate_spec normal_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate
+};
+static const struct snd_bebob_spec spec_normal = {
+ .clock = NULL,
+ .rate = &normal_rate_spec,
+ .meter = NULL
+};
+
+#define SPECIFIER_1394TA 0x00a02d
+
+// The immediate entry for version in unit directory differs depending on models:
+// * 0x010001
+// * 0x014001
+#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \
+{ \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID | \
+ IEEE1394_MATCH_SPECIFIER_ID, \
+ .vendor_id = vendor, \
+ .model_id = model, \
+ .specifier_id = SPECIFIER_1394TA, \
+ .driver_data = (kernel_ulong_t)data \
+}
+
+static const struct ieee1394_device_id bebob_id_table[] = {
+ /* Edirol, FA-66 */
+ SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal),
+ /* Edirol, FA-101 */
+ SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal),
+ /* Presonus, FIREBOX */
+ SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal),
+ /* PreSonus, FIREPOD/FP10 */
+ SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal),
+ /* PreSonus, Inspire1394 */
+ SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal),
+ /* BridgeCo, RDAudio1 */
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal),
+ /* BridgeCo, Audio5 */
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal),
+ /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
+ // Mackie, d.2 (optional Firewire card with DM1000).
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
+ /* Stanton, ScratchAmp */
+ SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
+ /* Tascam, IF-FW DM */
+ SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal),
+ /* Behringer, XENIX UFX 1204 */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal),
+ /* Behringer, XENIX UFX 1604 */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal),
+ /* Behringer, Digital Mixer X32 series (X-UF Card) */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal),
+ /* Behringer, F-Control Audio 1616 */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x001616, &spec_normal),
+ /* Behringer, F-Control Audio 610 */
+ SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x000610, &spec_normal),
+ /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */
+ /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */
+ SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal),
+ /* Apogee Electronics, Ensemble */
+ SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x01eeee, &spec_normal),
+ /* ESI, Quatafire610 */
+ SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal),
+ /* CME, MatrixKFW */
+ SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal),
+ // Phonic Helix Board 12 FireWire MkII.
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal),
+ // Phonic Helix Board 18 FireWire MkII.
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal),
+ // Phonic Helix Board 24 FireWire MkII.
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal),
+ // Phonic FireFly 808 FireWire.
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00080000, &spec_normal),
+ // Phonic FireFly 202, 302, 808 Universal.
+ // Phinic Helix Board 12/18/24 FireWire, 12/18/24 Universal
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal),
+ /* Lynx, Aurora 8/16 (LT-FW) */
+ SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal),
+ /* ICON, FireXon */
+ SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal),
+ /* PrismSound, Orpheus */
+ SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal),
+ /* PrismSound, ADA-8XR */
+ SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal),
+ /* TerraTec Electronic GmbH, PHASE 88 Rack FW */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec),
+ /* TerraTec Electronic GmbH, PHASE 24 FW */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &yamaha_terratec_spec),
+ /* TerraTec Electronic GmbH, Phase X24 FW */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &yamaha_terratec_spec),
+ /* TerraTec Electronic GmbH, EWS MIC2/MIC8 */
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal),
+ // Terratec Electronic GmbH, Aureon 7.1 Firewire.
+ // AcousticReality, eAR Master One, Eroica, Figaro, and Ciaccona. Perhaps Terratec OEM.
+ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal),
+ /* Yamaha, GO44 */
+ SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_terratec_spec),
+ /* YAMAHA, GO46 */
+ SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_terratec_spec),
+ /* Focusrite, SaffirePro 26 I/O */
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec),
+ /* Focusrite, SaffirePro 10 I/O */
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x000006, &saffirepro_10_spec),
+ /* Focusrite, Saffire(no label and LE) */
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH,
+ &saffire_spec),
+ // M-Audio, Firewire 410. The vendor field is left as BridgeCo. AG.
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010058, NULL),
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010046, &maudio_fw410_spec),
+ /* M-Audio, Firewire Audiophile */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_AUDIOPHILE_BOTH,
+ &maudio_audiophile_spec),
+ /* M-Audio, Firewire Solo */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010062, &maudio_solo_spec),
+ /* M-Audio, Ozonic */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x0000000a, &maudio_ozonic_spec),
+ /* M-Audio NRV10 */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010081, &maudio_nrv10_spec),
+ /* M-Audio, ProFireLightbridge */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_PROFIRELIGHTBRIDGE, &spec_normal),
+ /* Firewire 1814 */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010070, NULL), /* bootloader */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_FW1814,
+ &maudio_special_spec),
+ /* M-Audio ProjectMix */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_PROJECTMIX,
+ &maudio_special_spec),
+ /* Digidesign Mbox 2 Pro */
+ SND_BEBOB_DEV_ENTRY(VEN_DIGIDESIGN, 0x0000a9, &spec_normal),
+ // Toneweal FW66.
+ SND_BEBOB_DEV_ENTRY(OUI_SHOUYO, 0x020002, &spec_normal),
+ /* IDs are unknown but able to be supported */
+ /* Apogee, Mini-ME Firewire */
+ /* Apogee, Mini-DAC Firewire */
+ /* Cakawalk, Sonar Power Studio 66 */
+ /* CME, UF400e */
+ /* ESI, Quotafire XL */
+ /* Infrasonic, DewX */
+ /* Infrasonic, Windy6 */
+ /* Mackie, Digital X Bus x.200 */
+ /* Mackie, Digital X Bus x.400 */
+ /* Rolf Spuler, Firewire Guitar */
+ {}
+};
+MODULE_DEVICE_TABLE(ieee1394, bebob_id_table);
+
+static struct fw_driver bebob_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = KBUILD_MODNAME,
+ .bus = &fw_bus_type,
+ },
+ .probe = bebob_probe,
+ .update = bebob_update,
+ .remove = bebob_remove,
+ .id_table = bebob_id_table,
+};
+
+static int __init
+snd_bebob_init(void)
+{
+ return driver_register(&bebob_driver.driver);
+}
+
+static void __exit
+snd_bebob_exit(void)
+{
+ driver_unregister(&bebob_driver.driver);
+}
+
+module_init(snd_bebob_init);
+module_exit(snd_bebob_exit);
diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h
new file mode 100644
index 000000000..41e019b9e
--- /dev/null
+++ b/sound/firewire/bebob/bebob.h
@@ -0,0 +1,255 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * bebob.h - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#ifndef SOUND_BEBOB_H_INCLUDED
+#define SOUND_BEBOB_H_INCLUDED
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
+
+#include "../lib.h"
+#include "../fcp.h"
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp-am824.h"
+#include "../cmp.h"
+
+/* basic register addresses on DM1000/DM1100/DM1500 */
+#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL
+#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL
+
+struct snd_bebob;
+
+#define SND_BEBOB_STRM_FMT_ENTRIES 7
+struct snd_bebob_stream_formation {
+ unsigned int pcm;
+ unsigned int midi;
+};
+/* this is a lookup table for index of stream formations */
+extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES];
+
+/* device specific operations */
+enum snd_bebob_clock_type {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL = 0,
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL,
+ SND_BEBOB_CLOCK_TYPE_SYT,
+};
+struct snd_bebob_clock_spec {
+ unsigned int num;
+ const char *const *labels;
+ const enum snd_bebob_clock_type *types;
+ int (*get)(struct snd_bebob *bebob, unsigned int *id);
+};
+struct snd_bebob_rate_spec {
+ int (*get)(struct snd_bebob *bebob, unsigned int *rate);
+ int (*set)(struct snd_bebob *bebob, unsigned int rate);
+};
+struct snd_bebob_meter_spec {
+ unsigned int num;
+ const char *const *labels;
+ int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
+};
+struct snd_bebob_spec {
+ const struct snd_bebob_clock_spec *clock;
+ const struct snd_bebob_rate_spec *rate;
+ const struct snd_bebob_meter_spec *meter;
+};
+
+enum snd_bebob_quirk {
+ SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC = (1 << 0),
+ SND_BEBOB_QUIRK_WRONG_DBC = (1 << 1),
+};
+
+struct snd_bebob {
+ struct snd_card *card;
+ struct fw_unit *unit;
+ int card_index;
+
+ struct mutex mutex;
+ spinlock_t lock;
+
+ const struct snd_bebob_spec *spec;
+ unsigned int quirks; // Combination of snd_bebob_quirk enumerations.
+
+ unsigned int midi_input_ports;
+ unsigned int midi_output_ports;
+
+ struct amdtp_stream tx_stream;
+ struct amdtp_stream rx_stream;
+ struct cmp_connection out_conn;
+ struct cmp_connection in_conn;
+ unsigned int substreams_counter;
+
+ struct snd_bebob_stream_formation
+ tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
+ struct snd_bebob_stream_formation
+ rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
+
+ int sync_input_plug;
+
+ /* for uapi */
+ int dev_lock_count;
+ bool dev_lock_changed;
+ wait_queue_head_t hwdep_wait;
+
+ /* for M-Audio special devices */
+ void *maudio_special_quirk;
+
+ struct amdtp_domain domain;
+};
+
+static inline int
+snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size)
+{
+ return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST,
+ BEBOB_ADDR_REG_INFO + addr,
+ buf, size, 0);
+}
+
+static inline int
+snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf)
+{
+ return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
+ BEBOB_ADDR_REG_INFO + addr,
+ (void *)buf, sizeof(u32), 0);
+}
+
+/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */
+int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int num);
+int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int *num);
+
+/*
+ * AVC command extensions, AV/C Unit and Subunit, Revision 17
+ * (Nov 2003, BridgeCo)
+ */
+#define AVC_BRIDGECO_ADDR_BYTES 6
+enum avc_bridgeco_plug_dir {
+ AVC_BRIDGECO_PLUG_DIR_IN = 0x00,
+ AVC_BRIDGECO_PLUG_DIR_OUT = 0x01
+};
+enum avc_bridgeco_plug_mode {
+ AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00,
+ AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01,
+ AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02
+};
+enum avc_bridgeco_plug_unit {
+ AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00,
+ AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01,
+ AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02
+};
+enum avc_bridgeco_plug_type {
+ AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00,
+ AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01,
+ AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02,
+ AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03,
+ AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04,
+ AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05,
+ AVC_BRIDGECO_PLUG_TYPE_ADDITION = 0x06
+};
+static inline void
+avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_dir dir,
+ enum avc_bridgeco_plug_unit unit,
+ unsigned int pid)
+{
+ buf[0] = 0xff; /* Unit */
+ buf[1] = dir;
+ buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT;
+ buf[3] = unit;
+ buf[4] = 0xff & pid;
+ buf[5] = 0xff; /* reserved */
+}
+static inline void
+avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_dir dir,
+ unsigned int pid)
+{
+ buf[0] = 0x60; /* Music subunit */
+ buf[1] = dir;
+ buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT;
+ buf[3] = 0xff & pid;
+ buf[4] = 0xff; /* reserved */
+ buf[5] = 0xff; /* reserved */
+}
+int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ u8 *buf, unsigned int len);
+int avc_bridgeco_get_plug_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_type *type);
+int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int *ch_count);
+int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int id, u8 *type);
+int avc_bridgeco_get_plug_input(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ u8 input[7]);
+int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
+ unsigned int *len, unsigned int eid);
+
+/* for AMDTP streaming */
+int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate);
+int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate);
+int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
+ enum snd_bebob_clock_type *src);
+int snd_bebob_stream_discover(struct snd_bebob *bebob);
+int snd_bebob_stream_init_duplex(struct snd_bebob *bebob);
+int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate,
+ unsigned int frames_per_period,
+ unsigned int frames_per_buffer);
+int snd_bebob_stream_start_duplex(struct snd_bebob *bebob);
+void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob);
+void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
+
+void snd_bebob_stream_lock_changed(struct snd_bebob *bebob);
+int snd_bebob_stream_lock_try(struct snd_bebob *bebob);
+void snd_bebob_stream_lock_release(struct snd_bebob *bebob);
+
+void snd_bebob_proc_init(struct snd_bebob *bebob);
+
+int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
+
+int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
+
+int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
+
+/* model specific operations */
+extern const struct snd_bebob_spec phase88_rack_spec;
+extern const struct snd_bebob_spec yamaha_terratec_spec;
+extern const struct snd_bebob_spec saffirepro_26_spec;
+extern const struct snd_bebob_spec saffirepro_10_spec;
+extern const struct snd_bebob_spec saffire_le_spec;
+extern const struct snd_bebob_spec saffire_spec;
+extern const struct snd_bebob_spec maudio_fw410_spec;
+extern const struct snd_bebob_spec maudio_audiophile_spec;
+extern const struct snd_bebob_spec maudio_solo_spec;
+extern const struct snd_bebob_spec maudio_ozonic_spec;
+extern const struct snd_bebob_spec maudio_nrv10_spec;
+extern const struct snd_bebob_spec maudio_special_spec;
+int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
+int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
+
+#endif
diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c
new file mode 100644
index 000000000..022df09c6
--- /dev/null
+++ b/sound/firewire/bebob/bebob_command.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_command.c - driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int num)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x00; /* AV/C CONTROL */
+ buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */
+ buf[2] = 0xb8; /* FUNCTION BLOCK */
+ buf[3] = 0x80; /* type is 'selector'*/
+ buf[4] = 0xff & fb_id; /* function block id */
+ buf[5] = 0x10; /* control attribute is CURRENT */
+ buf[6] = 0x02; /* selector length is 2 */
+ buf[7] = 0xff & num; /* input function block plug number */
+ buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7) | BIT(8));
+ if (err < 0)
+ ;
+ else if (err < 9)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else
+ err = 0;
+
+ kfree(buf);
+ return err;
+}
+
+int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
+ unsigned int fb_id, unsigned int *num)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */
+ buf[2] = 0xb8; /* FUNCTION BLOCK */
+ buf[3] = 0x80; /* type is 'selector'*/
+ buf[4] = 0xff & fb_id; /* function block id */
+ buf[5] = 0x10; /* control attribute is CURRENT */
+ buf[6] = 0x02; /* selector length is 2 */
+ buf[7] = 0xff; /* input function block plug number */
+ buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(8));
+ if (err < 0)
+ ;
+ else if (err < 9)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *num = buf[7];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+static inline void
+avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr)
+{
+ buf[1] = addr[0];
+ memcpy(buf + 4, addr + 1, 5);
+}
+
+static inline void
+avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr,
+ unsigned int itype)
+{
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */
+ buf[3] = 0xc0; /* BridgeCo extension */
+ avc_bridgeco_fill_extension_addr(buf, addr);
+ buf[9] = itype; /* info type */
+}
+
+int avc_bridgeco_get_plug_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_type *type)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* Info type is 'plug type'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00);
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7) | BIT(9));
+ if (err < 0)
+ ;
+ else if (err < 11)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *type = buf[10];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int *ch_count)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ // Info type is 'plug type'.
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x02);
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7) | BIT(9));
+ if (err < 0)
+ ;
+ else if (err < 11)
+ err = -EIO;
+ else if (buf[0] == 0x08) // NOT IMPLEMENTED
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) // REJECTED
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) // IN TRANSITION
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *ch_count = buf[10];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ u8 *buf, unsigned int len)
+{
+ int err;
+
+ /* Info type is 'channel position'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03);
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 256,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) |
+ BIT(5) | BIT(6) | BIT(7) | BIT(9));
+ if (err < 0)
+ ;
+ else if (err < 11)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ /* Pick up specific data. */
+ memmove(buf, buf + 10, err - 10);
+ err = 0;
+end:
+ return err;
+}
+
+int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int id, u8 *type)
+{
+ u8 *buf;
+ int err;
+
+ /* section info includes charactors but this module don't need it */
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* Info type is 'section info'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07);
+ buf[10] = 0xff & ++id; /* section id */
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7) | BIT(9) | BIT(10));
+ if (err < 0)
+ ;
+ else if (err < 12)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *type = buf[11];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_input(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7])
+{
+ int err;
+ u8 *buf;
+
+ buf = kzalloc(18, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ /* Info type is 'plug input'. */
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05);
+
+ err = fcp_avc_transaction(unit, buf, 16, buf, 16,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7));
+ if (err < 0)
+ ;
+ else if (err < 16)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ memcpy(input, buf + 10, 5);
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
+int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
+ unsigned int *len, unsigned int eid)
+{
+ int err;
+
+ /* check given buffer */
+ if ((buf == NULL) || (*len < 12)) {
+ err = -EINVAL;
+ goto end;
+ }
+
+ buf[0] = 0x01; /* AV/C STATUS */
+ buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */
+ buf[3] = 0xc1; /* Bridgeco extension - List Request */
+ avc_bridgeco_fill_extension_addr(buf, addr);
+ buf[10] = 0xff & eid; /* Entry ID */
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, *len,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7) | BIT(10));
+ if (err < 0)
+ ;
+ else if (err < 12)
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) /* IN TRANSITION */
+ err = -EAGAIN;
+ else if (buf[10] != eid)
+ err = -EIO;
+ if (err < 0)
+ goto end;
+
+ /* Pick up 'stream format info'. */
+ memmove(buf, buf + 11, err - 11);
+ *len = err - 11;
+ err = 0;
+end:
+ return err;
+}
diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c
new file mode 100644
index 000000000..06d6a37cd
--- /dev/null
+++ b/sound/firewire/bebob/bebob_focusrite.c
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_focusrite.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+#define ANA_IN "Analog In"
+#define DIG_IN "Digital In"
+#define ANA_OUT "Analog Out"
+#define DIG_OUT "Digital Out"
+#define STM_IN "Stream In"
+
+#define SAFFIRE_ADDRESS_BASE 0x000100000000ULL
+
+#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8
+#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174
+
+/* whether sync to external device or not */
+#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c
+#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432
+#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164
+
+#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0
+#define SAFFIRE_CLOCK_SOURCE_SPDIF 1
+
+/* clock sources as returned from register of Saffire Pro 10 and 26 */
+#define SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK 0x000000ff
+#define SAFFIREPRO_CLOCK_SOURCE_DETECT_MASK 0x0000ff00
+#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0
+#define SAFFIREPRO_CLOCK_SOURCE_SKIP 1 /* never used on hardware */
+#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2
+#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 /* not used on s.pro. 10 */
+#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 /* not used on s.pro. 10 */
+#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5
+#define SAFFIREPRO_CLOCK_SOURCE_COUNT 6
+
+/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */
+#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4
+
+/* saffirepro has its own parameter for sampling frequency */
+#define SAFFIREPRO_RATE_NOREBOOT 0x01cc
+/* index is the value for this register */
+static const unsigned int rates[] = {
+ [0] = 0,
+ [1] = 44100,
+ [2] = 48000,
+ [3] = 88200,
+ [4] = 96000,
+ [5] = 176400,
+ [6] = 192000
+};
+
+/* saffire(no label)/saffire LE has metering */
+#define SAFFIRE_OFFSET_METER 0x0100
+#define SAFFIRE_LE_OFFSET_METER 0x0168
+
+static inline int
+saffire_read_block(struct snd_bebob *bebob, u64 offset,
+ u32 *buf, unsigned int size)
+{
+ unsigned int i;
+ int err;
+ __be32 *tmp = (__be32 *)buf;
+
+ err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
+ SAFFIRE_ADDRESS_BASE + offset,
+ tmp, size, 0);
+ if (err < 0)
+ goto end;
+
+ for (i = 0; i < size / sizeof(u32); i++)
+ buf[i] = be32_to_cpu(tmp[i]);
+end:
+ return err;
+}
+
+static inline int
+saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value)
+{
+ int err;
+ __be32 tmp;
+
+ err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST,
+ SAFFIRE_ADDRESS_BASE + offset,
+ &tmp, sizeof(__be32), 0);
+ if (err < 0)
+ goto end;
+
+ *value = be32_to_cpu(tmp);
+end:
+ return err;
+}
+
+static inline int
+saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value)
+{
+ __be32 data = cpu_to_be32(value);
+
+ return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST,
+ SAFFIRE_ADDRESS_BASE + offset,
+ &data, sizeof(__be32), 0);
+}
+
+static const enum snd_bebob_clock_type saffirepro_10_clk_src_types[] = {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL,
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
+};
+static const enum snd_bebob_clock_type saffirepro_26_clk_src_types[] = {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL,
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT1 */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT2 */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
+};
+/* Value maps between registers and labels for SaffirePro 10/26. */
+static const signed char saffirepro_clk_maps[][SAFFIREPRO_CLOCK_SOURCE_COUNT] = {
+ /* SaffirePro 10 */
+ [0] = {
+ [SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0,
+ [SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */
+ [SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1,
+ [SAFFIREPRO_CLOCK_SOURCE_ADAT1] = -1, /* not supported */
+ [SAFFIREPRO_CLOCK_SOURCE_ADAT2] = -1, /* not supported */
+ [SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 2,
+ },
+ /* SaffirePro 26 */
+ [1] = {
+ [SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0,
+ [SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */
+ [SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1,
+ [SAFFIREPRO_CLOCK_SOURCE_ADAT1] = 2,
+ [SAFFIREPRO_CLOCK_SOURCE_ADAT2] = 3,
+ [SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 4,
+ }
+};
+
+static int
+saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate)
+{
+ u32 id;
+ int err;
+
+ err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id);
+ if (err < 0)
+ goto end;
+ if (id >= ARRAY_SIZE(rates))
+ err = -EIO;
+ else
+ *rate = rates[id];
+end:
+ return err;
+}
+static int
+saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate)
+{
+ u32 id;
+
+ for (id = 0; id < ARRAY_SIZE(rates); id++) {
+ if (rates[id] == rate)
+ break;
+ }
+ if (id == ARRAY_SIZE(rates))
+ return -EINVAL;
+
+ return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id);
+}
+
+/*
+ * query hardware for current clock source, return our internally
+ * used clock index in *id, depending on hardware.
+ */
+static int
+saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ int err;
+ u32 value; /* clock source read from hw register */
+ const signed char *map;
+
+ err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value);
+ if (err < 0)
+ goto end;
+
+ /* depending on hardware, use a different mapping */
+ if (bebob->spec->clock->types == saffirepro_10_clk_src_types)
+ map = saffirepro_clk_maps[0];
+ else
+ map = saffirepro_clk_maps[1];
+
+ /* In a case that this driver cannot handle the value of register. */
+ value &= SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK;
+ if (value >= SAFFIREPRO_CLOCK_SOURCE_COUNT || map[value] < 0) {
+ err = -EIO;
+ goto end;
+ }
+
+ *id = (unsigned int)map[value];
+end:
+ return err;
+}
+
+const struct snd_bebob_spec saffire_le_spec;
+static const enum snd_bebob_clock_type saffire_both_clk_src_types[] = {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL,
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL,
+};
+static int
+saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ int err;
+ u32 value;
+
+ err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value);
+ if (err >= 0)
+ *id = 0xff & value;
+
+ return err;
+};
+static const char *const saffire_le_meter_labels[] = {
+ ANA_IN, ANA_IN, DIG_IN,
+ ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
+ STM_IN, STM_IN
+};
+static const char *const saffire_meter_labels[] = {
+ ANA_IN, ANA_IN,
+ STM_IN, STM_IN, STM_IN, STM_IN, STM_IN,
+};
+static int
+saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
+{
+ const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ unsigned int channels;
+ u64 offset;
+ int err;
+
+ if (spec->labels == saffire_le_meter_labels)
+ offset = SAFFIRE_LE_OFFSET_METER;
+ else
+ offset = SAFFIRE_OFFSET_METER;
+
+ channels = spec->num * 2;
+ if (size < channels * sizeof(u32))
+ return -EIO;
+
+ err = saffire_read_block(bebob, offset, buf, size);
+ if (err >= 0 && spec->labels == saffire_le_meter_labels) {
+ swap(buf[1], buf[3]);
+ swap(buf[2], buf[3]);
+ swap(buf[3], buf[4]);
+
+ swap(buf[7], buf[10]);
+ swap(buf[8], buf[10]);
+ swap(buf[9], buf[11]);
+ swap(buf[11], buf[12]);
+
+ swap(buf[15], buf[16]);
+ }
+
+ return err;
+}
+
+static const struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
+ .get = &saffirepro_both_clk_freq_get,
+ .set = &saffirepro_both_clk_freq_set,
+};
+/* Saffire Pro 26 I/O */
+static const struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
+ .num = ARRAY_SIZE(saffirepro_26_clk_src_types),
+ .types = saffirepro_26_clk_src_types,
+ .get = &saffirepro_both_clk_src_get,
+};
+const struct snd_bebob_spec saffirepro_26_spec = {
+ .clock = &saffirepro_26_clk_spec,
+ .rate = &saffirepro_both_rate_spec,
+ .meter = NULL
+};
+/* Saffire Pro 10 I/O */
+static const struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
+ .num = ARRAY_SIZE(saffirepro_10_clk_src_types),
+ .types = saffirepro_10_clk_src_types,
+ .get = &saffirepro_both_clk_src_get,
+};
+const struct snd_bebob_spec saffirepro_10_spec = {
+ .clock = &saffirepro_10_clk_spec,
+ .rate = &saffirepro_both_rate_spec,
+ .meter = NULL
+};
+
+static const struct snd_bebob_rate_spec saffire_both_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+static const struct snd_bebob_clock_spec saffire_both_clk_spec = {
+ .num = ARRAY_SIZE(saffire_both_clk_src_types),
+ .types = saffire_both_clk_src_types,
+ .get = &saffire_both_clk_src_get,
+};
+/* Saffire LE */
+static const struct snd_bebob_meter_spec saffire_le_meter_spec = {
+ .num = ARRAY_SIZE(saffire_le_meter_labels),
+ .labels = saffire_le_meter_labels,
+ .get = &saffire_meter_get,
+};
+const struct snd_bebob_spec saffire_le_spec = {
+ .clock = &saffire_both_clk_spec,
+ .rate = &saffire_both_rate_spec,
+ .meter = &saffire_le_meter_spec
+};
+/* Saffire */
+static const struct snd_bebob_meter_spec saffire_meter_spec = {
+ .num = ARRAY_SIZE(saffire_meter_labels),
+ .labels = saffire_meter_labels,
+ .get = &saffire_meter_get,
+};
+const struct snd_bebob_spec saffire_spec = {
+ .clock = &saffire_both_clk_spec,
+ .rate = &saffire_both_rate_spec,
+ .meter = &saffire_meter_spec
+};
diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c
new file mode 100644
index 000000000..216d1fceb
--- /dev/null
+++ b/sound/firewire/bebob/bebob_hwdep.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_hwdep.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+/*
+ * This codes give three functionality.
+ *
+ * 1.get firewire node infomation
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock stream
+ */
+
+#include "bebob.h"
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
+ loff_t *offset)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+ DEFINE_WAIT(wait);
+ union snd_firewire_event event;
+
+ spin_lock_irq(&bebob->lock);
+
+ while (!bebob->dev_lock_changed) {
+ prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+ spin_unlock_irq(&bebob->lock);
+ schedule();
+ finish_wait(&bebob->hwdep_wait, &wait);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ spin_lock_irq(&bebob->lock);
+ }
+
+ memset(&event, 0, sizeof(event));
+ count = min_t(long, count, sizeof(event.lock_status));
+ event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+ event.lock_status.status = (bebob->dev_lock_count > 0);
+ bebob->dev_lock_changed = false;
+
+ spin_unlock_irq(&bebob->lock);
+
+ if (copy_to_user(buf, &event, count))
+ return -EFAULT;
+
+ return count;
+}
+
+static __poll_t
+hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+
+ poll_wait(file, &bebob->hwdep_wait, wait);
+
+ guard(spinlock_irq)(&bebob->lock);
+ if (bebob->dev_lock_changed)
+ return EPOLLIN | EPOLLRDNORM;
+ else
+ return 0;
+}
+
+static int
+hwdep_get_info(struct snd_bebob *bebob, void __user *arg)
+{
+ struct fw_device *dev = fw_parent_device(bebob->unit);
+ struct snd_firewire_get_info info;
+
+ memset(&info, 0, sizeof(info));
+ info.type = SNDRV_FIREWIRE_TYPE_BEBOB;
+ info.card = dev->card->index;
+ *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
+ *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
+ strscpy(info.device_name, dev_name(&dev->device),
+ sizeof(info.device_name));
+
+ if (copy_to_user(arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int
+hwdep_lock(struct snd_bebob *bebob)
+{
+ guard(spinlock_irq)(&bebob->lock);
+
+ if (bebob->dev_lock_count == 0) {
+ bebob->dev_lock_count = -1;
+ return 0;
+ } else {
+ return -EBUSY;
+ }
+}
+
+static int
+hwdep_unlock(struct snd_bebob *bebob)
+{
+ guard(spinlock_irq)(&bebob->lock);
+
+ if (bebob->dev_lock_count == -1) {
+ bebob->dev_lock_count = 0;
+ return 0;
+ } else {
+ return -EBADFD;
+ }
+}
+
+static int
+hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+
+ guard(spinlock_irq)(&bebob->lock);
+ if (bebob->dev_lock_count == -1)
+ bebob->dev_lock_count = 0;
+
+ return 0;
+}
+
+static int
+hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct snd_bebob *bebob = hwdep->private_data;
+
+ switch (cmd) {
+ case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+ return hwdep_get_info(bebob, (void __user *)arg);
+ case SNDRV_FIREWIRE_IOCTL_LOCK:
+ return hwdep_lock(bebob);
+ case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+ return hwdep_unlock(bebob);
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+#ifdef CONFIG_COMPAT
+static int
+hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ return hwdep_ioctl(hwdep, file, cmd,
+ (unsigned long)compat_ptr(arg));
+}
+#else
+#define hwdep_compat_ioctl NULL
+#endif
+
+int snd_bebob_create_hwdep_device(struct snd_bebob *bebob)
+{
+ static const struct snd_hwdep_ops ops = {
+ .read = hwdep_read,
+ .release = hwdep_release,
+ .poll = hwdep_poll,
+ .ioctl = hwdep_ioctl,
+ .ioctl_compat = hwdep_compat_ioctl,
+ };
+ struct snd_hwdep *hwdep;
+ int err;
+
+ err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep);
+ if (err < 0)
+ goto end;
+ strscpy(hwdep->name, "BeBoB");
+ hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB;
+ hwdep->ops = ops;
+ hwdep->private_data = bebob;
+ hwdep->exclusive = true;
+end:
+ return err;
+}
+
diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c
new file mode 100644
index 000000000..376a9a175
--- /dev/null
+++ b/sound/firewire/bebob/bebob_maudio.c
@@ -0,0 +1,785 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_maudio.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+#include <sound/control.h>
+
+/*
+ * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to
+ * download firmware blob. To enable these devices, drivers should upload
+ * firmware blob and send a command to initialize configuration to factory
+ * settings when completing uploading. Then these devices generate bus reset
+ * and are recognized as new devices with the firmware.
+ *
+ * But with firmware version 5058 or later, the firmware is stored to flash
+ * memory in the device and drivers can tell bootloader to load the firmware
+ * by sending a cue. This cue must be sent one time.
+ *
+ * For streaming, both of output and input streams are needed for Firewire 410
+ * and Ozonic. The single stream is OK for the other devices even if the clock
+ * source is not SYT-Match (I note no devices use SYT-Match).
+ *
+ * Without streaming, the devices except for Firewire Audiophile can mix any
+ * input and output. For this reason, Audiophile cannot be used as standalone
+ * mixer.
+ *
+ * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed
+ * when receiving any commands which the firmware can't understand. These
+ * devices utilize completely different system to control. It is some
+ * write-transaction directly into a certain address. All of addresses for mixer
+ * functionality is between 0xffc700700000 to 0xffc70070009c.
+ */
+
+/* Offset from information register */
+#define INFO_OFFSET_SW_DATE 0x20
+
+/* Bootloader Protocol Version 1 */
+#define MAUDIO_BOOTLOADER_CUE1 0x00000001
+/*
+ * Initializing configuration to factory settings (= 0x1101), (swapped in line),
+ * Command code is zero (= 0x00),
+ * the number of operands is zero (= 0x00)(at least significant byte)
+ */
+#define MAUDIO_BOOTLOADER_CUE2 0x01110000
+/* padding */
+#define MAUDIO_BOOTLOADER_CUE3 0x00000000
+
+#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL
+
+#define METER_OFFSET 0x00600000
+
+/* some device has sync info after metering data */
+#define METER_SIZE_SPECIAL 84 /* with sync info */
+#define METER_SIZE_FW410 76 /* with sync info */
+#define METER_SIZE_AUDIOPHILE 60 /* with sync info */
+#define METER_SIZE_SOLO 52 /* with sync info */
+#define METER_SIZE_OZONIC 48
+#define METER_SIZE_NRV10 80
+
+/* labels for metering */
+#define ANA_IN "Analog In"
+#define ANA_OUT "Analog Out"
+#define DIG_IN "Digital In"
+#define SPDIF_IN "S/PDIF In"
+#define ADAT_IN "ADAT In"
+#define DIG_OUT "Digital Out"
+#define SPDIF_OUT "S/PDIF Out"
+#define ADAT_OUT "ADAT Out"
+#define STRM_IN "Stream In"
+#define AUX_OUT "Aux Out"
+#define HP_OUT "HP Out"
+/* for NRV */
+#define UNKNOWN_METER "Unknown"
+
+struct special_params {
+ bool is1814;
+ unsigned int clk_src;
+ unsigned int dig_in_fmt;
+ unsigned int dig_out_fmt;
+ unsigned int clk_lock;
+ struct snd_ctl_elem_id *ctl_id_sync;
+};
+
+/*
+ * For some M-Audio devices, this module just send cue to load firmware. After
+ * loading, the device generates bus reset and newly detected.
+ *
+ * If we make any transactions to load firmware, the operation may failed.
+ */
+int snd_bebob_maudio_load_firmware(struct fw_unit *unit)
+{
+ struct fw_device *device = fw_parent_device(unit);
+ int err, rcode;
+ u64 date;
+ __le32 *cues;
+
+ /* check date of software used to build */
+ err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE,
+ &date, sizeof(u64));
+ if (err < 0)
+ return err;
+ /*
+ * firmware version 5058 or later has date later than "20070401", but
+ * 'date' is not null-terminated.
+ */
+ if (date < 0x3230303730343031LL) {
+ dev_err(&unit->device,
+ "Use firmware version 5058 or later\n");
+ return -ENXIO;
+ }
+
+ cues = kmalloc_array(3, sizeof(*cues), GFP_KERNEL);
+ if (!cues)
+ return -ENOMEM;
+
+ cues[0] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE1);
+ cues[1] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE2);
+ cues[2] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE3);
+
+ rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST,
+ device->node_id, device->generation,
+ device->max_speed, BEBOB_ADDR_REG_REQ,
+ cues, 3 * sizeof(*cues));
+ kfree(cues);
+ if (rcode != RCODE_COMPLETE) {
+ dev_err(&unit->device,
+ "Failed to send a cue to load firmware\n");
+ err = -EIO;
+ }
+
+ return err;
+}
+
+static inline int
+get_meter(struct snd_bebob *bebob, void *buf, unsigned int size)
+{
+ return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
+ MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET,
+ buf, size, 0);
+}
+
+static int
+check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync)
+{
+ int err;
+ u8 *buf;
+
+ buf = kmalloc(size, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ err = get_meter(bebob, buf, size);
+ if (err < 0)
+ goto end;
+
+ /* if synced, this value is the same as SFC of FDF in CIP header */
+ *sync = (buf[size - 2] != 0xff);
+end:
+ kfree(buf);
+ return err;
+}
+
+/*
+ * dig_fmt: 0x00:S/PDIF, 0x01:ADAT
+ * clk_lock: 0x00:unlock, 0x01:lock
+ */
+static int
+avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src,
+ unsigned int dig_in_fmt, unsigned int dig_out_fmt,
+ unsigned int clk_lock)
+{
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err;
+ u8 *buf;
+
+ if (amdtp_stream_running(&bebob->rx_stream) ||
+ amdtp_stream_running(&bebob->tx_stream))
+ return -EBUSY;
+
+ buf = kmalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ buf[0] = 0x00; /* CONTROL */
+ buf[1] = 0xff; /* UNIT */
+ buf[2] = 0x00; /* vendor dependent */
+ buf[3] = 0x04; /* company ID high */
+ buf[4] = 0x00; /* company ID middle */
+ buf[5] = 0x04; /* company ID low */
+ buf[6] = 0xff & clk_src; /* clock source */
+ buf[7] = 0xff & dig_in_fmt; /* input digital format */
+ buf[8] = 0xff & dig_out_fmt; /* output digital format */
+ buf[9] = 0xff & clk_lock; /* lock these settings */
+ buf[10] = 0x00; /* padding */
+ buf[11] = 0x00; /* padding */
+
+ err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) |
+ BIT(5) | BIT(6) | BIT(7) | BIT(8) |
+ BIT(9));
+ if ((err > 0) && (err < 10))
+ err = -EIO;
+ else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) /* REJECTED */
+ err = -EINVAL;
+ if (err < 0)
+ goto end;
+
+ params->clk_src = buf[6];
+ params->dig_in_fmt = buf[7];
+ params->dig_out_fmt = buf[8];
+ params->clk_lock = buf[9];
+
+ if (params->ctl_id_sync)
+ snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ params->ctl_id_sync);
+
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+static void
+special_stream_formation_set(struct snd_bebob *bebob)
+{
+ static const unsigned int ch_table[2][2][3] = {
+ /* AMDTP_OUT_STREAM */
+ { { 6, 6, 4 }, /* SPDIF */
+ { 12, 8, 4 } }, /* ADAT */
+ /* AMDTP_IN_STREAM */
+ { { 10, 10, 2 }, /* SPDIF */
+ { 16, 12, 2 } } /* ADAT */
+ };
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int i, max;
+
+ max = SND_BEBOB_STRM_FMT_ENTRIES - 1;
+ if (!params->is1814)
+ max -= 2;
+
+ for (i = 0; i < max; i++) {
+ bebob->tx_stream_formations[i + 1].pcm =
+ ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2];
+ bebob->tx_stream_formations[i + 1].midi = 1;
+
+ bebob->rx_stream_formations[i + 1].pcm =
+ ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2];
+ bebob->rx_stream_formations[i + 1].midi = 1;
+ }
+}
+
+static int add_special_controls(struct snd_bebob *bebob);
+int
+snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814)
+{
+ struct special_params *params;
+ int err;
+
+ params = devm_kzalloc(&bebob->card->card_dev,
+ sizeof(struct special_params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ guard(mutex)(&bebob->mutex);
+
+ bebob->maudio_special_quirk = (void *)params;
+ params->is1814 = is1814;
+
+ /* initialize these parameters because driver is not allowed to ask */
+ bebob->rx_stream.context = ERR_PTR(-1);
+ bebob->tx_stream.context = ERR_PTR(-1);
+ err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to initialize clock params: %d\n", err);
+ return err;
+ }
+
+ err = add_special_controls(bebob);
+ if (err < 0)
+ return err;
+
+ special_stream_formation_set(bebob);
+
+ if (params->is1814) {
+ bebob->midi_input_ports = 1;
+ bebob->midi_output_ports = 1;
+ } else {
+ bebob->midi_input_ports = 2;
+ bebob->midi_output_ports = 2;
+ }
+ return err;
+}
+
+/* Input plug shows actual rate. Output plug is needless for this purpose. */
+static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate)
+{
+ int err, trials;
+
+ trials = 0;
+ do {
+ err = avc_general_get_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ } while (err == -EAGAIN && ++trials < 3);
+
+ return err;
+}
+static int special_set_rate(struct snd_bebob *bebob, unsigned int rate)
+{
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err;
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_OUT, 0);
+ if (err < 0)
+ goto end;
+
+ /*
+ * Just after changing sampling rate for output, a followed command
+ * for input is easy to fail. This is a workaround fot this issue.
+ */
+ msleep(100);
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ if (err < 0)
+ goto end;
+
+ if (params->ctl_id_sync)
+ snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
+ params->ctl_id_sync);
+end:
+ return err;
+}
+
+/* Clock source control for special firmware */
+static const enum snd_bebob_clock_type special_clk_types[] = {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL, /* With digital mute */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* SPDIF/ADAT */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
+ SND_BEBOB_CLOCK_TYPE_INTERNAL,
+};
+static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ struct special_params *params = bebob->maudio_special_quirk;
+ *id = params->clk_src;
+ return 0;
+}
+static int special_clk_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ static const char *const special_clk_labels[] = {
+ "Internal with Digital Mute",
+ "Digital",
+ "Word Clock",
+ "Internal"
+ };
+ return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_types),
+ special_clk_labels);
+}
+static int special_clk_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ uval->value.enumerated.item[0] = params->clk_src;
+ return 0;
+}
+static int special_clk_ctl_put(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err, id;
+
+ id = uval->value.enumerated.item[0];
+ if (id >= ARRAY_SIZE(special_clk_types))
+ return -EINVAL;
+
+ guard(mutex)(&bebob->mutex);
+
+ err = avc_maudio_set_special_clk(bebob, id,
+ params->dig_in_fmt,
+ params->dig_out_fmt,
+ params->clk_lock);
+ if (err >= 0)
+ err = 1;
+
+ return err;
+}
+static const struct snd_kcontrol_new special_clk_ctl = {
+ .name = "Clock Source",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = special_clk_ctl_info,
+ .get = special_clk_ctl_get,
+ .put = special_clk_ctl_put
+};
+
+/* Clock synchronization control for special firmware */
+static int special_sync_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ einf->count = 1;
+ einf->value.integer.min = 0;
+ einf->value.integer.max = 1;
+
+ return 0;
+}
+static int special_sync_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ int err;
+ bool synced = 0;
+
+ err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced);
+ if (err >= 0)
+ uval->value.integer.value[0] = synced;
+
+ return 0;
+}
+static const struct snd_kcontrol_new special_sync_ctl = {
+ .name = "Sync Status",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .info = special_sync_ctl_info,
+ .get = special_sync_ctl_get,
+};
+
+/* Digital input interface control for special firmware */
+static const char *const special_dig_in_iface_labels[] = {
+ "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"
+};
+static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ return snd_ctl_enum_info(einf, 1,
+ ARRAY_SIZE(special_dig_in_iface_labels),
+ special_dig_in_iface_labels);
+}
+static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int dig_in_iface;
+ int err, val;
+
+ guard(mutex)(&bebob->mutex);
+
+ err = avc_audio_get_selector(bebob->unit, 0x00, 0x04,
+ &dig_in_iface);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get digital input interface: %d\n", err);
+ return err;
+ }
+
+ /* encoded id for user value */
+ val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01);
+
+ /* for ADAT Optical */
+ if (val > 2)
+ val = 2;
+
+ uval->value.enumerated.item[0] = val;
+ return 0;
+}
+static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int id, dig_in_fmt, dig_in_iface;
+ int err;
+
+ id = uval->value.enumerated.item[0];
+ if (id >= ARRAY_SIZE(special_dig_in_iface_labels))
+ return -EINVAL;
+
+ /* decode user value */
+ dig_in_fmt = (id >> 1) & 0x01;
+ dig_in_iface = id & 0x01;
+
+ guard(mutex)(&bebob->mutex);
+
+ err = avc_maudio_set_special_clk(bebob,
+ params->clk_src,
+ dig_in_fmt,
+ params->dig_out_fmt,
+ params->clk_lock);
+ if (err < 0)
+ return err;
+
+ /* For ADAT, optical interface is only available. */
+ if (params->dig_in_fmt > 0)
+ return 1;
+
+ /* For S/PDIF, optical/coaxial interfaces are selectable. */
+ err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface);
+ if (err < 0)
+ dev_err(&bebob->unit->device,
+ "fail to set digital input interface: %d\n", err);
+ special_stream_formation_set(bebob);
+ return 1;
+}
+static const struct snd_kcontrol_new special_dig_in_iface_ctl = {
+ .name = "Digital Input Interface",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = special_dig_in_iface_ctl_info,
+ .get = special_dig_in_iface_ctl_get,
+ .put = special_dig_in_iface_ctl_set
+};
+
+/* Digital output interface control for special firmware */
+static const char *const special_dig_out_iface_labels[] = {
+ "S/PDIF Optical and Coaxial", "ADAT Optical"
+};
+static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_info *einf)
+{
+ return snd_ctl_enum_info(einf, 1,
+ ARRAY_SIZE(special_dig_out_iface_labels),
+ special_dig_out_iface_labels);
+}
+static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+
+ guard(mutex)(&bebob->mutex);
+ uval->value.enumerated.item[0] = params->dig_out_fmt;
+ return 0;
+}
+static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl,
+ struct snd_ctl_elem_value *uval)
+{
+ struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
+ struct special_params *params = bebob->maudio_special_quirk;
+ unsigned int id;
+ int err;
+
+ id = uval->value.enumerated.item[0];
+ if (id >= ARRAY_SIZE(special_dig_out_iface_labels))
+ return -EINVAL;
+
+ guard(mutex)(&bebob->mutex);
+
+ err = avc_maudio_set_special_clk(bebob,
+ params->clk_src,
+ params->dig_in_fmt,
+ id, params->clk_lock);
+ if (err >= 0) {
+ special_stream_formation_set(bebob);
+ err = 1;
+ }
+
+ return err;
+}
+static const struct snd_kcontrol_new special_dig_out_iface_ctl = {
+ .name = "Digital Output Interface",
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = special_dig_out_iface_ctl_info,
+ .get = special_dig_out_iface_ctl_get,
+ .put = special_dig_out_iface_ctl_set
+};
+
+static int add_special_controls(struct snd_bebob *bebob)
+{
+ struct snd_kcontrol *kctl;
+ struct special_params *params = bebob->maudio_special_quirk;
+ int err;
+
+ kctl = snd_ctl_new1(&special_clk_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+ if (err < 0)
+ goto end;
+
+ kctl = snd_ctl_new1(&special_sync_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+ if (err < 0)
+ goto end;
+ params->ctl_id_sync = &kctl->id;
+
+ kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+ if (err < 0)
+ goto end;
+
+ kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob);
+ err = snd_ctl_add(bebob->card, kctl);
+end:
+ return err;
+}
+
+/* Hardware metering for special firmware */
+static const char *const special_meter_labels[] = {
+ ANA_IN, ANA_IN, ANA_IN, ANA_IN,
+ SPDIF_IN,
+ ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN,
+ ANA_OUT, ANA_OUT,
+ SPDIF_OUT,
+ ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT,
+ HP_OUT, HP_OUT,
+ AUX_OUT
+};
+static int
+special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)
+{
+ __be16 *buf;
+ unsigned int i, c, channels;
+ int err;
+
+ channels = ARRAY_SIZE(special_meter_labels) * 2;
+ if (size < channels * sizeof(u32))
+ return -EINVAL;
+
+ /* omit last 4 bytes because it's clock info. */
+ buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4);
+ if (err < 0)
+ goto end;
+
+ /* Its format is u16 and some channels are unknown. */
+ i = 0;
+ for (c = 2; c < channels + 2; c++)
+ target[i++] = be16_to_cpu(buf[c]) << 16;
+end:
+ kfree(buf);
+ return err;
+}
+
+/* last 4 bytes are omitted because it's clock info. */
+static const char *const fw410_meter_labels[] = {
+ ANA_IN, DIG_IN,
+ ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,
+ HP_OUT
+};
+static const char *const audiophile_meter_labels[] = {
+ ANA_IN, DIG_IN,
+ ANA_OUT, ANA_OUT, DIG_OUT,
+ HP_OUT, AUX_OUT,
+};
+static const char *const solo_meter_labels[] = {
+ ANA_IN, DIG_IN,
+ STRM_IN, STRM_IN,
+ ANA_OUT, DIG_OUT
+};
+
+/* no clock info */
+static const char *const ozonic_meter_labels[] = {
+ ANA_IN, ANA_IN,
+ STRM_IN, STRM_IN,
+ ANA_OUT, ANA_OUT
+};
+/* TODO: need testers. these positions are based on authour's assumption */
+static const char *const nrv10_meter_labels[] = {
+ ANA_IN, ANA_IN, ANA_IN, ANA_IN,
+ DIG_IN,
+ ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
+ DIG_IN
+};
+static int
+normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
+{
+ const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ unsigned int c, channels;
+ int err;
+
+ channels = spec->num * 2;
+ if (size < channels * sizeof(u32))
+ return -EINVAL;
+
+ err = get_meter(bebob, (void *)buf, size);
+ if (err < 0)
+ goto end;
+
+ for (c = 0; c < channels; c++)
+ be32_to_cpus(&buf[c]);
+
+ /* swap stream channels because inverted */
+ if (spec->labels == solo_meter_labels) {
+ swap(buf[4], buf[6]);
+ swap(buf[5], buf[7]);
+ }
+end:
+ return err;
+}
+
+/* for special customized devices */
+static const struct snd_bebob_rate_spec special_rate_spec = {
+ .get = &special_get_rate,
+ .set = &special_set_rate,
+};
+static const struct snd_bebob_clock_spec special_clk_spec = {
+ .num = ARRAY_SIZE(special_clk_types),
+ .types = special_clk_types,
+ .get = &special_clk_get,
+};
+static const struct snd_bebob_meter_spec special_meter_spec = {
+ .num = ARRAY_SIZE(special_meter_labels),
+ .labels = special_meter_labels,
+ .get = &special_meter_get
+};
+const struct snd_bebob_spec maudio_special_spec = {
+ .clock = &special_clk_spec,
+ .rate = &special_rate_spec,
+ .meter = &special_meter_spec
+};
+
+/* Firewire 410 specification */
+static const struct snd_bebob_rate_spec usual_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+static const struct snd_bebob_meter_spec fw410_meter_spec = {
+ .num = ARRAY_SIZE(fw410_meter_labels),
+ .labels = fw410_meter_labels,
+ .get = &normal_meter_get
+};
+const struct snd_bebob_spec maudio_fw410_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &fw410_meter_spec
+};
+
+/* Firewire Audiophile specification */
+static const struct snd_bebob_meter_spec audiophile_meter_spec = {
+ .num = ARRAY_SIZE(audiophile_meter_labels),
+ .labels = audiophile_meter_labels,
+ .get = &normal_meter_get
+};
+const struct snd_bebob_spec maudio_audiophile_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &audiophile_meter_spec
+};
+
+/* Firewire Solo specification */
+static const struct snd_bebob_meter_spec solo_meter_spec = {
+ .num = ARRAY_SIZE(solo_meter_labels),
+ .labels = solo_meter_labels,
+ .get = &normal_meter_get
+};
+const struct snd_bebob_spec maudio_solo_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &solo_meter_spec
+};
+
+/* Ozonic specification */
+static const struct snd_bebob_meter_spec ozonic_meter_spec = {
+ .num = ARRAY_SIZE(ozonic_meter_labels),
+ .labels = ozonic_meter_labels,
+ .get = &normal_meter_get
+};
+const struct snd_bebob_spec maudio_ozonic_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &ozonic_meter_spec
+};
+
+/* NRV10 specification */
+static const struct snd_bebob_meter_spec nrv10_meter_spec = {
+ .num = ARRAY_SIZE(nrv10_meter_labels),
+ .labels = nrv10_meter_labels,
+ .get = &normal_meter_get
+};
+const struct snd_bebob_spec maudio_nrv10_spec = {
+ .clock = NULL,
+ .rate = &usual_rate_spec,
+ .meter = &nrv10_meter_spec
+};
diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c
new file mode 100644
index 000000000..678631f31
--- /dev/null
+++ b/sound/firewire/bebob/bebob_midi.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_midi.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "bebob.h"
+
+static int midi_open(struct snd_rawmidi_substream *substream)
+{
+ struct snd_bebob *bebob = substream->rmidi->private_data;
+ int err;
+
+ err = snd_bebob_stream_lock_try(bebob);
+ if (err < 0)
+ return err;
+
+ scoped_guard(mutex, &bebob->mutex) {
+ err = snd_bebob_stream_reserve_duplex(bebob, 0, 0, 0);
+ if (err >= 0) {
+ ++bebob->substreams_counter;
+ err = snd_bebob_stream_start_duplex(bebob);
+ if (err < 0)
+ --bebob->substreams_counter;
+ }
+ }
+ if (err < 0)
+ snd_bebob_stream_lock_release(bebob);
+
+ return err;
+}
+
+static int midi_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_bebob *bebob = substream->rmidi->private_data;
+
+ scoped_guard(mutex, &bebob->mutex) {
+ bebob->substreams_counter--;
+ snd_bebob_stream_stop_duplex(bebob);
+ }
+
+ snd_bebob_stream_lock_release(bebob);
+ return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+ struct snd_bebob *bebob = substrm->rmidi->private_data;
+
+ guard(spinlock_irqsave)(&bebob->lock);
+
+ if (up)
+ amdtp_am824_midi_trigger(&bebob->tx_stream,
+ substrm->number, substrm);
+ else
+ amdtp_am824_midi_trigger(&bebob->tx_stream,
+ substrm->number, NULL);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+ struct snd_bebob *bebob = substrm->rmidi->private_data;
+
+ guard(spinlock_irqsave)(&bebob->lock);
+
+ if (up)
+ amdtp_am824_midi_trigger(&bebob->rx_stream,
+ substrm->number, substrm);
+ else
+ amdtp_am824_midi_trigger(&bebob->rx_stream,
+ substrm->number, NULL);
+}
+
+static void set_midi_substream_names(struct snd_bebob *bebob,
+ struct snd_rawmidi_str *str)
+{
+ struct snd_rawmidi_substream *subs;
+
+ list_for_each_entry(subs, &str->substreams, list) {
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ bebob->card->shortname, subs->number + 1);
+ }
+}
+
+int snd_bebob_create_midi_devices(struct snd_bebob *bebob)
+{
+ static const struct snd_rawmidi_ops capture_ops = {
+ .open = midi_open,
+ .close = midi_close,
+ .trigger = midi_capture_trigger,
+ };
+ static const struct snd_rawmidi_ops playback_ops = {
+ .open = midi_open,
+ .close = midi_close,
+ .trigger = midi_playback_trigger,
+ };
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_str *str;
+ int err;
+
+ /* create midi ports */
+ err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0,
+ bebob->midi_output_ports, bebob->midi_input_ports,
+ &rmidi);
+ if (err < 0)
+ return err;
+
+ snprintf(rmidi->name, sizeof(rmidi->name),
+ "%s MIDI", bebob->card->shortname);
+ rmidi->private_data = bebob;
+
+ if (bebob->midi_input_ports > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &capture_ops);
+
+ str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+
+ set_midi_substream_names(bebob, str);
+ }
+
+ if (bebob->midi_output_ports > 0) {
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &playback_ops);
+
+ str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+
+ set_midi_substream_names(bebob, str);
+ }
+
+ if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0))
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+ return 0;
+}
diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c
new file mode 100644
index 000000000..692d33bac
--- /dev/null
+++ b/sound/firewire/bebob/bebob_pcm.c
@@ -0,0 +1,368 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_pcm.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+static int
+hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+ struct snd_bebob_stream_formation *formations = rule->private;
+ struct snd_interval *r =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+ const struct snd_interval *c =
+ hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ struct snd_interval t = {
+ .min = UINT_MAX, .max = 0, .integer = 1
+ };
+ unsigned int i;
+
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ /* entry is invalid */
+ if (formations[i].pcm == 0)
+ continue;
+
+ if (!snd_interval_test(c, formations[i].pcm))
+ continue;
+
+ t.min = min(t.min, snd_bebob_rate_table[i]);
+ t.max = max(t.max, snd_bebob_rate_table[i]);
+
+ }
+ return snd_interval_refine(r, &t);
+}
+
+static int
+hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+ struct snd_bebob_stream_formation *formations = rule->private;
+ struct snd_interval *c =
+ hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+ const struct snd_interval *r =
+ hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+ struct snd_interval t = {
+ .min = UINT_MAX, .max = 0, .integer = 1
+ };
+
+ unsigned int i;
+
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ /* entry is invalid */
+ if (formations[i].pcm == 0)
+ continue;
+
+ if (!snd_interval_test(r, snd_bebob_rate_table[i]))
+ continue;
+
+ t.min = min(t.min, formations[i].pcm);
+ t.max = max(t.max, formations[i].pcm);
+ }
+
+ return snd_interval_refine(c, &t);
+}
+
+static void
+limit_channels_and_rates(struct snd_pcm_hardware *hw,
+ struct snd_bebob_stream_formation *formations)
+{
+ unsigned int i;
+
+ hw->channels_min = UINT_MAX;
+ hw->channels_max = 0;
+
+ hw->rate_min = UINT_MAX;
+ hw->rate_max = 0;
+ hw->rates = 0;
+
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ /* entry has no PCM channels */
+ if (formations[i].pcm == 0)
+ continue;
+
+ hw->channels_min = min(hw->channels_min, formations[i].pcm);
+ hw->channels_max = max(hw->channels_max, formations[i].pcm);
+
+ hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]);
+ hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]);
+ hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]);
+ }
+}
+
+static int
+pcm_init_hw_params(struct snd_bebob *bebob,
+ struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct amdtp_stream *s;
+ struct snd_bebob_stream_formation *formations;
+ int err;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS;
+ s = &bebob->tx_stream;
+ formations = bebob->tx_stream_formations;
+ } else {
+ runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS;
+ s = &bebob->rx_stream;
+ formations = bebob->rx_stream_formations;
+ }
+
+ limit_channels_and_rates(&runtime->hw, formations);
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+ hw_rule_channels, formations,
+ SNDRV_PCM_HW_PARAM_RATE, -1);
+ if (err < 0)
+ goto end;
+
+ err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+ hw_rule_rate, formations,
+ SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+ if (err < 0)
+ goto end;
+
+ err = amdtp_am824_add_pcm_hw_constraints(s, runtime);
+end:
+ return err;
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ const struct snd_bebob_rate_spec *spec = bebob->spec->rate;
+ struct amdtp_domain *d = &bebob->domain;
+ enum snd_bebob_clock_type src;
+ int err;
+
+ err = snd_bebob_stream_lock_try(bebob);
+ if (err < 0)
+ return err;
+
+ err = pcm_init_hw_params(bebob, substream);
+ if (err < 0)
+ goto err_locked;
+
+ err = snd_bebob_stream_get_clock_src(bebob, &src);
+ if (err < 0)
+ goto err_locked;
+
+ scoped_guard(mutex, &bebob->mutex) {
+ // When source of clock is not internal or any stream is reserved for
+ // transmission of PCM frames, the available sampling rate is limited
+ // at current one.
+ if (src == SND_BEBOB_CLOCK_TYPE_EXTERNAL ||
+ (bebob->substreams_counter > 0 && d->events_per_period > 0)) {
+ unsigned int frames_per_period = d->events_per_period;
+ unsigned int frames_per_buffer = d->events_per_buffer;
+ unsigned int sampling_rate;
+
+ err = spec->get(bebob, &sampling_rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get sampling rate: %d\n", err);
+ goto err_locked;
+ }
+
+ substream->runtime->hw.rate_min = sampling_rate;
+ substream->runtime->hw.rate_max = sampling_rate;
+
+ if (frames_per_period > 0) {
+ err = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+ frames_per_period, frames_per_period);
+ if (err < 0)
+ goto err_locked;
+
+ err = snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+ frames_per_buffer, frames_per_buffer);
+ if (err < 0)
+ goto err_locked;
+ }
+ }
+ }
+
+ snd_pcm_set_sync(substream);
+
+ return 0;
+err_locked:
+ snd_bebob_stream_lock_release(bebob);
+ return err;
+}
+
+static int
+pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ snd_bebob_stream_lock_release(bebob);
+ return 0;
+}
+
+static int pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ int err = 0;
+
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
+ unsigned int rate = params_rate(hw_params);
+ unsigned int frames_per_period = params_period_size(hw_params);
+ unsigned int frames_per_buffer = params_buffer_size(hw_params);
+
+ guard(mutex)(&bebob->mutex);
+ err = snd_bebob_stream_reserve_duplex(bebob, rate,
+ frames_per_period, frames_per_buffer);
+ if (err >= 0)
+ ++bebob->substreams_counter;
+ }
+
+ return err;
+}
+
+static int pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ guard(mutex)(&bebob->mutex);
+
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
+ bebob->substreams_counter--;
+
+ snd_bebob_stream_stop_duplex(bebob);
+
+ return 0;
+}
+
+static int
+pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ int err;
+
+ err = snd_bebob_stream_start_duplex(bebob);
+ if (err >= 0)
+ amdtp_stream_pcm_prepare(&bebob->tx_stream);
+
+ return err;
+}
+static int
+pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+ int err;
+
+ err = snd_bebob_stream_start_duplex(bebob);
+ if (err >= 0)
+ amdtp_stream_pcm_prepare(&bebob->rx_stream);
+
+ return err;
+}
+
+static int
+pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ amdtp_stream_pcm_trigger(&bebob->tx_stream, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+static int
+pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ amdtp_stream_pcm_trigger(&bebob->rx_stream, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+ struct snd_bebob *bebob = sbstrm->private_data;
+
+ return amdtp_domain_stream_pcm_pointer(&bebob->domain,
+ &bebob->tx_stream);
+}
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+ struct snd_bebob *bebob = sbstrm->private_data;
+
+ return amdtp_domain_stream_pcm_pointer(&bebob->domain,
+ &bebob->rx_stream);
+}
+
+static int pcm_capture_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->tx_stream);
+}
+
+static int pcm_playback_ack(struct snd_pcm_substream *substream)
+{
+ struct snd_bebob *bebob = substream->private_data;
+
+ return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->rx_stream);
+}
+
+int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
+{
+ static const struct snd_pcm_ops capture_ops = {
+ .open = pcm_open,
+ .close = pcm_close,
+ .hw_params = pcm_hw_params,
+ .hw_free = pcm_hw_free,
+ .prepare = pcm_capture_prepare,
+ .trigger = pcm_capture_trigger,
+ .pointer = pcm_capture_pointer,
+ .ack = pcm_capture_ack,
+ };
+ static const struct snd_pcm_ops playback_ops = {
+ .open = pcm_open,
+ .close = pcm_close,
+ .hw_params = pcm_hw_params,
+ .hw_free = pcm_hw_free,
+ .prepare = pcm_playback_prepare,
+ .trigger = pcm_playback_trigger,
+ .pointer = pcm_playback_pointer,
+ .ack = pcm_playback_ack,
+ };
+ struct snd_pcm *pcm;
+ int err;
+
+ err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm);
+ if (err < 0)
+ goto end;
+
+ pcm->private_data = bebob;
+ pcm->nonatomic = true;
+ snprintf(pcm->name, sizeof(pcm->name),
+ "%s PCM", bebob->card->shortname);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
+ snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
+end:
+ return err;
+}
diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c
new file mode 100644
index 000000000..ad03734af
--- /dev/null
+++ b/sound/firewire/bebob/bebob_proc.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_proc.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+/* contents of information register */
+struct hw_info {
+ u64 manufacturer;
+ u32 protocol_ver;
+ u32 bld_ver;
+ u32 guid[2];
+ u32 model_id;
+ u32 model_rev;
+ u64 fw_date;
+ u64 fw_time;
+ u32 fw_id;
+ u32 fw_ver;
+ u32 base_addr;
+ u32 max_size;
+ u64 bld_date;
+ u64 bld_time;
+/* may not used in product
+ u64 dbg_date;
+ u64 dbg_time;
+ u32 dbg_id;
+ u32 dbg_version;
+*/
+} __packed;
+
+static void
+proc_read_hw_info(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ struct hw_info *info;
+
+ info = kzalloc_obj(struct hw_info);
+ if (info == NULL)
+ return;
+
+ if (snd_bebob_read_block(bebob->unit, 0,
+ info, sizeof(struct hw_info)) < 0)
+ goto end;
+
+ snd_iprintf(buffer, "Manufacturer:\t%.8s\n",
+ (char *)&info->manufacturer);
+ snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver);
+ snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver);
+ snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n",
+ info->guid[0], info->guid[1]);
+ snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id);
+ snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev);
+ snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date);
+ snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time);
+ snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id);
+ snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver);
+ snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr);
+ snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size);
+ snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date);
+ snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time);
+
+end:
+ kfree(info);
+}
+
+static void
+proc_read_meters(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ u32 *buf;
+ unsigned int i, c, channels, size;
+
+ if (spec == NULL)
+ return;
+
+ channels = spec->num * 2;
+ size = channels * sizeof(u32);
+ buf = kmalloc(size, GFP_KERNEL);
+ if (buf == NULL)
+ return;
+
+ if (spec->get(bebob, buf, size) < 0)
+ goto end;
+
+ for (i = 0, c = 1; i < channels; i++) {
+ snd_iprintf(buffer, "%s %d:\t%d\n",
+ spec->labels[i / 2], c++, buf[i]);
+ if ((i + 1 < channels - 1) &&
+ (strcmp(spec->labels[i / 2],
+ spec->labels[(i + 1) / 2]) != 0))
+ c = 1;
+ }
+end:
+ kfree(buf);
+}
+
+static void
+proc_read_formation(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ struct snd_bebob *bebob = entry->private_data;
+ struct snd_bebob_stream_formation *formation;
+ unsigned int i;
+
+ snd_iprintf(buffer, "Output Stream from device:\n");
+ snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+ formation = bebob->tx_stream_formations;
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ snd_iprintf(buffer,
+ "\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
+ formation[i].pcm, formation[i].midi);
+ }
+
+ snd_iprintf(buffer, "Input Stream to device:\n");
+ snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+ formation = bebob->rx_stream_formations;
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
+ snd_iprintf(buffer,
+ "\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
+ formation[i].pcm, formation[i].midi);
+ }
+}
+
+static void
+proc_read_clock(struct snd_info_entry *entry,
+ struct snd_info_buffer *buffer)
+{
+ static const char *const clk_labels[] = {
+ "Internal",
+ "External",
+ "SYT-Match",
+ };
+ struct snd_bebob *bebob = entry->private_data;
+ const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
+ const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ enum snd_bebob_clock_type src;
+ unsigned int rate;
+
+ if (rate_spec->get(bebob, &rate) >= 0)
+ snd_iprintf(buffer, "Sampling rate: %d\n", rate);
+
+ if (snd_bebob_stream_get_clock_src(bebob, &src) >= 0) {
+ if (clk_spec)
+ snd_iprintf(buffer, "Clock Source: %s\n",
+ clk_labels[src]);
+ else
+ snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n",
+ clk_labels[src], bebob->sync_input_plug);
+ }
+}
+
+static void
+add_node(struct snd_bebob *bebob, struct snd_info_entry *root, const char *name,
+ void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b))
+{
+ struct snd_info_entry *entry;
+
+ entry = snd_info_create_card_entry(bebob->card, name, root);
+ if (entry)
+ snd_info_set_text_ops(entry, bebob, op);
+}
+
+void snd_bebob_proc_init(struct snd_bebob *bebob)
+{
+ struct snd_info_entry *root;
+
+ /*
+ * All nodes are automatically removed at snd_card_disconnect(),
+ * by following to link list.
+ */
+ root = snd_info_create_card_entry(bebob->card, "firewire",
+ bebob->card->proc_root);
+ if (root == NULL)
+ return;
+ root->mode = S_IFDIR | 0555;
+
+ add_node(bebob, root, "clock", proc_read_clock);
+ add_node(bebob, root, "firmware", proc_read_hw_info);
+ add_node(bebob, root, "formation", proc_read_formation);
+
+ if (bebob->spec->meter != NULL)
+ add_node(bebob, root, "meter", proc_read_meters);
+}
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c
new file mode 100644
index 000000000..449cb1771
--- /dev/null
+++ b/sound/firewire/bebob/bebob_stream.c
@@ -0,0 +1,987 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_stream.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+#define READY_TIMEOUT_MS 4000
+
+/*
+ * NOTE;
+ * For BeBoB streams, Both of input and output CMP connection are important.
+ *
+ * For most devices, each CMP connection starts to transmit/receive a
+ * corresponding stream. But for a few devices, both of CMP connection needs
+ * to start transmitting stream. An example is 'M-Audio Firewire 410'.
+ */
+
+/* 128 is an arbitrary length but it seems to be enough */
+#define FORMAT_MAXIMUM_LENGTH 128
+
+const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = {
+ [0] = 32000,
+ [1] = 44100,
+ [2] = 48000,
+ [3] = 88200,
+ [4] = 96000,
+ [5] = 176400,
+ [6] = 192000,
+};
+
+/*
+ * See: Table 51: Extended Stream Format Info ‘Sampling Frequency’
+ * in Additional AVC commands (Nov 2003, BridgeCo)
+ */
+static const unsigned int bridgeco_freq_table[] = {
+ [0] = 0x02,
+ [1] = 0x03,
+ [2] = 0x04,
+ [3] = 0x0a,
+ [4] = 0x05,
+ [5] = 0x06,
+ [6] = 0x07,
+};
+
+static int
+get_formation_index(unsigned int rate, unsigned int *index)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) {
+ if (snd_bebob_rate_table[i] == rate) {
+ *index = i;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+int
+snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate)
+{
+ unsigned int tx_rate, rx_rate, trials;
+ int err;
+
+ trials = 0;
+ do {
+ err = avc_general_get_sig_fmt(bebob->unit, &tx_rate,
+ AVC_GENERAL_PLUG_DIR_OUT, 0);
+ } while (err == -EAGAIN && ++trials < 3);
+ if (err < 0)
+ goto end;
+
+ trials = 0;
+ do {
+ err = avc_general_get_sig_fmt(bebob->unit, &rx_rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ } while (err == -EAGAIN && ++trials < 3);
+ if (err < 0)
+ goto end;
+
+ *curr_rate = rx_rate;
+ if (rx_rate == tx_rate)
+ goto end;
+
+ /* synchronize receive stream rate to transmit stream rate */
+ err = avc_general_set_sig_fmt(bebob->unit, rx_rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+end:
+ return err;
+}
+
+int
+snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate)
+{
+ int err;
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_OUT, 0);
+ if (err < 0)
+ goto end;
+
+ err = avc_general_set_sig_fmt(bebob->unit, rate,
+ AVC_GENERAL_PLUG_DIR_IN, 0);
+ if (err < 0)
+ goto end;
+
+ /*
+ * Some devices need a bit time for transition.
+ * 300msec is got by some experiments.
+ */
+ msleep(300);
+end:
+ return err;
+}
+
+int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
+ enum snd_bebob_clock_type *src)
+{
+ const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7];
+ unsigned int id;
+ enum avc_bridgeco_plug_type type;
+ int err = 0;
+
+ /* 1.The device has its own operation to switch source of clock */
+ if (clk_spec) {
+ err = clk_spec->get(bebob, &id);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get clock source: %d\n", err);
+ goto end;
+ }
+
+ if (id >= clk_spec->num) {
+ dev_err(&bebob->unit->device,
+ "clock source %d out of range 0..%d\n",
+ id, clk_spec->num - 1);
+ err = -EIO;
+ goto end;
+ }
+
+ *src = clk_spec->types[id];
+ goto end;
+ }
+
+ /*
+ * 2.The device don't support to switch source of clock then assumed
+ * to use internal clock always
+ */
+ if (bebob->sync_input_plug < 0) {
+ *src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
+ goto end;
+ }
+
+ /*
+ * 3.The device supports to switch source of clock by an usual way.
+ * Let's check input for 'Music Sub Unit Sync Input' plug.
+ */
+ avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
+ bebob->sync_input_plug);
+ err = avc_bridgeco_get_plug_input(bebob->unit, addr, input);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get an input for MSU in plug %d: %d\n",
+ bebob->sync_input_plug, err);
+ goto end;
+ }
+
+ /*
+ * If there are no input plugs, all of fields are 0xff.
+ * Here check the first field. This field is used for direction.
+ */
+ if (input[0] == 0xff) {
+ *src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
+ goto end;
+ }
+
+ /* The source from any output plugs is for one purpose only. */
+ if (input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) {
+ /*
+ * In BeBoB architecture, the source from music subunit may
+ * bypass from oPCR[0]. This means that this source gives
+ * synchronization to IEEE 1394 cycle start packet.
+ */
+ if (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT &&
+ input[2] == 0x0c) {
+ *src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
+ goto end;
+ }
+ /* The source from any input units is for several purposes. */
+ } else if (input[1] == AVC_BRIDGECO_PLUG_MODE_UNIT) {
+ if (input[2] == AVC_BRIDGECO_PLUG_UNIT_ISOC) {
+ if (input[3] == 0x00) {
+ /*
+ * This source comes from iPCR[0]. This means
+ * that presentation timestamp calculated by
+ * SYT series of the received packets. In
+ * short, this driver is the master of
+ * synchronization.
+ */
+ *src = SND_BEBOB_CLOCK_TYPE_SYT;
+ goto end;
+ } else {
+ /*
+ * This source comes from iPCR[1-29]. This
+ * means that the synchronization stream is not
+ * the Audio/MIDI compound stream.
+ */
+ *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL;
+ goto end;
+ }
+ } else if (input[2] == AVC_BRIDGECO_PLUG_UNIT_EXT) {
+ /* Check type of this plug. */
+ avc_bridgeco_fill_unit_addr(addr,
+ AVC_BRIDGECO_PLUG_DIR_IN,
+ AVC_BRIDGECO_PLUG_UNIT_EXT,
+ input[3]);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr,
+ &type);
+ if (err < 0)
+ goto end;
+
+ if (type == AVC_BRIDGECO_PLUG_TYPE_DIG) {
+ /*
+ * SPDIF/ADAT or sometimes (not always) word
+ * clock.
+ */
+ *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL;
+ goto end;
+ } else if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) {
+ /* Often word clock. */
+ *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL;
+ goto end;
+ } else if (type == AVC_BRIDGECO_PLUG_TYPE_ADDITION) {
+ /*
+ * Not standard.
+ * Mostly, additional internal clock.
+ */
+ *src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
+ goto end;
+ }
+ }
+ }
+
+ /* Not supported. */
+ err = -EIO;
+end:
+ return err;
+}
+
+static int map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s)
+{
+ unsigned int sec, sections, ch, channels;
+ unsigned int pcm, midi, location;
+ unsigned int stm_pos, sec_loc, pos;
+ u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type;
+ enum avc_bridgeco_plug_dir dir;
+ int err;
+
+ /*
+ * The length of return value of this command cannot be expected. Here
+ * use the maximum length of FCP.
+ */
+ buf = kzalloc(256, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ if (s == &bebob->tx_stream)
+ dir = AVC_BRIDGECO_PLUG_DIR_OUT;
+ else
+ dir = AVC_BRIDGECO_PLUG_DIR_IN;
+
+ avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
+ err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get channel position for isoc %s plug 0: %d\n",
+ (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out",
+ err);
+ goto end;
+ }
+ pos = 0;
+
+ /* positions in I/O buffer */
+ pcm = 0;
+ midi = 0;
+
+ /* the number of sections in AMDTP packet */
+ sections = buf[pos++];
+
+ for (sec = 0; sec < sections; sec++) {
+ /* type of this section */
+ avc_bridgeco_fill_unit_addr(addr, dir,
+ AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
+ err = avc_bridgeco_get_plug_section_type(bebob->unit, addr,
+ sec, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get section type for isoc %s plug 0: %d\n",
+ (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" :
+ "out",
+ err);
+ goto end;
+ }
+ /* NoType */
+ if (type == 0xff) {
+ err = -ENOSYS;
+ goto end;
+ }
+
+ /* the number of channels in this section */
+ channels = buf[pos++];
+
+ for (ch = 0; ch < channels; ch++) {
+ /* position of this channel in AMDTP packet */
+ stm_pos = buf[pos++] - 1;
+ /* location of this channel in this section */
+ sec_loc = buf[pos++] - 1;
+
+ /*
+ * Basically the number of location is within the
+ * number of channels in this section. But some models
+ * of M-Audio don't follow this. Its location for MIDI
+ * is the position of MIDI channels in AMDTP packet.
+ */
+ if (sec_loc >= channels)
+ sec_loc = ch;
+
+ switch (type) {
+ /* for MIDI conformant data channel */
+ case 0x0a:
+ /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */
+ if ((midi > 0) && (stm_pos != midi)) {
+ err = -ENOSYS;
+ goto end;
+ }
+ amdtp_am824_set_midi_position(s, stm_pos);
+ midi = stm_pos;
+ break;
+ /* for PCM data channel */
+ case 0x01: /* Headphone */
+ case 0x02: /* Microphone */
+ case 0x03: /* Line */
+ case 0x04: /* SPDIF */
+ case 0x05: /* ADAT */
+ case 0x06: /* TDIF */
+ case 0x07: /* MADI */
+ /* for undefined/changeable signal */
+ case 0x08: /* Analog */
+ case 0x09: /* Digital */
+ default:
+ location = pcm + sec_loc;
+ if (location >= AM824_MAX_CHANNELS_FOR_PCM) {
+ err = -ENOSYS;
+ goto end;
+ }
+ amdtp_am824_set_pcm_position(s, location,
+ stm_pos);
+ break;
+ }
+ }
+
+ if (type != 0x0a)
+ pcm += channels;
+ else
+ midi += channels;
+ }
+end:
+ kfree(buf);
+ return err;
+}
+
+static int
+check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s)
+{
+ struct cmp_connection *conn;
+ bool used;
+ int err;
+
+ if (s == &bebob->tx_stream)
+ conn = &bebob->out_conn;
+ else
+ conn = &bebob->in_conn;
+
+ err = cmp_connection_check_used(conn, &used);
+ if ((err >= 0) && used && !amdtp_stream_running(s)) {
+ dev_err(&bebob->unit->device,
+ "Connection established by others: %cPCR[%d]\n",
+ (conn->direction == CMP_OUTPUT) ? 'o' : 'i',
+ conn->pcr_index);
+ err = -EBUSY;
+ }
+
+ return err;
+}
+
+static void break_both_connections(struct snd_bebob *bebob)
+{
+ cmp_connection_break(&bebob->in_conn);
+ cmp_connection_break(&bebob->out_conn);
+}
+
+static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
+{
+ struct cmp_connection *conn;
+ int err = 0;
+
+ if (stream == &bebob->rx_stream)
+ conn = &bebob->in_conn;
+ else
+ conn = &bebob->out_conn;
+
+ // channel mapping.
+ if (bebob->maudio_special_quirk == NULL) {
+ err = map_data_channels(bebob, stream);
+ if (err < 0)
+ return err;
+ }
+
+ err = cmp_connection_establish(conn);
+ if (err < 0)
+ return err;
+
+ return amdtp_domain_add_stream(&bebob->domain, stream,
+ conn->resources.channel, conn->speed);
+}
+
+static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
+{
+ unsigned int flags = CIP_BLOCKING;
+ enum amdtp_stream_direction dir_stream;
+ struct cmp_connection *conn;
+ enum cmp_direction dir_conn;
+ int err;
+
+ if (stream == &bebob->tx_stream) {
+ dir_stream = AMDTP_IN_STREAM;
+ conn = &bebob->out_conn;
+ dir_conn = CMP_OUTPUT;
+ } else {
+ dir_stream = AMDTP_OUT_STREAM;
+ conn = &bebob->in_conn;
+ dir_conn = CMP_INPUT;
+ }
+
+ if (stream == &bebob->tx_stream) {
+ if (bebob->quirks & SND_BEBOB_QUIRK_WRONG_DBC)
+ flags |= CIP_EMPTY_HAS_WRONG_DBC;
+ }
+
+ err = cmp_connection_init(conn, bebob->unit, dir_conn, 0);
+ if (err < 0)
+ return err;
+
+ err = amdtp_am824_init(stream, bebob->unit, dir_stream, flags);
+ if (err < 0) {
+ cmp_connection_destroy(conn);
+ return err;
+ }
+
+ return 0;
+}
+
+static void destroy_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
+{
+ amdtp_stream_destroy(stream);
+
+ if (stream == &bebob->tx_stream)
+ cmp_connection_destroy(&bebob->out_conn);
+ else
+ cmp_connection_destroy(&bebob->in_conn);
+}
+
+int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
+{
+ int err;
+
+ err = init_stream(bebob, &bebob->tx_stream);
+ if (err < 0)
+ return err;
+
+ err = init_stream(bebob, &bebob->rx_stream);
+ if (err < 0) {
+ destroy_stream(bebob, &bebob->tx_stream);
+ return err;
+ }
+
+ err = amdtp_domain_init(&bebob->domain);
+ if (err < 0) {
+ destroy_stream(bebob, &bebob->tx_stream);
+ destroy_stream(bebob, &bebob->rx_stream);
+ }
+
+ return err;
+}
+
+static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream,
+ unsigned int rate, unsigned int index)
+{
+ unsigned int pcm_channels;
+ unsigned int midi_ports;
+ struct cmp_connection *conn;
+ int err;
+
+ if (stream == &bebob->tx_stream) {
+ pcm_channels = bebob->tx_stream_formations[index].pcm;
+ midi_ports = bebob->midi_input_ports;
+ conn = &bebob->out_conn;
+ } else {
+ pcm_channels = bebob->rx_stream_formations[index].pcm;
+ midi_ports = bebob->midi_output_ports;
+ conn = &bebob->in_conn;
+ }
+
+ err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, false);
+ if (err < 0)
+ return err;
+
+ return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream));
+}
+
+int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate,
+ unsigned int frames_per_period,
+ unsigned int frames_per_buffer)
+{
+ unsigned int curr_rate;
+ int err;
+
+ // Considering JACK/FFADO streaming:
+ // TODO: This can be removed hwdep functionality becomes popular.
+ err = check_connection_used_by_others(bebob, &bebob->rx_stream);
+ if (err < 0)
+ return err;
+
+ err = bebob->spec->rate->get(bebob, &curr_rate);
+ if (err < 0)
+ return err;
+ if (rate == 0)
+ rate = curr_rate;
+ if (curr_rate != rate) {
+ amdtp_domain_stop(&bebob->domain);
+ break_both_connections(bebob);
+
+ cmp_connection_release(&bebob->out_conn);
+ cmp_connection_release(&bebob->in_conn);
+ }
+
+ if (bebob->substreams_counter == 0 || curr_rate != rate) {
+ unsigned int index;
+
+ // NOTE:
+ // If establishing connections at first, Yamaha GO46
+ // (and maybe Terratec X24) don't generate sound.
+ //
+ // For firmware customized by M-Audio, refer to next NOTE.
+ err = bebob->spec->rate->set(bebob, rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to set sampling rate: %d\n",
+ err);
+ return err;
+ }
+
+ err = get_formation_index(rate, &index);
+ if (err < 0)
+ return err;
+
+ err = keep_resources(bebob, &bebob->tx_stream, rate, index);
+ if (err < 0)
+ return err;
+
+ err = keep_resources(bebob, &bebob->rx_stream, rate, index);
+ if (err < 0) {
+ cmp_connection_release(&bebob->out_conn);
+ return err;
+ }
+
+ err = amdtp_domain_set_events_per_period(&bebob->domain,
+ frames_per_period, frames_per_buffer);
+ if (err < 0) {
+ cmp_connection_release(&bebob->out_conn);
+ cmp_connection_release(&bebob->in_conn);
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
+{
+ int err;
+
+ // Need no substreams.
+ if (bebob->substreams_counter == 0)
+ return -EIO;
+
+ // packet queueing error or detecting discontinuity
+ if (amdtp_streaming_error(&bebob->rx_stream) ||
+ amdtp_streaming_error(&bebob->tx_stream)) {
+ amdtp_domain_stop(&bebob->domain);
+ break_both_connections(bebob);
+ }
+
+ if (!amdtp_stream_running(&bebob->rx_stream)) {
+ enum snd_bebob_clock_type src;
+ unsigned int curr_rate;
+ unsigned int tx_init_skip_cycles;
+
+ if (bebob->maudio_special_quirk) {
+ err = bebob->spec->rate->get(bebob, &curr_rate);
+ if (err < 0)
+ return err;
+ }
+
+ err = snd_bebob_stream_get_clock_src(bebob, &src);
+ if (err < 0)
+ return err;
+
+ err = start_stream(bebob, &bebob->rx_stream);
+ if (err < 0)
+ goto error;
+
+ err = start_stream(bebob, &bebob->tx_stream);
+ if (err < 0)
+ goto error;
+
+ if (!(bebob->quirks & SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC))
+ tx_init_skip_cycles = 0;
+ else
+ tx_init_skip_cycles = 16000;
+
+ // MEMO: Some devices start packet transmission long enough after establishment of
+ // CMP connection. In the early stage of packet streaming, any device transfers
+ // NODATA packets. After several hundred cycles, it begins to multiplex event into
+ // the packet with adequate value of syt field in CIP header. Some devices are
+ // strictly to generate any discontinuity in the sequence of tx packet when they
+ // receives inadequate sequence of value in syt field of CIP header. In the case,
+ // the request to break CMP connection is often corrupted, then any transaction
+ // results in unrecoverable error, sometimes generate bus-reset.
+ err = amdtp_domain_start(&bebob->domain, tx_init_skip_cycles, true, false);
+ if (err < 0)
+ goto error;
+
+ // NOTE:
+ // The firmware customized by M-Audio uses these commands to
+ // start transmitting stream. This is not usual way.
+ if (bebob->maudio_special_quirk) {
+ err = bebob->spec->rate->set(bebob, curr_rate);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to ensure sampling rate: %d\n",
+ err);
+ goto error;
+ }
+ }
+
+ // Some devices postpone start of transmission mostly for 1 sec after receives
+ // packets firstly.
+ if (!amdtp_domain_wait_ready(&bebob->domain, READY_TIMEOUT_MS)) {
+ err = -ETIMEDOUT;
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ amdtp_domain_stop(&bebob->domain);
+ break_both_connections(bebob);
+ return err;
+}
+
+void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob)
+{
+ if (bebob->substreams_counter == 0) {
+ amdtp_domain_stop(&bebob->domain);
+ break_both_connections(bebob);
+
+ cmp_connection_release(&bebob->out_conn);
+ cmp_connection_release(&bebob->in_conn);
+ }
+}
+
+/*
+ * This function should be called before starting streams or after stopping
+ * streams.
+ */
+void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob)
+{
+ amdtp_domain_destroy(&bebob->domain);
+
+ destroy_stream(bebob, &bebob->tx_stream);
+ destroy_stream(bebob, &bebob->rx_stream);
+}
+
+/*
+ * See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’
+ * in Additional AVC commands (Nov 2003, BridgeCo)
+ * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005
+ */
+static int
+parse_stream_formation(u8 *buf, unsigned int len,
+ struct snd_bebob_stream_formation *formation)
+{
+ unsigned int i, e, channels, format;
+
+ /*
+ * this module can support a hierarchy combination that:
+ * Root: Audio and Music (0x90)
+ * Level 1: AM824 Compound (0x40)
+ */
+ if ((buf[0] != 0x90) || (buf[1] != 0x40))
+ return -ENOSYS;
+
+ /* check sampling rate */
+ for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) {
+ if (buf[2] == bridgeco_freq_table[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(bridgeco_freq_table))
+ return -ENOSYS;
+
+ /* Avoid double count by different entries for the same rate. */
+ memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation));
+
+ for (e = 0; e < buf[4]; e++) {
+ channels = buf[5 + e * 2];
+ format = buf[6 + e * 2];
+
+ switch (format) {
+ /* IEC 60958 Conformant, currently handled as MBLA */
+ case 0x00:
+ /* Multi bit linear audio */
+ case 0x06: /* Raw */
+ formation[i].pcm += channels;
+ break;
+ /* MIDI Conformant */
+ case 0x0d:
+ formation[i].midi += channels;
+ break;
+ /* IEC 61937-3 to 7 */
+ case 0x01:
+ case 0x02:
+ case 0x03:
+ case 0x04:
+ case 0x05:
+ /* Multi bit linear audio */
+ case 0x07: /* DVD-Audio */
+ case 0x0c: /* High Precision */
+ /* One Bit Audio */
+ case 0x08: /* (Plain) Raw */
+ case 0x09: /* (Plain) SACD */
+ case 0x0a: /* (Encoded) Raw */
+ case 0x0b: /* (Encoded) SACD */
+ /* Synchronization Stream (Stereo Raw audio) */
+ case 0x40:
+ /* Don't care */
+ case 0xff:
+ default:
+ return -ENOSYS; /* not supported */
+ }
+ }
+
+ if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM ||
+ formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI)
+ return -ENOSYS;
+
+ return 0;
+}
+
+static int fill_stream_formations(struct snd_bebob *bebob, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_dir plug_dir, unsigned int plug_id,
+ struct snd_bebob_stream_formation *formations)
+{
+ enum avc_bridgeco_plug_type plug_type;
+ u8 *buf;
+ unsigned int len, eid;
+ int err;
+
+ avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id);
+
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "Fail to get type for isoc %d plug 0: %d\n", plug_dir, err);
+ return err;
+ } else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_ISOC)
+ return -ENXIO;
+
+ buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; ++eid) {
+ avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id);
+
+ len = FORMAT_MAXIMUM_LENGTH;
+ err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, &len, eid);
+ // No entries remained.
+ if (err == -EINVAL && eid > 0) {
+ err = 0;
+ break;
+ } else if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get stream format %d for isoc %d plug %d:%d\n",
+ eid, plug_dir, plug_id, err);
+ break;
+ }
+
+ err = parse_stream_formation(buf, len, formations);
+ if (err < 0)
+ break;
+ }
+
+ kfree(buf);
+ return err;
+}
+
+static int detect_midi_ports(struct snd_bebob *bebob,
+ const struct snd_bebob_stream_formation *formats,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], enum avc_bridgeco_plug_dir plug_dir,
+ unsigned int plug_count, unsigned int *midi_ports)
+{
+ int i;
+ int err = 0;
+
+ *midi_ports = 0;
+
+ /// Detect the number of available MIDI ports when packet has MIDI conformant data channel.
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; ++i) {
+ if (formats[i].midi > 0)
+ break;
+ }
+ if (i >= SND_BEBOB_STRM_FMT_ENTRIES)
+ return 0;
+
+ for (i = 0; i < plug_count; ++i) {
+ enum avc_bridgeco_plug_type plug_type;
+ unsigned int ch_count;
+
+ avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_EXT, i);
+
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for external %d plug %d: %d\n",
+ plug_dir, i, err);
+ break;
+ } else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_MIDI) {
+ continue;
+ }
+
+ err = avc_bridgeco_get_plug_ch_count(bebob->unit, addr, &ch_count);
+ if (err < 0)
+ break;
+ // Yamaha GO44, GO46, Terratec Phase 24, Phase x24 reports 0 for the number of
+ // channels in external output plug 3 (MIDI type) even if it has a pair of physical
+ // MIDI jacks. As a workaround, assume it as one.
+ if (ch_count == 0)
+ ch_count = 1;
+ *midi_ports += ch_count;
+ }
+
+ return err;
+}
+
+static int
+seek_msu_sync_input_plug(struct snd_bebob *bebob)
+{
+ u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
+ unsigned int i;
+ enum avc_bridgeco_plug_type type;
+ int err;
+
+ /* Get the number of Music Sub Unit for both direction. */
+ err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get info for MSU in/out plugs: %d\n",
+ err);
+ goto end;
+ }
+
+ /* seek destination plugs for 'MSU sync input' */
+ bebob->sync_input_plug = -1;
+ for (i = 0; i < plugs[0]; i++) {
+ avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i);
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for MSU in plug %d: %d\n",
+ i, err);
+ goto end;
+ }
+
+ if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) {
+ bebob->sync_input_plug = i;
+ break;
+ }
+ }
+end:
+ return err;
+}
+
+int snd_bebob_stream_discover(struct snd_bebob *bebob)
+{
+ const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
+ int err;
+
+ /* the number of plugs for isoc in/out, ext in/out */
+ err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get info for isoc/external in/out plugs: %d\n",
+ err);
+ goto end;
+ }
+
+ /*
+ * This module supports at least one isoc input plug and one isoc
+ * output plug.
+ */
+ if ((plugs[0] == 0) || (plugs[1] == 0)) {
+ err = -ENOSYS;
+ goto end;
+ }
+
+ err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_IN, 0,
+ bebob->rx_stream_formations);
+ if (err < 0)
+ goto end;
+
+ err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_OUT, 0,
+ bebob->tx_stream_formations);
+ if (err < 0)
+ goto end;
+
+ err = detect_midi_ports(bebob, bebob->tx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_IN,
+ plugs[2], &bebob->midi_input_ports);
+ if (err < 0)
+ goto end;
+
+ err = detect_midi_ports(bebob, bebob->rx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_OUT,
+ plugs[3], &bebob->midi_output_ports);
+ if (err < 0)
+ goto end;
+
+ /* for check source of clock later */
+ if (!clk_spec)
+ err = seek_msu_sync_input_plug(bebob);
+end:
+ return err;
+}
+
+void snd_bebob_stream_lock_changed(struct snd_bebob *bebob)
+{
+ bebob->dev_lock_changed = true;
+ wake_up(&bebob->hwdep_wait);
+}
+
+int snd_bebob_stream_lock_try(struct snd_bebob *bebob)
+{
+ guard(spinlock_irq)(&bebob->lock);
+
+ /* user land lock this */
+ if (bebob->dev_lock_count < 0)
+ return -EBUSY;
+
+ /* this is the first time */
+ if (bebob->dev_lock_count++ == 0)
+ snd_bebob_stream_lock_changed(bebob);
+ return 0;
+}
+
+void snd_bebob_stream_lock_release(struct snd_bebob *bebob)
+{
+ guard(spinlock_irq)(&bebob->lock);
+
+ if (WARN_ON(bebob->dev_lock_count <= 0))
+ return;
+ if (--bebob->dev_lock_count == 0)
+ snd_bebob_stream_lock_changed(bebob);
+}
diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c
new file mode 100644
index 000000000..c2dd074ec
--- /dev/null
+++ b/sound/firewire/bebob/bebob_terratec.c
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_terratec.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+static const enum snd_bebob_clock_type phase88_rack_clk_src_types[] = {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL,
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
+};
+static int
+phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ unsigned int enable_ext, enable_word;
+ int err;
+
+ err = avc_audio_get_selector(bebob->unit, 0, 9, &enable_ext);
+ if (err < 0)
+ goto end;
+ err = avc_audio_get_selector(bebob->unit, 0, 8, &enable_word);
+ if (err < 0)
+ goto end;
+
+ if (enable_ext == 0)
+ *id = 0;
+ else if (enable_word == 0)
+ *id = 1;
+ else
+ *id = 2;
+end:
+ return err;
+}
+
+static const struct snd_bebob_rate_spec phase_series_rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+
+/* PHASE 88 Rack FW */
+static const struct snd_bebob_clock_spec phase88_rack_clk = {
+ .num = ARRAY_SIZE(phase88_rack_clk_src_types),
+ .types = phase88_rack_clk_src_types,
+ .get = &phase88_rack_clk_src_get,
+};
+const struct snd_bebob_spec phase88_rack_spec = {
+ .clock = &phase88_rack_clk,
+ .rate = &phase_series_rate_spec,
+ .meter = NULL
+};
diff --git a/sound/firewire/bebob/bebob_yamaha_terratec.c b/sound/firewire/bebob/bebob_yamaha_terratec.c
new file mode 100644
index 000000000..ce1975e6a
--- /dev/null
+++ b/sound/firewire/bebob/bebob_yamaha_terratec.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * bebob_yamaha.c - a part of driver for BeBoB based devices
+ *
+ * Copyright (c) 2013-2014 Takashi Sakamoto
+ */
+
+#include "./bebob.h"
+
+/*
+ * NOTE:
+ * Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams
+ * must be accompanied. If changing the state, a LED on the device starts to
+ * blink and its sync status is false. In this state, the device sounds nothing
+ * even if streaming. To start streaming at the current sampling rate is only
+ * way to recover this state. GO46 is better for stand-alone mixer.
+ *
+ * Both of them have a capability to change its sampling rate up to 192.0kHz.
+ * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out.
+ * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use
+ * 'Extended Stream Format Information Command - Single Request' in 'Additional
+ * AVC commands' defined by BridgeCo.
+ * This ALSA driver don't do this because a bit tiresome. Then isochronous
+ * streaming with many asynchronous transactions brings sounds with noises.
+ * Unfortunately current 'ffado-mixer' generated many asynchronous transaction
+ * to observe device's state, mainly check cmp connection and signal format. I
+ * recommend users to close ffado-mixer at 192.0kHz if mixer is needless.
+ *
+ * Terratec PHASE 24 FW and PHASE X24 FW are internally the same as
+ * Yamaha GO 44 and GO 46. Yamaha and Terratec had cooperated for these models.
+ */
+
+static const enum snd_bebob_clock_type clk_src_types[] = {
+ SND_BEBOB_CLOCK_TYPE_INTERNAL,
+ SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
+};
+static int
+clk_src_get(struct snd_bebob *bebob, unsigned int *id)
+{
+ int err;
+
+ err = avc_audio_get_selector(bebob->unit, 0, 4, id);
+ if (err < 0)
+ return err;
+
+ if (*id >= ARRAY_SIZE(clk_src_types))
+ return -EIO;
+
+ return 0;
+}
+static const struct snd_bebob_clock_spec clock_spec = {
+ .num = ARRAY_SIZE(clk_src_types),
+ .types = clk_src_types,
+ .get = &clk_src_get,
+};
+static const struct snd_bebob_rate_spec rate_spec = {
+ .get = &snd_bebob_stream_get_rate,
+ .set = &snd_bebob_stream_set_rate,
+};
+const struct snd_bebob_spec yamaha_terratec_spec = {
+ .clock = &clock_spec,
+ .rate = &rate_spec,
+ .meter = NULL
+};