Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / firewire / dice / dice-stream.c
diff --git a/kernel/sound/firewire/dice/dice-stream.c b/kernel/sound/firewire/dice/dice-stream.c
new file mode 100644 (file)
index 0000000..07dbd01
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * 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"
+
+#define        CALLBACK_TIMEOUT        200
+
+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;
+}
+
+static void release_resources(struct snd_dice *dice,
+                             struct fw_iso_resources *resources)
+{
+       unsigned int channel;
+
+       /* Reset channel number */
+       channel = cpu_to_be32((u32)-1);
+       if (resources == &dice->tx_resources)
+               snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
+                                             &channel, 4);
+       else
+               snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
+                                             &channel, 4);
+
+       fw_iso_resources_free(resources);
+}
+
+static int keep_resources(struct snd_dice *dice,
+                         struct fw_iso_resources *resources,
+                         unsigned int max_payload_bytes)
+{
+       unsigned int channel;
+       int err;
+
+       err = fw_iso_resources_allocate(resources, max_payload_bytes,
+                               fw_parent_device(dice->unit)->max_speed);
+       if (err < 0)
+               goto end;
+
+       /* Set channel number */
+       channel = cpu_to_be32(resources->channel);
+       if (resources == &dice->tx_resources)
+               err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
+                                                   &channel, 4);
+       else
+               err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
+                                                   &channel, 4);
+       if (err < 0)
+               release_resources(dice, resources);
+end:
+       return err;
+}
+
+static void stop_stream(struct snd_dice *dice, struct amdtp_stream *stream)
+{
+       amdtp_stream_pcm_abort(stream);
+       amdtp_stream_stop(stream);
+
+       if (stream == &dice->tx_stream)
+               release_resources(dice, &dice->tx_resources);
+       else
+               release_resources(dice, &dice->rx_resources);
+}
+
+static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream,
+                       unsigned int rate)
+{
+       struct fw_iso_resources *resources;
+       unsigned int i, mode, pcm_chs, midi_ports;
+       int err;
+
+       err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
+       if (err < 0)
+               goto end;
+       if (stream == &dice->tx_stream) {
+               resources = &dice->tx_resources;
+               pcm_chs = dice->tx_channels[mode];
+               midi_ports = dice->tx_midi_ports[mode];
+       } else {
+               resources = &dice->rx_resources;
+               pcm_chs = dice->rx_channels[mode];
+               midi_ports = dice->rx_midi_ports[mode];
+       }
+
+       /*
+        * 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
+        * a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are
+        * transferred on AMDTP packets at 96 kHz. Two successive samples of a
+        * channel are stored consecutively in the packet. This quirk is called
+        * as 'Dual Wire'.
+        * For this quirk, blocking mode is required and PCM buffer size should
+        * be aligned to SYT_INTERVAL.
+        */
+       if (mode > 1) {
+               rate /= 2;
+               pcm_chs *= 2;
+               stream->double_pcm_frames = true;
+       } else {
+               stream->double_pcm_frames = false;
+       }
+
+       amdtp_stream_set_parameters(stream, rate, pcm_chs, midi_ports);
+       if (mode > 1) {
+               pcm_chs /= 2;
+
+               for (i = 0; i < pcm_chs; i++) {
+                       stream->pcm_positions[i] = i * 2;
+                       stream->pcm_positions[i + pcm_chs] = i * 2 + 1;
+               }
+       }
+
+       err = keep_resources(dice, resources,
+                            amdtp_stream_get_max_payload(stream));
+       if (err < 0) {
+               dev_err(&dice->unit->device,
+                       "fail to keep isochronous resources\n");
+               goto end;
+       }
+
+       err = amdtp_stream_start(stream, resources->channel,
+                                fw_parent_device(dice->unit)->max_speed);
+       if (err < 0)
+               release_resources(dice, resources);
+end:
+       return err;
+}
+
+static int get_sync_mode(struct snd_dice *dice, enum cip_flags *sync_mode)
+{
+       u32 source;
+       int err;
+
+       err = snd_dice_transaction_get_clock_source(dice, &source);
+       if (err < 0)
+               goto end;
+
+       switch (source) {
+       /* So-called 'SYT Match' modes, sync_to_syt value of packets received */
+       case CLOCK_SOURCE_ARX4: /* in 4th stream */
+       case CLOCK_SOURCE_ARX3: /* in 3rd stream */
+       case CLOCK_SOURCE_ARX2: /* in 2nd stream */
+               err = -ENOSYS;
+               break;
+       case CLOCK_SOURCE_ARX1: /* in 1st stream, which this driver uses */
+               *sync_mode = 0;
+               break;
+       default:
+               *sync_mode = CIP_SYNC_TO_DEVICE;
+               break;
+       }
+end:
+       return err;
+}
+
+int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate)
+{
+       struct amdtp_stream *master, *slave;
+       unsigned int curr_rate;
+       enum cip_flags sync_mode;
+       int err = 0;
+
+       if (dice->substreams_counter == 0)
+               goto end;
+
+       err = get_sync_mode(dice, &sync_mode);
+       if (err < 0)
+               goto end;
+       if (sync_mode == CIP_SYNC_TO_DEVICE) {
+               master = &dice->tx_stream;
+               slave  = &dice->rx_stream;
+       } else {
+               master = &dice->rx_stream;
+               slave  = &dice->tx_stream;
+       }
+
+       /* Some packet queueing errors. */
+       if (amdtp_streaming_error(master) || amdtp_streaming_error(slave))
+               stop_stream(dice, master);
+
+       /* Stop stream if rate is different. */
+       err = snd_dice_transaction_get_rate(dice, &curr_rate);
+       if (err < 0) {
+               dev_err(&dice->unit->device,
+                       "fail to get sampling rate\n");
+               goto end;
+       }
+       if (rate == 0)
+               rate = curr_rate;
+       if (rate != curr_rate)
+               stop_stream(dice, master);
+
+       if (!amdtp_stream_running(master)) {
+               stop_stream(dice, slave);
+               snd_dice_transaction_clear_enable(dice);
+
+               amdtp_stream_set_sync(sync_mode, master, slave);
+
+               err = snd_dice_transaction_set_rate(dice, rate);
+               if (err < 0) {
+                       dev_err(&dice->unit->device,
+                               "fail to set sampling rate\n");
+                       goto end;
+               }
+
+               /* Start both streams. */
+               err = start_stream(dice, master, rate);
+               if (err < 0) {
+                       dev_err(&dice->unit->device,
+                               "fail to start AMDTP master stream\n");
+                       goto end;
+               }
+               err = start_stream(dice, slave, rate);
+               if (err < 0) {
+                       dev_err(&dice->unit->device,
+                               "fail to start AMDTP slave stream\n");
+                       stop_stream(dice, master);
+                       goto end;
+               }
+               err = snd_dice_transaction_set_enable(dice);
+               if (err < 0) {
+                       dev_err(&dice->unit->device,
+                               "fail to enable interface\n");
+                       stop_stream(dice, master);
+                       stop_stream(dice, slave);
+                       goto end;
+               }
+
+               /* Wait first callbacks */
+               if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT) ||
+                   !amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) {
+                       snd_dice_transaction_clear_enable(dice);
+                       stop_stream(dice, master);
+                       stop_stream(dice, slave);
+                       err = -ETIMEDOUT;
+               }
+       }
+end:
+       return err;
+}
+
+void snd_dice_stream_stop_duplex(struct snd_dice *dice)
+{
+       if (dice->substreams_counter > 0)
+               return;
+
+       snd_dice_transaction_clear_enable(dice);
+
+       stop_stream(dice, &dice->tx_stream);
+       stop_stream(dice, &dice->rx_stream);
+}
+
+static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream)
+{
+       int err;
+       struct fw_iso_resources *resources;
+       enum amdtp_stream_direction dir;
+
+       if (stream == &dice->tx_stream) {
+               resources = &dice->tx_resources;
+               dir = AMDTP_IN_STREAM;
+       } else {
+               resources = &dice->rx_resources;
+               dir = AMDTP_OUT_STREAM;
+       }
+
+       err = fw_iso_resources_init(resources, dice->unit);
+       if (err < 0)
+               goto end;
+       resources->channels_mask = 0x00000000ffffffffuLL;
+
+       err = amdtp_stream_init(stream, dice->unit, dir, CIP_BLOCKING);
+       if (err < 0) {
+               amdtp_stream_destroy(stream);
+               fw_iso_resources_destroy(resources);
+       }
+end:
+       return err;
+}
+
+/*
+ * This function should be called before starting streams or after stopping
+ * streams.
+ */
+static void destroy_stream(struct snd_dice *dice, struct amdtp_stream *stream)
+{
+       struct fw_iso_resources *resources;
+
+       if (stream == &dice->tx_stream)
+               resources = &dice->tx_resources;
+       else
+               resources = &dice->rx_resources;
+
+       amdtp_stream_destroy(stream);
+       fw_iso_resources_destroy(resources);
+}
+
+int snd_dice_stream_init_duplex(struct snd_dice *dice)
+{
+       int err;
+
+       dice->substreams_counter = 0;
+
+       err = init_stream(dice, &dice->tx_stream);
+       if (err < 0)
+               goto end;
+
+       err = init_stream(dice, &dice->rx_stream);
+       if (err < 0)
+               destroy_stream(dice, &dice->tx_stream);
+end:
+       return err;
+}
+
+void snd_dice_stream_destroy_duplex(struct snd_dice *dice)
+{
+       snd_dice_transaction_clear_enable(dice);
+
+       destroy_stream(dice, &dice->tx_stream);
+       destroy_stream(dice, &dice->rx_stream);
+
+       dice->substreams_counter = 0;
+}
+
+void snd_dice_stream_update_duplex(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;
+
+       stop_stream(dice, &dice->rx_stream);
+       stop_stream(dice, &dice->tx_stream);
+
+       fw_iso_resources_update(&dice->rx_resources);
+       fw_iso_resources_update(&dice->tx_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);
+}