diff mbox

[18/44] fireworks: Add connection and stream management

Message ID 1395400229-22957-19-git-send-email-o-takashi@sakamocchi.jp (mailing list archive)
State Superseded
Headers show

Commit Message

Takashi Sakamoto March 21, 2014, 11:10 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.

Unfortunately, Fireworks is not fully compliant to IEC 61883-1/6. Some commits
following to this commit add these quirks.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireworks/Makefile           |   3 +-
 sound/firewire/fireworks/fireworks.c        |  45 ++++
 sound/firewire/fireworks/fireworks.h        |  28 +++
 sound/firewire/fireworks/fireworks_stream.c | 331 ++++++++++++++++++++++++++++
 4 files changed, 406 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireworks/fireworks_stream.c

Comments

Clemens Ladisch April 3, 2014, 8:56 p.m. UTC | #1
Takashi Sakamoto wrote:
> Fireworks manages connections by CMP and can transmit/receive AMDTP streams
> with a few quirks. This commit adds functionality to start/stop the streams.
>
> +++ b/sound/firewire/fireworks/fireworks.c
> +static int
> +init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
> +{
> +	...
> +	err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);

amdtp_stream_destroy() must be called.

> +static int
> +start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
> +	     unsigned int sampling_rate)
> +{
> +	...
> +	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;
> +}

If amdtp_stream_start() fails, this function will try to wait for
the stream anyway.

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

If the second init_stream() fails, this function will return with only one
of the streams initialized.

> +
> +	/* set IEC61883 compliant mode */
> +	err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
> +end:
> +	return err;

And if this fails, this function will return an error, but the two streams
will still be initialized.

In the error cases, any so-far initialized stream must be destroyed.

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

snd_efw_stream_stop_duplex() must always succeed so that the
resources can be freed properly.  Therefore, it should not
try to ask the device for anything (the device might be
resetting or be unplugged).

Either cache the master/slave pointers when starting the stream,
or rewrite get_roles() so that it does not access the device.


Regards,
Clemens
Takashi Sakamoto April 4, 2014, 2:22 p.m. UTC | #2
Clemens,

(Apr 04 2014 05:56), Clemens Ladisch wrote:
> Takashi Sakamoto wrote:
>> Fireworks manages connections by CMP and can transmit/receive AMDTP streams
>> with a few quirks. This commit adds functionality to start/stop the streams.
>>
>> +++ b/sound/firewire/fireworks/fireworks.c
>> +static int
>> +init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
>> +{
>> +	...
>> +	err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
>
> amdtp_stream_destroy() must be called.

For me, there is an ambiguous on what you said. Must it be called before 
calling amdtp_stream_init() or in a case that the function returns error?

If the former, can I request you the reason?
Else, is it due to reference counter of firewire unit? (fw_unit_get/put)

>> +static int
>> +start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
>> +	     unsigned int sampling_rate)
>> +{
>> +	...
>> +	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;
>> +}
>
> If amdtp_stream_start() fails, this function will try to wait for
> the stream anyway.

Exactly. I missed it.

>> +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;
>
> If the second init_stream() fails, this function will return with only one
> of the streams initialized.
>
>> +
>> +	/* set IEC61883 compliant mode */
>> +	err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
>> +end:
>> +	return err;
>
> And if this fails, this function will return an error, but the two streams
> will still be initialized.
>
> In the error cases, any so-far initialized stream must be destroyed.

OK.

>> +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;
>
> snd_efw_stream_stop_duplex() must always succeed so that the
> resources can be freed properly.  Therefore, it should not
> try to ask the device for anything (the device might be
> resetting or be unplugged).

Exactly.

> Either cache the master/slave pointers when starting the stream,
> or rewrite get_roles() so that it does not access the device.

Hm. the cache is better idea for this issue.


Thank you

Takashi Sakamoto
o-takashi@sakamocchi.jp
Clemens Ladisch April 4, 2014, 3:05 p.m. UTC | #3
Takashi Sakamoto wrote:
> (Apr 04 2014 05:56), Clemens Ladisch wrote:
>> Takashi Sakamoto wrote:
>>> +++ b/sound/firewire/fireworks/fireworks.c
>>> +static int
>>> +init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
>>> +{
>>> +    ...
>>> +    err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
>>
>> amdtp_stream_destroy() must be called.
>
> For me, there is an ambiguous on what you said. Must it be called before
> calling amdtp_stream_init() or in a case that the function returns error?

It must be called when the driver is disconnected normally (I guess in
snd_efw_stream_destroy_duplex), or when driver amdtp_stream_init() has
succeeded but driver loading aborts due to some other error.

> is it due to reference counter of firewire unit? (fw_unit_get/put)

Yes, and any other resources it might have allocated.


Regards,
Clemens
Takashi Sakamoto April 6, 2014, 1:20 p.m. UTC | #4
Clemens

(Apr 5 2014 00:05), Clemens Ladisch wrote:
> It must be called when the driver is disconnected normally (I guess in
> snd_efw_stream_destroy_duplex), or when driver amdtp_stream_init() has
> succeeded but driver loading aborts due to some other error.
>
>> is it due to reference counter of firewire unit? (fw_unit_get/put)
>
> Yes, and any other resources it might have allocated.

Oops. Indeed. And all of patches which I've posted don't include 
amdtp_stream_destroy()! I completely forgot it, for a long time...

Thanks for your indication


Takashi Sakamoto
o-takashi@sakamocchi.jp
diff mbox

Patch

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 c7e7f46..af83204 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -96,6 +96,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;
@@ -153,6 +189,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;
@@ -170,12 +210,17 @@  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);
 }
 
 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);
+}