[alsa-devel] [PATCH 4/4] Add handling AMDTP receive stream
Takashi Sakamoto
o-takashi at sakamocchi.jp
Mon Apr 29 11:47:21 CEST 2013
To handle AMDTP receive stream, this patch adds some codes with condition of its
direction and new functions. Once amdtp_stream_init() is executed with its
direction, AMDTP receive and transmit stream can be handles by the same way.
Unfortunatelly Echo Audio's Fireworks(TM) is not fully compliant to IEC 61883-6
against their guide. This patch include some work arounds for Fireworks.
For future implementation of MIDI referring to MMA/AMEI RP-027, this commit add
some variables and functions which are currently useless.
Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
sound/firewire/amdtp.c | 275 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 263 insertions(+), 12 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c
index 59fd34c..136087b 100644
--- a/sound/firewire/amdtp.c
+++ b/sound/firewire/amdtp.c
@@ -56,6 +56,7 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
return -EINVAL;
s->unit = fw_unit_get(unit);
+ s->direction = direction;
s->flags = flags;
s->context = ERR_PTR(-1);
mutex_init(&s->mutex);
@@ -138,7 +139,10 @@ unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
s->data_block_quadlets = s->pcm_channels;
s->data_block_quadlets += DIV_ROUND_UP(s->midi_ports, 8);
- return 8 + max_data_blocks[s->sfc] * 4 * s->data_block_quadlets;
+ if (s->direction == AMDTP_STREAM_RECEIVE)
+ return 8 + s->syt_interval * s->data_block_quadlets * 4;
+ else
+ return 8 + max_data_blocks[s->sfc] * 4 * s->data_block_quadlets;
}
EXPORT_SYMBOL(amdtp_stream_get_max_payload);
@@ -148,6 +152,12 @@ static void amdtp_write_s16(struct amdtp_stream *s,
static void amdtp_write_s32(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames);
+static void amdtp_read_s16(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);
/**
* amdtp_stream_set_pcm_format - set the PCM format
@@ -168,10 +178,16 @@ void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
WARN_ON(1);
/* fall through */
case SNDRV_PCM_FORMAT_S16:
- s->transfer_samples = amdtp_write_s16;
+ if (s->direction == AMDTP_STREAM_RECEIVE)
+ s->transfer_samples = amdtp_read_s16;
+ else
+ s->transfer_samples = amdtp_write_s16;
break;
case SNDRV_PCM_FORMAT_S32:
- s->transfer_samples = amdtp_write_s32;
+ if (s->direction == AMDTP_STREAM_RECEIVE)
+ s->transfer_samples = amdtp_read_s32;
+ else
+ s->transfer_samples = amdtp_write_s32;
break;
}
}
@@ -320,6 +336,58 @@ static void amdtp_write_s16(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, frame_step, i, c;
+ u32 *dst;
+
+ channels = s->pcm_channels;
+ dst = (void *)runtime->dma_area +
+ s->pcm_buffer_pointer * (runtime->frame_bits / 8);
+ remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+ frame_step = s->data_block_quadlets - channels;
+
+ for (i = 0; i < frames; ++i) {
+ for (c = 0; c < channels; ++c) {
+ *dst = be32_to_cpu(*buffer) << 8;
+ dst += 1;
+ buffer += 1;
+ }
+ buffer += frame_step;
+ if (--remaining_frames == 0)
+ dst = (void *)runtime->dma_area;
+ }
+}
+
+static void amdtp_read_s16(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, frame_step, i, c;
+ u16 *dst;
+
+ channels = s->pcm_channels;
+ dst = (void *)runtime->dma_area +
+ s->pcm_buffer_pointer * (runtime->frame_bits / 8);
+ remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+ frame_step = s->data_block_quadlets - channels;
+
+ for (i = 0; i < frames; ++i) {
+ for (c = 0; c < channels; ++c) {
+ *dst = be32_to_cpu(*buffer) << 8;
+ dst += 1;
+ buffer += 1;
+ }
+ buffer += frame_step;
+ if (--remaining_frames == 0)
+ dst = (void *)runtime->dma_area;
+ }
+}
+
static void amdtp_fill_pcm_silence(struct amdtp_stream *s,
__be32 *buffer, unsigned int frames)
{
@@ -342,6 +410,13 @@ static void amdtp_fill_midi(struct amdtp_stream *s,
cpu_to_be32(0x80000000);
}
+static void amdtp_pull_midi(struct amdtp_stream *s,
+ __be32 *buffer, unsigned int frames)
+{
+ /* not implemented yet */
+ return;
+}
+
static void queue_out_packet(struct amdtp_stream *s, unsigned int cycle)
{
__be32 *buffer;
@@ -410,6 +485,117 @@ static void queue_out_packet(struct amdtp_stream *s, unsigned int cycle)
}
}
+static void handle_in_packet_data(struct amdtp_stream *s,
+ unsigned int data_quadlets)
+{
+ __be32 *buffer;
+ u32 cip_header[2];
+ unsigned int index, frames, data_block_quadlets,
+ data_block_counter, ptr;
+ struct snd_pcm_substream *pcm;
+ struct fw_iso_packet packet;
+ int err;
+
+ if (s->packet_index < 0)
+ return;
+ index = s->packet_index;
+
+ buffer = s->buffer.packets[index].buffer;
+ cip_header[0] = be32_to_cpu(buffer[0]);
+ cip_header[1] = be32_to_cpu(buffer[1]);
+
+ /* checking CIP headers for AMDTP with restriction of this module */
+ 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_err(&s->unit->device, "CIP header error: %08X:%08X\n",
+ cip_header[0], cip_header[1]);
+ pcm = NULL;
+ } else if ((data_quadlets < 3) ||
+ ((cip_header[1] & AMDTP_FDF_MASK) == AMDTP_FDF_NO_DATA)) {
+ pcm = NULL;
+ } else {
+ /*
+ * NOTE: this module doesn't check dbc and syt field
+ *
+ * Echo Audio's Fireworks reports wrong number of data block
+ * counter. It always reports it with increment by 8 blocks
+ * even if actual data blocks different from 8.
+ *
+ * Handling syt field is related to "presentation" time stamp,
+ * but ALSA has no implements equivalent to it so this module
+ * don't support it.
+ */
+ data_block_quadlets = (cip_header[0] & AMDTP_DBS_MASK) >>
+ AMDTP_DBS_SHIFT;
+ data_block_counter = cip_header[0] & AMDTP_DBC_MASK;
+
+ /*
+ * NOTE: Echo Audio's Fireworks reports a fixed value for data
+ * block quadlets but the actual value differs depending on
+ * current sampling rate. This is a workaround for Fireworks.
+ */
+ if ((data_quadlets - 2) % data_block_quadlets > 0)
+ s->data_block_quadlets =
+ s->pcm_channels + s->midi_ports;
+ else
+ s->data_block_quadlets = data_block_quadlets;
+
+ /* finish to check CIP header */
+ buffer += 2;
+
+ /*
+ * NOTE: here "frames" is equivalent to "events"
+ * in IEC 61883-1
+ */
+ frames = (data_quadlets - 2) / s->data_block_quadlets;
+ pcm = ACCESS_ONCE(s->pcm);
+ if (pcm)
+ s->transfer_samples(s, pcm, buffer, frames);
+ if (s->midi_ports)
+ amdtp_pull_midi(s, buffer, frames);
+
+ /* for next packet */
+ s->data_block_quadlets = data_block_quadlets;
+ s->data_block_counter = data_block_counter;
+ }
+
+ /* queueing a packet for next cycle */
+ packet.payload_length = amdtp_stream_get_max_payload(s);
+ packet.interrupt = IS_ALIGNED(index + 1, INTERRUPT_INTERVAL);
+ packet.skip = 0;
+ packet.header_length = 4;
+
+ err = fw_iso_context_queue(s->context, &packet, &s->buffer.iso_buffer,
+ s->buffer.packets[index].offset);
+ if (err < 0) {
+ dev_err(&s->unit->device, "queueing error: %d\n", err);
+ s->packet_index = -1;
+ amdtp_stream_pcm_abort(s);
+ return;
+ }
+
+ /* calculate packet index */
+ if (++index >= QUEUE_LENGTH)
+ index = 0;
+ s->packet_index = index;
+
+ /* calculate period and buffer borders */
+ if (pcm != NULL) {
+ ptr = s->pcm_buffer_pointer + frames;
+ if (ptr >= pcm->runtime->buffer_size)
+ ptr -= pcm->runtime->buffer_size;
+ ACCESS_ONCE(s->pcm_buffer_pointer) = ptr;
+
+ s->pcm_period_pointer += frames;
+ if (s->pcm_period_pointer >= pcm->runtime->period_size) {
+ s->pcm_period_pointer -= pcm->runtime->period_size;
+ s->pointer_flush = false;
+ tasklet_hi_schedule(&s->period_tasklet);
+ }
+ }
+}
+
static void pcm_period_tasklet(unsigned long data)
{
struct amdtp_stream *s = (void *)data;
@@ -437,6 +623,50 @@ static void out_packet_callback(struct fw_iso_context *context, u32 cycle,
fw_iso_context_queue_flush(s->context);
}
+static void in_packet_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, data_quadlets, packets = header_length / 4;
+ __be32 *headers = header;
+
+ /* each fields in an isochronous header are already used in juju */
+ for (p = 0; p < packets; p += 1) {
+ /* how many quadlet for data in this packet */
+ data_quadlets =
+ (be32_to_cpu(headers[p]) >> ISO_DATA_LENGTH_SHIFT) / 4;
+ /* handle each packet data */
+ handle_in_packet_data(s, data_quadlets);
+ }
+
+ fw_iso_context_queue_flush(s->context);
+}
+
+static int queue_initial_packets(struct amdtp_stream *s)
+{
+ struct fw_iso_packet initial_packet;
+ unsigned int i;
+ int err;
+
+ /* header length is needed for receive stream */
+ initial_packet.payload_length = amdtp_stream_get_max_payload(s);
+ initial_packet.skip = 0;
+ initial_packet.header_length = 4;
+
+ for (i = 0; i < QUEUE_LENGTH; ++i) {
+ initial_packet.interrupt = IS_ALIGNED(s->packet_index + 1,
+ INTERRUPT_INTERVAL);
+ err = fw_iso_context_queue(s->context, &initial_packet,
+ &s->buffer.iso_buffer, s->buffer.packets[i].offset);
+ if (err < 0)
+ return err;
+ if (++s->packet_index >= QUEUE_LENGTH)
+ s->packet_index = 0;
+ }
+
+ return 0;
+}
+
static int queue_initial_skip_packets(struct amdtp_stream *s)
{
struct fw_iso_packet skip_packet = {
@@ -497,16 +727,31 @@ 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;
- err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
- amdtp_stream_get_max_payload(s),
- DMA_TO_DEVICE);
+ /* initialize packet buffer */
+ if (s->direction == AMDTP_STREAM_RECEIVE)
+ err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
+ amdtp_stream_get_max_payload(s),
+ DMA_FROM_DEVICE);
+ else
+ err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH,
+ amdtp_stream_get_max_payload(s),
+ DMA_TO_DEVICE);
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_packet_callback, s);
+ /* create isochronous context */
+ if (s->direction == AMDTP_STREAM_RECEIVE)
+ s->context =
+ fw_iso_context_create(fw_parent_device(s->unit)->card,
+ FW_ISO_CONTEXT_RECEIVE,
+ channel, speed, 4,
+ in_packet_callback, s);
+ else
+ s->context =
+ fw_iso_context_create(fw_parent_device(s->unit)->card,
+ FW_ISO_CONTEXT_TRANSMIT,
+ channel, speed, 4,
+ out_packet_callback, s);
if (IS_ERR(s->context)) {
err = PTR_ERR(s->context);
if (err == -EBUSY)
@@ -519,11 +764,17 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
s->packet_index = 0;
s->data_block_counter = 0;
- err = queue_initial_skip_packets(s);
+
+ if (s->direction == AMDTP_STREAM_RECEIVE)
+ err = queue_initial_packets(s);
+ else
+ err = queue_initial_skip_packets(s);
if (err < 0)
goto err_context;
- err = fw_iso_context_start(s->context, -1, 0, 0);
+ /* NOTE: TAG1 matches CIP. This just affects receive stream */
+ err = fw_iso_context_start(s->context, -1, 0,
+ FW_ISO_CONTEXT_MATCH_TAG1);
if (err < 0)
goto err_context;
--
1.7.10.4
More information about the Alsa-devel
mailing list