Fireworks manages connections by CMP and can transmit/receive AMDTP stream with a few quirks. This commit adds functionality to start/stop streams.
As long as I know, major Fireworks products don't support 'SYT-Match' mode, except for AudioFire12/8(till 2009 July) with firmware version 1.0. So this commit add support for no 'SYT-Match' mode.
I note that there is a short gap for MIDI streams when starting PCM data. When AMDTP streams are running only for MIDI data and PCM data is going to be multiplexed at different sampling rate, then AMDTP streams are stopped once and started again after changing sampling rate.
Fireworks quirks I confirm with firmware version 4.8 or later with Windows:
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 transmits 'no data' packet with 0x00 ('No CIP header is included' = TAG0) but 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 can handle MIDI messages in the first 8 data blocks of out-packet Fireworks ignores MIDI messages in other data blocks.
4.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.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/Makefile | 3 +- sound/firewire/fireworks/fireworks.c | 49 ++++ sound/firewire/fireworks/fireworks.h | 27 +++ sound/firewire/fireworks/fireworks_stream.c | 348 ++++++++++++++++++++++++++++ 4 files changed, 426 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 ab60b60..0c3fb34 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -104,6 +104,45 @@ get_hardware_info(struct snd_efw *efw)
if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE)) efw->resp_addr_changable = true; + + /* set flag for supported sampling rate */ + 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; + + /* MIDI inputs and outputs */ + efw->midi_out_ports = hwinfo->midi_out_ports; + efw->midi_in_ports = hwinfo->midi_in_ports; + + /* PCM capture and playback channels */ + 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; @@ -162,6 +201,10 @@ efw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_efw_stream_init_duplex(efw); + if (err < 0) + goto error; + snd_card_set_dev(card, &unit->device); err = snd_card_register(card); if (err < 0) @@ -182,13 +225,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); return; diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index f07b57a..4867264 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -32,9 +32,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 @@ -56,6 +62,18 @@ 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; };
/* for transaction */ @@ -168,6 +186,15 @@ 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);
+/* for AMDTP stream and CMP */ +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..f0764538 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -0,0 +1,348 @@ +/* + * fireworks_stream.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ +#include "./fireworks.h" + +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, +}; + +int snd_efw_get_multiplier_mode(int sampling_rate) +{ + unsigned int i; + for (i = 0; i < sizeof(freq_table); i++) + if (freq_table[i] == sampling_rate) + return (i - 1) / 2; + + return -1; +} + +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); + goto end; + } + +end: + return err; +} + +static void +stop_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + if (amdtp_stream_running(stream)) + amdtp_stream_stop(stream); + + if (stream == &efw->tx_stream) + cmp_connection_break(&efw->out_conn); + else + cmp_connection_break(&efw->in_conn); + + return; +} + +static int +start_stream(struct snd_efw *efw, struct amdtp_stream *stream, + unsigned int sampling_rate) +{ + struct cmp_connection *conn; + unsigned int pcm_channels, midi_ports; + int mode, err; + + /* already running */ + if (amdtp_stream_running(stream)) { + err = 0; + goto end; + } + + mode = snd_efw_get_multiplier_mode(sampling_rate); + 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)) { + stop_stream(efw, stream); + err = -ETIMEDOUT; + goto end; + } +end: + return err; +} + +static void +update_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (&efw->tx_stream == stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + if (cmp_connection_update(conn) < 0) { + amdtp_stream_pcm_abort(stream); + mutex_lock(&efw->mutex); + stop_stream(efw, stream); + mutex_unlock(&efw->mutex); + return; + } + amdtp_stream_update(stream); +} + +static void +destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + stop_stream(efw, stream); + + if (stream == &efw->tx_stream) + cmp_connection_destroy(&efw->out_conn); + else + cmp_connection_destroy(&efw->in_conn); +} + +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, bool *used) +{ + struct cmp_connection *conn; + 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 = (*used && !amdtp_stream_running(s)); + + 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 sampling_rate) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + int err, curr_rate; + bool slave_flag, used; + + err = get_roles(efw, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if ((request == slave) || amdtp_stream_running(slave)) + slave_flag = true; + else + slave_flag = false; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(efw, master, &used); + if (err < 0) + goto end; + if (used) { + dev_err(&efw->unit->device, + "connections established by others: %d\n", + used); + err = -EBUSY; + goto end; + } + + /* change sampling rate if possible */ + err = snd_efw_command_get_sampling_rate(efw, &curr_rate); + if (err < 0) + goto end; + if (sampling_rate == 0) + sampling_rate = curr_rate; + if (sampling_rate != curr_rate) { + /* master is just for MIDI stream */ + if (amdtp_stream_running(master) && + !amdtp_stream_pcm_running(master)) + stop_stream(efw, master); + + /* slave is just for MIDI stream */ + if (amdtp_stream_running(slave) && + !amdtp_stream_pcm_running(slave)) + stop_stream(efw, slave); + + err = snd_efw_command_set_sampling_rate(efw, sampling_rate); + if (err < 0) + goto end; + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + err = start_stream(efw, master, sampling_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, sampling_rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP slave stream:%d\n", err); + goto end; + } + } +end: + return err; +} + +int snd_efw_stream_stop_duplex(struct snd_efw *efw) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + int err; + + err = get_roles(efw, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if (amdtp_stream_pcm_running(slave) || + amdtp_stream_midi_running(slave)) + goto end; + + stop_stream(efw, slave); + + if (!amdtp_stream_pcm_running(master) && + !amdtp_stream_midi_running(master)) + stop_stream(efw, master); + +end: + return err; +} + +void snd_efw_stream_update_duplex(struct snd_efw *efw) +{ + update_stream(efw, &efw->rx_stream); + update_stream(efw, &efw->tx_stream); +} + +void snd_efw_stream_destroy_duplex(struct snd_efw *efw) +{ + if (amdtp_stream_pcm_running(&efw->rx_stream)) + amdtp_stream_pcm_abort(&efw->rx_stream); + if (amdtp_stream_pcm_running(&efw->tx_stream)) + amdtp_stream_pcm_abort(&efw->tx_stream); + + destroy_stream(efw, &efw->rx_stream); + destroy_stream(efw, &efw->tx_stream); +}