--- /dev/null
+/*
+ * motu-stream.c - a part of driver for MOTU FireWire series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "motu.h"
+
+#define        CALLBACK_TIMEOUT        200
+
+#define ISOC_COMM_CONTROL_OFFSET               0x0b00
+#define  ISOC_COMM_CONTROL_MASK                        0xffff0000
+#define  CHANGE_RX_ISOC_COMM_STATE             0x80000000
+#define  RX_ISOC_COMM_IS_ACTIVATED             0x40000000
+#define  RX_ISOC_COMM_CHANNEL_MASK             0x3f000000
+#define  RX_ISOC_COMM_CHANNEL_SHIFT            24
+#define  CHANGE_TX_ISOC_COMM_STATE             0x00800000
+#define  TX_ISOC_COMM_IS_ACTIVATED             0x00400000
+#define  TX_ISOC_COMM_CHANNEL_MASK             0x003f0000
+#define  TX_ISOC_COMM_CHANNEL_SHIFT            16
+
+#define PACKET_FORMAT_OFFSET                   0x0b10
+#define  TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS        0x00000080
+#define  RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS        0x00000040
+#define  TX_PACKET_TRANSMISSION_SPEED_MASK     0x0000000f
+
+static int start_both_streams(struct snd_motu *motu, unsigned int rate)
+{
+       __be32 reg;
+       u32 data;
+       int err;
+
+       /* Set packet formation to our packet streaming engine. */
+       err = amdtp_motu_set_parameters(&motu->rx_stream, rate,
+                                       &motu->rx_packet_formats);
+       if (err < 0)
+               return err;
+
+       err = amdtp_motu_set_parameters(&motu->tx_stream, rate,
+                                       &motu->tx_packet_formats);
+       if (err < 0)
+               return err;
+
+
+       /* Get isochronous resources on the bus. */
+       err = fw_iso_resources_allocate(&motu->rx_resources,
+                               amdtp_stream_get_max_payload(&motu->rx_stream),
+                               fw_parent_device(motu->unit)->max_speed);
+       if (err < 0)
+               return err;
+
+       err = fw_iso_resources_allocate(&motu->tx_resources,
+                               amdtp_stream_get_max_payload(&motu->tx_stream),
+                               fw_parent_device(motu->unit)->max_speed);
+       if (err < 0)
+               return err;
+
+       /* Configure the unit to start isochronous communication. */
+       err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®,
+                                       sizeof(reg));
+       if (err < 0)
+               return err;
+       data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK;
+
+       data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED |
+               (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) |
+               CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED |
+               (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT);
+
+       reg = cpu_to_be32(data);
+       return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®,
+                                         sizeof(reg));
+}
+
+static void stop_both_streams(struct snd_motu *motu)
+{
+       __be32 reg;
+       u32 data;
+       int err;
+
+       err = motu->spec->protocol->switch_fetching_mode(motu, false);
+       if (err < 0)
+               return;
+
+       err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®,
+                                       sizeof(reg));
+       if (err < 0)
+               return;
+       data = be32_to_cpu(reg);
+
+       data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED);
+       data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE;
+
+       reg = cpu_to_be32(data);
+       snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®,
+                                  sizeof(reg));
+
+       fw_iso_resources_free(&motu->tx_resources);
+       fw_iso_resources_free(&motu->rx_resources);
+}
+
+static int start_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream)
+{
+       struct fw_iso_resources *resources;
+       int err;
+
+       if (stream == &motu->rx_stream)
+               resources = &motu->rx_resources;
+       else
+               resources = &motu->tx_resources;
+
+       err = amdtp_stream_start(stream, resources->channel,
+                                fw_parent_device(motu->unit)->max_speed);
+       if (err < 0)
+               return err;
+
+       if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
+               amdtp_stream_stop(stream);
+               fw_iso_resources_free(resources);
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static void stop_isoc_ctx(struct snd_motu *motu, struct amdtp_stream *stream)
+{
+       struct fw_iso_resources *resources;
+
+       if (stream == &motu->rx_stream)
+               resources = &motu->rx_resources;
+       else
+               resources = &motu->tx_resources;
+
+       amdtp_stream_stop(stream);
+       fw_iso_resources_free(resources);
+}
+
+static int ensure_packet_formats(struct snd_motu *motu)
+{
+       __be32 reg;
+       u32 data;
+       int err;
+
+       err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®,
+                                       sizeof(reg));
+       if (err < 0)
+               return err;
+       data = be32_to_cpu(reg);
+
+       data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS |
+                 RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS|
+                 TX_PACKET_TRANSMISSION_SPEED_MASK);
+       if (motu->tx_packet_formats.differed_part_pcm_chunks[0] == 0)
+               data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
+       if (motu->rx_packet_formats.differed_part_pcm_chunks[0] == 0)
+               data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
+       data |= fw_parent_device(motu->unit)->max_speed;
+
+       reg = cpu_to_be32(data);
+       return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®,
+                                         sizeof(reg));
+}
+
+int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate)
+{
+       const struct snd_motu_protocol *protocol = motu->spec->protocol;
+       unsigned int curr_rate;
+       int err = 0;
+
+       if (motu->capture_substreams == 0 && motu->playback_substreams == 0)
+               return 0;
+
+       /* Some packet queueing errors. */
+       if (amdtp_streaming_error(&motu->rx_stream) ||
+           amdtp_streaming_error(&motu->tx_stream)) {
+               amdtp_stream_stop(&motu->rx_stream);
+               amdtp_stream_stop(&motu->tx_stream);
+               stop_both_streams(motu);
+       }
+
+       err = protocol->cache_packet_formats(motu);
+       if (err < 0)
+               return err;
+
+       /* Stop stream if rate is different. */
+       err = protocol->get_clock_rate(motu, &curr_rate);
+       if (err < 0) {
+               dev_err(&motu->unit->device,
+                       "fail to get sampling rate: %d\n", err);
+               return err;
+       }
+       if (rate == 0)
+               rate = curr_rate;
+       if (rate != curr_rate) {
+               amdtp_stream_stop(&motu->rx_stream);
+               amdtp_stream_stop(&motu->tx_stream);
+               stop_both_streams(motu);
+       }
+
+       if (!amdtp_stream_running(&motu->rx_stream)) {
+               err = protocol->set_clock_rate(motu, rate);
+               if (err < 0) {
+                       dev_err(&motu->unit->device,
+                               "fail to set sampling rate: %d\n", err);
+                       return err;
+               }
+
+               err = ensure_packet_formats(motu);
+               if (err < 0)
+                       return err;
+
+               err = start_both_streams(motu, rate);
+               if (err < 0) {
+                       dev_err(&motu->unit->device,
+                               "fail to start isochronous comm: %d\n", err);
+                       stop_both_streams(motu);
+                       return err;
+               }
+
+               err = start_isoc_ctx(motu, &motu->rx_stream);
+               if (err < 0) {
+                       dev_err(&motu->unit->device,
+                               "fail to start IT context: %d\n", err);
+                       stop_both_streams(motu);
+                       return err;
+               }
+
+               err = protocol->switch_fetching_mode(motu, true);
+               if (err < 0) {
+                       dev_err(&motu->unit->device,
+                               "fail to enable frame fetching: %d\n", err);
+                       stop_both_streams(motu);
+                       return err;
+               }
+       }
+
+       if (!amdtp_stream_running(&motu->tx_stream) &&
+           motu->capture_substreams > 0) {
+               err = start_isoc_ctx(motu, &motu->tx_stream);
+               if (err < 0) {
+                       dev_err(&motu->unit->device,
+                               "fail to start IR context: %d", err);
+                       amdtp_stream_stop(&motu->rx_stream);
+                       stop_both_streams(motu);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
+void snd_motu_stream_stop_duplex(struct snd_motu *motu)
+{
+       if (motu->capture_substreams == 0) {
+               if (amdtp_stream_running(&motu->tx_stream))
+                       stop_isoc_ctx(motu, &motu->tx_stream);
+
+               if (motu->playback_substreams == 0) {
+                       if (amdtp_stream_running(&motu->rx_stream))
+                               stop_isoc_ctx(motu, &motu->rx_stream);
+                       stop_both_streams(motu);
+               }
+       }
+}
+
+static int init_stream(struct snd_motu *motu, enum amdtp_stream_direction dir)
+{
+       int err;
+       struct amdtp_stream *stream;
+       struct fw_iso_resources *resources;
+
+       if (dir == AMDTP_IN_STREAM) {
+               stream = &motu->tx_stream;
+               resources = &motu->tx_resources;
+       } else {
+               stream = &motu->rx_stream;
+               resources = &motu->rx_resources;
+       }
+
+       err = fw_iso_resources_init(resources, motu->unit);
+       if (err < 0)
+               return err;
+
+       err = amdtp_motu_init(stream, motu->unit, dir, motu->spec->protocol);
+       if (err < 0) {
+               amdtp_stream_destroy(stream);
+               fw_iso_resources_destroy(resources);
+       }
+
+       return err;
+}
+
+static void destroy_stream(struct snd_motu *motu,
+                          enum amdtp_stream_direction dir)
+{
+       struct amdtp_stream *stream;
+       struct fw_iso_resources *resources;
+
+       if (dir == AMDTP_IN_STREAM) {
+               stream = &motu->tx_stream;
+               resources = &motu->tx_resources;
+       } else {
+               stream = &motu->rx_stream;
+               resources = &motu->rx_resources;
+       }
+
+       amdtp_stream_destroy(stream);
+       fw_iso_resources_free(resources);
+}
+
+int snd_motu_stream_init_duplex(struct snd_motu *motu)
+{
+       int err;
+
+       err = init_stream(motu, AMDTP_IN_STREAM);
+       if (err < 0)
+               return err;
+
+       err = init_stream(motu, AMDTP_OUT_STREAM);
+       if (err < 0)
+               destroy_stream(motu, AMDTP_IN_STREAM);
+
+       return err;
+}
+
+/*
+ * This function should be called before starting streams or after stopping
+ * streams.
+ */
+void snd_motu_stream_destroy_duplex(struct snd_motu *motu)
+{
+       destroy_stream(motu, AMDTP_IN_STREAM);
+       destroy_stream(motu, AMDTP_OUT_STREAM);
+
+       motu->playback_substreams = 0;
+       motu->capture_substreams = 0;
+}