ALSA: firewire-motu: add MIDI functionality
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Wed, 22 Mar 2017 12:30:23 +0000 (21:30 +0900)
committerTakashi Iwai <tiwai@suse.de>
Tue, 28 Mar 2017 10:33:56 +0000 (12:33 +0200)
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>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/firewire/motu/Makefile
sound/firewire/motu/amdtp-motu.c
sound/firewire/motu/motu-midi.c [new file with mode: 0644]
sound/firewire/motu/motu-stream.c
sound/firewire/motu/motu.c
sound/firewire/motu/motu.h

index 508b6894826ad563e307fc0172f98ed9c6a457fe..a512c1e0f49cb09c3f218c34b386698d8f000b77 100644 (file)
@@ -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
index 11e44123ad653c6072efafa8cc08589ebd069920..0930cd8ca2cb52ab8f7ce76a342efd91c35bb779 100644 (file)
 #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 (file)
index 0000000..f232f29
--- /dev/null
@@ -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;
+}
index 9aa698fc8da2001042b912224ef1eb1615244205..911d3487f7756c9743628c9aee5a13cadd7f9d6c 100644 (file)
 
 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),
index 801d6a73b0f3a46f15a8660eb4a5c0baca58577b..d4da1377fa50e6e28d462b42d61ee72f1064cfab 100644 (file)
@@ -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);
index afc6de654daa6e21aadf2c93e09e130387c3fcdb..338b35193001f1f87341cf108dd472aaf1e65056 100644 (file)
 #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