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

index d7819d57eadf500dfa948fd8dbfe7d63dc8887ef..37391f5c623da344eb47ba30b221ec2e0864a401 100644 (file)
@@ -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 (file)
index 0000000..11e4412
--- /dev/null
@@ -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;
+}
index cb6b57353cc160c1e6824fc3792aec234c2466e9..cd1b3dd3e3712d6b2a0d4e22cd6ab93785015b8c 100644 (file)
 
 #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