[21/39] fireworks: Add connection and stream management
diff mbox

Message ID 1394016507-15761-22-git-send-email-o-takashi@sakamocchi.jp
State Superseded
Delegated to: Takashi Iwai
Headers show

Commit Message

Takashi Sakamoto March 5, 2014, 10:48 a.m. UTC
Fireworks manages connections by CMP and can transmit/receive AMDTP streams
with a few quirks. This commit adds functionality to start/stop the streams.

Major Fireworks products don't support 'SYT-Match' modes, except for
AudioFire12/8(till 2009 July) with firmware version 1.0. Already in previous
commit, this driver don't support such old firmwares. So this commit adds
support for non 'SYT-Match' modes.

I note that this driver has a short gap for MIDI streams when starting PCM
stream. When AMDTP streams are running only for MIDI data and PCM data is
going to be joined at different sampling rate, then AMDTP streams are
stopped once and started again after changing sampling rate.

Fireworks quirks:

1.Fireworks transmits packets with both TAG0 and TAG1
In IEC 61883-1, each common isochronous packet (CIP) has 'tag' field in its
packet header. The value is generally 0x01 ('CIP header is included' = TAG1).
But Fireworks in 'IEC 61883 compliant mode' transmits 'no data' packet with 0x00
('No CIP header is included' = TAG0) but actually the packet includes CIP
structure. To handle 'presentation timestamp' for duplex streams
synchronization, this packets must be handled.

2.Fireworks transmits 'no data' packet out of specification
Fireworks transmits 'no data' packet with 'NO-DATA' FDF and no dummy data. It
includes CIP headers only. In IEC 61883-6, 'no data' packet is generated by two
ways. One is empty CIP packet defined in IEC 61883-1. Another is
'special non-empty packet' defined in IEC 61883-6. Fireworks uses strange
combination of them.

3.Fireworks transmits packets with a bit disorder
A sequence of DBC field in packets transmitted by Fireworks includes a bit
disorder. See logs in the end of this commit.

4.Fireworks can handle MIDI messages in the first 8 data blocks of out-packet
Fireworks ignores MIDI messages in other data blocks.

5.AudioFirePre8 transmits packets with wrong data block size
One of Fireworks device, AudioFirePre8, always reports 8 data block size in
transmitted packets and increments the data block counter by 8. But this is not
actual value.

I confirm them with firmware version 4.8 or later with Windows. According to
a contact persion in Echo Audio, there is no difference between firmwares
installed by Windows/OS X.

Here, an example of a sequence of packets transmitted by Fireworks:

Index  Payload  CIP header 1  CIP header 2
023    210      3F1100F8      900478E9
024    002      3F1100F8      90FFFFFF
025    210      3F110000      900490E9
026    210      3F110008      9004A4E9
027    210      3F110010      9004B8E9
028    002      3F110010      90FFFFFF
029    210      3F110020      9004E4E8 <-
030    210      3F110018      9004D0E8 <-
031    210      3F110028      9004F8E8
032    002      3F110028      90FFFFFF
033    210      3F110030      900410E8
034    210      3F110038      900424E8
035    210      3F110040      900438E8
036    002      3F110040      90FFFFFF
037    210      3F110050      900464E8 <-
038    210      3F110048      900450E8 <-
039    210      3F110058      900478E8
040    002      3F110058      90FFFFFF
041    210      3F110068      9004A4E8 <-
042    210      3F110060      900490E8 <-
043    210      3F110070      9004B8E8
044    002      3F110070      90FFFFFF
045    210      3F110080      9004E4E7 <-
046    210      3F110078      9004D0E8 <-
---
 sound/firewire/fireworks/Makefile           |   3 +-
 sound/firewire/fireworks/fireworks.c        |  46 ++++
 sound/firewire/fireworks/fireworks.h        |  28 +++
 sound/firewire/fireworks/fireworks_stream.c | 331 ++++++++++++++++++++++++++++
 4 files changed, 407 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireworks/fireworks_stream.c

Patch
diff mbox

diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index a6ce214..1bccb65 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,2 +1,3 @@ 
-snd-fireworks-objs := fireworks_transaction.o fireworks_command.o fireworks.o
+snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
+		      fireworks_stream.o fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index 1f8c7e5..e483b55 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -94,6 +94,42 @@  get_hardware_info(struct snd_efw *efw)
 
 	if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE))
 		efw->resp_addr_changable = true;
+
+	efw->supported_sampling_rate = 0;
+	if ((hwinfo->min_sample_rate <= 22050)
+	 && (22050 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050;
+	if ((hwinfo->min_sample_rate <= 32000)
+	 && (32000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000;
+	if ((hwinfo->min_sample_rate <= 44100)
+	 && (44100 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100;
+	if ((hwinfo->min_sample_rate <= 48000)
+	 && (48000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000;
+	if ((hwinfo->min_sample_rate <= 88200)
+	 && (88200 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200;
+	if ((hwinfo->min_sample_rate <= 96000)
+	 && (96000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000;
+	if ((hwinfo->min_sample_rate <= 176400)
+	 && (176400 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400;
+	if ((hwinfo->min_sample_rate <= 192000)
+	 && (192000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000;
+
+	efw->midi_out_ports = hwinfo->midi_out_ports;
+	efw->midi_in_ports = hwinfo->midi_in_ports;
+
+	efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels;
+	efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x;
+	efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x;
+	efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels;
+	efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x;
+	efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x;
 end:
 	kfree(hwinfo);
 	return err;
@@ -149,6 +185,10 @@  efw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_efw_stream_init_duplex(efw);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
@@ -168,13 +208,19 @@  error:
 static void efw_update(struct fw_unit *unit)
 {
 	struct snd_efw *efw = dev_get_drvdata(&unit->device);
+
 	snd_efw_transaction_bus_reset(efw->unit);
+	snd_efw_stream_update_duplex(efw);
+
 	return;
 }
 
 static void efw_remove(struct fw_unit *unit)
 {
 	struct snd_efw *efw = dev_get_drvdata(&unit->device);
+
+	snd_efw_stream_destroy_duplex(efw);
+
 	snd_card_disconnect(efw->card);
 	snd_card_free_when_closed(efw->card);
 }
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 751302d..c022c71 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -22,9 +22,15 @@ 
 #include <sound/initval.h>
 #include <sound/pcm.h>
 
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp.h"
 #include "../cmp.h"
 #include "../lib.h"
 
+#define SND_EFW_MAX_MIDI_OUT_PORTS	2
+#define SND_EFW_MAX_MIDI_IN_PORTS	2
+
 #define SND_EFW_MUITIPLIER_MODES	3
 #define HWINFO_NAME_SIZE_BYTES		32
 #define HWINFO_MAX_CAPS_GROUPS		8
@@ -45,6 +51,20 @@  struct snd_efw {
 	/* for transaction */
 	u32 seqnum;
 	bool resp_addr_changable;
+
+	unsigned int midi_in_ports;
+	unsigned int midi_out_ports;
+
+	unsigned int supported_sampling_rate;
+	unsigned int pcm_capture_channels[SND_EFW_MUITIPLIER_MODES];
+	unsigned int pcm_playback_channels[SND_EFW_MUITIPLIER_MODES];
+
+	struct amdtp_stream tx_stream;
+	struct amdtp_stream rx_stream;
+	struct cmp_connection out_conn;
+	struct cmp_connection in_conn;
+	unsigned int capture_substreams;
+	unsigned int playback_substreams;
 };
 
 struct snd_efw_transaction {
@@ -150,6 +170,14 @@  int snd_efw_command_set_clock_source(struct snd_efw *efw,
 int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate);
 int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
 
+int snd_efw_stream_init_duplex(struct snd_efw *efw);
+int snd_efw_stream_start_duplex(struct snd_efw *efw,
+				struct amdtp_stream *request,
+				int sampling_rate);
+int snd_efw_stream_stop_duplex(struct snd_efw *efw);
+void snd_efw_stream_update_duplex(struct snd_efw *efw);
+void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
new file mode 100644
index 0000000..f41aee4
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -0,0 +1,331 @@ 
+/*
+ * fireworks_stream.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+#include "./fireworks.h"
+
+#define CALLBACK_TIMEOUT	100
+
+static unsigned int freq_table[] = {
+	/* multiplier mode 0 */
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	/* multiplier mode 1 */
+	[3] = 88200,
+	[4] = 96000,
+	/* multiplier mode 2 */
+	[5] = 176400,
+	[6] = 192000,
+};
+
+static inline unsigned int
+get_multiplier_mode_with_index(unsigned int index)
+{
+	return ((int)index - 1) / 2;
+}
+
+int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode)
+{
+	unsigned int i;
+
+	for (i = 0; i < sizeof(freq_table); i++) {
+		if (freq_table[i] == sampling_rate) {
+			*mode = get_multiplier_mode_with_index(i);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int
+init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+	struct cmp_connection *conn;
+	enum cmp_direction c_dir;
+	enum amdtp_stream_direction s_dir;
+	int err;
+
+	if (stream == &efw->tx_stream) {
+		conn = &efw->out_conn;
+		c_dir = CMP_OUTPUT;
+		s_dir = AMDTP_IN_STREAM;
+	} else {
+		conn = &efw->in_conn;
+		c_dir = CMP_INPUT;
+		s_dir = AMDTP_OUT_STREAM;
+	}
+
+	err = cmp_connection_init(conn, efw->unit, c_dir, 0);
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
+	if (err < 0)
+		cmp_connection_destroy(conn);
+end:
+	return err;
+}
+
+static void
+stop_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+	amdtp_stream_stop(stream);
+
+	if (stream == &efw->tx_stream)
+		cmp_connection_break(&efw->out_conn);
+	else
+		cmp_connection_break(&efw->in_conn);
+}
+
+static int
+start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
+	     unsigned int sampling_rate)
+{
+	struct cmp_connection *conn;
+	unsigned int mode, pcm_channels, midi_ports;
+	int err;
+
+	err = snd_efw_get_multiplier_mode(sampling_rate, &mode);
+	if (err < 0)
+		goto end;
+	if (stream == &efw->tx_stream) {
+		conn = &efw->out_conn;
+		pcm_channels = efw->pcm_capture_channels[mode];
+		midi_ports = efw->midi_out_ports;
+	} else {
+		conn = &efw->in_conn;
+		pcm_channels = efw->pcm_playback_channels[mode];
+		midi_ports = efw->midi_in_ports;
+	}
+
+	amdtp_stream_set_parameters(stream, sampling_rate,
+				    pcm_channels, midi_ports);
+
+	/*  establish connection via CMP */
+	err = cmp_connection_establish(conn,
+				amdtp_stream_get_max_payload(stream));
+	if (err < 0)
+		goto end;
+
+	/* start amdtp stream */
+	err = amdtp_stream_start(stream,
+				 conn->resources.channel,
+				 conn->speed);
+	if (err < 0)
+		stop_stream(efw, stream);
+
+	/* wait first callback */
+	if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
+		stop_stream(efw, stream);
+		err = -ETIMEDOUT;
+	}
+end:
+	return err;
+}
+
+static int
+get_roles(struct snd_efw *efw, enum cip_flags *sync_mode,
+	  struct amdtp_stream **master, struct amdtp_stream **slave)
+{
+	enum snd_efw_clock_source clock_source;
+	int err;
+
+	err = snd_efw_command_get_clock_source(efw, &clock_source);
+	if (err < 0)
+		goto end;
+
+	if (clock_source != SND_EFW_CLOCK_SOURCE_SYTMATCH) {
+		*master = &efw->tx_stream;
+		*slave = &efw->rx_stream;
+		*sync_mode = CIP_SYNC_TO_DEVICE;
+	} else {
+		err = -ENOSYS;
+	}
+end:
+	return err;
+}
+
+static int
+check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
+{
+	struct cmp_connection *conn;
+	bool used;
+	int err;
+
+	if (s == &efw->tx_stream)
+		conn = &efw->out_conn;
+	else
+		conn = &efw->in_conn;
+
+	err = cmp_connection_check_used(conn, &used);
+	if ((err >= 0) && used && !amdtp_stream_running(s)) {
+		dev_err(&efw->unit->device,
+			"Connection established by others: %cPCR[%d]\n",
+			(conn->direction == CMP_OUTPUT) ? 'o' : 'i',
+			conn->pcr_index);
+		err = -EBUSY;
+	}
+
+	return err;
+}
+
+int snd_efw_stream_init_duplex(struct snd_efw *efw)
+{
+	int err;
+
+	err = init_stream(efw, &efw->tx_stream);
+	if (err < 0)
+		goto end;
+
+	err = init_stream(efw, &efw->rx_stream);
+	if (err < 0)
+		goto end;
+
+	/* set IEC61883 compliant mode */
+	err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
+end:
+	return err;
+}
+
+int snd_efw_stream_start_duplex(struct snd_efw *efw,
+				struct amdtp_stream *request,
+				int rate)
+{
+	struct amdtp_stream *master, *slave;
+	enum cip_flags sync_mode;
+	unsigned int curr_rate;
+	bool slave_flag;
+	int err;
+
+	mutex_lock(&efw->mutex);
+
+	err = get_roles(efw, &sync_mode, &master, &slave);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * Considering JACK/FFADO streaming:
+	 * TODO: This can be removed hwdep functionality becomes popular.
+	 */
+	err = check_connection_used_by_others(efw, master);
+	if (err < 0)
+		goto end;
+
+	/* need to touch slave stream */
+	slave_flag = (request == slave) || amdtp_stream_running(slave);
+
+	/* packet queueing error */
+	if (amdtp_streaming_error(slave))
+		stop_stream(efw, slave);
+	if (amdtp_streaming_error(master))
+		stop_stream(efw, master);
+
+	/* stop streams if rate is different */
+	err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
+	if (err < 0)
+		goto end;
+	if (rate == 0)
+		rate = curr_rate;
+	if (rate != curr_rate) {
+		stop_stream(efw, slave);
+		stop_stream(efw, master);
+	}
+
+	/* master should be always running */
+	if (!amdtp_stream_running(master)) {
+		amdtp_stream_set_sync(sync_mode, master, slave);
+
+		err = snd_efw_command_set_sampling_rate(efw, rate);
+		if (err < 0)
+			goto end;
+
+		err = start_stream(efw, master, rate);
+		if (err < 0) {
+			dev_err(&efw->unit->device,
+				"fail to start AMDTP master stream:%d\n", err);
+			goto end;
+		}
+	}
+
+	/* start slave if needed */
+	if (slave_flag && !amdtp_stream_running(slave)) {
+		err = start_stream(efw, slave, rate);
+		if (err < 0) {
+			dev_err(&efw->unit->device,
+				"fail to start AMDTP slave stream:%d\n", err);
+			stop_stream(efw, master);
+		}
+	}
+end:
+	mutex_unlock(&efw->mutex);
+	return err;
+}
+
+int snd_efw_stream_stop_duplex(struct snd_efw *efw)
+{
+	struct amdtp_stream *master, *slave;
+	enum cip_flags sync_mode;
+	unsigned int slave_substreams;
+	int err;
+
+	mutex_lock(&efw->mutex);
+
+	err = get_roles(efw, &sync_mode, &master, &slave);
+	if (err < 0)
+		goto end;
+
+	if (slave == &efw->tx_stream)
+		slave_substreams = efw->capture_substreams;
+	else
+		slave_substreams = efw->playback_substreams;
+
+	if (slave_substreams > 0)
+		goto end;
+
+	stop_stream(efw, slave);
+
+	if ((efw->capture_substreams > 0) || (efw->playback_substreams > 0))
+		goto end;
+
+	stop_stream(efw, master);
+end:
+	mutex_unlock(&efw->mutex);
+	return err;
+}
+
+void snd_efw_stream_update_duplex(struct snd_efw *efw)
+{
+	if ((cmp_connection_update(&efw->out_conn) < 0) ||
+	    (cmp_connection_update(&efw->in_conn) < 0)) {
+		amdtp_stream_pcm_abort(&efw->rx_stream);
+		amdtp_stream_pcm_abort(&efw->tx_stream);
+		mutex_lock(&efw->mutex);
+		stop_stream(efw, &efw->rx_stream);
+		stop_stream(efw, &efw->tx_stream);
+		mutex_unlock(&efw->mutex);
+	} else {
+		amdtp_stream_update(&efw->rx_stream);
+		amdtp_stream_update(&efw->tx_stream);
+	}
+}
+
+void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
+{
+	mutex_lock(&efw->mutex);
+
+	amdtp_stream_pcm_abort(&efw->rx_stream);
+	amdtp_stream_pcm_abort(&efw->tx_stream);
+
+	stop_stream(efw, &efw->tx_stream);
+	cmp_connection_destroy(&efw->out_conn);
+
+	stop_stream(efw, &efw->rx_stream);
+	cmp_connection_destroy(&efw->in_conn);
+
+	mutex_unlock(&efw->mutex);
+}