ALSA: firewire-lib: Add support for AMDTP in-stream and PCM capture
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Fri, 25 Apr 2014 13:44:46 +0000 (22:44 +0900)
committerTakashi Iwai <tiwai@suse.de>
Mon, 26 May 2014 12:12:35 +0000 (14:12 +0200)
For capturing PCM, this commit adds the functionality to handle in-stream.
This is also applied for dual-wire mode.

Currently, capturing 32bit samples are supported.

When the sequence of in-packet has discontinuity of dbc, in-stream isn't handled
and amdtp_streaming_error() returns true.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/firewire/amdtp.c
sound/firewire/amdtp.h

index 790aa86..be1aabc 100644 (file)
@@ -39,6 +39,7 @@
  * only "Clock-based rate control mode" is supported
  */
 #define AMDTP_FDF_AM824                (0 << (CIP_FDF_SFC_SHIFT + 3))
+#define AMDTP_FDF_NO_DATA      0xff
 #define AMDTP_DBS_MASK         0x00ff0000
 #define AMDTP_DBS_SHIFT                16
 #define AMDTP_DBC_MASK         0x000000ff
@@ -47,6 +48,7 @@
 #define INTERRUPT_INTERVAL     16
 #define QUEUE_LENGTH           48
 
+#define IN_PACKET_HEADER_SIZE  4
 #define OUT_PACKET_HEADER_SIZE 0
 
 static void pcm_period_tasklet(unsigned long data);
@@ -179,6 +181,12 @@ static void amdtp_write_s16_dualwire(struct amdtp_stream *s,
 static void amdtp_write_s32_dualwire(struct amdtp_stream *s,
                                     struct snd_pcm_substream *pcm,
                                     __be32 *buffer, unsigned int frames);
+static void amdtp_read_s32(struct amdtp_stream *s,
+                          struct snd_pcm_substream *pcm,
+                          __be32 *buffer, unsigned int frames);
+static void amdtp_read_s32_dualwire(struct amdtp_stream *s,
+                                   struct snd_pcm_substream *pcm,
+                                   __be32 *buffer, unsigned int frames);
 
 /**
  * amdtp_stream_set_pcm_format - set the PCM format
@@ -200,16 +208,27 @@ void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
                WARN_ON(1);
                /* fall through */
        case SNDRV_PCM_FORMAT_S16:
-               if (s->dual_wire)
-                       s->transfer_samples = amdtp_write_s16_dualwire;
-               else
-                       s->transfer_samples = amdtp_write_s16;
-               break;
+               if (s->direction == AMDTP_OUT_STREAM) {
+                       if (s->dual_wire)
+                               s->transfer_samples = amdtp_write_s16_dualwire;
+                       else
+                               s->transfer_samples = amdtp_write_s16;
+                       break;
+               }
+               WARN_ON(1);
+               /* fall through */
        case SNDRV_PCM_FORMAT_S32:
-               if (s->dual_wire)
-                       s->transfer_samples = amdtp_write_s32_dualwire;
-               else
-                       s->transfer_samples = amdtp_write_s32;
+               if (s->direction == AMDTP_OUT_STREAM) {
+                       if (s->dual_wire)
+                               s->transfer_samples = amdtp_write_s32_dualwire;
+                       else
+                               s->transfer_samples = amdtp_write_s32;
+               } else {
+                       if (s->dual_wire)
+                               s->transfer_samples = amdtp_read_s32_dualwire;
+                       else
+                               s->transfer_samples = amdtp_read_s32;
+               }
                break;
        }
 }
@@ -420,6 +439,59 @@ static void amdtp_write_s16_dualwire(struct amdtp_stream *s,
        }
 }
 
+static void amdtp_read_s32(struct amdtp_stream *s,
+                          struct snd_pcm_substream *pcm,
+                          __be32 *buffer, unsigned int frames)
+{
+       struct snd_pcm_runtime *runtime = pcm->runtime;
+       unsigned int channels, remaining_frames, i, c;
+       u32 *dst;
+
+       channels = s->pcm_channels;
+       dst  = (void *)runtime->dma_area +
+                       frames_to_bytes(runtime, s->pcm_buffer_pointer);
+       remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+       for (i = 0; i < frames; ++i) {
+               for (c = 0; c < channels; ++c) {
+                       *dst = be32_to_cpu(buffer[c]) << 8;
+                       dst++;
+               }
+               buffer += s->data_block_quadlets;
+               if (--remaining_frames == 0)
+                       dst = (void *)runtime->dma_area;
+       }
+}
+
+static void amdtp_read_s32_dualwire(struct amdtp_stream *s,
+                                   struct snd_pcm_substream *pcm,
+                                   __be32 *buffer, unsigned int frames)
+{
+       struct snd_pcm_runtime *runtime = pcm->runtime;
+       unsigned int channels, remaining_frames, i, c;
+       u32 *dst;
+
+       dst = (void *)runtime->dma_area +
+                       frames_to_bytes(runtime, s->pcm_buffer_pointer);
+       remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+       channels = s->pcm_channels / 2;
+
+       for (i = 0; i < frames; ++i) {
+               for (c = 0; c < channels; ++c) {
+                       *dst = be32_to_cpu(buffer[c * 2]) << 8;
+                       dst++;
+               }
+               buffer += 1;
+               for (c = 0; c < channels; ++c) {
+                       *dst = be32_to_cpu(buffer[c * 2]) << 8;
+                       dst++;
+               }
+               buffer += s->data_block_quadlets - 1;
+               if (--remaining_frames == 0)
+                       dst = (void *)runtime->dma_area;
+       }
+}
+
 static void amdtp_fill_pcm_silence(struct amdtp_stream *s,
                                   __be32 *buffer, unsigned int frames)
 {
@@ -504,6 +576,12 @@ static inline int queue_out_packet(struct amdtp_stream *s,
                            payload_length, skip);
 }
 
+static inline int queue_in_packet(struct amdtp_stream *s)
+{
+       return queue_packet(s, IN_PACKET_HEADER_SIZE,
+                           amdtp_stream_get_max_payload(s), false);
+}
+
 static void handle_out_packet(struct amdtp_stream *s, unsigned int cycle)
 {
        __be32 *buffer;
@@ -552,6 +630,80 @@ static void handle_out_packet(struct amdtp_stream *s, unsigned int cycle)
                update_pcm_pointers(s, pcm, data_blocks);
 }
 
+static void handle_in_packet(struct amdtp_stream *s,
+                            unsigned int payload_quadlets,
+                            __be32 *buffer)
+{
+       u32 cip_header[2];
+       unsigned int data_blocks, data_block_quadlets, data_block_counter;
+       struct snd_pcm_substream *pcm = NULL;
+
+       cip_header[0] = be32_to_cpu(buffer[0]);
+       cip_header[1] = be32_to_cpu(buffer[1]);
+
+       /*
+        * This module supports 'Two-quadlet CIP header with SYT field'.
+        * For convinience, also check FMT field is AM824 or not.
+        */
+       if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
+           ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH) ||
+           ((cip_header[1] & CIP_FMT_MASK) != CIP_FMT_AM)) {
+               dev_info_ratelimited(&s->unit->device,
+                               "Invalid CIP header for AMDTP: %08X:%08X\n",
+                               cip_header[0], cip_header[1]);
+               goto end;
+       }
+
+       /* Calculate data blocks */
+       if (payload_quadlets < 3 ||
+           ((cip_header[1] & CIP_FDF_MASK) ==
+                               (AMDTP_FDF_NO_DATA << CIP_FDF_SFC_SHIFT))) {
+               data_blocks = 0;
+       } else {
+               data_block_quadlets =
+                       (cip_header[0] & AMDTP_DBS_MASK) >> AMDTP_DBS_SHIFT;
+               /* avoid division by zero */
+               if (data_block_quadlets == 0) {
+                       dev_info_ratelimited(&s->unit->device,
+                               "Detect invalid value in dbs field: %08X\n",
+                               cip_header[0]);
+                       goto err;
+               }
+
+               data_blocks = (payload_quadlets - 2) / data_block_quadlets;
+       }
+
+       /* Check data block counter continuity */
+       data_block_counter = cip_header[0] & AMDTP_DBC_MASK;
+       if (data_block_counter != s->data_block_counter) {
+               dev_info(&s->unit->device,
+                        "Detect discontinuity of CIP: %02X %02X\n",
+                        s->data_block_counter, data_block_counter);
+               goto err;
+       }
+
+       if (data_blocks > 0) {
+               buffer += 2;
+
+               pcm = ACCESS_ONCE(s->pcm);
+               if (pcm)
+                       s->transfer_samples(s, pcm, buffer, data_blocks);
+       }
+
+       s->data_block_counter = (data_block_counter + data_blocks) & 0xff;
+end:
+       if (queue_in_packet(s) < 0)
+               goto err;
+
+       if (pcm)
+               update_pcm_pointers(s, pcm, data_blocks);
+
+       return;
+err:
+       s->packet_index = -1;
+       amdtp_stream_pcm_abort(s);
+}
+
 static void out_stream_callback(struct fw_iso_context *context, u32 cycle,
                                size_t header_length, void *header,
                                void *private_data)
@@ -571,6 +723,31 @@ static void out_stream_callback(struct fw_iso_context *context, u32 cycle,
        fw_iso_context_queue_flush(s->context);
 }
 
+static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
+                              size_t header_length, void *header,
+                              void *private_data)
+{
+       struct amdtp_stream *s = private_data;
+       unsigned int p, packets, payload_quadlets;
+       __be32 *buffer, *headers = header;
+
+       /* The number of packets in buffer */
+       packets = header_length / IN_PACKET_HEADER_SIZE;
+
+       for (p = 0; p < packets; p++) {
+               if (s->packet_index < 0)
+                       return;
+               buffer = s->buffer.packets[s->packet_index].buffer;
+
+               /* The number of quadlets in this packet */
+               payload_quadlets =
+                       (be32_to_cpu(headers[p]) >> ISO_DATA_LENGTH_SHIFT) / 4;
+               handle_in_packet(s, payload_quadlets, buffer);
+       }
+
+       fw_iso_context_queue_flush(s->context);
+}
+
 /**
  * amdtp_stream_start - start transferring packets
  * @s: the AMDTP stream to start
@@ -595,7 +772,10 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
                [CIP_SFC_88200]  = {  0,   67 },
                [CIP_SFC_176400] = {  0,   67 },
        };
-       int err;
+       unsigned int header_size;
+       enum dma_data_direction dir;
+       fw_iso_callback_t cb;
+       int type, err;
 
        mutex_lock(&s->mutex);
 
@@ -610,16 +790,26 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
        s->syt_offset_state = initial_state[s->sfc].syt_offset;
        s->last_syt_offset = TICKS_PER_CYCLE;
 
+       /* initialize packet buffer */
+       if (s->direction == AMDTP_IN_STREAM) {
+               dir = DMA_FROM_DEVICE;
+               type = FW_ISO_CONTEXT_RECEIVE;
+               header_size = IN_PACKET_HEADER_SIZE;
+               cb = in_stream_callback;
+       } else {
+               dir = DMA_TO_DEVICE;
+               type = FW_ISO_CONTEXT_TRANSMIT;
+               header_size = OUT_PACKET_HEADER_SIZE;
+               cb = out_stream_callback;
+       }
        err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
-                                     amdtp_stream_get_max_payload(s),
-                                     DMA_TO_DEVICE);
+                                     amdtp_stream_get_max_payload(s), dir);
        if (err < 0)
                goto err_unlock;
 
        s->context = fw_iso_context_create(fw_parent_device(s->unit)->card,
-                                          FW_ISO_CONTEXT_TRANSMIT,
-                                          channel, speed, 0,
-                                          out_stream_callback, s);
+                                          type, channel, speed, header_size,
+                                          cb, s);
        if (IS_ERR(s->context)) {
                err = PTR_ERR(s->context);
                if (err == -EBUSY)
@@ -632,12 +822,17 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
 
        s->packet_index = 0;
        do {
-               err = queue_out_packet(s, 0, true);
+               if (s->direction == AMDTP_IN_STREAM)
+                       err = queue_in_packet(s);
+               else
+                       err = queue_out_packet(s, 0, true);
                if (err < 0)
                        goto err_context;
        } while (s->packet_index > 0);
 
-       err = fw_iso_context_start(s->context, -1, 0, 0);
+       /* NOTE: TAG1 matches CIP. This just affects in stream. */
+       err = fw_iso_context_start(s->context, -1, 0,
+                                  FW_ISO_CONTEXT_MATCH_TAG1);
        if (err < 0)
                goto err_context;
 
index 019134e..c831aaa 100644 (file)
@@ -41,6 +41,8 @@ enum cip_sfc {
        CIP_SFC_COUNT
 };
 
+#define AMDTP_IN_PCM_FORMAT_BITS       SNDRV_PCM_FMTBIT_S32
+
 #define AMDTP_OUT_PCM_FORMAT_BITS      (SNDRV_PCM_FMTBIT_S16 | \
                                         SNDRV_PCM_FMTBIT_S32)