ALSA: fireface: support rx MIDI functionality for Fireface UCX
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Tue, 22 Jan 2019 13:17:05 +0000 (22:17 +0900)
committerTakashi Iwai <tiwai@suse.de>
Tue, 22 Jan 2019 16:20:56 +0000 (17:20 +0100)
In latter model of Fireface series, asynchronous transaction includes
a prefix byte to indicate the way to decode included MIDI bytes.

Upper 4 bits of the prefix byte indicates port number, and the rest 4
bits indicate the way to decode rest of bytes for MIDI messages.

Basically the rest bits indicates the number of bytes for MIDI message.
However, if the last byte of each MIDi message is included, the rest
bits are 0xf. For example:

message: f0 00 00 66 14 20 00 00 f7
offset: content (big endian, port 0)
 '0030: 0x02f00000
 '0030: 0x03006614
 '0030: 0x03200000
 '0030: 0x0ff70000

This commit supports encoding scheme for the above and allows
applications to transfer MIDI messages via ALSA rawmidi interface.
An unused member (running_status) is reused to keep state of
transmission of system exclusive messages.

For your information, this is a dump of config rom.

$ sudo ./hinawa-config-rom-printer /dev/fw1
{ 'bus-info': { 'bmc': False,
                'chip_ID': 13225063715,
                'cmc': False,
                'cyc_clk_acc': 0,
                'imc': False,
                'isc': True,
                'max_rec': 512,
                'name': '1394',
                'node_vendor_ID': 2613},
  'root-directory': [ [ 'NODE_CAPABILITIES',
                        { 'addressing': {'64': True, 'fix': True, 'prv': False},
                          'misc': {'int': False, 'ms': False, 'spt': True},
                          'state': { 'atn': False,
                                     'ded': False,
                                     'drq': True,
                                     'elo': False,
                                     'init': False,
                                     'lst': True,
                                     'off': False},
                          'testing': {'bas': False, 'ext': False}}],
                      ['VENDOR', 2613],
                      ['DESCRIPTOR', 'RME!'],
                      ['EUI_64', 2873037108442403],
                      [ 'UNIT',
                        [ ['SPECIFIER_ID', 2613],
                          ['VERSION', 4],
                          ['MODEL', 1054720],
                          ['DESCRIPTOR', 'Fireface UCX']]]]}

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/firewire/fireface/ff-midi.c
sound/firewire/fireface/ff-protocol-latter.c
sound/firewire/fireface/ff.c
sound/firewire/fireface/ff.h

index 6a49611..5b44e1c 100644 (file)
@@ -19,7 +19,7 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
        struct snd_ff *ff = substream->rmidi->private_data;
 
        /* Initialize internal status. */
-       ff->running_status[substream->number] = 0;
+       ff->on_sysex[substream->number] = 0;
        ff->rx_midi_error[substream->number] = false;
 
        WRITE_ONCE(ff->rx_midi_substreams[substream->number], substream);
index a812ab6..817af44 100644 (file)
@@ -306,8 +306,104 @@ static void latter_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
                snd_rawmidi_receive(substream, byte, len);
 }
 
+/*
+ * When return minus value, given argument is not MIDI status.
+ * When return 0, given argument is a beginning of system exclusive.
+ * When return the others, given argument is MIDI data.
+ */
+static inline int calculate_message_bytes(u8 status)
+{
+       switch (status) {
+       case 0xf6:      /* Tune request. */
+       case 0xf8:      /* Timing clock. */
+       case 0xfa:      /* Start. */
+       case 0xfb:      /* Continue. */
+       case 0xfc:      /* Stop. */
+       case 0xfe:      /* Active sensing. */
+       case 0xff:      /* System reset. */
+               return 1;
+       case 0xf1:      /* MIDI time code quarter frame. */
+       case 0xf3:      /* Song select. */
+               return 2;
+       case 0xf2:      /* Song position pointer. */
+               return 3;
+       case 0xf0:      /* Exclusive. */
+               return 0;
+       case 0xf7:      /* End of exclusive. */
+               break;
+       case 0xf4:      /* Undefined. */
+       case 0xf5:      /* Undefined. */
+       case 0xf9:      /* Undefined. */
+       case 0xfd:      /* Undefined. */
+               break;
+       default:
+               switch (status & 0xf0) {
+               case 0x80:      /* Note on. */
+               case 0x90:      /* Note off. */
+               case 0xa0:      /* Polyphonic key pressure. */
+               case 0xb0:      /* Control change and Mode change. */
+               case 0xe0:      /* Pitch bend change. */
+                       return 3;
+               case 0xc0:      /* Program change. */
+               case 0xd0:      /* Channel pressure. */
+                       return 2;
+               default:
+               break;
+               }
+       break;
+       }
+
+       return -EINVAL;
+}
+
+static int latter_fill_midi_msg(struct snd_ff *ff,
+                               struct snd_rawmidi_substream *substream,
+                               unsigned int port)
+{
+       u32 data = {0};
+       u8 *buf = (u8 *)&data;
+       int consumed;
+
+       buf[0] = port << 4;
+       consumed = snd_rawmidi_transmit_peek(substream, buf + 1, 3);
+       if (consumed <= 0)
+               return consumed;
+
+       if (!ff->on_sysex[port]) {
+               if (buf[1] != 0xf0) {
+                       if (consumed < calculate_message_bytes(buf[1]))
+                               return 0;
+               } else {
+                       // The beginning of exclusives.
+                       ff->on_sysex[port] = true;
+               }
+
+               buf[0] |= consumed;
+       } else {
+               if (buf[1] != 0xf7) {
+                       if (buf[2] == 0xf7 || buf[3] == 0xf7) {
+                               // Transfer end code at next time.
+                               consumed -= 1;
+                       }
+
+                       buf[0] |= consumed;
+               } else {
+                       // The end of exclusives.
+                       ff->on_sysex[port] = false;
+                       consumed = 1;
+                       buf[0] |= 0x0f;
+               }
+       }
+
+       ff->msg_buf[port][0] = cpu_to_le32(data);
+       ff->rx_bytes[port] = consumed;
+
+       return 1;
+}
+
 const struct snd_ff_protocol snd_ff_protocol_latter = {
        .handle_midi_msg        = latter_handle_midi_msg,
+       .fill_midi_msg          = latter_fill_midi_msg,
        .get_clock              = latter_get_clock,
        .switch_fetching_mode   = latter_switch_fetching_mode,
        .begin_session          = latter_begin_session,
index 675c6ab..a961115 100644 (file)
@@ -174,6 +174,7 @@ static const struct snd_ff_spec spec_ucx = {
        .pcm_capture_channels = {18, 14, 12},
        .pcm_playback_channels = {18, 14, 12},
        .midi_in_ports = 2,
+       .midi_out_ports = 2,
        .protocol = &snd_ff_protocol_latter,
        .midi_high_addr = 0xffff00000034ull,
        .midi_addr_range = 0x80,
index e52ad11..ed8fea0 100644 (file)
@@ -75,7 +75,7 @@ struct snd_ff {
 
        /* TO handle MIDI rx. */
        struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
-       u8 running_status[SND_FF_OUT_MIDI_PORTS];
+       bool on_sysex[SND_FF_OUT_MIDI_PORTS];
        __le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
        struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
        struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];