[alsa-devel] [PATCH 00/18] ALSA: firewire-motu: new driver for MOTU FireWire series

Hi,
This patchset updates my previous RFCv2, and go for ALSA upstream. http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117211.htm...
This patchset newly adds a driver into ALSA firewire stack, to support for some models in MOTU FireWire series: - 828mk2 - 828mk3 (FireWire only) - 828mk3 (Hybrid)
The driver supports playbacking/capturing PCM frames and MIDI messages for any sampling transfer frequencies, with current ALSA IEC 61883-1/6 packet streaming engine.
Unfortunately, units on MOTU FireWire series have many quirks against IEC 61883-1/6. This brings much commits and descriptions on this patchset. Sorry for reviewers but I'm glad to get your assist for this patchset.
As I note in 8th commit, at sampling transfer frequency based on 44.1kHz, the driver handles the units with choppy noises every few seconds. This is an issue which is not resolved yet.
Changes from RFCv2: - drop trial support for 828. If you have interests in development, please refer to one commit of my previous RFC. It describes v1 protocol. - http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117226.htm... - improve commit messages.
Takashi Sakamoto (18): ALSA: firewire-motu: add skeleton for Mark of the unicorn (MOTU) FireWire series ALSA: firewire-motu: postpone sound card registration ALSA: firewire-motu: add a structure for model-dependent parameters. ALSA: firewire-motu: add an abstraction layer for three types of protocols ALSA: firewire-lib: record cycle count for the first packet ALSA: firewire-lib: add support for source packet header field in CIP header ALSA: firewire-lib: enable CIP_DBC_IS_END_EVENT for both directions of stream ALSA: firewire-motu: add MOTU specific protocol layer ALSA: firewire-motu: handle transactions specific for MOTU FireWire models ALSA: firewire-motu: add stream management functionality ALSA: firewire-motu: add proc node to show current statuc of clock and packet formats ALSA: firewire-motu: add PCM functionality ALSA: firewire-motu: add MIDI functionality ALSA: firewire-motu: add hwdep interface ALSA: firewire-motu: enable to read transaction cache via hwdep interface ALSA: firewire-motu: add support for MOTU 828mk2 as a model with protocol version 2 ALSA: firewire-lib: add a quirk of packet without valid EOH in CIP format ALSA: firewire-motu: add support for MOTU 828mk3 (FireWire/Hybrid) as a model with protocol version 3
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 10 +- sound/firewire/Kconfig | 12 + sound/firewire/Makefile | 1 + sound/firewire/amdtp-stream.c | 36 ++- sound/firewire/amdtp-stream.h | 9 +- sound/firewire/motu/Makefile | 4 + sound/firewire/motu/amdtp-motu.c | 388 ++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-hwdep.c | 198 ++++++++++++++++ sound/firewire/motu/motu-midi.c | 169 ++++++++++++++ sound/firewire/motu/motu-pcm.c | 398 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-proc.c | 118 ++++++++++ sound/firewire/motu/motu-protocol-v2.c | 237 ++++++++++++++++++++ sound/firewire/motu/motu-protocol-v3.c | 312 ++++++++++++++++++++++++++ sound/firewire/motu/motu-stream.c | 381 +++++++++++++++++++++++++++++++ sound/firewire/motu/motu-transaction.c | 137 ++++++++++++ sound/firewire/motu/motu.c | 264 ++++++++++++++++++++++ sound/firewire/motu/motu.h | 161 +++++++++++++ 18 files changed, 2827 insertions(+), 11 deletions(-) create mode 100644 sound/firewire/motu/Makefile create mode 100644 sound/firewire/motu/amdtp-motu.c create mode 100644 sound/firewire/motu/motu-hwdep.c create mode 100644 sound/firewire/motu/motu-midi.c create mode 100644 sound/firewire/motu/motu-pcm.c create mode 100644 sound/firewire/motu/motu-proc.c create mode 100644 sound/firewire/motu/motu-protocol-v2.c create mode 100644 sound/firewire/motu/motu-protocol-v3.c create mode 100644 sound/firewire/motu/motu-stream.c create mode 100644 sound/firewire/motu/motu-transaction.c create mode 100644 sound/firewire/motu/motu.c create mode 100644 sound/firewire/motu/motu.h

This commit adds an new driver for MOTU FireWire series. In this commit, this driver just creates/removes card instance according to bus event. More functionalities will be added in following commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 10 ++++ sound/firewire/Makefile | 1 + sound/firewire/motu/Makefile | 2 + sound/firewire/motu/motu.c | 134 +++++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.h | 29 ++++++++++ 5 files changed, 176 insertions(+) create mode 100644 sound/firewire/motu/Makefile create mode 100644 sound/firewire/motu/motu.c create mode 100644 sound/firewire/motu/motu.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 9f00696..11a3285 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -140,4 +140,14 @@ config SND_FIREWIRE_TASCAM To compile this driver as a module, choose M here: the module will be called snd-firewire-tascam.
+config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 0ee1fb1..9388ded 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 0000000..d7819d5 --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,2 @@ +snd-firewire-motu-objs := motu.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 0000000..2684e74 --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,134 @@ +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_VERSION: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU (version:%d), GUID %08x%08x at %s, S%d", + version, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_card_free(struct snd_card *card) +{ + struct snd_motu *motu = card->private_data; + + fw_unit_put(motu->unit); + + mutex_destroy(&motu->mutex); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_motu *motu; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(*motu), &card); + if (err < 0) + return err; + + motu = card->private_data; + motu->card = card; + motu->unit = fw_unit_get(unit); + card->private_free = motu_card_free; + + mutex_init(&motu->mutex); + + name_card(motu); + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, motu); + + return 0; +error: + snd_card_free(card); + return err; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); +} + +static void motu_bus_update(struct fw_unit *unit) +{ + return; +} + +#define SND_MOTU_DEV_ENTRY(model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID | \ + IEEE1394_MATCH_SPECIFIER_ID, \ + .vendor_id = OUI_MOTU, \ + .model_id = model, \ + .specifier_id = OUI_MOTU, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 0000000..f3d0b28 --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,29 @@ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <sound/control.h> +#include <sound/core.h> + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; +}; + +#endif

Just after appearing on IEEE 1394 bus, this unit generates several bus resets. This is due to loading firmware from on-board flash memory and initialize hardware. It's better to postpone sound card registration.
This commit applies this idea.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/motu.c | 97 ++++++++++++++++++++++++++++++++++------------ sound/firewire/motu/motu.h | 5 +++ 2 files changed, 78 insertions(+), 24 deletions(-)
diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 2684e74..bdd82dd 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -38,59 +38,108 @@ static void name_card(struct snd_motu *motu) dev_name(&motu->unit->device), 100 << fw_dev->max_speed); }
-static void motu_card_free(struct snd_card *card) +static void motu_free(struct snd_motu *motu) { - struct snd_motu *motu = card->private_data; - fw_unit_put(motu->unit);
mutex_destroy(&motu->mutex); + kfree(motu); }
-static int motu_probe(struct fw_unit *unit, - const struct ieee1394_device_id *entry) +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void motu_card_free(struct snd_card *card) { - struct snd_card *card; - struct snd_motu *motu; - int err; + motu_free(card->private_data); +}
- err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, - sizeof(*motu), &card); - if (err < 0) - return err; +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err;
- motu = card->private_data; - motu->card = card; - motu->unit = fw_unit_get(unit); - card->private_free = motu_card_free; + if (motu->registered) + return;
- mutex_init(&motu->mutex); + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return;
name_card(motu);
- err = snd_card_register(card); + err = snd_card_register(motu->card); if (err < 0) goto error;
+ /* + * After registered, motu instance can be released corresponding to + * releasing the sound card instance. + */ + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + motu->registered = true; + + return; +error: + snd_card_free(motu->card); + dev_info(&motu->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_motu *motu; + + /* Allocate this independently of sound card instance. */ + motu = kzalloc(sizeof(struct snd_motu), GFP_KERNEL); + if (motu == NULL) + return -ENOMEM; + + motu->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, motu);
+ mutex_init(&motu->mutex); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); + snd_fw_schedule_registration(unit, &motu->dwork); + return 0; -error: - snd_card_free(card); - return err; }
static void motu_remove(struct fw_unit *unit) { struct snd_motu *motu = dev_get_drvdata(&unit->device);
- /* No need to wait for releasing card object in this context. */ - snd_card_free_when_closed(motu->card); + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&motu->dwork); + + if (motu->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); + } else { + /* Don't forget this case. */ + motu_free(motu); + } }
static void motu_bus_update(struct fw_unit *unit) { - return; + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!motu->registered) + snd_fw_schedule_registration(unit, &motu->dwork); }
#define SND_MOTU_DEV_ENTRY(model) \ diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index f3d0b28..eb0ffd5 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -20,10 +20,15 @@ #include <sound/control.h> #include <sound/core.h>
+#include "../lib.h" + struct snd_motu { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + + bool registered; + struct delayed_work dwork; };
#endif

MOTU FireWire series doesn't tell drivers their capabilities, thus the drivers should have and apply model-dependent parameters to detected models.
This commit adds a structure to represent such parameters. Capabilities are represented by enumeration except for the number of analog line in/out. Identification name also be in the structure because the units has no registers for this purpose.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/motu.c | 10 +++++++--- sound/firewire/motu/motu.h | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index bdd82dd..e69aa7b 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -31,9 +31,11 @@ static void name_card(struct snd_motu *motu) }
strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); snprintf(motu->card->longname, sizeof(motu->card->longname), - "MOTU (version:%d), GUID %08x%08x at %s, S%d", - version, + "MOTU %s (version:%d), GUID %08x%08x at %s, S%d", + motu->spec->name, version, fw_dev->config_rom[3], fw_dev->config_rom[4], dev_name(&motu->unit->device), 100 << fw_dev->max_speed); } @@ -101,6 +103,7 @@ static int motu_probe(struct fw_unit *unit, if (motu == NULL) return -ENOMEM;
+ motu->spec = (const struct snd_motu_spec *)entry->driver_data; motu->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, motu);
@@ -142,7 +145,7 @@ static void motu_bus_update(struct fw_unit *unit) snd_fw_schedule_registration(unit, &motu->dwork); }
-#define SND_MOTU_DEV_ENTRY(model) \ +#define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ IEEE1394_MATCH_MODEL_ID | \ @@ -150,6 +153,7 @@ static void motu_bus_update(struct fw_unit *unit) .vendor_id = OUI_MOTU, \ .model_id = model, \ .specifier_id = OUI_MOTU, \ + .driver_data = (kernel_ulong_t)data, \ }
static const struct ieee1394_device_id motu_id_table[] = { diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index eb0ffd5..cb7324d 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -29,6 +29,29 @@ struct snd_motu {
bool registered; struct delayed_work dwork; + + /* Model dependent information. */ + const struct snd_motu_spec *spec; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_SUPPORT_CLOCK_X2 = 0x0001, + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 = 0x0002, + SND_MOTU_SPEC_TX_MICINST_CHUNK = 0x0004, + SND_MOTU_SPEC_TX_RETURN_CHUNK = 0x0008, + SND_MOTU_SPEC_TX_REVERB_CHUNK = 0x0010, + SND_MOTU_SPEC_TX_AESEBU_CHUNK = 0x0020, + SND_MOTU_SPEC_HAS_OPT_IFACE_A = 0x0040, + SND_MOTU_SPEC_HAS_OPT_IFACE_B = 0x0080, + SND_MOTU_SPEC_HAS_MIDI = 0x0100, +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_spec_flags flags; + + unsigned char analog_in_ports; + unsigned char analog_out_ports; };
#endif

In an aspect of used protocols to communicate, models of MOTU FireWire units are categorized to three generations.
This commit adds an abstraction layer of the protocols for features related to packet streaming functionality. This layer includes 5 operations.
When configuring packet streaming functionality with sampling rate and sampling transmission frequency, .get_clock_rate and .set_clock_rate are called with proper arguments. MOTU FireWire series supports up to 192.0kHz.
When checking current source of sampling clock (not clock for packetization layer), .get_clock_source is used. Enumeration is added to represent the sources supported by this series. This operation can be used to expose available sampling rate to user space applications when the unit is configured to use any input signal as source of clock instead of crystal clock.
In the protocols, the path between packet processing layer and digital signal processing layer can be controlled. This looks a functionality to 'mute' the unit. For this feature, .switch_fetching_mode is added. This can be used to suppress noises every time packet streaming starts/stops.
In a point of the size of data blocks at a certain sampling transmission frequency, the most units accept several modes. This is due to usage of optical interfaces. The size differs depending on which modes are configured to the interfaces; None, S/PDIF and ADAT. Additionally, format of packet is different depending on protocols. To cache current size of data blocks and its format, .cache_packet_formats is added. This is used by PCM functionality, packet streaming functionality and data block processing layer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/motu.c | 12 ++++++++++++ sound/firewire/motu/motu.h | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+)
diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index e69aa7b..1e6fc74 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -14,6 +14,18 @@ MODULE_DESCRIPTION("MOTU FireWire driver"); MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); MODULE_LICENSE("GPL v2");
+const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + static void name_card(struct snd_motu *motu) { struct fw_device *fw_dev = fw_parent_device(motu->unit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index cb7324d..cb6b573 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -22,6 +22,16 @@
#include "../lib.h"
+struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char fixed_part_pcm_chunks[3]; + unsigned char differed_part_pcm_chunks[3]; +}; + struct snd_motu { struct snd_card *card; struct fw_unit *unit; @@ -32,6 +42,10 @@ struct snd_motu {
/* Model dependent information. */ const struct snd_motu_spec *spec; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; };
enum snd_motu_spec_flags { @@ -46,12 +60,41 @@ enum snd_motu_spec_flags { SND_MOTU_SPEC_HAS_MIDI = 0x0100, };
+#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +struct snd_motu_protocol { + int (*get_clock_rate)(struct snd_motu *motu, unsigned int *rate); + int (*set_clock_rate)(struct snd_motu *motu, unsigned int rate); + int (*get_clock_source)(struct snd_motu *motu, + enum snd_motu_clock_source *source); + int (*switch_fetching_mode)(struct snd_motu *motu, bool enable); + int (*cache_packet_formats)(struct snd_motu *motu); +}; + struct snd_motu_spec { const char *const name; enum snd_motu_spec_flags flags;
unsigned char analog_in_ports; unsigned char analog_out_ports; + + const struct snd_motu_protocol *const protocol; };
#endif

Currently, packet streaming layer passes generated SYT value to data block processing layer. However, this is not enough in a case that the data block processing layer generates time stamps by its own ways.
For out-packet stream, the packet streaming layer guarantees 8,000 times calls of data block processing layers per sec. Therefore, when cycle count of the first packet is recorded, data block processing layers can calculate own time stamps with the recorded value.
For the reason, this commit allows packet streaming layer to record the first cycle count. Each data block processing layer can read the count by accessing a member of structure for packet streaming layer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-stream.c | 15 +++++++++++++-- sound/firewire/amdtp-stream.h | 1 + 2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 00060c4..371cf97 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -671,6 +671,8 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, void *header, void *private_data) { struct amdtp_stream *s = private_data; + u32 cycle; + unsigned int packets;
/* * For in-stream, first packet has come. @@ -679,10 +681,19 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context, s->callbacked = true; wake_up(&s->callback_wait);
- if (s->direction == AMDTP_IN_STREAM) + cycle = compute_cycle_count(tstamp); + + if (s->direction == AMDTP_IN_STREAM) { + packets = header_length / IN_PACKET_HEADER_SIZE; + cycle = decrement_cycle_count(cycle, packets); context->callback.sc = in_stream_callback; - else + } else { + packets = header_length / 4; + cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets); context->callback.sc = out_stream_callback; + } + + s->start_cycle = cycle;
context->callback.sc(context, tstamp, header_length, header, s); } diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index c1bc7fa..edf5646 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -130,6 +130,7 @@ struct amdtp_stream { /* To wait for first packet. */ bool callbacked; wait_queue_head_t callback_wait; + u32 start_cycle;
/* For backends to process data blocks. */ void *protocol;

In IEC 61883-1, CIP headers can have a SPH field. When a packet has 1 in SPH field of its CIP header, the packet has a source packet headers. A source packet header consists of 32 bit field (= 1 quadlet) and it transfers time stamp, which is the same value as the lower 25 bits of the IEEE 1394 CYCLE_TIMER register and the rest is zero.
This commit just supports source packet header field because IEC 61883-1 includes ambiguity the position of this header and its count. Each protocol layer is allowed to have actual implementation according its requirements.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-stream.c | 8 ++++++-- sound/firewire/amdtp-stream.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 371cf97..65c5ed7 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -37,6 +37,8 @@ #define CIP_SID_MASK 0x3f000000 #define CIP_DBS_MASK 0x00ff0000 #define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 #define CIP_DBC_MASK 0x000000ff #define CIP_FMT_SHIFT 24 #define CIP_FMT_MASK 0x3f000000 @@ -426,6 +428,7 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | + ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | s->data_block_counter); buffer[1] = cpu_to_be32(CIP_EOH | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | @@ -454,7 +457,7 @@ static int handle_in_packet(struct amdtp_stream *s, { __be32 *buffer; u32 cip_header[2]; - unsigned int fmt, fdf, syt; + unsigned int sph, fmt, fdf, syt; unsigned int data_block_quadlets, data_block_counter, dbc_interval; unsigned int data_blocks; struct snd_pcm_substream *pcm; @@ -482,8 +485,9 @@ static int handle_in_packet(struct amdtp_stream *s, }
/* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; - if (fmt != s->fmt) { + if (sph != s->sph || fmt != s->fmt) { dev_info_ratelimited(&s->unit->device, "Detect unexpected protocol: %08x %08x\n", cip_header[0], cip_header[1]); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index edf5646..679053d 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -106,6 +106,7 @@ struct amdtp_stream { unsigned int source_node_id_field; unsigned int data_block_quadlets; unsigned int data_block_counter; + unsigned int sph; unsigned int fmt; unsigned int fdf; /* quirk: fixed interval of dbc between previos/current packets. */

Commit c8bdf49b9935("ALSA: fireworks/firewire-lib: Add a quirk for the meaning of dbc") adds CIP_DBC_IS_END_EVENT flag just for tx packets. However, MOTU FireWire series has this quirk for rx packets.
This commit allows both directions with the flag.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-stream.c | 8 +++++++- sound/firewire/amdtp-stream.h | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 65c5ed7..f9d12f4 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -426,6 +426,10 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, data_blocks = calculate_data_blocks(s, syt); pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt);
+ if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; + buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | @@ -435,7 +439,9 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | (syt & CIP_SYT_MASK));
- s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + s->data_block_counter = + (s->data_block_counter + data_blocks) & 0xff; payload_length = 8 + data_blocks * 4 * s->data_block_quadlets;
trace_out_packet(s, cycle, buffer, payload_length, index); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index 679053d..d2a3163 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -18,8 +18,8 @@ * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. - * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet - * corresponds to the end of event in the packet. Out of IEC 61883. + * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end + * of event in the packet. Out of IEC 61883. * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. * The value of data_block_quadlets is used instead of reported value. * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is

MOTU FireWire series uses blocking transmission for AMDTP packet streaming. They transmit/receive 8,000 packets per second, to handle the same number of data blocks as current sampling transmission frequency. Thus, IEC 61883-1/6 packet streaming engine of ALSA firewire stack is available for them.
However, the sequence of packet and data blocks includes some quirks. Below sample is a sequence of CIP headers of packets received by 828mk2, at 44.1kHz of sampling transmission frequency.
quads CIP1 CIP2 488 0x020F04E8 0x8222FFFF 8 0x020F04F8 0x8222FFFF 488 0x020F0400 0x8222FFFF 488 0x020F0408 0x8222FFFF 8 0x020F04E8 0x8222FFFF 488 0x020F04F0 0x8222FFFF 488 0x020F04F8 0x8222FFFF
The SID (source node ID), DBS (data block size), SPH (source packet header), FMT (format ID), FDF (format dependent field) and SYT (time stamp) fields are in IEC 61883-1. Especially, FMT is 0x02, FDF is 0x22 and SYT is 0xffff to define MOTU specific protocol. In an aspect of dbc field, the value represents accumulated number of data blocks included the packet. This is against IEC 61883-1, because according to the specification this value should be the number of data blocks already transferred.
In ALSA IEC 61883-1/6 engine, this quirk is already supported by CIP_DBC_IS_END_EVENT flag, because Echo Audio Fireworks has.
Each data block includes SPH as its first quadlet field, to represent its presentation time stamp. Actual value of SPH is compliant to IEC 61883-1; lower 25 bits of 32 bits width consists of 13 bits cycle count and 12 bits cycle offset.
The rest of each data block consists of 24 bit chunks. All of PCM samples, MIDI messages, status and control messages are transferred by the chunks. This is similar to '24-bit * 4 Audio Pack' in IEC 61883-6. The position of each kind of data depends on generations of each model. The number of whole chunks in a data block is a multiple of 4, to consists of quadlet-aligned packets.
This commit adds data block processing layer specific for the MOTU protocol. The remarkable point is the way to generate SPH header. Time stamps for each data blocks are generated by below calculation:
* Using pre-computed table for the number of ticks per event * 44,1kHz: (557 + 123/441) * 48.0kHz: (512 + 0/441) * 88.2kHz: (278 + 282/441) * 96.0kHz: (256 + 0/441) * 176.4kHz: (139 + 141/441) * 192.0kHz: (128 + 0/441) * Accumulate the ticks and set the value to SPH for every events. * This way makes sense only for blocking transmission because this mode transfers fixed number or none of events.
This calculation assumes that each data block has a PCM frame which is sampled according to event timing clock. Current packet streaming layer has the same assumption.
Although this sequence works fine for MOTU FireWire series at sampling transmission frequency based on 48.0kHz, it is not enough at the frequency based on 44.1kHz. The units generate choppy noise every few seconds.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 292 +++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.h | 13 +- 3 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 sound/firewire/motu/amdtp-motu.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index d7819d5..37391f5 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o +snd-firewire-motu-objs := motu.o amdtp-motu.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 0000000..11e4412 --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,292 @@ +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include "motu.h" + +#define CIP_FMT_MOTU 0x02 +#define MOTU_FDF_AM824 0x22 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + struct snd_motu_packet_format *formats) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_chunks, data_chunks, data_block_quadlets; + unsigned int delay; + unsigned int mode; + int i, err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + pcm_chunks = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + data_chunks = formats->msg_chunks + pcm_chunks; + + /* + * Each data block includes SPH in its head. Data chunks follow with + * 3 byte alignment. Padding follows with zero to conform to quadlet + * alignment. + */ + data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4); + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + /* IEEE 1394 bus requires. */ + delay = 0x2e00; + + /* For no-data or empty packets to adjust PCM sampling frequency. */ + delay += 8000 * 3072 * s->syt_interval / rate; + + p->next_seconds = 0; + p->next_cycles = delay / 3072; + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_ticks = delay % 3072; + p->next_accumulated = 0; + + return 0; +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + u32 *dst; + + channels = p->pcm_chunks; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | (byte[1] << 16) | byte[2]; + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u32 *src; + + channels = p->pcm_chunks; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + read_pcm_s32(s, pcm->runtime, buffer, data_blocks); + + return data_blocks; +} + +static inline void compute_next_elapse_from_start(struct amdtp_motu *p) +{ + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_ticks++; + } + + p->next_ticks += p->quotient_ticks_per_event; + if (p->next_ticks >= 3072) { + p->next_ticks -= 3072; + p->next_cycles++; + } + + if (p->next_cycles >= 8000) { + p->next_cycles -= 8000; + p->next_seconds++; + } + + if (p->next_seconds >= 128) + p->next_seconds -= 128; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm; + + /* Not used. */ + *syt = 0xffff; + + /* TODO: how to interact control messages between userspace? */ + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + write_pcm_s32(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + write_sph(s, buffer, data_blocks); + + return data_blocks; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_data_blocks = process_tx_data_blocks; + } else { + process_data_blocks = process_rx_data_blocks; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_data_blocks, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + s->fdf = MOTU_FDF_AM824; + + return 0; +} diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index cb6b573..cd1b3dd 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -19,12 +19,12 @@
#include <sound/control.h> #include <sound/core.h> +#include <sound/pcm.h>
#include "../lib.h" +#include "../amdtp-stream.h"
struct snd_motu_packet_format { - unsigned char midi_flag_offset; - unsigned char midi_byte_offset; unsigned char pcm_byte_offset;
unsigned char msg_chunks; @@ -46,6 +46,8 @@ struct snd_motu { /* For packet streaming */ struct snd_motu_packet_format tx_packet_formats; struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; };
enum snd_motu_spec_flags { @@ -97,4 +99,11 @@ struct snd_motu_spec { const struct snd_motu_protocol *const protocol; };
+int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_protocol *const protocol); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); #endif

All models of MOTU FireWire series can be controlled by write transaction to addresses in a range from 0x'ffff'f0000'0b00 to 0x'ffff'f000'0cff.
The models support asynchronous notification. This notification has 32 bit field data, and is transferred when status of clock changes. Meaning of the value is not enough clear yet.
Drivers can register its address to receive the notification. Write transaction to 0x'ffff'f000'0b04 registers higher 16 bits of the address. Write transaction to 0x'ffff'f0000'0b08 registers the rest of bits. The address includes node ID, thus it should be registered every time of bus reset.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-transaction.c | 117 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 10 +++ sound/firewire/motu/motu.h | 12 ++++ 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-transaction.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 37391f5..03b0769 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o amdtp-motu.o +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 0000000..416dd98 --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,117 @@ +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + fw_send_response(card, request, RCODE_COMPLETE); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 1e6fc74..db6014c 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -54,6 +54,8 @@ static void name_card(struct snd_motu *motu)
static void motu_free(struct snd_motu *motu) { + snd_motu_transaction_unregister(motu); + fw_unit_put(motu->unit);
mutex_destroy(&motu->mutex); @@ -86,6 +88,10 @@ static void do_registration(struct work_struct *work)
name_card(motu);
+ err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; @@ -100,6 +106,7 @@ static void do_registration(struct work_struct *work)
return; error: + snd_motu_transaction_unregister(motu); snd_card_free(motu->card); dev_info(&motu->unit->device, "Sound card registration failed: %d\n", err); @@ -155,6 +162,9 @@ static void motu_bus_update(struct fw_unit *unit) /* Postpone a workqueue for deferred registration. */ if (!motu->registered) snd_fw_schedule_registration(unit, &motu->dwork); + + /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); }
#define SND_MOTU_DEV_ENTRY(model, data) \ diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index cd1b3dd..ed1d779 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -48,6 +48,10 @@ struct snd_motu { struct snd_motu_packet_format rx_packet_formats; struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; };
enum snd_motu_spec_flags { @@ -106,4 +110,12 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, struct snd_motu_packet_format *formats); int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); #endif

This commit adds a functionality to manage packet streaming for MOTU FireWire series.
The streaming is not controlled by CMP, thus against IEC 61883-1. Write transaction to certain addresses start/stop packet streaming.
Transactions to 0x'ffff'f000'0b00 results in isochronous channel number for both directions and starting/stopping transmission of packets. The isochronous channel number is represented in 6 bit field, thus units can identify the channels up to 64, as IEEE 1394 bus specification described.
Transactions to 0x'ffff'f000'0b10 results in packet format for both directions and transmission speed. When each of data block includes fixed part of data chunks only, corresponding flags stand.
When bus reset occurs, the units continue to transmit packets with non-contiguous data block counter. This causes discontinuity detection in packet streaming engine and ALSA PCM applications receives EPIPE from any I/O operation. In this case, typical applications manage to recover corresponding PCM substream. This behaviour is kicked much earlier than callback of bus reset handler by Linux FireWire subsystem, therefore status of packet streaming is not changed in the handler.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-stream.c | 340 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 5 + sound/firewire/motu/motu.h | 10 ++ 4 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-stream.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 03b0769..504a4f9 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 0000000..9aa698f --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,340 @@ +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int start_both_streams(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int err; + + /* Set packet formation to our packet streaming engine. */ + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, + &motu->rx_packet_formats); + if (err < 0) + return err; + + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, + &motu->tx_packet_formats); + if (err < 0) + return err; + + + /* Get isochronous resources on the bus. */ + err = fw_iso_resources_allocate(&motu->rx_resources, + amdtp_stream_get_max_payload(&motu->rx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + err = fw_iso_resources_allocate(&motu->tx_resources, + amdtp_stream_get_max_payload(&motu->tx_stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + /* Configure the unit to start isochronous communication. */ + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void stop_both_streams(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = motu->spec->protocol->switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); +} + +static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + int err; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + err = amdtp_stream_start(stream, resources->channel, + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); + return -ETIMEDOUT; + } + + return 0; +} + +static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + + if (stream == &motu->rx_stream) + resources = &motu->rx_resources; + else + resources = &motu->tx_resources; + + amdtp_stream_stop(stream); + fw_iso_resources_free(resources); +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate) +{ + const struct snd_motu_protocol *protocol = motu->spec->protocol; + unsigned int curr_rate; + int err = 0; + + if (motu->capture_substreams == 0 && motu->playback_substreams == 0) + return 0; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + err = protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + /* Stop stream if rate is different. */ + err = protocol->get_clock_rate(motu, &curr_rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to get sampling rate: %d\n", err); + return err; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(&motu->rx_stream); + amdtp_stream_stop(&motu->tx_stream); + stop_both_streams(motu); + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + err = protocol->set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = start_both_streams(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = start_isoc_ctx(motu, &motu->rx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IT context: %d\n", err); + stop_both_streams(motu); + return err; + } + + err = protocol->switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + stop_both_streams(motu); + return err; + } + } + + if (!amdtp_stream_running(&motu->tx_stream) && + motu->capture_substreams > 0) { + err = start_isoc_ctx(motu, &motu->tx_stream); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start IR context: %d", err); + amdtp_stream_stop(&motu->rx_stream); + stop_both_streams(motu); + return err; + } + } + + return 0; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->capture_substreams == 0) { + if (amdtp_stream_running(&motu->tx_stream)) + stop_isoc_ctx(motu, &motu->tx_stream); + + if (motu->playback_substreams == 0) { + if (amdtp_stream_running(&motu->rx_stream)) + stop_isoc_ctx(motu, &motu->rx_stream); + stop_both_streams(motu); + } + } +} + +static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir) +{ + int err; + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } + + return err; +} + +static void destroy_stream(struct snd_motu *motu, + enum amdtp_stream_direction dir) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &motu->tx_stream; + resources = &motu->tx_resources; + } else { + stream = &motu->rx_stream; + resources = &motu->rx_resources; + } + + amdtp_stream_destroy(stream); + fw_iso_resources_free(resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, AMDTP_IN_STREAM); + if (err < 0) + return err; + + err = init_stream(motu, AMDTP_OUT_STREAM); + if (err < 0) + destroy_stream(motu, AMDTP_IN_STREAM); + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + destroy_stream(motu, AMDTP_IN_STREAM); + destroy_stream(motu, AMDTP_OUT_STREAM); + + motu->playback_substreams = 0; + motu->capture_substreams = 0; +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index db6014c..9d52238 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -56,6 +56,7 @@ static void motu_free(struct snd_motu *motu) { snd_motu_transaction_unregister(motu);
+ snd_motu_stream_destroy_duplex(motu); fw_unit_put(motu->unit);
mutex_destroy(&motu->mutex); @@ -92,6 +93,10 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error;
+ err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index ed1d779..90d2741 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -23,6 +23,7 @@
#include "../lib.h" #include "../amdtp-stream.h" +#include "../iso-resources.h"
struct snd_motu_packet_format { unsigned char pcm_byte_offset; @@ -48,6 +49,10 @@ struct snd_motu { struct snd_motu_packet_format rx_packet_formats; struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int capture_substreams; + unsigned int playback_substreams;
/* For notification. */ struct fw_address_handler async_handler; @@ -118,4 +123,9 @@ int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, int snd_motu_transaction_register(struct snd_motu *motu); int snd_motu_transaction_reregister(struct snd_motu *motu); void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); #endif

This commit adds a proc node for debugging purpose.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 3 +- sound/firewire/motu/motu-proc.c | 118 ++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 2 + sound/firewire/motu/motu.h | 3 + 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-proc.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 504a4f9..0eccbe2 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,3 @@ -snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ + motu-proc.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 0000000..4edc064 --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,118 @@ +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PCIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int rate; + enum snd_motu_clock_source source; + + if (protocol->get_clock_rate(motu, &rate) < 0) + return; + if (protocol->get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (protocol->cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\tdiffered\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + formats->fixed_part_pcm_chunks[mode], + formats->differed_part_pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, 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(motu->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, motu, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + 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(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 9d52238..cbf4ed0 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -97,6 +97,8 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error;
+ snd_motu_proc_init(motu); + err = snd_card_register(motu->card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 90d2741..4d079d6 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -20,6 +20,7 @@ #include <sound/control.h> #include <sound/core.h> #include <sound/pcm.h> +#include <sound/info.h>
#include "../lib.h" #include "../amdtp-stream.h" @@ -128,4 +129,6 @@ int snd_motu_stream_init_duplex(struct snd_motu *motu); void snd_motu_stream_destroy_duplex(struct snd_motu *motu); int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); #endif

This commit adds PCM functionality to transmit/receive PCM samples.
When one of PCM substreams are running or external clock source is selected, current sampling rate is used. Else, the sampling rate is changed according to requests from a userspace application.
Available number of samples in a frame of PCM substream is determined at open(2) to corresponding PCM character device. Later, packet streaming starts by ioctl(2) with SNDRV_PCM_IOCTL_PREPARE. In theory, between them, applications can change state of the unit by any write transaction to change the number. In this case, this driver may fail packet streaming due to wrong data format.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-pcm.c | 386 +++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 4 + sound/firewire/motu/motu.h | 2 + 4 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-pcm.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 0eccbe2..508b689 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o + motu-proc.o motu-pcm.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 0000000..a50bcd6 --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,386 @@ +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm_params.h> +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->fixed_part_pcm_chunks[mode] + + formats->differed_part_pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* byte for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + hw->info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_BLOCK_TRANSFER; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + limit_period_and_buffer(hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + const struct snd_motu_protocol *const protocol = motu->spec->protocol; + enum snd_motu_clock_source src; + unsigned int rate; + int err; + + mutex_lock(&motu->mutex); + + err = protocol->cache_packet_formats(motu); + if (err < 0) + return err; + + err = init_hw_info(motu, substream); + if (err < 0) + return err; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + err = protocol->get_clock_source(motu, &src); + if (err < 0) + return err; + if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || + amdtp_stream_pcm_running(&motu->tx_stream) || + amdtp_stream_pcm_running(&motu->rx_stream)) { + err = protocol->get_clock_rate(motu, &rate); + if (err < 0) + return err; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->capture_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->playback_substreams++; + mutex_unlock(&motu->mutex); + } + + return 0; +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->capture_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->playback_substreams--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + return 0; +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index cbf4ed0..801d6a7 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -99,6 +99,10 @@ static void do_registration(struct work_struct *work)
snd_motu_proc_init(motu);
+ err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 4d079d6..afc6de6 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -131,4 +131,6 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu);
void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); #endif

In MOTU FireWire series, MIDI messages are multiplexed to isochronous packets as well as PCM frames, while the way is different from the one in IEC 61883-6.
MIDI messages are put into a certain position in message chunks. One data block can includes one byte of the MIDI messages. When data block includes a MIDI byte, the block has a flag in a certain position of the message chunk. These positions are unique depending on protocols.
Once a data block includes a MIDI byte, some following data blocks includes no MIDI bytes. Next MIDI byte appears on a data block corresponding to next cycle of physical MIDI bus. This seems to avoid buffer overflow caused by bandwidth differences between IEEE 1394 bus and physical MIDI bus.
This commit adds MIDI functionality to transfer/receive MIDI messages.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 84 +++++++++++++++++++++ sound/firewire/motu/motu-midi.c | 153 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-stream.c | 9 ++- sound/firewire/motu/motu.c | 7 ++ sound/firewire/motu/motu.h | 9 +++ 6 files changed, 260 insertions(+), 4 deletions(-) create mode 100644 sound/firewire/motu/motu-midi.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 508b689..a512c1e 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o motu-pcm.o + motu-proc.o motu-pcm.o motu-midi.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 11e4412..0930cd8 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -13,6 +13,12 @@ #define CIP_FMT_MOTU 0x02 #define MOTU_FDF_AM824 0x22
+/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + struct amdtp_motu { /* For timestamp processing. */ unsigned int quotient_ticks_per_event; @@ -24,9 +30,18 @@ struct amdtp_motu {
unsigned int pcm_chunks; unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; };
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, struct snd_motu_packet_format *formats) { static const struct { @@ -76,6 +91,13 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, p->pcm_chunks = pcm_chunks; p->pcm_byte_offset = formats->pcm_byte_offset;
+ p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + p->midi_db_count = 0; + p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND; + /* IEEE 1394 bus requires. */ delay = 0x2e00;
@@ -187,12 +209,70 @@ int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, return amdtp_stream_add_pcm_hw_constraints(s, runtime); }
+void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, unsigned int *syt) { + struct amdtp_motu *p = s->protocol; struct snd_pcm_substream *pcm;
+ if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + pcm = ACCESS_ONCE(s->pcm); if (data_blocks > 0 && pcm) read_pcm_s32(s, pcm->runtime, buffer, data_blocks); @@ -246,6 +326,7 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, unsigned int *syt) { + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; struct snd_pcm_substream *pcm;
/* Not used. */ @@ -253,6 +334,9 @@ static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
/* TODO: how to interact control messages between userspace? */
+ if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + pcm = ACCESS_ONCE(s->pcm); if (pcm) write_pcm_s32(s, pcm->runtime, buffer, data_blocks); diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 0000000..f232f29 --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,153 @@ +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "motu.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + mutex_lock(&motu->mutex); + + motu->capture_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + mutex_lock(&motu->mutex); + + motu->playback_substreams++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->capture_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->playback_substreams--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index 9aa698f..911d348 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -28,22 +28,25 @@
static int start_both_streams(struct snd_motu *motu, unsigned int rate) { + unsigned int midi_ports = 0; __be32 reg; u32 data; int err;
+ if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) + midi_ports = 1; + /* Set packet formation to our packet streaming engine. */ - err = amdtp_motu_set_parameters(&motu->rx_stream, rate, + err = amdtp_motu_set_parameters(&motu->rx_stream, rate, midi_ports, &motu->rx_packet_formats); if (err < 0) return err;
- err = amdtp_motu_set_parameters(&motu->tx_stream, rate, + err = amdtp_motu_set_parameters(&motu->tx_stream, rate, midi_ports, &motu->tx_packet_formats); if (err < 0) return err;
- /* Get isochronous resources on the bus. */ err = fw_iso_resources_allocate(&motu->rx_resources, amdtp_stream_get_max_payload(&motu->rx_stream), diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 801d6a7..d4da137 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -103,6 +103,12 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error;
+ if (motu->spec->flags & SND_MOTU_SPEC_HAS_MIDI) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + err = snd_card_register(motu->card); if (err < 0) goto error; @@ -138,6 +144,7 @@ static int motu_probe(struct fw_unit *unit, dev_set_drvdata(&unit->device, motu);
mutex_init(&motu->mutex); + spin_lock_init(&motu->lock);
/* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index afc6de6..338b351 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -21,12 +21,15 @@ #include <sound/core.h> #include <sound/pcm.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../lib.h" #include "../amdtp-stream.h" #include "../iso-resources.h"
struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; unsigned char pcm_byte_offset;
unsigned char msg_chunks; @@ -38,6 +41,7 @@ struct snd_motu { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + spinlock_t lock;
bool registered; struct delayed_work dwork; @@ -113,9 +117,12 @@ int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, const struct snd_motu_protocol *const protocol); int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, struct snd_motu_packet_format *formats); int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi);
int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, size_t size); @@ -133,4 +140,6 @@ void snd_motu_stream_stop_duplex(struct snd_motu *motu); void snd_motu_proc_init(struct snd_motu *motu);
int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); #endif

This commit adds hwdep interface so as the other sound drivers for units on IEEE 1394 bus have.
This interface is designed for mixer/control applications. By using this interface, an application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-hwdep.c | 192 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-midi.c | 16 ++++ sound/firewire/motu/motu-pcm.c | 20 +++- sound/firewire/motu/motu-stream.c | 38 ++++++++ sound/firewire/motu/motu.c | 5 + sound/firewire/motu/motu.h | 13 +++ 9 files changed, 285 insertions(+), 7 deletions(-) create mode 100644 sound/firewire/motu/motu-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index be353a7..fd7b561 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -107,9 +107,10 @@ enum { SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ + SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_LINE6 + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_MOTU };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index db79a12..59c6d81 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -65,7 +65,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 -/* RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_MOTU 7 +/* RME... */
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index a512c1e..cc195d5 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o motu-pcm.o motu-midi.o + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 0000000..e795a52 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,192 @@ +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + unsigned int events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | POLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + 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]); + strlcpy(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_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + 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_motu_create_hwdep_device(struct snd_motu *motu) +{ + 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(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c index f232f29..e3acfcc 100644 --- a/sound/firewire/motu/motu-midi.c +++ b/sound/firewire/motu/motu-midi.c @@ -12,6 +12,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) struct snd_motu *motu = substream->rmidi->private_data; int err;
+ err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex);
motu->capture_substreams++; @@ -19,6 +23,9 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream)
mutex_unlock(&motu->mutex);
+ if (err < 0) + snd_motu_stream_lock_release(motu); + return err; }
@@ -27,6 +34,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) struct snd_motu *motu = substream->rmidi->private_data; int err;
+ err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex);
motu->playback_substreams++; @@ -34,6 +45,9 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
mutex_unlock(&motu->mutex);
+ if (err < 0) + snd_motu_stream_lock_release(motu); + return err; }
@@ -48,6 +62,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
mutex_unlock(&motu->mutex);
+ snd_motu_stream_lock_release(motu); return 0; }
@@ -62,6 +77,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
mutex_unlock(&motu->mutex);
+ snd_motu_stream_lock_release(motu); return 0; }
diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c index a50bcd6..94558f3 100644 --- a/sound/firewire/motu/motu-pcm.c +++ b/sound/firewire/motu/motu-pcm.c @@ -159,15 +159,19 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int rate; int err;
+ err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex);
err = protocol->cache_packet_formats(motu); if (err < 0) - return err; + goto err_locked;
err = init_hw_info(motu, substream); if (err < 0) - return err; + goto err_locked;
/* * When source of clock is not internal or any PCM streams are running, @@ -175,13 +179,13 @@ static int pcm_open(struct snd_pcm_substream *substream) */ err = protocol->get_clock_source(motu, &src); if (err < 0) - return err; + goto err_locked; if (src != SND_MOTU_CLOCK_SOURCE_INTERNAL || amdtp_stream_pcm_running(&motu->tx_stream) || amdtp_stream_pcm_running(&motu->rx_stream)) { err = protocol->get_clock_rate(motu, &rate); if (err < 0) - return err; + goto err_locked; substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; } @@ -191,10 +195,18 @@ static int pcm_open(struct snd_pcm_substream *substream) mutex_unlock(&motu->mutex);
return err; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + return 0; }
diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index 911d348..bd45802 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -341,3 +341,41 @@ void snd_motu_stream_destroy_duplex(struct snd_motu *motu) motu->playback_substreams = 0; motu->capture_substreams = 0; } + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index d4da137..619554b 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -109,6 +109,10 @@ static void do_registration(struct work_struct *work) goto error; }
+ err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + err = snd_card_register(motu->card); if (err < 0) goto error; @@ -145,6 +149,7 @@ static int motu_probe(struct fw_unit *unit,
mutex_init(&motu->mutex); spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait);
/* Allocate and register this sound card later. */ INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 338b351..7b1d85f 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -16,12 +16,16 @@ #include <linux/mod_devicetable.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h>
#include <sound/control.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../lib.h" #include "../amdtp-stream.h" @@ -62,6 +66,11 @@ struct snd_motu { /* For notification. */ struct fw_address_handler async_handler; u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
enum snd_motu_spec_flags { @@ -136,10 +145,14 @@ int snd_motu_stream_init_duplex(struct snd_motu *motu); void snd_motu_stream_destroy_duplex(struct snd_motu *motu); int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu);
void snd_motu_proc_init(struct snd_motu *motu);
int snd_motu_create_pcm_devices(struct snd_motu *motu);
int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(struct snd_motu *motu); #endif

MOTU FireWire series can transfer messages to registered address. These messages are transferred for the status of internal clock synchronization just after starting streams.
When the synchronization is stable, it's 0x01ffffff. Else, it's 0x05ffffff.
This commit adds a functionality for user space applications to receive content of the message.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/firewire.h | 7 +++++++ sound/firewire/motu/motu-hwdep.c | 10 ++++++++-- sound/firewire/motu/motu-transaction.c | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 59c6d81..29afc5e 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -10,6 +10,7 @@ #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475 #define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c +#define SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION 0x64776479
struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -46,12 +47,18 @@ struct snd_firewire_event_digi00x_message { __u32 message; /* Digi00x-specific message */ };
+struct snd_firewire_event_motu_notification { + unsigned int type; + __u32 message; /* MOTU-specific bits. */ +}; + union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_dice_notification dice_notification; struct snd_firewire_event_efw_response efw_response; struct snd_firewire_event_digi00x_message digi00x_message; + struct snd_firewire_event_motu_notification motu_notification; };
diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c index e795a52..b87ccb6 100644 --- a/sound/firewire/motu/motu-hwdep.c +++ b/sound/firewire/motu/motu-hwdep.c @@ -26,7 +26,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
spin_lock_irq(&motu->lock);
- while (!motu->dev_lock_changed) { + while (!motu->dev_lock_changed && motu->msg == 0) { prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); spin_unlock_irq(&motu->lock); schedule(); @@ -43,6 +43,12 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, motu->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); }
spin_unlock_irq(&motu->lock); @@ -62,7 +68,7 @@ static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_wait(file, &motu->hwdep_wait, wait);
spin_lock_irq(&motu->lock); - if (motu->dev_lock_changed) + if (motu->dev_lock_changed || motu->msg) events = POLLIN | POLLRDNORM; else events = 0; diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c index 416dd98..7fc3009 100644 --- a/sound/firewire/motu/motu-transaction.c +++ b/sound/firewire/motu/motu-transaction.c @@ -50,7 +50,27 @@ static void handle_message(struct fw_card *card, struct fw_request *request, int generation, unsigned long long offset, void *data, size_t length, void *callback_data) { + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); }
int snd_motu_transaction_reregister(struct snd_motu *motu)

MOTU 828mk2 is one of second generation in MOTU FireWire series, produced in 2003. This model consists of four chips: * TI TSB41AB2 (Physical layer for IEEE 1394 bus) * PDI 1394L40BE (Link layer for IEEE 1394 bus and packet processing layer) * ALTERA ACEX 1K EP1K30 Series FPGA (Data block processing layer) * TI TMS320VC5402 (Digital signal processing)
This commit adds a support for this model, with its unique protocol as version 2. The features of this protocol are:
* Support data chunks for status and control messages for both directions. * Support a pair of MIDI input/output. * Support a data chunk for mic/instrument independent of analog line in. * Support a data chunk for playback return. * Support independent data chunks for S/PDIF of both optical/coaxial interfaces. * Support independent data chunks for each of main out and phone out.
Status of clock is configured by write transactions to 0x'ffff'f000'0b14. Modes of optical interfaces are configured by write transactions to 0x'ffff'f000'0c04.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/motu/Makefile | 3 +- sound/firewire/motu/motu-protocol-v2.c | 237 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 14 ++ sound/firewire/motu/motu.h | 2 + 5 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-protocol-v2.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 11a3285..951d510 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -146,6 +146,7 @@ config SND_FIREWIRE_MOTU select SND_HWDEP help Say Y here to enable support for FireWire devices which MOTU produced: + * 828mk2
To compile this driver as a module, choose M here: the module will be called snd-firewire-motu. diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index cc195d5..21968bc 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,4 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ - motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 0000000..05b5d287 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,237 @@ +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = (be32_to_cpu(reg) & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +static int v2_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + unsigned int index; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK; + if (index > 5) + return -EIO; + + /* To check the configuration of optical interface. */ + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + if (be32_to_cpu(reg) & 0x00000200) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB; + break; + default: + return -EIO; + } + + return 0; +} + +static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + /* V2 protocol doesn't have this feature. */ + return 0; +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * All of v2 models have a pair of coaxial interfaces for digital in/out + * port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these + * ports. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* This part should be multiples of 4. */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, + u32 data, u32 mask, u32 shift) +{ + unsigned char pcm_chunks[3] = {0, 0}; + + /* + * When optical interfaces are configured for S/PDIF (TOSLINK), + * the above PCM frames come from them, instead of coaxial + * interfaces. + */ + data = (data & mask) >> shift; + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && + data == V2_OPT_IFACE_MODE_ADAT) { + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + + /* At mode x4, no data chunks are supported in this part. */ + formats->differed_part_pcm_chunks[0] = pcm_chunks[0]; + formats->differed_part_pcm_chunks[1] = pcm_chunks[1]; +} + +static int v2_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags, + data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags, + data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT); + + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v2 = { + .get_clock_rate = v2_get_clock_rate, + .set_clock_rate = v2_set_clock_rate, + .get_clock_source = v2_get_clock_source, + .switch_fetching_mode = v2_switch_fetching_mode, + .cache_packet_formats = v2_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 619554b..0acd134 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -190,6 +190,19 @@ static void motu_bus_update(struct fw_unit *unit) snd_motu_transaction_reregister(motu); }
+static struct snd_motu_spec motu_828mk2 = { + .name = "828mk2", + .protocol = &snd_motu_protocol_v2, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + #define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ @@ -202,6 +215,7 @@ static void motu_bus_update(struct fw_unit *unit) }
static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), { } }; MODULE_DEVICE_TABLE(ieee1394, motu_id_table); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 7b1d85f..29f20a5 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -122,6 +122,8 @@ struct snd_motu_spec { const struct snd_motu_protocol *const protocol; };
+extern const struct snd_motu_protocol snd_motu_protocol_v2; + int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, const struct snd_motu_protocol *const protocol);

In IEC 61883-1, when two quadlets CIP header is used, the most significant bit in second CIP header stands. However, packets from units with MOTU protocol version 3 have a quirk without this flag. Current packet streaming layer handles this as protocol error.
This commit adds a new enumeration constant for this quirk, to handle MOTU protocol version 3.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-stream.c | 5 +++-- sound/firewire/amdtp-stream.h | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index f9d12f4..112ad03 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -480,8 +480,9 @@ static int handle_in_packet(struct amdtp_stream *s, * This module supports 'Two-quadlet CIP header with SYT field'. * For convenience, also check FMT field is AM824 or not. */ - if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || - ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) { + if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) && + (!(s->flags & CIP_HEADER_WITHOUT_EOH))) { dev_info_ratelimited(&s->unit->device, "Invalid CIP header for AMDTP: %08X:%08X\n", cip_header[0], cip_header[1]); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index d2a3163..a31dfd8 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -29,6 +29,8 @@ * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an * packet is larger than IEC 61883-6 defines. Current implementation * allows 5 times as large as IEC 61883-6 defines. + * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include + * valid EOH. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -39,6 +41,7 @@ enum cip_flags { CIP_SKIP_DBC_ZERO_CHECK = 0x10, CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_JUMBO_PAYLOAD = 0x40, + CIP_HEADER_WITHOUT_EOH = 0x80, };
/**

MOTU 828mk3 (FireWire/Hybrid) is one of third generation in MOTU FireWire series, produced in 2008/2014. This model consists of three chips for functionality on IEEE 1394 bus:
* TI TSB41AB2 (Physical layer for IEEE 1394 bus) * Xilinx Spartan-3E FPGA Family (Link layer for IEEE 1394 bus, packet processing and data block processing layer) * TI TMS320C6722 (Digital signal processing)
This commit adds a support for this model, with its unique protocol as version 3. This protocol has some additional features to protocol version 2.
* Support several optical interfaces. * Support a data chunk for return of reverb effect. * Have a quirk of tx packets. * Support heartbeat asynchronous transaction.
In this protocol, series of transferred packets has some quirks. Below fields in CIP headers of the packets are out of IEC 61883-1: - SID (source node id): always 0x0d - DBS (data block size): always 0x04 - DBC (data block counter): always 0x00 - EOH (End of header): always 0x00
Below is an actual sample of transferred packets.
quads CIP1 CIP2 520 0x0D040400 0x22FFFFFF 8 0x0D040400 0x22FFFFFF 520 0x0D040400 0x22FFFFFF 520 0x0D040400 0x22FFFFFF 8 0x0D040400 0x22FFFFFF
Status of clock is configured by write transactions to 0x'ffff'f000'0b14, as well as version 2, while meanings of fields are different from the former protocols. Modes of optical interfaces are configured by write transactions to 0x'ffff'f000'0c94.
Drivers can register its address to receive heatbeat transactions from the unit. 0x'ffff'f000'0b0c is for the higher part and 0x'ffff'f000'0b10 is for the lower part. Nevertheless, this feature is not useless for this driver and this commit omits it.
Each data block consists of two parts in a point of the number of included data chunks. In both of 'fixed' and 'differed' parts, the number of included data blocks are a multiple of 4, thus depending on models there's some empty data chunks. For example, 828mk3 includes one pair of empty data chunks in its fixed part. When optical interface is configured to S/PDIF, 828mk3 includes one pair of empty data chunks in its differed part. To reduce consumption of CPU cycles with additional conditions/loops, this commit just exposes these empty chunks to user space as PCM channels.
Additionally, 828mk3 has a non-negligible overhead to change its sampling transfer frequency. When softwares send asynchronous transaction to perform it, LED on the unit starts to blink. In a worst case, it continues blink during several seconds; e.g. 10 seconds. When stopping blinking, the unit seems to be prepared for the requested sampling transfer frequency. To wait for the preparation, this commit forces the driver to call task scheduler and applications sleeps for 4 seconds.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 12 ++ sound/firewire/motu/motu-protocol-v3.c | 312 +++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 18 ++ sound/firewire/motu/motu.h | 1 + 6 files changed, 345 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-protocol-v3.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 951d510..6acfacf 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -147,6 +147,7 @@ config SND_FIREWIRE_MOTU help Say Y here to enable support for FireWire devices which MOTU produced: * 828mk2 + * 828mk3
To compile this driver as a module, choose M here: the module will be called snd-firewire-motu. diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 21968bc..ae84ae6 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,4 +1,4 @@ snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ - motu-protocol-v2.o + motu-protocol-v2.o motu-protocol-v3.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c index 0930cd8..08bd176 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -11,6 +11,7 @@ #include "motu.h"
#define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 #define MOTU_FDF_AM824 0x22
/* @@ -359,6 +360,17 @@ int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
if (dir == AMDTP_IN_STREAM) { process_data_blocks = process_tx_data_blocks; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (protocol == &snd_motu_protocol_v3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } } else { process_data_blocks = process_rx_data_blocks; flags |= CIP_DBC_IS_END_EVENT; diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c new file mode 100644 index 0000000..b463da9 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,312 @@ +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff +#define V3_CLOCK_SOURCE_SHIFT 8 + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +static int v3_get_clock_rate(struct snd_motu *motu, unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +static int v3_set_clock_rate(struct snd_motu *motu, unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + /* Cost expensive. */ + if (msleep_interruptible(4000) > 0) + return -EINTR; + } + + return 0; +} + +static int v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + u32 data; + unsigned int val; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + val = (data & V3_CLOCK_SOURCE_MASK) >> V3_CLOCK_SOURCE_SHIFT; + if (val == 0x00) { + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + } else if (val == 0x01) { + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + } else if (val == 0x10) { + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + } else if (val == 0x18 || val == 0x19) { + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (val == 0x18) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; + } + } else { + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + } + + return 0; +} + +static int v3_switch_fetching_mode(struct snd_motu *motu, bool enable) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static void calculate_fixed_part(struct snd_motu_packet_format *formats, + enum amdtp_stream_direction dir, + enum snd_motu_spec_flags flags, + unsigned char analog_ports) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + + formats->msg_chunks = 2; + + pcm_chunks[0] = analog_ports; + pcm_chunks[1] = analog_ports; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] = analog_ports; + + if (dir == AMDTP_IN_STREAM) { + if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + pcm_chunks[2] += 2; + } + + if (flags & SND_MOTU_SPEC_TX_REVERB_CHUNK) { + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + } + } else { + /* + * Packets to v2 units transfer main-out-1/2 and phone-out-1/2. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } + + /* + * At least, packets have two data chunks for S/PDIF on coaxial + * interface. + */ + pcm_chunks[0] += 2; + pcm_chunks[1] += 2; + + /* + * Fixed part consists of PCM chunks multiple of 4, with msg chunks. As + * a result, this part can includes empty data chunks. + */ + formats->fixed_part_pcm_chunks[0] = round_up(2 + pcm_chunks[0], 4) - 2; + formats->fixed_part_pcm_chunks[1] = round_up(2 + pcm_chunks[1], 4) - 2; + if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4) + formats->fixed_part_pcm_chunks[2] = + round_up(2 + pcm_chunks[2], 4) - 2; +} + +static void calculate_differed_part(struct snd_motu_packet_format *formats, + enum snd_motu_spec_flags flags, u32 data, + u32 a_enable_mask, u32 a_no_adat_mask, + u32 b_enable_mask, u32 b_no_adat_mask) +{ + unsigned char pcm_chunks[3] = {0, 0, 0}; + int i; + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) && (data & a_enable_mask)) { + if (data & a_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface A. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * A. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + if ((flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) && (data & b_enable_mask)) { + if (data & b_no_adat_mask) { + /* + * Additional two data chunks for S/PDIF on optical + * interface B. This includes empty data chunks. + */ + pcm_chunks[0] += 4; + pcm_chunks[1] += 4; + } else { + /* + * Additional data chunks for ADAT on optical interface + * B. + */ + pcm_chunks[0] += 8; + pcm_chunks[1] += 4; + } + } + + for (i = 0; i < 3; ++i) { + if (pcm_chunks[i] > 0) + pcm_chunks[i] = round_up(pcm_chunks[i], 4); + + formats->differed_part_pcm_chunks[i] = pcm_chunks[i]; + } +} + +static int v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM, + motu->spec->flags, motu->spec->analog_in_ports); + calculate_differed_part(&motu->tx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_IN_IFACE_A, V3_NO_ADAT_OPT_IN_IFACE_A, + V3_ENABLE_OPT_IN_IFACE_B, V3_NO_ADAT_OPT_IN_IFACE_B); + + calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM, + motu->spec->flags, motu->spec->analog_out_ports); + calculate_differed_part(&motu->rx_packet_formats, + motu->spec->flags, data, + V3_ENABLE_OPT_OUT_IFACE_A, V3_NO_ADAT_OPT_OUT_IFACE_A, + V3_ENABLE_OPT_OUT_IFACE_B, V3_NO_ADAT_OPT_OUT_IFACE_B); + + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + motu->tx_packet_formats.pcm_byte_offset = 10; + + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + motu->rx_packet_formats.pcm_byte_offset = 10; + + return 0; +} + +const struct snd_motu_protocol snd_motu_protocol_v3 = { + .get_clock_rate = v3_get_clock_rate, + .set_clock_rate = v3_set_clock_rate, + .get_clock_source = v3_get_clock_source, + .switch_fetching_mode = v3_switch_fetching_mode, + .cache_packet_formats = v3_cache_packet_formats, +}; diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 0acd134..bf779cf 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -203,6 +203,22 @@ static struct snd_motu_spec motu_828mk2 = { .analog_out_ports = 8, };
+static struct snd_motu_spec motu_828mk3 = { + .name = "828mk3", + .protocol = &snd_motu_protocol_v3, + .flags = SND_MOTU_SPEC_SUPPORT_CLOCK_X2 | + SND_MOTU_SPEC_SUPPORT_CLOCK_X4 | + SND_MOTU_SPEC_TX_MICINST_CHUNK | + SND_MOTU_SPEC_TX_RETURN_CHUNK | + SND_MOTU_SPEC_TX_REVERB_CHUNK | + SND_MOTU_SPEC_HAS_OPT_IFACE_A | + SND_MOTU_SPEC_HAS_OPT_IFACE_B | + SND_MOTU_SPEC_HAS_MIDI, + + .analog_in_ports = 8, + .analog_out_ports = 8, +}; + #define SND_MOTU_DEV_ENTRY(model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ @@ -216,6 +232,8 @@ static struct snd_motu_spec motu_828mk2 = {
static const struct ieee1394_device_id motu_id_table[] = { SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), + SND_MOTU_DEV_ENTRY(0x106800, &motu_828mk3), /* FireWire only. */ + SND_MOTU_DEV_ENTRY(0x100800, &motu_828mk3), /* Hybrid. */ { } }; MODULE_DEVICE_TABLE(ieee1394, motu_id_table); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 29f20a5..8d6a4a3 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -123,6 +123,7 @@ struct snd_motu_spec { };
extern const struct snd_motu_protocol snd_motu_protocol_v2; +extern const struct snd_motu_protocol snd_motu_protocol_v3;
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir,

On Wed, 22 Mar 2017 13:30:10 +0100, Takashi Sakamoto wrote:
Hi,
This patchset updates my previous RFCv2, and go for ALSA upstream. http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117211.htm...
This patchset newly adds a driver into ALSA firewire stack, to support for some models in MOTU FireWire series:
- 828mk2
- 828mk3 (FireWire only)
- 828mk3 (Hybrid)
The driver supports playbacking/capturing PCM frames and MIDI messages for any sampling transfer frequencies, with current ALSA IEC 61883-1/6 packet streaming engine.
Unfortunately, units on MOTU FireWire series have many quirks against IEC 61883-1/6. This brings much commits and descriptions on this patchset. Sorry for reviewers but I'm glad to get your assist for this patchset.
As I note in 8th commit, at sampling transfer frequency based on 44.1kHz, the driver handles the units with choppy noises every few seconds. This is an issue which is not resolved yet.
Changes from RFCv2:
- drop trial support for 828. If you have interests in development, please refer to one commit of my previous RFC. It describes v1 protocol.
- http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117226.htm...
- improve commit messages.
Takashi Sakamoto (18): ALSA: firewire-motu: add skeleton for Mark of the unicorn (MOTU) FireWire series ALSA: firewire-motu: postpone sound card registration ALSA: firewire-motu: add a structure for model-dependent parameters. ALSA: firewire-motu: add an abstraction layer for three types of protocols ALSA: firewire-lib: record cycle count for the first packet ALSA: firewire-lib: add support for source packet header field in CIP header ALSA: firewire-lib: enable CIP_DBC_IS_END_EVENT for both directions of stream ALSA: firewire-motu: add MOTU specific protocol layer ALSA: firewire-motu: handle transactions specific for MOTU FireWire models ALSA: firewire-motu: add stream management functionality ALSA: firewire-motu: add proc node to show current statuc of clock and packet formats ALSA: firewire-motu: add PCM functionality ALSA: firewire-motu: add MIDI functionality ALSA: firewire-motu: add hwdep interface ALSA: firewire-motu: enable to read transaction cache via hwdep interface ALSA: firewire-motu: add support for MOTU 828mk2 as a model with protocol version 2 ALSA: firewire-lib: add a quirk of packet without valid EOH in CIP format ALSA: firewire-motu: add support for MOTU 828mk3 (FireWire/Hybrid) as a model with protocol version 3
Applied all patches now to for-next branch.
thanks,
Takashi

Hi,
On Mar 28 2017 19:40, Takashi Iwai wrote:
On Wed, 22 Mar 2017 13:30:10 +0100, Takashi Sakamoto wrote:
Hi,
This patchset updates my previous RFCv2, and go for ALSA upstream. http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117211.htm...
This patchset newly adds a driver into ALSA firewire stack, to support for some models in MOTU FireWire series:
- 828mk2
- 828mk3 (FireWire only)
- 828mk3 (Hybrid)
The driver supports playbacking/capturing PCM frames and MIDI messages for any sampling transfer frequencies, with current ALSA IEC 61883-1/6 packet streaming engine.
Unfortunately, units on MOTU FireWire series have many quirks against IEC 61883-1/6. This brings much commits and descriptions on this patchset. Sorry for reviewers but I'm glad to get your assist for this patchset.
As I note in 8th commit, at sampling transfer frequency based on 44.1kHz, the driver handles the units with choppy noises every few seconds. This is an issue which is not resolved yet.
Changes from RFCv2:
- drop trial support for 828. If you have interests in development, please refer to one commit of my previous RFC. It describes v1 protocol.
- http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117226.htm...
- improve commit messages.
Takashi Sakamoto (18): ALSA: firewire-motu: add skeleton for Mark of the unicorn (MOTU) FireWire series ALSA: firewire-motu: postpone sound card registration ALSA: firewire-motu: add a structure for model-dependent parameters. ALSA: firewire-motu: add an abstraction layer for three types of protocols ALSA: firewire-lib: record cycle count for the first packet ALSA: firewire-lib: add support for source packet header field in CIP header ALSA: firewire-lib: enable CIP_DBC_IS_END_EVENT for both directions of stream ALSA: firewire-motu: add MOTU specific protocol layer ALSA: firewire-motu: handle transactions specific for MOTU FireWire models ALSA: firewire-motu: add stream management functionality ALSA: firewire-motu: add proc node to show current statuc of clock and packet formats ALSA: firewire-motu: add PCM functionality ALSA: firewire-motu: add MIDI functionality ALSA: firewire-motu: add hwdep interface ALSA: firewire-motu: enable to read transaction cache via hwdep interface ALSA: firewire-motu: add support for MOTU 828mk2 as a model with protocol version 2 ALSA: firewire-lib: add a quirk of packet without valid EOH in CIP format ALSA: firewire-motu: add support for MOTU 828mk3 (FireWire/Hybrid) as a model with protocol version 3
Applied all patches now to for-next branch.
Thanks for your reviewing and applying them. I have ease from the work which takes almost two years.
For users and testers, I push the commits into my remote repository to backport them to kernel since v4.4 till v4.11. Please refer to README in topmost of the file tree. * https://github.com/takaswie/snd-firewire-improve/
If using Ubuntu 16.04, deb package is now available in David Henningsson's PPA. I appreciate his continuous help for my work, thank you. * https://code.launchpad.net/~diwic/+archive/ubuntu/snd-firewire-improve
As I note, a commit for protocol v1 is omitted from this patchset, thus it's not supported yet. For anyone has interests in it, I push a remote branch with the commit. * https://github.com/takaswie/snd-firewire-improve/tree/topic/motu-v1
I wrote libhinawa, a user space library with gobject introspection, to communicate audio and music units on IEEE 1394 bus. Soon I'll release v0.8.0 with HinawaSndMotu gobject class, to receive asynchronous notifications from MOTU units. You can see its initial version in my remote repository. * https://github.com/takaswie/libhinawa/tree/topic/motu
libhinawa is already packaged for debian/ubuntu. In a few months, I'll work to update the package in each repositories with a contributor, Kentaro hayashi, who is a package maintainer in debian project. * https://tracker.debian.org/pkg/libhinawa * https://launchpad.net/ubuntu/+source/libhinawa
By the way, next, I'll post a patchset for snd-fireface, a driver for RME FireWire series.
Thanks
Takashi Sakamoto
participants (2)
-
Takashi Iwai
-
Takashi Sakamoto