diff mbox

[RFC,31/37] ALSA: firewire-motu: add MOTU specific protocol layer

Message ID 1436623968-10780-32-git-send-email-o-takashi@sakamocchi.jp (mailing list archive)
State New, archived
Headers show

Commit Message

Takashi Sakamoto July 11, 2015, 2:12 p.m. UTC
MOTU FireWire series uses blocking transmission for AMDTP packet
streaming, while the format of data blocks is unique.

The CIP headers includes 0x82 in FMT field and 0x22ffff in FDF field.

The first data channel in each data block is used for source packet
header, thus the lower 25 bits show timestamp.

The rest of data channels are used to transfer PCM samples, MIDI data
and status/control information. Each data is in 24 bits data chunk. This
is similar to '24-bit * 4 Audio Pack' in IEC 61883-6.

The first two data chunks have MIDI messages and status/control
information. The rest has PCM samples up to 24 bits.

Currently, this protocol layer causes noise like rectanglar wave, due to
timestampling perhaps. The theory to implement timestamping is:

 * 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 sence for blocking transmission because this mode
   transfers fixed number or none of events.

The reason is not clear.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/motu/Makefile     |   2 +-
 sound/firewire/motu/amdtp-motu.c | 442 +++++++++++++++++++++++++++++++++++++++
 sound/firewire/motu/motu.h       |  12 ++
 3 files changed, 455 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/motu/amdtp-motu.c

Comments

Jonathan Woithe July 12, 2015, 1:05 a.m. UTC | #1
Hi Takashi

Thanks for coding up a starting point of the MOTU firewire streaming
driver.  I have been seriously short of time this year and have not had a
chance to look at this yet.

On Sat, Jul 11, 2015 at 11:12:42PM +0900, Takashi Sakamoto wrote:
> Currently, this protocol layer causes noise like rectanglar wave, due to
> timestampling perhaps. The theory to implement timestamping is:
> 
>  * 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 sence for blocking transmission because this mode
>    transfers fixed number or none of events.
> 
> The reason is not clear.

The above approach will definitely not work and the resulting noise is not
unexpected.  The reason is that the MOTU audio clock is not locked to the
firewire bus clock in any way at all.  This means that you cannot base the
time stamps of the audio samples on the assumption that there is, for
example exactly 512 ticks per sample at 48 kHz.  Instead one must determine
the audio clock rate relative to the firewire cycle timer based on the
incoming audio packets from the device.  What this means is that the "ticks
per sample" is not a constant, is fractional (and possibly irrational), and
will vary from device to device (even on a single device depending on things
such as temperature of the internal timebase components).

In FFADO the generation of the audio clock used for timestamping tx packets
is done by locking an internal DLL to the incoming timestamps.  However, I
did it this way only because that was the way the rest of the FFADO
subsystem expected it to be done.  An alternative (which I had working very
early on with an out-of-tree proof of concept) is to base the advancing of
the tx timestamps on the differences seen in the rx timestamps.  Note that
there is an offset between the two due to pipeline delays.  I would have to
look up my early development notes on the MOTU driver to find out what that
was.

An alternative approach might be to simply use the arrival time of the
packet as a proxy for the internal timestamp.  However, since the timestamp
within the rx packet is likely to be more accurate for the purposes of
synchronising to the internal audio clock I would strongly suggest using
that.

The plan would therefore be as follows.  After starting streaming on the
device, wait for a few packets to arrive and store their timestamps.  During
this time it might be necessary to send "silence" packets - I would have to
check.  Then start transmission, with the tx timestamps being determined
from the timestamp queue and using a fixed offset as explained earlier.  I
would have to look up my old code to determine exactly how this worked in
practice, but it was stable and is a whole lot simpler than messing with
DLLs and other similar synchronisation mechanisms.

Another general comment about the MOTU streaming: when shutting streaming
down it is necessary to send a series of "silence" packets to the device
before disabling streaming.  If this is not done the device can end up in a
state where it outputs high amplitude oscillations (around 1-2 KHz) across
random channels.  I have comments within the FFADO driver source which refer
to this issue.

I don't have time to look through the rest of the patch right now but I will
try to do so in the next few days.

Regards
  jonathan
Takashi Sakamoto July 15, 2015, 4:10 p.m. UTC | #2
Jonathan,

On JUl 12 2015 10:05, Jonathan Woithe wrote:
> Thanks for coding up a starting point of the MOTU firewire streaming
> driver.  I have been seriously short of time this year and have not had a
> chance to look at this yet.

Your comments, itself, is helpful for developing of MOTU driver, while
in this developing cycle, I focus on the other things.

The main theme of this patchset is to expand current streaming engine to
non-AMDTP protocols, with enough compatibility and reasonable overhead
for in-tree drivers. My intention to add under-developing MOTU driver is
to assist this intention, not for developing MOTU driver itself.

Therefore, in this developing cycle, I have little responses to comments
which doesn't relate to the theme, sorry.


Thanks

Takashi Sakamoto
diff mbox

Patch

diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile
index f4cfd14..5c35d77 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 := amdtp-motu.o motu.o
 obj-m += 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..a10e57c
--- /dev/null
+++ b/sound/firewire/motu/amdtp-motu.c
@@ -0,0 +1,442 @@ 
+/*
+ * amdtp-motu.c - a part of driver for MOTU FireWire series
+ *
+ * Copyright (c) 2015 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"
+
+/*
+ * 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;
+	unsigned int remainder_ticks_per_event;
+	unsigned int next_offsets;
+	unsigned int next_accumulated;
+
+	unsigned int pcm_channels;
+
+	unsigned int midi_ports;
+	struct snd_rawmidi_substream *midi[8];
+	int midi_fifo_limit;
+	int midi_fifo_used[8];
+
+	void (*transfer_samples)(struct amdtp_stream *s,
+				 struct snd_pcm_runtime *runtime,
+				 __be32 *buffer, unsigned int data_blocks);
+};
+
+int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
+			      unsigned int pcm_channels,
+			      unsigned int midi_ports)
+{
+	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 data_block_quadlets;
+	int err;
+
+	if (amdtp_stream_running(s))
+		return -EBUSY;
+
+	/* Use 24-bit audio pack in IEC 61883-6, and SPH in IEC 61883-1. */
+	data_block_quadlets = DIV_ROUND_UP((pcm_channels + 2) * 3, 4) + 1;
+
+	err = amdtp_stream_set_parameters(s, rate, data_block_quadlets);
+	if (err < 0)
+		return err;
+
+	p->pcm_channels = pcm_channels;
+	p->midi_ports = midi_ports;
+
+	/*
+	 * We do not know the actual MIDI FIFO size of most devices.  Just
+	 * assume two bytes, i.e., one byte can be received over the bus while
+	 * the previous one is transmitted over MIDI.
+	 * (The value here is adjusted for midi_ratelimit_per_packet().)
+	 */
+	p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
+
+	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_offsets = 0;
+	p->next_accumulated = 0;
+
+	return 0;
+}
+
+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_channels;
+	src = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	/* Skip SPH. */
+	buffer++;
+	for (i = 0; i < data_blocks; ++i) {
+		byte = (u8 *)buffer;
+
+		/* Skip MIDI and control messages. */
+		byte += 6;
+
+		for (c = 0; c < channels; ++c) {
+			byte[0] = ((*src) >> 8) & 0xff;
+			byte[1] = ((*src) >> 16) & 0xff;
+			byte[2] = ((*src) >> 24) & 0xff;
+			byte += 3;
+			src++;
+		}
+
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			src = (void *)runtime->dma_area;
+	}
+}
+
+static void write_pcm_s16(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 u16 *src;
+
+	channels = p->pcm_channels;
+	src = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	/* Skip SPH. */
+	buffer++;
+	for (i = 0; i < data_blocks; ++i) {
+		byte = (u8 *)buffer;
+
+		/* Skip MIDI and control messages. */
+		byte += 6;
+
+		for (c = 0; c < channels; ++c) {
+			byte[0] = ((*src) >> 8) & 0xff;
+			byte[1] = (*src) & 0xff;
+			byte[2] = 0;
+			byte += 3;
+			src++;
+		}
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			src = (void *)runtime->dma_area;
+	}
+}
+
+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_channels;
+	dst  = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	/* Skip SPH. */
+	buffer++;
+	for (i = 0; i < data_blocks; ++i) {
+		byte = (u8 *)buffer;
+
+		/* Skip MIDI and control message. */
+		byte += 6;
+
+		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_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_channels;
+
+	/* Skip SPH. */
+	buffer++;
+	for (i = 0; i < data_blocks; ++i) {
+		byte = (u8 *)buffer;
+
+		/* Skip MIDI and control messages. */
+		byte += 6;
+
+		for (c = 0; c < channels; ++c) {
+			byte[0] = 0;
+			byte[1] = 0;
+			byte[2] = 0;
+			byte += 3;
+		}
+
+		buffer += s->data_block_quadlets;
+	}
+}
+
+void amdtp_motu_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format)
+{
+	struct amdtp_motu *p = s->protocol;
+
+	if (WARN_ON(amdtp_stream_pcm_running(s)))
+		return;
+
+	switch (format) {
+	default:
+		WARN_ON(1);
+		/* fall through */
+	case SNDRV_PCM_FORMAT_S16:
+		if (s->direction == AMDTP_OUT_STREAM) {
+			p->transfer_samples = write_pcm_s16;
+			break;
+		}
+		WARN_ON(1);
+		/* fall through */
+	case SNDRV_PCM_FORMAT_S32:
+		if (s->direction == AMDTP_OUT_STREAM)
+			p->transfer_samples = write_pcm_s32;
+		else
+			p->transfer_samples = read_pcm_s32;
+		break;
+	}
+}
+
+/*
+ * To avoid sending MIDI bytes at too high a rate, assume that the receiving
+ * device has a FIFO, and track how much it is filled.  This values increases
+ * by one whenever we send one byte in a packet, but the FIFO empties at
+ * a constant rate independent of our packet rate.  One packet has syt_interval
+ * samples, so the number of bytes that empty out of the FIFO, per packet(!),
+ * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate.  To avoid storing
+ * fractional values, the values in midi_fifo_used[] are measured in bytes
+ * multiplied by the sample rate.
+ */
+static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
+{
+	struct amdtp_motu *p = s->protocol;
+	int used;
+
+	used = p->midi_fifo_used[port];
+	if (used == 0) /* common shortcut */
+		return true;
+
+	used -= MIDI_BYTES_PER_SECOND * s->syt_interval;
+	used = max(used, 0);
+	p->midi_fifo_used[port] = used;
+
+	return used < p->midi_fifo_limit;
+}
+
+static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port)
+{
+	struct amdtp_motu *p = s->protocol;
+
+	p->midi_fifo_used[port] += amdtp_rate_table[s->sfc];
+}
+
+static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
+				unsigned int data_blocks)
+{
+	struct amdtp_motu *p = s->protocol;
+	unsigned int f;
+	u8 *b;
+
+	/* Skip SPH. */
+	buffer++;
+	for (f = 0; f < data_blocks; f++) {
+		b = (u8 *)buffer;
+
+		if (p->midi[0] &&
+		    midi_ratelimit_per_packet(s, 0) &&
+		    snd_rawmidi_transmit(p->midi[0], b + 2, 1) == 1) {
+			midi_rate_use_one_byte(s, 0);
+			b[0] |= 0x01;
+		} else {
+			b[2] = 0x00;
+		}
+
+		buffer += s->data_block_quadlets;
+	}
+}
+
+static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
+			       unsigned int data_blocks)
+{
+	struct amdtp_motu *p = s->protocol;
+	unsigned int f;
+	u8 *b;
+
+	/* Skip SPH. */
+	buffer++;
+	for (f = 0; f < data_blocks; f++) {
+		b = (u8 *)buffer;
+
+		if ((b[0] & 0x01) && p->midi[0])
+			snd_rawmidi_receive(p->midi[0], b + 2, 1);
+
+		buffer += s->data_block_quadlets;
+	}
+}
+
+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)
+		ACCESS_ONCE(p->midi[port]) = midi;
+}
+
+static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
+					   __be32 *buffer,
+					   unsigned int data_blocks,
+					   unsigned int cycle,
+					   unsigned int *syt)
+{
+	struct amdtp_motu *p = (struct amdtp_motu *)s->protocol;
+	struct snd_pcm_substream *pcm;
+
+	/*
+	 * NOTE: The gap of data in two successive SPH fields has jitters. For
+	 * example, at 44.1kHz:
+	 * 022d, 022f, 022d, 022d, 022d, 022e, 022c, 022d, 022d, 022e, 022d
+	 *
+	 * Therefore, it's better not to reuse it.,
+	 */
+
+	/* TODO: how to interact control messages between userspace? */
+
+	if (p->midi_ports)
+		read_midi_messages(s, buffer, data_blocks);
+
+	pcm = ACCESS_ONCE(s->pcm);
+	if (data_blocks > 0 && pcm)
+		p->transfer_samples(s, pcm->runtime, buffer, data_blocks);
+
+	return data_blocks;
+}
+
+static void write_sph(struct amdtp_motu *p, u32 *buffer, unsigned int cycle,
+		      unsigned int data_blocks)
+{
+	unsigned int seconds;
+	unsigned int counts;
+	__u32 sph;
+	unsigned int i;
+
+	seconds = cycle >> 13;
+	counts = cycle % 8000;
+
+	for (i = 0; i < data_blocks; i++) {
+		sph = (seconds << 25) | (counts << 12) | p->next_offsets;
+		buffer[2] = cpu_to_be32(sph);
+
+		p->next_accumulated += p->remainder_ticks_per_event;
+		if (p->next_accumulated >= 441) {
+			p->next_accumulated -= 441;
+			p->next_offsets++;
+		}
+
+		p->next_offsets += p->quotient_ticks_per_event;
+		if (p->next_offsets >= 3072) {
+			p->next_offsets -= 3072;
+			if (++counts >= 8000) {
+				counts -= 8000;
+				seconds = (seconds + 1) % 8;
+			}
+		}
+	}
+}
+
+static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
+					   __be32 *buffer,
+					   unsigned int data_blocks,
+					   unsigned int cycle,
+					   unsigned int *syt)
+{
+	struct amdtp_motu *p = (struct amdtp_motu *)s->protocol;
+	struct snd_pcm_substream *pcm;
+
+	/* Not used. */
+	*syt = 0xffff;
+
+	/* TODO: how to interact control messages between userspace? */
+
+	write_sph(p, buffer, cycle, data_blocks);
+
+	if (p->midi_ports)
+		write_midi_messages(s, buffer, data_blocks);
+
+	pcm = ACCESS_ONCE(s->pcm);
+	if (pcm)
+		p->transfer_samples(s, pcm->runtime, buffer, data_blocks);
+	else
+		write_pcm_silence(s, buffer, data_blocks);
+
+	return data_blocks;
+}
+
+int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
+		   enum amdtp_stream_direction dir)
+{
+	struct amdtp_motu *p;
+	int err;
+
+	err = amdtp_stream_init(s, unit, dir, CIP_BLOCKING,
+				sizeof(struct amdtp_motu));
+	if (err < 0)
+		return err;
+	p = s->protocol;
+
+	if (dir == AMDTP_IN_STREAM)
+		s->process_data_blocks = process_tx_data_blocks;
+	else
+		s->process_data_blocks = process_rx_data_blocks;
+
+	s->sph = 0x1;
+	s->fmt = 0x02;
+	s->fdf = 0x22;
+
+	return 0;
+}
diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h
index d5dddbd..690818f 100644
--- a/sound/firewire/motu/motu.h
+++ b/sound/firewire/motu/motu.h
@@ -19,6 +19,10 @@ 
 
 #include <sound/control.h>
 #include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/rawmidi.h>
 
 #include "../amdtp-stream.h"
 #include "../iso-resources.h"
@@ -47,4 +51,12 @@  struct snd_motu {
 	struct snd_motu_spec *spec;
 };
 
+int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
+		    enum amdtp_stream_direction dir);
+int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
+			      unsigned int pcm_channels,
+			      unsigned int midi_ports);
+void amdtp_motu_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format);
+void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port,
+			     struct snd_rawmidi_substream *midi);
 #endif