#include "dice.h"
 
+static int dice_rate_constraint(struct snd_pcm_hw_params *params,
+                               struct snd_pcm_hw_rule *rule)
+{
+       struct snd_pcm_substream *substream = rule->private;
+       struct snd_dice *dice = substream->private_data;
+       unsigned int index = substream->pcm->device;
+
+       const struct snd_interval *c =
+               hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+       struct snd_interval *r =
+               hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+       struct snd_interval rates = {
+               .min = UINT_MAX, .max = 0, .integer = 1
+       };
+       unsigned int *pcm_channels;
+       enum snd_dice_rate_mode mode;
+       unsigned int i, rate;
+
+       if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+               pcm_channels = dice->tx_pcm_chs[index];
+       else
+               pcm_channels = dice->rx_pcm_chs[index];
+
+       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;
+
+               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(r, &rates);
+}
+
+static int dice_channels_constraint(struct snd_pcm_hw_params *params,
+                                   struct snd_pcm_hw_rule *rule)
+{
+       struct snd_pcm_substream *substream = rule->private;
+       struct snd_dice *dice = substream->private_data;
+       unsigned int index = substream->pcm->device;
+
+       const struct snd_interval *r =
+               hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+       struct snd_interval *c =
+               hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+       struct snd_interval channels = {
+               .min = UINT_MAX, .max = 0, .integer = 1
+       };
+       unsigned int *pcm_channels;
+       enum snd_dice_rate_mode mode;
+       unsigned int i, rate;
+
+       if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+               pcm_channels = dice->tx_pcm_chs[index];
+       else
+               pcm_channels = dice->rx_pcm_chs[index];
+
+       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;
+
+               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 limit_channels_and_rates(struct snd_dice *dice,
                                    struct snd_pcm_runtime *runtime,
                                    enum amdtp_stream_direction dir,
-                                   unsigned int index, unsigned int size)
+                                   unsigned int index)
 {
        struct snd_pcm_hardware *hw = &runtime->hw;
-       struct amdtp_stream *stream;
-       unsigned int rate;
-       __be32 reg;
-       int err;
-
-       /*
-        * Retrieve current Multi Bit Linear Audio data channel and limit to
-        * it.
-        */
-       if (dir == AMDTP_IN_STREAM) {
-               stream = &dice->tx_stream[index];
-               err = snd_dice_transaction_read_tx(dice,
-                               size * index + TX_NUMBER_AUDIO,
-                               ®, sizeof(reg));
-       } else {
-               stream = &dice->rx_stream[index];
-               err = snd_dice_transaction_read_rx(dice,
-                               size * index + RX_NUMBER_AUDIO,
-                               ®, sizeof(reg));
+       unsigned int *pcm_channels;
+       unsigned int i;
+
+       if (dir == AMDTP_IN_STREAM)
+               pcm_channels = dice->tx_pcm_chs[index];
+       else
+               pcm_channels = dice->rx_pcm_chs[index];
+
+       hw->channels_min = UINT_MAX;
+       hw->channels_max = 0;
+
+       for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
+               enum snd_dice_rate_mode mode;
+               unsigned int rate, channels;
+
+               rate = snd_dice_rates[i];
+               if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0)
+                       continue;
+               hw->rates |= snd_pcm_rate_to_rate_bit(rate);
+
+               channels = pcm_channels[mode];
+               if (channels == 0)
+                       continue;
+               hw->channels_min = min(hw->channels_min, channels);
+               hw->channels_max = max(hw->channels_max, channels);
        }
-       if (err < 0)
-               return err;
-
-       hw->channels_min = hw->channels_max = be32_to_cpu(reg);
-
-       /* Retrieve current sampling transfer frequency and limit to it. */
-       err = snd_dice_transaction_get_rate(dice, &rate);
-       if (err < 0)
-               return err;
 
-       hw->rates = snd_pcm_rate_to_rate_bit(rate);
        snd_pcm_limit_hw_rates(runtime);
 
        return 0;
 {
        struct snd_pcm_runtime *runtime = substream->runtime;
        struct snd_pcm_hardware *hw = &runtime->hw;
+       unsigned int index = substream->pcm->device;
        enum amdtp_stream_direction dir;
        struct amdtp_stream *stream;
-       __be32 reg[2];
-       unsigned int count, size;
        int err;
 
        if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
                hw->formats = AM824_IN_PCM_FORMAT_BITS;
                dir = AMDTP_IN_STREAM;
-               stream = &dice->tx_stream[substream->pcm->device];
-               err = snd_dice_transaction_read_tx(dice, TX_NUMBER, reg,
-                                                  sizeof(reg));
+               stream = &dice->tx_stream[index];
        } else {
                hw->formats = AM824_OUT_PCM_FORMAT_BITS;
                dir = AMDTP_OUT_STREAM;
-               stream = &dice->rx_stream[substream->pcm->device];
-               err = snd_dice_transaction_read_rx(dice, RX_NUMBER, reg,
-                                                  sizeof(reg));
+               stream = &dice->rx_stream[index];
        }
 
+       err = limit_channels_and_rates(dice, substream->runtime, dir,
+                                      index);
        if (err < 0)
                return err;
 
-       count = min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS);
-       if (substream->pcm->device >= count)
-               return -ENXIO;
-
-       size = be32_to_cpu(reg[1]) * 4;
-       err = limit_channels_and_rates(dice, substream->runtime, dir,
-                                      substream->pcm->device, size);
+       err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+                                 dice_rate_constraint, substream,
+                                 SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+       if (err < 0)
+               return err;
+       err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+                                 dice_channels_constraint, substream,
+                                 SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                return err;
 
 static int pcm_open(struct snd_pcm_substream *substream)
 {
        struct snd_dice *dice = substream->private_data;
+       unsigned int source;
+       bool internal;
        int err;
 
        err = snd_dice_stream_lock_try(dice);
        if (err < 0)
                goto err_locked;
 
+       err = snd_dice_transaction_get_clock_source(dice, &source);
+       if (err < 0)
+               goto err_locked;
+       switch (source) {
+       case CLOCK_SOURCE_AES1:
+       case CLOCK_SOURCE_AES2:
+       case CLOCK_SOURCE_AES3:
+       case CLOCK_SOURCE_AES4:
+       case CLOCK_SOURCE_AES_ANY:
+       case CLOCK_SOURCE_ADAT:
+       case CLOCK_SOURCE_TDIF:
+       case CLOCK_SOURCE_WC:
+               internal = false;
+               break;
+       default:
+               internal = true;
+               break;
+       }
+
+       /*
+        * When source of clock is not internal or any PCM streams are running,
+        * available sampling rate is limited at current sampling rate.
+        */
+       if (!internal ||
+           amdtp_stream_pcm_running(&dice->tx_stream[0]) ||
+           amdtp_stream_pcm_running(&dice->tx_stream[1]) ||
+           amdtp_stream_pcm_running(&dice->rx_stream[0]) ||
+           amdtp_stream_pcm_running(&dice->rx_stream[1])) {
+               unsigned int rate;
+
+               err = snd_dice_transaction_get_rate(dice, &rate);
+               if (err < 0)
+                       goto err_locked;
+               substream->runtime->hw.rate_min = rate;
+               substream->runtime->hw.rate_max = rate;
+       }
+
        snd_pcm_set_sync(substream);
 end:
        return err;
                .page      = snd_pcm_lib_get_vmalloc_page,
                .mmap      = snd_pcm_lib_mmap_vmalloc,
        };
-       __be32 reg;
        struct snd_pcm *pcm;
        unsigned int i, max_capture, max_playback, capture, playback;
        int err;
        if (dice->force_two_pcms) {
                max_capture = max_playback = 2;
        } else {
+               int j;
                max_capture = max_playback = 0;
-               err = snd_dice_transaction_read_tx(dice, TX_NUMBER, ®,
-                                                  sizeof(reg));
-               if (err < 0)
-                       return err;
-               max_capture = min_t(unsigned int, be32_to_cpu(reg), MAX_STREAMS);
-
-               err = snd_dice_transaction_read_rx(dice, RX_NUMBER, ®,
-                                                  sizeof(reg));
-               if (err < 0)
-                       return err;
-               max_playback = min_t(unsigned int, be32_to_cpu(reg), MAX_STREAMS);
+               for (i = 0; i < MAX_STREAMS; ++i) {
+                       for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) {
+                               if (dice->tx_pcm_chs[i][j] > 0) {
+                                       ++max_capture;
+                                       break;
+                               }
+                       }
+
+                       for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) {
+                               if (dice->rx_pcm_chs[i][j] > 0) {
+                                       ++max_playback;
+                                       break;
+                               }
+                       }
+               }
        }
 
        for (i = 0; i < MAX_STREAMS; i++) {