--- /dev/null
+/*
+ * dice_stream.c - a part of driver for DICE based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "dice.h"
+
+const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = {
+       /* mode 0 */
+       [0] =  32000,
+       [1] =  44100,
+       [2] =  48000,
+       /* mode 1 */
+       [3] =  88200,
+       [4] =  96000,
+       /* mode 2 */
+       [5] = 176400,
+       [6] = 192000,
+};
+
+int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate,
+                                 unsigned int *mode)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) {
+               if (!(dice->clock_caps & BIT(i)))
+                       continue;
+               if (snd_dice_rates[i] != rate)
+                       continue;
+
+               *mode = (i - 1) / 2;
+               return 0;
+       }
+       return -EINVAL;
+}
+
+int snd_dice_stream_start_packets(struct snd_dice *dice)
+{
+       int err;
+
+       if (amdtp_stream_running(&dice->rx_stream))
+               return 0;
+
+       err = amdtp_stream_start(&dice->rx_stream, dice->rx_resources.channel,
+                                fw_parent_device(dice->unit)->max_speed);
+       if (err < 0)
+               return err;
+
+       err = snd_dice_transaction_set_enable(dice);
+       if (err < 0) {
+               amdtp_stream_stop(&dice->rx_stream);
+               return err;
+       }
+
+       return 0;
+}
+
+int snd_dice_stream_start(struct snd_dice *dice)
+{
+       __be32 channel;
+       int err;
+
+       if (!dice->rx_resources.allocated) {
+               err = fw_iso_resources_allocate(&dice->rx_resources,
+                               amdtp_stream_get_max_payload(&dice->rx_stream),
+                               fw_parent_device(dice->unit)->max_speed);
+               if (err < 0)
+                       goto error;
+
+               channel = cpu_to_be32(dice->rx_resources.channel);
+               err = snd_dice_transaction_write_tx(dice, RX_ISOCHRONOUS,
+                                                   &channel, 4);
+               if (err < 0)
+                       goto err_resources;
+       }
+
+       err = snd_dice_stream_start_packets(dice);
+       if (err < 0)
+               goto err_rx_channel;
+
+       return 0;
+
+err_rx_channel:
+       channel = cpu_to_be32((u32)-1);
+       snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4);
+err_resources:
+       fw_iso_resources_free(&dice->rx_resources);
+error:
+       return err;
+}
+
+void snd_dice_stream_stop_packets(struct snd_dice *dice)
+{
+       if (amdtp_stream_running(&dice->rx_stream)) {
+               snd_dice_transaction_clear_enable(dice);
+               amdtp_stream_stop(&dice->rx_stream);
+       }
+}
+
+void snd_dice_stream_stop(struct snd_dice *dice)
+{
+       __be32 channel;
+
+       snd_dice_stream_stop_packets(dice);
+
+       if (!dice->rx_resources.allocated)
+               return;
+
+       channel = cpu_to_be32((u32)-1);
+       snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4);
+
+       fw_iso_resources_free(&dice->rx_resources);
+}
+
+int snd_dice_stream_init(struct snd_dice *dice)
+{
+       int err;
+
+       err = fw_iso_resources_init(&dice->rx_resources, dice->unit);
+       if (err < 0)
+               goto end;
+       dice->rx_resources.channels_mask = 0x00000000ffffffffuLL;
+
+       err = amdtp_stream_init(&dice->rx_stream, dice->unit, AMDTP_OUT_STREAM,
+                               CIP_BLOCKING);
+       if (err < 0)
+               goto error;
+
+       err = snd_dice_transaction_set_clock_source(dice, CLOCK_SOURCE_ARX1);
+       if (err < 0)
+               goto error;
+end:
+       return err;
+error:
+       amdtp_stream_destroy(&dice->rx_stream);
+       fw_iso_resources_destroy(&dice->rx_resources);
+       return err;
+}
+
+void snd_dice_stream_destroy(struct snd_dice *dice)
+{
+       amdtp_stream_pcm_abort(&dice->rx_stream);
+       snd_dice_stream_stop(dice);
+       amdtp_stream_destroy(&dice->rx_stream);
+       fw_iso_resources_destroy(&dice->rx_resources);
+}
+
+void snd_dice_stream_update(struct snd_dice *dice)
+{
+       /*
+        * On a bus reset, the DICE firmware disables streaming and then goes
+        * off contemplating its own navel for hundreds of milliseconds before
+        * it can react to any of our attempts to reenable streaming.  This
+        * means that we lose synchronization anyway, so we force our streams
+        * to stop so that the application can restart them in an orderly
+        * manner.
+        */
+       dice->global_enabled = false;
+
+       amdtp_stream_pcm_abort(&dice->rx_stream);
+       snd_dice_stream_stop_packets(dice);
+       fw_iso_resources_update(&dice->rx_resources);
+}
+
+static void dice_lock_changed(struct snd_dice *dice)
+{
+       dice->dev_lock_changed = true;
+       wake_up(&dice->hwdep_wait);
+}
+
+int snd_dice_stream_lock_try(struct snd_dice *dice)
+{
+       int err;
+
+       spin_lock_irq(&dice->lock);
+
+       if (dice->dev_lock_count < 0) {
+               err = -EBUSY;
+               goto out;
+       }
+
+       if (dice->dev_lock_count++ == 0)
+               dice_lock_changed(dice);
+       err = 0;
+out:
+       spin_unlock_irq(&dice->lock);
+       return err;
+}
+
+void snd_dice_stream_lock_release(struct snd_dice *dice)
+{
+       spin_lock_irq(&dice->lock);
+
+       if (WARN_ON(dice->dev_lock_count <= 0))
+               goto out;
+
+       if (--dice->dev_lock_count == 0)
+               dice_lock_changed(dice);
+out:
+       spin_unlock_irq(&dice->lock);
+}
 
 MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 
-const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = {
-       /* mode 0 */
-       [0] =  32000,
-       [1] =  44100,
-       [2] =  48000,
-       /* mode 1 */
-       [3] =  88200,
-       [4] =  96000,
-       /* mode 2 */
-       [5] = 176400,
-       [6] = 192000,
-};
-
-static unsigned int rate_to_index(unsigned int rate)
-{
-       unsigned int i;
-
-       for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i)
-               if (snd_dice_rates[i] == rate)
-                       return i;
-
-       return 0;
-}
-
-static unsigned int rate_index_to_mode(unsigned int rate_index)
-{
-       return ((int)rate_index - 1) / 2;
-}
-
-static void dice_lock_changed(struct snd_dice *dice)
-{
-       dice->dev_lock_changed = true;
-       wake_up(&dice->hwdep_wait);
-}
-
-static int dice_try_lock(struct snd_dice *dice)
-{
-       int err;
-
-       spin_lock_irq(&dice->lock);
-
-       if (dice->dev_lock_count < 0) {
-               err = -EBUSY;
-               goto out;
-       }
-
-       if (dice->dev_lock_count++ == 0)
-               dice_lock_changed(dice);
-       err = 0;
-
-out:
-       spin_unlock_irq(&dice->lock);
-
-       return err;
-}
-
-static void dice_unlock(struct snd_dice *dice)
-{
-       spin_lock_irq(&dice->lock);
-
-       if (WARN_ON(dice->dev_lock_count <= 0))
-               goto out;
-
-       if (--dice->dev_lock_count == 0)
-               dice_lock_changed(dice);
-
-out:
-       spin_unlock_irq(&dice->lock);
-}
-
 static int dice_rate_constraint(struct snd_pcm_hw_params *params,
                                struct snd_pcm_hw_rule *rule)
 {
        struct snd_dice *dice = rule->private;
-       const struct snd_interval *channels =
+
+       const struct snd_interval *c =
                hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-       struct snd_interval *rate =
+       struct snd_interval *r =
                hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-       struct snd_interval allowed_rates = {
+       struct snd_interval rates = {
                .min = UINT_MAX, .max = 0, .integer = 1
        };
-       unsigned int i, mode;
+       unsigned int i, rate, mode, *pcm_channels = dice->rx_channels;
 
        for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
-               mode = rate_index_to_mode(i);
-               if ((dice->clock_caps & (1 << i)) &&
-                   snd_interval_test(channels, dice->rx_channels[mode])) {
-                       allowed_rates.min = min(allowed_rates.min,
-                                               snd_dice_rates[i]);
-                       allowed_rates.max = max(allowed_rates.max,
-                                               snd_dice_rates[i]);
-               }
+               rate = snd_dice_rates[i];
+               if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0)
+                       continue;
+
+               if (!snd_interval_test(c, pcm_channels[mode]))
+                       continue;
+
+               rates.min = min(rates.min, rate);
+               rates.max = max(rates.max, rate);
        }
 
-       return snd_interval_refine(rate, &allowed_rates);
+       return snd_interval_refine(r, &rates);
 }
 
 static int dice_channels_constraint(struct snd_pcm_hw_params *params,
                                    struct snd_pcm_hw_rule *rule)
 {
        struct snd_dice *dice = rule->private;
-       const struct snd_interval *rate =
+
+       const struct snd_interval *r =
                hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
-       struct snd_interval *channels =
+       struct snd_interval *c =
                hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-       struct snd_interval allowed_channels = {
+       struct snd_interval channels = {
                .min = UINT_MAX, .max = 0, .integer = 1
        };
-       unsigned int i, mode;
+       unsigned int i, rate, mode, *pcm_channels = dice->rx_channels;
 
-       for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i)
-               if ((dice->clock_caps & (1 << i)) &&
-                   snd_interval_test(rate, snd_dice_rates[i])) {
-                       mode = rate_index_to_mode(i);
-                       allowed_channels.min = min(allowed_channels.min,
-                                                  dice->rx_channels[mode]);
-                       allowed_channels.max = max(allowed_channels.max,
-                                                  dice->rx_channels[mode]);
-               }
+       for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
+               rate = snd_dice_rates[i];
+               if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0)
+                       continue;
 
-       return snd_interval_refine(channels, &allowed_channels);
+               if (!snd_interval_test(r, rate))
+                       continue;
+
+               channels.min = min(channels.min, pcm_channels[mode]);
+               channels.max = max(channels.max, pcm_channels[mode]);
+       }
+
+       return snd_interval_refine(c, &channels);
 }
 
 static int dice_open(struct snd_pcm_substream *substream)
        unsigned int i;
        int err;
 
-       err = dice_try_lock(dice);
+       err = snd_dice_stream_lock_try(dice);
        if (err < 0)
                goto error;
 
        return 0;
 
 err_lock:
-       dice_unlock(dice);
+       snd_dice_stream_lock_release(dice);
 error:
        return err;
 }
 {
        struct snd_dice *dice = substream->private_data;
 
-       dice_unlock(dice);
+       snd_dice_stream_lock_release(dice);
 
        return 0;
 }
 
-static int dice_stream_start_packets(struct snd_dice *dice)
-{
-       int err;
-
-       if (amdtp_stream_running(&dice->rx_stream))
-               return 0;
-
-       err = amdtp_stream_start(&dice->rx_stream, dice->rx_resources.channel,
-                                fw_parent_device(dice->unit)->max_speed);
-       if (err < 0)
-               return err;
-
-       err = snd_dice_transaction_set_enable(dice);
-       if (err < 0) {
-               amdtp_stream_stop(&dice->rx_stream);
-               return err;
-       }
-
-       return 0;
-}
-
-static int dice_stream_start(struct snd_dice *dice)
-{
-       __be32 channel;
-       int err;
-
-       if (!dice->rx_resources.allocated) {
-               err = fw_iso_resources_allocate(&dice->rx_resources,
-                               amdtp_stream_get_max_payload(&dice->rx_stream),
-                               fw_parent_device(dice->unit)->max_speed);
-               if (err < 0)
-                       goto error;
-
-               channel = cpu_to_be32(dice->rx_resources.channel);
-               err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
-                                                   &channel, 4);
-               if (err < 0)
-                       goto err_resources;
-       }
-
-       err = dice_stream_start_packets(dice);
-       if (err < 0)
-               goto err_rx_channel;
-
-       return 0;
-
-err_rx_channel:
-       channel = cpu_to_be32((u32)-1);
-       snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4);
-err_resources:
-       fw_iso_resources_free(&dice->rx_resources);
-error:
-       return err;
-}
-
-static void dice_stream_stop_packets(struct snd_dice *dice)
-{
-       if (amdtp_stream_running(&dice->rx_stream)) {
-               snd_dice_transaction_clear_enable(dice);
-               amdtp_stream_stop(&dice->rx_stream);
-       }
-}
-
-static void dice_stream_stop(struct snd_dice *dice)
-{
-       __be32 channel;
-
-       dice_stream_stop_packets(dice);
-
-       if (!dice->rx_resources.allocated)
-               return;
-
-       channel = cpu_to_be32((u32)-1);
-       snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS, &channel, 4);
-
-       fw_iso_resources_free(&dice->rx_resources);
-}
-
 static int dice_hw_params(struct snd_pcm_substream *substream,
                          struct snd_pcm_hw_params *hw_params)
 {
        struct snd_dice *dice = substream->private_data;
-       unsigned int rate_index, mode, rate, channels, i;
+       unsigned int mode, rate, channels, i;
        int err;
 
        mutex_lock(&dice->mutex);
-       dice_stream_stop(dice);
+       snd_dice_stream_stop(dice);
        mutex_unlock(&dice->mutex);
 
        err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
        if (err < 0)
                return err;
 
+       err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
+       if (err < 0)
+               return err;
+
        /*
         * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in
         * one data block of AMDTP packet. Thus sampling transfer frequency is
         * be aligned to SYT_INTERVAL.
         */
        channels = params_channels(hw_params);
-       rate_index = rate_to_index(rate);
-       if (rate_index > 4) {
+       if (mode > 1) {
                if (channels > AMDTP_MAX_CHANNELS_FOR_PCM / 2) {
                        err = -ENOSYS;
                        return err;
                dice->rx_stream.double_pcm_frames = false;
        }
 
-       mode = rate_index_to_mode(rate_index);
        amdtp_stream_set_parameters(&dice->rx_stream, rate, channels,
                                    dice->rx_midi_ports[mode]);
-       if (rate_index > 4) {
+       if (mode > 4) {
                channels /= 2;
 
                for (i = 0; i < channels; i++) {
        struct snd_dice *dice = substream->private_data;
 
        mutex_lock(&dice->mutex);
-       dice_stream_stop(dice);
+       snd_dice_stream_stop(dice);
        mutex_unlock(&dice->mutex);
 
        return snd_pcm_lib_free_vmalloc_buffer(substream);
        mutex_lock(&dice->mutex);
 
        if (amdtp_streaming_error(&dice->rx_stream))
-               dice_stream_stop_packets(dice);
+               snd_dice_stream_stop_packets(dice);
 
-       err = dice_stream_start(dice);
+       err = snd_dice_stream_start(dice);
        if (err < 0) {
                mutex_unlock(&dice->mutex);
                return err;
        return err;
 }
 
-static int highest_supported_mode_rate(struct snd_dice *dice, unsigned int mode)
+static int highest_supported_mode_rate(struct snd_dice *dice,
+                                      unsigned int mode, unsigned int *rate)
 {
-       int i;
+       unsigned int i, m;
 
-       for (i = ARRAY_SIZE(snd_dice_rates) - 1; i >= 0; --i)
-               if ((dice->clock_caps & (1 << i)) &&
-                   rate_index_to_mode(i) == mode)
-                       return i;
+       for (i = ARRAY_SIZE(snd_dice_rates); i > 0; i--) {
+               *rate = snd_dice_rates[i - 1];
+               if (snd_dice_stream_get_rate_mode(dice, *rate, &m) < 0)
+                       continue;
+               if (mode == m)
+                       break;
+       }
+       if (i == 0)
+               return -EINVAL;
 
-       return -1;
+       return 0;
 }
 
 static int dice_read_mode_params(struct snd_dice *dice, unsigned int mode)
 {
        __be32 values[2];
-       int rate_index, err;
+       unsigned int rate;
+       int err;
 
-       rate_index = highest_supported_mode_rate(dice, mode);
-       if (rate_index < 0) {
+       if (highest_supported_mode_rate(dice, mode, &rate) < 0) {
                dice->rx_channels[mode] = 0;
                dice->rx_midi_ports[mode] = 0;
                return 0;
        }
 
-       err = snd_dice_transaction_set_rate(dice, snd_dice_rates[rate_index]);
+       err = snd_dice_transaction_set_rate(dice, rate);
        if (err < 0)
                return err;
 
 
        dice_card_strings(dice);
 
-       err = snd_dice_transaction_set_clock_source(dice, CLOCK_SOURCE_ARX1);
-       if (err < 0)
-               goto error;
-
        err = dice_create_pcm(dice);
        if (err < 0)
                goto error;
 
        dice_create_proc(dice);
 
-       err = fw_iso_resources_init(&dice->rx_resources, unit);
+       err = snd_dice_stream_init(dice);
        if (err < 0)
                goto error;
-       dice->rx_resources.channels_mask = 0x00000000ffffffffuLL;
-
-       err = amdtp_stream_init(&dice->rx_stream, unit, AMDTP_OUT_STREAM,
-                               CIP_BLOCKING);
-       if (err < 0) {
-               fw_iso_resources_destroy(&dice->rx_resources);
-               goto error;
-       }
 
        err = snd_card_register(card);
        if (err < 0) {
-               amdtp_stream_destroy(&dice->rx_stream);
-               fw_iso_resources_destroy(&dice->rx_resources);
+               snd_dice_stream_destroy(dice);
                goto error;
        }
 
 {
        struct snd_dice *dice = dev_get_drvdata(&unit->device);
 
-       amdtp_stream_pcm_abort(&dice->rx_stream);
-
        snd_card_disconnect(dice->card);
 
        mutex_lock(&dice->mutex);
 
-       dice_stream_stop(dice);
+       snd_dice_stream_destroy(dice);
 
        mutex_unlock(&dice->mutex);
 
        /* The handler address register becomes initialized. */
        snd_dice_transaction_reinit(dice);
 
-       /*
-        * On a bus reset, the DICE firmware disables streaming and then goes
-        * off contemplating its own navel for hundreds of milliseconds before
-        * it can react to any of our attempts to reenable streaming.  This
-        * means that we lose synchronization anyway, so we force our streams
-        * to stop so that the application can restart them in an orderly
-        * manner.
-        */
-       amdtp_stream_pcm_abort(&dice->rx_stream);
-
        mutex_lock(&dice->mutex);
-
-       dice->global_enabled = false;
-
-       dice_stream_stop_packets(dice);
-       fw_iso_resources_update(&dice->rx_resources);
-
+       snd_dice_stream_update(dice);
        mutex_unlock(&dice->mutex);
 }