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
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); +}