[alsa-devel] [PATCH 00/49 v4] Enhancement for support of Firewire devices
This 49 patchset is to update previous series: [alsa-devel] [PATCH 00/44 v3] Enhancement of support for Firewire devices http://mailman.alsa-project.org/pipermail/alsa-devel/2014-March/074624.html
4 patches are newly added: [PATCH 09/48] dice/firewire-lib: Keep dualwire mode but obsolete CIP_HI_DUALWIRE - Make firewire-lib simpler. [PATCH 10/48] firewire-lib/dice/speakers: Add common PCM constraints for AMDTP streams - To share codes for drivers. Currently no improvement. [PATCH 25/48] fireworks/firewire-lib: Add a quirk to reset data block counter at bus reset [PATCH 46/48] bebob: Add a quirk of data blocks for MIDI messages for some M-Audio devices - New quirks are identified.
For reviewers, you can see previous series in 'request_v3' branch of my remote repository. I hope this will help your work.
---------------------------------------------------------------- The following changes since commit 38137a064199a02630f136ce7bb1580d5d3fedeb:
ALSA: lx_core: Translate comments from french to english (2014-04-18 09:57:03 +0200)
are available in the git repository at:
https://github.com/takaswie/sound.git request_v4
for you to fetch changes up to e851b7740f218d5d5a8e957851b383bde3e28654:
firewire/bebob: Add a workaround for M-Audio special Firewire series (2014-04-25 22:29:32 +0900)
---------------------------------------------------------------- Takashi Sakamoto (49): firewire-lib: Rename functions, structure, member for AMDTP firewire-lib: Add macros instead of fixed value for AMDTP firewire-lib: Add 'direction' member to 'amdtp_stream' structure firewire-lib: Split some codes into functions to reuse for both streams firewire-lib: Add support for AMDTP in-stream and PCM capture firewire-lib: Add support for MIDI capture/playback firewire-lib: Give syt value as parameter to handle_out_packet() firewire-lib: Add support for duplex streams synchronization in blocking mode firewire-lib: Add support for channel mapping dice/firewire-lib: Keep dualwire mode but obsolete CIP_HI_DUALWIRE firewire-lib/dice/speakers: Add common PCM constraints for AMDTP streams firewire-lib: Restrict calling flush_context_completion() when context exists firewire-lib: Rename macros, variables and functions for CMP firewire-lib: Add 'direction' member to 'cmp_connection' structure firewire-lib: Add handling output connection by CMP firewire-lib: Add a new function to check others' connection firewire-lib: Add support for deferred transaction firewire-lib: Add some AV/C general commands fireworks: Add skelton for Fireworks based devices fireworks: Add transaction and some commands fireworks: Add connection and stream management fireworks/firewire-lib: Add a quirk for empty packet with TAG0 fireworks/firewire-lib: Add a quirk for the meaning of dbc fireworks/firewire-lib: Add a quirk for wrong dbs in tx packets fireworks/firewire-lib: Add a quirk for fixed interval of reported dbc fireworks/firewire-lib: Add a quirk to reset data block counter at bus reset fireworks: Add proc interface for debugging purpose fireworks: Add MIDI interface fireworks/firewire-lib: Add a quirk of data blocks for MIDI in out-stream fireworks: Add PCM interface fireworks: Add hwdep interface fireworks: Add command/response functionality into hwdep interface bebob: Add skelton for BeBoB based devices bebob: Add commands and connections/streams management bebob/firewire-lib: Add a quirk for discontinuity at bus reset bebob: Add proc interface for debugging purpose bebob: Add MIDI interface bebob: Add PCM interface bebob: Add hwdep interface bebob: Prepare for device specific operations bebob: Add support for Terratec PHASE, EWS series and Aureon bebob: Add support for Yamaha GO series bebob: Add support for Focusrite Saffire/SaffirePro series bebob: Add support for M-Audio usual Firewire series bebob: Add support for M-Audio special Firewire series bebob/firewire-lib: Add a quirk of wrong dbc in empty packet for M-Audio special Firewire series bebob: Add a quirk of data blocks for MIDI messages for some M-Audio devices bebob: Send a cue to load firmware for M-Audio Firewire series firewire/bebob: Add a workaround for M-Audio special Firewire series
drivers/firewire/core.h | 1 - include/linux/firewire.h | 3 + include/uapi/sound/asound.h | 4 +- include/uapi/sound/firewire.h | 23 +- sound/firewire/Kconfig | 59 ++ sound/firewire/Makefile | 2 + sound/firewire/amdtp.c | 797 +++++++++++------ sound/firewire/amdtp.h | 200 ++++- sound/firewire/bebob/Makefile | 4 + sound/firewire/bebob/bebob.c | 472 ++++++++++ sound/firewire/bebob/bebob.h | 257 ++++++ sound/firewire/bebob/bebob_command.c | 282 ++++++ sound/firewire/bebob/bebob_focusrite.c | 279 ++++++ sound/firewire/bebob/bebob_hwdep.c | 199 +++++ sound/firewire/bebob/bebob_maudio.c | 792 +++++++++++++++++ sound/firewire/bebob/bebob_midi.c | 168 ++++ sound/firewire/bebob/bebob_pcm.c | 378 ++++++++ sound/firewire/bebob/bebob_proc.c | 196 +++++ sound/firewire/bebob/bebob_stream.c | 1020 ++++++++++++++++++++++ sound/firewire/bebob/bebob_terratec.c | 68 ++ sound/firewire/bebob/bebob_yamaha.c | 50 ++ sound/firewire/cmp.c | 203 +++-- sound/firewire/cmp.h | 14 +- sound/firewire/dice.c | 88 +- sound/firewire/fcp.c | 189 +++- sound/firewire/fcp.h | 21 + sound/firewire/fireworks/Makefile | 4 + sound/firewire/fireworks/fireworks.c | 357 ++++++++ sound/firewire/fireworks/fireworks.h | 232 +++++ sound/firewire/fireworks/fireworks_command.c | 372 ++++++++ sound/firewire/fireworks/fireworks_hwdep.c | 298 +++++++ sound/firewire/fireworks/fireworks_midi.c | 168 ++++ sound/firewire/fireworks/fireworks_pcm.c | 403 +++++++++ sound/firewire/fireworks/fireworks_proc.c | 232 +++++ sound/firewire/fireworks/fireworks_stream.c | 372 ++++++++ sound/firewire/fireworks/fireworks_transaction.c | 326 +++++++ sound/firewire/speakers.c | 100 +-- 37 files changed, 8173 insertions(+), 460 deletions(-) create mode 100644 sound/firewire/bebob/Makefile create mode 100644 sound/firewire/bebob/bebob.c create mode 100644 sound/firewire/bebob/bebob.h create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_focusrite.c create mode 100644 sound/firewire/bebob/bebob_hwdep.c create mode 100644 sound/firewire/bebob/bebob_maudio.c create mode 100644 sound/firewire/bebob/bebob_midi.c create mode 100644 sound/firewire/bebob/bebob_pcm.c create mode 100644 sound/firewire/bebob/bebob_proc.c create mode 100644 sound/firewire/bebob/bebob_stream.c create mode 100644 sound/firewire/bebob/bebob_terratec.c create mode 100644 sound/firewire/bebob/bebob_yamaha.c create mode 100644 sound/firewire/fireworks/Makefile create mode 100644 sound/firewire/fireworks/fireworks.c create mode 100644 sound/firewire/fireworks/fireworks.h create mode 100644 sound/firewire/fireworks/fireworks_command.c create mode 100644 sound/firewire/fireworks/fireworks_hwdep.c create mode 100644 sound/firewire/fireworks/fireworks_midi.c create mode 100644 sound/firewire/fireworks/fireworks_pcm.c create mode 100644 sound/firewire/fireworks/fireworks_proc.c create mode 100644 sound/firewire/fireworks/fireworks_stream.c create mode 100644 sound/firewire/fireworks/fireworks_transaction.c
This patch renames some functions, a structure and its member to reuse them in both AMDTP in/out stream.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 154 +++++++++++++++++++++++----------------------- sound/firewire/amdtp.h | 64 ++++++++++--------- sound/firewire/dice.c | 46 +++++++------- sound/firewire/speakers.c | 46 +++++++------- 4 files changed, 158 insertions(+), 152 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 9048777..16fe858 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -34,13 +34,13 @@ static void pcm_period_tasklet(unsigned long data);
/** - * amdtp_out_stream_init - initialize an AMDTP output stream structure - * @s: the AMDTP output stream to initialize + * amdtp_stream_init - initialize an AMDTP stream structure + * @s: the AMDTP stream to initialize * @unit: the target of the stream * @flags: the packet transmission method to use */ -int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, - enum cip_out_flags flags) +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum cip_flags flags) { s->unit = fw_unit_get(unit); s->flags = flags; @@ -51,19 +51,19 @@ int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit,
return 0; } -EXPORT_SYMBOL(amdtp_out_stream_init); +EXPORT_SYMBOL(amdtp_stream_init);
/** - * amdtp_out_stream_destroy - free stream resources - * @s: the AMDTP output stream to destroy + * amdtp_stream_destroy - free stream resources + * @s: the AMDTP stream to destroy */ -void amdtp_out_stream_destroy(struct amdtp_out_stream *s) +void amdtp_stream_destroy(struct amdtp_stream *s) { - WARN_ON(amdtp_out_stream_running(s)); + WARN_ON(amdtp_stream_running(s)); mutex_destroy(&s->mutex); fw_unit_put(s->unit); } -EXPORT_SYMBOL(amdtp_out_stream_destroy); +EXPORT_SYMBOL(amdtp_stream_destroy);
const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { [CIP_SFC_32000] = 8, @@ -77,8 +77,8 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { EXPORT_SYMBOL(amdtp_syt_intervals);
/** - * amdtp_out_stream_set_parameters - set stream parameters - * @s: the AMDTP output stream to configure + * amdtp_stream_set_parameters - set stream parameters + * @s: the AMDTP stream to configure * @rate: the sample rate * @pcm_channels: the number of PCM samples in each data block, to be encoded * as AM824 multi-bit linear audio @@ -87,10 +87,10 @@ EXPORT_SYMBOL(amdtp_syt_intervals); * The parameters must be set before the stream is started, and must not be * changed while the stream is running. */ -void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports) +void amdtp_stream_set_parameters(struct amdtp_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports) { static const unsigned int rates[] = { [CIP_SFC_32000] = 32000, @@ -103,7 +103,7 @@ void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, }; unsigned int sfc;
- if (WARN_ON(amdtp_out_stream_running(s))) + if (WARN_ON(amdtp_stream_running(s))) return;
for (sfc = 0; sfc < CIP_SFC_COUNT; ++sfc) @@ -132,47 +132,47 @@ sfc_found: /* additional buffering needed to adjust for no-data packets */ s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate; } -EXPORT_SYMBOL(amdtp_out_stream_set_parameters); +EXPORT_SYMBOL(amdtp_stream_set_parameters);
/** - * amdtp_out_stream_get_max_payload - get the stream's packet size - * @s: the AMDTP output stream + * amdtp_stream_get_max_payload - get the stream's packet size + * @s: the AMDTP stream * * This function must not be called before the stream has been configured - * with amdtp_out_stream_set_parameters(). + * with amdtp_stream_set_parameters(). */ -unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s) +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) { return 8 + s->syt_interval * s->data_block_quadlets * 4; } -EXPORT_SYMBOL(amdtp_out_stream_get_max_payload); +EXPORT_SYMBOL(amdtp_stream_get_max_payload);
-static void amdtp_write_s16(struct amdtp_out_stream *s, +static void amdtp_write_s16(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); -static void amdtp_write_s32(struct amdtp_out_stream *s, +static void amdtp_write_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); -static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, +static void amdtp_write_s16_dualwire(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); -static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, +static void amdtp_write_s32_dualwire(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames);
/** - * amdtp_out_stream_set_pcm_format - set the PCM format - * @s: the AMDTP output stream to configure + * amdtp_stream_set_pcm_format - set the PCM format + * @s: the AMDTP stream to configure * @format: the format of the ALSA PCM device * * The sample format must be set after the other paramters (rate/PCM channels/ * MIDI) and before the stream is started, and must not be changed while the * stream is running. */ -void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, - snd_pcm_format_t format) +void amdtp_stream_set_pcm_format(struct amdtp_stream *s, + snd_pcm_format_t format) { - if (WARN_ON(amdtp_out_stream_running(s))) + if (WARN_ON(amdtp_stream_running(s))) return;
switch (format) { @@ -193,24 +193,24 @@ void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, break; } } -EXPORT_SYMBOL(amdtp_out_stream_set_pcm_format); +EXPORT_SYMBOL(amdtp_stream_set_pcm_format);
/** - * amdtp_out_stream_pcm_prepare - prepare PCM device for running - * @s: the AMDTP output stream + * amdtp_stream_pcm_prepare - prepare PCM device for running + * @s: the AMDTP stream * * This function should be called from the PCM device's .prepare callback. */ -void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s) +void amdtp_stream_pcm_prepare(struct amdtp_stream *s) { tasklet_kill(&s->period_tasklet); s->pcm_buffer_pointer = 0; s->pcm_period_pointer = 0; s->pointer_flush = true; } -EXPORT_SYMBOL(amdtp_out_stream_pcm_prepare); +EXPORT_SYMBOL(amdtp_stream_pcm_prepare);
-static unsigned int calculate_data_blocks(struct amdtp_out_stream *s) +static unsigned int calculate_data_blocks(struct amdtp_stream *s) { unsigned int phase, data_blocks;
@@ -243,7 +243,7 @@ static unsigned int calculate_data_blocks(struct amdtp_out_stream *s) return data_blocks; }
-static unsigned int calculate_syt(struct amdtp_out_stream *s, +static unsigned int calculate_syt(struct amdtp_stream *s, unsigned int cycle) { unsigned int syt_offset, phase, index, syt; @@ -286,7 +286,7 @@ static unsigned int calculate_syt(struct amdtp_out_stream *s, } }
-static void amdtp_write_s32(struct amdtp_out_stream *s, +static void amdtp_write_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) { @@ -312,7 +312,7 @@ static void amdtp_write_s32(struct amdtp_out_stream *s, } }
-static void amdtp_write_s16(struct amdtp_out_stream *s, +static void amdtp_write_s16(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) { @@ -338,7 +338,7 @@ static void amdtp_write_s16(struct amdtp_out_stream *s, } }
-static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, +static void amdtp_write_s32_dualwire(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) { @@ -369,7 +369,7 @@ static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, } }
-static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, +static void amdtp_write_s16_dualwire(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) { @@ -400,7 +400,7 @@ static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, } }
-static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s, +static void amdtp_fill_pcm_silence(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { unsigned int i, c; @@ -412,7 +412,7 @@ static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s, } }
-static void amdtp_fill_midi(struct amdtp_out_stream *s, +static void amdtp_fill_midi(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { unsigned int i; @@ -422,7 +422,7 @@ static void amdtp_fill_midi(struct amdtp_out_stream *s, cpu_to_be32(0x80000000); }
-static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) +static void queue_out_packet(struct amdtp_stream *s, unsigned int cycle) { __be32 *buffer; unsigned int index, data_blocks, syt, ptr; @@ -473,7 +473,7 @@ static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) if (err < 0) { dev_err(&s->unit->device, "queueing error: %d\n", err); s->packet_index = -1; - amdtp_out_stream_pcm_abort(s); + amdtp_stream_pcm_abort(s); return; }
@@ -501,7 +501,7 @@ static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle)
static void pcm_period_tasklet(unsigned long data) { - struct amdtp_out_stream *s = (void *)data; + struct amdtp_stream *s = (void *)data; struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
if (pcm) @@ -509,9 +509,9 @@ static void pcm_period_tasklet(unsigned long data) }
static void out_packet_callback(struct fw_iso_context *context, u32 cycle, - size_t header_length, void *header, void *data) + size_t header_length, void *header, void *private_data) { - struct amdtp_out_stream *s = data; + struct amdtp_stream *s = private_data; unsigned int i, packets = header_length / 4;
/* @@ -526,7 +526,7 @@ static void out_packet_callback(struct fw_iso_context *context, u32 cycle, fw_iso_context_queue_flush(s->context); }
-static int queue_initial_skip_packets(struct amdtp_out_stream *s) +static int queue_initial_skip_packets(struct amdtp_stream *s) { struct fw_iso_packet skip_packet = { .skip = 1, @@ -548,16 +548,16 @@ static int queue_initial_skip_packets(struct amdtp_out_stream *s) }
/** - * amdtp_out_stream_start - start sending packets - * @s: the AMDTP output stream to start + * amdtp_stream_start - start transferring packets + * @s: the AMDTP stream to start * @channel: the isochronous channel on the bus * @speed: firewire speed code * * The stream cannot be started until it has been configured with - * amdtp_out_stream_set_parameters() and amdtp_out_stream_set_pcm_format(), - * and it must be started before any PCM or MIDI device can be started. + * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI + * device can be started. */ -int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) { static const struct { unsigned int data_block; @@ -575,7 +575,7 @@ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed)
mutex_lock(&s->mutex);
- if (WARN_ON(amdtp_out_stream_running(s) || + if (WARN_ON(amdtp_stream_running(s) || (!s->pcm_channels && !s->midi_ports))) { err = -EBADFD; goto err_unlock; @@ -586,7 +586,7 @@ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) s->last_syt_offset = TICKS_PER_CYCLE;
err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH, - amdtp_out_stream_get_max_payload(s), + amdtp_stream_get_max_payload(s), DMA_TO_DEVICE); if (err < 0) goto err_unlock; @@ -599,11 +599,11 @@ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) err = PTR_ERR(s->context); if (err == -EBUSY) dev_err(&s->unit->device, - "no free output stream on this controller\n"); + "no free stream on this controller\n"); goto err_buffer; }
- amdtp_out_stream_update(s); + amdtp_stream_update(s);
s->packet_index = 0; s->data_block_counter = 0; @@ -629,15 +629,15 @@ err_unlock:
return err; } -EXPORT_SYMBOL(amdtp_out_stream_start); +EXPORT_SYMBOL(amdtp_stream_start);
/** - * amdtp_out_stream_pcm_pointer - get the PCM buffer position - * @s: the AMDTP output stream that transports the PCM data + * amdtp_stream_pcm_pointer - get the PCM buffer position + * @s: the AMDTP stream that transports the PCM data * * Returns the current buffer position, in frames. */ -unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s) +unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s) { /* this optimization is allowed to be racy */ if (s->pointer_flush) @@ -647,31 +647,31 @@ unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s)
return ACCESS_ONCE(s->pcm_buffer_pointer); } -EXPORT_SYMBOL(amdtp_out_stream_pcm_pointer); +EXPORT_SYMBOL(amdtp_stream_pcm_pointer);
/** - * amdtp_out_stream_update - update the stream after a bus reset - * @s: the AMDTP output stream + * amdtp_stream_update - update the stream after a bus reset + * @s: the AMDTP stream */ -void amdtp_out_stream_update(struct amdtp_out_stream *s) +void amdtp_stream_update(struct amdtp_stream *s) { ACCESS_ONCE(s->source_node_id_field) = (fw_parent_device(s->unit)->card->node_id & 0x3f) << 24; } -EXPORT_SYMBOL(amdtp_out_stream_update); +EXPORT_SYMBOL(amdtp_stream_update);
/** - * amdtp_out_stream_stop - stop sending packets - * @s: the AMDTP output stream to stop + * amdtp_stream_stop - stop sending packets + * @s: the AMDTP stream to stop * * All PCM and MIDI devices of the stream must be stopped before the stream * itself can be stopped. */ -void amdtp_out_stream_stop(struct amdtp_out_stream *s) +void amdtp_stream_stop(struct amdtp_stream *s) { mutex_lock(&s->mutex);
- if (!amdtp_out_stream_running(s)) { + if (!amdtp_stream_running(s)) { mutex_unlock(&s->mutex); return; } @@ -684,16 +684,16 @@ void amdtp_out_stream_stop(struct amdtp_out_stream *s)
mutex_unlock(&s->mutex); } -EXPORT_SYMBOL(amdtp_out_stream_stop); +EXPORT_SYMBOL(amdtp_stream_stop);
/** - * amdtp_out_stream_pcm_abort - abort the running PCM device + * amdtp_stream_pcm_abort - abort the running PCM device * @s: the AMDTP stream about to be stopped * * If the isochronous stream needs to be stopped asynchronously, call this * function first to stop the PCM device. */ -void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s) +void amdtp_stream_pcm_abort(struct amdtp_stream *s) { struct snd_pcm_substream *pcm;
@@ -705,4 +705,4 @@ void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s) snd_pcm_stream_unlock_irq(pcm); } } -EXPORT_SYMBOL(amdtp_out_stream_pcm_abort); +EXPORT_SYMBOL(amdtp_stream_pcm_abort); diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 2746ecd..60028c7 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -8,7 +8,7 @@ #include "packets-buffer.h"
/** - * enum cip_out_flags - describes details of the streaming protocol + * enum cip_flags - describes details of the streaming protocol * @CIP_NONBLOCKING: In non-blocking mode, each packet contains * sample_rate/8000 samples, with rounding up or down to adjust * for clock skew and left-over fractional samples. This should @@ -21,7 +21,7 @@ * two samples of a channel are stored consecutively in the packet. * Requires blocking mode and SYT_INTERVAL-aligned PCM buffer size. */ -enum cip_out_flags { +enum cip_flags { CIP_NONBLOCKING = 0x00, CIP_BLOCKING = 0x01, CIP_HI_DUALWIRE = 0x02, @@ -48,9 +48,9 @@ struct fw_unit; struct fw_iso_context; struct snd_pcm_substream;
-struct amdtp_out_stream { +struct amdtp_stream { struct fw_unit *unit; - enum cip_out_flags flags; + enum cip_flags flags; struct fw_iso_context *context; struct mutex mutex;
@@ -59,7 +59,7 @@ struct amdtp_out_stream { unsigned int data_block_quadlets; unsigned int pcm_channels; unsigned int midi_ports; - void (*transfer_samples)(struct amdtp_out_stream *s, + void (*transfer_samples)(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames);
@@ -84,56 +84,62 @@ struct amdtp_out_stream { bool pointer_flush; };
-int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, - enum cip_out_flags flags); -void amdtp_out_stream_destroy(struct amdtp_out_stream *s); +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum cip_flags flags); +void amdtp_stream_destroy(struct amdtp_stream *s);
-void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports); -unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s); +void amdtp_stream_set_parameters(struct amdtp_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports); +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s);
-int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed); -void amdtp_out_stream_update(struct amdtp_out_stream *s); -void amdtp_out_stream_stop(struct amdtp_out_stream *s); +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); +void amdtp_stream_update(struct amdtp_stream *s); +void amdtp_stream_stop(struct amdtp_stream *s);
-void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, - snd_pcm_format_t format); -void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s); -unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s); -void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s); +void amdtp_stream_set_pcm_format(struct amdtp_stream *s, + snd_pcm_format_t format); +void amdtp_stream_pcm_prepare(struct amdtp_stream *s); +unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s); +void amdtp_stream_pcm_abort(struct amdtp_stream *s);
extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
-static inline bool amdtp_out_stream_running(struct amdtp_out_stream *s) +/** + * amdtp_stream_running - check stream is running or not + * @s: the AMDTP stream + * + * If this function returns true, the stream is running. + */ +static inline bool amdtp_stream_running(struct amdtp_stream *s) { return !IS_ERR(s->context); }
/** - * amdtp_out_streaming_error - check for streaming error - * @s: the AMDTP output stream + * amdtp_streaming_error - check for streaming error + * @s: the AMDTP stream * * If this function returns true, the stream's packet queue has stopped due to * an asynchronous error. */ -static inline bool amdtp_out_streaming_error(struct amdtp_out_stream *s) +static inline bool amdtp_streaming_error(struct amdtp_stream *s) { return s->packet_index < 0; }
/** - * amdtp_out_stream_pcm_trigger - start/stop playback from a PCM device - * @s: the AMDTP output stream + * amdtp_stream_pcm_trigger - start/stop playback from a PCM device + * @s: the AMDTP stream * @pcm: the PCM device to be started, or %NULL to stop the current device * * Call this function on a running isochronous stream to enable the actual * transmission of PCM data. This function should be called from the PCM * device's .trigger callback. */ -static inline void amdtp_out_stream_pcm_trigger(struct amdtp_out_stream *s, - struct snd_pcm_substream *pcm) +static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s, + struct snd_pcm_substream *pcm) { ACCESS_ONCE(s->pcm) = pcm; } diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 0c39486..ca12fcb 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -51,7 +51,7 @@ struct dice { wait_queue_head_t hwdep_wait; u32 notification_bits; struct fw_iso_resources resources; - struct amdtp_out_stream stream; + struct amdtp_stream stream; };
MODULE_DESCRIPTION("DICE driver"); @@ -460,17 +460,17 @@ static int dice_stream_start_packets(struct dice *dice) { int err;
- if (amdtp_out_stream_running(&dice->stream)) + if (amdtp_stream_running(&dice->stream)) return 0;
- err = amdtp_out_stream_start(&dice->stream, dice->resources.channel, - fw_parent_device(dice->unit)->max_speed); + err = amdtp_stream_start(&dice->stream, dice->resources.channel, + fw_parent_device(dice->unit)->max_speed); if (err < 0) return err;
err = dice_enable_set(dice); if (err < 0) { - amdtp_out_stream_stop(&dice->stream); + amdtp_stream_stop(&dice->stream); return err; }
@@ -484,7 +484,7 @@ static int dice_stream_start(struct dice *dice)
if (!dice->resources.allocated) { err = fw_iso_resources_allocate(&dice->resources, - amdtp_out_stream_get_max_payload(&dice->stream), + amdtp_stream_get_max_payload(&dice->stream), fw_parent_device(dice->unit)->max_speed); if (err < 0) goto error; @@ -516,9 +516,9 @@ error:
static void dice_stream_stop_packets(struct dice *dice) { - if (amdtp_out_stream_running(&dice->stream)) { + if (amdtp_stream_running(&dice->stream)) { dice_enable_clear(dice); - amdtp_out_stream_stop(&dice->stream); + amdtp_stream_stop(&dice->stream); } }
@@ -581,12 +581,12 @@ static int dice_hw_params(struct snd_pcm_substream *substream, return err;
mode = rate_index_to_mode(rate_index); - amdtp_out_stream_set_parameters(&dice->stream, - params_rate(hw_params), - params_channels(hw_params), - dice->rx_midi_ports[mode]); - amdtp_out_stream_set_pcm_format(&dice->stream, - params_format(hw_params)); + amdtp_stream_set_parameters(&dice->stream, + params_rate(hw_params), + params_channels(hw_params), + dice->rx_midi_ports[mode]); + amdtp_stream_set_pcm_format(&dice->stream, + params_format(hw_params));
return 0; } @@ -609,7 +609,7 @@ static int dice_prepare(struct snd_pcm_substream *substream)
mutex_lock(&dice->mutex);
- if (amdtp_out_streaming_error(&dice->stream)) + if (amdtp_streaming_error(&dice->stream)) dice_stream_stop_packets(dice);
err = dice_stream_start(dice); @@ -620,7 +620,7 @@ static int dice_prepare(struct snd_pcm_substream *substream)
mutex_unlock(&dice->mutex);
- amdtp_out_stream_pcm_prepare(&dice->stream); + amdtp_stream_pcm_prepare(&dice->stream);
return 0; } @@ -640,7 +640,7 @@ static int dice_trigger(struct snd_pcm_substream *substream, int cmd) default: return -EINVAL; } - amdtp_out_stream_pcm_trigger(&dice->stream, pcm); + amdtp_stream_pcm_trigger(&dice->stream, pcm);
return 0; } @@ -649,7 +649,7 @@ static snd_pcm_uframes_t dice_pointer(struct snd_pcm_substream *substream) { struct dice *dice = substream->private_data;
- return amdtp_out_stream_pcm_pointer(&dice->stream); + return amdtp_stream_pcm_pointer(&dice->stream); }
static int dice_create_pcm(struct dice *dice) @@ -1104,7 +1104,7 @@ static void dice_card_free(struct snd_card *card) { struct dice *dice = card->private_data;
- amdtp_out_stream_destroy(&dice->stream); + amdtp_stream_destroy(&dice->stream); fw_core_remove_address_handler(&dice->notification_handler); mutex_destroy(&dice->mutex); } @@ -1360,8 +1360,8 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) goto err_owner; dice->resources.channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_out_stream_init(&dice->stream, unit, - CIP_BLOCKING | CIP_HI_DUALWIRE); + err = amdtp_stream_init(&dice->stream, unit, + CIP_BLOCKING | CIP_HI_DUALWIRE); if (err < 0) goto err_resources;
@@ -1417,7 +1417,7 @@ static void dice_remove(struct fw_unit *unit) { struct dice *dice = dev_get_drvdata(&unit->device);
- amdtp_out_stream_pcm_abort(&dice->stream); + amdtp_stream_pcm_abort(&dice->stream);
snd_card_disconnect(dice->card);
@@ -1443,7 +1443,7 @@ static void dice_bus_reset(struct fw_unit *unit) * to stop so that the application can restart them in an orderly * manner. */ - amdtp_out_stream_pcm_abort(&dice->stream); + amdtp_stream_pcm_abort(&dice->stream);
mutex_lock(&dice->mutex);
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 9f7ef21..2096ad7 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -51,7 +51,7 @@ struct fwspk { const struct device_info *device_info; struct mutex mutex; struct cmp_connection connection; - struct amdtp_out_stream stream; + struct amdtp_stream stream; bool mute; s16 volume[6]; s16 volume_min; @@ -187,8 +187,8 @@ static int fwspk_close(struct snd_pcm_substream *substream)
static void fwspk_stop_stream(struct fwspk *fwspk) { - if (amdtp_out_stream_running(&fwspk->stream)) { - amdtp_out_stream_stop(&fwspk->stream); + if (amdtp_stream_running(&fwspk->stream)) { + amdtp_stream_stop(&fwspk->stream); cmp_connection_break(&fwspk->connection); } } @@ -244,13 +244,13 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, if (err < 0) goto error;
- amdtp_out_stream_set_parameters(&fwspk->stream, - params_rate(hw_params), - params_channels(hw_params), - 0); + amdtp_stream_set_parameters(&fwspk->stream, + params_rate(hw_params), + params_channels(hw_params), + 0);
- amdtp_out_stream_set_pcm_format(&fwspk->stream, - params_format(hw_params)); + amdtp_stream_set_pcm_format(&fwspk->stream, + params_format(hw_params));
err = fwspk_set_rate(fwspk, fwspk->stream.sfc); if (err < 0) @@ -282,25 +282,25 @@ static int fwspk_prepare(struct snd_pcm_substream *substream)
mutex_lock(&fwspk->mutex);
- if (amdtp_out_streaming_error(&fwspk->stream)) + if (amdtp_streaming_error(&fwspk->stream)) fwspk_stop_stream(fwspk);
- if (!amdtp_out_stream_running(&fwspk->stream)) { + if (!amdtp_stream_running(&fwspk->stream)) { err = cmp_connection_establish(&fwspk->connection, - amdtp_out_stream_get_max_payload(&fwspk->stream)); + amdtp_stream_get_max_payload(&fwspk->stream)); if (err < 0) goto err_mutex;
- err = amdtp_out_stream_start(&fwspk->stream, - fwspk->connection.resources.channel, - fwspk->connection.speed); + err = amdtp_stream_start(&fwspk->stream, + fwspk->connection.resources.channel, + fwspk->connection.speed); if (err < 0) goto err_connection; }
mutex_unlock(&fwspk->mutex);
- amdtp_out_stream_pcm_prepare(&fwspk->stream); + amdtp_stream_pcm_prepare(&fwspk->stream);
return 0;
@@ -327,7 +327,7 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd) default: return -EINVAL; } - amdtp_out_stream_pcm_trigger(&fwspk->stream, pcm); + amdtp_stream_pcm_trigger(&fwspk->stream, pcm); return 0; }
@@ -335,7 +335,7 @@ static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream) { struct fwspk *fwspk = substream->private_data;
- return amdtp_out_stream_pcm_pointer(&fwspk->stream); + return amdtp_stream_pcm_pointer(&fwspk->stream); }
static int fwspk_create_pcm(struct fwspk *fwspk) @@ -653,7 +653,7 @@ static void fwspk_card_free(struct snd_card *card) { struct fwspk *fwspk = card->private_data;
- amdtp_out_stream_destroy(&fwspk->stream); + amdtp_stream_destroy(&fwspk->stream); cmp_connection_destroy(&fwspk->connection); fw_unit_put(fwspk->unit); mutex_destroy(&fwspk->mutex); @@ -683,7 +683,7 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_unit;
- err = amdtp_out_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING); + err = amdtp_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING); if (err < 0) goto err_connection;
@@ -733,21 +733,21 @@ static void fwspk_bus_reset(struct fw_unit *unit) fcp_bus_reset(fwspk->unit);
if (cmp_connection_update(&fwspk->connection) < 0) { - amdtp_out_stream_pcm_abort(&fwspk->stream); + amdtp_stream_pcm_abort(&fwspk->stream); mutex_lock(&fwspk->mutex); fwspk_stop_stream(fwspk); mutex_unlock(&fwspk->mutex); return; }
- amdtp_out_stream_update(&fwspk->stream); + amdtp_stream_update(&fwspk->stream); }
static void fwspk_remove(struct fw_unit *unit) { struct fwspk *fwspk = dev_get_drvdata(&unit->device);
- amdtp_out_stream_pcm_abort(&fwspk->stream); + amdtp_stream_pcm_abort(&fwspk->stream); snd_card_disconnect(fwspk->card);
mutex_lock(&fwspk->mutex);
This patch adds some macros instead of fixed value for AMDTP according to IEC 61883-1/6. These macros will also be used by followed patches.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 16fe858..38013f9 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -20,12 +20,28 @@
#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 µs */
+/* isochronous header parameters */ +#define ISO_DATA_LENGTH_SHIFT 16 #define TAG_CIP 1
+/* common isochronous packet header parameters */ #define CIP_EOH (1u << 31) +#define CIP_EOH_MASK 0x80000000 #define CIP_FMT_AM (0x10 << 24) -#define AMDTP_FDF_AM824 (0 << 19) -#define AMDTP_FDF_SFC_SHIFT 16 +#define CIP_FMT_MASK 0x3f000000 +#define CIP_SYT_MASK 0x0000ffff +#define CIP_SYT_NO_INFO 0xffff +#define CIP_FDF_MASK 0x00ff0000 +#define CIP_FDF_SFC_SHIFT 16 + +/* + * Audio and Music transfer protocol specific parameters + * only "Clock-based rate control mode" is supported + */ +#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SFC_SHIFT + 3)) +#define AMDTP_DBS_MASK 0x00ff0000 +#define AMDTP_DBS_SHIFT 16 +#define AMDTP_DBC_MASK 0x000000ff
/* TODO: make these configurable */ #define INTERRUPT_INTERVAL 16 @@ -280,9 +296,9 @@ static unsigned int calculate_syt(struct amdtp_stream *s, syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; syt += syt_offset % TICKS_PER_CYCLE;
- return syt & 0xffff; + return syt & CIP_SYT_MASK; } else { - return 0xffff; /* no info */ + return CIP_SYT_NO_INFO; } }
@@ -438,17 +454,17 @@ static void queue_out_packet(struct amdtp_stream *s, unsigned int cycle) syt = calculate_syt(s, cycle); if (!(s->flags & CIP_BLOCKING)) data_blocks = calculate_data_blocks(s); - else if (syt != 0xffff) + else if (syt != CIP_SYT_NO_INFO) data_blocks = s->syt_interval; else data_blocks = 0;
buffer = s->buffer.packets[index].buffer; buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | - (s->data_block_quadlets << 16) | + (s->data_block_quadlets << AMDTP_DBS_SHIFT) | s->data_block_counter); buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 | - (s->sfc << AMDTP_FDF_SFC_SHIFT) | syt); + (s->sfc << CIP_FDF_SFC_SHIFT) | syt); buffer += 2;
pcm = ACCESS_ONCE(s->pcm);
This patch adds 'direction' member to amdtp_stream structure to indicate its direction. This patch also adds 'direction' argument to amdtp_stream_init() function to determine its direction.
The amdtp_stream_init() function is exported and used by firewire-speakers and dice so this patch also affects them.
This patch just add them. Actual implementation will be done by followed patches.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 4 +++- sound/firewire/amdtp.h | 7 +++++++ sound/firewire/dice.c | 2 +- sound/firewire/speakers.c | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 38013f9..95c5a15 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -53,12 +53,14 @@ static void pcm_period_tasklet(unsigned long data); * amdtp_stream_init - initialize an AMDTP stream structure * @s: the AMDTP stream to initialize * @unit: the target of the stream + * @dir: the direction of stream * @flags: the packet transmission method to use */ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, - enum cip_flags flags) + enum amdtp_stream_direction dir, enum cip_flags flags) { s->unit = fw_unit_get(unit); + s->direction = dir; s->flags = flags; s->context = ERR_PTR(-1); mutex_init(&s->mutex); diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 60028c7..019134e 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -48,9 +48,15 @@ struct fw_unit; struct fw_iso_context; struct snd_pcm_substream;
+enum amdtp_stream_direction { + AMDTP_OUT_STREAM = 0, + AMDTP_IN_STREAM +}; + struct amdtp_stream { struct fw_unit *unit; enum cip_flags flags; + enum amdtp_stream_direction direction; struct fw_iso_context *context; struct mutex mutex;
@@ -85,6 +91,7 @@ struct amdtp_stream { };
int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags); void amdtp_stream_destroy(struct amdtp_stream *s);
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index ca12fcb..26b2158 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -1360,7 +1360,7 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) goto err_owner; dice->resources.channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_stream_init(&dice->stream, unit, + err = amdtp_stream_init(&dice->stream, unit, AMDTP_OUT_STREAM, CIP_BLOCKING | CIP_HI_DUALWIRE); if (err < 0) goto err_resources; diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 2096ad7..c07e7cd 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -683,7 +683,8 @@ static int fwspk_probe(struct fw_unit *unit, if (err < 0) goto err_unit;
- err = amdtp_stream_init(&fwspk->stream, unit, CIP_NONBLOCKING); + err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM, + CIP_NONBLOCKING); if (err < 0) goto err_connection;
Some codes can be reused to handle in-stream. This commit adds new functions. This commit also renames some functions to keep naming consistency.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 154 ++++++++++++++++++++++++++----------------------- 1 file changed, 81 insertions(+), 73 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 95c5a15..790aa86 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -47,6 +47,8 @@ #define INTERRUPT_INTERVAL 16 #define QUEUE_LENGTH 48
+#define OUT_PACKET_HEADER_SIZE 0 + static void pcm_period_tasklet(unsigned long data);
/** @@ -440,13 +442,73 @@ static void amdtp_fill_midi(struct amdtp_stream *s, cpu_to_be32(0x80000000); }
-static void queue_out_packet(struct amdtp_stream *s, unsigned int cycle) +static void update_pcm_pointers(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + unsigned int frames) +{ unsigned int ptr; + + if (s->dual_wire) + frames *= 2; + + 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; + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + + if (pcm) + snd_pcm_period_elapsed(pcm); +} + +static int queue_packet(struct amdtp_stream *s, + unsigned int header_length, + unsigned int payload_length, bool skip) +{ + struct fw_iso_packet p = {0}; + int err; + + p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); + p.tag = TAG_CIP; + p.header_length = header_length; + p.payload_length = (!skip) ? payload_length : 0; + p.skip = skip; + err = fw_iso_context_queue(s->context, &p, &s->buffer.iso_buffer, + s->buffer.packets[s->packet_index].offset); + if (err < 0) { + dev_err(&s->unit->device, "queueing error: %d\n", err); + goto end; + } + + if (++s->packet_index >= QUEUE_LENGTH) + s->packet_index = 0; +end: + return err; +} + +static inline int queue_out_packet(struct amdtp_stream *s, + unsigned int payload_length, bool skip) +{ + return queue_packet(s, OUT_PACKET_HEADER_SIZE, + payload_length, skip); +} + +static void handle_out_packet(struct amdtp_stream *s, unsigned int cycle) { __be32 *buffer; - unsigned int index, data_blocks, syt, ptr; + unsigned int index, data_blocks, syt, payload_length; struct snd_pcm_substream *pcm; - struct fw_iso_packet packet; - int err;
if (s->packet_index < 0) return; @@ -479,55 +541,20 @@ static void queue_out_packet(struct amdtp_stream *s, unsigned int cycle)
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
- packet.payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; - packet.interrupt = IS_ALIGNED(index + 1, INTERRUPT_INTERVAL); - packet.skip = 0; - packet.tag = TAG_CIP; - packet.sy = 0; - packet.header_length = 0; - - 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); + payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; + if (queue_out_packet(s, payload_length, false) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; }
- if (++index >= QUEUE_LENGTH) - index = 0; - s->packet_index = index; - - if (pcm) { - if (s->dual_wire) - data_blocks *= 2; - - ptr = s->pcm_buffer_pointer + data_blocks; - if (ptr >= pcm->runtime->buffer_size) - ptr -= pcm->runtime->buffer_size; - ACCESS_ONCE(s->pcm_buffer_pointer) = ptr; - - s->pcm_period_pointer += data_blocks; - 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; - struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); - if (pcm) - snd_pcm_period_elapsed(pcm); + update_pcm_pointers(s, pcm, data_blocks); }
-static void out_packet_callback(struct fw_iso_context *context, u32 cycle, - size_t header_length, void *header, void *private_data) +static void out_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 i, packets = header_length / 4; @@ -540,31 +567,10 @@ static void out_packet_callback(struct fw_iso_context *context, u32 cycle, cycle += QUEUE_LENGTH - packets;
for (i = 0; i < packets; ++i) - queue_out_packet(s, ++cycle); + handle_out_packet(s, ++cycle); fw_iso_context_queue_flush(s->context); }
-static int queue_initial_skip_packets(struct amdtp_stream *s) -{ - struct fw_iso_packet skip_packet = { - .skip = 1, - }; - unsigned int i; - int err; - - for (i = 0; i < QUEUE_LENGTH; ++i) { - skip_packet.interrupt = IS_ALIGNED(s->packet_index + 1, - INTERRUPT_INTERVAL); - err = fw_iso_context_queue(s->context, &skip_packet, NULL, 0); - if (err < 0) - return err; - if (++s->packet_index >= QUEUE_LENGTH) - s->packet_index = 0; - } - - return 0; -} - /** * amdtp_stream_start - start transferring packets * @s: the AMDTP stream to start @@ -594,11 +600,12 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) mutex_lock(&s->mutex);
if (WARN_ON(amdtp_stream_running(s) || - (!s->pcm_channels && !s->midi_ports))) { + (s->data_block_quadlets < 1))) { err = -EBADFD; goto err_unlock; }
+ s->data_block_counter = 0; s->data_block_state = initial_state[s->sfc].data_block; s->syt_offset_state = initial_state[s->sfc].syt_offset; s->last_syt_offset = TICKS_PER_CYCLE; @@ -612,7 +619,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, FW_ISO_CONTEXT_TRANSMIT, channel, speed, 0, - out_packet_callback, s); + out_stream_callback, s); if (IS_ERR(s->context)) { err = PTR_ERR(s->context); if (err == -EBUSY) @@ -624,10 +631,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) amdtp_stream_update(s);
s->packet_index = 0; - s->data_block_counter = 0; - err = queue_initial_skip_packets(s); - if (err < 0) - goto err_context; + do { + 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); if (err < 0)
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 --- sound/firewire/amdtp.c | 229 +++++++++++++++++++++++++++++++++++++++++++++---- sound/firewire/amdtp.h | 2 + 2 files changed, 214 insertions(+), 17 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 790aa86..be1aabc 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -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;
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 019134e..c831aaa 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -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)
For capturing/playbacking MIDI messages, this commit adds one MIDI conformant data channel. This data channel has multiplexed 8 MIDI data streams. So this data channel can transfer messages from/to 8 MIDI ports.
And this commit allows to set PCM format even if AMDTP streams already start. I suppose the case that PCM substreams are going to be joined into AMDTP streams when AMDTP streams are already started for MIDI substreams. Each driver must count how many PCM/MIDI substreams use AMDTP streams to stop AMDTP streams.
There are differences between specifications about MIDI conformant data.
About the multiplexing, IEC 61883-6:2002, itself, has no information. It describes labels and bytes for MIDI messages and refers to MMA/AMEI RP-027 for 'successfull implementation'. MMA/AMEI RP-027 describes 8 MPX-MIDI data streams for one MIDI conformant data channel. IEC 61883-6:2005 adds 'sequence multiplexing' and apply this way and describe incompatibility between 2002 and 2005.
So this commit applies IEC 61883-6:2005. When we find some devices compliant to IEC 61883-6:2002, then this difference should be handles as device quirk in additional work.
About the number of bytes in an MIDI conformant data, IEC 61883-6:2002 describe 0,1,2,3 bytes. MMA/AMEI RP-027 describes 'MIDI1.0-1x-SPEED', 'MIDI1.0-2x-SPEED', 'MIDI1.0-3x-SPEED' modes and the maximum bytes for each mode corresponds to 1, 2, 3 bytes. The 'MIDI1.0-2x/3x-SPEED' modes are accompanied with 'negotiation procedure' and 'encapsulation details' but there is no specifications for them.
So this commit implements 'MIDI1.0-1x-SPEED' mode for playback, but allows to pick up 1-3 bytes for capturing.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 53 ++++++++++++++++++++++++++++++++++++++++++-------- sound/firewire/amdtp.h | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index be1aabc..a4553ec 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/slab.h> #include <sound/pcm.h> +#include <sound/rawmidi.h> #include "amdtp.h"
#define TICKS_PER_CYCLE 3072 @@ -123,9 +124,12 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s, [CIP_SFC_176400] = 176400, [CIP_SFC_192000] = 192000, }; - unsigned int sfc; + unsigned int sfc, midi_channels;
- if (WARN_ON(amdtp_stream_running(s))) + midi_channels = DIV_ROUND_UP(midi_ports, 8); + + if (WARN_ON(amdtp_stream_running(s)) || + WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI)) return;
for (sfc = 0; sfc < CIP_SFC_COUNT; ++sfc) @@ -142,7 +146,7 @@ sfc_found: pcm_channels *= 2; } s->sfc = sfc; - s->data_block_quadlets = pcm_channels + DIV_ROUND_UP(midi_ports, 8); + s->data_block_quadlets = pcm_channels + midi_channels; s->pcm_channels = pcm_channels; s->midi_ports = midi_ports;
@@ -200,7 +204,7 @@ static void amdtp_read_s32_dualwire(struct amdtp_stream *s, void amdtp_stream_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) { - if (WARN_ON(amdtp_stream_running(s))) + if (WARN_ON(amdtp_stream_pcm_running(s))) return;
switch (format) { @@ -507,11 +511,41 @@ static void amdtp_fill_pcm_silence(struct amdtp_stream *s, static void amdtp_fill_midi(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { - unsigned int i; + unsigned int f, port; + u8 *b; + + for (f = 0; f < frames; f++) { + buffer[s->pcm_channels + 1] = 0; + b = (u8 *)&buffer[s->pcm_channels + 1]; + + port = (s->data_block_counter + f) % 8; + if ((s->midi[port] == NULL) || + (snd_rawmidi_transmit(s->midi[port], b + 1, 1) <= 0)) + b[0] = 0x80; + else + b[0] = 0x81; + + buffer += s->data_block_quadlets; + } +} + +static void amdtp_pull_midi(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + unsigned int f, port; + int len; + u8 *b; + + for (f = 0; f < frames; f++) { + port = (s->data_block_counter + f) % 8; + b = (u8 *)&buffer[s->pcm_channels + 1];
- for (i = 0; i < frames; ++i) - buffer[s->pcm_channels + i * s->data_block_quadlets] = - cpu_to_be32(0x80000000); + len = b[0] - 0x80; + if ((1 <= len) && (len <= 3) && (s->midi[port])) + snd_rawmidi_receive(s->midi[port], b + 1, len); + + buffer += s->data_block_quadlets; + } }
static void update_pcm_pointers(struct amdtp_stream *s, @@ -688,6 +722,9 @@ static void handle_in_packet(struct amdtp_stream *s, pcm = ACCESS_ONCE(s->pcm); if (pcm) s->transfer_samples(s, pcm, buffer, data_blocks); + + if (s->midi_ports) + amdtp_pull_midi(s, buffer, data_blocks); }
s->data_block_counter = (data_block_counter + data_blocks) & 0xff; diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index c831aaa..098187d 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -46,9 +46,21 @@ enum cip_sfc { #define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \ SNDRV_PCM_FMTBIT_S32)
+ +/* + * AMDTP packet can include channels for MIDI conformant data. + * Each MIDI conformant data channel includes 8 MPX-MIDI data stream. + * Each MPX-MIDI data stream includes one data stream from/to MIDI ports. + * + * This module supports maximum 1 MIDI conformant data channels. + * Then this AMDTP packets can transfer maximum 8 MIDI data streams. + */ +#define AMDTP_MAX_CHANNELS_FOR_MIDI 1 + struct fw_unit; struct fw_iso_context; struct snd_pcm_substream; +struct snd_rawmidi_substream;
enum amdtp_stream_direction { AMDTP_OUT_STREAM = 0, @@ -90,6 +102,8 @@ struct amdtp_stream { unsigned int pcm_buffer_pointer; unsigned int pcm_period_pointer; bool pointer_flush; + + struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; };
int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, @@ -139,6 +153,17 @@ static inline bool amdtp_streaming_error(struct amdtp_stream *s) }
/** + * amdtp_stream_pcm_running - check PCM substream is running or not + * @s: the AMDTP stream + * + * If this function returns true, PCM substream in the AMDTP stream is running. + */ +static inline bool amdtp_stream_pcm_running(struct amdtp_stream *s) +{ + return !!s->pcm; +} + +/** * amdtp_stream_pcm_trigger - start/stop playback from a PCM device * @s: the AMDTP stream * @pcm: the PCM device to be started, or %NULL to stop the current device @@ -153,6 +178,24 @@ static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s, ACCESS_ONCE(s->pcm) = pcm; }
+/** + * amdtp_stream_midi_trigger - start/stop playback/capture with a MIDI device + * @s: the AMDTP stream + * @port: index of MIDI port + * @midi: the MIDI device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of MIDI data. This function should be called from the MIDI + * device's .trigger callback. + */ +static inline void amdtp_stream_midi_trigger(struct amdtp_stream *s, + unsigned int port, + struct snd_rawmidi_substream *midi) +{ + if (port < s->midi_ports) + ACCESS_ONCE(s->midi[port]) = midi; +} + static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc) { return sfc & 1;
For duplex streams with synchronization, drivers should pick up 'presentation timestamp' from in-packets and use the timestamp for out-packets. This commit is preparation for this.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index a4553ec..24e9a96 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -257,7 +257,9 @@ static unsigned int calculate_data_blocks(struct amdtp_stream *s) { unsigned int phase, data_blocks;
- if (!cip_sfc_is_base_44100(s->sfc)) { + if (s->flags & CIP_BLOCKING) + data_blocks = s->syt_interval; + else if (!cip_sfc_is_base_44100(s->sfc)) { /* Sample_rate / 8000 is an integer, and precomputed. */ data_blocks = s->data_block_state; } else { @@ -616,26 +618,22 @@ static inline int queue_in_packet(struct amdtp_stream *s) amdtp_stream_get_max_payload(s), false); }
-static void handle_out_packet(struct amdtp_stream *s, unsigned int cycle) +static void handle_out_packet(struct amdtp_stream *s, unsigned int syt) { __be32 *buffer; - unsigned int index, data_blocks, syt, payload_length; + unsigned int data_blocks, payload_length; struct snd_pcm_substream *pcm;
if (s->packet_index < 0) return; - index = s->packet_index;
/* this module generate empty packet for 'no data' */ - syt = calculate_syt(s, cycle); - if (!(s->flags & CIP_BLOCKING)) + if (!(s->flags & CIP_BLOCKING) || (syt != CIP_SYT_NO_INFO)) data_blocks = calculate_data_blocks(s); - else if (syt != CIP_SYT_NO_INFO) - data_blocks = s->syt_interval; else data_blocks = 0;
- buffer = s->buffer.packets[index].buffer; + buffer = s->buffer.packets[s->packet_index].buffer; buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << AMDTP_DBS_SHIFT) | s->data_block_counter); @@ -746,7 +744,7 @@ static void out_stream_callback(struct fw_iso_context *context, u32 cycle, void *private_data) { struct amdtp_stream *s = private_data; - unsigned int i, packets = header_length / 4; + unsigned int i, syt, packets = header_length / 4;
/* * Compute the cycle of the last queued packet. @@ -755,8 +753,10 @@ static void out_stream_callback(struct fw_iso_context *context, u32 cycle, */ cycle += QUEUE_LENGTH - packets;
- for (i = 0; i < packets; ++i) - handle_out_packet(s, ++cycle); + for (i = 0; i < packets; ++i) { + syt = calculate_syt(s, ++cycle); + handle_out_packet(s, syt); + } fw_iso_context_queue_flush(s->context); }
Generally, the devices can synchronize to handle 'presentation timestamp' in CIP packets. This commit adds functionality to pick up this timestamp from in-packets transmitted by the device, then use it for out packets.
In current implementation, this module generated the timestamp by itself. This is 'SYT Match' mode. Then drivers with this module acts as synchronization master. This commit allows this module to act as synchronization slave.
This commit restricts this mechanism is only available in blocking mode because handling the timestamp in non-blocking mode is more complicated than in blocking mode.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 75 +++++++++++++++++++++++++++++++++++++++++++++----- sound/firewire/amdtp.h | 45 ++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 10 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 24e9a96..8498155 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -11,6 +11,7 @@ #include <linux/firewire.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/sched.h> #include <sound/pcm.h> #include <sound/rawmidi.h> #include "amdtp.h" @@ -72,6 +73,10 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, tasklet_init(&s->period_tasklet, pcm_period_tasklet, (unsigned long)s); s->packet_index = 0;
+ init_waitqueue_head(&s->callback_wait); + s->callbacked = false; + s->sync_slave = NULL; + return 0; } EXPORT_SYMBOL(amdtp_stream_init); @@ -585,7 +590,10 @@ static int queue_packet(struct amdtp_stream *s, unsigned int payload_length, bool skip) { struct fw_iso_packet p = {0}; - int err; + int err = 0; + + if (IS_ERR(s->context)) + goto end;
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); p.tag = TAG_CIP; @@ -765,7 +773,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle, void *private_data) { struct amdtp_stream *s = private_data; - unsigned int p, packets, payload_quadlets; + unsigned int p, syt, packets, payload_quadlets; __be32 *buffer, *headers = header;
/* The number of packets in buffer */ @@ -773,18 +781,71 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
for (p = 0; p < packets; p++) { if (s->packet_index < 0) - return; + break; + buffer = s->buffer.packets[s->packet_index].buffer;
+ /* Process sync slave stream */ + if (s->sync_slave && s->sync_slave->callbacked) { + syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK; + handle_out_packet(s->sync_slave, syt); + } + /* 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); }
+ /* Queueing error or detecting discontinuity */ + if (s->packet_index < 0) { + /* Abort sync slave. */ + if (s->sync_slave) { + s->sync_slave->packet_index = -1; + amdtp_stream_pcm_abort(s->sync_slave); + } + return; + } + + /* when sync to device, flush the packets for slave stream */ + if (s->sync_slave && s->sync_slave->callbacked) + fw_iso_context_queue_flush(s->sync_slave->context); + fw_iso_context_queue_flush(s->context); }
+/* processing is done by master callback */ +static void slave_stream_callback(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, + void *private_data) +{ + return; +} + +/* this is executed one time */ +static void amdtp_stream_first_callback(struct fw_iso_context *context, + u32 cycle, size_t header_length, + void *header, void *private_data) +{ + struct amdtp_stream *s = private_data; + + /* + * For in-stream, first packet has come. + * For out-stream, prepared to transmit first packet + */ + s->callbacked = true; + wake_up(&s->callback_wait); + + if (s->direction == AMDTP_IN_STREAM) + context->callback.sc = in_stream_callback; + else if ((s->flags & CIP_BLOCKING) && (s->flags & CIP_SYNC_TO_DEVICE)) + context->callback.sc = slave_stream_callback; + else + context->callback.sc = out_stream_callback; + + context->callback.sc(context, cycle, header_length, header, s); +} + /** * amdtp_stream_start - start transferring packets * @s: the AMDTP stream to start @@ -811,7 +872,6 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) }; unsigned int header_size; enum dma_data_direction dir; - fw_iso_callback_t cb; int type, err;
mutex_lock(&s->mutex); @@ -832,12 +892,10 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) 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), dir); @@ -846,7 +904,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, type, channel, speed, header_size, - cb, s); + amdtp_stream_first_callback, s); if (IS_ERR(s->context)) { err = PTR_ERR(s->context); if (err == -EBUSY) @@ -868,6 +926,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) } while (s->packet_index > 0);
/* NOTE: TAG1 matches CIP. This just affects in stream. */ + s->callbacked = false; err = fw_iso_context_start(s->context, -1, 0, FW_ISO_CONTEXT_MATCH_TAG1); if (err < 0) @@ -940,6 +999,8 @@ void amdtp_stream_stop(struct amdtp_stream *s) s->context = ERR_PTR(-1); iso_packets_buffer_destroy(&s->buffer, s->unit);
+ s->callbacked = false; + mutex_unlock(&s->mutex); } EXPORT_SYMBOL(amdtp_stream_stop); diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 098187d..2bd3b27 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -20,11 +20,14 @@ * at half the actual sample rate with twice the number of channels; * two samples of a channel are stored consecutively in the packet. * Requires blocking mode and SYT_INTERVAL-aligned PCM buffer size. + * @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is + * generated by in packets. Defaultly this driver generates timestamp. */ enum cip_flags { - CIP_NONBLOCKING = 0x00, - CIP_BLOCKING = 0x01, - CIP_HI_DUALWIRE = 0x02, + CIP_NONBLOCKING = 0x00, + CIP_BLOCKING = 0x01, + CIP_HI_DUALWIRE = 0x02, + CIP_SYNC_TO_DEVICE = 0x04, };
/** @@ -104,6 +107,10 @@ struct amdtp_stream { bool pointer_flush;
struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; + + bool callbacked; + wait_queue_head_t callback_wait; + struct amdtp_stream *sync_slave; };
int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, @@ -201,4 +208,36 @@ static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc) return sfc & 1; }
+static inline void amdtp_stream_set_sync(enum cip_flags sync_mode, + struct amdtp_stream *master, + struct amdtp_stream *slave) +{ + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master->flags |= CIP_SYNC_TO_DEVICE; + slave->flags |= CIP_SYNC_TO_DEVICE; + master->sync_slave = slave; + } else { + master->flags &= ~CIP_SYNC_TO_DEVICE; + slave->flags &= ~CIP_SYNC_TO_DEVICE; + master->sync_slave = NULL; + } + + slave->sync_slave = NULL; +} + +/** + * amdtp_stream_wait_callback - sleep till callbacked or timeout + * @s: the AMDTP stream + * @timeout: msec till timeout + * + * If this function return false, the AMDTP stream should be stopped. + */ +static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s, + unsigned int timeout) +{ + return wait_event_timeout(s->callback_wait, + s->callbacked == true, + msecs_to_jiffies(timeout)) > 0; +} + #endif
Some devices arrange the position of PCM/MIDI data channel in AMDTP packet. This commit allows drivers to set channel mapping.
To be simple, the mapping table is an array with fixed length. Then the number of channels for PCM is restricted by 64 channels.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 118 +++++++++++++++++++++++++++++-------------------- sound/firewire/amdtp.h | 8 ++++ 2 files changed, 79 insertions(+), 47 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 8498155..9cf81b2 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -129,11 +129,12 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s, [CIP_SFC_176400] = 176400, [CIP_SFC_192000] = 192000, }; - unsigned int sfc, midi_channels; + unsigned int i, sfc, midi_channels;
midi_channels = DIV_ROUND_UP(midi_ports, 8);
- if (WARN_ON(amdtp_stream_running(s)) || + if (WARN_ON(amdtp_stream_running(s)) | + WARN_ON(pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM) | WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI)) return;
@@ -148,11 +149,12 @@ sfc_found: if (s->dual_wire) { sfc -= 2; rate /= 2; - pcm_channels *= 2; + s->pcm_channels = pcm_channels * 2; + } else { + s->pcm_channels = pcm_channels; } s->sfc = sfc; - s->data_block_quadlets = pcm_channels + midi_channels; - s->pcm_channels = pcm_channels; + s->data_block_quadlets = s->pcm_channels + midi_channels; s->midi_ports = midi_ports;
s->syt_interval = amdtp_syt_intervals[sfc]; @@ -162,6 +164,11 @@ sfc_found: if (s->flags & CIP_BLOCKING) /* additional buffering needed to adjust for no-data packets */ s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate; + + /* init the position map for PCM and MIDI channels */ + for (i = 0; i < pcm_channels; i++) + s->pcm_positions[i] = i; + s->midi_position = s->pcm_channels; } EXPORT_SYMBOL(amdtp_stream_set_parameters);
@@ -341,22 +348,21 @@ static void amdtp_write_s32(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, frame_step, i, c; + unsigned int channels, remaining_frames, i, c; const u32 *src;
channels = s->pcm_channels; src = (void *)runtime->dma_area + frames_to_bytes(runtime, s->pcm_buffer_pointer); 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) { - *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + buffer[s->pcm_positions[c]] = + cpu_to_be32((*src >> 8) | 0x40000000); src++; - buffer++; } - buffer += frame_step; + buffer += s->data_block_quadlets; if (--remaining_frames == 0) src = (void *)runtime->dma_area; } @@ -367,22 +373,21 @@ static void amdtp_write_s16(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, frame_step, i, c; + unsigned int channels, remaining_frames, i, c; const u16 *src;
channels = s->pcm_channels; src = (void *)runtime->dma_area + frames_to_bytes(runtime, s->pcm_buffer_pointer); 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) { - *buffer = cpu_to_be32((*src << 8) | 0x40000000); + buffer[s->pcm_positions[c]] = + cpu_to_be32((*src << 8) | 0x40000000); src++; - buffer++; } - buffer += frame_step; + buffer += s->data_block_quadlets; if (--remaining_frames == 0) src = (void *)runtime->dma_area; } @@ -393,29 +398,29 @@ static void amdtp_write_s32_dualwire(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, frame_adjust_1, frame_adjust_2, i, c; + unsigned int channels, remaining_frames, i, c; const u32 *src;
- channels = s->pcm_channels; src = (void *)runtime->dma_area + - s->pcm_buffer_pointer * (runtime->frame_bits / 8); - frame_adjust_1 = channels - 1; - frame_adjust_2 = 1 - (s->data_block_quadlets - channels); + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + channels = s->pcm_channels / 2;
- channels /= 2; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + buffer[s->pcm_positions[c] * 2] = + cpu_to_be32((*src >> 8) | 0x40000000); src++; - buffer += 2; } - buffer -= frame_adjust_1; + buffer += 1; for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + buffer[s->pcm_positions[c] * 2] = + cpu_to_be32((*src >> 8) | 0x40000000); src++; - buffer += 2; } - buffer -= frame_adjust_2; + buffer += s->data_block_quadlets - 1; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; } }
@@ -424,29 +429,29 @@ static void amdtp_write_s16_dualwire(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, frame_adjust_1, frame_adjust_2, i, c; + unsigned int channels, remaining_frames, i, c; const u16 *src;
- channels = s->pcm_channels; src = (void *)runtime->dma_area + - s->pcm_buffer_pointer * (runtime->frame_bits / 8); - frame_adjust_1 = channels - 1; - frame_adjust_2 = 1 - (s->data_block_quadlets - channels); + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + channels = s->pcm_channels / 2;
- channels /= 2; for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src << 8) | 0x40000000); + buffer[s->pcm_positions[c] * 2] = + cpu_to_be32((*src << 8) | 0x40000000); src++; - buffer += 2; } - buffer -= frame_adjust_1; + buffer += 1; for (c = 0; c < channels; ++c) { - *buffer = cpu_to_be32((*src << 8) | 0x40000000); + buffer[s->pcm_positions[c] * 2] = + cpu_to_be32((*src << 8) | 0x40000000); src++; - buffer += 2; } - buffer -= frame_adjust_2; + buffer += s->data_block_quadlets - 1; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; } }
@@ -465,7 +470,7 @@ static void amdtp_read_s32(struct amdtp_stream *s,
for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *dst = be32_to_cpu(buffer[c]) << 8; + *dst = be32_to_cpu(buffer[s->pcm_positions[c]]) << 8; dst++; } buffer += s->data_block_quadlets; @@ -489,12 +494,14 @@ static void amdtp_read_s32_dualwire(struct amdtp_stream *s,
for (i = 0; i < frames; ++i) { for (c = 0; c < channels; ++c) { - *dst = be32_to_cpu(buffer[c * 2]) << 8; + *dst = + be32_to_cpu(buffer[s->pcm_positions[c] * 2]) << 8; dst++; } buffer += 1; for (c = 0; c < channels; ++c) { - *dst = be32_to_cpu(buffer[c * 2]) << 8; + *dst = + be32_to_cpu(buffer[s->pcm_positions[c] * 2]) << 8; dst++; } buffer += s->data_block_quadlets - 1; @@ -510,11 +517,26 @@ static void amdtp_fill_pcm_silence(struct amdtp_stream *s,
for (i = 0; i < frames; ++i) { for (c = 0; c < s->pcm_channels; ++c) - buffer[c] = cpu_to_be32(0x40000000); + buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000); buffer += s->data_block_quadlets; } }
+static void amdtp_fill_pcm_silence_dualwire(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + unsigned int i, c, channels; + + channels = s->pcm_channels / 2; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[s->pcm_positions[c] * 2] = + buffer[s->pcm_positions[c] * 2 + 1] = + cpu_to_be32(0x40000000); + } + buffer += s->data_block_quadlets; + } +} static void amdtp_fill_midi(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { @@ -522,8 +544,8 @@ static void amdtp_fill_midi(struct amdtp_stream *s, u8 *b;
for (f = 0; f < frames; f++) { - buffer[s->pcm_channels + 1] = 0; - b = (u8 *)&buffer[s->pcm_channels + 1]; + buffer[s->midi_position] = 0; + b = (u8 *)&buffer[s->midi_position];
port = (s->data_block_counter + f) % 8; if ((s->midi[port] == NULL) || @@ -545,7 +567,7 @@ static void amdtp_pull_midi(struct amdtp_stream *s,
for (f = 0; f < frames; f++) { port = (s->data_block_counter + f) % 8; - b = (u8 *)&buffer[s->pcm_channels + 1]; + b = (u8 *)&buffer[s->midi_position];
len = b[0] - 0x80; if ((1 <= len) && (len <= 3) && (s->midi[port])) @@ -652,6 +674,8 @@ static void handle_out_packet(struct amdtp_stream *s, unsigned int syt) pcm = ACCESS_ONCE(s->pcm); if (pcm) s->transfer_samples(s, pcm, buffer, data_blocks); + else if (s->dual_wire) + amdtp_fill_pcm_silence_dualwire(s, buffer, data_blocks); else amdtp_fill_pcm_silence(s, buffer, data_blocks); if (s->midi_ports) @@ -683,7 +707,7 @@ static void handle_in_packet(struct amdtp_stream *s,
/* * This module supports 'Two-quadlet CIP header with SYT field'. - * For convinience, also check FMT field is AM824 or not. + * For convenience, 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) || diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 2bd3b27..e8d62ac 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -51,6 +51,12 @@ enum cip_sfc {
/* + * This module supports maximum 64 PCM channels for one PCM stream + * This is for our convenience. + */ +#define AMDTP_MAX_CHANNELS_FOR_PCM 64 + +/* * AMDTP packet can include channels for MIDI conformant data. * Each MIDI conformant data channel includes 8 MPX-MIDI data stream. * Each MPX-MIDI data stream includes one data stream from/to MIDI ports. @@ -85,6 +91,8 @@ struct amdtp_stream { void (*transfer_samples)(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); + u8 pcm_positions[AMDTP_MAX_CHANNELS_FOR_PCM]; + u8 midi_position;
unsigned int syt_interval; unsigned int transfer_delay;
In previous commit, AMDTP functionality in firewire-lib supports mapping for PCM data channels. With this mapping, firewire-lib can obsolete a flag, CIP_HI_DUALWIRE, but Dice driver still keeps dual wire mode.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 151 ++----------------------------------------------- sound/firewire/amdtp.h | 8 +-- sound/firewire/dice.c | 33 +++++++++-- 3 files changed, 34 insertions(+), 158 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 9cf81b2..3475b76 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -145,14 +145,7 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s, return;
sfc_found: - s->dual_wire = (s->flags & CIP_HI_DUALWIRE) && sfc > CIP_SFC_96000; - if (s->dual_wire) { - sfc -= 2; - rate /= 2; - s->pcm_channels = pcm_channels * 2; - } else { - s->pcm_channels = pcm_channels; - } + s->pcm_channels = pcm_channels; s->sfc = sfc; s->data_block_quadlets = s->pcm_channels + midi_channels; s->midi_ports = midi_ports; @@ -191,18 +184,9 @@ 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_write_s16_dualwire(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); -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 @@ -225,26 +209,16 @@ void amdtp_stream_set_pcm_format(struct amdtp_stream *s, /* fall through */ case SNDRV_PCM_FORMAT_S16: if (s->direction == AMDTP_OUT_STREAM) { - if (s->dual_wire) - s->transfer_samples = amdtp_write_s16_dualwire; - else - s->transfer_samples = amdtp_write_s16; + s->transfer_samples = amdtp_write_s16; break; } WARN_ON(1); /* fall through */ case SNDRV_PCM_FORMAT_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; - } + if (s->direction == AMDTP_OUT_STREAM) + s->transfer_samples = amdtp_write_s32; + else + s->transfer_samples = amdtp_read_s32; break; } } @@ -393,68 +367,6 @@ static void amdtp_write_s16(struct amdtp_stream *s, } }
-static void amdtp_write_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; - const u32 *src; - - src = (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) { - buffer[s->pcm_positions[c] * 2] = - cpu_to_be32((*src >> 8) | 0x40000000); - src++; - } - buffer += 1; - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c] * 2] = - cpu_to_be32((*src >> 8) | 0x40000000); - src++; - } - buffer += s->data_block_quadlets - 1; - if (--remaining_frames == 0) - src = (void *)runtime->dma_area; - } -} - -static void amdtp_write_s16_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; - const u16 *src; - - src = (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) { - buffer[s->pcm_positions[c] * 2] = - cpu_to_be32((*src << 8) | 0x40000000); - src++; - } - buffer += 1; - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c] * 2] = - cpu_to_be32((*src << 8) | 0x40000000); - src++; - } - buffer += s->data_block_quadlets - 1; - if (--remaining_frames == 0) - src = (void *)runtime->dma_area; - } -} - static void amdtp_read_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames) @@ -479,37 +391,6 @@ static void amdtp_read_s32(struct amdtp_stream *s, } }
-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[s->pcm_positions[c] * 2]) << 8; - dst++; - } - buffer += 1; - for (c = 0; c < channels; ++c) { - *dst = - be32_to_cpu(buffer[s->pcm_positions[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) { @@ -522,21 +403,6 @@ static void amdtp_fill_pcm_silence(struct amdtp_stream *s, } }
-static void amdtp_fill_pcm_silence_dualwire(struct amdtp_stream *s, - __be32 *buffer, unsigned int frames) -{ - unsigned int i, c, channels; - - channels = s->pcm_channels / 2; - for (i = 0; i < frames; ++i) { - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c] * 2] = - buffer[s->pcm_positions[c] * 2 + 1] = - cpu_to_be32(0x40000000); - } - buffer += s->data_block_quadlets; - } -} static void amdtp_fill_midi(struct amdtp_stream *s, __be32 *buffer, unsigned int frames) { @@ -582,9 +448,6 @@ static void update_pcm_pointers(struct amdtp_stream *s, unsigned int frames) { unsigned int ptr;
- if (s->dual_wire) - frames *= 2; - ptr = s->pcm_buffer_pointer + frames; if (ptr >= pcm->runtime->buffer_size) ptr -= pcm->runtime->buffer_size; @@ -674,8 +537,6 @@ static void handle_out_packet(struct amdtp_stream *s, unsigned int syt) pcm = ACCESS_ONCE(s->pcm); if (pcm) s->transfer_samples(s, pcm, buffer, data_blocks); - else if (s->dual_wire) - amdtp_fill_pcm_silence_dualwire(s, buffer, data_blocks); else amdtp_fill_pcm_silence(s, buffer, data_blocks); if (s->midi_ports) diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index e8d62ac..db60425 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -16,18 +16,13 @@ * @CIP_BLOCKING: In blocking mode, each packet contains either zero or * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. - * @CIP_HI_DUALWIRE: At rates above 96 kHz, pretend that the stream runs - * at half the actual sample rate with twice the number of channels; - * two samples of a channel are stored consecutively in the packet. - * Requires blocking mode and SYT_INTERVAL-aligned PCM buffer size. * @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is * generated by in packets. Defaultly this driver generates timestamp. */ enum cip_flags { CIP_NONBLOCKING = 0x00, CIP_BLOCKING = 0x01, - CIP_HI_DUALWIRE = 0x02, - CIP_SYNC_TO_DEVICE = 0x04, + CIP_SYNC_TO_DEVICE = 0x02, };
/** @@ -84,7 +79,6 @@ struct amdtp_stream { struct mutex mutex;
enum cip_sfc sfc; - bool dual_wire; unsigned int data_block_quadlets; unsigned int pcm_channels; unsigned int midi_ports; diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 26b2158..cd4c6b6 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -563,7 +563,7 @@ static int dice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct dice *dice = substream->private_data; - unsigned int rate_index, mode; + unsigned int rate_index, mode, rate, channels, i; int err;
mutex_lock(&dice->mutex); @@ -575,15 +575,36 @@ static int dice_hw_params(struct snd_pcm_substream *substream, if (err < 0) return err;
- rate_index = rate_to_index(params_rate(hw_params)); + rate = params_rate(hw_params); + rate_index = rate_to_index(rate); err = dice_change_rate(dice, rate_index << CLOCK_RATE_SHIFT); if (err < 0) return err;
+ /* + * At rates above 96 kHz, pretend that the stream runs at half the + * actual sample rate with twice the number of channels; two samples + * of a channel are stored consecutively in the packet. Requires + * blocking mode and PCM buffer size should be aligned to SYT_INTERVAL. + */ + channels = params_channels(hw_params); + if (rate_index > 4) { + if (channels > AMDTP_MAX_CHANNELS_FOR_PCM / 2) { + err = -ENOSYS; + return err; + } + + for (i = 0; i < channels; i++) { + dice->stream.pcm_positions[i * 2] = i; + dice->stream.pcm_positions[i * 2 + 1] = i + channels; + } + + rate /= 2; + channels *= 2; + } + mode = rate_index_to_mode(rate_index); - amdtp_stream_set_parameters(&dice->stream, - params_rate(hw_params), - params_channels(hw_params), + amdtp_stream_set_parameters(&dice->stream, rate, channels, dice->rx_midi_ports[mode]); amdtp_stream_set_pcm_format(&dice->stream, params_format(hw_params)); @@ -1361,7 +1382,7 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) dice->resources.channels_mask = 0x00000000ffffffffuLL;
err = amdtp_stream_init(&dice->stream, unit, AMDTP_OUT_STREAM, - CIP_BLOCKING | CIP_HI_DUALWIRE); + CIP_BLOCKING); if (err < 0) goto err_resources;
This commit adds common PCM constraints according to current firewire-lib implementation.
1.Maximum width for each sample is limited by 24. AM824 in IEC 61883-6 can deliver 24bit data.
2. Minimum time for period is 5msec. Apply the old value. For shorter latency, further works are needed.
3. In blocking mode, frames per period/buffer is aligned to 32. Each packet can include some frames depending on its sampling rate. In blocking mode, the number equals to SYT_INTERVAL. Currently firewire-lib can schedule snd_pcm_period_elapsed() for each packet. So, for accurate PCM interrupt, the number of frames per period/buffer should be aligned to SYT_INTERVAL. Currently firewire-lib is lack of better rules to achieve this. So LCM of each value of SYT_INTERVALs (=32) is applied. This can be improved for further work.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++ sound/firewire/amdtp.h | 3 +++ sound/firewire/dice.c | 17 +------------- sound/firewire/speakers.c | 8 +------ 4 files changed, 61 insertions(+), 23 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 3475b76..ac8c358 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -13,6 +13,7 @@ #include <linux/slab.h> #include <linux/sched.h> #include <sound/pcm.h> +#include <sound/pcm_params.h> #include <sound/rawmidi.h> #include "amdtp.h"
@@ -105,6 +106,61 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { EXPORT_SYMBOL(amdtp_syt_intervals);
/** + * amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream + * @s: the AMDTP stream, which must be initialized. + * @runtime: the PCM substream runtime + */ +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* AM824 in IEC 61883-6 can deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + goto end; + + /* + * Currently firewire-lib processes 16 packets in one software + * interrupt callback. This equals to 2msec but actually the + * interval of the interrupts has a jitter. + * Additionally, even if adding a constraint to fit period size to + * 2msec, actual calculated frames per period doesn't equal to 2msec, + * depending on sampling rate. + * Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec. + * Here let us use 5msec for safe period interrupt. + */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, UINT_MAX); + if (err < 0) + goto end; + + /* Non-Blocking stream has no more constraints */ + if (!(s->flags & CIP_BLOCKING)) + goto end; + + /* + * One AMDTP packet can include some frames. In blocking mode, the + * number equals to SYT_INTERVAL. So the number is 8, 16 or 32, + * depending on its sampling rate. For accurate period interrupt, it's + * preferrable to aligh period/buffer sizes to current SYT_INTERVAL. + * + * TODO: These constraints can be improved with propper rules. + * Currently apply LCM of SYT_INTEVALs. + */ + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); + if (err < 0) + goto end; + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); +end: + return err; +} +EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints); + +/** * amdtp_stream_set_parameters - set stream parameters * @s: the AMDTP stream to configure * @rate: the sample rate diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index db60425..d6bb7eb 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -64,6 +64,7 @@ enum cip_sfc { struct fw_unit; struct fw_iso_context; struct snd_pcm_substream; +struct snd_pcm_runtime; struct snd_rawmidi_substream;
enum amdtp_stream_direction { @@ -130,6 +131,8 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); void amdtp_stream_update(struct amdtp_stream *s); void amdtp_stream_stop(struct amdtp_stream *s);
+int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); void amdtp_stream_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); void amdtp_stream_pcm_prepare(struct amdtp_stream *s); diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index cd4c6b6..a9a30c0 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -420,22 +420,7 @@ static int dice_open(struct snd_pcm_substream *substream) if (err < 0) goto err_lock;
- err = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); - if (err < 0) - goto err_lock; - err = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); - if (err < 0) - goto err_lock; - - err = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 5000, UINT_MAX); - if (err < 0) - goto err_lock; - - err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + err = amdtp_stream_add_pcm_hw_constraints(&dice->stream, runtime); if (err < 0) goto err_lock;
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index c07e7cd..3427527 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -167,13 +167,7 @@ static int fwspk_open(struct snd_pcm_substream *substream) if (err < 0) return err;
- err = snd_pcm_hw_constraint_minmax(runtime, - SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 5000, UINT_MAX); - if (err < 0) - return err; - - err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + err = amdtp_stream_add_pcm_hw_constraints(fwspk->stream, runtime); if (err < 0) return err;
At Fri, 25 Apr 2014 22:44:52 +0900, Takashi Sakamoto wrote:
This commit adds common PCM constraints according to current firewire-lib implementation.
1.Maximum width for each sample is limited by 24. AM824 in IEC 61883-6 can deliver 24bit data.
- Minimum time for period is 5msec.
Apply the old value. For shorter latency, further works are needed.
- In blocking mode, frames per period/buffer is aligned to 32.
Each packet can include some frames depending on its sampling rate. In blocking mode, the number equals to SYT_INTERVAL. Currently firewire-lib can schedule snd_pcm_period_elapsed() for each packet. So, for accurate PCM interrupt, the number of frames per period/buffer should be aligned to SYT_INTERVAL. Currently firewire-lib is lack of better rules to achieve this. So LCM of each value of SYT_INTERVALs (=32) is applied. This can be improved for further work.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/amdtp.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++ sound/firewire/amdtp.h | 3 +++ sound/firewire/dice.c | 17 +------------- sound/firewire/speakers.c | 8 +------ 4 files changed, 61 insertions(+), 23 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 3475b76..ac8c358 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -13,6 +13,7 @@ #include <linux/slab.h> #include <linux/sched.h> #include <sound/pcm.h> +#include <sound/pcm_params.h> #include <sound/rawmidi.h> #include "amdtp.h"
@@ -105,6 +106,61 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { EXPORT_SYMBOL(amdtp_syt_intervals);
/**
- amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream
- @s: the AMDTP stream, which must be initialized.
- @runtime: the PCM substream runtime
- */
+int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime)
+{
- int err;
- /* AM824 in IEC 61883-6 can deliver 24bit data */
- err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
- if (err < 0)
goto end;
- /*
* Currently firewire-lib processes 16 packets in one software
* interrupt callback. This equals to 2msec but actually the
* interval of the interrupts has a jitter.
* Additionally, even if adding a constraint to fit period size to
* 2msec, actual calculated frames per period doesn't equal to 2msec,
* depending on sampling rate.
* Anyway, the interval to call snd_pcm_period_elapsed() cannot 2msec.
* Here let us use 5msec for safe period interrupt.
*/
- err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
5000, UINT_MAX);
- if (err < 0)
goto end;
- /* Non-Blocking stream has no more constraints */
- if (!(s->flags & CIP_BLOCKING))
goto end;
- /*
* One AMDTP packet can include some frames. In blocking mode, the
* number equals to SYT_INTERVAL. So the number is 8, 16 or 32,
* depending on its sampling rate. For accurate period interrupt, it's
* preferrable to aligh period/buffer sizes to current SYT_INTERVAL.
*
* TODO: These constraints can be improved with propper rules.
* Currently apply LCM of SYT_INTEVALs.
*/
- err = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32);
- if (err < 0)
goto end;
- err = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
+end:
- return err;
+} +EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints);
+/**
- amdtp_stream_set_parameters - set stream parameters
- @s: the AMDTP stream to configure
- @rate: the sample rate
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index db60425..d6bb7eb 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -64,6 +64,7 @@ enum cip_sfc { struct fw_unit; struct fw_iso_context; struct snd_pcm_substream; +struct snd_pcm_runtime; struct snd_rawmidi_substream;
enum amdtp_stream_direction { @@ -130,6 +131,8 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); void amdtp_stream_update(struct amdtp_stream *s); void amdtp_stream_stop(struct amdtp_stream *s);
+int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
void amdtp_stream_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); void amdtp_stream_pcm_prepare(struct amdtp_stream *s); diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index cd4c6b6..a9a30c0 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -420,22 +420,7 @@ static int dice_open(struct snd_pcm_substream *substream) if (err < 0) goto err_lock;
- err = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32);
- if (err < 0)
goto err_lock;
- err = snd_pcm_hw_constraint_step(runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32);
- if (err < 0)
goto err_lock;
- err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
5000, UINT_MAX);
- if (err < 0)
goto err_lock;
- err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
- err = amdtp_stream_add_pcm_hw_constraints(&dice->stream, runtime); if (err < 0) goto err_lock;
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index c07e7cd..3427527 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -167,13 +167,7 @@ static int fwspk_open(struct snd_pcm_substream *substream) if (err < 0) return err;
- err = snd_pcm_hw_constraint_minmax(runtime,
SNDRV_PCM_HW_PARAM_PERIOD_TIME,
5000, UINT_MAX);
- if (err < 0)
return err;
- err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
- err = amdtp_stream_add_pcm_hw_constraints(fwspk->stream, runtime);
"&" is missing here. I applied the patch with the fix.
Takashi
Currently, drivers can bring XRUN state for PCM substreams when error to queue packets or detecting discontinuity of packet. The application may try to recover this state by calling snd_pcm_prepare().
Depending on each driver, .prepare() includes restart streaming. Then there is a state that PCM substreams are running but isochronous contexts are stopped. In this case, when .pointer() is called, it refers to error pointer.
This commit is for a prevention of this bug.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index ac8c358..7c814ace 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -898,7 +898,7 @@ EXPORT_SYMBOL(amdtp_stream_start); unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s) { /* this optimization is allowed to be racy */ - if (s->pointer_flush) + if (s->pointer_flush && amdtp_stream_running(s)) fw_iso_context_flush_completions(s->context); else s->pointer_flush = true;
Referring to IEC 61883-1, oMPR and iMPR, oPCR and iPCR have some fields with the same role in the same position. This patch renames some macros, variables and function arguments with "i" in its prefix to reuse them between oMPR and iMPR, oPCR and iPCR.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/cmp.c | 91 ++++++++++++++++++++++++++-------------------------- sound/firewire/cmp.h | 6 ++-- 2 files changed, 49 insertions(+), 48 deletions(-)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index efdbf58..deb494d 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -14,18 +14,20 @@ #include "iso-resources.h" #include "cmp.h"
-#define IMPR_SPEED_MASK 0xc0000000 -#define IMPR_SPEED_SHIFT 30 -#define IMPR_XSPEED_MASK 0x00000060 -#define IMPR_XSPEED_SHIFT 5 -#define IMPR_PLUGS_MASK 0x0000001f - -#define IPCR_ONLINE 0x80000000 -#define IPCR_BCAST_CONN 0x40000000 -#define IPCR_P2P_CONN_MASK 0x3f000000 -#define IPCR_P2P_CONN_SHIFT 24 -#define IPCR_CHANNEL_MASK 0x003f0000 -#define IPCR_CHANNEL_SHIFT 16 +/* MPR common fields */ +#define MPR_SPEED_MASK 0xc0000000 +#define MPR_SPEED_SHIFT 30 +#define MPR_XSPEED_MASK 0x00000060 +#define MPR_XSPEED_SHIFT 5 +#define MPR_PLUGS_MASK 0x0000001f + +/* PCR common fields */ +#define PCR_ONLINE 0x80000000 +#define PCR_BCAST_CONN 0x40000000 +#define PCR_P2P_CONN_MASK 0x3f000000 +#define PCR_P2P_CONN_SHIFT 24 +#define PCR_CHANNEL_MASK 0x003f0000 +#define PCR_CHANNEL_SHIFT 16
enum bus_reset_handling { ABORT_ON_BUS_RESET, @@ -88,24 +90,24 @@ static int pcr_modify(struct cmp_connection *c, * cmp_connection_init - initializes a connection manager * @c: the connection manager to initialize * @unit: a unit of the target device - * @ipcr_index: the index of the iPCR on the target device + * @pcr_index: the index of the iPCR/oPCR on the target device */ int cmp_connection_init(struct cmp_connection *c, struct fw_unit *unit, - unsigned int ipcr_index) + unsigned int pcr_index) { - __be32 impr_be; - u32 impr; + __be32 mpr_be; + u32 mpr; int err;
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, CSR_REGISTER_BASE + CSR_IMPR, - &impr_be, 4, 0); + &mpr_be, 4, 0); if (err < 0) return err; - impr = be32_to_cpu(impr_be); + mpr = be32_to_cpu(mpr_be);
- if (ipcr_index >= (impr & IMPR_PLUGS_MASK)) + if (pcr_index >= (mpr & MPR_PLUGS_MASK)) return -EINVAL;
err = fw_iso_resources_init(&c->resources, unit); @@ -115,10 +117,10 @@ int cmp_connection_init(struct cmp_connection *c, c->connected = false; mutex_init(&c->mutex); c->last_pcr_value = cpu_to_be32(0x80000000); - c->pcr_index = ipcr_index; - c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; + c->pcr_index = pcr_index; + c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT; if (c->max_speed == SCODE_BETA) - c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; + c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT;
return 0; } @@ -139,23 +141,23 @@ EXPORT_SYMBOL(cmp_connection_destroy);
static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) { - ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | - IPCR_P2P_CONN_MASK | - IPCR_CHANNEL_MASK); - ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); - ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); + ipcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + PCR_CHANNEL_MASK); + ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);
return ipcr; }
-static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) +static int pcr_set_check(struct cmp_connection *c, __be32 pcr) { - if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | - IPCR_P2P_CONN_MASK)) { + if (pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)) { cmp_error(c, "plug is already in use\n"); return -EBUSY; } - if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { + if (!(pcr & cpu_to_be32(PCR_ONLINE))) { cmp_error(c, "plug is not on-line\n"); return -ECONNREFUSED; } @@ -170,9 +172,9 @@ static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) * * This function establishes a point-to-point connection from the local * computer to the target by allocating isochronous resources (channel and - * bandwidth) and setting the target's input plug control register. When this - * function succeeds, the caller is responsible for starting transmitting - * packets. + * bandwidth) and setting the target's input/output plug control register. + * When this function succeeds, the caller is responsible for starting + * transmitting packets. */ int cmp_connection_establish(struct cmp_connection *c, unsigned int max_payload_bytes) @@ -193,7 +195,7 @@ retry_after_bus_reset: if (err < 0) goto err_mutex;
- err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, ABORT_ON_BUS_RESET); if (err == -EAGAIN) { fw_iso_resources_free(&c->resources); @@ -221,8 +223,8 @@ EXPORT_SYMBOL(cmp_connection_establish); * cmp_connection_update - update the connection after a bus reset * @c: the connection manager * - * This function must be called from the driver's .update handler to reestablish - * any connection that might have been active. + * This function must be called from the driver's .update handler to + * reestablish any connection that might have been active. * * Returns zero on success, or a negative error code. On an error, the * connection is broken and the caller must stop transmitting iso packets. @@ -242,7 +244,7 @@ int cmp_connection_update(struct cmp_connection *c) if (err < 0) goto err_unconnect;
- err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, SUCCEED_ON_BUS_RESET); if (err < 0) goto err_resources; @@ -261,19 +263,18 @@ err_unconnect: } EXPORT_SYMBOL(cmp_connection_update);
- -static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) +static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr) { - return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); + return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK); }
/** * cmp_connection_break - break the connection to the target * @c: the connection manager * - * This function deactives the connection in the target's input plug control - * register, and frees the isochronous resources of the connection. Before - * calling this function, the caller should cease transmitting packets. + * This function deactives the connection in the target's input/output plug + * control register, and frees the isochronous resources of the connection. + * Before calling this function, the caller should cease transmitting packets. */ void cmp_connection_break(struct cmp_connection *c) { @@ -286,7 +287,7 @@ void cmp_connection_break(struct cmp_connection *c) return; }
- err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); + err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); if (err < 0) cmp_error(c, "plug is still connected\n");
diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h index f47de08..2320cd4 100644 --- a/sound/firewire/cmp.h +++ b/sound/firewire/cmp.h @@ -11,8 +11,8 @@ struct fw_unit; * struct cmp_connection - manages an isochronous connection to a device * @speed: the connection's actual speed * - * This structure manages (using CMP) an isochronous stream from the local - * computer to a device's input plug (iPCR). + * This structure manages (using CMP) an isochronous stream between the local + * computer and a device's input plug (iPCR) and output plug (oPCR). * * There is no corresponding oPCR created on the local computer, so it is not * possible to overlay connections on top of this one. @@ -30,7 +30,7 @@ struct cmp_connection {
int cmp_connection_init(struct cmp_connection *connection, struct fw_unit *unit, - unsigned int ipcr_index); + unsigned int pcr_index); void cmp_connection_destroy(struct cmp_connection *connection);
int cmp_connection_establish(struct cmp_connection *connection,
This patch adds 'direction' member to 'cmp_connection' structure to indicate the direction of connection. This patch also adds 'direction' argument to cmp_connection_init() function to determine the direction.
The cmp_connection_init() function is exported and used in snd-firewire-speakers so this patch also affect it.
This patch just add them. Actual implementation will be done by followed patches.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/cmp.c | 1 + sound/firewire/cmp.h | 7 +++++++ sound/firewire/speakers.c | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index deb494d..c7f7a31 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -94,6 +94,7 @@ static int pcr_modify(struct cmp_connection *c, */ int cmp_connection_init(struct cmp_connection *c, struct fw_unit *unit, + enum cmp_direction direction, unsigned int pcr_index) { __be32 mpr_be; diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h index 2320cd4..9b58448 100644 --- a/sound/firewire/cmp.h +++ b/sound/firewire/cmp.h @@ -7,6 +7,11 @@
struct fw_unit;
+enum cmp_direction { + CMP_INPUT = 0, + CMP_OUTPUT, +}; + /** * struct cmp_connection - manages an isochronous connection to a device * @speed: the connection's actual speed @@ -26,10 +31,12 @@ struct cmp_connection { __be32 last_pcr_value; unsigned int pcr_index; unsigned int max_speed; + enum cmp_direction direction; };
int cmp_connection_init(struct cmp_connection *connection, struct fw_unit *unit, + enum cmp_direction direction, unsigned int pcr_index); void cmp_connection_destroy(struct cmp_connection *connection);
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 3427527..1b95494 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -673,7 +673,7 @@ static int fwspk_probe(struct fw_unit *unit, fwspk->unit = fw_unit_get(unit); fwspk->device_info = (const struct device_info *)id->driver_data;
- err = cmp_connection_init(&fwspk->connection, unit, 0); + err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0); if (err < 0) goto err_unit;
This patch adds some macros, codes with condition of direction and new functions to handle output connection. Once cmp_connection_init() is executed with its direction, CMP input and output connection can be handled by the same way.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/cmp.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 9 deletions(-)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index c7f7a31..9da40d9 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -29,6 +29,14 @@ #define PCR_CHANNEL_MASK 0x003f0000 #define PCR_CHANNEL_SHIFT 16
+/* oPCR specific fields */ +#define OPCR_XSPEED_MASK 0x00C00000 +#define OPCR_XSPEED_SHIFT 22 +#define OPCR_SPEED_MASK 0x0000C000 +#define OPCR_SPEED_SHIFT 14 +#define OPCR_OVERHEAD_ID_MASK 0x00003C00 +#define OPCR_OVERHEAD_ID_SHIFT 10 + enum bus_reset_handling { ABORT_ON_BUS_RESET, SUCCEED_ON_BUS_RESET, @@ -41,10 +49,27 @@ void cmp_error(struct cmp_connection *c, const char *fmt, ...)
va_start(va, fmt); dev_err(&c->resources.unit->device, "%cPCR%u: %pV", - 'i', c->pcr_index, &(struct va_format){ fmt, &va }); + (c->direction == CMP_INPUT) ? 'i' : 'o', + c->pcr_index, &(struct va_format){ fmt, &va }); va_end(va); }
+static u64 mpr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IMPR; + else + return CSR_REGISTER_BASE + CSR_OMPR; +} + +static u64 pcr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index); + else + return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index); +} + static int pcr_modify(struct cmp_connection *c, __be32 (*modify)(struct cmp_connection *c, __be32 old), int (*check)(struct cmp_connection *c, __be32 pcr), @@ -60,8 +85,7 @@ static int pcr_modify(struct cmp_connection *c,
err = snd_fw_transaction( c->resources.unit, TCODE_LOCK_COMPARE_SWAP, - CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), - buffer, 8, + pcr_address(c), buffer, 8, FW_FIXED_GENERATION | c->resources.generation);
if (err < 0) { @@ -101,9 +125,9 @@ int cmp_connection_init(struct cmp_connection *c, u32 mpr; int err;
+ c->direction = direction; err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - CSR_REGISTER_BASE + CSR_IMPR, - &mpr_be, 4, 0); + mpr_address(c), &mpr_be, 4, 0); if (err < 0) return err; mpr = be32_to_cpu(mpr_be); @@ -151,6 +175,53 @@ static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) return ipcr; }
+static int get_overhead_id(struct cmp_connection *c) +{ + int id; + + /* + * apply "oPCR overhead ID encoding" + * the encoding table can convert up to 512. + * here the value over 512 is converted as the same way as 512. + */ + for (id = 1; id < 16; id++) { + if (c->resources.bandwidth_overhead < (id << 5)) + break; + } + if (id == 16) + id = 0; + + return id; +} + +static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr) +{ + unsigned int spd, xspd; + + /* generate speed and extended speed field value */ + if (c->speed > SCODE_400) { + spd = SCODE_800; + xspd = c->speed - SCODE_800; + } else { + spd = c->speed; + xspd = 0; + } + + opcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + OPCR_XSPEED_MASK | + PCR_CHANNEL_MASK | + OPCR_SPEED_MASK | + OPCR_OVERHEAD_ID_MASK); + opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT); + opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); + opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT); + opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT); + + return opcr; +} + static int pcr_set_check(struct cmp_connection *c, __be32 pcr) { if (pcr & cpu_to_be32(PCR_BCAST_CONN | @@ -196,8 +267,13 @@ retry_after_bus_reset: if (err < 0) goto err_mutex;
- err = pcr_modify(c, ipcr_set_modify, pcr_set_check, - ABORT_ON_BUS_RESET); + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + if (err == -EAGAIN) { fw_iso_resources_free(&c->resources); goto retry_after_bus_reset; @@ -245,8 +321,13 @@ int cmp_connection_update(struct cmp_connection *c) if (err < 0) goto err_unconnect;
- err = pcr_modify(c, ipcr_set_modify, pcr_set_check, - SUCCEED_ON_BUS_RESET); + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + if (err < 0) goto err_resources;
Plug Control Registers have two fields related to the number of established connections, one is 'Broadcast connection counter' and another is 'Point-to-point connection counter'. The driver can know there are established connections or not to check these fields.
This commit is for considering about JACK/FFADO streaming. Currently, when JACK/FFADO starts its streaming to the device, cmp_connection_establish() is failed expectedly. This seems to be enough but there are some devices which needs to change sampling frequency before trying to establish connections. For such devices, this functionality is needed.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/cmp.c | 18 ++++++++++++++++++ sound/firewire/cmp.h | 1 + 2 files changed, 19 insertions(+)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index 9da40d9..d31a403 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -152,6 +152,24 @@ int cmp_connection_init(struct cmp_connection *c, EXPORT_SYMBOL(cmp_connection_init);
/** + * cmp_connection_check_used - check connection is already esablished or not + * @c: the connection manager to be checked + */ +int cmp_connection_check_used(struct cmp_connection *c, bool *used) +{ + __be32 pcr; + int err; + + err = snd_fw_transaction( + c->resources.unit, TCODE_READ_QUADLET_REQUEST, + pcr_address(c), &pcr, 4, 0); + if (err >= 0) + *used = (pcr & cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK)); + return err; +} +EXPORT_SYMBOL(cmp_connection_check_used); + +/** * cmp_connection_destroy - free connection manager resources * @c: the connection manager */ diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h index 9b58448..ebcb484 100644 --- a/sound/firewire/cmp.h +++ b/sound/firewire/cmp.h @@ -38,6 +38,7 @@ int cmp_connection_init(struct cmp_connection *connection, struct fw_unit *unit, enum cmp_direction direction, unsigned int pcr_index); +int cmp_connection_check_used(struct cmp_connection *connection, bool *used); void cmp_connection_destroy(struct cmp_connection *connection);
int cmp_connection_establish(struct cmp_connection *connection,
Some devices based on BeBoB use this type of AV/C transaction.
'Deferred Transaction' is defined in 'AV/C Digital Interface Command Set General Specification' and is used by targets to make a response deferred during processing it.
If a target may not be able to complete a command within 100msec since receiving the command, then the target shall return INTERIM response, to which final response will follow later. CONTROL/NOTIFY commands are allowed for deferred transaction.
In the specification, devices allow to send INTERIM response just one time. But this commit allows to handle several INTERIM response with two reasons. One reason is to simplify codes, and another reason is to prepare for devices which is out of specification.
There is an issue. In the specification, the interval between INTERIM response and final response is 'Unspecified interval'. The specification depends on each subunit specification for this interval.
But we promise to finish this function for caller. In this reason, I use FCP_TIMEOUT_MS for this interval. Currently it's 125msec. When we find devices which needs more time for this interval, then let us add some codes to apply more interval for 'Unspecified interval'.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fcp.c | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-)
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index 860c080..6876bfa 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -30,6 +30,7 @@ enum fcp_state { STATE_PENDING, STATE_BUS_RESET, STATE_COMPLETE, + STATE_DEFERRED, };
struct fcp_transaction { @@ -40,6 +41,7 @@ struct fcp_transaction { unsigned int response_match_bytes; enum fcp_state state; wait_queue_head_t wait; + bool deferrable; };
/** @@ -81,6 +83,9 @@ int fcp_avc_transaction(struct fw_unit *unit, t.state = STATE_PENDING; init_waitqueue_head(&t.wait);
+ if (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03) + t.deferrable = true; + spin_lock_irq(&transactions_lock); list_add_tail(&t.list, &transactions); spin_unlock_irq(&transactions_lock); @@ -93,11 +98,21 @@ int fcp_avc_transaction(struct fw_unit *unit, (void *)command, command_size, 0); if (ret < 0) break; - +deferred: wait_event_timeout(t.wait, t.state != STATE_PENDING, msecs_to_jiffies(FCP_TIMEOUT_MS));
- if (t.state == STATE_COMPLETE) { + if (t.state == STATE_DEFERRED) { + /* + * 'AV/C General Specification' define no time limit + * on command completion once an INTERIM response has + * been sent. but we promise to finish this function + * for a caller. Here we use FCP_TIMEOUT_MS for next + * interval. This is not in the specification. + */ + t.state = STATE_PENDING; + goto deferred; + } else if (t.state == STATE_COMPLETE) { ret = t.response_size; break; } else if (t.state == STATE_BUS_RESET) { @@ -132,7 +147,8 @@ void fcp_bus_reset(struct fw_unit *unit) spin_lock_irq(&transactions_lock); list_for_each_entry(t, &transactions, list) { if (t->unit == unit && - t->state == STATE_PENDING) { + (t->state == STATE_PENDING || + t->state == STATE_DEFERRED)) { t->state = STATE_BUS_RESET; wake_up(&t->wait); } @@ -186,10 +202,15 @@ static void fcp_response(struct fw_card *card, struct fw_request *request,
if (t->state == STATE_PENDING && is_matching_response(t, data, length)) { - t->state = STATE_COMPLETE; - t->response_size = min((unsigned int)length, - t->response_size); - memcpy(t->response_buffer, data, t->response_size); + if (t->deferrable && *(const u8 *)data == 0x0f) { + t->state = STATE_DEFERRED; + } else { + t->state = STATE_COMPLETE; + t->response_size = min_t(unsigned int, length, + t->response_size); + memcpy(t->response_buffer, data, + t->response_size); + } wake_up(&t->wait); } }
This commit adds three commands, which may be used by some firewire device drivers. These commands are defined in 'AV/C Digital Interface Command Set General Specification Version 4.2 (2004006, 1394TA)'.
1. PLUG INFO command (clause 10.1) 2. INPUT PLUG SIGNAL FORMAT command (clause 10.10) 3. OUTPUT PLUG SIGNAL FORMAT command (clause 10.11)
By the command 1, the drivers can get the number of plugs for AV/C unit or subunit. By the command 2 and 3, the drivers can get/set sampling frequency.
The 'firewire-speakers' already uses INPUT PLUG SIGNAL FORMAT command to set sampling rate. So this commit also affects the driver.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 24 ++++---- sound/firewire/amdtp.h | 1 + sound/firewire/fcp.c | 154 ++++++++++++++++++++++++++++++++++++++++++++++ sound/firewire/fcp.h | 21 +++++++ sound/firewire/speakers.c | 43 ++----------- 5 files changed, 194 insertions(+), 49 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 7c814ace..5b88461 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -105,6 +105,17 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { }; EXPORT_SYMBOL(amdtp_syt_intervals);
+const unsigned int amdtp_rate_table[] = { + [CIP_SFC_32000] = 32000, + [CIP_SFC_44100] = 44100, + [CIP_SFC_48000] = 48000, + [CIP_SFC_88200] = 88200, + [CIP_SFC_96000] = 96000, + [CIP_SFC_176400] = 176400, + [CIP_SFC_192000] = 192000, +}; +EXPORT_SYMBOL(amdtp_rate_table); + /** * amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream * @s: the AMDTP stream, which must be initialized. @@ -176,15 +187,6 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int pcm_channels, unsigned int midi_ports) { - static const unsigned int rates[] = { - [CIP_SFC_32000] = 32000, - [CIP_SFC_44100] = 44100, - [CIP_SFC_48000] = 48000, - [CIP_SFC_88200] = 88200, - [CIP_SFC_96000] = 96000, - [CIP_SFC_176400] = 176400, - [CIP_SFC_192000] = 192000, - }; unsigned int i, sfc, midi_channels;
midi_channels = DIV_ROUND_UP(midi_ports, 8); @@ -194,8 +196,8 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s, WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI)) return;
- for (sfc = 0; sfc < CIP_SFC_COUNT; ++sfc) - if (rates[sfc] == rate) + for (sfc = 0; sfc < sizeof(amdtp_rate_table); ++sfc) + if (amdtp_rate_table[sfc] == rate) goto sfc_found; WARN_ON(1); return; diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index d6bb7eb..3de3463 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -140,6 +140,7 @@ unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s); void amdtp_stream_pcm_abort(struct amdtp_stream *s);
extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT]; +extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT];
/** * amdtp_stream_running - check stream is running or not diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index 6876bfa..d48e326 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -10,12 +10,14 @@ #include <linux/firewire-constants.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/sched.h> #include <linux/spinlock.h> #include <linux/wait.h> #include <linux/delay.h> #include "fcp.h" #include "lib.h" +#include "amdtp.h"
#define CTS_AVC 0x00
@@ -23,6 +25,158 @@ #define ERROR_DELAY_MS 5 #define FCP_TIMEOUT_MS 125
+int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + bool flag; + int err; + + flag = false; + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) { + flag = true; + break; + } + } + if (!flag) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_set_sig_fmt); + +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* Unit */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0xff; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-4] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* check sfc field and pick up rate */ + sfc = 0x07 & buf[5]; + if (sfc >= CIP_SFC_COUNT) { + err = -EAGAIN; /* also in transition */ + goto end; + } + + *rate = amdtp_rate_table[sfc]; + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_sig_fmt); + +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]) +{ + u8 *buf; + int err; + + /* extended subunit in spec.4.2 is not supported */ + if ((subunit_type == 0x1E) || (subunit_id == 5)) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + /* UNIT or Subunit, Functionblock */ + buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7); + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xff & subfunction; + + err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + info[0] = buf[4]; + info[1] = buf[5]; + info[2] = buf[6]; + info[3] = buf[7]; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_plug_info); + static DEFINE_SPINLOCK(transactions_lock); static LIST_HEAD(transactions);
diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h index 8659568..63ae4f7 100644 --- a/sound/firewire/fcp.h +++ b/sound/firewire/fcp.h @@ -1,8 +1,29 @@ #ifndef SOUND_FIREWIRE_FCP_H_INCLUDED #define SOUND_FIREWIRE_FCP_H_INCLUDED
+#define AVC_PLUG_INFO_BUF_BYTES 4 + struct fw_unit;
+/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +enum avc_general_plug_dir { + AVC_GENERAL_PLUG_DIR_IN = 0, + AVC_GENERAL_PLUG_DIR_OUT = 1, + AVC_GENERAL_PLUG_DIR_COUNT +}; +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]); + int fcp_avc_transaction(struct fw_unit *unit, const void *command, unsigned int command_size, void *response, unsigned int response_size, diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 1b95494..8b362e7 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -187,42 +187,6 @@ static void fwspk_stop_stream(struct fwspk *fwspk) } }
-static int fwspk_set_rate(struct fwspk *fwspk, unsigned int sfc) -{ - u8 *buf; - int err; - - buf = kmalloc(8, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - buf[0] = 0x00; /* AV/C, CONTROL */ - buf[1] = 0xff; /* unit */ - buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ - buf[3] = 0x00; /* plug 0 */ - buf[4] = 0x90; /* format: audio */ - buf[5] = 0x00 | sfc; /* AM824, frequency */ - buf[6] = 0xff; /* SYT (not used) */ - buf[7] = 0xff; - - err = fcp_avc_transaction(fwspk->unit, buf, 8, buf, 8, - BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); - if (err < 0) - goto error; - if (err < 6 || buf[0] != 0x09 /* ACCEPTED */) { - dev_err(&fwspk->unit->device, "failed to set sample rate\n"); - err = -EIO; - goto error; - } - - err = 0; - -error: - kfree(buf); - - return err; -} - static int fwspk_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -246,9 +210,12 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, amdtp_stream_set_pcm_format(&fwspk->stream, params_format(hw_params));
- err = fwspk_set_rate(fwspk, fwspk->stream.sfc); - if (err < 0) + err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params), + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) { + dev_err(&fwspk->unit->device, "failed to set sample rate\n"); goto err_buffer; + }
return 0;
This commit adds a new driver for devices based on Fireworks. This driver just creates/removes card instance according to callbacks.
Fireworks is a board module which Echo Audio produced. This module consists of three chipsets: - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6 - DSP or/and FPGA for signal processing - Flash Memory to store firmwares
Current supported devices: - Mackie Onyx 400F/1200F - Echo AudioFire12/8(until 2009 July) - Echo AudioFire2/4/Pre8/8(since 2009 July) - Echo Fireworks 8/HDMI - Gibson Robot Interface pack/GoldTop
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 14 +++ sound/firewire/Makefile | 1 + sound/firewire/fireworks/Makefile | 2 + sound/firewire/fireworks/fireworks.c | 182 +++++++++++++++++++++++++++++++++++ sound/firewire/fireworks/fireworks.h | 41 ++++++++ 5 files changed, 240 insertions(+) create mode 100644 sound/firewire/fireworks/Makefile create mode 100644 sound/firewire/fireworks/fireworks.c create mode 100644 sound/firewire/fireworks/fireworks.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b3e274f..8cd4f1f 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -61,4 +61,18 @@ config SND_SCS1X To compile this driver as a module, choose M here: the module will be called snd-scs1x.
+config SND_FIREWORKS + tristate "Echo Fireworks board module support" + help + Say Y here to include support for FireWire devices based + on Echo Digital Audio Fireworks board: + * Mackie Onyx 400F/1200F + * Echo AudioFire12/8(until 2009 July) + * Echo AudioFire2/4/Pre8/8(since 2009 July) + * Echo Fireworks 8/HDMI + * Gibson Robot Interface Pack/GoldTop + + To compile this driver as a module, choose M here: the module + will be called snd-fireworks. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 5099550..5cd39dc 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_SND_DICE) += snd-dice.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o +obj-$(CONFIG_SND_FIREWORKS) += fireworks/ diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile new file mode 100644 index 0000000..99f6fc3 --- /dev/null +++ b/sound/firewire/fireworks/Makefile @@ -0,0 +1,2 @@ +snd-fireworks-objs := fireworks.o +obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c new file mode 100644 index 0000000..ad719a1 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.c @@ -0,0 +1,182 @@ +/* + * fireworks.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks is a board module which Echo Audio produced. This module consists + * of three chipsets: + * - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6 + * - DSP or/and FPGA for signal processing + * - Flash Memory to store firmwares + */ + +#include "fireworks.h" + +MODULE_DESCRIPTION("Echo Fireworks driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable Fireworks sound card"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +#define VENDOR_LOUD 0x000ff2 +#define MODEL_MACKIE_400F 0x00400f +#define MODEL_MACKIE_1200F 0x01200f + +#define VENDOR_ECHO 0x001486 +#define MODEL_ECHO_AUDIOFIRE_12 0x00af12 +#define MODEL_ECHO_AUDIOFIRE_12HD 0x0af12d +#define MODEL_ECHO_AUDIOFIRE_12_APPLE 0x0af12a +/* This is applied for AudioFire8 (until 2009 July) */ +#define MODEL_ECHO_AUDIOFIRE_8 0x000af8 +#define MODEL_ECHO_AUDIOFIRE_2 0x000af2 +#define MODEL_ECHO_AUDIOFIRE_4 0x000af4 +/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */ +#define MODEL_ECHO_AUDIOFIRE_9 0x000af9 +/* unknown as product */ +#define MODEL_ECHO_FIREWORKS_8 0x0000f8 +#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1 + +#define VENDOR_GIBSON 0x00075b +/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */ +#define MODEL_GIBSON_RIP 0x00afb2 +/* unknown as product */ +#define MODEL_GIBSON_GOLDTOP 0x00afb9 + +static void +efw_card_free(struct snd_card *card) +{ + struct snd_efw *efw = card->private_data; + + if (efw->card_index >= 0) { + mutex_lock(&devices_mutex); + clear_bit(efw->card_index, devices_used); + mutex_unlock(&devices_mutex); + } + + mutex_destroy(&efw->mutex); +} + +static int +efw_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_efw *efw; + int card_index, err; + + mutex_lock(&devices_mutex); + + /* check registered cards */ + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + err = snd_card_new(&unit->device, index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_efw), &card); + if (err < 0) + goto end; + efw = card->private_data; + efw->card_index = card_index; + set_bit(card_index, devices_used); + card->private_free = efw_card_free; + + efw->card = card; + efw->unit = unit; + mutex_init(&efw->mutex); + spin_lock_init(&efw->lock); + + strcpy(efw->card->driver, "Fireworks"); + strcpy(efw->card->shortname, efw->card->driver); + strcpy(efw->card->longname, efw->card->driver); + strcpy(efw->card->mixername, efw->card->driver); + + err = snd_card_register(card); + if (err < 0) + goto error; + dev_set_drvdata(&unit->device, efw); +end: + mutex_unlock(&devices_mutex); + return err; +error: + mutex_unlock(&devices_mutex); + snd_card_free(card); + return err; +} + +static void efw_update(struct fw_unit *unit) +{ + return; +} + +static void efw_remove(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + snd_card_disconnect(efw->card); + snd_card_free_when_closed(efw->card); +} + +static const struct ieee1394_device_id efw_id_table[] = { + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F), + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP), + {} +}; +MODULE_DEVICE_TABLE(ieee1394, efw_id_table); + +static struct fw_driver efw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireworks", + .bus = &fw_bus_type, + }, + .probe = efw_probe, + .update = efw_update, + .remove = efw_remove, + .id_table = efw_id_table, +}; + +static int __init snd_efw_init(void) +{ + return driver_register(&efw_driver.driver); +} + +static void __exit snd_efw_exit(void) +{ + driver_unregister(&efw_driver.driver); + mutex_destroy(&devices_mutex); +} + +module_init(snd_efw_init); +module_exit(snd_efw_exit); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h new file mode 100644 index 0000000..9dfeb82 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.h @@ -0,0 +1,41 @@ +/* + * fireworks.h - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#ifndef SOUND_FIREWORKS_H_INCLUDED +#define SOUND_FIREWORKS_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> + +struct snd_efw { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; +}; + +#define SND_EFW_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor,\ + .model_id = model \ +} + +#endif
Fireworks uses own command and response. This commit adds functionality to transact and adds some commands required for sound card instance and kernel streaming.
There are two ways to deliver substance of this transaction: 1.AV/C vendor dependent command for command/response 2.Async transaction to specific addresses for command/response
By way 1, I confirm AudioFire12 cannot correctly response to some commands with firmware version 5.0 or later. This is also confirmed by FFADO. So this driver implement way 2.
The address for response gives an issue. When this driver allocate own callback function into the address, then no one can allocate its own callback function. This situation is not good for applications in user-land. This issue is solved in later commit.
I note there is a command to change the address for response if the device supports. But this driver uses default value. So users should not execute this command as long as hoping this driver works correctly.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/fireworks/Makefile | 2 +- sound/firewire/fireworks/fireworks.c | 69 ++++- sound/firewire/fireworks/fireworks.h | 126 ++++++++ sound/firewire/fireworks/fireworks_command.c | 370 +++++++++++++++++++++++ sound/firewire/fireworks/fireworks_transaction.c | 190 ++++++++++++ 6 files changed, 751 insertions(+), 7 deletions(-) create mode 100644 sound/firewire/fireworks/fireworks_command.c create mode 100644 sound/firewire/fireworks/fireworks_transaction.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 8cd4f1f..0b85ebd 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -63,6 +63,7 @@ config SND_SCS1X
config SND_FIREWORKS tristate "Echo Fireworks board module support" + select SND_FIREWIRE_LIB help Say Y here to include support for FireWire devices based on Echo Digital Audio Fireworks board: diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile index 99f6fc3..a6ce214 100644 --- a/sound/firewire/fireworks/Makefile +++ b/sound/firewire/fireworks/Makefile @@ -1,2 +1,2 @@ -snd-fireworks-objs := fireworks.o +snd-fireworks-objs := fireworks_transaction.o fireworks_command.o fireworks.o obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index ad719a1..6fa3a5d 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -59,6 +59,50 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS); /* unknown as product */ #define MODEL_GIBSON_GOLDTOP 0x00afb9
+/* part of hardware capability flags */ +#define FLAG_RESP_ADDR_CHANGABLE 0 + +static int +get_hardware_info(struct snd_efw *efw) +{ + struct fw_device *fw_dev = fw_parent_device(efw->unit); + struct snd_efw_hwinfo *hwinfo; + char version[12] = {0}; + int err; + + hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return -ENOMEM; + + err = snd_efw_command_get_hwinfo(efw, hwinfo); + if (err < 0) + goto end; + + /* firmware version for communication chipset */ + snprintf(version, sizeof(version), "%u.%u", + (hwinfo->arm_version >> 24) & 0xff, + (hwinfo->arm_version >> 16) & 0xff); + if (err < 0) + goto end; + + strcpy(efw->card->driver, "Fireworks"); + strcpy(efw->card->shortname, hwinfo->model_name); + strcpy(efw->card->mixername, hwinfo->model_name); + snprintf(efw->card->longname, sizeof(efw->card->longname), + "%s %s v%s, GUID %08x%08x at %s, S%d", + hwinfo->vendor_name, hwinfo->model_name, version, + hwinfo->guid_hi, hwinfo->guid_lo, + dev_name(&efw->unit->device), 100 << fw_dev->max_speed); + if (err < 0) + goto end; + + if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE)) + efw->resp_addr_changable = true; +end: + kfree(hwinfo); + return err; +} + static void efw_card_free(struct snd_card *card) { @@ -107,14 +151,14 @@ efw_probe(struct fw_unit *unit, mutex_init(&efw->mutex); spin_lock_init(&efw->lock);
- strcpy(efw->card->driver, "Fireworks"); - strcpy(efw->card->shortname, efw->card->driver); - strcpy(efw->card->longname, efw->card->driver); - strcpy(efw->card->mixername, efw->card->driver); + err = get_hardware_info(efw); + if (err < 0) + goto error;
err = snd_card_register(card); if (err < 0) goto error; + dev_set_drvdata(&unit->device, efw); end: mutex_unlock(&devices_mutex); @@ -127,7 +171,8 @@ error:
static void efw_update(struct fw_unit *unit) { - return; + struct snd_efw *efw = dev_get_drvdata(&unit->device); + snd_efw_transaction_bus_reset(efw->unit); }
static void efw_remove(struct fw_unit *unit) @@ -169,11 +214,23 @@ static struct fw_driver efw_driver = {
static int __init snd_efw_init(void) { - return driver_register(&efw_driver.driver); + int err; + + err = snd_efw_transaction_register(); + if (err < 0) + goto end; + + err = driver_register(&efw_driver.driver); + if (err < 0) + snd_efw_transaction_unregister(); + +end: + return err; }
static void __exit snd_efw_exit(void) { + snd_efw_transaction_unregister(); driver_unregister(&efw_driver.driver); mutex_destroy(&devices_mutex); } diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 9dfeb82..e999802 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -20,6 +20,28 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/pcm.h> + +#include "../cmp.h" +#include "../lib.h" + +#define SND_EFW_MULTIPLIER_MODES 3 +#define HWINFO_NAME_SIZE_BYTES 32 +#define HWINFO_MAX_CAPS_GROUPS 8 + +/* + * This should be greater than maximum bytes for EFW response content. + * Currently response against command for isochronous channel mapping is + * confirmed to be the maximum one. But for flexibility, use maximum data + * payload for asynchronous primary packets at S100 (Cable base rate) in + * IEEE Std 1394-1995. + */ +#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U + +struct snd_efw_phys_grp { + u8 type; /* see enum snd_efw_grp_type */ + u8 count; +} __packed;
struct snd_efw { struct snd_card *card; @@ -28,7 +50,111 @@ struct snd_efw {
struct mutex mutex; spinlock_t lock; + + /* for transaction */ + u32 seqnum; + bool resp_addr_changable; +}; + +struct snd_efw_transaction { + __be32 length; + __be32 version; + __be32 seqnum; + __be32 category; + __be32 command; + __be32 status; + __be32 params[0]; +}; +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size); +int snd_efw_transaction_register(void); +void snd_efw_transaction_unregister(void); +void snd_efw_transaction_bus_reset(struct fw_unit *unit); + +struct snd_efw_hwinfo { + u32 flags; + u32 guid_hi; + u32 guid_lo; + u32 type; + u32 version; + char vendor_name[HWINFO_NAME_SIZE_BYTES]; + char model_name[HWINFO_NAME_SIZE_BYTES]; + u32 supported_clocks; + u32 amdtp_rx_pcm_channels; + u32 amdtp_tx_pcm_channels; + u32 phys_out; + u32 phys_in; + u32 phys_out_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 phys_in_grp_count; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 midi_out_ports; + u32 midi_in_ports; + u32 max_sample_rate; + u32 min_sample_rate; + u32 dsp_version; + u32 arm_version; + u32 mixer_playback_channels; + u32 mixer_capture_channels; + u32 fpga_version; + u32 amdtp_rx_pcm_channels_2x; + u32 amdtp_tx_pcm_channels_2x; + u32 amdtp_rx_pcm_channels_4x; + u32 amdtp_tx_pcm_channels_4x; + u32 reserved[16]; +} __packed; +enum snd_efw_grp_type { + SND_EFW_CH_TYPE_ANALOG = 0, + SND_EFW_CH_TYPE_SPDIF = 1, + SND_EFW_CH_TYPE_ADAT = 2, + SND_EFW_CH_TYPE_SPDIF_OR_ADAT = 3, + SND_EFW_CH_TYPE_ANALOG_MIRRORING = 4, + SND_EFW_CH_TYPE_HEADPHONES = 5, + SND_EFW_CH_TYPE_I2S = 6, + SND_EFW_CH_TYPE_GUITAR = 7, + SND_EFW_CH_TYPE_PIEZO_GUITAR = 8, + SND_EFW_CH_TYPE_GUITAR_STRING = 9, + SND_EFW_CH_TYPE_VIRTUAL = 0x10000, + SND_EFW_CH_TYPE_DUMMY +}; +struct snd_efw_phys_meters { + u32 status; /* guitar state/midi signal/clock input detect */ + u32 reserved0; + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 out_meters; + u32 in_meters; + u32 reserved4; + u32 reserved5; + u32 values[0]; +} __packed; +enum snd_efw_clock_source { + SND_EFW_CLOCK_SOURCE_INTERNAL = 0, + SND_EFW_CLOCK_SOURCE_SYTMATCH = 1, + SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2, + SND_EFW_CLOCK_SOURCE_SPDIF = 3, + SND_EFW_CLOCK_SOURCE_ADAT_1 = 4, + SND_EFW_CLOCK_SOURCE_ADAT_2 = 5, + SND_EFW_CLOCK_SOURCE_CONTINUOUS = 6 /* internal variable clock */ +}; +enum snd_efw_transport_mode { + SND_EFW_TRANSPORT_MODE_WINDOWS = 0, + SND_EFW_TRANSPORT_MODE_IEC61883 = 1, }; +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low); +int snd_efw_command_set_tx_mode(struct snd_efw *efw, unsigned int mode); +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo); +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len); +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source); +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate); +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
#define SND_EFW_DEV_ENTRY(vendor, model) \ { \ diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c new file mode 100644 index 0000000..d5ea705 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_command.c @@ -0,0 +1,370 @@ +/* + * fireworks_command.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./fireworks.h" + +/* + * This driver uses transaction version 1 or later to use extended hardware + * information. Then too old devices are not available. + * + * Each commands are not required to have continuous sequence numbers. This + * number is just used to match command and response. + * + * This module support a part of commands. Please see FFADO if you want to see + * whole commands. But there are some commands which FFADO don't implement. + * + * Fireworks also supports AV/C general commands and AV/C Stream Format + * Information commands. But this module don't use them. + */ + +#define EFW_TRANSACTION_SEQNUM_MAX ((u32)~0) + +/* for clock source and sampling rate */ +struct efc_clock { + u32 source; + u32 sampling_rate; + u32 index; +}; + +/* command categories */ +enum efc_category { + EFC_CAT_HWINFO = 0, + EFC_CAT_TRANSPORT = 2, + EFC_CAT_HWCTL = 3, +}; + +/* hardware info category commands */ +enum efc_cmd_hwinfo { + EFC_CMD_HWINFO_GET_CAPS = 0, + EFC_CMD_HWINFO_GET_POLLED = 1, + EFC_CMD_HWINFO_SET_RESP_ADDR = 2 +}; + +enum efc_cmd_transport { + EFC_CMD_TRANSPORT_SET_TX_MODE = 0 +}; + +/* hardware control category commands */ +enum efc_cmd_hwctl { + EFC_CMD_HWCTL_SET_CLOCK = 0, + EFC_CMD_HWCTL_GET_CLOCK = 1, + EFC_CMD_HWCTL_IDENTIFY = 5 +}; + +/* return values in response */ +enum efr_status { + EFR_STATUS_OK = 0, + EFR_STATUS_BAD = 1, + EFR_STATUS_BAD_COMMAND = 2, + EFR_STATUS_COMM_ERR = 3, + EFR_STATUS_BAD_QUAD_COUNT = 4, + EFR_STATUS_UNSUPPORTED = 5, + EFR_STATUS_1394_TIMEOUT = 6, + EFR_STATUS_DSP_TIMEOUT = 7, + EFR_STATUS_BAD_RATE = 8, + EFR_STATUS_BAD_CLOCK = 9, + EFR_STATUS_BAD_CHANNEL = 10, + EFR_STATUS_BAD_PAN = 11, + EFR_STATUS_FLASH_BUSY = 12, + EFR_STATUS_BAD_MIRROR = 13, + EFR_STATUS_BAD_LED = 14, + EFR_STATUS_BAD_PARAMETER = 15, + EFR_STATUS_INCOMPLETE = 0x80000000 +}; + +static const char *const efr_status_names[] = { + [EFR_STATUS_OK] = "OK", + [EFR_STATUS_BAD] = "bad", + [EFR_STATUS_BAD_COMMAND] = "bad command", + [EFR_STATUS_COMM_ERR] = "comm err", + [EFR_STATUS_BAD_QUAD_COUNT] = "bad quad count", + [EFR_STATUS_UNSUPPORTED] = "unsupported", + [EFR_STATUS_1394_TIMEOUT] = "1394 timeout", + [EFR_STATUS_DSP_TIMEOUT] = "DSP timeout", + [EFR_STATUS_BAD_RATE] = "bad rate", + [EFR_STATUS_BAD_CLOCK] = "bad clock", + [EFR_STATUS_BAD_CHANNEL] = "bad channel", + [EFR_STATUS_BAD_PAN] = "bad pan", + [EFR_STATUS_FLASH_BUSY] = "flash busy", + [EFR_STATUS_BAD_MIRROR] = "bad mirror", + [EFR_STATUS_BAD_LED] = "bad LED", + [EFR_STATUS_BAD_PARAMETER] = "bad parameter", + [EFR_STATUS_BAD_PARAMETER + 1] = "incomplete" +}; + +static int +efw_transaction(struct snd_efw *efw, unsigned int category, + unsigned int command, + const __be32 *params, unsigned int param_bytes, + const __be32 *resp, unsigned int resp_bytes) +{ + struct snd_efw_transaction *header; + __be32 *buf; + u32 seqnum; + unsigned int buf_bytes, cmd_bytes; + int err; + + /* calculate buffer size*/ + buf_bytes = sizeof(struct snd_efw_transaction) + + max(param_bytes, resp_bytes); + + /* keep buffer */ + buf = kzalloc(buf_bytes, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* to keep consistency of sequence number */ + spin_lock(&efw->lock); + if (efw->seqnum + 2 >= EFW_TRANSACTION_SEQNUM_MAX) + efw->seqnum = 0; + else + efw->seqnum += 2; + seqnum = efw->seqnum; + spin_unlock(&efw->lock); + + /* fill transaction header fields */ + cmd_bytes = sizeof(struct snd_efw_transaction) + param_bytes; + header = (struct snd_efw_transaction *)buf; + header->length = cpu_to_be32(cmd_bytes / sizeof(__be32)); + header->version = cpu_to_be32(1); + header->seqnum = cpu_to_be32(seqnum); + header->category = cpu_to_be32(category); + header->command = cpu_to_be32(command); + header->status = 0; + + /* fill transaction command parameters */ + memcpy(header->params, params, param_bytes); + + err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes, + buf, buf_bytes); + if (err < 0) + goto end; + + /* check transaction header fields */ + if ((be32_to_cpu(header->version) < 1) || + (be32_to_cpu(header->category) != category) || + (be32_to_cpu(header->command) != command) || + (be32_to_cpu(header->status) != EFR_STATUS_OK)) { + dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n", + be32_to_cpu(header->category), + be32_to_cpu(header->command), + efr_status_names[be32_to_cpu(header->status)]); + err = -EIO; + goto end; + } + + if (resp == NULL) + goto end; + + /* fill transaction response parameters */ + memset((void *)resp, 0, resp_bytes); + resp_bytes = min_t(unsigned int, resp_bytes, + be32_to_cpu(header->length) * sizeof(__be32) - + sizeof(struct snd_efw_transaction)); + memcpy((void *)resp, &buf[6], resp_bytes); +end: + kfree(buf); + return err; +} + +/* + * The address in host system for transaction response is changable when the + * device supports. struct hwinfo.flags includes its flag. The default is + * MEMORY_SPACE_EFW_RESPONSE. + */ +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low) +{ + __be32 addr[2]; + + addr[0] = cpu_to_be32(addr_high); + addr[1] = cpu_to_be32(addr_low); + + if (!efw->resp_addr_changable) + return -ENOSYS; + + return efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWINFO_SET_RESP_ADDR, + addr, sizeof(addr), NULL, 0); +} + +/* + * This is for timestamp processing. In Windows mode, all 32bit fields of second + * CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In + * 'no data' packet the value of this field is 0x90ffffff. + */ +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode) +{ + __be32 param = cpu_to_be32(mode); + return efw_transaction(efw, EFC_CAT_TRANSPORT, + EFC_CMD_TRANSPORT_SET_TX_MODE, + ¶m, sizeof(param), NULL, 0); +} + +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_CAPS, + NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo)); + if (err < 0) + goto end; + + be32_to_cpus(&hwinfo->flags); + be32_to_cpus(&hwinfo->guid_hi); + be32_to_cpus(&hwinfo->guid_lo); + be32_to_cpus(&hwinfo->type); + be32_to_cpus(&hwinfo->version); + be32_to_cpus(&hwinfo->supported_clocks); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels); + be32_to_cpus(&hwinfo->phys_out); + be32_to_cpus(&hwinfo->phys_in); + be32_to_cpus(&hwinfo->phys_out_grp_count); + be32_to_cpus(&hwinfo->phys_in_grp_count); + be32_to_cpus(&hwinfo->midi_out_ports); + be32_to_cpus(&hwinfo->midi_in_ports); + be32_to_cpus(&hwinfo->max_sample_rate); + be32_to_cpus(&hwinfo->min_sample_rate); + be32_to_cpus(&hwinfo->dsp_version); + be32_to_cpus(&hwinfo->arm_version); + be32_to_cpus(&hwinfo->mixer_playback_channels); + be32_to_cpus(&hwinfo->mixer_capture_channels); + be32_to_cpus(&hwinfo->fpga_version); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x); + + /* ensure terminated */ + hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; + hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; +end: + return err; +} + +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len) +{ + __be32 *buf = (__be32 *)meters; + unsigned int i; + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_POLLED, + NULL, 0, (__be32 *)meters, len); + if (err >= 0) + for (i = 0; i < len / sizeof(u32); i++) + be32_to_cpus(&buf[i]); + + return err; +} + +static int +command_get_clock(struct snd_efw *efw, struct efc_clock *clock) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_GET_CLOCK, + NULL, 0, + (__be32 *)clock, sizeof(struct efc_clock)); + if (err >= 0) { + be32_to_cpus(&clock->source); + be32_to_cpus(&clock->sampling_rate); + be32_to_cpus(&clock->index); + } + + return err; +} + +/* give UINT_MAX if set nothing */ +static int +command_set_clock(struct snd_efw *efw, + unsigned int source, unsigned int rate) +{ + struct efc_clock clock = {0}; + int err; + + /* check arguments */ + if ((source == UINT_MAX) && (rate == UINT_MAX)) { + err = -EINVAL; + goto end; + } + + /* get current status */ + err = command_get_clock(efw, &clock); + if (err < 0) + goto end; + + /* no need */ + if ((clock.source == source) && (clock.sampling_rate == rate)) + goto end; + + /* set params */ + if ((source != UINT_MAX) && (clock.source != source)) + clock.source = source; + if ((rate != UINT_MAX) && (clock.sampling_rate != rate)) + clock.sampling_rate = rate; + clock.index = 0; + + cpu_to_be32s(&clock.source); + cpu_to_be32s(&clock.sampling_rate); + cpu_to_be32s(&clock.index); + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_SET_CLOCK, + (__be32 *)&clock, sizeof(struct efc_clock), + NULL, 0); + if (err < 0) + goto end; + + /* + * With firmware version 5.8, just after changing clock state, these + * parameters are not immediately retrieved by get command. In my + * trial, there needs to be 100msec to get changed parameters. + */ + msleep(150); +end: + return err; +} + +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *source = clock.source; + + return err; +} + +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *rate = clock.sampling_rate; + + return err; +} + +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate) +{ + return command_set_clock(efw, UINT_MAX, rate); +} + diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c new file mode 100644 index 0000000..aac91d8 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -0,0 +1,190 @@ +/* + * fireworks_transaction.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks have its own transaction. The transaction can be delivered by AV/C + * Vendor Specific command. But at least Windows driver and firmware version 5.5 + * or later don't use it. + * + * Transaction substance: + * At first, 6 data exist. Following to the 6 data, parameters for each + * commands exists. All of parameters are 32 bit alighed to big endian. + * data[0]: Length of transaction substance + * data[1]: Transaction version + * data[2]: Sequence number. This is incremented by the device + * data[3]: transaction category + * data[4]: transaction command + * data[5]: return value in response. + * data[6-]: parameters + * + * Transaction address: + * command: 0xecc000000000 + * response: 0xecc080000000 (default) + * + * I note that the address for response can be changed by command. But this + * module uses the default address. + */ +#include "./fireworks.h" + +#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000 +#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000 + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define EFC_TIMEOUT_MS 125 + +static DEFINE_SPINLOCK(transaction_queues_lock); +static LIST_HEAD(transaction_queues); + +enum transaction_queue_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE +}; + +struct transaction_queue { + struct list_head list; + struct fw_unit *unit; + void *buf; + unsigned int size; + u32 seqnum; + enum transaction_queue_state state; + wait_queue_head_t wait; +}; + +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size) +{ + struct transaction_queue t; + unsigned int tries; + int ret; + + t.unit = unit; + t.buf = resp; + t.size = resp_size; + t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + + spin_lock_irq(&transaction_queues_lock); + list_add_tail(&t.list, &transaction_queues); + spin_unlock_irq(&transaction_queues_lock); + + tries = 0; + do { + ret = snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, + MEMORY_SPACE_EFW_COMMAND, + (void *)cmd, cmd_size, 0); + if (ret < 0) + break; + + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(EFC_TIMEOUT_MS)); + + if (t.state == STATE_COMPLETE) { + ret = t.size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "EFW transaction timed out\n"); + ret = -EIO; + break; + } + } while (1); + + spin_lock_irq(&transaction_queues_lock); + list_del(&t.list); + spin_unlock_irq(&transaction_queues_lock); + + return ret; +} + +static void +efw_response(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct fw_device *device; + struct transaction_queue *t; + unsigned long flags; + int rcode; + u32 seqnum; + + rcode = RCODE_TYPE_ERROR; + if (length < sizeof(struct snd_efw_transaction)) { + rcode = RCODE_DATA_ERROR; + goto end; + } else if (offset != MEMORY_SPACE_EFW_RESPONSE) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + + seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); + + spin_lock_irqsave(&transaction_queues_lock, flags); + list_for_each_entry(t, &transaction_queues, list) { + device = fw_parent_device(t->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) { + t->state = STATE_COMPLETE; + t->size = min_t(unsigned int, length, t->size); + memcpy(t->buf, data, t->size); + wake_up(&t->wait); + rcode = RCODE_COMPLETE; + } + } + spin_unlock_irqrestore(&transaction_queues_lock, flags); +end: + fw_send_response(card, request, rcode); +} + +void snd_efw_transaction_bus_reset(struct fw_unit *unit) +{ + struct transaction_queue *t; + + spin_lock_irq(&transaction_queues_lock); + list_for_each_entry(t, &transaction_queues, list) { + if ((t->unit == unit) && + (t->state == STATE_PENDING)) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transaction_queues_lock); +} + +static struct fw_address_handler resp_register_handler = { + .length = SND_EFW_RESPONSE_MAXIMUM_BYTES, + .address_callback = efw_response +}; + +int snd_efw_transaction_register(void) +{ + static const struct fw_address_region resp_register_region = { + .start = MEMORY_SPACE_EFW_RESPONSE, + .end = MEMORY_SPACE_EFW_RESPONSE + + SND_EFW_RESPONSE_MAXIMUM_BYTES + }; + return fw_core_add_address_handler(&resp_register_handler, + &resp_register_region); +} + +void snd_efw_transaction_unregister(void) +{ + WARN_ON(!list_empty(&transaction_queues)); + fw_core_remove_address_handler(&resp_register_handler); +}
Fireworks manages connections by CMP and can transmit/receive AMDTP streams with a few quirks. This commit adds functionality to start/stop the streams.
Major Fireworks products don't support 'SYT-Match' clock source mode, except for AudioFire12/8(till 2009 July) with firmware version 1.0. Already in previous commit, this driver don't support such old firmwares. So this commit adds support for non 'SYT-Match' clock source modes.
I note that this driver has a short gap for MIDI streams when starting PCM stream. When AMDTP streams are running only for MIDI data and PCM data is going to be joined at different sampling rate, then AMDTP streams are stopped once and started again after changing sampling rate.
Unfortunately, Fireworks is not fully compliant to IEC 61883-1/6. Some commits following to this commit add these quirks.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/Makefile | 3 +- sound/firewire/fireworks/fireworks.c | 64 ++++- sound/firewire/fireworks/fireworks.h | 27 +++ sound/firewire/fireworks/fireworks_stream.c | 349 ++++++++++++++++++++++++++++ 4 files changed, 441 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/fireworks/fireworks_stream.c
diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile index a6ce214..1bccb65 100644 --- a/sound/firewire/fireworks/Makefile +++ b/sound/firewire/fireworks/Makefile @@ -1,2 +1,3 @@ -snd-fireworks-objs := fireworks_transaction.o fireworks_command.o fireworks.o +snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ + fireworks_stream.o fireworks.o obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 6fa3a5d..307fb2f 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -98,6 +98,57 @@ get_hardware_info(struct snd_efw *efw)
if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE)) efw->resp_addr_changable = true; + + efw->supported_sampling_rate = 0; + if ((hwinfo->min_sample_rate <= 22050) + && (22050 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050; + if ((hwinfo->min_sample_rate <= 32000) + && (32000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000; + if ((hwinfo->min_sample_rate <= 44100) + && (44100 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100; + if ((hwinfo->min_sample_rate <= 48000) + && (48000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000; + if ((hwinfo->min_sample_rate <= 88200) + && (88200 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200; + if ((hwinfo->min_sample_rate <= 96000) + && (96000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000; + if ((hwinfo->min_sample_rate <= 176400) + && (176400 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400; + if ((hwinfo->min_sample_rate <= 192000) + && (192000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000; + + /* the number of MIDI ports, not of MIDI conformant data channels */ + if (hwinfo->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS || + hwinfo->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) { + err = -EIO; + goto end; + } + efw->midi_out_ports = hwinfo->midi_out_ports; + efw->midi_in_ports = hwinfo->midi_in_ports; + + if (hwinfo->amdtp_tx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels; + efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x; + efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x; + efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels; + efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x; + efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x; end: kfree(hwinfo); return err; @@ -155,10 +206,16 @@ efw_probe(struct fw_unit *unit, if (err < 0) goto error;
- err = snd_card_register(card); + err = snd_efw_stream_init_duplex(efw); if (err < 0) goto error;
+ err = snd_card_register(card); + if (err < 0) { + snd_efw_stream_destroy_duplex(efw); + goto error; + } + dev_set_drvdata(&unit->device, efw); end: mutex_unlock(&devices_mutex); @@ -172,12 +229,17 @@ error: static void efw_update(struct fw_unit *unit) { struct snd_efw *efw = dev_get_drvdata(&unit->device); + snd_efw_transaction_bus_reset(efw->unit); + snd_efw_stream_update_duplex(efw); }
static void efw_remove(struct fw_unit *unit) { struct snd_efw *efw = dev_get_drvdata(&unit->device); + + snd_efw_stream_destroy_duplex(efw); + snd_card_disconnect(efw->card); snd_card_free_when_closed(efw->card); } diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index e999802..ce511be 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -22,9 +22,15 @@ #include <sound/initval.h> #include <sound/pcm.h>
+#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" #include "../cmp.h" #include "../lib.h"
+#define SND_EFW_MAX_MIDI_OUT_PORTS 2 +#define SND_EFW_MAX_MIDI_IN_PORTS 2 + #define SND_EFW_MULTIPLIER_MODES 3 #define HWINFO_NAME_SIZE_BYTES 32 #define HWINFO_MAX_CAPS_GROUPS 8 @@ -54,6 +60,21 @@ struct snd_efw { /* for transaction */ u32 seqnum; bool resp_addr_changable; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + unsigned int supported_sampling_rate; + unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES]; + unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES]; + + struct amdtp_stream *master; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + atomic_t capture_substreams; + atomic_t playback_substreams; };
struct snd_efw_transaction { @@ -156,6 +177,12 @@ int snd_efw_command_get_clock_source(struct snd_efw *efw, int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate); int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
+int snd_efw_stream_init_duplex(struct snd_efw *efw); +int snd_efw_stream_start_duplex(struct snd_efw *efw, int sampling_rate); +void snd_efw_stream_stop_duplex(struct snd_efw *efw); +void snd_efw_stream_update_duplex(struct snd_efw *efw); +void snd_efw_stream_destroy_duplex(struct snd_efw *efw); + #define SND_EFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c new file mode 100644 index 0000000..ec62aa6 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -0,0 +1,349 @@ +/* + * fireworks_stream.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "./fireworks.h" + +#define CALLBACK_TIMEOUT 100 + +static unsigned int freq_table[] = { + /* multiplier mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* multiplier mode 1 */ + [3] = 88200, + [4] = 96000, + /* multiplier mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +static inline unsigned int +get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + if (freq_table[i] == sampling_rate) { + *mode = get_multiplier_mode_with_index(i); + return 0; + } + } + + return -EINVAL; +} + +static int +init_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &efw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, efw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + } +end: + return err; +} + +static void +stop_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream); + + if (stream == &efw->tx_stream) + cmp_connection_break(&efw->out_conn); + else + cmp_connection_break(&efw->in_conn); +} + +static int +start_stream(struct snd_efw *efw, struct amdtp_stream *stream, + unsigned int sampling_rate) +{ + struct cmp_connection *conn; + unsigned int mode, pcm_channels, midi_ports; + int err; + + err = snd_efw_get_multiplier_mode(sampling_rate, &mode); + if (err < 0) + goto end; + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + pcm_channels = efw->pcm_capture_channels[mode]; + midi_ports = efw->midi_out_ports; + } else { + conn = &efw->in_conn; + pcm_channels = efw->pcm_playback_channels[mode]; + midi_ports = efw->midi_in_ports; + } + + amdtp_stream_set_parameters(stream, sampling_rate, + pcm_channels, midi_ports); + + /* establish connection via CMP */ + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); + if (err < 0) + goto end; + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); + if (err < 0) { + stop_stream(efw, stream); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + stop_stream(efw, stream); + err = -ETIMEDOUT; + } +end: + return err; +} + +static void +destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + stop_stream(efw, stream); + + amdtp_stream_destroy(stream); + + if (stream == &efw->tx_stream) + cmp_connection_destroy(&efw->out_conn); + else + cmp_connection_destroy(&efw->in_conn); +} + +static int +get_sync_mode(struct snd_efw *efw, enum cip_flags *sync_mode) +{ + enum snd_efw_clock_source clock_source; + int err; + + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + return err; + + if (clock_source == SND_EFW_CLOCK_SOURCE_SYTMATCH) + return -ENOSYS; + + *sync_mode = CIP_SYNC_TO_DEVICE; + return 0; +} + +static int +check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &efw->tx_stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&efw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_efw_stream_init_duplex(struct snd_efw *efw) +{ + int err; + + err = init_stream(efw, &efw->tx_stream); + if (err < 0) + goto end; + + err = init_stream(efw, &efw->rx_stream); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + goto end; + } + + /* set IEC61883 compliant mode (actually not fully compliant...) */ + err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + destroy_stream(efw, &efw->rx_stream); + } +end: + return err; +} + +int snd_efw_stream_start_duplex(struct snd_efw *efw, int rate) +{ + struct amdtp_stream *master, *slave; + atomic_t *slave_substreams; + enum cip_flags sync_mode; + unsigned int curr_rate; + int err = 0; + + mutex_lock(&efw->mutex); + + /* Need no substreams */ + if ((atomic_read(&efw->playback_substreams) == 0) && + (atomic_read(&efw->capture_substreams) == 0)) + goto end; + + err = get_sync_mode(efw, &sync_mode); + if (err < 0) + goto end; + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master = &efw->tx_stream; + slave = &efw->rx_stream; + slave_substreams = &efw->playback_substreams; + } else { + master = &efw->rx_stream; + slave = &efw->tx_stream; + slave_substreams = &efw->capture_substreams; + } + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(efw, master); + if (err < 0) + goto end; + + /* packet queueing error */ + if (amdtp_streaming_error(slave)) + stop_stream(efw, slave); + if (amdtp_streaming_error(master)) + stop_stream(efw, master); + + /* stop streams if rate is different */ + err = snd_efw_command_get_sampling_rate(efw, &curr_rate); + if (err < 0) + goto end; + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + stop_stream(efw, slave); + stop_stream(efw, master); + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + efw->master = master; + + err = snd_efw_command_set_sampling_rate(efw, rate); + if (err < 0) + goto end; + + err = start_stream(efw, master, rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP master stream:%d\n", err); + goto end; + } + } + + /* start slave if needed */ + if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) { + err = start_stream(efw, slave, rate); + if (err < 0) { + dev_err(&efw->unit->device, + "fail to start AMDTP slave stream:%d\n", err); + stop_stream(efw, master); + } + } +end: + mutex_unlock(&efw->mutex); + return err; +} + +void snd_efw_stream_stop_duplex(struct snd_efw *efw) +{ + struct amdtp_stream *master, *slave; + atomic_t *master_substreams, *slave_substreams; + + mutex_lock(&efw->mutex); + + if (efw->master == &efw->rx_stream) { + slave = &efw->tx_stream; + master = &efw->rx_stream; + slave_substreams = &efw->capture_substreams; + master_substreams = &efw->playback_substreams; + } else { + slave = &efw->rx_stream; + master = &efw->tx_stream; + slave_substreams = &efw->playback_substreams; + master_substreams = &efw->capture_substreams; + } + + if (atomic_read(slave_substreams) == 0) { + stop_stream(efw, slave); + + if (atomic_read(master_substreams) == 0) + stop_stream(efw, master); + } + + mutex_unlock(&efw->mutex); +} + +void snd_efw_stream_update_duplex(struct snd_efw *efw) +{ + if ((cmp_connection_update(&efw->out_conn) < 0) || + (cmp_connection_update(&efw->in_conn) < 0)) { + mutex_lock(&efw->mutex); + stop_stream(efw, &efw->rx_stream); + stop_stream(efw, &efw->tx_stream); + mutex_unlock(&efw->mutex); + } else { + amdtp_stream_update(&efw->rx_stream); + amdtp_stream_update(&efw->tx_stream); + } +} + +void snd_efw_stream_destroy_duplex(struct snd_efw *efw) +{ + mutex_lock(&efw->mutex); + + destroy_stream(efw, &efw->rx_stream); + destroy_stream(efw, &efw->tx_stream); + + mutex_unlock(&efw->mutex); +}
Fireworks has a quirk to transmit empty packets with TAG0. This commit adds handling this quirk for full duplex stream synchronization.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 9 ++++++--- sound/firewire/amdtp.h | 2 ++ sound/firewire/fireworks/fireworks_stream.c | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 5b88461..dce4c6d 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -815,7 +815,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) }; unsigned int header_size; enum dma_data_direction dir; - int type, err; + int type, tag, err;
mutex_lock(&s->mutex);
@@ -869,9 +869,12 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) } while (s->packet_index > 0);
/* NOTE: TAG1 matches CIP. This just affects in stream. */ + tag = FW_ISO_CONTEXT_MATCH_TAG1; + if (s->flags & CIP_EMPTY_WITH_TAG0) + tag |= FW_ISO_CONTEXT_MATCH_TAG0; + s->callbacked = false; - err = fw_iso_context_start(s->context, -1, 0, - FW_ISO_CONTEXT_MATCH_TAG1); + err = fw_iso_context_start(s->context, -1, 0, tag); if (err < 0) goto err_context;
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 3de3463..96b96ec 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -18,11 +18,13 @@ * the overall sample rate comes out right. * @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is * generated by in packets. Defaultly this driver generates timestamp. + * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. */ enum cip_flags { CIP_NONBLOCKING = 0x00, CIP_BLOCKING = 0x01, CIP_SYNC_TO_DEVICE = 0x02, + CIP_EMPTY_WITH_TAG0 = 0x04, };
/** diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index ec62aa6..360e871 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -194,6 +194,8 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) err = init_stream(efw, &efw->tx_stream); if (err < 0) goto end; + /* Fireworks transmits NODATA packets with TAG0. */ + efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0;
err = init_stream(efw, &efw->rx_stream); if (err < 0) {
Fireworks has a quirk for the value of dbc field in transmitted packets. For Fireworks, dbc means the end of events in current packet. This is out of specification.
For example, AudioFire4: CIP0 CIP1 Payload 01070092 90FFFFFF 02 0107009A 9001E17B 3A <- 010700A2 9001F6E5 3A 010700A2 90FFFFFF 02 010700AA 9001104F 3A <- 010700B2 900125B9 3A 010700BA 90013B23 3A 010700BA 90FFFFFF 02 010700C2 9001548E 3A <- 010700CA 900169F8 3A 010700CA 90FFFFFF 02 010700D2 90018362 3A <- 010700DA 900198CC 3A
According to IEC 61883-1/6, a packet following to empty packet has the same value for its dbc. But for Fireworks, it's incremented and empty packet has the same value as previous packet in dbc field.
This commit adds a flag for Fireworks and some codes to checking dbc continuity.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 15 +++++++++++++-- sound/firewire/amdtp.h | 3 +++ sound/firewire/fireworks/fireworks_stream.c | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index dce4c6d..51aad68 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -620,6 +620,7 @@ static void handle_in_packet(struct amdtp_stream *s, u32 cip_header[2]; unsigned int data_blocks, data_block_quadlets, data_block_counter; struct snd_pcm_substream *pcm = NULL; + bool lost;
cip_header[0] = be32_to_cpu(buffer[0]); cip_header[1] = be32_to_cpu(buffer[1]); @@ -658,7 +659,13 @@ static void handle_in_packet(struct amdtp_stream *s,
/* Check data block counter continuity */ data_block_counter = cip_header[0] & AMDTP_DBC_MASK; - if (data_block_counter != s->data_block_counter) { + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + lost = data_block_counter != s->data_block_counter; + else + lost = data_block_counter != + ((s->data_block_counter + data_blocks) & 0xff); + + if (lost) { dev_info(&s->unit->device, "Detect discontinuity of CIP: %02X %02X\n", s->data_block_counter, data_block_counter); @@ -676,7 +683,11 @@ static void handle_in_packet(struct amdtp_stream *s, amdtp_pull_midi(s, buffer, data_blocks); }
- s->data_block_counter = (data_block_counter + data_blocks) & 0xff; + if (s->flags & CIP_DBC_IS_END_EVENT) + s->data_block_counter = data_block_counter; + else + s->data_block_counter = + (data_block_counter + data_blocks) & 0xff; end: if (queue_in_packet(s) < 0) goto err; diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 96b96ec..1ca1a19 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -19,12 +19,15 @@ * @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is * generated by in packets. Defaultly this driver generates timestamp. * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. + * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet + * corresponds to the end of event in the packet. Out of IEC 61883. */ enum cip_flags { CIP_NONBLOCKING = 0x00, CIP_BLOCKING = 0x01, CIP_SYNC_TO_DEVICE = 0x02, CIP_EMPTY_WITH_TAG0 = 0x04, + CIP_DBC_IS_END_EVENT = 0x08, };
/** diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index 360e871..d687b04 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -196,6 +196,8 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) goto end; /* Fireworks transmits NODATA packets with TAG0. */ efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; + /* Fireworks has its own meaning for dbc. */ + efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT;
err = init_stream(efw, &efw->rx_stream); if (err < 0) {
One of Fireworks firmware, named as 'AudioFire9', seems to transmit packets with wrong value of dbs. It's always 0x11 but actual size of data block is different.
This commit adds a flag for this quirk and some codes to calculate correct size.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 2 ++ sound/firewire/amdtp.h | 3 +++ sound/firewire/fireworks/fireworks.c | 2 ++ sound/firewire/fireworks/fireworks.h | 3 +++ sound/firewire/fireworks/fireworks_stream.c | 3 +++ 5 files changed, 13 insertions(+)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 51aad68..3903387 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -653,6 +653,8 @@ static void handle_in_packet(struct amdtp_stream *s, cip_header[0]); goto err; } + if (s->flags & CIP_WRONG_DBS) + data_block_quadlets = s->data_block_quadlets;
data_blocks = (payload_quadlets - 2) / data_block_quadlets; } diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 1ca1a19..f334ae5 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -21,6 +21,8 @@ * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. * @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet * corresponds to the end of event in the packet. Out of IEC 61883. + * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. + * The value of data_block_quadlets is used instead of reported value. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -28,6 +30,7 @@ enum cip_flags { CIP_SYNC_TO_DEVICE = 0x02, CIP_EMPTY_WITH_TAG0 = 0x04, CIP_DBC_IS_END_EVENT = 0x08, + CIP_WRONG_DBS = 0x10, };
/** diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 307fb2f..02b3259 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -205,6 +205,8 @@ efw_probe(struct fw_unit *unit, err = get_hardware_info(efw); if (err < 0) goto error; + if (entry->model_id == MODEL_ECHO_AUDIOFIRE_9) + efw->is_af9 = true;
err = snd_efw_stream_init_duplex(efw); if (err < 0) diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index ce511be..9534e93 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -61,6 +61,9 @@ struct snd_efw { u32 seqnum; bool resp_addr_changable;
+ /* for quirks */ + bool is_af9; + unsigned int midi_in_ports; unsigned int midi_out_ports;
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index d687b04..7447af7 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -198,6 +198,9 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; /* Fireworks has its own meaning for dbc. */ efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; + /* AudioFire9 always reports wrong dbs. */ + if (efw->is_af9) + efw->tx_stream.flags |= CIP_WRONG_DBS;
err = init_stream(efw, &efw->rx_stream); if (err < 0) {
Fireworks firmware version 5.5 reports fix interval for dbc in each packet.
For example, AudioFire4: CIP0 CIP1 Payload 00070000 900484FF 72 00070008 9004A8FF 72 00070008 90FFFFFF 02 00070010 9004D0FF 72 00070018 9004C4FF 72 00070020 9004E8FF 72 00070020 90FFFFFF 02 00070028 900410FE 72
The interval of each dbc should be 16 except for empty packet but it's still 8.
This commit adds a flag for this quirk and codes to refer to a fixed value.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 15 +++++++++++---- sound/firewire/amdtp.h | 3 +++ sound/firewire/fireworks/fireworks.c | 1 + sound/firewire/fireworks/fireworks.h | 1 + sound/firewire/fireworks/fireworks_stream.c | 3 +++ 5 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 3903387..4a7cc1f 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -618,7 +618,8 @@ static void handle_in_packet(struct amdtp_stream *s, __be32 *buffer) { u32 cip_header[2]; - unsigned int data_blocks, data_block_quadlets, data_block_counter; + unsigned int data_blocks, data_block_quadlets, data_block_counter, + dbc_interval; struct snd_pcm_substream *pcm = NULL; bool lost;
@@ -661,11 +662,17 @@ static void handle_in_packet(struct amdtp_stream *s,
/* Check data block counter continuity */ data_block_counter = cip_header[0] & AMDTP_DBC_MASK; - if (!(s->flags & CIP_DBC_IS_END_EVENT)) + if (!(s->flags & CIP_DBC_IS_END_EVENT)) { lost = data_block_counter != s->data_block_counter; - else + } else { + if ((data_blocks > 0) && (s->tx_dbc_interval > 0)) + dbc_interval = s->tx_dbc_interval; + else + dbc_interval = data_blocks; + lost = data_block_counter != - ((s->data_block_counter + data_blocks) & 0xff); + ((s->data_block_counter + dbc_interval) & 0xff); + }
if (lost) { dev_info(&s->unit->device, diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index f334ae5..05f1b8b 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -119,6 +119,9 @@ struct amdtp_stream {
struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];
+ /* quirk: fixed interval of dbc between previos/current packets. */ + unsigned int tx_dbc_interval; + bool callbacked; wait_queue_head_t callback_wait; struct amdtp_stream *sync_slave; diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 02b3259..25997f1 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -84,6 +84,7 @@ get_hardware_info(struct snd_efw *efw) (hwinfo->arm_version >> 16) & 0xff); if (err < 0) goto end; + efw->firmware_version = hwinfo->arm_version;
strcpy(efw->card->driver, "Fireworks"); strcpy(efw->card->shortname, hwinfo->model_name); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 9534e93..3f99851 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -63,6 +63,7 @@ struct snd_efw {
/* for quirks */ bool is_af9; + u32 firmware_version;
unsigned int midi_in_ports; unsigned int midi_out_ports; diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index 7447af7..c75c2ef 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -201,6 +201,9 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) /* AudioFire9 always reports wrong dbs. */ if (efw->is_af9) efw->tx_stream.flags |= CIP_WRONG_DBS; + /* Firmware version 5.5 reports fixed interval for dbc. */ + if (efw->firmware_version == 0x5050000) + efw->tx_stream.tx_dbc_interval = 8;
err = init_stream(efw, &efw->rx_stream); if (err < 0) {
Fireworks has a quirk to reset data block counter at bus reset.
This commit adds a flag of CIP_SKIP_DBC_ZERO_CHECK. This flag has an effect to skip checking dbc continuity when dbc is zero.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 4 +++- sound/firewire/amdtp.h | 3 +++ sound/firewire/fireworks/fireworks_stream.c | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 4a7cc1f..5e13b3f 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -662,7 +662,9 @@ static void handle_in_packet(struct amdtp_stream *s,
/* Check data block counter continuity */ data_block_counter = cip_header[0] & AMDTP_DBC_MASK; - if (!(s->flags & CIP_DBC_IS_END_EVENT)) { + if ((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && data_block_counter == 0) { + lost = false; + } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) { lost = data_block_counter != s->data_block_counter; } else { if ((data_blocks > 0) && (s->tx_dbc_interval > 0)) diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 05f1b8b..42c75c9 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -23,6 +23,8 @@ * corresponds to the end of event in the packet. Out of IEC 61883. * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. * The value of data_block_quadlets is used instead of reported value. + * @SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is + * skipped for detecting discontinuity. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -31,6 +33,7 @@ enum cip_flags { CIP_EMPTY_WITH_TAG0 = 0x04, CIP_DBC_IS_END_EVENT = 0x08, CIP_WRONG_DBS = 0x10, + CIP_SKIP_DBC_ZERO_CHECK = 0x20, };
/** diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index c75c2ef..3a3f203 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -198,6 +198,8 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; /* Fireworks has its own meaning for dbc. */ efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; + /* Fireworks reset dbc at bus reset. */ + efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; /* AudioFire9 always reports wrong dbs. */ if (efw->is_af9) efw->tx_stream.flags |= CIP_WRONG_DBS;
This commit adds proc interface to output infomation for debugging. - firmware information - sampling rate and clock source - physical metering (linear value)
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/Makefile | 2 +- sound/firewire/fireworks/fireworks.c | 17 +++ sound/firewire/fireworks/fireworks.h | 11 ++ sound/firewire/fireworks/fireworks_proc.c | 214 ++++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireworks/fireworks_proc.c
diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile index 1bccb65..52bd15e 100644 --- a/sound/firewire/fireworks/Makefile +++ b/sound/firewire/fireworks/Makefile @@ -1,3 +1,3 @@ snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ - fireworks_stream.o fireworks.o + fireworks_stream.o fireworks_proc.o fireworks.o obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 25997f1..311e4a6 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -150,6 +150,21 @@ get_hardware_info(struct snd_efw *efw) efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels; efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x; efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x; + + /* Hardware metering. */ + if (hwinfo->phys_in_grp_count > HWINFO_MAX_CAPS_GROUPS || + hwinfo->phys_out_grp_count > HWINFO_MAX_CAPS_GROUPS) { + return -EIO; + goto end; + } + efw->phys_in = hwinfo->phys_in; + efw->phys_out = hwinfo->phys_out; + efw->phys_in_grp_count = hwinfo->phys_in_grp_count; + efw->phys_out_grp_count = hwinfo->phys_out_grp_count; + memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count); + memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count); end: kfree(hwinfo); return err; @@ -209,6 +224,8 @@ efw_probe(struct fw_unit *unit, if (entry->model_id == MODEL_ECHO_AUDIOFIRE_9) efw->is_af9 = true;
+ snd_efw_proc_init(efw); + err = snd_efw_stream_init_duplex(efw); if (err < 0) goto error; diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 3f99851..eef7ad8 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -21,6 +21,7 @@ #include <sound/core.h> #include <sound/initval.h> #include <sound/pcm.h> +#include <sound/info.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -79,6 +80,14 @@ struct snd_efw { struct cmp_connection in_conn; atomic_t capture_substreams; atomic_t playback_substreams; + + /* hardware metering parameters */ + unsigned int phys_out; + unsigned int phys_in; + unsigned int phys_out_grp_count; + unsigned int phys_in_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; };
struct snd_efw_transaction { @@ -187,6 +196,8 @@ void snd_efw_stream_stop_duplex(struct snd_efw *efw); void snd_efw_stream_update_duplex(struct snd_efw *efw); void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
+void snd_efw_proc_init(struct snd_efw *efw); + #define SND_EFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c new file mode 100644 index 0000000..631c91f --- /dev/null +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -0,0 +1,214 @@ +/* + * fireworks_proc.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./fireworks.h" + +static inline const char* +get_phys_name(struct snd_efw_phys_grp *grp, bool input) +{ + const char *const ch_type[] = { + "Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", "Mirroring", + "Headphones", "I2S", "Guitar", "Pirzo Guitar", "Guitar String", + }; + + if (grp->type < ARRAY_SIZE(ch_type)) + return ch_type[grp->type]; + else if (input) + return "Input"; + else + return "Output"; +} + +static void +proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned short i; + struct snd_efw_hwinfo *hwinfo; + + hwinfo = kmalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return; + + if (snd_efw_command_get_hwinfo(efw, hwinfo) < 0) + goto end; + + snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo->guid_hi); + snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo->guid_lo); + snd_iprintf(buffer, "type: 0x%X\n", hwinfo->type); + snd_iprintf(buffer, "version: 0x%X\n", hwinfo->version); + snd_iprintf(buffer, "vendor_name: %s\n", hwinfo->vendor_name); + snd_iprintf(buffer, "model_name: %s\n", hwinfo->model_name); + + snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo->dsp_version); + snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo->arm_version); + snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo->fpga_version); + + snd_iprintf(buffer, "flags: 0x%X\n", hwinfo->flags); + + snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo->max_sample_rate); + snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo->min_sample_rate); + snd_iprintf(buffer, "supported_clock: 0x%X\n", + hwinfo->supported_clocks); + + snd_iprintf(buffer, "phys out: 0x%X\n", hwinfo->phys_out); + snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo->phys_in); + + snd_iprintf(buffer, "phys in grps: 0x%X\n", + hwinfo->phys_in_grp_count); + for (i = 0; i < hwinfo->phys_in_grp_count; i++) { + snd_iprintf(buffer, + "phys in grp[0x%d]: type 0x%d, count 0x%d\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "phys out grps: 0x%X\n", + hwinfo->phys_out_grp_count); + for (i = 0; i < hwinfo->phys_out_grp_count; i++) { + snd_iprintf(buffer, + "phys out grps[0x%d]: type 0x%d, count 0x%d\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels); + snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels); + snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_4x); + snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_4x); + + snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo->midi_out_ports); + snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo->midi_in_ports); + + snd_iprintf(buffer, "mixer playback channels: 0x%X\n", + hwinfo->mixer_playback_channels); + snd_iprintf(buffer, "mixer capture channels: 0x%X\n", + hwinfo->mixer_capture_channels); +end: + kfree(hwinfo); +} + +static void +proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + enum snd_efw_clock_source clock_source; + unsigned int sampling_rate; + + if (snd_efw_command_get_clock_source(efw, &clock_source) < 0) + return; + + if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0) + return; + + snd_iprintf(buffer, "Clock Source: %d\n", clock_source); + snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate); +} + +/* + * NOTE: + * dB = 20 * log10(linear / 0x01000000) + * -144.0 dB when linear is 0 + */ +static void +proc_read_phys_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + struct snd_efw_phys_meters *meters; + unsigned int g, c, m, max, size; + const char *name; + u32 *linear; + int err; + + size = sizeof(struct snd_efw_phys_meters) + + (efw->phys_in + efw->phys_out) * sizeof(u32); + meters = kzalloc(size, GFP_KERNEL); + if (meters == NULL) + return; + + err = snd_efw_command_get_phys_meters(efw, meters, size); + if (err < 0) + goto end; + + snd_iprintf(buffer, "Physical Meters:\n"); + + m = 0; + max = min(efw->phys_out, meters->out_meters); + linear = meters->values; + snd_iprintf(buffer, " %d Outputs:\n", max); + for (g = 0; g < efw->phys_out_grp_count; g++) { + name = get_phys_name(&efw->phys_out_grps[g], false); + for (c = 0; c < efw->phys_out_grps[g].count; c++) { + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } + } + + m = 0; + max = min(efw->phys_in, meters->in_meters); + linear = meters->values + meters->out_meters; + snd_iprintf(buffer, " %d Inputs:\n", max); + for (g = 0; g < efw->phys_in_grp_count; g++) { + name = get_phys_name(&efw->phys_in_grps[g], true); + for (c = 0; c < efw->phys_in_grps[g].count; c++) + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } +end: + kfree(meters); +} + +static void +add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name, + void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(efw->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, efw, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_efw_proc_init(struct snd_efw *efw) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(efw->card, "firewire", + efw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(efw, root, "clock", proc_read_clock); + add_node(efw, root, "firmware", proc_read_hwinfo); + add_node(efw, root, "meters", proc_read_phys_meters); +}
This commit adds a functionality to capture/playback MIDI messages.
When no AMDTP streams are running, this driver starts AMDTP stream for MIDI stream at current sampling rate.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/fireworks/Makefile | 3 +- sound/firewire/fireworks/fireworks.c | 6 ++ sound/firewire/fireworks/fireworks.h | 3 + sound/firewire/fireworks/fireworks_midi.c | 147 ++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireworks/fireworks_midi.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 0b85ebd..36a1212 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -64,6 +64,7 @@ config SND_SCS1X config SND_FIREWORKS tristate "Echo Fireworks board module support" select SND_FIREWIRE_LIB + select SND_RAWMIDI help Say Y here to include support for FireWire devices based on Echo Digital Audio Fireworks board: diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile index 52bd15e..a2cecc6 100644 --- a/sound/firewire/fireworks/Makefile +++ b/sound/firewire/fireworks/Makefile @@ -1,3 +1,4 @@ snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ - fireworks_stream.o fireworks_proc.o fireworks.o + fireworks_stream.o fireworks_proc.o fireworks_midi.o \ + fireworks.o obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 311e4a6..9204c7c 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -226,6 +226,12 @@ efw_probe(struct fw_unit *unit,
snd_efw_proc_init(efw);
+ if (efw->midi_out_ports || efw->midi_in_ports) { + err = snd_efw_create_midi_devices(efw); + if (err < 0) + goto error; + } + err = snd_efw_stream_init_duplex(efw); if (err < 0) goto error; diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index eef7ad8..d181969 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -22,6 +22,7 @@ #include <sound/initval.h> #include <sound/pcm.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -198,6 +199,8 @@ void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
void snd_efw_proc_init(struct snd_efw *efw);
+int snd_efw_create_midi_devices(struct snd_efw *efw); + #define SND_EFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c new file mode 100644 index 0000000..cbf34e9 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -0,0 +1,147 @@ +/* + * fireworks_midi.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "fireworks.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + atomic_inc(&efw->capture_substreams); + return snd_efw_stream_start_duplex(efw, 0); +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + atomic_inc(&efw->playback_substreams); + return snd_efw_stream_start_duplex(efw, 0); +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + atomic_dec(&efw->capture_substreams); + snd_efw_stream_stop_duplex(efw); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + atomic_dec(&efw->playback_substreams); + snd_efw_stream_stop_duplex(efw); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&efw->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&efw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&efw->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&efw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_efw *efw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", efw->card->shortname, subs->number + 1); + } +} + +int snd_efw_create_midi_devices(struct snd_efw *efw) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(efw->card, efw->card->driver, 0, + efw->midi_out_ports, efw->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", efw->card->shortname); + rmidi->private_data = efw; + + if (efw->midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(efw, str); + } + + if (efw->midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(efw, str); + } + + if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +}
Fireworks has a quirk to ignore MIDI messages in data blocks more than 8. This commit adds a flag for this quirk and codes to skip 8 or more data blocks to transfer MIDI messages.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 5 ++++- sound/firewire/amdtp.h | 3 +++ sound/firewire/fireworks/fireworks_stream.c | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 5e13b3f..28ee3d8 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -78,6 +78,8 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, s->callbacked = false; s->sync_slave = NULL;
+ s->rx_blocks_for_midi = UINT_MAX; + return 0; } EXPORT_SYMBOL(amdtp_stream_init); @@ -472,7 +474,8 @@ static void amdtp_fill_midi(struct amdtp_stream *s, b = (u8 *)&buffer[s->midi_position];
port = (s->data_block_counter + f) % 8; - if ((s->midi[port] == NULL) || + if ((f >= s->rx_blocks_for_midi) || + (s->midi[port] == NULL) || (snd_rawmidi_transmit(s->midi[port], b + 1, 1) <= 0)) b[0] = 0x80; else diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 42c75c9..fb5934c 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -125,6 +125,9 @@ struct amdtp_stream { /* quirk: fixed interval of dbc between previos/current packets. */ unsigned int tx_dbc_interval;
+ /* quirk: the first count of data blocks in an rx packet for MIDI */ + unsigned int rx_blocks_for_midi; + bool callbacked; wait_queue_head_t callback_wait; struct amdtp_stream *sync_slave; diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index 3a3f203..f9d836f 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -212,6 +212,11 @@ int snd_efw_stream_init_duplex(struct snd_efw *efw) destroy_stream(efw, &efw->tx_stream); goto end; } + /* + * Fireworks ignores MIDI messages in more than first 8 data + * blocks of an received AMDTP packet. + */ + efw->rx_stream.rx_blocks_for_midi = 8;
/* set IEC61883 compliant mode (actually not fully compliant...) */ err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
This commit adds a functionality to capture/playback PCM samples.
When AMDTP stream is already running for PCM or the source of clock is not internal, available sampling rate is limited at current one.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/fireworks/Makefile | 2 +- sound/firewire/fireworks/fireworks.c | 4 + sound/firewire/fireworks/fireworks.h | 4 + sound/firewire/fireworks/fireworks_pcm.c | 394 ++++++++++++++++++++++++++++ sound/firewire/fireworks/fireworks_stream.c | 33 --- 6 files changed, 404 insertions(+), 34 deletions(-) create mode 100644 sound/firewire/fireworks/fireworks_pcm.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 36a1212..fb39f9c 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -65,6 +65,7 @@ config SND_FIREWORKS tristate "Echo Fireworks board module support" select SND_FIREWIRE_LIB select SND_RAWMIDI + select SND_PCM help Say Y here to include support for FireWire devices based on Echo Digital Audio Fireworks board: diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile index a2cecc6..d7ebf83 100644 --- a/sound/firewire/fireworks/Makefile +++ b/sound/firewire/fireworks/Makefile @@ -1,4 +1,4 @@ snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ fireworks_stream.o fireworks_proc.o fireworks_midi.o \ - fireworks.o + fireworks_pcm.o fireworks.o obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 9204c7c..d7877c7 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -232,6 +232,10 @@ efw_probe(struct fw_unit *unit, goto error; }
+ err = snd_efw_create_pcm_devices(efw); + if (err < 0) + goto error; + err = snd_efw_stream_init_duplex(efw); if (err < 0) goto error; diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index d181969..36419ca 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -23,6 +23,7 @@ #include <sound/pcm.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/pcm_params.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -201,6 +202,9 @@ void snd_efw_proc_init(struct snd_efw *efw);
int snd_efw_create_midi_devices(struct snd_efw *efw);
+int snd_efw_create_pcm_devices(struct snd_efw *efw); +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode); + #define SND_EFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c new file mode 100644 index 0000000..15ec4e0 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -0,0 +1,394 @@ +/* + * fireworks_pcm.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "./fireworks.h" + +/* + * NOTE: + * Fireworks changes its AMDTP channels for PCM data according to its sampling + * rate. There are three modes. Here _XX is either _rx or _tx. + * 0: 32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied + * 1: 88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied + * 2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied + * + * The number of PCM channels for analog input and output are always fixed but + * the number of PCM channels for digital input and output are differed. + * + * Additionally, according to "AudioFire Owner's Manual Version 2.2", in some + * model, the number of PCM channels for digital input has more restriction + * depending on which digital interface is selected. + * - S/PDIF coaxial and optical : use input 1-2 + * - ADAT optical at 32.0-48.0 kHz : use input 1-8 + * - ADAT optical at 88.2-96.0 kHz : use input 1-4 (S/MUX format) + * + * The data in AMDTP channels for blank PCM channels are zero. + */ +static const unsigned int freq_table[] = { + /* multiplier mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* multiplier mode 1 */ + [3] = 88200, + [4] = 96000, + /* multiplier mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +static inline unsigned int +get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + if (freq_table[i] == sampling_rate) { + *mode = get_multiplier_mode_with_index(i); + return 0; + } + } + + return -EINVAL; +} + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, freq_table[i]); + t.max = max(t.max, freq_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, freq_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels(struct snd_pcm_hardware *hw, unsigned int *pcm_channels) +{ + unsigned int i, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (pcm_channels[mode] == 0) + continue; + + hw->channels_min = min(hw->channels_min, pcm_channels[mode]); + hw->channels_max = max(hw->channels_max, pcm_channels[mode]); + } +} + +static void +limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int +pcm_init_hw_params(struct snd_efw *efw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + unsigned int *pcm_channels; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + s = &efw->tx_stream; + pcm_channels = efw->pcm_capture_channels; + } else { + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + s = &efw->rx_stream; + pcm_channels = efw->pcm_playback_channels; + } + + /* limit rates */ + runtime->hw.rates = efw->supported_sampling_rate, + snd_pcm_limit_hw_rates(runtime); + + limit_channels(&runtime->hw, pcm_channels); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + int sampling_rate; + unsigned int clock_source; + int err; + + err = pcm_init_hw_params(efw, substream); + if (err < 0) + goto end; + + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + goto end; + + /* + * When source of clock is not internal or any PCM streams are running, + * available sampling rate is limited at current sampling rate. + */ + if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) || + amdtp_stream_pcm_running(&efw->tx_stream) || + amdtp_stream_pcm_running(&efw->rx_stream)) { + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + if (err < 0) + goto end; + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&efw->capture_substreams); + amdtp_stream_set_pcm_format(&efw->tx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&efw->playback_substreams); + amdtp_stream_set_pcm_format(&efw->rx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&efw->capture_substreams); + + snd_efw_stream_stop_duplex(efw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&efw->playback_substreams); + + snd_efw_stream_stop_duplex(efw); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_efw_stream_start_duplex(efw, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->tx_stream); + + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_efw_stream_start_duplex(efw, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->rx_stream); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&efw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&efw->rx_stream); +} + +static const struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static const struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_efw_create_pcm_devices(struct snd_efw *efw) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = efw; + snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index f9d836f..1860914 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -9,39 +9,6 @@
#define CALLBACK_TIMEOUT 100
-static unsigned int freq_table[] = { - /* multiplier mode 0 */ - [0] = 32000, - [1] = 44100, - [2] = 48000, - /* multiplier mode 1 */ - [3] = 88200, - [4] = 96000, - /* multiplier mode 2 */ - [5] = 176400, - [6] = 192000, -}; - -static inline unsigned int -get_multiplier_mode_with_index(unsigned int index) -{ - return ((int)index - 1) / 2; -} - -int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(freq_table); i++) { - if (freq_table[i] == sampling_rate) { - *mode = get_multiplier_mode_with_index(i); - return 0; - } - } - - return -EINVAL; -} - static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream) {
This interface is designed for mixer/control application. To use hwdep interface, the application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 3 +- sound/firewire/Kconfig | 1 + sound/firewire/fireworks/Makefile | 2 +- sound/firewire/fireworks/fireworks.c | 5 + sound/firewire/fireworks/fireworks.h | 12 ++ sound/firewire/fireworks/fireworks_hwdep.c | 199 ++++++++++++++++++++++++++++ sound/firewire/fireworks/fireworks_midi.c | 25 +++- sound/firewire/fireworks/fireworks_pcm.c | 15 ++- sound/firewire/fireworks/fireworks_stream.c | 39 ++++++ 10 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 sound/firewire/fireworks/fireworks_hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 9fc6219..5dfbfdf 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -94,9 +94,10 @@ enum { SNDRV_HWDEP_IFACE_HDA, /* HD-audio */ SNDRV_HWDEP_IFACE_USB_STREAM, /* direct access to usb stream */ SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */ + SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DICE + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREWORKS };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 59f5961..fc9afb2 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -34,7 +34,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_IOCTL_UNLOCK _IO('H', 0xfa)
#define SNDRV_FIREWIRE_TYPE_DICE 1 -/* Fireworks, AV/C, RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 +/* AV/C, RME, MOTU, ... */
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index fb39f9c..875af02 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -66,6 +66,7 @@ config SND_FIREWORKS select SND_FIREWIRE_LIB select SND_RAWMIDI select SND_PCM + select SND_HWDEP help Say Y here to include support for FireWire devices based on Echo Digital Audio Fireworks board: diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile index d7ebf83..0c74408 100644 --- a/sound/firewire/fireworks/Makefile +++ b/sound/firewire/fireworks/Makefile @@ -1,4 +1,4 @@ snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ fireworks_stream.o fireworks_proc.o fireworks_midi.o \ - fireworks_pcm.o fireworks.o + fireworks_pcm.o fireworks_hwdep.o fireworks.o obj-m += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index d7877c7..f8d06f5 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -217,6 +217,7 @@ efw_probe(struct fw_unit *unit, efw->unit = unit; mutex_init(&efw->mutex); spin_lock_init(&efw->lock); + init_waitqueue_head(&efw->hwdep_wait);
err = get_hardware_info(efw); if (err < 0) @@ -236,6 +237,10 @@ efw_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_efw_create_hwdep_device(efw); + if (err < 0) + goto error; + err = snd_efw_stream_init_duplex(efw); if (err < 0) goto error; diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 36419ca..4aaf2dc 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -24,6 +24,8 @@ #include <sound/info.h> #include <sound/rawmidi.h> #include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -90,6 +92,11 @@ struct snd_efw { unsigned int phys_in_grp_count; struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
struct snd_efw_transaction { @@ -197,6 +204,9 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, int sampling_rate); void snd_efw_stream_stop_duplex(struct snd_efw *efw); void snd_efw_stream_update_duplex(struct snd_efw *efw); void snd_efw_stream_destroy_duplex(struct snd_efw *efw); +void snd_efw_stream_lock_changed(struct snd_efw *efw); +int snd_efw_stream_lock_try(struct snd_efw *efw); +void snd_efw_stream_lock_release(struct snd_efw *efw);
void snd_efw_proc_init(struct snd_efw *efw);
@@ -205,6 +215,8 @@ int snd_efw_create_midi_devices(struct snd_efw *efw); int snd_efw_create_pcm_devices(struct snd_efw *efw); int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode);
+int snd_efw_create_hwdep_device(struct snd_efw *efw); + #define SND_EFW_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c new file mode 100644 index 0000000..1cf491d --- /dev/null +++ b/sound/firewire/fireworks/fireworks_hwdep.c @@ -0,0 +1,199 @@ +/* + * fireworks_hwdep.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have three functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + */ + +#include "fireworks.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&efw->lock); + + while (!efw->dev_lock_changed) { + prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&efw->lock); + schedule(); + finish_wait(&efw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&efw->lock); + } + + memset(&event, 0, sizeof(event)); + if (efw->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (efw->dev_lock_count > 0); + efw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&efw->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_efw *efw = hwdep->private_data; + unsigned int events; + + poll_wait(file, &efw->hwdep_wait, wait); + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&efw->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_efw *efw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(efw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == 0) { + efw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == -1) { + efw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_efw *efw = hwdep->private_data; + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_count == -1) + efw->dev_lock_count = 0; + spin_unlock_irq(&efw->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_efw *efw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(efw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(efw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(efw); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, +}; + +int snd_efw_create_hwdep_device(struct snd_efw *efw) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "Fireworks"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; + hwdep->ops = hwdep_ops; + hwdep->private_data = efw; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c index cbf34e9..4a600d2 100644 --- a/sound/firewire/fireworks/fireworks_midi.c +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -11,17 +11,36 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) { struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end;
atomic_inc(&efw->capture_substreams); - return snd_efw_stream_start_duplex(efw, 0); + err = snd_efw_stream_start_duplex(efw, 0); + if (err < 0) + snd_efw_stream_lock_release(efw); + +end: + return err; }
static int midi_playback_open(struct snd_rawmidi_substream *substream) { struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end;
atomic_inc(&efw->playback_substreams); - return snd_efw_stream_start_duplex(efw, 0); + err = snd_efw_stream_start_duplex(efw, 0); + if (err < 0) + snd_efw_stream_lock_release(efw); +end: + return err; }
static int midi_capture_close(struct snd_rawmidi_substream *substream) @@ -31,6 +50,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) atomic_dec(&efw->capture_substreams); snd_efw_stream_stop_duplex(efw);
+ snd_efw_stream_lock_release(efw); return 0; }
@@ -41,6 +61,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream) atomic_dec(&efw->playback_substreams); snd_efw_stream_stop_duplex(efw);
+ snd_efw_stream_lock_release(efw); return 0; }
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c index 15ec4e0..ed211d0 100644 --- a/sound/firewire/fireworks/fireworks_pcm.c +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -199,13 +199,17 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int clock_source; int err;
- err = pcm_init_hw_params(efw, substream); + err = snd_efw_stream_lock_try(efw); if (err < 0) goto end;
+ err = pcm_init_hw_params(efw, substream); + if (err < 0) + goto err_locked; + err = snd_efw_command_get_clock_source(efw, &clock_source); if (err < 0) - goto end; + goto err_locked;
/* * When source of clock is not internal or any PCM streams are running, @@ -216,7 +220,7 @@ static int pcm_open(struct snd_pcm_substream *substream) amdtp_stream_pcm_running(&efw->rx_stream)) { err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); if (err < 0) - goto end; + goto err_locked; substream->runtime->hw.rate_min = sampling_rate; substream->runtime->hw.rate_max = sampling_rate; } @@ -224,10 +228,15 @@ static int pcm_open(struct snd_pcm_substream *substream) snd_pcm_set_sync(substream); end: return err; +err_locked: + snd_efw_stream_lock_release(efw); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_efw *efw = substream->private_data; + snd_efw_stream_lock_release(efw); return 0; }
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index 1860914..eaab8f6 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -331,3 +331,42 @@ void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
mutex_unlock(&efw->mutex); } + +void snd_efw_stream_lock_changed(struct snd_efw *efw) +{ + efw->dev_lock_changed = true; + wake_up(&efw->hwdep_wait); +} + +int snd_efw_stream_lock_try(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + /* user land lock this */ + if (efw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (efw->dev_lock_count++ == 0) + snd_efw_stream_lock_changed(efw); + err = 0; +end: + spin_unlock_irq(&efw->lock); + return err; +} + +void snd_efw_stream_lock_release(struct snd_efw *efw) +{ + spin_lock_irq(&efw->lock); + + if (WARN_ON(efw->dev_lock_count <= 0)) + goto end; + if (--efw->dev_lock_count == 0) + snd_efw_stream_lock_changed(efw); +end: + spin_unlock_irq(&efw->lock); +}
This commit adds two functionality for hwdep interface, adds two parameters for this driver, add a node for proc interface.
To receive responses from devices, this driver already allocate own callback into initial memory space in host controller. This means no one can allocate its own callback to the address. So this driver must give a way for user applications to receive responses.
This commit adds a functionality to receive responses via hwdep interface. The application can receive responses to read from this interface. To achieve this, this commit adds a buffer to queue responses. The default size of this buffer is 1024 bytes. This size can be changed to give preferrable size to 'resp_buf_size' parameter for this driver. The application should notice rest of space in this buffer because this driver don't push responses when this buffer has no space.
Additionaly, this commit adds a functionality to transmit commands via hwdep interface. The application can transmit commands to write into this interface. I note that the application can transmit one command at once, but can receive as many responses as possible untill the user-buffer is full.
When using these interfaces, the application must keep maximum number of sequence number in command within the number in firewire.h because this driver uses this number to distinguish the response is against the command by the application or this driver.
Usually responses against commands which the application transmits are pushed into this buffer. But to enable 'resp_buf_debug' parameter for this driver, all responses are pushed into the buffer. When using this mode, I reccomend to expand the size of buffer.
Finally this commit adds a new node into proc interface to output status of the buffer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/firewire.h | 19 +++ sound/firewire/fireworks/fireworks.c | 21 +++ sound/firewire/fireworks/fireworks.h | 22 +-- sound/firewire/fireworks/fireworks_command.c | 8 +- sound/firewire/fireworks/fireworks_hwdep.c | 131 ++++++++++++++--- sound/firewire/fireworks/fireworks_proc.c | 18 +++ sound/firewire/fireworks/fireworks_transaction.c | 176 ++++++++++++++++++++--- 7 files changed, 347 insertions(+), 48 deletions(-)
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index fc9afb2..bfc0e23e4 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -2,11 +2,13 @@ #define _UAPI_SOUND_FIREWIRE_H_INCLUDED
#include <linux/ioctl.h> +#include <linux/types.h>
/* events can be read() from the hwdep device */
#define SNDRV_FIREWIRE_EVENT_LOCK_STATUS 0x000010cc #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e +#define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475
struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -22,10 +24,27 @@ struct snd_firewire_event_dice_notification { unsigned int notification; /* DICE-specific bits */ };
+#define SND_EFW_TRANSACTION_USER_SEQNUM_MAX ((__u32)((__u16)~0) - 1) +/* each field should be in big endian */ +struct snd_efw_transaction { + __be32 length; + __be32 version; + __be32 seqnum; + __be32 category; + __be32 command; + __be32 status; + __be32 params[0]; +}; +struct snd_firewire_event_efw_response { + unsigned int type; + __be32 response[0]; /* some responses */ +}; + union snd_firewire_event { struct snd_firewire_event_common common; struct snd_firewire_event_lock_status lock_status; struct snd_firewire_event_dice_notification dice_notification; + struct snd_firewire_event_efw_response efw_response; };
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index f8d06f5..a354d26 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -24,6 +24,8 @@ MODULE_LICENSE("GPL v2"); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +unsigned int snd_efw_resp_buf_size = 1024; +bool snd_efw_resp_buf_debug = false;
module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "card index"); @@ -31,6 +33,11 @@ module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string"); module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "enable Fireworks sound card"); +module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444); +MODULE_PARM_DESC(resp_buf_size, + "response buffer size (max 4096, default 1024)"); +module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444); +MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer");
static DEFINE_MUTEX(devices_mutex); static DECLARE_BITMAP(devices_used, SNDRV_CARDS); @@ -182,6 +189,7 @@ efw_card_free(struct snd_card *card) }
mutex_destroy(&efw->mutex); + kfree(efw->resp_buf); }
static int @@ -219,6 +227,17 @@ efw_probe(struct fw_unit *unit, spin_lock_init(&efw->lock); init_waitqueue_head(&efw->hwdep_wait);
+ /* prepare response buffer */ + snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size, + SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U); + efw->resp_buf = kzalloc(snd_efw_resp_buf_size, GFP_KERNEL); + if (efw->resp_buf == NULL) { + err = -ENOMEM; + goto error; + } + efw->pull_ptr = efw->push_ptr = efw->resp_buf; + snd_efw_transaction_add_instance(efw); + err = get_hardware_info(efw); if (err < 0) goto error; @@ -256,6 +275,7 @@ end: mutex_unlock(&devices_mutex); return err; error: + snd_efw_transaction_remove_instance(efw); mutex_unlock(&devices_mutex); snd_card_free(card); return err; @@ -274,6 +294,7 @@ static void efw_remove(struct fw_unit *unit) struct snd_efw *efw = dev_get_drvdata(&unit->device);
snd_efw_stream_destroy_duplex(efw); + snd_efw_transaction_remove_instance(efw);
snd_card_disconnect(efw->card); snd_card_free_when_closed(efw->card); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 4aaf2dc..494195b 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -49,6 +49,9 @@ */ #define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U
+extern unsigned int snd_efw_resp_buf_size; +extern bool snd_efw_resp_buf_debug; + struct snd_efw_phys_grp { u8 type; /* see enum snd_efw_grp_type */ u8 count; @@ -97,23 +100,24 @@ struct snd_efw { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait; -};
-struct snd_efw_transaction { - __be32 length; - __be32 version; - __be32 seqnum; - __be32 category; - __be32 command; - __be32 status; - __be32 params[0]; + /* response queue */ + u8 *resp_buf; + u8 *pull_ptr; + u8 *push_ptr; + unsigned int resp_queues; }; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size); int snd_efw_transaction_run(struct fw_unit *unit, const void *cmd, unsigned int cmd_size, void *resp, unsigned int resp_size); int snd_efw_transaction_register(void); void snd_efw_transaction_unregister(void); void snd_efw_transaction_bus_reset(struct fw_unit *unit); +void snd_efw_transaction_add_instance(struct snd_efw *efw); +void snd_efw_transaction_remove_instance(struct snd_efw *efw);
struct snd_efw_hwinfo { u32 flags; diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c index d5ea705..166f805 100644 --- a/sound/firewire/fireworks/fireworks_command.c +++ b/sound/firewire/fireworks/fireworks_command.c @@ -22,7 +22,8 @@ * Information commands. But this module don't use them. */
-#define EFW_TRANSACTION_SEQNUM_MAX ((u32)~0) +#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2) +#define KERNEL_SEQNUM_MAX ((u32)~0)
/* for clock source and sampling rate */ struct efc_clock { @@ -120,8 +121,9 @@ efw_transaction(struct snd_efw *efw, unsigned int category,
/* to keep consistency of sequence number */ spin_lock(&efw->lock); - if (efw->seqnum + 2 >= EFW_TRANSACTION_SEQNUM_MAX) - efw->seqnum = 0; + if ((efw->seqnum < KERNEL_SEQNUM_MIN) || + (efw->seqnum >= KERNEL_SEQNUM_MAX - 2)) + efw->seqnum = KERNEL_SEQNUM_MIN; else efw->seqnum += 2; seqnum = efw->seqnum; diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c index 1cf491d..6b50a67 100644 --- a/sound/firewire/fireworks/fireworks_hwdep.c +++ b/sound/firewire/fireworks/fireworks_hwdep.c @@ -7,26 +7,101 @@ */
/* - * This codes have three functionalities. + * This codes have five functionalities. * * 1.get information about firewire node * 2.get notification about starting/stopping stream * 3.lock/unlock streaming + * 4.transmit command of EFW transaction + * 5.receive response of EFW transaction + * */
#include "fireworks.h"
static long -hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, +hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained, + loff_t *offset) +{ + unsigned int length, till_end, type; + struct snd_efw_transaction *t; + long count = 0; + + if (remained < sizeof(type) + sizeof(struct snd_efw_transaction)) + return -ENOSPC; + + /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */ + type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE; + if (copy_to_user(buf, &type, sizeof(type))) + return -EFAULT; + remained -= sizeof(type); + buf += sizeof(type); + + /* write into buffer as many responses as possible */ + while (efw->resp_queues > 0) { + t = (struct snd_efw_transaction *)(efw->pull_ptr); + length = be32_to_cpu(t->length) * sizeof(__be32); + + /* confirm enough space for this response */ + if (remained < length) + break; + + /* copy from ring buffer to user buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(efw->pull_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + if (copy_to_user(buf, efw->pull_ptr, till_end)) + return -EFAULT; + + efw->pull_ptr += till_end; + if (efw->pull_ptr >= efw->resp_buf + + snd_efw_resp_buf_size) + efw->pull_ptr = efw->resp_buf; + + length -= till_end; + buf += till_end; + count += till_end; + remained -= till_end; + } + + efw->resp_queues--; + } + + return count; +} + +static long +hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count, + loff_t *offset) +{ + union snd_firewire_event event; + + memset(&event, 0, sizeof(event)); + + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (efw->dev_lock_count > 0); + efw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, loff_t *offset) { struct snd_efw *efw = hwdep->private_data; DEFINE_WAIT(wait); - union snd_firewire_event event;
spin_lock_irq(&efw->lock);
- while (!efw->dev_lock_changed) { + while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) { prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); spin_unlock_irq(&efw->lock); schedule(); @@ -36,20 +111,43 @@ hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, spin_lock_irq(&efw->lock); }
- memset(&event, 0, sizeof(event)); - if (efw->dev_lock_changed) { - event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; - event.lock_status.status = (efw->dev_lock_count > 0); - efw->dev_lock_changed = false; - - count = min_t(long, count, sizeof(event.lock_status)); - } + if (efw->dev_lock_changed) + count = hwdep_read_locked(efw, buf, count, offset); + else if (efw->resp_queues > 0) + count = hwdep_read_resp_buf(efw, buf, count, offset);
spin_unlock_irq(&efw->lock);
- if (copy_to_user(buf, &event, count)) - return -EFAULT; + return count; +}
+static long +hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + u32 seqnum; + u8 *buf; + + if (count < sizeof(struct snd_efw_transaction) || + SND_EFW_RESPONSE_MAXIMUM_BYTES < count) + return -EINVAL; + + buf = memdup_user(data, count); + if (IS_ERR(buf)) + return PTR_ERR(data); + + /* check seqnum is not for kernel-land */ + seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) { + count = -EINVAL; + goto end; + } + + if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0) + count = -EIO; +end: + kfree(buf); return count; }
@@ -62,13 +160,13 @@ hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) poll_wait(file, &efw->hwdep_wait, wait);
spin_lock_irq(&efw->lock); - if (efw->dev_lock_changed) + if (efw->dev_lock_changed || (efw->resp_queues > 0)) events = POLLIN | POLLRDNORM; else events = 0; spin_unlock_irq(&efw->lock);
- return events; + return events | POLLOUT; }
static int @@ -174,6 +272,7 @@ hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
static const struct snd_hwdep_ops hwdep_ops = { .read = hwdep_read, + .write = hwdep_write, .release = hwdep_release, .poll = hwdep_poll, .ioctl = hwdep_ioctl, diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c index 631c91f..f29d4aa 100644 --- a/sound/firewire/fireworks/fireworks_proc.c +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -176,6 +176,23 @@ end: }
static void +proc_read_queues_state(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned int consumed; + + if (efw->pull_ptr > efw->push_ptr) + consumed = snd_efw_resp_buf_size - + (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr); + + snd_iprintf(buffer, "%d %d/%d\n", + efw->resp_queues, consumed, snd_efw_resp_buf_size); +} + +static void add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name, void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) { @@ -211,4 +228,5 @@ void snd_efw_proc_init(struct snd_efw *efw) add_node(efw, root, "clock", proc_read_clock); add_node(efw, root, "firmware", proc_read_hwinfo); add_node(efw, root, "meters", proc_read_phys_meters); + add_node(efw, root, "queues", proc_read_queues_state); } diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c index aac91d8..81a65eb 100644 --- a/sound/firewire/fireworks/fireworks_transaction.c +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -38,6 +38,9 @@ #define ERROR_DELAY_MS 5 #define EFC_TIMEOUT_MS 125
+static DEFINE_SPINLOCK(instances_lock); +static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + static DEFINE_SPINLOCK(transaction_queues_lock); static LIST_HEAD(transaction_queues);
@@ -57,6 +60,14 @@ struct transaction_queue { wait_queue_head_t wait; };
+int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size) +{ + return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, + MEMORY_SPACE_EFW_COMMAND, + (void *)cmd, size, 0); +} + int snd_efw_transaction_run(struct fw_unit *unit, const void *cmd, unsigned int cmd_size, void *resp, unsigned int resp_size) @@ -78,9 +89,7 @@ int snd_efw_transaction_run(struct fw_unit *unit,
tries = 0; do { - ret = snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, - MEMORY_SPACE_EFW_COMMAND, - (void *)cmd, cmd_size, 0); + ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size); if (ret < 0) break;
@@ -107,27 +116,92 @@ int snd_efw_transaction_run(struct fw_unit *unit, }
static void -efw_response(struct fw_card *card, struct fw_request *request, - int tcode, int destination, int source, - int generation, unsigned long long offset, - void *data, size_t length, void *callback_data) +copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode) { - struct fw_device *device; - struct transaction_queue *t; - unsigned long flags; - int rcode; - u32 seqnum; + size_t capacity, till_end; + struct snd_efw_transaction *t;
- rcode = RCODE_TYPE_ERROR; - if (length < sizeof(struct snd_efw_transaction)) { - rcode = RCODE_DATA_ERROR; - goto end; - } else if (offset != MEMORY_SPACE_EFW_RESPONSE) { - rcode = RCODE_ADDRESS_ERROR; + spin_lock_irq(&efw->lock); + + t = (struct snd_efw_transaction *)data; + length = min_t(size_t, t->length * sizeof(t->length), length); + + if (efw->push_ptr < efw->pull_ptr) + capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + capacity = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->pull_ptr); + + /* confirm enough space for this response */ + if (capacity < length) { + *rcode = RCODE_CONFLICT_ERROR; goto end; }
- seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); + /* copy to ring buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + memcpy(efw->push_ptr, data, till_end); + + efw->push_ptr += till_end; + if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size) + efw->push_ptr = efw->resp_buf; + + length -= till_end; + data += till_end; + } + + /* for hwdep */ + efw->resp_queues++; + wake_up(&efw->hwdep_wait); + + *rcode = RCODE_COMPLETE; +end: + spin_unlock_irq(&efw->lock); +} + +static void +handle_resp_for_user(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode) +{ + struct fw_device *device; + struct snd_efw *efw; + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + efw = instances[i]; + if (efw == NULL) + continue; + device = fw_parent_device(efw->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node id vs. generation */ + if (device->node_id != source) + continue; + + break; + } + if (i == SNDRV_CARDS) + goto end; + + copy_resp_to_buf(efw, data, length, rcode); +end: + spin_unlock_irq(&instances_lock); +} + +static void +handle_resp_for_kernel(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode, u32 seqnum) +{ + struct fw_device *device; + struct transaction_queue *t; + unsigned long flags;
spin_lock_irqsave(&transaction_queues_lock, flags); list_for_each_entry(t, &transaction_queues, list) { @@ -144,14 +218,76 @@ efw_response(struct fw_card *card, struct fw_request *request, t->size = min_t(unsigned int, length, t->size); memcpy(t->buf, data, t->size); wake_up(&t->wait); - rcode = RCODE_COMPLETE; + *rcode = RCODE_COMPLETE; } } spin_unlock_irqrestore(&transaction_queues_lock, flags); +} + +static void +efw_response(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + int rcode, dummy; + u32 seqnum; + + rcode = RCODE_TYPE_ERROR; + if (length < sizeof(struct snd_efw_transaction)) { + rcode = RCODE_DATA_ERROR; + goto end; + } else if (offset != MEMORY_SPACE_EFW_RESPONSE) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + + seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) { + handle_resp_for_kernel(card, generation, source, + data, length, &rcode, seqnum); + if (snd_efw_resp_buf_debug) + handle_resp_for_user(card, generation, source, + data, length, &dummy); + } else { + handle_resp_for_user(card, generation, source, + data, length, &rcode); + } end: fw_send_response(card, request, rcode); }
+void snd_efw_transaction_add_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != NULL) + continue; + instances[i] = efw; + break; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_remove_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != efw) + continue; + instances[i] = NULL; + } + + spin_unlock_irq(&instances_lock); +} + void snd_efw_transaction_bus_reset(struct fw_unit *unit) { struct transaction_queue *t;
This commit adds a new driver for BeBoB based devices with no specific operations. Currently this driver just create/remove card instance according to callbacks.
BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire devices with DM1000/DM1100/DM1500 chipset. It gives common way for host system to handle BeBoB based devices.
Current supported devices: - Edirol FA-66/FA-101 - PreSonus FIREBOX/FIREPOD/FP10/Inspire1394 - BridgeCo RDAudio1/Audio5 - Mackie Onyx 1220/1620/1640 (Firewire I/O Card) - Mackie d.2 (Firewire Option) - Stanton FinalScratch 2 (ScratchAmp) - Tascam IF-FW DM - Behringer XENIX UFX 1204/1604 - Behringer Digital Mixer X32 series (X-UF Card) - Apogee Rosetta 200/Rosetta 400 (X-FireWire card) - Apogee DA-16X/AD-16X/DD-16X (X-FireWire card) - Apogee Ensemble - ESI Quotafire610 - AcousticReality eARMasterOne - CME MatrixKFW - Phonix Helix Board 12 MkII/18 MkII/24 MkII - Phonic Helix Board 12 Universal/18 Universal/24 Universal - Lynx Aurora 8/16 (LT-FW) - ICON FireXon - PrismSound Orpheus/ADA-8XR
Devices possible to be supported if identifying IDs: - Apogee Mini-Me Firewire/Mini-DAC Firewire - Behringer F-Control Audio 610/1616 - Cakewalk Sonar Power Studio 66 - CME UF400e - ESI Quotafire XL - Infrasonic DewX/Windy6 - Mackie Digital X Bus x.200/400 - Phonic Helix Board 12/18/24 - Phonic FireFly 202/302 - Rolf Spuler Firewire Guitar
Tested-by: David Henningsson david.henningsson@canonical.com Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 30 +++++ sound/firewire/Makefile | 1 + sound/firewire/bebob/Makefile | 2 + sound/firewire/bebob/bebob.c | 292 ++++++++++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob.h | 64 +++++++++ 5 files changed, 389 insertions(+) create mode 100644 sound/firewire/bebob/Makefile create mode 100644 sound/firewire/bebob/bebob.c create mode 100644 sound/firewire/bebob/bebob.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 875af02..5539baa 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -79,4 +79,34 @@ config SND_FIREWORKS To compile this driver as a module, choose M here: the module will be called snd-fireworks.
+config SND_BEBOB + tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware" + select SND_FIREWIRE_LIB + help + Say Y here to include support for FireWire devices based + on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: + * Edirol FA-66/FA-101 + * PreSonus FIREBOX/FIREPOD/FP10/Inspire1394 + * BridgeCo RDAudio1/Audio5 + * Mackie Onyx 1220/1620/1640 (Firewire I/O Card) + * Mackie d.2 (Firewire Option) + * Stanton FinalScratch 2 (ScratchAmp) + * Tascam IF-FW/DM + * Behringer XENIX UFX 1204/1604 + * Behringer Digital Mixer X32 series (X-UF Card) + * Apogee Rosetta 200/400 (X-FireWire card) + * Apogee DA/AD/DD-16X (X-FireWire card) + * Apogee Ensemble + * ESI Quotafire610 + * AcousticReality eARMasterOne + * CME MatrixKFW + * Phonic Helix Board 12 MkII/18 MkII/24 MkII + * Phonic Helix Board 12 Universal/18 Universal/24 Universal + * Lynx Aurora 8/16 (LT-FW) + * ICON FireXon + * PrismSound Orpheus/ADA-8XR + + To compile this driver as a module, choose M here: the module + will be called snd-bebob. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 5cd39dc..fad8d49 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o obj-$(CONFIG_SND_FIREWORKS) += fireworks/ +obj-$(CONFIG_SND_BEBOB) += bebob/ diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile new file mode 100644 index 0000000..c6f0141 --- /dev/null +++ b/sound/firewire/bebob/Makefile @@ -0,0 +1,2 @@ +snd-bebob-objs := bebob.o +obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c new file mode 100644 index 0000000..b5644ab --- /dev/null +++ b/sound/firewire/bebob/bebob.c @@ -0,0 +1,292 @@ +/* + * bebob.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire + * devices with DM1000/DM1100/DM1500 chipset. It gives common way for host + * system to handle BeBoB based devices. + */ + +#include "bebob.h" + +MODULE_DESCRIPTION("BridgeCo BeBoB driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable BeBoB sound card"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +/* Offsets from information register. */ +#define INFO_OFFSET_GUID 0x10 +#define INFO_OFFSET_HW_MODEL_ID 0x18 +#define INFO_OFFSET_HW_MODEL_REVISION 0x1c + +#define VEN_EDIROL 0x000040ab +#define VEN_PRESONUS 0x00000a92 +#define VEN_BRIDGECO 0x000007f5 +#define VEN_MACKIE 0x0000000f +#define VEN_STANTON 0x00001260 +#define VEN_TASCAM 0x0000022e +#define VEN_BEHRINGER 0x00001564 +#define VEN_APOGEE 0x000003db +#define VEN_ESI 0x00000f1b +#define VEN_ACOUSTIC 0x00000002 +#define VEN_CME 0x0000000a +#define VEN_PHONIC 0x00001496 +#define VEN_LYNX 0x000019e5 +#define VEN_ICON 0x00001a9e +#define VEN_PRISMSOUND 0x00001198 + +static int +name_device(struct snd_bebob *bebob, unsigned int vendor_id) +{ + struct fw_device *fw_dev = fw_parent_device(bebob->unit); + char vendor[24] = {0}; + char model[32] = {0}; + u32 id; + u32 data[2] = {0}; + u32 revision; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(bebob->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + /* get hardware id */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID, + &id); + if (err < 0) + goto end; + + /* get hardware revision */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION, + &revision); + if (err < 0) + goto end; + + /* get GUID */ + err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID, + data, sizeof(data)); + if (err < 0) + goto end; + + strcpy(bebob->card->driver, "BeBoB"); + strcpy(bebob->card->shortname, model); + strcpy(bebob->card->mixername, model); + snprintf(bebob->card->longname, sizeof(bebob->card->longname), + "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d", + vendor, model, id, revision, + data[0], data[1], dev_name(&bebob->unit->device), + 100 << fw_dev->max_speed); +end: + return err; +} + +static void +bebob_card_free(struct snd_card *card) +{ + struct snd_bebob *bebob = card->private_data; + + if (bebob->card_index >= 0) { + mutex_lock(&devices_mutex); + clear_bit(bebob->card_index, devices_used); + mutex_unlock(&devices_mutex); + } + + mutex_destroy(&bebob->mutex); +} + +static int +bebob_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_bebob *bebob; + unsigned int card_index; + int err; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; card_index++) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + err = snd_card_new(&unit->device, index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_bebob), &card); + if (err < 0) + goto end; + bebob = card->private_data; + bebob->card_index = card_index; + set_bit(card_index, devices_used); + card->private_free = bebob_card_free; + + bebob->card = card; + bebob->unit = unit; + mutex_init(&bebob->mutex); + spin_lock_init(&bebob->lock); + + err = name_device(bebob, entry->vendor_id); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + dev_set_drvdata(&unit->device, bebob); +end: + mutex_unlock(&devices_mutex); + return err; +error: + mutex_unlock(&devices_mutex); + snd_card_free(card); + return err; +} + +static void +bebob_update(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + fcp_bus_reset(bebob->unit); +} + +static void bebob_remove(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + snd_card_disconnect(bebob->card); + snd_card_free_when_closed(bebob->card); +} + +static const struct ieee1394_device_id bebob_id_table[] = { + /* Edirol, FA-66 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049), + /* Edirol, FA-101 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048), + /* Presonus, FIREBOX */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000), + /* PreSonus, FIREPOD/FP10 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066), + /* PreSonus, Inspire1394 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001), + /* BridgeCo, RDAudio1 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048), + /* BridgeCo, Audio5 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049), + /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065), + /* Mackie, d.2 (Firewire Option) */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067), + /* Stanton, ScratchAmp */ + SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001), + /* Tascam, IF-FW DM */ + SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067), + /* Behringer, XENIX UFX 1204 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204), + /* Behringer, XENIX UFX 1604 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604), + /* Behringer, Digital Mixer X32 series (X-UF Card) */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006), + /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */ + /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048), + /* Apogee Electronics, Ensemble */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00001eee), + /* ESI, Quatafire610 */ + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064), + /* AcousticReality, eARMasterOne */ + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002), + /* CME, MatrixKFW */ + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000), + /* Phonic, Helix Board 12 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000), + /* Phonic, Helix Board 18 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000), + /* Phonic, Helix Board 24 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000), + /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000), + /* Lynx, Aurora 8/16 (LT-FW) */ + SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001), + /* ICON, FireXon */ + SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001), + /* PrismSound, Orpheus */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048), + /* PrismSound, ADA-8XR */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8), + /* IDs are unknown but able to be supported */ + /* Apogee, Mini-ME Firewire */ + /* Apogee, Mini-DAC Firewire */ + /* Behringer, F-Control Audio 1616 */ + /* Behringer, F-Control Audio 610 */ + /* Cakawalk, Sonar Power Studio 66 */ + /* CME, UF400e */ + /* ESI, Quotafire XL */ + /* Infrasonic, DewX */ + /* Infrasonic, Windy6 */ + /* Mackie, Digital X Bus x.200 */ + /* Mackie, Digital X Bus x.400 */ + /* Phonic, HB 12 */ + /* Phonic, HB 24 */ + /* Phonic, HB 18 */ + /* Phonic, FireFly 202 */ + /* Phonic, FireFly 302 */ + /* Rolf Spuler, Firewire Guitar */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, bebob_id_table); + +static struct fw_driver bebob_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-bebob", + .bus = &fw_bus_type, + }, + .probe = bebob_probe, + .update = bebob_update, + .remove = bebob_remove, + .id_table = bebob_id_table, +}; + +static int __init +snd_bebob_init(void) +{ + return driver_register(&bebob_driver.driver); +} + +static void __exit +snd_bebob_exit(void) +{ + driver_unregister(&bebob_driver.driver); + mutex_destroy(&devices_mutex); +} + +module_init(snd_bebob_init); +module_exit(snd_bebob_exit); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h new file mode 100644 index 0000000..862f756 --- /dev/null +++ b/sound/firewire/bebob/bebob.h @@ -0,0 +1,64 @@ +/* + * bebob.h - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_BEBOB_H_INCLUDED +#define SOUND_BEBOB_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> + +#include "../lib.h" +#include "../fcp.h" + +/* basic register addresses on DM1000/DM1100/DM1500 */ +#define BEBOB_ADDR_REG_INFO 0xffffc8020000 +#define BEBOB_ADDR_REG_REQ 0xffffc8021000 + +struct snd_bebob { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; +}; + +static inline int +snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size) +{ + return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + buf, size, 0); +} + +static inline int +snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) +{ + return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + (void *)buf, sizeof(u32), 0); +} + +#define SND_BEBOB_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor, \ + .model_id = model, \ +} + +#endif
This commit adds management functionality for connections and streams. BeBoB uses CMP to manage connections and uses AMDTP for streams.
This commit also adds some BridgeCo's AV/C extension commands. There are some BridgeCo's AV/C extension commands but this commit just uses below commands to get device's capability and status:
1.Extended Plug Info commands - Plug Channel Position Specific Data - Plug Type Specific Data - Cluster(Section) Info Specific Data - Plug Input Specific Data 2.Extended Stream Format Information commands - Extended Stream Format Information Command - List Request
For Extended Plug Info commands for Cluster Info Specific Data, I pick up 'section' instead of 'cluster' from document to prevent from misunderstanding because 'cluster' is also used in IEC 61883-6.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 15 +- sound/firewire/bebob/bebob.h | 114 +++++ sound/firewire/bebob/bebob_command.c | 205 ++++++++ sound/firewire/bebob/bebob_stream.c | 874 +++++++++++++++++++++++++++++++++++ 5 files changed, 1208 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_stream.c
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index c6f0141..5cece62 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,2 +1,2 @@ -snd-bebob-objs := bebob.o +snd-bebob-objs := bebob_command.o bebob_stream.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index b5644ab..180284d 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -158,9 +158,20 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
- err = snd_card_register(card); + err = snd_bebob_stream_discover(bebob); if (err < 0) goto error; + + err = snd_bebob_stream_init_duplex(bebob); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) { + snd_bebob_stream_destroy_duplex(bebob); + goto error; + } + dev_set_drvdata(&unit->device, bebob); end: mutex_unlock(&devices_mutex); @@ -176,11 +187,13 @@ bebob_update(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); fcp_bus_reset(bebob->unit); + snd_bebob_stream_update_duplex(bebob); }
static void bebob_remove(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + snd_bebob_stream_destroy_duplex(bebob); snd_card_disconnect(bebob->card); snd_card_free_when_closed(bebob->card); } diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 862f756..a195c16 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -23,11 +23,25 @@
#include "../lib.h" #include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" +#include "../cmp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */ #define BEBOB_ADDR_REG_INFO 0xffffc8020000 #define BEBOB_ADDR_REG_REQ 0xffffc8021000
+struct snd_bebob; + +#define SND_BEBOB_STRM_FMT_ENTRIES 7 +struct snd_bebob_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob { struct snd_card *card; struct fw_unit *unit; @@ -35,6 +49,24 @@ struct snd_bebob {
struct mutex mutex; spinlock_t lock; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + struct amdtp_stream *master; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + atomic_t capture_substreams; + atomic_t playback_substreams; + + struct snd_bebob_stream_formation + tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob_stream_formation + rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + + int sync_input_plug; };
static inline int @@ -53,6 +85,88 @@ snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) (void *)buf, sizeof(u32), 0); }
+/* + * AVC command extensions, AV/C Unit and Subunit, Revision 17 + * (Nov 2003, BridgeCo) + */ +#define AVC_BRIDGECO_ADDR_BYTES 6 +enum avc_bridgeco_plug_dir { + AVC_BRIDGECO_PLUG_DIR_IN = 0x00, + AVC_BRIDGECO_PLUG_DIR_OUT = 0x01 +}; +enum avc_bridgeco_plug_mode { + AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00, + AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01, + AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02 +}; +enum avc_bridgeco_plug_unit { + AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00, + AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01, + AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02 +}; +enum avc_bridgeco_plug_type { + AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00, + AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01, + AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02, + AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03, + AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04, + AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05 +}; +static inline void +avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + enum avc_bridgeco_plug_unit unit, + unsigned int pid) +{ + buf[0] = 0xff; /* Unit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT; + buf[3] = unit; + buf[4] = 0xff & pid; + buf[5] = 0xff; /* reserved */ +} +static inline void +avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + unsigned int pid) +{ + buf[0] = 0x60; /* Music subunit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT; + buf[3] = 0xff & pid; + buf[4] = 0xff; /* reserved */ + buf[5] = 0xff; /* reserved */ +} +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len); +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type); +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type); +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 input[7]); +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid); + +/* for AMDTP streaming */ +int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate); +int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate); +int snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, + bool *internal); +int snd_bebob_stream_discover(struct snd_bebob *bebob); +int snd_bebob_stream_map(struct snd_bebob *bebob, + struct amdtp_stream *stream); +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate); +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c new file mode 100644 index 0000000..6a01795 --- /dev/null +++ b/sound/firewire/bebob/bebob_command.c @@ -0,0 +1,205 @@ +/* + * bebob_command.c - driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static inline void +avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr) +{ + buf[1] = addr[0]; + memcpy(buf + 4, addr + 1, 5); +} + +static inline void +avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr, + unsigned int itype) +{ + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */ + buf[3] = 0xc0; /* BridgeCo extension */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[9] = itype; /* info type */ +} + +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug type'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00); + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[10]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len) +{ + int err; + + /* Info type is 'channel position'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03); + + err = fcp_avc_transaction(unit, buf, 12, buf, 256, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(9)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* Pick up specific data. */ + memmove(buf, buf + 10, err - 10); + err = 0; +end: + return err; +} + +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type) +{ + u8 *buf; + int err; + + /* section info includes charactors but this module don't need it */ + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'section info'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07); + buf[10] = 0xff & ++id; /* section id */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9) | BIT(10)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[11]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7]) +{ + int err; + u8 *buf; + + buf = kzalloc(18, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug input'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05); + + err = fcp_avc_transaction(unit, buf, 16, buf, 16, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if ((err >= 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + memcpy(input, buf + 10, 5); + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid) +{ + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */ + buf[3] = 0xc1; /* Bridgeco extension - List Request */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[10] = 0xff & eid; /* Entry ID */ + + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(10)); + if ((err >= 0) && (err < 12)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + else if (buf[10] != eid) + err = -EIO; + if (err < 0) + goto end; + + /* Pick up 'stream format info'. */ + memmove(buf, buf + 11, err - 11); + *len = err - 11; + err = 0; +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 0000000..c868c17 --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,874 @@ +/* + * bebob_stream.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define CALLBACK_TIMEOUT 1000 + +/* + * NOTE; + * For BeBoB streams, Both of input and output CMP connection are important. + * + * For most devices, each CMP connection starts to transmit/receive a + * corresponding stream. But for a few devices, both of CMP connection needs + * to start transmitting stream. An example is 'M-Audio Firewire 410'. + */ + +/* 128 is an arbitrary length but it seems to be enough */ +#define FORMAT_MAXIMUM_LENGTH 128 + +const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + +/* + * See: Table 51: Extended Stream Format Info ‘Sampling Frequency’ + * in Additional AVC commands (Nov 2003, BridgeCo) + */ +static const unsigned int bridgeco_freq_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x06, + [6] = 0x07, +}; + +static unsigned int +get_formation_index(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { + if (snd_bebob_rate_table[i] == rate) + return i; + } + return -EINVAL; +} + +int +snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate, trials; + int err; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &tx_rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + *curr_rate = rx_rate; + if (rx_rate == tx_rate) + goto end; + + /* synchronize receive stream rate to transmit stream rate */ + err = avc_general_set_sig_fmt(bebob->unit, rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); +end: + return err; +} + +int +snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + /* + * Some devices need a bit time for transition. + * 300msec is got by some experiments. + */ + msleep(300); +end: + return err; +} + +int +snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, bool *internal) +{ + u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; + int err = 0; + + *internal = false; + + /* + * 1.The device don't support to switch source of clock then assumed + * to use internal clock always + */ + if (bebob->sync_input_plug < 0) { + *internal = true; + goto end; + } + + /* + * 2.The device supports to switch source of clock by an usual way. + * Let's check input for 'Music Sub Unit Sync Input' plug. + */ + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + bebob->sync_input_plug); + err = avc_bridgeco_get_plug_input(bebob->unit, addr, input); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get an input for MSU in plug %d: %d\n", + bebob->sync_input_plug, err); + goto end; + } + + /* + * If there are no input plugs, all of fields are 0xff. + * Here check the first field. This field is used for direction. + */ + if (input[0] == 0xff) { + *internal = true; + goto end; + } + + /* + * If source of clock is internal CSR, Music Sub Unit Sync Input is + * a destination of Music Sub Unit Sync Output. + */ + *internal = ((input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) && + (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT) && + (input[2] == 0x0c) && + (input[3] == 0x00)); +end: + return err; +} + +static unsigned int +map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + unsigned int sec, sections, ch, channels; + unsigned int pcm, midi, location; + unsigned int stm_pos, sec_loc, pos; + u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type; + enum avc_bridgeco_plug_dir dir; + int err; + + /* + * The length of return value of this command cannot be expected. Here + * use the maximum length of FCP. + */ + buf = kzalloc(256, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (s == &bebob->tx_stream) + dir = AVC_BRIDGECO_PLUG_DIR_OUT; + else + dir = AVC_BRIDGECO_PLUG_DIR_IN; + + avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get channel position for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out", + err); + goto end; + } + pos = 0; + + /* positions in I/O buffer */ + pcm = 0; + midi = 0; + + /* the number of sections in AMDTP packet */ + sections = buf[pos++]; + + for (sec = 0; sec < sections; sec++) { + /* type of this section */ + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_section_type(bebob->unit, addr, + sec, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get section type for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + err); + goto end; + } + /* NoType */ + if (type == 0xff) { + err = -ENOSYS; + goto end; + } + + /* the number of channels in this section */ + channels = buf[pos++]; + + for (ch = 0; ch < channels; ch++) { + /* position of this channel in AMDTP packet */ + stm_pos = buf[pos++] - 1; + /* location of this channel in this section */ + sec_loc = buf[pos++] - 1; + + switch (type) { + /* for MIDI conformant data channel */ + case 0x0a: + /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */ + if ((midi > 0) && (stm_pos != midi)) { + err = -ENOSYS; + goto end; + } + s->midi_position = stm_pos; + midi = stm_pos; + break; + /* for PCM data channel */ + case 0x01: /* Headphone */ + case 0x02: /* Microphone */ + case 0x03: /* Line */ + case 0x04: /* SPDIF */ + case 0x05: /* ADAT */ + case 0x06: /* TDIF */ + case 0x07: /* MADI */ + /* for undefined/changeable signal */ + case 0x08: /* Analog */ + case 0x09: /* Digital */ + default: + location = pcm + sec_loc; + if (location >= AMDTP_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + s->pcm_positions[location] = stm_pos; + break; + } + } + + if (type != 0x0a) + pcm += channels; + else + midi += channels; + } +end: + kfree(buf); + return err; +} + +static int +init_both_connections(struct snd_bebob *bebob) +{ + int err; + + err = cmp_connection_init(&bebob->in_conn, + bebob->unit, CMP_INPUT, 0); + if (err < 0) + goto end; + + err = cmp_connection_init(&bebob->out_conn, + bebob->unit, CMP_OUTPUT, 0); + if (err < 0) + cmp_connection_destroy(&bebob->in_conn); +end: + return err; +} + +static int +check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &bebob->tx_stream) + conn = &bebob->out_conn; + else + conn = &bebob->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&bebob->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +static int +make_both_connections(struct snd_bebob *bebob, unsigned int rate) +{ + int index, pcm_channels, midi_channels, err; + + /* confirm params for both streams */ + index = get_formation_index(rate); + pcm_channels = bebob->tx_stream_formations[index].pcm; + midi_channels = bebob->tx_stream_formations[index].midi; + amdtp_stream_set_parameters(&bebob->tx_stream, + rate, pcm_channels, midi_channels * 8); + pcm_channels = bebob->rx_stream_formations[index].pcm; + midi_channels = bebob->rx_stream_formations[index].midi; + amdtp_stream_set_parameters(&bebob->rx_stream, + rate, pcm_channels, midi_channels * 8); + + /* establish connections for both streams */ + err = cmp_connection_establish(&bebob->out_conn, + amdtp_stream_get_max_payload(&bebob->tx_stream)); + if (err < 0) + goto end; + err = cmp_connection_establish(&bebob->in_conn, + amdtp_stream_get_max_payload(&bebob->rx_stream)); + if (err < 0) + cmp_connection_break(&bebob->out_conn); +end: + return err; +} + +static void +break_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_break(&bebob->in_conn); + cmp_connection_break(&bebob->out_conn); +} + +static void +destroy_both_connections(struct snd_bebob *bebob) +{ + break_both_connections(bebob); + + cmp_connection_destroy(&bebob->in_conn); + cmp_connection_destroy(&bebob->out_conn); +} + +static int +get_sync_mode(struct snd_bebob *bebob, enum cip_flags *sync_mode) +{ + /* currently this module doesn't support SYT-Match mode */ + *sync_mode = CIP_SYNC_TO_DEVICE; + return 0; +} + +static int +start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream, + unsigned int rate) +{ + struct cmp_connection *conn; + int err = 0; + + if (stream == &bebob->rx_stream) + conn = &bebob->in_conn; + else + conn = &bebob->out_conn; + + /* channel mapping */ + err = map_data_channels(bebob, stream); + if (err < 0) + goto end; + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); +end: + return err; +} + +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) +{ + int err; + + err = init_both_connections(bebob); + if (err < 0) + goto end; + + err = amdtp_stream_init(&bebob->tx_stream, bebob->unit, + AMDTP_IN_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + destroy_both_connections(bebob); + goto end; + } + + err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, + AMDTP_OUT_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + amdtp_stream_destroy(&bebob->rx_stream); + destroy_both_connections(bebob); + } +end: + return err; +} + +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) +{ + struct amdtp_stream *master, *slave; + atomic_t *slave_substreams; + enum cip_flags sync_mode; + unsigned int curr_rate; + int err = 0; + + mutex_lock(&bebob->mutex); + + /* Need no substreams */ + if (atomic_read(&bebob->playback_substreams) == 0 && + atomic_read(&bebob->capture_substreams) == 0) + goto end; + + err = get_sync_mode(bebob, &sync_mode); + if (err < 0) + goto end; + if (sync_mode == CIP_SYNC_TO_DEVICE) { + master = &bebob->tx_stream; + slave = &bebob->rx_stream; + slave_substreams = &bebob->playback_substreams; + } else { + master = &bebob->rx_stream; + slave = &bebob->tx_stream; + slave_substreams = &bebob->capture_substreams; + } + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(bebob, master); + if (err < 0) + goto end; + + /* packet queueing error */ + if (amdtp_streaming_error(master)) { + amdtp_stream_stop(master); + amdtp_stream_stop(slave); + } + if (amdtp_streaming_error(slave)) + amdtp_stream_stop(slave); + + /* stop streams if rate is different */ + err = snd_bebob_stream_get_rate(bebob, &curr_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto end; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(master); + amdtp_stream_stop(slave); + break_both_connections(bebob); + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + bebob->master = master; + + /* + * NOTE: + * If establishing connections at first, Yamaha GO46 + * (and maybe Terratec X24) don't generate sound. + */ + err = snd_bebob_stream_set_rate(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to set sampling rate: %d\n", + err); + goto end; + } + + err = make_both_connections(bebob, rate); + if (err < 0) + goto end; + + err = start_stream(bebob, master, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP master stream:%d\n", err); + break_both_connections(bebob); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(master); + break_both_connections(bebob); + err = -ETIMEDOUT; + goto end; + } + } + + /* start slave if needed */ + if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) { + err = start_stream(bebob, slave, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP slave stream:%d\n", err); + amdtp_stream_stop(master); + break_both_connections(bebob); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) { + amdtp_stream_stop(slave); + amdtp_stream_stop(master); + break_both_connections(bebob); + err = -ETIMEDOUT; + } + } +end: + mutex_unlock(&bebob->mutex); + return err; +} + +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) +{ + struct amdtp_stream *master, *slave; + atomic_t *master_substreams, *slave_substreams; + + mutex_lock(&bebob->mutex); + + if (bebob->master == &bebob->rx_stream) { + slave = &bebob->tx_stream; + master = &bebob->rx_stream; + slave_substreams = &bebob->capture_substreams; + master_substreams = &bebob->playback_substreams; + } else { + slave = &bebob->rx_stream; + master = &bebob->tx_stream; + slave_substreams = &bebob->playback_substreams; + master_substreams = &bebob->capture_substreams; + } + + if (atomic_read(slave_substreams) == 0) { + amdtp_stream_pcm_abort(slave); + amdtp_stream_stop(slave); + + if (atomic_read(master_substreams) == 0) { + amdtp_stream_pcm_abort(master); + amdtp_stream_stop(master); + break_both_connections(bebob); + } + } + + mutex_unlock(&bebob->mutex); +} + +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob) +{ + /* vs. XRUN recovery due to discontinuity at bus reset */ + mutex_lock(&bebob->mutex); + + if ((cmp_connection_update(&bebob->in_conn) < 0) || + (cmp_connection_update(&bebob->out_conn) < 0)) { + amdtp_stream_pcm_abort(&bebob->rx_stream); + amdtp_stream_pcm_abort(&bebob->tx_stream); + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + break_both_connections(bebob); + } else { + amdtp_stream_update(&bebob->rx_stream); + amdtp_stream_update(&bebob->tx_stream); + } + + mutex_unlock(&bebob->mutex); +} + +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) +{ + mutex_lock(&bebob->mutex); + + amdtp_stream_pcm_abort(&bebob->rx_stream); + amdtp_stream_pcm_abort(&bebob->tx_stream); + + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + + amdtp_stream_destroy(&bebob->rx_stream); + amdtp_stream_destroy(&bebob->tx_stream); + + destroy_both_connections(bebob); + + mutex_unlock(&bebob->mutex); +} + +/* + * See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’ + * in Additional AVC commands (Nov 2003, BridgeCo) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +static int +parse_stream_formation(u8 *buf, unsigned int len, + struct snd_bebob_stream_formation *formation) +{ + unsigned int i, e, channels, format; + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((buf[0] != 0x90) || (buf[1] != 0x40)) + return -ENOSYS; + + /* check sampling rate */ + for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) { + if (buf[2] == bridgeco_freq_table[i]) + break; + } + if (i == sizeof(bridgeco_freq_table)) + return -ENOSYS; + + /* Avoid double count by different entries for the same rate. */ + memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation)); + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* IEC 60958-3, currently handle as MBLA */ + case 0x00: + /* Multi bit linear audio */ + case 0x06: /* Raw */ + formation[i].pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation[i].midi += channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi bit linear audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation[i].pcm > AMDTP_MAX_CHANNELS_FOR_PCM || + formation[i].midi > AMDTP_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct snd_bebob_stream_formation *formations; + unsigned int len, eid; + u8 addr[AVC_BRIDGECO_ADDR_BYTES]; + int err; + + buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_BRIDGECO_PLUG_DIR_IN) + formations = bebob->rx_stream_formations; + else + formations = bebob->tx_stream_formations; + + for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) { + len = FORMAT_MAXIMUM_LENGTH; + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, pid); + err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, + &len, eid); + /* No entries remained. */ + if (err == -EINVAL && eid > 0) { + err = 0; + break; + } else if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + + err = parse_stream_formation(buf, len, formations); + if (err < 0) + break; + } + + kfree(buf); + return err; +} + +static int +seek_msu_sync_input_plug(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + unsigned int i, type; + int err; + + /* Get the number of Music Sub Unit for both direction. */ + err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for MSU in/out plugs: %d\n", + err); + goto end; + } + + /* seek destination plugs for 'MSU sync input' */ + bebob->sync_input_plug = -1; + for (i = 0; i < plugs[0]; i++) { + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for MSU in plug %d: %d\n", + i, err); + goto end; + } + + if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { + bebob->sync_input_plug = i; + break; + } + } +end: + return err; +} + +int snd_bebob_stream_discover(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + enum avc_bridgeco_plug_type type; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } + + /* + * This module supports at least one isoc input plug and one isoc + * output plug. + */ + if ((plugs[0] == 0) || (plugs[1] == 0)) { + err = -ENOSYS; + goto end; + } + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc in plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc out plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* count external input plugs for MIDI */ + bebob->midi_input_ports = 0; + for (i = 0; i < plugs[2]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external in plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_input_ports++; + } + } + + /* count external output plugs for MIDI */ + bebob->midi_output_ports = 0; + for (i = 0; i < plugs[3]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external out plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_output_ports++; + } + } + + /* for check source of clock later */ + err = seek_msu_sync_input_plug(bebob); +end: + return err; +}
Normal BeBoB firmware has a quirk. When receiving bus reset, it transmits packets with discontinuous value in dbc field.
This causes two situation, one is to abort streaming by firewire-lib as a result of detecting the discontinuity. Another is to call driver's .update() because of bus reset. These two is generated independently. (The former depends on isochronous stream and the latter depends on IEEE1394 bus driver.)
When BeBoB driver works with XRUN-recoverable applications, this situation looks like stream_start_duplex() call followed by stream_update_duplex() call because applications will call snd_pcm_prepare() immediately at XRUN.
To update connections and streams at first, this commit use completion. When queueing error occurs, stream_start_duplex() is forced to wait maximum 1000msec. During this, when .update() is called, the completion is waken and stream_start_duplex() is processed without breaking connections.
At bus reset, stream_start_duplex() shouldn't break/establish connections and stream_update_duplex() should update connections because a caller of fw_iso_resources_allocate() is responsible for calling fw_iso_resources_update() on bus reset.
This commit also adds a flag, which has an effect to skip checking continuity for first packet. This flag is useful for BeBoB quirk to start handling packets during streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 9 +++++-- sound/firewire/amdtp.h | 3 +++ sound/firewire/bebob/bebob.h | 4 +++ sound/firewire/bebob/bebob_stream.c | 52 ++++++++++++++++++++++++++++++++----- 4 files changed, 60 insertions(+), 8 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 28ee3d8..690c608 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -665,7 +665,8 @@ static void handle_in_packet(struct amdtp_stream *s,
/* Check data block counter continuity */ data_block_counter = cip_header[0] & AMDTP_DBC_MASK; - if ((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && data_block_counter == 0) { + if (((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && data_block_counter == 0) || + (s->data_block_counter == UINT_MAX)) { lost = false; } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) { lost = data_block_counter != s->data_block_counter; @@ -850,7 +851,11 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) goto err_unlock; }
- s->data_block_counter = 0; + if (s->direction == AMDTP_IN_STREAM && + s->flags & CIP_SKIP_INIT_DBC_CHECK) + s->data_block_counter = UINT_MAX; + else + s->data_block_counter = 0; s->data_block_state = initial_state[s->sfc].data_block; s->syt_offset_state = initial_state[s->sfc].syt_offset; s->last_syt_offset = TICKS_PER_CYCLE; diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index fb5934c..c79f058 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -25,6 +25,8 @@ * The value of data_block_quadlets is used instead of reported value. * @SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is * skipped for detecting discontinuity. + * @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first + * packet is not continuous from an initial value. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -34,6 +36,7 @@ enum cip_flags { CIP_DBC_IS_END_EVENT = 0x08, CIP_WRONG_DBS = 0x10, CIP_SKIP_DBC_ZERO_CHECK = 0x20, + CIP_SKIP_INIT_DBC_CHECK = 0x40, };
/** diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index a195c16..031ca78 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -53,6 +53,10 @@ struct snd_bebob { unsigned int midi_input_ports; unsigned int midi_output_ports;
+ /* for bus reset quirk */ + struct completion bus_reset; + bool connected; + struct amdtp_stream *master; struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index c868c17..46b056c 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -9,6 +9,7 @@ #include "./bebob.h"
#define CALLBACK_TIMEOUT 1000 +#define FW_ISO_RESOURCE_DELAY 1000
/* * NOTE; @@ -325,7 +326,10 @@ check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s) static int make_both_connections(struct snd_bebob *bebob, unsigned int rate) { - int index, pcm_channels, midi_channels, err; + int index, pcm_channels, midi_channels, err = 0; + + if (bebob->connected) + goto end;
/* confirm params for both streams */ index = get_formation_index(rate); @@ -345,8 +349,12 @@ make_both_connections(struct snd_bebob *bebob, unsigned int rate) goto end; err = cmp_connection_establish(&bebob->in_conn, amdtp_stream_get_max_payload(&bebob->rx_stream)); - if (err < 0) + if (err < 0) { cmp_connection_break(&bebob->out_conn); + goto end; + } + + bebob->connected = true; end: return err; } @@ -356,6 +364,8 @@ break_both_connections(struct snd_bebob *bebob) { cmp_connection_break(&bebob->in_conn); cmp_connection_break(&bebob->out_conn); + + bebob->connected = false; }
static void @@ -415,6 +425,9 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) destroy_both_connections(bebob); goto end; } + /* See comments in next function */ + init_completion(&bebob->bus_reset); + bebob->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK;
err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, AMDTP_OUT_STREAM, CIP_BLOCKING); @@ -433,8 +446,25 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) atomic_t *slave_substreams; enum cip_flags sync_mode; unsigned int curr_rate; + bool updated = false; int err = 0;
+ /* + * Normal BeBoB firmware has a quirk at bus reset to transmits packets + * with discontinuous value in dbc field. + * + * This 'struct completion' is used to call .update() at first to update + * connections/streams. Next following codes handle streaming error. + */ + if (amdtp_streaming_error(&bebob->tx_stream)) { + if (completion_done(&bebob->bus_reset)) + reinit_completion(&bebob->bus_reset); + + updated = (wait_for_completion_interruptible_timeout( + &bebob->bus_reset, + msecs_to_jiffies(FW_ISO_RESOURCE_DELAY)) > 0); + } + mutex_lock(&bebob->mutex);
/* Need no substreams */ @@ -463,13 +493,19 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) if (err < 0) goto end;
- /* packet queueing error */ - if (amdtp_streaming_error(master)) { + /* + * packet queueing error or detecting discontinuity + * + * At bus reset, connections should not be broken here. So streams need + * to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag. + */ + if (amdtp_streaming_error(master)) amdtp_stream_stop(master); - amdtp_stream_stop(slave); - } if (amdtp_streaming_error(slave)) amdtp_stream_stop(slave); + if (!updated && + !amdtp_stream_running(master) && !amdtp_stream_running(slave)) + break_both_connections(bebob);
/* stop streams if rate is different */ err = snd_bebob_stream_get_rate(bebob, &curr_rate); @@ -599,6 +635,10 @@ void snd_bebob_stream_update_duplex(struct snd_bebob *bebob) amdtp_stream_update(&bebob->tx_stream); }
+ /* wake up stream_start_duplex() */ + if (!completion_done(&bebob->bus_reset)) + complete_all(&bebob->bus_reset); + mutex_unlock(&bebob->mutex); }
This commit adds proc interface to get these information for debugging: - firmware information - stream formation - current clock source and sampling rate
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 2 + sound/firewire/bebob/bebob.h | 3 + sound/firewire/bebob/bebob_proc.c | 151 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_proc.c
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 5cece62..757c40e 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,2 +1,2 @@ -snd-bebob-objs := bebob_command.o bebob_stream.o bebob.o +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 180284d..0c3f1f6 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -162,6 +162,8 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_bebob_proc_init(bebob); + err = snd_bebob_stream_init_duplex(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 031ca78..1247cca 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -20,6 +20,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/info.h>
#include "../lib.h" #include "../fcp.h" @@ -171,6 +172,8 @@ void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
+void snd_bebob_proc_init(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c new file mode 100644 index 0000000..c31ca4f --- /dev/null +++ b/sound/firewire/bebob/bebob_proc.c @@ -0,0 +1,151 @@ +/* + * bebob_proc.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* contents of information register */ +struct hw_info { + u64 manufacturer; + u32 protocol_ver; + u32 bld_ver; + u32 guid[2]; + u32 model_id; + u32 model_rev; + u64 fw_date; + u64 fw_time; + u32 fw_id; + u32 fw_ver; + u32 base_addr; + u32 max_size; + u64 bld_date; + u64 bld_time; +/* may not used in product + u64 dbg_date; + u64 dbg_time; + u32 dbg_id; + u32 dbg_version; +*/ +} __packed; + +static void +proc_read_hw_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct hw_info *info; + + info = kzalloc(sizeof(struct hw_info), GFP_KERNEL); + if (info == NULL) + return; + + if (snd_bebob_read_block(bebob->unit, 0, + info, sizeof(struct hw_info)) < 0) + goto end; + + snd_iprintf(buffer, "Manufacturer:\t%.8s\n", + (char *)&info->manufacturer); + snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver); + snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver); + snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n", + info->guid[0], info->guid[1]); + snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id); + snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev); + snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date); + snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time); + snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id); + snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver); + snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr); + snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size); + snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date); + snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time); + +end: + kfree(info); +} + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->tx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->rx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } +} + +static void +proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + unsigned int rate; + bool internal; + + if (snd_bebob_stream_get_rate(bebob, &rate) >= 0) + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (snd_bebob_stream_check_internal_clock(bebob, &internal) >= 0) + snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)", + (internal) ? "Internal" : "External", + bebob->sync_input_plug); +} + +static void +add_node(struct snd_bebob *bebob, struct snd_info_entry *root, const char *name, + void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(bebob->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, bebob, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_bebob_proc_init(struct snd_bebob *bebob) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(bebob->card, "firewire", + bebob->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(bebob, root, "clock", proc_read_clock); + add_node(bebob, root, "firmware", proc_read_hw_info); + add_node(bebob, root, "formation", proc_read_formation); +}
This commit adds a functionality to capture/playback MIDI messages.
When no AMDTP streams are running, this module starts AMDTP stream at current sampling rate for MIDI substream.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 3 +- sound/firewire/bebob/bebob.c | 7 ++ sound/firewire/bebob/bebob.h | 3 + sound/firewire/bebob/bebob_midi.c | 148 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_midi.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 5539baa..708d3c8 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -82,6 +82,7 @@ config SND_FIREWORKS config SND_BEBOB tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware" select SND_FIREWIRE_LIB + select SND_RAWMIDI help Say Y here to include support for FireWire devices based on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 757c40e..1e39e59 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,2 +1,3 @@ -snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob.o +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ + bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 0c3f1f6..383df9a 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -164,6 +164,13 @@ bebob_probe(struct fw_unit *unit,
snd_bebob_proc_init(bebob);
+ if ((bebob->midi_input_ports > 0) || + (bebob->midi_output_ports > 0)) { + err = snd_bebob_create_midi_devices(bebob); + if (err < 0) + goto error; + } + err = snd_bebob_stream_init_duplex(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 1247cca..0d53cff 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -21,6 +21,7 @@ #include <sound/core.h> #include <sound/initval.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../lib.h" #include "../fcp.h" @@ -174,6 +175,8 @@ void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
void snd_bebob_proc_init(struct snd_bebob *bebob);
+int snd_bebob_create_midi_devices(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c new file mode 100644 index 0000000..120a61b9 --- /dev/null +++ b/sound/firewire/bebob/bebob_midi.c @@ -0,0 +1,148 @@ +/* + * bebob_midi.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "bebob.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + atomic_inc(&bebob->capture_substreams); + return snd_bebob_stream_start_duplex(bebob, 0); +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + atomic_inc(&bebob->playback_substreams); + return snd_bebob_stream_start_duplex(bebob, 0); +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + atomic_dec(&bebob->capture_substreams); + snd_bebob_stream_stop_duplex(bebob); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + atomic_dec(&bebob->playback_substreams); + snd_bebob_stream_stop_duplex(bebob); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&bebob->tx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&bebob->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_stream_midi_trigger(&bebob->rx_stream, + substrm->number, substrm); + else + amdtp_stream_midi_trigger(&bebob->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_bebob *bebob, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + bebob->card->shortname, subs->number + 1); + } +} + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0, + bebob->midi_output_ports, bebob->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", bebob->card->shortname); + rmidi->private_data = bebob; + + if (bebob->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(bebob, str); + } + + if (bebob->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(bebob, str); + } + + if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +}
This commit adds a functionality to capture/playback PCM samples.
When AMDTP stream is already running for PCM or the source of clock is not internal, available sampling rate is limited at current one.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 1 + sound/firewire/bebob/bebob.c | 4 + sound/firewire/bebob/bebob.h | 4 + sound/firewire/bebob/bebob_pcm.c | 369 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 379 insertions(+) create mode 100644 sound/firewire/bebob/bebob_pcm.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 708d3c8..d55fd5f 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -83,6 +83,7 @@ config SND_BEBOB tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware" select SND_FIREWIRE_LIB select SND_RAWMIDI + select SND_PCM help Say Y here to include support for FireWire devices based on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 1e39e59..533718a 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,3 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ + bebob_pcm.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 383df9a..dbd12c3 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -171,6 +171,10 @@ bebob_probe(struct fw_unit *unit, goto error; }
+ err = snd_bebob_create_pcm_devices(bebob); + if (err < 0) + goto error; + err = snd_bebob_stream_init_duplex(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 0d53cff..b41bb91 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -22,6 +22,8 @@ #include <sound/initval.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h>
#include "../lib.h" #include "../fcp.h" @@ -177,6 +179,8 @@ void snd_bebob_proc_init(struct snd_bebob *bebob);
int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
+int snd_bebob_create_pcm_devices(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c new file mode 100644 index 0000000..7474309 --- /dev/null +++ b/sound/firewire/bebob/bebob_pcm.c @@ -0,0 +1,369 @@ +/* + * bebob_pcm.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(c, formations[i].pcm)) + continue; + + t.min = min(t.min, snd_bebob_rate_table[i]); + t.max = max(t.max, snd_bebob_rate_table[i]); + + } + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_bebob_rate_table[i])) + continue; + + t.min = min(t.min, formations[i].pcm); + t.max = max(t.max, formations[i].pcm); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels_and_rates(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + + hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]); + } +} + +static void +limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int +pcm_init_hw_params(struct snd_bebob *bebob, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + struct snd_bebob_stream_formation *formations; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + s = &bebob->tx_stream; + formations = bebob->tx_stream_formations; + } else { + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + s = &bebob->rx_stream; + formations = bebob->rx_stream_formations; + } + + limit_channels_and_rates(&runtime->hw, formations); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formations, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formations, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + unsigned int sampling_rate; + bool internal; + int err; + + err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto end; + + err = snd_bebob_stream_check_internal_clock(bebob, &internal); + if (err < 0) + goto end; + + /* + * When source of clock is internal or any PCM stream are running, + * the available sampling rate is limited at current sampling rate. + */ + if (!internal || + amdtp_stream_pcm_running(&bebob->tx_stream) || + amdtp_stream_pcm_running(&bebob->rx_stream)) { + err = snd_bebob_stream_get_rate(bebob, &sampling_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto end; + } + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); + +end: + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int +pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&bebob->capture_substreams); + amdtp_stream_set_pcm_format(&bebob->tx_stream, + params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int +pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&bebob->playback_substreams); + amdtp_stream_set_pcm_format(&bebob->rx_stream, + params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int +pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&bebob->capture_substreams); + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int +pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&bebob->playback_substreams); + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->tx_stream); + + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->rx_stream); + + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->tx_stream); +} +static snd_pcm_uframes_t +pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->rx_stream); +} + +static const struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; +static const struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = bebob; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", bebob->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); +end: + return err; +}
This interface is designed for mixer/control application. By using hwdep interface, the application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 1 + sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 5 + sound/firewire/bebob/bebob.h | 13 +++ sound/firewire/bebob/bebob_hwdep.c | 199 ++++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_midi.c | 24 ++++- sound/firewire/bebob/bebob_pcm.c | 16 ++- sound/firewire/bebob/bebob_stream.c | 39 +++++++ 10 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 sound/firewire/bebob/bebob_hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 5dfbfdf..2249483 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -95,9 +95,10 @@ enum { SNDRV_HWDEP_IFACE_USB_STREAM, /* direct access to usb stream */ SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */ SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */ + SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREWORKS + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index bfc0e23e4..af4bd13 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -54,6 +54,7 @@ union snd_firewire_event {
#define SNDRV_FIREWIRE_TYPE_DICE 1 #define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 +#define SNDRV_FIREWIRE_TYPE_BEBOB 3 /* AV/C, RME, MOTU, ... */
struct snd_firewire_get_info { diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index d55fd5f..2175064 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -84,6 +84,7 @@ config SND_BEBOB select SND_FIREWIRE_LIB select SND_RAWMIDI select SND_PCM + select SND_HWDEP help Say Y here to include support for FireWire devices based on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 533718a..7808777 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,4 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ - bebob_pcm.o \ + bebob_pcm.o bebob_hwdep.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index dbd12c3..b7d70c2 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -153,6 +153,7 @@ bebob_probe(struct fw_unit *unit, bebob->unit = unit; mutex_init(&bebob->mutex); spin_lock_init(&bebob->lock); + init_waitqueue_head(&bebob->hwdep_wait);
err = name_device(bebob, entry->vendor_id); if (err < 0) @@ -175,6 +176,10 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_bebob_create_hwdep_device(bebob); + if (err < 0) + goto error; + err = snd_bebob_stream_init_duplex(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index b41bb91..e8a5e44 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -24,6 +24,8 @@ #include <sound/rawmidi.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../lib.h" #include "../fcp.h" @@ -75,6 +77,11 @@ struct snd_bebob { rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
int sync_input_plug; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
static inline int @@ -175,12 +182,18 @@ void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
+void snd_bebob_stream_lock_changed(struct snd_bebob *bebob); +int snd_bebob_stream_lock_try(struct snd_bebob *bebob); +void snd_bebob_stream_lock_release(struct snd_bebob *bebob); + void snd_bebob_proc_init(struct snd_bebob *bebob);
int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
+int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c new file mode 100644 index 0000000..ce731f4 --- /dev/null +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -0,0 +1,199 @@ +/* + * bebob_hwdep.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node infomation + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "bebob.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_bebob *bebob = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&bebob->lock); + + while (!bebob->dev_lock_changed) { + prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&bebob->lock); + schedule(); + finish_wait(&bebob->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&bebob->lock); + } + + memset(&event, 0, sizeof(event)); + if (bebob->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (bebob->dev_lock_count > 0); + bebob->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&bebob->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_bebob *bebob = hwdep->private_data; + unsigned int events; + + poll_wait(file, &bebob->hwdep_wait, wait); + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&bebob->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_bebob *bebob, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(bebob->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_BEBOB; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == 0) { + bebob->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == -1) { + bebob->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_bebob *bebob = hwdep->private_data; + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_count == -1) + bebob->dev_lock_count = 0; + spin_unlock_irq(&bebob->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_bebob *bebob = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(bebob, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(bebob); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(bebob); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, +}; + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "BeBoB"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB; + hwdep->ops = hwdep_ops; + hwdep->private_data = bebob; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c index 120a61b9..c04cea2 100644 --- a/sound/firewire/bebob/bebob_midi.c +++ b/sound/firewire/bebob/bebob_midi.c @@ -11,17 +11,35 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) { struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end;
atomic_inc(&bebob->capture_substreams); - return snd_bebob_stream_start_duplex(bebob, 0); + err = snd_bebob_stream_start_duplex(bebob, 0); + if (err < 0) + snd_bebob_stream_lock_release(bebob); +end: + return err; }
static int midi_playback_open(struct snd_rawmidi_substream *substream) { struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end;
atomic_inc(&bebob->playback_substreams); - return snd_bebob_stream_start_duplex(bebob, 0); + err = snd_bebob_stream_start_duplex(bebob, 0); + if (err < 0) + snd_bebob_stream_lock_release(bebob); +end: + return err; }
static int midi_capture_close(struct snd_rawmidi_substream *substream) @@ -31,6 +49,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) atomic_dec(&bebob->capture_substreams); snd_bebob_stream_stop_duplex(bebob);
+ snd_bebob_stream_lock_release(bebob); return 0; }
@@ -41,6 +60,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream) atomic_dec(&bebob->playback_substreams); snd_bebob_stream_stop_duplex(bebob);
+ snd_bebob_stream_lock_release(bebob); return 0; }
diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c index 7474309..9d86817 100644 --- a/sound/firewire/bebob/bebob_pcm.c +++ b/sound/firewire/bebob/bebob_pcm.c @@ -159,13 +159,17 @@ pcm_open(struct snd_pcm_substream *substream) bool internal; int err;
- err = pcm_init_hw_params(bebob, substream); + err = snd_bebob_stream_lock_try(bebob); if (err < 0) goto end;
+ err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto err_locked; + err = snd_bebob_stream_check_internal_clock(bebob, &internal); if (err < 0) - goto end; + goto err_locked;
/* * When source of clock is internal or any PCM stream are running, @@ -178,7 +182,7 @@ pcm_open(struct snd_pcm_substream *substream) if (err < 0) { dev_err(&bebob->unit->device, "fail to get sampling rate: %d\n", err); - goto end; + goto err_locked; }
substream->runtime->hw.rate_min = sampling_rate; @@ -186,14 +190,18 @@ pcm_open(struct snd_pcm_substream *substream) }
snd_pcm_set_sync(substream); - end: return err; +err_locked: + snd_bebob_stream_lock_release(bebob); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_bebob *bebob = substream->private_data; + snd_bebob_stream_lock_release(bebob); return 0; }
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 46b056c..85b4fd6 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -912,3 +912,42 @@ int snd_bebob_stream_discover(struct snd_bebob *bebob) end: return err; } + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob) +{ + bebob->dev_lock_changed = true; + wake_up(&bebob->hwdep_wait); +} + +int snd_bebob_stream_lock_try(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + /* user land lock this */ + if (bebob->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (bebob->dev_lock_count++ == 0) + snd_bebob_stream_lock_changed(bebob); + err = 0; +end: + spin_unlock_irq(&bebob->lock); + return err; +} + +void snd_bebob_stream_lock_release(struct snd_bebob *bebob) +{ + spin_lock_irq(&bebob->lock); + + if (WARN_ON(bebob->dev_lock_count <= 0)) + goto end; + if (--bebob->dev_lock_count == 0) + snd_bebob_stream_lock_changed(bebob); +end: + spin_unlock_irq(&bebob->lock); +}
This commit is for some devices which have its own operations or quirks.
Many functionality should be implemented in user land. Then this commit adds functionality related to stream such as sampling frequency or clock source. For help to debug, this commit adds the functionality to get metering information if it's available.
To help these functionalities, this commit adds some AV/C commands defined in 'AV/C Audio Subunit Specification (1394TA).
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/Makefile | 3 +- sound/firewire/bebob/bebob.c | 72 ++++++++++++++++++++------------- sound/firewire/bebob/bebob.h | 33 +++++++++++++++- sound/firewire/bebob/bebob_command.c | 77 ++++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_pcm.c | 3 +- sound/firewire/bebob/bebob_proc.c | 57 +++++++++++++++++++++++--- sound/firewire/bebob/bebob_stream.c | 27 ++++++++++--- 7 files changed, 230 insertions(+), 42 deletions(-)
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 7808777..e4b08e3 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,4 +1,3 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ - bebob_pcm.o bebob_hwdep.o \ - bebob.o + bebob_pcm.o bebob_hwdep.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index b7d70c2..3d79090 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -126,6 +126,7 @@ bebob_probe(struct fw_unit *unit, { struct snd_card *card; struct snd_bebob *bebob; + const struct snd_bebob_spec *spec; unsigned int card_index; int err;
@@ -140,6 +141,12 @@ bebob_probe(struct fw_unit *unit, goto end; }
+ spec = (const struct snd_bebob_spec *)entry->driver_data; + if (spec == NULL) { + err = -ENOSYS; + goto end; + } + err = snd_card_new(&unit->device, index[card_index], id[card_index], THIS_MODULE, sizeof(struct snd_bebob), &card); if (err < 0) @@ -151,6 +158,7 @@ bebob_probe(struct fw_unit *unit,
bebob->card = card; bebob->unit = unit; + bebob->spec = spec; mutex_init(&bebob->mutex); spin_lock_init(&bebob->lock); init_waitqueue_head(&bebob->hwdep_wait); @@ -216,62 +224,72 @@ static void bebob_remove(struct fw_unit *unit) snd_card_free_when_closed(bebob->card); }
+struct snd_bebob_rate_spec normal_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate +}; +static const struct snd_bebob_spec spec_normal = { + .clock = NULL, + .rate = &normal_rate_spec, + .meter = NULL +}; + static const struct ieee1394_device_id bebob_id_table[] = { /* Edirol, FA-66 */ - SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049), + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal), /* Edirol, FA-101 */ - SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal), /* Presonus, FIREBOX */ - SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000), + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal), /* PreSonus, FIREPOD/FP10 */ - SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066), + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal), /* PreSonus, Inspire1394 */ - SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001), + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal), /* BridgeCo, RDAudio1 */ - SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal), /* BridgeCo, Audio5 */ - SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049), + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal), /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */ - SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065), + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal), /* Mackie, d.2 (Firewire Option) */ - SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067), + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal), /* Stanton, ScratchAmp */ - SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001), + SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal), /* Tascam, IF-FW DM */ - SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067), + SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal), /* Behringer, XENIX UFX 1204 */ - SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204), + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal), /* Behringer, XENIX UFX 1604 */ - SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604), + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal), /* Behringer, Digital Mixer X32 series (X-UF Card) */ - SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006), + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal), /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */ /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */ - SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal), /* Apogee Electronics, Ensemble */ - SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00001eee), + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00001eee, &spec_normal), /* ESI, Quatafire610 */ - SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064), + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal), /* AcousticReality, eARMasterOne */ - SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002), + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal), /* CME, MatrixKFW */ - SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000), + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal), /* Phonic, Helix Board 12 MkII */ - SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000), + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal), /* Phonic, Helix Board 18 MkII */ - SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000), + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal), /* Phonic, Helix Board 24 MkII */ - SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000), + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal), /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */ - SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000), + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal), /* Lynx, Aurora 8/16 (LT-FW) */ - SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001), + SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal), /* ICON, FireXon */ - SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001), + SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal), /* PrismSound, Orpheus */ - SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal), /* PrismSound, ADA-8XR */ - SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8), + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal), /* IDs are unknown but able to be supported */ /* Apogee, Mini-ME Firewire */ /* Apogee, Mini-DAC Firewire */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index e8a5e44..7365f92 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -48,6 +48,28 @@ struct snd_bebob_stream_formation { /* this is a lookup table for index of stream formations */ extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES];
+/* device specific operations */ +#define SND_BEBOB_CLOCK_INTERNAL "Internal" +struct snd_bebob_clock_spec { + unsigned int num; + char *const *labels; + int (*get)(struct snd_bebob *bebob, unsigned int *id); +}; +struct snd_bebob_rate_spec { + int (*get)(struct snd_bebob *bebob, unsigned int *rate); + int (*set)(struct snd_bebob *bebob, unsigned int rate); +}; +struct snd_bebob_meter_spec { + unsigned int num; + char *const *labels; + int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size); +}; +struct snd_bebob_spec { + struct snd_bebob_clock_spec *clock; + struct snd_bebob_rate_spec *rate; + struct snd_bebob_meter_spec *meter; +}; + struct snd_bebob { struct snd_card *card; struct fw_unit *unit; @@ -56,6 +78,8 @@ struct snd_bebob { struct mutex mutex; spinlock_t lock;
+ const struct snd_bebob_spec *spec; + unsigned int midi_input_ports; unsigned int midi_output_ports;
@@ -100,6 +124,12 @@ snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) (void *)buf, sizeof(u32), 0); }
+/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */ +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num); +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num); + /* * AVC command extensions, AV/C Unit and Subunit, Revision 17 * (Nov 2003, BridgeCo) @@ -194,12 +224,13 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
-#define SND_BEBOB_DEV_ENTRY(vendor, model) \ +#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ IEEE1394_MATCH_MODEL_ID, \ .vendor_id = vendor, \ .model_id = model, \ + .driver_data = (kernel_ulong_t)data \ }
#endif diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c index 6a01795..9402cc1 100644 --- a/sound/firewire/bebob/bebob_command.c +++ b/sound/firewire/bebob/bebob_command.c @@ -8,6 +8,83 @@
#include "./bebob.h"
+int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff & num; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err > 0 && err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (err > 0) + err = 0; + + kfree(buf); + return err; +} + +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(8)); + if (err > 0 && err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *num = buf[7]; + err = 0; +end: + kfree(buf); + return err; +} + static inline void avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr) { diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c index 9d86817..4a55561 100644 --- a/sound/firewire/bebob/bebob_pcm.c +++ b/sound/firewire/bebob/bebob_pcm.c @@ -155,6 +155,7 @@ static int pcm_open(struct snd_pcm_substream *substream) { struct snd_bebob *bebob = substream->private_data; + struct snd_bebob_rate_spec *spec = bebob->spec->rate; unsigned int sampling_rate; bool internal; int err; @@ -178,7 +179,7 @@ pcm_open(struct snd_pcm_substream *substream) if (!internal || amdtp_stream_pcm_running(&bebob->tx_stream) || amdtp_stream_pcm_running(&bebob->rx_stream)) { - err = snd_bebob_stream_get_rate(bebob, &sampling_rate); + err = spec->get(bebob, &sampling_rate); if (err < 0) { dev_err(&bebob->unit->device, "fail to get sampling rate: %d\n", err); diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c index c31ca4f..335da64 100644 --- a/sound/firewire/bebob/bebob_proc.c +++ b/sound/firewire/bebob/bebob_proc.c @@ -69,6 +69,39 @@ end: }
static void +proc_read_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + u32 *buf; + unsigned int i, c, channels, size; + + if (spec == NULL) + return; + + channels = spec->num * 2; + size = channels * sizeof(u32); + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return; + + if (spec->get(bebob, buf, size) < 0) + goto end; + + for (i = 0, c = 1; i < channels; i++) { + snd_iprintf(buffer, "%s %d:\t%d\n", + spec->labels[i / 2], c++, buf[i]); + if ((i + 1 < channels - 1) && + (strcmp(spec->labels[i / 2], + spec->labels[(i + 1) / 2]) != 0)) + c = 1; + } +end: + kfree(buf); +} + +static void proc_read_formation(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { @@ -100,16 +133,25 @@ proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { struct snd_bebob *bebob = entry->private_data; - unsigned int rate; + struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; + struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + unsigned int rate, id; bool internal;
- if (snd_bebob_stream_get_rate(bebob, &rate) >= 0) + if (rate_spec->get(bebob, &rate) >= 0) snd_iprintf(buffer, "Sampling rate: %d\n", rate);
- if (snd_bebob_stream_check_internal_clock(bebob, &internal) >= 0) - snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)", - (internal) ? "Internal" : "External", - bebob->sync_input_plug); + if (clk_spec) { + if (clk_spec->get(bebob, &id) >= 0) + snd_iprintf(buffer, "Clock Source: %s\n", + clk_spec->labels[id]); + } else { + if (snd_bebob_stream_check_internal_clock(bebob, + &internal) >= 0) + snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n", + (internal) ? "Internal" : "External", + bebob->sync_input_plug); + } }
static void @@ -148,4 +190,7 @@ void snd_bebob_proc_init(struct snd_bebob *bebob) add_node(bebob, root, "clock", proc_read_clock); add_node(bebob, root, "firmware", proc_read_hw_info); add_node(bebob, root, "formation", proc_read_formation); + + if (bebob->spec->meter != NULL) + add_node(bebob, root, "meter", proc_read_meters); } diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 85b4fd6..4a21dcf 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -119,13 +119,27 @@ end: int snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, bool *internal) { + struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; + unsigned int id; int err = 0;
*internal = false;
+ /* 1.The device has its own operation to switch source of clock */ + if (clk_spec) { + err = clk_spec->get(bebob, &id); + if (err < 0) + dev_err(&bebob->unit->device, + "fail to get clock source: %d\n", err); + else if (strncmp(clk_spec->labels[id], SND_BEBOB_CLOCK_INTERNAL, + strlen(SND_BEBOB_CLOCK_INTERNAL)) == 0) + *internal = true; + goto end; + } + /* - * 1.The device don't support to switch source of clock then assumed + * 2.The device don't support to switch source of clock then assumed * to use internal clock always */ if (bebob->sync_input_plug < 0) { @@ -134,7 +148,7 @@ snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, bool *internal) }
/* - * 2.The device supports to switch source of clock by an usual way. + * 3.The device supports to switch source of clock by an usual way. * Let's check input for 'Music Sub Unit Sync Input' plug. */ avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, @@ -442,6 +456,7 @@ end:
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) { + struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; struct amdtp_stream *master, *slave; atomic_t *slave_substreams; enum cip_flags sync_mode; @@ -508,7 +523,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) break_both_connections(bebob);
/* stop streams if rate is different */ - err = snd_bebob_stream_get_rate(bebob, &curr_rate); + err = rate_spec->get(bebob, &curr_rate); if (err < 0) { dev_err(&bebob->unit->device, "fail to get sampling rate: %d\n", err); @@ -532,7 +547,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) * If establishing connections at first, Yamaha GO46 * (and maybe Terratec X24) don't generate sound. */ - err = snd_bebob_stream_set_rate(bebob, rate); + err = rate_spec->set(bebob, rate); if (err < 0) { dev_err(&bebob->unit->device, "fail to set sampling rate: %d\n", @@ -822,6 +837,7 @@ end:
int snd_bebob_stream_discover(struct snd_bebob *bebob) { + struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; enum avc_bridgeco_plug_type type; unsigned int i; @@ -908,7 +924,8 @@ int snd_bebob_stream_discover(struct snd_bebob *bebob) }
/* for check source of clock later */ - err = seek_msu_sync_input_plug(bebob); + if (!clk_spec) + err = seek_msu_sync_input_plug(bebob); end: return err; }
This commit allows this driver to support all of models which Terratec produced with DM1000/BeBoB. They are: - PHASE 24 FW - PHASE X24 FW - PHASE 88 Rack FW - EWS MIC2 - EWS MIC4 - Aureon 7.1 Firewire
For Phase series, this commit adds a Terratec specific operation. To get source of clock. AV/C Audio Subunit command is used.
For EWS series and Aureon, this module uses normal operations.
Tested-by: Maximilian Engelhardt maxi@daemonizer.de (PHASE 24 FW) Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 3 ++ sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 11 ++++++ sound/firewire/bebob/bebob.h | 4 +++ sound/firewire/bebob/bebob_terratec.c | 68 +++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_terratec.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 2175064..b1a1d7d 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -108,6 +108,9 @@ config SND_BEBOB * Lynx Aurora 8/16 (LT-FW) * ICON FireXon * PrismSound Orpheus/ADA-8XR + * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW + * Terratec EWS MIC2/EWS MIC4 + * Terratec Aureon 7.1 Firewire
To compile this driver as a module, choose M here: the module will be called snd-bebob. diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index e4b08e3..cb38dd1 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,3 +1,3 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ - bebob_pcm.o bebob_hwdep.o bebob.o + bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 3d79090..262677e 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -52,6 +52,7 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS); #define VEN_LYNX 0x000019e5 #define VEN_ICON 0x00001a9e #define VEN_PRISMSOUND 0x00001198 +#define VEN_TERRATEC 0x00000aac
static int name_device(struct snd_bebob *bebob, unsigned int vendor_id) @@ -290,6 +291,16 @@ static const struct ieee1394_device_id bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal), /* PrismSound, ADA-8XR */ SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal), + /* TerraTec Electronic GmbH, PHASE 88 Rack FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec), + /* TerraTec Electronic GmbH, PHASE 24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &phase24_series_spec), + /* TerraTec Electronic GmbH, Phase X24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &phase24_series_spec), + /* TerraTec Electronic GmbH, EWS MIC2/MIC8 */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal), + /* Terratec Electronic GmbH, Aureon 7.1 Firewire */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal), /* IDs are unknown but able to be supported */ /* Apogee, Mini-ME Firewire */ /* Apogee, Mini-DAC Firewire */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 7365f92..6c11af8 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -224,6 +224,10 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
+/* model specific operations */ +extern struct snd_bebob_spec phase88_rack_spec; +extern struct snd_bebob_spec phase24_series_spec; + #define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c new file mode 100644 index 0000000..2a89c1c --- /dev/null +++ b/sound/firewire/bebob/bebob_terratec.c @@ -0,0 +1,68 @@ +/* + * bebob_terratec.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static char *const phase88_rack_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital In", "Word Clock" +}; +static int +phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + unsigned int enable_ext, enable_word; + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_ext); + if (err < 0) + goto end; + err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_word); + if (err < 0) + goto end; + + *id = (enable_ext & 0x01) | ((enable_word & 0x01) << 1); +end: + return err; +} + +static char *const phase24_series_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital In" +}; +static int +phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + return avc_audio_get_selector(bebob->unit, 0, 4, id); +} + +struct snd_bebob_rate_spec phase_series_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; + +/* PHASE 88 Rack FW */ +struct snd_bebob_clock_spec phase88_rack_clk = { + .num = ARRAY_SIZE(phase88_rack_clk_src_labels), + .labels = phase88_rack_clk_src_labels, + .get = &phase88_rack_clk_src_get, +}; +struct snd_bebob_spec phase88_rack_spec = { + .clock = &phase88_rack_clk, + .rate = &phase_series_rate_spec, + .meter = NULL +}; + +/* 'PHASE 24 FW' and 'PHASE X24 FW' */ +struct snd_bebob_clock_spec phase24_series_clk = { + .num = ARRAY_SIZE(phase24_series_clk_src_labels), + .labels = phase24_series_clk_src_labels, + .get = &phase24_series_clk_src_get, +}; +struct snd_bebob_spec phase24_series_spec = { + .clock = &phase24_series_clk, + .rate = &phase_series_rate_spec, + .meter = NULL +};
This commit allows this driver to support all of models which Yamaha produced with DM1000/BeBoB. They are: - GO44 - GO46
This commit adds Yamaha specific operations. To get source of clock, AV/C Audio Subunit command is used.
I note that their appearances are similar to some models of TerraTec; 'Go44' is similar to 'PHASE 24 FW' and 'GO46' is similar to 'PHASE X24 FW'. But their combination of Audio/Music subunits is a bit different.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 3 ++- sound/firewire/bebob/bebob.c | 5 ++++ sound/firewire/bebob/bebob.h | 1 + sound/firewire/bebob/bebob_yamaha.c | 50 +++++++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_yamaha.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b1a1d7d..f8e4845 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -111,6 +111,7 @@ config SND_BEBOB * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW * Terratec EWS MIC2/EWS MIC4 * Terratec Aureon 7.1 Firewire + * Yamaha GO44/GO46
To compile this driver as a module, choose M here: the module will be called snd-bebob. diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index cb38dd1..c35eee1 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,3 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ - bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob.o + bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \ + bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 262677e..6a63504 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -53,6 +53,7 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS); #define VEN_ICON 0x00001a9e #define VEN_PRISMSOUND 0x00001198 #define VEN_TERRATEC 0x00000aac +#define VEN_YAMAHA 0x0000a0de
static int name_device(struct snd_bebob *bebob, unsigned int vendor_id) @@ -301,6 +302,10 @@ static const struct ieee1394_device_id bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal), /* Terratec Electronic GmbH, Aureon 7.1 Firewire */ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal), + /* Yamaha, GO44 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_go_spec), + /* YAMAHA, GO46 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_go_spec), /* IDs are unknown but able to be supported */ /* Apogee, Mini-ME Firewire */ /* Apogee, Mini-DAC Firewire */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 6c11af8..e92d356 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -227,6 +227,7 @@ int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); /* model specific operations */ extern struct snd_bebob_spec phase88_rack_spec; extern struct snd_bebob_spec phase24_series_spec; +extern struct snd_bebob_spec yamaha_go_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ diff --git a/sound/firewire/bebob/bebob_yamaha.c b/sound/firewire/bebob/bebob_yamaha.c new file mode 100644 index 0000000..9b7e798 --- /dev/null +++ b/sound/firewire/bebob/bebob_yamaha.c @@ -0,0 +1,50 @@ +/* + * bebob_yamaha.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* + * NOTE: + * Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams + * must be accompanied. If changing the state, a LED on the device starts to + * blink and its sync status is false. In this state, the device sounds nothing + * even if streaming. To start streaming at the current sampling rate is only + * way to revocer this state. GO46 is better for stand-alone mixer. + * + * Both of them have a capability to change its sampling rate up to 192.0kHz. + * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out. + * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use + * 'Extended Stream Format Information Command - Single Request' in 'Additional + * AVC commands' defined by BridgeCo. + * This ALSA driver don't do this because a bit tiresome. Then isochronous + * streaming with many asynchronous transactions brings sounds with noises. + * Unfortunately current 'ffado-mixer' generated many asynchronous transaction + * to observe device's state, mainly check cmp connection and signal format. I + * reccomend users to close ffado-mixer at 192.0kHz if mixer is needless. + */ + +static char *const clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"}; +static int +clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + return avc_audio_get_selector(bebob->unit, 0, 4, id); +} +static struct snd_bebob_clock_spec clock_spec = { + .num = ARRAY_SIZE(clk_src_labels), + .labels = clk_src_labels, + .get = &clk_src_get, +}; +static struct snd_bebob_rate_spec rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +struct snd_bebob_spec yamaha_go_spec = { + .clock = &clock_spec, + .rate = &rate_spec, + .meter = NULL +};
This commit allows this driver to support all of models which Focusrite produces with DM1000/BeBoB. They are: - Saffire - Saffire LE - SaffirePro 10 I/O - SaffirePro 26 I/O
This commit adds Focusrite specific operations: 1. Get source of clock 2. Get/Set sampling frequency 3. Get metering information
The driver uses these functionalities to read/write specific address by async transaction.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 30 +++- sound/firewire/bebob/bebob.h | 4 + sound/firewire/bebob/bebob_focusrite.c | 279 +++++++++++++++++++++++++++++++++ 5 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/bebob/bebob_focusrite.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index f8e4845..605e44b 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -112,6 +112,7 @@ config SND_BEBOB * Terratec EWS MIC2/EWS MIC4 * Terratec Aureon 7.1 Firewire * Yamaha GO44/GO46 + * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO
To compile this driver as a module, choose M here: the module will be called snd-bebob. diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index c35eee1..6565420 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,4 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \ - bebob.o + bebob_focusrite.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 6a63504..6d23e44 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -54,6 +54,9 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS); #define VEN_PRISMSOUND 0x00001198 #define VEN_TERRATEC 0x00000aac #define VEN_YAMAHA 0x0000a0de +#define VEN_FOCUSRITE 0x0000130e + +#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
static int name_device(struct snd_bebob *bebob, unsigned int vendor_id) @@ -122,6 +125,20 @@ bebob_card_free(struct snd_card *card) mutex_destroy(&bebob->mutex); }
+static const struct snd_bebob_spec * +get_saffire_spec(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return NULL; + + if (strcmp(name, "SaffireLE") == 0) + return &saffire_le_spec; + else + return &saffire_spec; +} + static int bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) @@ -143,7 +160,11 @@ bebob_probe(struct fw_unit *unit, goto end; }
- spec = (const struct snd_bebob_spec *)entry->driver_data; + if ((entry->vendor_id == VEN_FOCUSRITE) && + (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)) + spec = get_saffire_spec(unit); + else + spec = (const struct snd_bebob_spec *)entry->driver_data; if (spec == NULL) { err = -ENOSYS; goto end; @@ -306,6 +327,13 @@ static const struct ieee1394_device_id bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_go_spec), /* YAMAHA, GO46 */ SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_go_spec), + /* Focusrite, SaffirePro 26 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec), + /* Focusrite, SaffirePro 10 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000006, &saffirepro_10_spec), + /* Focusrite, Saffire(no label and LE) */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, + &saffire_spec), /* IDs are unknown but able to be supported */ /* Apogee, Mini-ME Firewire */ /* Apogee, Mini-DAC Firewire */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index e92d356..220b115 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -228,6 +228,10 @@ int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); extern struct snd_bebob_spec phase88_rack_spec; extern struct snd_bebob_spec phase24_series_spec; extern struct snd_bebob_spec yamaha_go_spec; +extern struct snd_bebob_spec saffirepro_26_spec; +extern struct snd_bebob_spec saffirepro_10_spec; +extern struct snd_bebob_spec saffire_le_spec; +extern struct snd_bebob_spec saffire_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c new file mode 100644 index 0000000..cd29dbc --- /dev/null +++ b/sound/firewire/bebob/bebob_focusrite.c @@ -0,0 +1,279 @@ +/* + * bebob_focusrite.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define ANA_IN "Analog In" +#define DIG_IN "Digital In" +#define ANA_OUT "Analog Out" +#define DIG_OUT "Digital Out" +#define STM_IN "Stream In" + +#define SAFFIRE_ADDRESS_BASE 0x000100000000 + +#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x0000000000f8 +#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x000000000174 + +/* whether sync to external device or not */ +#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x00000000013c +#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x000000000432 +#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x000000000164 + +#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIRE_CLOCK_SOURCE_SPDIF 1 + +/* '1' is absent, why... */ +#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 +#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5 + +/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */ +#define SAFFIREPRO_ENABLE_DIG_IFACES 0x0000000001a4 + +/* saffirepro has its own parameter for sampling frequency */ +#define SAFFIREPRO_RATE_NOREBOOT 0x0000000001cc +/* index is the value for this register */ +static const unsigned int rates[] = { + [0] = 0, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000 +}; + +/* saffire(no label)/saffire LE has metering */ +#define SAFFIRE_OFFSET_METER 0x000000000100 +#define SAFFIRE_LE_OFFSET_METER 0x000000000168 + +static inline int +saffire_read_block(struct snd_bebob *bebob, u64 offset, + u32 *buf, unsigned int size) +{ + unsigned int i; + int err; + __be32 *tmp = (__be32 *)buf; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + tmp, size, 0); + if (err < 0) + goto end; + + for (i = 0; i < size / sizeof(u32); i++) + buf[i] = be32_to_cpu(tmp[i]); +end: + return err; +} + +static inline int +saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value) +{ + int err; + __be32 tmp; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &tmp, sizeof(__be32), 0); + if (err < 0) + goto end; + + *value = be32_to_cpu(tmp); +end: + return err; +} + +static inline int +saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value) +{ + __be32 data = cpu_to_be32(value); + + return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &data, sizeof(__be32), 0); +} + +static char *const saffirepro_26_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "ADAT1", "ADAT2", "Word Clock" +}; + +static char *const saffirepro_10_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "Word Clock" +}; +static int +saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate) +{ + u32 id; + int err; + + err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id); + if (err < 0) + goto end; + if (id >= ARRAY_SIZE(rates)) + err = -EIO; + else + *rate = rates[id]; +end: + return err; +} +static int +saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate) +{ + u32 id; + + for (id = 0; id < ARRAY_SIZE(rates); id++) { + if (rates[id] == rate) + break; + } + if (id == ARRAY_SIZE(rates)) + return -EINVAL; + + return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id); +} +static int +saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value); + if (err < 0) + goto end; + + if (bebob->spec->clock->labels == saffirepro_10_clk_src_labels) { + if (value == SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK) + *id = 2; + else if (value == SAFFIREPRO_CLOCK_SOURCE_SPDIF) + *id = 1; + } else if (value > 1) { + *id = value - 1; + } +end: + return err; +} + +struct snd_bebob_spec saffire_le_spec; +static char *const saffire_both_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF" +}; +static int +saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value); + if (err >= 0) + *id = 0xff & value; + + return err; +}; +static char *const saffire_le_meter_labels[] = { + ANA_IN, ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + STM_IN, STM_IN +}; +static char *const saffire_meter_labels[] = { + ANA_IN, ANA_IN, + STM_IN, STM_IN, STM_IN, STM_IN, STM_IN, +}; +static int +saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int channels; + u64 offset; + int err; + + if (spec->labels == saffire_le_meter_labels) + offset = SAFFIRE_LE_OFFSET_METER; + else + offset = SAFFIRE_OFFSET_METER; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EIO; + + err = saffire_read_block(bebob, offset, buf, size); + if (err >= 0 && spec->labels == saffire_le_meter_labels) { + swap(buf[1], buf[3]); + swap(buf[2], buf[3]); + swap(buf[3], buf[4]); + + swap(buf[7], buf[10]); + swap(buf[8], buf[10]); + swap(buf[9], buf[11]); + swap(buf[11], buf[12]); + + swap(buf[15], buf[16]); + } + + return err; +} + +static struct snd_bebob_rate_spec saffirepro_both_rate_spec = { + .get = &saffirepro_both_clk_freq_get, + .set = &saffirepro_both_clk_freq_set, +}; +/* Saffire Pro 26 I/O */ +static struct snd_bebob_clock_spec saffirepro_26_clk_spec = { + .num = ARRAY_SIZE(saffirepro_26_clk_src_labels), + .labels = saffirepro_26_clk_src_labels, + .get = &saffirepro_both_clk_src_get, +}; +struct snd_bebob_spec saffirepro_26_spec = { + .clock = &saffirepro_26_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; +/* Saffire Pro 10 I/O */ +static struct snd_bebob_clock_spec saffirepro_10_clk_spec = { + .num = ARRAY_SIZE(saffirepro_10_clk_src_labels), + .labels = saffirepro_10_clk_src_labels, + .get = &saffirepro_both_clk_src_get, +}; +struct snd_bebob_spec saffirepro_10_spec = { + .clock = &saffirepro_10_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; + +static struct snd_bebob_rate_spec saffire_both_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static struct snd_bebob_clock_spec saffire_both_clk_spec = { + .num = ARRAY_SIZE(saffire_both_clk_src_labels), + .labels = saffire_both_clk_src_labels, + .get = &saffire_both_clk_src_get, +}; +/* Saffire LE */ +struct snd_bebob_meter_spec saffire_le_meter_spec = { + .num = ARRAY_SIZE(saffire_le_meter_labels), + .labels = saffire_le_meter_labels, + .get = &saffire_meter_get, +}; +struct snd_bebob_spec saffire_le_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_le_meter_spec +}; +/* Saffire */ +struct snd_bebob_meter_spec saffire_meter_spec = { + .num = ARRAY_SIZE(saffire_meter_labels), + .labels = saffire_meter_labels, + .get = &saffire_meter_get, +}; +struct snd_bebob_spec saffire_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_meter_spec +};
This commit allows this driver to support some models which M-Audio produces with DM1000/DM1000E with usual firmware. They are: - Firewire 410 - Firewire AudioPhile - Firewire Solo - Ozonic - NRV10 - FirewireLightBridge
According to a person who worked in BridgeCo, some models are produced with 'Pre-BeBoB'. This means that these products were released before BeBoB was officially produced, and later BeBoB specification was formed. So these models have some quirks.
M-Audio usual firmware quirks: - Just after powering on, 'Firewire 410' waits to download firmware. This state is changed when receiving cue. Then bus reset is generated and the device is recognized as a different model with the uploaded firmware. - 'Firewire Audiophile' also waits to download firmware but its vendor id/model id is the same as the one after loading firmware. - The information of channel mapping for MIDI conformant data channel is invalid against BridgeCo specification.
This commit adds some codes for these quirks but don't support to upload firmware.
This commit also adds specific operations to get metering information. The metering information also includes status of clock synchronization if the model supports to switch source of clock.
The specification of FirewireLightBridge is unknown. So in this time, normal operations are applied for this model.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 2 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 45 ++++++++- sound/firewire/bebob/bebob.h | 5 + sound/firewire/bebob/bebob_maudio.c | 180 ++++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_stream.c | 9 ++ 6 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 sound/firewire/bebob/bebob_maudio.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 605e44b..3b8d1f2 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -113,6 +113,8 @@ config SND_BEBOB * Terratec Aureon 7.1 Firewire * Yamaha GO44/GO46 * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO + * M-Audio Firewire410/AudioPhile/Solo + * M-Audio Ozonic/NRV10/ProfireLightBridge
To compile this driver as a module, choose M here: the module will be called snd-bebob. diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 6565420..6cf470c 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,4 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \ - bebob_focusrite.o bebob.o + bebob_focusrite.o bebob_maudio.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 6d23e44..9bf9149 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -55,8 +55,11 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS); #define VEN_TERRATEC 0x00000aac #define VEN_YAMAHA 0x0000a0de #define VEN_FOCUSRITE 0x0000130e +#define VEN_MAUDIO1 0x00000d6c +#define VEN_MAUDIO2 0x000007f5
#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000 +#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060
static int name_device(struct snd_bebob *bebob, unsigned int vendor_id) @@ -139,6 +142,17 @@ get_saffire_spec(struct fw_unit *unit) return &saffire_spec; }
+static bool +check_audiophile_booted(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return false; + + return strncmp(name, "FW Audiophile Bootloader", 15) != 0; +} + static int bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) @@ -161,10 +175,16 @@ bebob_probe(struct fw_unit *unit, }
if ((entry->vendor_id == VEN_FOCUSRITE) && - (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)) + (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)) { spec = get_saffire_spec(unit); - else + } else if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) && + !check_audiophile_booted(unit)) { + err = 0; + goto end; + } else { spec = (const struct snd_bebob_spec *)entry->driver_data; + } if (spec == NULL) { err = -ENOSYS; goto end; @@ -235,6 +255,10 @@ static void bebob_update(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + fcp_bus_reset(bebob->unit); snd_bebob_stream_update_duplex(bebob); } @@ -242,6 +266,10 @@ bebob_update(struct fw_unit *unit) static void bebob_remove(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + snd_bebob_stream_destroy_duplex(bebob); snd_card_disconnect(bebob->card); snd_card_free_when_closed(bebob->card); @@ -334,6 +362,19 @@ static const struct ieee1394_device_id bebob_id_table[] = { /* Focusrite, Saffire(no label and LE) */ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, &saffire_spec), + /* M-Audio, Firewire 410 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec), + /* M-Audio, Firewire Audiophile */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH, + &maudio_audiophile_spec), + /* M-Audio, Firewire Solo */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec), + /* M-Audio, Ozonic */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec), + /* M-Audio NRV10 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec), + /* M-Audio, ProFireLightbridge */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal), /* IDs are unknown but able to be supported */ /* Apogee, Mini-ME Firewire */ /* Apogee, Mini-DAC Firewire */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 220b115..a851639 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -232,6 +232,11 @@ extern struct snd_bebob_spec saffirepro_26_spec; extern struct snd_bebob_spec saffirepro_10_spec; extern struct snd_bebob_spec saffire_le_spec; extern struct snd_bebob_spec saffire_spec; +extern struct snd_bebob_spec maudio_fw410_spec; +extern struct snd_bebob_spec maudio_audiophile_spec; +extern struct snd_bebob_spec maudio_solo_spec; +extern struct snd_bebob_spec maudio_ozonic_spec; +extern struct snd_bebob_spec maudio_nrv10_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c new file mode 100644 index 0000000..dededb3 --- /dev/null +++ b/sound/firewire/bebob/bebob_maudio.c @@ -0,0 +1,180 @@ +/* + * bebob_maudio.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +/* + * Just powering on, Firewire 410/Audiophile wait to + * download firmware blob. To enable these devices, drivers should upload + * firmware blob and send a command to initialize configuration to factory + * settings when completing uploading. Then these devices generate bus reset + * and are recognized as new devices with the firmware. + * + * For streaming, both of output and input streams are needed for Firewire 410 + * and Ozonic. The single stream is OK for the other devices even if the clock + * source is not SYT-Match (I note no devices use SYT-Match). + * + * Without streaming, the devices except for Firewire Audiophile can mix any + * input and output. For this reason, Audiophile cannot be used as standalone + * mixer. + */ + +#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000 + +#define METER_OFFSET 0x00600000 + +/* some device has sync info after metering data */ +#define METER_SIZE_FW410 76 /* with sync info */ +#define METER_SIZE_AUDIOPHILE 60 /* with sync info */ +#define METER_SIZE_SOLO 52 /* with sync info */ +#define METER_SIZE_OZONIC 48 +#define METER_SIZE_NRV10 80 + +/* labels for metering */ +#define ANA_IN "Analog In" +#define ANA_OUT "Analog Out" +#define DIG_IN "Digital In" +#define SPDIF_IN "S/PDIF In" +#define ADAT_IN "ADAT In" +#define DIG_OUT "Digital Out" +#define SPDIF_OUT "S/PDIF Out" +#define ADAT_OUT "ADAT Out" +#define STRM_IN "Stream In" +#define AUX_OUT "Aux Out" +#define HP_OUT "HP Out" +/* for NRV */ +#define UNKNOWN_METER "Unknown" + +static inline int +get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) +{ + return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET, + buf, size, 0); +} + +/* last 4 bytes are omitted because it's clock info. */ +static char *const fw410_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT +}; +static char *const audiophile_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT, AUX_OUT, +}; +static char *const solo_meter_labels[] = { + ANA_IN, DIG_IN, + STRM_IN, STRM_IN, + ANA_OUT, DIG_OUT +}; + +/* no clock info */ +static char *const ozonic_meter_labels[] = { + ANA_IN, ANA_IN, + STRM_IN, STRM_IN, + ANA_OUT, ANA_OUT +}; +/* TODO: need testers. these positions are based on authour's assumption */ +static char *const nrv10_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + DIG_IN +}; +static int +normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int c, channels; + int err; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + err = get_meter(bebob, (void *)buf, size); + if (err < 0) + goto end; + + for (c = 0; c < channels; c++) + be32_to_cpus(&buf[c]); + + /* swap stream channels because inverted */ + if (spec->labels == solo_meter_labels) { + swap(buf[4], buf[6]); + swap(buf[5], buf[7]); + } +end: + return err; +} + +/* Firewire 410 specification */ +static struct snd_bebob_rate_spec usual_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static struct snd_bebob_meter_spec fw410_meter_spec = { + .num = ARRAY_SIZE(fw410_meter_labels), + .labels = fw410_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_fw410_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &fw410_meter_spec +}; + +/* Firewire Audiophile specification */ +static struct snd_bebob_meter_spec audiophile_meter_spec = { + .num = ARRAY_SIZE(audiophile_meter_labels), + .labels = audiophile_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_audiophile_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &audiophile_meter_spec +}; + +/* Firewire Solo specification */ +static struct snd_bebob_meter_spec solo_meter_spec = { + .num = ARRAY_SIZE(solo_meter_labels), + .labels = solo_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_solo_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &solo_meter_spec +}; + +/* Ozonic specification */ +static struct snd_bebob_meter_spec ozonic_meter_spec = { + .num = ARRAY_SIZE(ozonic_meter_labels), + .labels = ozonic_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_ozonic_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &ozonic_meter_spec +}; + +/* NRV10 specification */ +static struct snd_bebob_meter_spec nrv10_meter_spec = { + .num = ARRAY_SIZE(nrv10_meter_labels), + .labels = nrv10_meter_labels, + .get = &normal_meter_get +}; +struct snd_bebob_spec maudio_nrv10_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &nrv10_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 4a21dcf..5fc5270 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -252,6 +252,15 @@ map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) /* location of this channel in this section */ sec_loc = buf[pos++] - 1;
+ /* + * Basically the number of location is within the + * number of channels in this section. But some models + * of M-Audio don't follow this. Its location for MIDI + * is the position of MIDI channels in AMDTP packet. + */ + if (sec_loc >= channels) + sec_loc = ch; + switch (type) { /* for MIDI conformant data channel */ case 0x0a:
This commit allows this driver to support some models which M-Audio produces with DM1000 but its firmware is special. They are: - Firewire 1814 - ProjectMix I/O
They have heavily customized firmware. The usual operations can't be applied to them. For this reason, this commit adds a model specific member to 'struct snd_bebob' and some model specific functions. Some parameters are write-only so this commit also adds control interface for applications to set them.
M-Audio special firmware quirks: - Just after powering on, they wait to download firmware. This state is changed when receiving cue. Then bus reset is generated and the device is recognized as a different model with the uploaded firmware. - They don't respond against BridgeCo AV/C extension commands. So drivers can't get their stream formations and so on. - They do not start to transmit packets only by establishing connection but also by receiving SIGNAL FORMAT command. - After booting up, they often fail to send response against driver's request due to mismatch of gap_count.
This module don't support to upload firmware.
Tested-by: Darren Anderson darrena092@gmail.com (ProjectMix I/O) Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/bebob.c | 19 +- sound/firewire/bebob/bebob.h | 5 + sound/firewire/bebob/bebob_maudio.c | 550 +++++++++++++++++++++++++++++++++++- sound/firewire/bebob/bebob_stream.c | 45 ++- 5 files changed, 609 insertions(+), 11 deletions(-)
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 3b8d1f2..9f363fa 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -115,6 +115,7 @@ config SND_BEBOB * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO * M-Audio Firewire410/AudioPhile/Solo * M-Audio Ozonic/NRV10/ProfireLightBridge + * M-Audio Firewire 1814/ProjectMix IO
To compile this driver as a module, choose M here: the module will be called snd-bebob. diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 9bf9149..ffb042b 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -60,6 +60,8 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000 #define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 +#define MODEL_MAUDIO_FW1814 0x00010071 +#define MODEL_MAUDIO_PROJECTMIX 0x00010091
static int name_device(struct snd_bebob *bebob, unsigned int vendor_id) @@ -210,7 +212,14 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
- err = snd_bebob_stream_discover(bebob); + if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_FW1814)) + err = snd_bebob_maudio_special_discover(bebob, true); + else if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_PROJECTMIX)) + err = snd_bebob_maudio_special_discover(bebob, false); + else + err = snd_bebob_stream_discover(bebob); if (err < 0) goto error;
@@ -270,6 +279,8 @@ static void bebob_remove(struct fw_unit *unit) if (bebob == NULL) return;
+ kfree(bebob->maudio_special_quirk); + snd_bebob_stream_destroy_duplex(bebob); snd_card_disconnect(bebob->card); snd_card_free_when_closed(bebob->card); @@ -375,6 +386,12 @@ static const struct ieee1394_device_id bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec), /* M-Audio, ProFireLightbridge */ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal), + /* Firewire 1814 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814, + &maudio_special_spec), + /* M-Audio ProjectMix */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX, + &maudio_special_spec), /* IDs are unknown but able to be supported */ /* Apogee, Mini-ME Firewire */ /* Apogee, Mini-DAC Firewire */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index a851639..eba0129 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -106,6 +106,9 @@ struct snd_bebob { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait; + + /* for M-Audio special devices */ + void *maudio_special_quirk; };
static inline int @@ -237,6 +240,8 @@ extern struct snd_bebob_spec maudio_audiophile_spec; extern struct snd_bebob_spec maudio_solo_spec; extern struct snd_bebob_spec maudio_ozonic_spec; extern struct snd_bebob_spec maudio_nrv10_spec; +extern struct snd_bebob_spec maudio_special_spec; +int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c index dededb3..544094b 100644 --- a/sound/firewire/bebob/bebob_maudio.c +++ b/sound/firewire/bebob/bebob_maudio.c @@ -7,9 +7,10 @@ */
#include "./bebob.h" +#include <sound/control.h>
/* - * Just powering on, Firewire 410/Audiophile wait to + * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to * download firmware blob. To enable these devices, drivers should upload * firmware blob and send a command to initialize configuration to factory * settings when completing uploading. Then these devices generate bus reset @@ -22,6 +23,12 @@ * Without streaming, the devices except for Firewire Audiophile can mix any * input and output. For this reason, Audiophile cannot be used as standalone * mixer. + * + * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed + * when receiving any commands which the firmware can't understand. These + * devices utilize completely different system to control. It is some + * write-transaction directly into a certain address. All of addresses for mixer + * functionality is between 0xffc700700000 to 0xffc70070009c. */
#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000 @@ -29,6 +36,7 @@ #define METER_OFFSET 0x00600000
/* some device has sync info after metering data */ +#define METER_SIZE_SPECIAL 84 /* with sync info */ #define METER_SIZE_FW410 76 /* with sync info */ #define METER_SIZE_AUDIOPHILE 60 /* with sync info */ #define METER_SIZE_SOLO 52 /* with sync info */ @@ -50,6 +58,15 @@ /* for NRV */ #define UNKNOWN_METER "Unknown"
+struct special_params { + bool is1814; + unsigned int clk_src; + unsigned int dig_in_fmt; + unsigned int dig_out_fmt; + unsigned int clk_lock; + struct snd_ctl_elem_id *ctl_id_sync; +}; + static inline int get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) { @@ -58,6 +75,516 @@ get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) buf, size, 0); }
+static int +check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync) +{ + int err; + u8 *buf; + + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, buf, size); + if (err < 0) + goto end; + + /* if synced, this value is the same as SFC of FDF in CIP header */ + *sync = (buf[size - 2] != 0xff); +end: + kfree(buf); + return err; +} + +/* + * dig_fmt: 0x00:S/PDIF, 0x01:ADAT + * clk_lock: 0x00:unlock, 0x01:lock + */ +static int +avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src, + unsigned int dig_in_fmt, unsigned int dig_out_fmt, + unsigned int clk_lock) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + u8 *buf; + + if (amdtp_stream_running(&bebob->rx_stream) || + amdtp_stream_running(&bebob->tx_stream)) + return -EBUSY; + + buf = kmalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x00; /* vendor dependent */ + buf[3] = 0x04; /* company ID high */ + buf[4] = 0x00; /* company ID middle */ + buf[5] = 0x04; /* company ID low */ + buf[6] = 0xff & clk_src; /* clock source */ + buf[7] = 0xff & dig_in_fmt; /* input digital format */ + buf[8] = 0xff & dig_out_fmt; /* output digital format */ + buf[9] = 0xff & clk_lock; /* lock these settings */ + buf[10] = 0x00; /* padding */ + buf[11] = 0x00; /* padding */ + + err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(8) | + BIT(9)); + if ((err > 0) && (err < 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + params->clk_src = buf[6]; + params->dig_in_fmt = buf[7]; + params->dig_out_fmt = buf[8]; + params->clk_lock = buf[9]; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); + + err = 0; +end: + kfree(buf); + return err; +} +static void +special_stream_formation_set(struct snd_bebob *bebob) +{ + static const unsigned int ch_table[2][2][3] = { + /* AMDTP_OUT_STREAM */ + { { 6, 6, 4 }, /* SPDIF */ + { 12, 8, 4 } }, /* ADAT */ + /* AMDTP_IN_STREAM */ + { { 10, 10, 2 }, /* SPDIF */ + { 16, 12, 2 } } /* ADAT */ + }; + struct special_params *params = bebob->maudio_special_quirk; + unsigned int i, max; + + max = SND_BEBOB_STRM_FMT_ENTRIES - 1; + if (!params->is1814) + max -= 2; + + for (i = 0; i < max; i++) { + bebob->tx_stream_formations[i + 1].pcm = + ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2]; + bebob->tx_stream_formations[i + 1].midi = 1; + + bebob->rx_stream_formations[i + 1].pcm = + ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2]; + bebob->rx_stream_formations[i + 1].midi = 1; + } +} + +static int add_special_controls(struct snd_bebob *bebob); +int +snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814) +{ + struct special_params *params; + int err; + + params = kzalloc(sizeof(struct special_params), GFP_KERNEL); + if (params == NULL) + return -ENOMEM; + + mutex_lock(&bebob->mutex); + + bebob->maudio_special_quirk = (void *)params; + params->is1814 = is1814; + + /* initialize these parameters because driver is not allowed to ask */ + bebob->rx_stream.context = ERR_PTR(-1); + bebob->tx_stream.context = ERR_PTR(-1); + err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to initialize clock params: %d\n", err); + goto end; + } + + err = add_special_controls(bebob); + if (err < 0) + goto end; + + special_stream_formation_set(bebob); + + if (params->is1814) { + bebob->midi_input_ports = 1; + bebob->midi_output_ports = 1; + } else { + bebob->midi_input_ports = 2; + bebob->midi_output_ports = 2; + } +end: + if (err < 0) { + kfree(params); + bebob->maudio_special_quirk = NULL; + } + mutex_unlock(&bebob->mutex); + return err; +} + +/* Input plug shows actual rate. Output plug is needless for this purpose. */ +static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate) +{ + int err, trials; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + + return err; +} +static int special_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* + * Just after changing sampling rate for output, a followed command + * for input is easy to fail. This is a workaround fot this issue. + */ + msleep(100); + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); +end: + return err; +} + +/* Clock source control for special firmware */ +static char *const special_clk_labels[] = { + SND_BEBOB_CLOCK_INTERNAL " with Digital Mute", "Digital", + "Word Clock", SND_BEBOB_CLOCK_INTERNAL}; +static int special_clk_get(struct snd_bebob *bebob, unsigned int *id) +{ + struct special_params *params = bebob->maudio_special_quirk; + *id = params->clk_src; + return 0; +} +static int special_clk_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(special_clk_labels); + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + special_clk_labels[einf->value.enumerated.item]); + + return 0; +} +static int special_clk_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + uval->value.enumerated.item[0] = params->clk_src; + return 0; +} +static int special_clk_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + int err, id; + + mutex_lock(&bebob->mutex); + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_clk_labels)) + return 0; + + err = avc_maudio_set_special_clk(bebob, id, + params->dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + mutex_unlock(&bebob->mutex); + + return err >= 0; +} +static struct snd_kcontrol_new special_clk_ctl = { + .name = "Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_clk_ctl_info, + .get = special_clk_ctl_get, + .put = special_clk_ctl_put +}; + +/* Clock synchronization control for special firmware */ +static int special_sync_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + einf->count = 1; + einf->value.integer.min = 0; + einf->value.integer.max = 1; + + return 0; +} +static int special_sync_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + int err; + bool synced = 0; + + err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced); + if (err >= 0) + uval->value.integer.value[0] = synced; + + return 0; +} +static struct snd_kcontrol_new special_sync_ctl = { + .name = "Sync Status", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = special_sync_ctl_info, + .get = special_sync_ctl_get, +}; + +/* Digital interface control for special firmware */ +static char *const special_dig_iface_labels[] = { + "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical" +}; +static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(special_dig_iface_labels); + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + special_dig_iface_labels[einf->value.enumerated.item]); + + return 0; +} +static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int dig_in_iface; + int err, val; + + mutex_lock(&bebob->mutex); + + err = avc_audio_get_selector(bebob->unit, 0x00, 0x04, + &dig_in_iface); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get digital input interface: %d\n", err); + goto end; + } + + /* encoded id for user value */ + val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01); + + /* for ADAT Optical */ + if (val > 2) + val = 2; + + uval->value.enumerated.item[0] = val; +end: + mutex_unlock(&bebob->mutex); + return err; +} +static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id, dig_in_fmt, dig_in_iface; + int err; + + mutex_lock(&bebob->mutex); + + id = uval->value.enumerated.item[0]; + + /* decode user value */ + dig_in_fmt = (id >> 1) & 0x01; + dig_in_iface = id & 0x01; + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + if ((err < 0) || (params->dig_in_fmt > 0)) /* ADAT */ + goto end; + + err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface); + if (err < 0) + dev_err(&bebob->unit->device, + "fail to set digital input interface: %d\n", err); +end: + special_stream_formation_set(bebob); + mutex_unlock(&bebob->mutex); + return err; +} +static struct snd_kcontrol_new special_dig_in_iface_ctl = { + .name = "Digital Input Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_in_iface_ctl_info, + .get = special_dig_in_iface_ctl_get, + .put = special_dig_in_iface_ctl_set +}; + +static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(special_dig_iface_labels) - 1; + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + special_dig_iface_labels[einf->value.enumerated.item + 1]); + + return 0; +} +static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + mutex_lock(&bebob->mutex); + uval->value.enumerated.item[0] = params->dig_out_fmt; + mutex_unlock(&bebob->mutex); + return 0; +} +static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id; + int err; + + mutex_lock(&bebob->mutex); + + id = uval->value.enumerated.item[0]; + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + params->dig_in_fmt, + id, params->clk_lock); + if (err >= 0) + special_stream_formation_set(bebob); + + mutex_unlock(&bebob->mutex); + return err; +} +static struct snd_kcontrol_new special_dig_out_iface_ctl = { + .name = "Digital Output Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_out_iface_ctl_info, + .get = special_dig_out_iface_ctl_get, + .put = special_dig_out_iface_ctl_set +}; + +static int add_special_controls(struct snd_bebob *bebob) +{ + struct snd_kcontrol *kctl; + struct special_params *params = bebob->maudio_special_quirk; + int err; + + kctl = snd_ctl_new1(&special_clk_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_sync_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + params->ctl_id_sync = &kctl->id; + + kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); +end: + return err; +} + +/* Hardware metering for special firmware */ +static char *const special_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + SPDIF_IN, + ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN, + ANA_OUT, ANA_OUT, + SPDIF_OUT, + ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT, + HP_OUT, HP_OUT, + AUX_OUT +}; +static int +special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size) +{ + u16 *buf; + unsigned int i, c, channels; + int err; + + channels = ARRAY_SIZE(special_meter_labels) * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + /* omit last 4 bytes because it's clock info. */ + buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4); + if (err < 0) + goto end; + + /* Its format is u16 and some channels are unknown. */ + i = 0; + for (c = 2; c < channels + 2; c++) + target[i++] = be16_to_cpu(buf[c]) << 16; +end: + kfree(buf); + return err; +} + /* last 4 bytes are omitted because it's clock info. */ static char *const fw410_meter_labels[] = { ANA_IN, DIG_IN, @@ -115,6 +642,27 @@ end: return err; }
+/* for special customized devices */ +static struct snd_bebob_rate_spec special_rate_spec = { + .get = &special_get_rate, + .set = &special_set_rate, +}; +static struct snd_bebob_clock_spec special_clk_spec = { + .num = ARRAY_SIZE(special_clk_labels), + .labels = special_clk_labels, + .get = &special_clk_get, +}; +static struct snd_bebob_meter_spec special_meter_spec = { + .num = ARRAY_SIZE(special_meter_labels), + .labels = special_meter_labels, + .get = &special_meter_get +}; +struct snd_bebob_spec maudio_special_spec = { + .clock = &special_clk_spec, + .rate = &special_rate_spec, + .meter = &special_meter_spec +}; + /* Firewire 410 specification */ static struct snd_bebob_rate_spec usual_rate_spec = { .get = &snd_bebob_stream_get_rate, diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 5fc5270..2695b78 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -389,6 +389,10 @@ break_both_connections(struct snd_bebob *bebob) cmp_connection_break(&bebob->out_conn);
bebob->connected = false; + + /* These models seems to be in transition state for a longer time. */ + if (bebob->maudio_special_quirk != NULL) + msleep(200); }
static void @@ -421,9 +425,11 @@ start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream, conn = &bebob->out_conn;
/* channel mapping */ - err = map_data_channels(bebob, stream); - if (err < 0) - goto end; + if (bebob->maudio_special_quirk == NULL) { + err = map_data_channels(bebob, stream); + if (err < 0) + goto end; + }
/* start amdtp stream */ err = amdtp_stream_start(stream, @@ -555,13 +561,17 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) * NOTE: * If establishing connections at first, Yamaha GO46 * (and maybe Terratec X24) don't generate sound. + * + * For firmware customized by M-Audio, refer to next NOTE. */ - err = rate_spec->set(bebob, rate); - if (err < 0) { - dev_err(&bebob->unit->device, - "fail to set sampling rate: %d\n", - err); - goto end; + if (bebob->maudio_special_quirk == NULL) { + err = rate_spec->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to set sampling rate: %d\n", + err); + goto end; + } }
err = make_both_connections(bebob, rate); @@ -576,6 +586,23 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) goto end; }
+ /* + * NOTE: + * The firmware customized by M-Audio uses these commands to + * start transmitting stream. This is not usual way. + */ + if (bebob->maudio_special_quirk != NULL) { + err = rate_spec->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to ensure sampling rate: %d\n", + err); + amdtp_stream_stop(master); + break_both_connections(bebob); + goto end; + } + } + /* wait first callback */ if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT)) { amdtp_stream_stop(master);
M-Audio Firewire 1814 has a quirk, ProjectMix I/O also has. They transmit empty packet with wrong value of dbc incremented by 8 at high sampling rate. According to IEC 61883-1, this value should be the same as the one in previous packet.
This commit adds a flag named as CIP_EMPTY_HAS_WRONG_DBC. With flag, the value of dbc in empty packet is overwittern by an expected value.
This is an example of this quirk: CIP Header 0 CIP Header 1 Payload size 010D0000 9004F759 210 010D0010 90040B59 210 010D0020 90042359 210 01020028 9004FFFF 2 <- 010D0030 90043759 210 010D0040 90044B59 210 010D0050 90046359 210 01020058 9004FFFF 2 <- 010D0060 90047759 210 010D0070 90048B59 210 010D0080 9004A359 210 01020088 9004FFFF 2 <- 010D0090 9004B759 210 010D00A0 9004CB59 210 010D00B0 9004E359 210 010200B8 9004FFFF 2 <- 010D00C0 9004F759 210 010D00D0 90040B59 210 010D00E0 90042359 210
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp.c | 4 ++++ sound/firewire/amdtp.h | 3 +++ sound/firewire/bebob/bebob_stream.c | 7 +++++++ 3 files changed, 14 insertions(+)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 690c608..e573f25 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -665,6 +665,10 @@ static void handle_in_packet(struct amdtp_stream *s,
/* Check data block counter continuity */ data_block_counter = cip_header[0] & AMDTP_DBC_MASK; + if (data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) && + s->data_block_counter != UINT_MAX) + data_block_counter = s->data_block_counter; + if (((s->flags & CIP_SKIP_DBC_ZERO_CHECK) && data_block_counter == 0) || (s->data_block_counter == UINT_MAX)) { lost = false; diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index c79f058..d8ee7b0 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -27,6 +27,8 @@ * skipped for detecting discontinuity. * @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first * packet is not continuous from an initial value. + * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty + * packet is wrong but the others are correct. */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -37,6 +39,7 @@ enum cip_flags { CIP_WRONG_DBS = 0x10, CIP_SKIP_DBC_ZERO_CHECK = 0x20, CIP_SKIP_INIT_DBC_CHECK = 0x40, + CIP_EMPTY_HAS_WRONG_DBC = 0x80, };
/** diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 2695b78..3e74d9b 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -457,6 +457,13 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) /* See comments in next function */ init_completion(&bebob->bus_reset); bebob->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK; + /* + * At high sampling rate, M-Audio special firmware transmits empty + * packet with the value of dbc incremented by 8 but the others are + * valid to IEC 61883-1. + */ + if (bebob->maudio_special_quirk) + bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC;
err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, AMDTP_OUT_STREAM, CIP_BLOCKING);
The firmwares for M-Audio Firewire 410/1814 and ProjectMix I/O has a quirk to ignore MIDI messages in data blocks more than 8. This commit uses a flag which Fireworks uses for a similar quirk.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/bebob_stream.c | 7 +++++++ 1 file changed, 7 insertions(+)
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 3e74d9b..452e936 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -472,6 +472,13 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) amdtp_stream_destroy(&bebob->rx_stream); destroy_both_connections(bebob); } + /* + * The firmware for these devices ignore MIDI messages in more than + * first 8 data blocks of an received AMDTP packet. + */ + if (bebob->spec == &maudio_fw410_spec || + bebob->spec == &maudio_special_spec) + bebob->rx_stream.rx_blocks_for_midi = 8; end: return err; }
Just powering on, these devices below wait to download firmware. - Firewire Audiophile - Firewire 410 - Firewire 1814 - ProjectMix I/O
But firmware version 5058 or later, flash memory in the device stores the firmware. So this driver can enable these devices by sending a certain cue to load the firmware.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/bebob.c | 23 +++++++------ sound/firewire/bebob/bebob.h | 1 + sound/firewire/bebob/bebob_maudio.c | 64 +++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-)
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index ffb042b..e1dd421 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -177,18 +177,21 @@ bebob_probe(struct fw_unit *unit, }
if ((entry->vendor_id == VEN_FOCUSRITE) && - (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)) { + (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)) spec = get_saffire_spec(unit); - } else if ((entry->vendor_id == VEN_MAUDIO1) && - (entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) && - !check_audiophile_booted(unit)) { - err = 0; - goto end; - } else { + else if ((entry->vendor_id == VEN_MAUDIO1) && + (entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) && + !check_audiophile_booted(unit)) + spec = NULL; + else spec = (const struct snd_bebob_spec *)entry->driver_data; - } + if (spec == NULL) { - err = -ENOSYS; + if ((entry->vendor_id == VEN_MAUDIO1) || + (entry->vendor_id == VEN_MAUDIO2)) + err = snd_bebob_maudio_load_firmware(unit); + else + err = -ENOSYS; goto end; }
@@ -374,6 +377,7 @@ static const struct ieee1394_device_id bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, &saffire_spec), /* M-Audio, Firewire 410 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec), /* M-Audio, Firewire Audiophile */ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH, @@ -387,6 +391,7 @@ static const struct ieee1394_device_id bebob_id_table[] = { /* M-Audio, ProFireLightbridge */ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal), /* Firewire 1814 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814, &maudio_special_spec), /* M-Audio ProjectMix */ diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index eba0129..4a54e74 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -242,6 +242,7 @@ extern struct snd_bebob_spec maudio_ozonic_spec; extern struct snd_bebob_spec maudio_nrv10_spec; extern struct snd_bebob_spec maudio_special_spec; int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814); +int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ { \ diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c index 544094b..52b1a40 100644 --- a/sound/firewire/bebob/bebob_maudio.c +++ b/sound/firewire/bebob/bebob_maudio.c @@ -16,6 +16,10 @@ * settings when completing uploading. Then these devices generate bus reset * and are recognized as new devices with the firmware. * + * But with firmware version 5058 or later, the firmware is stored to flash + * memory in the device and drivers can tell bootloader to load the firmware + * by sending a cue. This cue must be sent one time. + * * For streaming, both of output and input streams are needed for Firewire 410 * and Ozonic. The single stream is OK for the other devices even if the clock * source is not SYT-Match (I note no devices use SYT-Match). @@ -31,6 +35,20 @@ * functionality is between 0xffc700700000 to 0xffc70070009c. */
+/* Offset from information register */ +#define INFO_OFFSET_SW_DATE 0x20 + +/* Bootloader Protocol Version 1 */ +#define MAUDIO_BOOTLOADER_CUE1 0x00000001 +/* + * Initializing configuration to factory settings (= 0x1101), (swapped in line), + * Command code is zero (= 0x00), + * the number of operands is zero (= 0x00)(at least significant byte) + */ +#define MAUDIO_BOOTLOADER_CUE2 0x01110000 +/* padding */ +#define MAUDIO_BOOTLOADER_CUE3 0x00000000 + #define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000
#define METER_OFFSET 0x00600000 @@ -67,6 +85,52 @@ struct special_params { struct snd_ctl_elem_id *ctl_id_sync; };
+/* + * For some M-Audio devices, this module just send cue to load firmware. After + * loading, the device generates bus reset and newly detected. + * + * If we make any transactions to load firmware, the operation may failed. + */ +int snd_bebob_maudio_load_firmware(struct fw_unit *unit) +{ + struct fw_device *device = fw_parent_device(unit); + int err, rcode; + u64 date; + __be32 cues[3] = { + MAUDIO_BOOTLOADER_CUE1, + MAUDIO_BOOTLOADER_CUE2, + MAUDIO_BOOTLOADER_CUE3 + }; + + /* check date of software used to build */ + err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE, + &date, sizeof(u64)); + if (err < 0) + goto end; + /* + * firmware version 5058 or later has date later than "20070401", but + * 'date' is not null-terminated. + */ + if (date < 0x3230303730343031) { + dev_err(&unit->device, + "Use firmware version 5058 or later\n"); + err = -ENOSYS; + goto end; + } + + rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST, + device->node_id, device->generation, + device->max_speed, BEBOB_ADDR_REG_REQ, + cues, sizeof(cues)); + if (rcode != RCODE_COMPLETE) { + dev_err(&unit->device, + "Failed to send a cue to load firmware\n"); + err = -EIO; + } +end: + return err; +} + static inline int get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) {
In post commit, a quirk of this firmware about transactions is reported. This commit apply a workaround for this quirk.
They often fail transactions due to gap_count mismatch. This state is changed by generating bus reset.
The fw_schedule_bus_reset() is an exported symbol in firewire-core. But there are no header for public. This commit moves its prototype from drivers/firewire/core.h to include/linux/firewire.h.
This mismatch still affects bus management before generating this bus reset. It still takes a time to call driver's probe() because transactions are still often failed.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- drivers/firewire/core.h | 1 - include/linux/firewire.h | 3 +++ sound/firewire/bebob/bebob.c | 32 ++++++++++++++++++++++++++++---- sound/firewire/bebob/bebob.h | 1 + 4 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index c98764a..870044e 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -118,7 +118,6 @@ int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid); void fw_core_remove_card(struct fw_card *card); int fw_compute_block_crc(__be32 *block); -void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset); void fw_schedule_bm_work(struct fw_card *card, unsigned long delay);
/* -cdev */ diff --git a/include/linux/firewire.h b/include/linux/firewire.h index c3683bd..d4b7683 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -367,6 +367,9 @@ static inline int fw_stream_packet_destination_id(int tag, int channel, int sy) return tag << 14 | channel << 8 | sy; }
+void fw_schedule_bus_reset(struct fw_card *card, bool delayed, + bool short_reset); + struct fw_descriptor { struct list_head link; size_t length; diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index e1dd421..31b96b7 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -247,10 +247,26 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
- err = snd_card_register(card); - if (err < 0) { - snd_bebob_stream_destroy_duplex(bebob); - goto error; + if (!bebob->maudio_special_quirk) { + err = snd_card_register(card); + if (err < 0) { + snd_bebob_stream_destroy_duplex(bebob); + goto error; + } + } else { + /* + * This is a workaround. This bus reset seems to have an effect + * to make devices correctly handling transactions. Without + * this, the devices have gap_count mismatch. This causes much + * failure of transaction. + * + * Just after registration, user-land application receive + * signals from dbus and starts I/Os. To avoid I/Os till the + * future bus reset, registration is done in next update(). + */ + bebob->deferred_registration = true; + fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, + false, true); }
dev_set_drvdata(&unit->device, bebob); @@ -273,6 +289,14 @@ bebob_update(struct fw_unit *unit)
fcp_bus_reset(bebob->unit); snd_bebob_stream_update_duplex(bebob); + + if (bebob->deferred_registration) { + if (snd_card_register(bebob->card) < 0) { + snd_bebob_stream_destroy_duplex(bebob); + snd_card_free(bebob->card); + } + bebob->deferred_registration = false; + } }
static void bebob_remove(struct fw_unit *unit) diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 4a54e74..91b26b0 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -109,6 +109,7 @@ struct snd_bebob {
/* for M-Audio special devices */ void *maudio_special_quirk; + bool deferred_registration; };
static inline int
At Fri, 25 Apr 2014 22:45:30 +0900, Takashi Sakamoto wrote:
In post commit, a quirk of this firmware about transactions is reported. This commit apply a workaround for this quirk.
They often fail transactions due to gap_count mismatch. This state is changed by generating bus reset.
The fw_schedule_bus_reset() is an exported symbol in firewire-core. But there are no header for public. This commit moves its prototype from drivers/firewire/core.h to include/linux/firewire.h.
This mismatch still affects bus management before generating this bus reset. It still takes a time to call driver's probe() because transactions are still often failed.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
Hrm, I should have taken a deeper review. This change was slipped to my tree without Stefan's ack. Stefan, is it OK to keep this change as is?
thanks,
Takashi
drivers/firewire/core.h | 1 - include/linux/firewire.h | 3 +++ sound/firewire/bebob/bebob.c | 32 ++++++++++++++++++++++++++++---- sound/firewire/bebob/bebob.h | 1 + 4 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h index c98764a..870044e 100644 --- a/drivers/firewire/core.h +++ b/drivers/firewire/core.h @@ -118,7 +118,6 @@ int fw_card_add(struct fw_card *card, u32 max_receive, u32 link_speed, u64 guid); void fw_core_remove_card(struct fw_card *card); int fw_compute_block_crc(__be32 *block); -void fw_schedule_bus_reset(struct fw_card *card, bool delayed, bool short_reset); void fw_schedule_bm_work(struct fw_card *card, unsigned long delay);
/* -cdev */ diff --git a/include/linux/firewire.h b/include/linux/firewire.h index c3683bd..d4b7683 100644 --- a/include/linux/firewire.h +++ b/include/linux/firewire.h @@ -367,6 +367,9 @@ static inline int fw_stream_packet_destination_id(int tag, int channel, int sy) return tag << 14 | channel << 8 | sy; }
+void fw_schedule_bus_reset(struct fw_card *card, bool delayed,
bool short_reset);
struct fw_descriptor { struct list_head link; size_t length; diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index e1dd421..31b96b7 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -247,10 +247,26 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
- err = snd_card_register(card);
- if (err < 0) {
snd_bebob_stream_destroy_duplex(bebob);
goto error;
if (!bebob->maudio_special_quirk) {
err = snd_card_register(card);
if (err < 0) {
snd_bebob_stream_destroy_duplex(bebob);
goto error;
}
} else {
/*
* This is a workaround. This bus reset seems to have an effect
* to make devices correctly handling transactions. Without
* this, the devices have gap_count mismatch. This causes much
* failure of transaction.
*
* Just after registration, user-land application receive
* signals from dbus and starts I/Os. To avoid I/Os till the
* future bus reset, registration is done in next update().
*/
bebob->deferred_registration = true;
fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card,
false, true);
}
dev_set_drvdata(&unit->device, bebob);
@@ -273,6 +289,14 @@ bebob_update(struct fw_unit *unit)
fcp_bus_reset(bebob->unit); snd_bebob_stream_update_duplex(bebob);
- if (bebob->deferred_registration) {
if (snd_card_register(bebob->card) < 0) {
snd_bebob_stream_destroy_duplex(bebob);
snd_card_free(bebob->card);
}
bebob->deferred_registration = false;
- }
}
static void bebob_remove(struct fw_unit *unit) diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 4a54e74..91b26b0 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -109,6 +109,7 @@ struct snd_bebob {
/* for M-Audio special devices */ void *maudio_special_quirk;
- bool deferred_registration;
};
static inline int
1.8.3.2
On May 30 Takashi Iwai wrote:
At Fri, 25 Apr 2014 22:45:30 +0900, Takashi Sakamoto wrote:
In post commit, a quirk of this firmware about transactions is reported. This commit apply a workaround for this quirk.
They often fail transactions due to gap_count mismatch. This state is changed by generating bus reset.
The fw_schedule_bus_reset() is an exported symbol in firewire-core. But there are no header for public. This commit moves its prototype from drivers/firewire/core.h to include/linux/firewire.h.
This mismatch still affects bus management before generating this bus reset. It still takes a time to call driver's probe() because transactions are still often failed.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
Hrm, I should have taken a deeper review. This change was slipped to my tree without Stefan's ack. Stefan, is it OK to keep this change as is?
Yes, the implementation of this patch and its routing through the ALSA tree is OK with me. I should have sent Acked-by: Stefan Richter stefanr@s5r6.in-berlin.de right away when this revision of the patch series showed up on the list. (I think I only sent an informal agreement a time ago for an earlier revision.)
Longer term we should look into improving this workaround to address 1. the above mentioned transaction failures which happen before the bebob driver's .probe is called (which are merely transient recoverable failures, apparently), 2. the theoretical possibility of the very same problem occurring with other IEEE 1394 devices. If we indeed come up with such a more general solution, its implementation will of course move out of the bebob driver into firewire-core. But this is really longer term since we need to be careful with that, and I might want to recreate an environment which reproduces the issue before I attempt such generalization or before I take a respective patch from somebody else. --- So in short, let's proceed with this patch through ALSA git.
thanks,
Takashi
drivers/firewire/core.h | 1 - include/linux/firewire.h | 3 +++ sound/firewire/bebob/bebob.c | 32 ++++++++++++++++++++++++++++---- sound/firewire/bebob/bebob.h | 1 + 4 files changed, 32 insertions(+), 5 deletions(-)
[...]
At Fri, 30 May 2014 22:22:59 +0200, Stefan Richter wrote:
On May 30 Takashi Iwai wrote:
At Fri, 25 Apr 2014 22:45:30 +0900, Takashi Sakamoto wrote:
In post commit, a quirk of this firmware about transactions is reported. This commit apply a workaround for this quirk.
They often fail transactions due to gap_count mismatch. This state is changed by generating bus reset.
The fw_schedule_bus_reset() is an exported symbol in firewire-core. But there are no header for public. This commit moves its prototype from drivers/firewire/core.h to include/linux/firewire.h.
This mismatch still affects bus management before generating this bus reset. It still takes a time to call driver's probe() because transactions are still often failed.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
Hrm, I should have taken a deeper review. This change was slipped to my tree without Stefan's ack. Stefan, is it OK to keep this change as is?
Yes, the implementation of this patch and its routing through the ALSA tree is OK with me. I should have sent Acked-by: Stefan Richter stefanr@s5r6.in-berlin.de right away when this revision of the patch series showed up on the list. (I think I only sent an informal agreement a time ago for an earlier revision.)
OK, thanks for confirmation!
Longer term we should look into improving this workaround to address
- the above mentioned transaction failures which happen before the bebob driver's .probe is called (which are merely transient recoverable failures, apparently),
- the theoretical possibility of the very same problem occurring with other IEEE 1394 devices.
If we indeed come up with such a more general solution, its implementation will of course move out of the bebob driver into firewire-core. But this is really longer term since we need to be careful with that, and I might want to recreate an environment which reproduces the issue before I attempt such generalization or before I take a respective patch from somebody else. --- So in short, let's proceed with this patch through ALSA git.
Sounds reasonable.
thanks,
Takashi
thanks,
Takashi
drivers/firewire/core.h | 1 - include/linux/firewire.h | 3 +++ sound/firewire/bebob/bebob.c | 32 ++++++++++++++++++++++++++++---- sound/firewire/bebob/bebob.h | 1 + 4 files changed, 32 insertions(+), 5 deletions(-)
[...]
-- Stefan Richter -=====-====- -=-= ====- http://arcgraph.de/sr/
= A Restriction Drivers with this patchset has a restriction comparing to windows drivers.
As long as reading manual of PreSonus/Focusrite/PrismSound, these BeBoB based devices may synchronize to the other BeBoB based devices on the same bus.
I guess this mechanism is driven by SYT in CIP packets: - Driver select a device as 'Sample clock source' then the other devices are 'Sample clock destination'. - Driver handle packets from 'Sample clock source' device. - Driver read SYT and calculate presentation timestamp for each devices, then transfer packets to the other devices.
If you're interested, please refer to IEC 61883-6:2005 (Edition 2.0). Actual examples are described in: Annex D Transport characteristics D.1.3 Embedded sample-clock jitter D.1.3.2.1 Example one: simple two-node bus D.1.3.2.2 Example two: three-node bus D.1.3.2.3 Example: thirty-five-node system
Well, drivers with this patchset don't support this mechanism. The reason is bad balance between the const to implement below requirements and actual advantage to implement these: - Requirement to manage states of several devices simultaneously - Requirement to handle several streams from/to each device simultaneously - Requirement to calculate SYT with the length between host controller and each device
The drivers synchronized each devices independently. The drivers handle packets from a device and read SYT, then transfer packets with the SYT to the device.
= Rest of issues Currently I focus on enabling ALSA to handle devices. So there are some issues which don't cause big problems.
1.Sorting packets I know some Fireworks based devices sometimes transfer packets with disorder. But current implementation is a lack of sorting packets. The packets with disorder is detected as dicontinuity then streams are forced to stop and XRUN is generated. In this case, usual ALSA PCM applications may recover XRUN, so there may be some XRUNS to start straems successfully. I need more time to investigate frequency and pattern of this issue. Near future I want to discuss this issue again with enough data.
2.Data rate for MIDI substream With current implementation, the maximum data rate for MIDI substream in out-stream is one eighth of sampling rate. For example, at 48.0kHz, it's 6,000 bytes. This is excess over standard MIDI data rate, 3,125 bits per second. So a few devices can have buffer over flow when receiving much MIDI messages. For example, M-Audio Firewire 1814 transmits packets with discontinuity in this case. I guess that Fireworks based devices and some BeBoB based devices ignore MIDI messages in more than 8 data blocks of packets and this is vendor's effort for this issue. Drivers need a throttle for this issue but this is for future work.
3.Latency Currently, the minumum number of frames per period is not less than 5msec. Drivers has a flag, SNDRV_PCM_INFO_BATCH and the minimum number of periods in buffer is 2. As a result, its minimum latency is 10msec. For more severe latency, further work is needed. But I feel this is not so higher priority than other issues because actually devices are available.
4.A few devices are freezed when frequent establishing/breaking connections One of testers reported this issue. 'PreSonus FP10' can be freezed when frequent establishing/breaking connections. This brings a disadvantage to PulseAudio. When PulseAudio detects the card, it starts/stops 4 streams at least. If PulseAudio can detect devices with some hacks, it may take the device freezed.
5.Various results at bus reset I confirm various results with Fireworks/BeBoB and its devices at bus reset during streaming, depending on cases. Case 1. Software do bus reset When generating bus reset with ffado-test or jujuutils, streaming stopped. When generating bus reset, the drivers update connections according to IEC 61883-1. But after a few hundred msec, the device break connections by itself. I don't know the reason exactly but I guess the synchronizing mechanism I describe have some relations. Case 2. Connecting devices or removing devices This case depends on which devices are connected or removed. If they're Fireworks/BeBoB based devices, streaming sometimes continues and sometimes stops. If they're not Fireworks/BeBoB based device, streaming continues. I don't know the reason exactly but I guess the synchronizing mechanism I describe have some relations.
Regards
Takashi Sakamoto o-takashi@sakamocchi.jp
(Apr 25 2014 22:44), Takashi Sakamoto wrote:
This 49 patchset is to update previous series: [alsa-devel] [PATCH 00/44 v3] Enhancement of support for Firewire devices http://mailman.alsa-project.org/pipermail/alsa-devel/2014-March/074624.html
4 patches are newly added: [PATCH 09/48] dice/firewire-lib: Keep dualwire mode but obsolete CIP_HI_DUALWIRE
- Make firewire-lib simpler.
[PATCH 10/48] firewire-lib/dice/speakers: Add common PCM constraints for AMDTP streams
- To share codes for drivers. Currently no improvement.
[PATCH 25/48] fireworks/firewire-lib: Add a quirk to reset data block counter at bus reset [PATCH 46/48] bebob: Add a quirk of data blocks for MIDI messages for some M-Audio devices
- New quirks are identified.
Oops! The count of patch in these descriptions is wrong. The toal number of patch is 49. This is due to my forgetting to send [PATCH 01/49] in my previous series version 2 and 3. I didn't realize this...
5 patches are newly added: [PATCH 01/49] firewire-lib: Rename functions, structure, member for AMDTP - I forgot to post this patch in series 2 and 3... [PATCH 10/49] dice/firewire-lib: Keep dualwire mode but obsolete CIP_HI_DUALWIRE - Make firewire-lib simpler. [PATCH 11/49] firewire-lib/dice/speakers: Add common PCM constraints for AMDTP streams - To share codes for drivers. Currently no improvement. [PATCH 26/49] fireworks/firewire-lib: Add a quirk to reset data block counter at bus reset [PATCH 47/49] bebob: Add a quirk of data blocks for MIDI messages for some M-Audio devices - New quirks are identified.
For reviewers, you can see previous series in 'request_v3' branch of my remote repository. I hope this will help your work.
The missing patch is already included in this branch. I've just failed to generate series of patch when I posted version 2 and 3...
I'm sorry to trouble you.
Takashi Sakamoto o-takashi@sakamocchi.jp
Any reactions for this patchset?
(Apr 25 2014 22:44), Takashi Sakamoto wrote:
This 49 patchset is to update previous series: [alsa-devel] [PATCH 00/44 v3] Enhancement of support for Firewire devices http://mailman.alsa-project.org/pipermail/alsa-devel/2014-March/074624.html
4 patches are newly added: [PATCH 09/48] dice/firewire-lib: Keep dualwire mode but obsolete CIP_HI_DUALWIRE
- Make firewire-lib simpler.
[PATCH 10/48] firewire-lib/dice/speakers: Add common PCM constraints for AMDTP streams
- To share codes for drivers. Currently no improvement.
[PATCH 25/48] fireworks/firewire-lib: Add a quirk to reset data block counter at bus reset [PATCH 46/48] bebob: Add a quirk of data blocks for MIDI messages for some M-Audio devices
- New quirks are identified.
For reviewers, you can see previous series in 'request_v3' branch of my remote repository. I hope this will help your work.
The following changes since commit 38137a064199a02630f136ce7bb1580d5d3fedeb:
ALSA: lx_core: Translate comments from french to english (2014-04-18 09:57:03 +0200)
are available in the git repository at:
https://github.com/takaswie/sound.git request_v4
for you to fetch changes up to e851b7740f218d5d5a8e957851b383bde3e28654:
firewire/bebob: Add a workaround for M-Audio special Firewire series (2014-04-25 22:29:32 +0900)
Takashi Sakamoto (49): firewire-lib: Rename functions, structure, member for AMDTP firewire-lib: Add macros instead of fixed value for AMDTP firewire-lib: Add 'direction' member to 'amdtp_stream' structure firewire-lib: Split some codes into functions to reuse for both streams firewire-lib: Add support for AMDTP in-stream and PCM capture firewire-lib: Add support for MIDI capture/playback firewire-lib: Give syt value as parameter to handle_out_packet() firewire-lib: Add support for duplex streams synchronization in blocking mode firewire-lib: Add support for channel mapping dice/firewire-lib: Keep dualwire mode but obsolete CIP_HI_DUALWIRE firewire-lib/dice/speakers: Add common PCM constraints for AMDTP streams firewire-lib: Restrict calling flush_context_completion() when context exists firewire-lib: Rename macros, variables and functions for CMP firewire-lib: Add 'direction' member to 'cmp_connection' structure firewire-lib: Add handling output connection by CMP firewire-lib: Add a new function to check others' connection firewire-lib: Add support for deferred transaction firewire-lib: Add some AV/C general commands fireworks: Add skelton for Fireworks based devices fireworks: Add transaction and some commands fireworks: Add connection and stream management fireworks/firewire-lib: Add a quirk for empty packet with TAG0 fireworks/firewire-lib: Add a quirk for the meaning of dbc fireworks/firewire-lib: Add a quirk for wrong dbs in tx packets fireworks/firewire-lib: Add a quirk for fixed interval of reported dbc fireworks/firewire-lib: Add a quirk to reset data block counter at bus reset fireworks: Add proc interface for debugging purpose fireworks: Add MIDI interface fireworks/firewire-lib: Add a quirk of data blocks for MIDI in out-stream fireworks: Add PCM interface fireworks: Add hwdep interface fireworks: Add command/response functionality into hwdep interface bebob: Add skelton for BeBoB based devices bebob: Add commands and connections/streams management bebob/firewire-lib: Add a quirk for discontinuity at bus reset bebob: Add proc interface for debugging purpose bebob: Add MIDI interface bebob: Add PCM interface bebob: Add hwdep interface bebob: Prepare for device specific operations bebob: Add support for Terratec PHASE, EWS series and Aureon bebob: Add support for Yamaha GO series bebob: Add support for Focusrite Saffire/SaffirePro series bebob: Add support for M-Audio usual Firewire series bebob: Add support for M-Audio special Firewire series bebob/firewire-lib: Add a quirk of wrong dbc in empty packet for M-Audio special Firewire series bebob: Add a quirk of data blocks for MIDI messages for some M-Audio devices bebob: Send a cue to load firmware for M-Audio Firewire series firewire/bebob: Add a workaround for M-Audio special Firewire series
drivers/firewire/core.h | 1 - include/linux/firewire.h | 3 + include/uapi/sound/asound.h | 4 +- include/uapi/sound/firewire.h | 23 +- sound/firewire/Kconfig | 59 ++ sound/firewire/Makefile | 2 + sound/firewire/amdtp.c | 797 +++++++++++------ sound/firewire/amdtp.h | 200 ++++- sound/firewire/bebob/Makefile | 4 + sound/firewire/bebob/bebob.c | 472 ++++++++++ sound/firewire/bebob/bebob.h | 257 ++++++ sound/firewire/bebob/bebob_command.c | 282 ++++++ sound/firewire/bebob/bebob_focusrite.c | 279 ++++++ sound/firewire/bebob/bebob_hwdep.c | 199 +++++ sound/firewire/bebob/bebob_maudio.c | 792 +++++++++++++++++ sound/firewire/bebob/bebob_midi.c | 168 ++++ sound/firewire/bebob/bebob_pcm.c | 378 ++++++++ sound/firewire/bebob/bebob_proc.c | 196 +++++ sound/firewire/bebob/bebob_stream.c | 1020 ++++++++++++++++++++++ sound/firewire/bebob/bebob_terratec.c | 68 ++ sound/firewire/bebob/bebob_yamaha.c | 50 ++ sound/firewire/cmp.c | 203 +++-- sound/firewire/cmp.h | 14 +- sound/firewire/dice.c | 88 +- sound/firewire/fcp.c | 189 +++- sound/firewire/fcp.h | 21 + sound/firewire/fireworks/Makefile | 4 + sound/firewire/fireworks/fireworks.c | 357 ++++++++ sound/firewire/fireworks/fireworks.h | 232 +++++ sound/firewire/fireworks/fireworks_command.c | 372 ++++++++ sound/firewire/fireworks/fireworks_hwdep.c | 298 +++++++ sound/firewire/fireworks/fireworks_midi.c | 168 ++++ sound/firewire/fireworks/fireworks_pcm.c | 403 +++++++++ sound/firewire/fireworks/fireworks_proc.c | 232 +++++ sound/firewire/fireworks/fireworks_stream.c | 372 ++++++++ sound/firewire/fireworks/fireworks_transaction.c | 326 +++++++ sound/firewire/speakers.c | 100 +-- 37 files changed, 8173 insertions(+), 460 deletions(-) create mode 100644 sound/firewire/bebob/Makefile create mode 100644 sound/firewire/bebob/bebob.c create mode 100644 sound/firewire/bebob/bebob.h create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_focusrite.c create mode 100644 sound/firewire/bebob/bebob_hwdep.c create mode 100644 sound/firewire/bebob/bebob_maudio.c create mode 100644 sound/firewire/bebob/bebob_midi.c create mode 100644 sound/firewire/bebob/bebob_pcm.c create mode 100644 sound/firewire/bebob/bebob_proc.c create mode 100644 sound/firewire/bebob/bebob_stream.c create mode 100644 sound/firewire/bebob/bebob_terratec.c create mode 100644 sound/firewire/bebob/bebob_yamaha.c create mode 100644 sound/firewire/fireworks/Makefile create mode 100644 sound/firewire/fireworks/fireworks.c create mode 100644 sound/firewire/fireworks/fireworks.h create mode 100644 sound/firewire/fireworks/fireworks_command.c create mode 100644 sound/firewire/fireworks/fireworks_hwdep.c create mode 100644 sound/firewire/fireworks/fireworks_midi.c create mode 100644 sound/firewire/fireworks/fireworks_pcm.c create mode 100644 sound/firewire/fireworks/fireworks_proc.c create mode 100644 sound/firewire/fireworks/fireworks_stream.c create mode 100644 sound/firewire/fireworks/fireworks_transaction.c
Takashi Sakamoto wrote:
Any reactions for this patchset?
Sorry, I haven't been able to find time, and probably' won't until next week.
In any case, I think all patches deserve testing in the -next tree.
Regards, Clemens
At Mon, 26 May 2014 10:41:31 +0200, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
Any reactions for this patchset?
Sorry, I haven't been able to find time, and probably' won't until next week.
In any case, I think all patches deserve testing in the -next tree.
So, do you recommend to merge this stuff into 3.16? Then I can queue them up to for-next branch. That is, we have ca two weeks to fix things before hitting to the upstream.
Takashi
At Mon, 26 May 2014 10:55:08 +0200, Takashi Iwai wrote:
At Mon, 26 May 2014 10:41:31 +0200, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
Any reactions for this patchset?
Sorry, I haven't been able to find time, and probably' won't until next week.
In any case, I think all patches deserve testing in the -next tree.
So, do you recommend to merge this stuff into 3.16? Then I can queue them up to for-next branch. That is, we have ca two weeks to fix things before hitting to the upstream.
Meanwhile I applied the patches to topic/firewire branch and put to my build test bot. I'll merge the branch to for-next tomorrow if no one gives objections.
thanks,
Takashi
Iwai-san,
(May 26 2014 21:38), Takashi Iwai wrote:
Meanwhile I applied the patches to topic/firewire branch and put to my build test bot. I'll merge the branch to for-next tomorrow if no one gives objections.
Thanks for your applying this patchset to topic/firewire branch. I receive some sparse messages from kbuild test backend. There're several codes which should be fixed, mainly for type conflict.
I want you to apply revised patchset. I've already done most of this work for revised patchset.
Should I post them as new patchset? Or post new patches to previous patchset?
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
Iwai-san,
Attached files are fixups for each patches. I think it better to renew patches because there're somewhat much lines to be modified.
Regards
Takashi Sakamoto o-takashi@sakamocchi.jp
(May 27 2014 07:45), Takashi Sakamoto wrote:
Iwai-san,
(May 26 2014 21:38), Takashi Iwai wrote:
Meanwhile I applied the patches to topic/firewire branch and put to my build test bot. I'll merge the branch to for-next tomorrow if no one gives objections.
Thanks for your applying this patchset to topic/firewire branch. I receive some sparse messages from kbuild test backend. There're several codes which should be fixed, mainly for type conflict.
I want you to apply revised patchset. I've already done most of this work for revised patchset.
Should I post them as new patchset? Or post new patches to previous patchset?
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
At Tue, 27 May 2014 13:13:25 +0900, Takashi Sakamoto wrote:
From 7bfccd9826cf2a84f39a0b22fe3930fbe4bc702d Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 02:47:19 +0900 Subject: fixup for [PATCH 11/49]
A wrong type of variable is given as function argument.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/speakers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 3427527..58e7ab0 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -167,7 +167,7 @@ static int fwspk_open(struct snd_pcm_substream *substream) if (err < 0) return err;
- err = amdtp_stream_add_pcm_hw_constraints(fwspk->stream, runtime);
- err = amdtp_stream_add_pcm_hw_constraints(&fwspk->stream, runtime); if (err < 0) return err;
Already fixed in topic/firewire branch.
From 9d6136c7974b2bdb4530008808ab3fcf00fe74b4 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 11:57:38 +0900 Subject: fixup for [PATCH 16/49]
A variable is assigned to a different type of variable.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/cmp.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index d31a403..32c0209 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -163,8 +163,13 @@ int cmp_connection_check_used(struct cmp_connection *c, bool *used) err = snd_fw_transaction( c->resources.unit, TCODE_READ_QUADLET_REQUEST, pcr_address(c), &pcr, 4, 0);
- if (err >= 0)
*used = (pcr & cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK));
- if (err >= 0) {
if ((pcr & cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK)) > 0)
*used = true;
else
*used = false;
An idiom is to use !! in such a case.
*used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK));
From 19ae60f0f4e9cbf1e18b4bb43134e68d6e22f1d4 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:44:01 +0900 Subject: fixup for [PATCH 18/49]
Apply the same prototype in header and fix limitation of loop counter.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/amdtp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index 5b88461..21cc2a4 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -105,7 +105,7 @@ const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { }; EXPORT_SYMBOL(amdtp_syt_intervals);
-const unsigned int amdtp_rate_table[] = { +const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = { [CIP_SFC_32000] = 32000, [CIP_SFC_44100] = 44100, [CIP_SFC_48000] = 48000, @@ -196,7 +196,7 @@ void amdtp_stream_set_parameters(struct amdtp_stream *s, WARN_ON(midi_channels > AMDTP_MAX_CHANNELS_FOR_MIDI)) return;
- for (sfc = 0; sfc < sizeof(amdtp_rate_table); ++sfc)
- for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) if (amdtp_rate_table[sfc] == rate) goto sfc_found; WARN_ON(1);
-- 1.8.3.2
From 3adc93a785e8b30f310c2710284f664891e67d5b Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:49:47 +0900 Subject: fixup for [PATCH 20/49]
Fix different prototype than function and wrong narrowing conversion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/fireworks/fireworks.h | 3 ++- sound/firewire/fireworks/fireworks_transaction.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index e999802..051cf04 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -145,7 +145,8 @@ enum snd_efw_transport_mode { }; int snd_efw_command_set_resp_addr(struct snd_efw *efw, u16 addr_high, u32 addr_low); -int snd_efw_command_set_tx_mode(struct snd_efw *efw, unsigned int mode); +int snd_efw_command_set_tx_mode(struct snd_efw *efw,
enum snd_efw_transport_mode mode);
int snd_efw_command_get_hwinfo(struct snd_efw *efw, struct snd_efw_hwinfo *hwinfo); int snd_efw_command_get_phys_meters(struct snd_efw *efw, diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c index aac91d8..13f99b7 100644 --- a/sound/firewire/fireworks/fireworks_transaction.c +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -31,8 +31,8 @@ */ #include "./fireworks.h"
-#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000 -#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000 +#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000LL +#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000LL
These two are different kind of fixes, and the addition of LL suffix is seen in many places below. So, I'd suggest to create a single patch to just fix LL suffix in all places at once.
BTW, the use of unsigned int instead of enum isn't "wrong" in C. enum is better as you can check the items more explicitly.
#define ERROR_RETRIES 3
#define ERROR_DELAY_MS 5
1.8.3.2
From 934d7ca623f242301de03990b7394a6d59e707c5 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:47:22 +0900 Subject: fixup for [PATCH 21/49]
Fix wrong type of variable. The type of struct snd_pcm_runtime.rate is 'unsigned int'.
Again, this isn't wrong, either. It's, though, better to use unsigned int in case a value greater than 0x80000000 is used. (But, IIRC, this won't happen from API perspective.)
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/fireworks/fireworks.h | 2 +- sound/firewire/fireworks/fireworks_stream.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 7ebfb78..7fd79d6 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -179,7 +179,7 @@ int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate); int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
int snd_efw_stream_init_duplex(struct snd_efw *efw); -int snd_efw_stream_start_duplex(struct snd_efw *efw, int sampling_rate); +int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate); void snd_efw_stream_stop_duplex(struct snd_efw *efw); void snd_efw_stream_update_duplex(struct snd_efw *efw); void snd_efw_stream_destroy_duplex(struct snd_efw *efw); diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index ec62aa6..915c60e 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -211,7 +211,7 @@ end: return err; }
-int snd_efw_stream_start_duplex(struct snd_efw *efw, int rate) +int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate) { struct amdtp_stream *master, *slave; atomic_t *slave_substreams; -- 1.8.3.2
From d6241ae2269f11196581baa1f8ae2473b1667b94 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 02:10:41 +0900 Subject: fixup for [PATCH 28/49]
Improve indentation.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/fireworks/fireworks_midi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c index cbf34e9..1727de8 100644 --- a/sound/firewire/fireworks/fireworks_midi.c +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -122,7 +122,7 @@ int snd_efw_create_midi_devices(struct snd_efw *efw) rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&midi_capture_ops);
&midi_capture_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
@@ -133,7 +133,7 @@ int snd_efw_create_midi_devices(struct snd_efw *efw) rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&midi_playback_ops);
&midi_playback_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
-- 1.8.3.2
From 561308d267184f80dc47a9ede9699c426b3bd6ca Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:50:48 +0900 Subject: fixup for [PATCH 30/49]
Fix wrong type of variable for function argument.
Again, if it's wrong, describe more what's really wrong.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/fireworks/fireworks_pcm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c index 15ec4e0..8cc5c8b7 100644 --- a/sound/firewire/fireworks/fireworks_pcm.c +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -195,8 +195,8 @@ end: static int pcm_open(struct snd_pcm_substream *substream) { struct snd_efw *efw = substream->private_data;
- int sampling_rate;
- unsigned int clock_source;
unsigned int sampling_rate;
enum snd_efw_clock_source clock_source; int err;
err = pcm_init_hw_params(efw, substream);
-- 1.8.3.2
From 1a1e970e3bd18ebc69b109f315c001b4bf14d3e5 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 11:29:03 +0900 Subject: fixup for [PATCH 32/49]
Fix a wrong variable to PTR_ERR().
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/fireworks/fireworks_hwdep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c index 6b50a67..4f8216f 100644 --- a/sound/firewire/fireworks/fireworks_hwdep.c +++ b/sound/firewire/fireworks/fireworks_hwdep.c @@ -135,7 +135,7 @@ hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
buf = memdup_user(data, count); if (IS_ERR(buf))
return PTR_ERR(data);
return PTR_ERR(buf);
/* check seqnum is not for kernel-land */ seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
-- 1.8.3.2
From f59ab9520cb99e442f749b1c95e4482311886c02 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 02:14:01 +0900 Subject: fixup for [PATCH 33/49]
Use different names for global and local symbols, remove meaningless function and prevent from narrowing conversion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob.c | 7 +++---- sound/firewire/bebob/bebob.h | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index b5644ab..3666b73 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -59,7 +59,7 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id) struct fw_device *fw_dev = fw_parent_device(bebob->unit); char vendor[24] = {0}; char model[32] = {0};
- u32 id;
- u32 hw_id; u32 data[2] = {0}; u32 revision; int err;
@@ -78,7 +78,7 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id)
/* get hardware id */ err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID,
&id);
if (err < 0) goto end;&hw_id);
@@ -99,7 +99,7 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id) strcpy(bebob->card->mixername, model); snprintf(bebob->card->longname, sizeof(bebob->card->longname), "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d",
vendor, model, id, revision,
data[0], data[1], dev_name(&bebob->unit->device), 100 << fw_dev->max_speed);vendor, model, hw_id, revision,
end: @@ -285,7 +285,6 @@ static void __exit snd_bebob_exit(void) { driver_unregister(&bebob_driver.driver);
- mutex_destroy(&devices_mutex);
}
module_init(snd_bebob_init); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 862f756..9575f03 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -25,8 +25,8 @@ #include "../fcp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */ -#define BEBOB_ADDR_REG_INFO 0xffffc8020000 -#define BEBOB_ADDR_REG_REQ 0xffffc8021000 +#define BEBOB_ADDR_REG_INFO 0xffffc8020000LL +#define BEBOB_ADDR_REG_REQ 0xffffc8021000LL
struct snd_bebob { struct snd_card *card; -- 1.8.3.2
From bf230c5036c9c1b4cf137a0a8b549e043a5e9e69 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:53:19 +0900 Subject: fixup for [PATCH 34/49]
Fix wrong type of variable. The type of struct snd_pcm_runtime.rate is 'unsigned int'. And fix wrong type of variable for function argument, improve comments for stream format.
Instead of putting different fixes in a single patch for one part of previous patchset, split & merge among other patches. That is, one patch should cover one same thing (e.g. add the missing LL suffix, or use unsigned int for PCM rate, improve comments, etc).
I stop commenting at this point, as the same applied to the rest.
thanks,
Takashi
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_stream.c | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 6686871..89d66e5 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -162,7 +162,7 @@ int snd_bebob_stream_discover(struct snd_bebob *bebob); int snd_bebob_stream_map(struct snd_bebob *bebob, struct amdtp_stream *stream); int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); -int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate); void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index c868c17..6a2a66d 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -427,7 +427,7 @@ end: return err; }
-int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, int rate) +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate) { struct amdtp_stream *master, *slave; atomic_t *slave_substreams; @@ -655,7 +655,7 @@ parse_stream_formation(u8 *buf, unsigned int len, format = buf[6 + e * 2];
switch (format) {
/* IEC 60958-3, currently handle as MBLA */
case 0x00: /* Multi bit linear audio */ case 0x06: /* Raw *//* IEC 60958 Conformant, currently handled as MBLA */
@@ -747,7 +747,8 @@ static int seek_msu_sync_input_plug(struct snd_bebob *bebob) { u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
- unsigned int i, type;
unsigned int i;
enum avc_bridgeco_plug_type type; int err;
/* Get the number of Music Sub Unit for both direction. */
-- 1.8.3.2
From bf973b05f35cf038adf79778da2fba7271e41ccb Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 02:14:46 +0900 Subject: fixup for [PATCH 37/49]
Improve indentaion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob_midi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c index 120a61b9..03a996df 100644 --- a/sound/firewire/bebob/bebob_midi.c +++ b/sound/firewire/bebob/bebob_midi.c @@ -123,7 +123,7 @@ int snd_bebob_create_midi_devices(struct snd_bebob *bebob) rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&midi_capture_ops);
&midi_capture_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
@@ -134,7 +134,7 @@ int snd_bebob_create_midi_devices(struct snd_bebob *bebob) rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&midi_playback_ops);
&midi_playback_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
-- 1.8.3.2
From ddc64059ed3a3b7ef24158031890d1f6849c1aee Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 02:07:06 +0900 Subject: fixup for [PATCH 40/49]
Add static specifier to file local variable.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 5e17d09..9ed96c4 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -224,7 +224,7 @@ static void bebob_remove(struct fw_unit *unit) snd_card_free_when_closed(bebob->card); }
-struct snd_bebob_rate_spec normal_rate_spec = { +static struct snd_bebob_rate_spec normal_rate_spec = { .get = &snd_bebob_stream_get_rate, .set = &snd_bebob_stream_set_rate }; -- 1.8.3.2
From 64ee38712a8ec557c6c0dbad63677d9cf5c6f6b2 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:54:32 +0900 Subject: fixup for [PATCH 41/49]
Add static specifier to file local variable.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob_terratec.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c index 2a89c1c..eef8ea7 100644 --- a/sound/firewire/bebob/bebob_terratec.c +++ b/sound/firewire/bebob/bebob_terratec.c @@ -38,13 +38,13 @@ phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id) return avc_audio_get_selector(bebob->unit, 0, 4, id); }
-struct snd_bebob_rate_spec phase_series_rate_spec = { +static struct snd_bebob_rate_spec phase_series_rate_spec = { .get = &snd_bebob_stream_get_rate, .set = &snd_bebob_stream_set_rate, };
/* PHASE 88 Rack FW */ -struct snd_bebob_clock_spec phase88_rack_clk = { +static struct snd_bebob_clock_spec phase88_rack_clk = { .num = ARRAY_SIZE(phase88_rack_clk_src_labels), .labels = phase88_rack_clk_src_labels, .get = &phase88_rack_clk_src_get, @@ -56,7 +56,7 @@ struct snd_bebob_spec phase88_rack_spec = { };
/* 'PHASE 24 FW' and 'PHASE X24 FW' */ -struct snd_bebob_clock_spec phase24_series_clk = { +static struct snd_bebob_clock_spec phase24_series_clk = { .num = ARRAY_SIZE(phase24_series_clk_src_labels), .labels = phase24_series_clk_src_labels, .get = &phase24_series_clk_src_get, -- 1.8.3.2
From 5ddaa135241e358f9d3c6b01f3478cdd36cfdcde Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 01:55:51 +0900 Subject: fixup for [PATCH 43/49]
Add static specifier to file local variable, and prevent from narrowing conversion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob_focusrite.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c index cd29dbc..474d0c9 100644 --- a/sound/firewire/bebob/bebob_focusrite.c +++ b/sound/firewire/bebob/bebob_focusrite.c @@ -14,15 +14,15 @@ #define DIG_OUT "Digital Out" #define STM_IN "Stream In"
-#define SAFFIRE_ADDRESS_BASE 0x000100000000 +#define SAFFIRE_ADDRESS_BASE 0x000100000000LL
-#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x0000000000f8 -#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x000000000174 +#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8 +#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174
/* whether sync to external device or not */ -#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x00000000013c -#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x000000000432 -#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x000000000164 +#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c +#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432 +#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164
#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0 #define SAFFIRE_CLOCK_SOURCE_SPDIF 1 @@ -35,10 +35,10 @@ #define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5
/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */ -#define SAFFIREPRO_ENABLE_DIG_IFACES 0x0000000001a4 +#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4
/* saffirepro has its own parameter for sampling frequency */ -#define SAFFIREPRO_RATE_NOREBOOT 0x0000000001cc +#define SAFFIREPRO_RATE_NOREBOOT 0x01cc /* index is the value for this register */ static const unsigned int rates[] = { [0] = 0, @@ -51,8 +51,8 @@ static const unsigned int rates[] = { };
/* saffire(no label)/saffire LE has metering */ -#define SAFFIRE_OFFSET_METER 0x000000000100 -#define SAFFIRE_LE_OFFSET_METER 0x000000000168 +#define SAFFIRE_OFFSET_METER 0x0100 +#define SAFFIRE_LE_OFFSET_METER 0x0168
static inline int saffire_read_block(struct snd_bebob *bebob, u64 offset, @@ -256,7 +256,7 @@ static struct snd_bebob_clock_spec saffire_both_clk_spec = { .get = &saffire_both_clk_src_get, }; /* Saffire LE */ -struct snd_bebob_meter_spec saffire_le_meter_spec = { +static struct snd_bebob_meter_spec saffire_le_meter_spec = { .num = ARRAY_SIZE(saffire_le_meter_labels), .labels = saffire_le_meter_labels, .get = &saffire_meter_get, @@ -267,7 +267,7 @@ struct snd_bebob_spec saffire_le_spec = { .meter = &saffire_le_meter_spec }; /* Saffire */ -struct snd_bebob_meter_spec saffire_meter_spec = { +static struct snd_bebob_meter_spec saffire_meter_spec = { .num = ARRAY_SIZE(saffire_meter_labels), .labels = saffire_meter_labels, .get = &saffire_meter_get, -- 1.8.3.2
From 0a076eeea485b381a0eeb2448a6ad874bb2b5c73 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 11:26:28 +0900 Subject: fixup for [PATCH 44/49]
Prevent from narrowing conversion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob_maudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c index dededb3..1cc0271 100644 --- a/sound/firewire/bebob/bebob_maudio.c +++ b/sound/firewire/bebob/bebob_maudio.c @@ -24,7 +24,7 @@
- mixer.
*/
-#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000 +#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000LL
#define METER_OFFSET 0x00600000
-- 1.8.3.2
From b12634cce5b43885a40cd5fdde4975e6824bf274 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto o-takashi@sakamocchi.jp Date: Tue, 27 May 2014 11:27:44 +0900 Subject: fixup for [PATCH 48/49]
Prevent from narrowing conversion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
sound/firewire/bebob/bebob_maudio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c index cf3abb9..4c61709 100644 --- a/sound/firewire/bebob/bebob_maudio.c +++ b/sound/firewire/bebob/bebob_maudio.c @@ -111,7 +111,7 @@ int snd_bebob_maudio_load_firmware(struct fw_unit *unit) * firmware version 5058 or later has date later than "20070401", but * 'date' is not null-terminated. */
- if (date < 0x3230303730343031) {
- if (date < 0x3230303730343031LL) { dev_err(&unit->device, "Use firmware version 5058 or later\n"); err = -ENOSYS;
-- 1.8.3.2
At Tue, 27 May 2014 07:45:49 +0900, Takashi Sakamoto wrote:
Iwai-san,
(May 26 2014 21:38), Takashi Iwai wrote:
Meanwhile I applied the patches to topic/firewire branch and put to my build test bot. I'll merge the branch to for-next tomorrow if no one gives objections.
Thanks for your applying this patchset to topic/firewire branch. I receive some sparse messages from kbuild test backend. There're several codes which should be fixed, mainly for type conflict.
I want you to apply revised patchset. I've already done most of this work for revised patchset.
Should I post them as new patchset? Or post new patches to previous patchset?
Please give the incremental patches on top of topic/firewire branch if it's only about some sparse warnings (no big crash bug or such).
thanks,
Takashi
(May 27 2014 14:20), Takashi Iwai wrote:
Please give the incremental patches on top of topic/firewire branch if it's only about some sparse warnings (no big crash bug or such).
These patches are not for such big bug. OK, I'll do it later, maybe 8 hours later.
Thanks
Takashi Sakamoto o-takashi@sakamocchi.jp
participants (4)
-
Clemens Ladisch
-
Stefan Richter
-
Takashi Iwai
-
Takashi Sakamoto