[alsa-devel] [RFC][PATCH 00/37] ALSA: firewire: support AMDTP variants
Hi,
This patchset is for three new drivers and related modification to firewire-lib module.
Overview: * Patch 01-02: for firewire-lib, to split AMDTP layer to two parts * Patch 03-07: for firewire-lib, to add MIDI asynchronous transaction * Patch 08-16: for firewire-digi00x, a new driver for Digi 002/003 family * Patch 17-26: for firewire-tascam, a new driver for TASCAM FireWire series * Patch 27-28: for firewire-lib, to add SPH field in CIP header support * Patch 29-37: for firewire-motu, a new driver for MOTU FireWire series
Status: * firewire-digi00x: * well-prepared, except for final testing with Digi 003 series * firewire-tascam: * well-prepared, except for conversion of controll messages to MIDI * firewire-motu: * experimental, for preview to developers
As the other my RFCs, I prepare for a branch in my backport repository, to make it easy to test this patchset. https://github.com/takaswie/snd-firewire-improve/tree/amdtp-variants
I'm happy if receiving your comments, reviews, tests and your patches. I have a plan to posting patches again to merging these well-prepared drivers till next merge window for Linux 4.3.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Cc: Yoshifuji Hideaki yoshfuji@linux-ipv6.org
Regards
Takashi Sakamoto (37): ALSA: firewire-lib: rename 'amdtp' to 'amdtp-stream' for functional separation ALSA: firewire-lib: functional separation between packet stream and data block format ALSA: firewire-lib: add helper functions for asynchronous MIDI port ALSA: firewire-lib: serialize request transaction and response transaction ALSA: firewire-lib: schedule tasklet again when MIDI substream has rest of MIDI messages ALSA: firewire-lib: add throttle for MIDI data rate ALSA: firewire-lib: avoid endless loop to transfer MIDI messages at fatal error ALSA: firewire-digi00x: add skelton for Digi 002/003 family ALSA: firewire-digi00x: add protocol layer ALSA: firewire-digi00x: add stream functionality ALSA: firewire-digi00x: add proc node to show clock status ALSA: firewire-digi00x: add PCM functionality ALSA: firewire-digi00x: add MIDI functionality ALSA: firewire-digi00x: add hwdep interface ALSA: firewire-digi00x: add support for asynchronous messaging ALSA: firewire-digi00x: add support for MIDI ports for machine control ALSA: firewire-tascam: add skeleton for TASCAM FireWire series ALSA: firewire-tascam: add a structure for model-dependent parameters. ALSA: firewire-tascam: add proc node to show firmware information ALSA: firewire-tascam: add specific protocol layer ALSA: firewire-tascam: add streaming functionality ALSA: firewire-tascam: add PCM functionality ALSA: firewire-tascam: add transaction functionality ALSA: firewire-tascam: add MIDI functionality ALSA: firewire-tascam: add hwdep interface ALSA: firewire-tascam: add MMAP support to show status and control message ALSA: firewire-lib: add support for source packet header field in CIP header ALSA: firewire-lib: enable protocol layer to handle current cycle count ALSA: firewire-motu: add skeleton for Mark of the unicorn (MOTU) FireWire series ALSA: firewire-motu: add a structure for model-dependent parameters. ALSA: firewire-motu: add MOTU specific protocol layer ALSA: firewire-motu: add stream functionality ALSA: firewire-motu: add transaction functionality ALSA: firewire-motu: add proc node to show the status of internal clock ALSA: firewire-motu: add PCM functionality ALSA: firewire-motu: add MIDI functionality ALSA: firewire-motu: add hwdep interface
include/uapi/sound/asound.h | 5 +- include/uapi/sound/firewire.h | 16 +- sound/firewire/Kconfig | 38 + sound/firewire/Makefile | 5 +- sound/firewire/amdtp-am824.c | 432 ++++++++++ sound/firewire/amdtp-am824.h | 45 ++ sound/firewire/amdtp-stream.c | 882 ++++++++++++++++++++ sound/firewire/amdtp-stream.h | 253 ++++++ sound/firewire/amdtp.c | 1107 -------------------------- sound/firewire/amdtp.h | 296 ------- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_midi.c | 16 +- sound/firewire/bebob/bebob_pcm.c | 10 +- sound/firewire/bebob/bebob_stream.c | 34 +- sound/firewire/dice/dice-midi.c | 16 +- sound/firewire/dice/dice-pcm.c | 10 +- sound/firewire/dice/dice-stream.c | 41 +- sound/firewire/dice/dice.h | 2 +- sound/firewire/digi00x/Makefile | 4 + sound/firewire/digi00x/amdtp-dot.c | 425 ++++++++++ sound/firewire/digi00x/digi00x-hwdep.c | 200 +++++ sound/firewire/digi00x/digi00x-midi.c | 198 +++++ sound/firewire/digi00x/digi00x-pcm.c | 355 +++++++++ sound/firewire/digi00x/digi00x-proc.c | 99 +++ sound/firewire/digi00x/digi00x-stream.c | 422 ++++++++++ sound/firewire/digi00x/digi00x-transaction.c | 137 ++++ sound/firewire/digi00x/digi00x.c | 174 ++++ sound/firewire/digi00x/digi00x.h | 155 ++++ sound/firewire/fcp.c | 2 +- sound/firewire/fireworks/fireworks.c | 12 +- sound/firewire/fireworks/fireworks.h | 2 +- sound/firewire/fireworks/fireworks_midi.c | 16 +- sound/firewire/fireworks/fireworks_pcm.c | 8 +- sound/firewire/fireworks/fireworks_stream.c | 8 +- sound/firewire/lib.c | 127 +++ sound/firewire/lib.h | 52 ++ sound/firewire/motu/Makefile | 3 + sound/firewire/motu/amdtp-motu.c | 442 ++++++++++ sound/firewire/motu/motu-hwdep.c | 210 +++++ sound/firewire/motu/motu-midi.c | 151 ++++ sound/firewire/motu/motu-pcm.c | 397 +++++++++ sound/firewire/motu/motu-proc.c | 73 ++ sound/firewire/motu/motu-stream.c | 670 ++++++++++++++++ sound/firewire/motu/motu-transaction.c | 97 +++ sound/firewire/motu/motu.c | 311 ++++++++ sound/firewire/motu/motu.h | 127 +++ sound/firewire/oxfw/oxfw-midi.c | 16 +- sound/firewire/oxfw/oxfw-pcm.c | 8 +- sound/firewire/oxfw/oxfw-stream.c | 11 +- sound/firewire/oxfw/oxfw.h | 2 +- sound/firewire/tascam/Makefile | 4 + sound/firewire/tascam/amdtp-tascam.c | 236 ++++++ sound/firewire/tascam/tascam-hwdep.c | 232 ++++++ sound/firewire/tascam/tascam-midi.c | 158 ++++ sound/firewire/tascam/tascam-pcm.c | 306 +++++++ sound/firewire/tascam/tascam-proc.c | 88 ++ sound/firewire/tascam/tascam-stream.c | 512 ++++++++++++ sound/firewire/tascam/tascam-transaction.c | 234 ++++++ sound/firewire/tascam/tascam.c | 232 ++++++ sound/firewire/tascam/tascam.h | 141 ++++ 60 files changed, 8760 insertions(+), 1507 deletions(-) create mode 100644 sound/firewire/amdtp-am824.c create mode 100644 sound/firewire/amdtp-am824.h create mode 100644 sound/firewire/amdtp-stream.c create mode 100644 sound/firewire/amdtp-stream.h delete mode 100644 sound/firewire/amdtp.c delete mode 100644 sound/firewire/amdtp.h create mode 100644 sound/firewire/digi00x/Makefile create mode 100644 sound/firewire/digi00x/amdtp-dot.c create mode 100644 sound/firewire/digi00x/digi00x-hwdep.c create mode 100644 sound/firewire/digi00x/digi00x-midi.c create mode 100644 sound/firewire/digi00x/digi00x-pcm.c create mode 100644 sound/firewire/digi00x/digi00x-proc.c create mode 100644 sound/firewire/digi00x/digi00x-stream.c create mode 100644 sound/firewire/digi00x/digi00x-transaction.c create mode 100644 sound/firewire/digi00x/digi00x.c create mode 100644 sound/firewire/digi00x/digi00x.h create mode 100644 sound/firewire/motu/Makefile create mode 100644 sound/firewire/motu/amdtp-motu.c create mode 100644 sound/firewire/motu/motu-hwdep.c create mode 100644 sound/firewire/motu/motu-midi.c create mode 100644 sound/firewire/motu/motu-pcm.c create mode 100644 sound/firewire/motu/motu-proc.c create mode 100644 sound/firewire/motu/motu-stream.c create mode 100644 sound/firewire/motu/motu-transaction.c create mode 100644 sound/firewire/motu/motu.c create mode 100644 sound/firewire/motu/motu.h create mode 100644 sound/firewire/tascam/Makefile create mode 100644 sound/firewire/tascam/amdtp-tascam.c create mode 100644 sound/firewire/tascam/tascam-hwdep.c create mode 100644 sound/firewire/tascam/tascam-midi.c create mode 100644 sound/firewire/tascam/tascam-pcm.c create mode 100644 sound/firewire/tascam/tascam-proc.c create mode 100644 sound/firewire/tascam/tascam-stream.c create mode 100644 sound/firewire/tascam/tascam-transaction.c create mode 100644 sound/firewire/tascam/tascam.c create mode 100644 sound/firewire/tascam/tascam.h
This commit is a preparation for splitting to transmission layer and packetizatin layer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Makefile | 2 +- sound/firewire/amdtp-stream.c | 1107 ++++++++++++++++++++++++++++++++++ sound/firewire/amdtp-stream.h | 296 +++++++++ sound/firewire/amdtp.c | 1107 ---------------------------------- sound/firewire/amdtp.h | 296 --------- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/dice/dice.h | 2 +- sound/firewire/fcp.c | 2 +- sound/firewire/fireworks/fireworks.h | 2 +- sound/firewire/oxfw/oxfw.h | 2 +- 10 files changed, 1409 insertions(+), 1409 deletions(-) create mode 100644 sound/firewire/amdtp-stream.c create mode 100644 sound/firewire/amdtp-stream.h delete mode 100644 sound/firewire/amdtp.c delete mode 100644 sound/firewire/amdtp.h
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 8b37f08..102e342 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -1,5 +1,5 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ - fcp.o cmp.o amdtp.o + fcp.o cmp.o amdtp-stream.o snd-oxfw-objs := oxfw.o snd-isight-objs := isight.o snd-scs1x-objs := scs1x.o diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c new file mode 100644 index 0000000..9965984 --- /dev/null +++ b/sound/firewire/amdtp-stream.c @@ -0,0 +1,1107 @@ +/* + * Audio and Music Data Transmission Protocol (IEC 61883-6) streams + * with Common Isochronous Packet (IEC 61883-1) headers + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/firewire.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rawmidi.h> +#include "amdtp-stream.h" + +#define TICKS_PER_CYCLE 3072 +#define CYCLES_PER_SECOND 8000 +#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND) + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */ + +/* isochronous header parameters */ +#define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_CIP 1 + +/* common isochronous packet header parameters */ +#define CIP_EOH_SHIFT 31 +#define CIP_EOH (1u << CIP_EOH_SHIFT) +#define CIP_EOH_MASK 0x80000000 +#define CIP_SID_SHIFT 24 +#define CIP_SID_MASK 0x3f000000 +#define CIP_DBS_MASK 0x00ff0000 +#define CIP_DBS_SHIFT 16 +#define CIP_DBC_MASK 0x000000ff +#define CIP_FMT_SHIFT 24 +#define CIP_FMT_MASK 0x3f000000 +#define CIP_FDF_MASK 0x00ff0000 +#define CIP_FDF_SHIFT 16 +#define CIP_SYT_MASK 0x0000ffff +#define CIP_SYT_NO_INFO 0xffff + +/* + * Audio and Music transfer protocol specific parameters + * only "Clock-based rate control mode" is supported + */ +#define CIP_FMT_AM (0x10 << CIP_FMT_SHIFT) +#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SHIFT + 3)) +#define AMDTP_FDF_NO_DATA 0xff + +/* TODO: make these configurable */ +#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); + +/** + * 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 amdtp_stream_direction dir, enum cip_flags flags) +{ + s->unit = unit; + s->direction = dir; + s->flags = flags; + s->context = ERR_PTR(-1); + mutex_init(&s->mutex); + 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); + +/** + * amdtp_stream_destroy - free stream resources + * @s: the AMDTP stream to destroy + */ +void amdtp_stream_destroy(struct amdtp_stream *s) +{ + WARN_ON(amdtp_stream_running(s)); + mutex_destroy(&s->mutex); +} +EXPORT_SYMBOL(amdtp_stream_destroy); + +const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 8, + [CIP_SFC_44100] = 8, + [CIP_SFC_48000] = 8, + [CIP_SFC_88200] = 16, + [CIP_SFC_96000] = 16, + [CIP_SFC_176400] = 32, + [CIP_SFC_192000] = 32, +}; +EXPORT_SYMBOL(amdtp_syt_intervals); + +const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = { + [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. + * @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 align period/buffer sizes to current SYT_INTERVAL. + * + * TODO: These constraints can be improved with proper rules. + * Currently apply LCM of SYT_INTERVALs. + */ + 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 + * @pcm_channels: the number of PCM samples in each data block, to be encoded + * as AM824 multi-bit linear audio + * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) + * + * The parameters must be set before the stream is started, and must not be + * changed while the stream is running. + */ +void amdtp_stream_set_parameters(struct amdtp_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports) +{ + unsigned int i, sfc, midi_channels; + + midi_channels = DIV_ROUND_UP(midi_ports, 8); + + 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; + + for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) + if (amdtp_rate_table[sfc] == rate) + goto sfc_found; + WARN_ON(1); + return; + +sfc_found: + s->pcm_channels = pcm_channels; + s->sfc = sfc; + s->data_block_quadlets = s->pcm_channels + midi_channels; + s->midi_ports = midi_ports; + + s->syt_interval = amdtp_syt_intervals[sfc]; + + /* default buffering in the device */ + s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; + 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; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + s->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; +} +EXPORT_SYMBOL(amdtp_stream_set_parameters); + +/** + * 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_stream_set_parameters(). + */ +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) +{ + unsigned int multiplier = 1; + + if (s->flags & CIP_JUMBO_PAYLOAD) + multiplier = 5; + + return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; +} +EXPORT_SYMBOL(amdtp_stream_get_max_payload); + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); + +/** + * 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 parameters (rate/PCM channels/ + * MIDI) and before the stream is started, and must not be changed while the + * stream is running. + */ +void amdtp_stream_set_pcm_format(struct amdtp_stream *s, + snd_pcm_format_t format) +{ + if (WARN_ON(amdtp_stream_pcm_running(s))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + if (s->direction == AMDTP_OUT_STREAM) { + s->transfer_samples = write_pcm_s16; + break; + } + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S32: + if (s->direction == AMDTP_OUT_STREAM) + s->transfer_samples = write_pcm_s32; + else + s->transfer_samples = read_pcm_s32; + break; + } +} +EXPORT_SYMBOL(amdtp_stream_set_pcm_format); + +/** + * 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_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_stream_pcm_prepare); + +static unsigned int calculate_data_blocks(struct amdtp_stream *s, + unsigned int syt) +{ + unsigned int phase, data_blocks; + + /* Blocking mode. */ + if (s->flags & CIP_BLOCKING) { + /* This module generate empty packet for 'no data'. */ + if (syt == CIP_SYT_NO_INFO) + data_blocks = 0; + else + data_blocks = s->syt_interval; + /* Non-blocking mode. */ + } else { + if (!cip_sfc_is_base_44100(s->sfc)) { + /* Sample_rate / 8000 is an integer, and precomputed. */ + data_blocks = s->data_block_state; + } else { + phase = s->data_block_state; + + /* + * This calculates the number of data blocks per packet so that + * 1) the overall rate is correct and exactly synchronized to + * the bus clock, and + * 2) packets with a rounded-up number of blocks occur as early + * as possible in the sequence (to prevent underruns of the + * device's buffer). + */ + if (s->sfc == CIP_SFC_44100) + /* 6 6 5 6 5 6 5 ... */ + data_blocks = 5 + ((phase & 1) ^ + (phase == 0 || phase >= 40)); + else + /* 12 11 11 11 11 ... or 23 22 22 22 22 ... */ + data_blocks = 11 * (s->sfc >> 1) + (phase == 0); + if (++phase >= (80 >> (s->sfc >> 1))) + phase = 0; + s->data_block_state = phase; + } + } + + return data_blocks; +} + +static unsigned int calculate_syt(struct amdtp_stream *s, + unsigned int cycle) +{ + unsigned int syt_offset, phase, index, syt; + + if (s->last_syt_offset < TICKS_PER_CYCLE) { + if (!cip_sfc_is_base_44100(s->sfc)) + syt_offset = s->last_syt_offset + s->syt_offset_state; + else { + /* + * The time, in ticks, of the n'th SYT_INTERVAL sample is: + * n * SYT_INTERVAL * 24576000 / sample_rate + * Modulo TICKS_PER_CYCLE, the difference between successive + * elements is about 1386.23. Rounding the results of this + * formula to the SYT precision results in a sequence of + * differences that begins with: + * 1386 1386 1387 1386 1386 1386 1387 1386 1386 1386 1387 ... + * This code generates _exactly_ the same sequence. + */ + phase = s->syt_offset_state; + index = phase % 13; + syt_offset = s->last_syt_offset; + syt_offset += 1386 + ((index && !(index & 3)) || + phase == 146); + if (++phase >= 147) + phase = 0; + s->syt_offset_state = phase; + } + } else + syt_offset = s->last_syt_offset - TICKS_PER_CYCLE; + s->last_syt_offset = syt_offset; + + if (syt_offset < TICKS_PER_CYCLE) { + syt_offset += s->transfer_delay; + syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; + syt += syt_offset % TICKS_PER_CYCLE; + + return syt & CIP_SYT_MASK; + } else { + return CIP_SYT_NO_INFO; + } +} + +static void write_pcm_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; + 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; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[s->pcm_positions[c]] = + cpu_to_be32((*src >> 8) | 0x40000000); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, 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; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[s->pcm_positions[c]] = + cpu_to_be32((*src << 8) | 0x42000000); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_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[s->pcm_positions[c]]) << 8; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + unsigned int i, c; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < s->pcm_channels; ++c) + buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +/* + * To avoid sending MIDI bytes at too high a rate, assume that the receiving + * device has a FIFO, and track how much it is filled. This values increases + * by one whenever we send one byte in a packet, but the FIFO empties at + * a constant rate independent of our packet rate. One packet has syt_interval + * samples, so the number of bytes that empty out of the FIFO, per packet(!), + * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing + * fractional values, the values in midi_fifo_used[] are measured in bytes + * multiplied by the sample rate. + */ +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + int used; + + used = s->midi_fifo_used[port]; + if (used == 0) /* common shortcut */ + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + s->midi_fifo_used[port] = used; + + return used < s->midi_fifo_limit; +} + +static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) +{ + s->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; +} + +static void write_midi_messages(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + unsigned int f, port; + u8 *b; + + for (f = 0; f < frames; f++) { + b = (u8 *)&buffer[s->midi_position]; + + port = (s->data_block_counter + f) % 8; + if (f < MAX_MIDI_RX_BLOCKS && + midi_ratelimit_per_packet(s, port) && + s->midi[port] != NULL && + snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) { + midi_rate_use_one_byte(s, port); + b[0] = 0x81; + } else { + b[0] = 0x80; + b[1] = 0; + } + b[2] = 0; + b[3] = 0; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(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->midi_position]; + + 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, + struct snd_pcm_substream *pcm, + unsigned int frames) +{ + unsigned int ptr; + + /* + * In IEC 61883-6, one data block represents one event. In ALSA, one + * event equals to one PCM frame. But Dice has a quirk to transfer + * two PCM frames in one data block. + */ + if (s->double_pcm_frames) + 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 = 0; + + if (IS_ERR(s->context)) + goto end; + + 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 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 int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks, + unsigned int syt) +{ + __be32 *buffer; + unsigned int payload_length; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | + (s->data_block_quadlets << CIP_DBS_SHIFT) | + s->data_block_counter); + buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 | + (s->sfc << CIP_FDF_SHIFT) | syt); + buffer += 2; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + s->transfer_samples(s, pcm, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + if (s->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; + if (queue_out_packet(s, payload_length, false) < 0) + return -EIO; + + if (pcm) + update_pcm_pointers(s, pcm, data_blocks); + + /* No need to return the number of handled data blocks. */ + return 0; +} + +static int handle_in_packet(struct amdtp_stream *s, + unsigned int payload_quadlets, __be32 *buffer, + unsigned int *data_blocks) +{ + u32 cip_header[2]; + unsigned int data_block_quadlets, data_block_counter, dbc_interval; + struct snd_pcm_substream *pcm = NULL; + bool lost; + + 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 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) || + ((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]); + *data_blocks = 0; + goto end; + } + + /* Calculate data blocks */ + if (payload_quadlets < 3 || + ((cip_header[1] & CIP_FDF_MASK) == + (AMDTP_FDF_NO_DATA << CIP_FDF_SHIFT))) { + *data_blocks = 0; + } else { + data_block_quadlets = + (cip_header[0] & CIP_DBS_MASK) >> CIP_DBS_SHIFT; + /* avoid division by zero */ + if (data_block_quadlets == 0) { + dev_err(&s->unit->device, + "Detect invalid value in dbs field: %08X\n", + cip_header[0]); + return -EPROTO; + } + if (s->flags & CIP_WRONG_DBS) + data_block_quadlets = s->data_block_quadlets; + + *data_blocks = (payload_quadlets - 2) / data_block_quadlets; + } + + /* Check data block counter continuity */ + data_block_counter = cip_header[0] & CIP_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; + } 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)) + dbc_interval = s->tx_dbc_interval; + else + dbc_interval = *data_blocks; + + lost = data_block_counter != + ((s->data_block_counter + dbc_interval) & 0xff); + } + + if (lost) { + dev_err(&s->unit->device, + "Detect discontinuity of CIP: %02X %02X\n", + s->data_block_counter, data_block_counter); + return -EIO; + } + + if (*data_blocks > 0) { + buffer += 2; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + s->transfer_samples(s, pcm, buffer, *data_blocks); + + if (s->midi_ports) + read_midi_messages(s, buffer, *data_blocks); + } + + 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) + return -EIO; + + if (pcm) + update_pcm_pointers(s, pcm, *data_blocks); + + return 0; +} + +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, syt, packets = header_length / 4; + unsigned int data_blocks; + + if (s->packet_index < 0) + return; + + /* + * Compute the cycle of the last queued packet. + * (We need only the four lowest bits for the SYT, so we can ignore + * that bits 0-11 must wrap around at 3072.) + */ + cycle += QUEUE_LENGTH - packets; + + for (i = 0; i < packets; ++i) { + syt = calculate_syt(s, ++cycle); + data_blocks = calculate_data_blocks(s, syt); + + if (handle_out_packet(s, data_blocks, syt) < 0) { + s->packet_index = -1; + amdtp_stream_pcm_abort(s); + return; + } + } + + 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, syt, packets; + unsigned int payload_quadlets, max_payload_quadlets; + unsigned int data_blocks; + __be32 *buffer, *headers = header; + + if (s->packet_index < 0) + return; + + /* The number of packets in buffer */ + packets = header_length / IN_PACKET_HEADER_SIZE; + + /* For buffer-over-run prevention. */ + max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4; + + for (p = 0; p < packets; p++) { + 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; + if (payload_quadlets > max_payload_quadlets) { + dev_err(&s->unit->device, + "Detect jumbo payload: %02x %02x\n", + payload_quadlets, max_payload_quadlets); + s->packet_index = -1; + break; + } + + if (handle_in_packet(s, payload_quadlets, buffer, + &data_blocks) < 0) { + s->packet_index = -1; + break; + } + + /* Process sync slave stream */ + if (s->sync_slave && s->sync_slave->callbacked) { + syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK; + if (handle_out_packet(s->sync_slave, + data_blocks, syt) < 0) { + s->packet_index = -1; + break; + } + } + } + + /* Queueing error or detecting discontinuity */ + if (s->packet_index < 0) { + amdtp_stream_pcm_abort(s); + + /* 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_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 + * @channel: the isochronous channel on the bus + * @speed: firewire speed code + * + * The stream cannot be started until it has been configured with + * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI + * device can be started. + */ +int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) +{ + static const struct { + unsigned int data_block; + unsigned int syt_offset; + } initial_state[] = { + [CIP_SFC_32000] = { 4, 3072 }, + [CIP_SFC_48000] = { 6, 1024 }, + [CIP_SFC_96000] = { 12, 1024 }, + [CIP_SFC_192000] = { 24, 1024 }, + [CIP_SFC_44100] = { 0, 67 }, + [CIP_SFC_88200] = { 0, 67 }, + [CIP_SFC_176400] = { 0, 67 }, + }; + unsigned int header_size; + enum dma_data_direction dir; + int type, tag, err; + + mutex_lock(&s->mutex); + + if (WARN_ON(amdtp_stream_running(s) || + (s->data_block_quadlets < 1))) { + err = -EBADFD; + goto err_unlock; + } + + 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; + + /* initialize packet buffer */ + if (s->direction == AMDTP_IN_STREAM) { + dir = DMA_FROM_DEVICE; + type = FW_ISO_CONTEXT_RECEIVE; + header_size = IN_PACKET_HEADER_SIZE; + } else { + dir = DMA_TO_DEVICE; + type = FW_ISO_CONTEXT_TRANSMIT; + header_size = OUT_PACKET_HEADER_SIZE; + } + err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH, + 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, + type, channel, speed, header_size, + amdtp_stream_first_callback, s); + if (IS_ERR(s->context)) { + err = PTR_ERR(s->context); + if (err == -EBUSY) + dev_err(&s->unit->device, + "no free stream on this controller\n"); + goto err_buffer; + } + + amdtp_stream_update(s); + + s->packet_index = 0; + do { + 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); + + /* 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, tag); + if (err < 0) + goto err_context; + + mutex_unlock(&s->mutex); + + return 0; + +err_context: + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); +err_buffer: + iso_packets_buffer_destroy(&s->buffer, s->unit); +err_unlock: + mutex_unlock(&s->mutex); + + return err; +} +EXPORT_SYMBOL(amdtp_stream_start); + +/** + * 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_stream_pcm_pointer(struct amdtp_stream *s) +{ + /* this optimization is allowed to be racy */ + if (s->pointer_flush && amdtp_stream_running(s)) + fw_iso_context_flush_completions(s->context); + else + s->pointer_flush = true; + + return ACCESS_ONCE(s->pcm_buffer_pointer); +} +EXPORT_SYMBOL(amdtp_stream_pcm_pointer); + +/** + * amdtp_stream_update - update the stream after a bus reset + * @s: the AMDTP stream + */ +void amdtp_stream_update(struct amdtp_stream *s) +{ + /* Precomputing. */ + ACCESS_ONCE(s->source_node_id_field) = + (fw_parent_device(s->unit)->card->node_id << CIP_SID_SHIFT) & + CIP_SID_MASK; +} +EXPORT_SYMBOL(amdtp_stream_update); + +/** + * 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_stream_stop(struct amdtp_stream *s) +{ + mutex_lock(&s->mutex); + + if (!amdtp_stream_running(s)) { + mutex_unlock(&s->mutex); + return; + } + + tasklet_kill(&s->period_tasklet); + fw_iso_context_stop(s->context); + fw_iso_context_destroy(s->context); + 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); + +/** + * 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_stream_pcm_abort(struct amdtp_stream *s) +{ + struct snd_pcm_substream *pcm; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + snd_pcm_stop_xrun(pcm); +} +EXPORT_SYMBOL(amdtp_stream_pcm_abort); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h new file mode 100644 index 0000000..f7a6f5f --- /dev/null +++ b/sound/firewire/amdtp-stream.h @@ -0,0 +1,296 @@ +#ifndef SOUND_FIREWIRE_AMDTP_STREAM_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_STREAM_H_INCLUDED + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <sound/asound.h> +#include "packets-buffer.h" + +/** + * 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 + * be used if supported by the device. + * @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_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. + * @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. + * @CIP_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. + * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty + * packet is wrong but the others are correct. + * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an + * packet is larger than IEC 61883-6 defines. Current implementation + * allows 5 times as large as IEC 61883-6 defines. + */ +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, + CIP_WRONG_DBS = 0x10, + CIP_SKIP_DBC_ZERO_CHECK = 0x20, + CIP_SKIP_INIT_DBC_CHECK = 0x40, + CIP_EMPTY_HAS_WRONG_DBC = 0x80, + CIP_JUMBO_PAYLOAD = 0x100, +}; + +/** + * enum cip_sfc - supported Sampling Frequency Codes (SFCs) + * @CIP_SFC_32000: 32,000 data blocks + * @CIP_SFC_44100: 44,100 data blocks + * @CIP_SFC_48000: 48,000 data blocks + * @CIP_SFC_88200: 88,200 data blocks + * @CIP_SFC_96000: 96,000 data blocks + * @CIP_SFC_176400: 176,400 data blocks + * @CIP_SFC_192000: 192,000 data blocks + * @CIP_SFC_COUNT: the number of supported SFCs + * + * These values are used to show nominal Sampling Frequency Code in + * Format Dependent Field (FDF) of AMDTP packet header. In IEC 61883-6:2002, + * this code means the number of events per second. Actually the code + * represents the number of data blocks transferred per second in an AMDTP + * stream. + * + * In IEC 61883-6:2005, some extensions were added to support more types of + * data such as 'One Bit LInear Audio', therefore the meaning of SFC became + * different depending on the types. + * + * Currently our implementation is compatible with IEC 61883-6:2002. + */ +enum cip_sfc { + CIP_SFC_32000 = 0, + CIP_SFC_44100 = 1, + CIP_SFC_48000 = 2, + CIP_SFC_88200 = 3, + CIP_SFC_96000 = 4, + CIP_SFC_176400 = 5, + CIP_SFC_192000 = 6, + 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) + + +/* + * 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. + * + * 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_pcm_runtime; +struct snd_rawmidi_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; + + enum cip_sfc sfc; + unsigned int data_block_quadlets; + unsigned int pcm_channels; + unsigned int midi_ports; + 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; + unsigned int source_node_id_field; + struct iso_packets_buffer buffer; + + struct snd_pcm_substream *pcm; + struct tasklet_struct period_tasklet; + + int packet_index; + unsigned int data_block_counter; + + unsigned int data_block_state; + + unsigned int last_syt_offset; + unsigned int syt_offset_state; + + unsigned int pcm_buffer_pointer; + unsigned int pcm_period_pointer; + bool pointer_flush; + bool double_pcm_frames; + + struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; + int midi_fifo_limit; + int midi_fifo_used[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; +}; + +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); + +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_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); +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 + * @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_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_streaming_error(struct amdtp_stream *s) +{ + return s->packet_index < 0; +} + +/** + * 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 + * + * 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_stream_pcm_trigger(struct amdtp_stream *s, + struct snd_pcm_substream *pcm) +{ + 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; +} + +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 diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c deleted file mode 100644 index 7bb988f..0000000 --- a/sound/firewire/amdtp.c +++ /dev/null @@ -1,1107 +0,0 @@ -/* - * Audio and Music Data Transmission Protocol (IEC 61883-6) streams - * with Common Isochronous Packet (IEC 61883-1) headers - * - * Copyright (c) Clemens Ladisch clemens@ladisch.de - * Licensed under the terms of the GNU General Public License, version 2. - */ - -#include <linux/device.h> -#include <linux/err.h> -#include <linux/firewire.h> -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/sched.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/rawmidi.h> -#include "amdtp.h" - -#define TICKS_PER_CYCLE 3072 -#define CYCLES_PER_SECOND 8000 -#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND) - -/* - * Nominally 3125 bytes/second, but the MIDI port's clock might be - * 1% too slow, and the bus clock 100 ppm too fast. - */ -#define MIDI_BYTES_PER_SECOND 3093 - -/* - * Several devices look only at the first eight data blocks. - * In any case, this is more than enough for the MIDI data rate. - */ -#define MAX_MIDI_RX_BLOCKS 8 - -#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */ - -/* isochronous header parameters */ -#define ISO_DATA_LENGTH_SHIFT 16 -#define TAG_CIP 1 - -/* common isochronous packet header parameters */ -#define CIP_EOH_SHIFT 31 -#define CIP_EOH (1u << CIP_EOH_SHIFT) -#define CIP_EOH_MASK 0x80000000 -#define CIP_SID_SHIFT 24 -#define CIP_SID_MASK 0x3f000000 -#define CIP_DBS_MASK 0x00ff0000 -#define CIP_DBS_SHIFT 16 -#define CIP_DBC_MASK 0x000000ff -#define CIP_FMT_SHIFT 24 -#define CIP_FMT_MASK 0x3f000000 -#define CIP_FDF_MASK 0x00ff0000 -#define CIP_FDF_SHIFT 16 -#define CIP_SYT_MASK 0x0000ffff -#define CIP_SYT_NO_INFO 0xffff - -/* - * Audio and Music transfer protocol specific parameters - * only "Clock-based rate control mode" is supported - */ -#define CIP_FMT_AM (0x10 << CIP_FMT_SHIFT) -#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SHIFT + 3)) -#define AMDTP_FDF_NO_DATA 0xff - -/* TODO: make these configurable */ -#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); - -/** - * 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 amdtp_stream_direction dir, enum cip_flags flags) -{ - s->unit = unit; - s->direction = dir; - s->flags = flags; - s->context = ERR_PTR(-1); - mutex_init(&s->mutex); - 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); - -/** - * amdtp_stream_destroy - free stream resources - * @s: the AMDTP stream to destroy - */ -void amdtp_stream_destroy(struct amdtp_stream *s) -{ - WARN_ON(amdtp_stream_running(s)); - mutex_destroy(&s->mutex); -} -EXPORT_SYMBOL(amdtp_stream_destroy); - -const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { - [CIP_SFC_32000] = 8, - [CIP_SFC_44100] = 8, - [CIP_SFC_48000] = 8, - [CIP_SFC_88200] = 16, - [CIP_SFC_96000] = 16, - [CIP_SFC_176400] = 32, - [CIP_SFC_192000] = 32, -}; -EXPORT_SYMBOL(amdtp_syt_intervals); - -const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = { - [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. - * @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 align period/buffer sizes to current SYT_INTERVAL. - * - * TODO: These constraints can be improved with proper rules. - * Currently apply LCM of SYT_INTERVALs. - */ - 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 - * @pcm_channels: the number of PCM samples in each data block, to be encoded - * as AM824 multi-bit linear audio - * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) - * - * The parameters must be set before the stream is started, and must not be - * changed while the stream is running. - */ -void amdtp_stream_set_parameters(struct amdtp_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports) -{ - unsigned int i, sfc, midi_channels; - - midi_channels = DIV_ROUND_UP(midi_ports, 8); - - 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; - - for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) - if (amdtp_rate_table[sfc] == rate) - goto sfc_found; - WARN_ON(1); - return; - -sfc_found: - s->pcm_channels = pcm_channels; - s->sfc = sfc; - s->data_block_quadlets = s->pcm_channels + midi_channels; - s->midi_ports = midi_ports; - - s->syt_interval = amdtp_syt_intervals[sfc]; - - /* default buffering in the device */ - s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; - 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; - - /* - * We do not know the actual MIDI FIFO size of most devices. Just - * assume two bytes, i.e., one byte can be received over the bus while - * the previous one is transmitted over MIDI. - * (The value here is adjusted for midi_ratelimit_per_packet().) - */ - s->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; -} -EXPORT_SYMBOL(amdtp_stream_set_parameters); - -/** - * 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_stream_set_parameters(). - */ -unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) -{ - unsigned int multiplier = 1; - - if (s->flags & CIP_JUMBO_PAYLOAD) - multiplier = 5; - - return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; -} -EXPORT_SYMBOL(amdtp_stream_get_max_payload); - -static void write_pcm_s16(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); -static void write_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); -static void read_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); - -/** - * 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 parameters (rate/PCM channels/ - * MIDI) and before the stream is started, and must not be changed while the - * stream is running. - */ -void amdtp_stream_set_pcm_format(struct amdtp_stream *s, - snd_pcm_format_t format) -{ - if (WARN_ON(amdtp_stream_pcm_running(s))) - return; - - switch (format) { - default: - WARN_ON(1); - /* fall through */ - case SNDRV_PCM_FORMAT_S16: - if (s->direction == AMDTP_OUT_STREAM) { - s->transfer_samples = write_pcm_s16; - break; - } - WARN_ON(1); - /* fall through */ - case SNDRV_PCM_FORMAT_S32: - if (s->direction == AMDTP_OUT_STREAM) - s->transfer_samples = write_pcm_s32; - else - s->transfer_samples = read_pcm_s32; - break; - } -} -EXPORT_SYMBOL(amdtp_stream_set_pcm_format); - -/** - * 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_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_stream_pcm_prepare); - -static unsigned int calculate_data_blocks(struct amdtp_stream *s, - unsigned int syt) -{ - unsigned int phase, data_blocks; - - /* Blocking mode. */ - if (s->flags & CIP_BLOCKING) { - /* This module generate empty packet for 'no data'. */ - if (syt == CIP_SYT_NO_INFO) - data_blocks = 0; - else - data_blocks = s->syt_interval; - /* Non-blocking mode. */ - } else { - if (!cip_sfc_is_base_44100(s->sfc)) { - /* Sample_rate / 8000 is an integer, and precomputed. */ - data_blocks = s->data_block_state; - } else { - phase = s->data_block_state; - - /* - * This calculates the number of data blocks per packet so that - * 1) the overall rate is correct and exactly synchronized to - * the bus clock, and - * 2) packets with a rounded-up number of blocks occur as early - * as possible in the sequence (to prevent underruns of the - * device's buffer). - */ - if (s->sfc == CIP_SFC_44100) - /* 6 6 5 6 5 6 5 ... */ - data_blocks = 5 + ((phase & 1) ^ - (phase == 0 || phase >= 40)); - else - /* 12 11 11 11 11 ... or 23 22 22 22 22 ... */ - data_blocks = 11 * (s->sfc >> 1) + (phase == 0); - if (++phase >= (80 >> (s->sfc >> 1))) - phase = 0; - s->data_block_state = phase; - } - } - - return data_blocks; -} - -static unsigned int calculate_syt(struct amdtp_stream *s, - unsigned int cycle) -{ - unsigned int syt_offset, phase, index, syt; - - if (s->last_syt_offset < TICKS_PER_CYCLE) { - if (!cip_sfc_is_base_44100(s->sfc)) - syt_offset = s->last_syt_offset + s->syt_offset_state; - else { - /* - * The time, in ticks, of the n'th SYT_INTERVAL sample is: - * n * SYT_INTERVAL * 24576000 / sample_rate - * Modulo TICKS_PER_CYCLE, the difference between successive - * elements is about 1386.23. Rounding the results of this - * formula to the SYT precision results in a sequence of - * differences that begins with: - * 1386 1386 1387 1386 1386 1386 1387 1386 1386 1386 1387 ... - * This code generates _exactly_ the same sequence. - */ - phase = s->syt_offset_state; - index = phase % 13; - syt_offset = s->last_syt_offset; - syt_offset += 1386 + ((index && !(index & 3)) || - phase == 146); - if (++phase >= 147) - phase = 0; - s->syt_offset_state = phase; - } - } else - syt_offset = s->last_syt_offset - TICKS_PER_CYCLE; - s->last_syt_offset = syt_offset; - - if (syt_offset < TICKS_PER_CYCLE) { - syt_offset += s->transfer_delay; - syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; - syt += syt_offset % TICKS_PER_CYCLE; - - return syt & CIP_SYT_MASK; - } else { - return CIP_SYT_NO_INFO; - } -} - -static void write_pcm_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; - 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; - - for (i = 0; i < frames; ++i) { - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c]] = - cpu_to_be32((*src >> 8) | 0x40000000); - src++; - } - buffer += s->data_block_quadlets; - if (--remaining_frames == 0) - src = (void *)runtime->dma_area; - } -} - -static void write_pcm_s16(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) -{ - struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, 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; - - for (i = 0; i < frames; ++i) { - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c]] = - cpu_to_be32((*src << 8) | 0x42000000); - src++; - } - buffer += s->data_block_quadlets; - if (--remaining_frames == 0) - src = (void *)runtime->dma_area; - } -} - -static void read_pcm_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[s->pcm_positions[c]]) << 8; - dst++; - } - buffer += s->data_block_quadlets; - if (--remaining_frames == 0) - dst = (void *)runtime->dma_area; - } -} - -static void write_pcm_silence(struct amdtp_stream *s, - __be32 *buffer, unsigned int frames) -{ - unsigned int i, c; - - for (i = 0; i < frames; ++i) { - for (c = 0; c < s->pcm_channels; ++c) - buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000); - buffer += s->data_block_quadlets; - } -} - -/* - * To avoid sending MIDI bytes at too high a rate, assume that the receiving - * device has a FIFO, and track how much it is filled. This values increases - * by one whenever we send one byte in a packet, but the FIFO empties at - * a constant rate independent of our packet rate. One packet has syt_interval - * samples, so the number of bytes that empty out of the FIFO, per packet(!), - * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing - * fractional values, the values in midi_fifo_used[] are measured in bytes - * multiplied by the sample rate. - */ -static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) -{ - int used; - - used = s->midi_fifo_used[port]; - if (used == 0) /* common shortcut */ - return true; - - used -= MIDI_BYTES_PER_SECOND * s->syt_interval; - used = max(used, 0); - s->midi_fifo_used[port] = used; - - return used < s->midi_fifo_limit; -} - -static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) -{ - s->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; -} - -static void write_midi_messages(struct amdtp_stream *s, - __be32 *buffer, unsigned int frames) -{ - unsigned int f, port; - u8 *b; - - for (f = 0; f < frames; f++) { - b = (u8 *)&buffer[s->midi_position]; - - port = (s->data_block_counter + f) % 8; - if (f < MAX_MIDI_RX_BLOCKS && - midi_ratelimit_per_packet(s, port) && - s->midi[port] != NULL && - snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) { - midi_rate_use_one_byte(s, port); - b[0] = 0x81; - } else { - b[0] = 0x80; - b[1] = 0; - } - b[2] = 0; - b[3] = 0; - - buffer += s->data_block_quadlets; - } -} - -static void read_midi_messages(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->midi_position]; - - 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, - struct snd_pcm_substream *pcm, - unsigned int frames) -{ - unsigned int ptr; - - /* - * In IEC 61883-6, one data block represents one event. In ALSA, one - * event equals to one PCM frame. But Dice has a quirk to transfer - * two PCM frames in one data block. - */ - if (s->double_pcm_frames) - 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 = 0; - - if (IS_ERR(s->context)) - goto end; - - 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 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 int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks, - unsigned int syt) -{ - __be32 *buffer; - unsigned int payload_length; - struct snd_pcm_substream *pcm; - - buffer = s->buffer.packets[s->packet_index].buffer; - buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | - (s->data_block_quadlets << CIP_DBS_SHIFT) | - s->data_block_counter); - buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 | - (s->sfc << CIP_FDF_SHIFT) | syt); - buffer += 2; - - pcm = ACCESS_ONCE(s->pcm); - if (pcm) - s->transfer_samples(s, pcm, buffer, data_blocks); - else - write_pcm_silence(s, buffer, data_blocks); - if (s->midi_ports) - write_midi_messages(s, buffer, data_blocks); - - s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; - - payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; - if (queue_out_packet(s, payload_length, false) < 0) - return -EIO; - - if (pcm) - update_pcm_pointers(s, pcm, data_blocks); - - /* No need to return the number of handled data blocks. */ - return 0; -} - -static int handle_in_packet(struct amdtp_stream *s, - unsigned int payload_quadlets, __be32 *buffer, - unsigned int *data_blocks) -{ - u32 cip_header[2]; - unsigned int data_block_quadlets, data_block_counter, dbc_interval; - struct snd_pcm_substream *pcm = NULL; - bool lost; - - 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 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) || - ((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]); - *data_blocks = 0; - goto end; - } - - /* Calculate data blocks */ - if (payload_quadlets < 3 || - ((cip_header[1] & CIP_FDF_MASK) == - (AMDTP_FDF_NO_DATA << CIP_FDF_SHIFT))) { - *data_blocks = 0; - } else { - data_block_quadlets = - (cip_header[0] & CIP_DBS_MASK) >> CIP_DBS_SHIFT; - /* avoid division by zero */ - if (data_block_quadlets == 0) { - dev_err(&s->unit->device, - "Detect invalid value in dbs field: %08X\n", - cip_header[0]); - return -EPROTO; - } - if (s->flags & CIP_WRONG_DBS) - data_block_quadlets = s->data_block_quadlets; - - *data_blocks = (payload_quadlets - 2) / data_block_quadlets; - } - - /* Check data block counter continuity */ - data_block_counter = cip_header[0] & CIP_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; - } 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)) - dbc_interval = s->tx_dbc_interval; - else - dbc_interval = *data_blocks; - - lost = data_block_counter != - ((s->data_block_counter + dbc_interval) & 0xff); - } - - if (lost) { - dev_err(&s->unit->device, - "Detect discontinuity of CIP: %02X %02X\n", - s->data_block_counter, data_block_counter); - return -EIO; - } - - if (*data_blocks > 0) { - buffer += 2; - - pcm = ACCESS_ONCE(s->pcm); - if (pcm) - s->transfer_samples(s, pcm, buffer, *data_blocks); - - if (s->midi_ports) - read_midi_messages(s, buffer, *data_blocks); - } - - 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) - return -EIO; - - if (pcm) - update_pcm_pointers(s, pcm, *data_blocks); - - return 0; -} - -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, syt, packets = header_length / 4; - unsigned int data_blocks; - - if (s->packet_index < 0) - return; - - /* - * Compute the cycle of the last queued packet. - * (We need only the four lowest bits for the SYT, so we can ignore - * that bits 0-11 must wrap around at 3072.) - */ - cycle += QUEUE_LENGTH - packets; - - for (i = 0; i < packets; ++i) { - syt = calculate_syt(s, ++cycle); - data_blocks = calculate_data_blocks(s, syt); - - if (handle_out_packet(s, data_blocks, syt) < 0) { - s->packet_index = -1; - amdtp_stream_pcm_abort(s); - return; - } - } - - 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, syt, packets; - unsigned int payload_quadlets, max_payload_quadlets; - unsigned int data_blocks; - __be32 *buffer, *headers = header; - - if (s->packet_index < 0) - return; - - /* The number of packets in buffer */ - packets = header_length / IN_PACKET_HEADER_SIZE; - - /* For buffer-over-run prevention. */ - max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4; - - for (p = 0; p < packets; p++) { - 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; - if (payload_quadlets > max_payload_quadlets) { - dev_err(&s->unit->device, - "Detect jumbo payload: %02x %02x\n", - payload_quadlets, max_payload_quadlets); - s->packet_index = -1; - break; - } - - if (handle_in_packet(s, payload_quadlets, buffer, - &data_blocks) < 0) { - s->packet_index = -1; - break; - } - - /* Process sync slave stream */ - if (s->sync_slave && s->sync_slave->callbacked) { - syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK; - if (handle_out_packet(s->sync_slave, - data_blocks, syt) < 0) { - s->packet_index = -1; - break; - } - } - } - - /* Queueing error or detecting discontinuity */ - if (s->packet_index < 0) { - amdtp_stream_pcm_abort(s); - - /* 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_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 - * @channel: the isochronous channel on the bus - * @speed: firewire speed code - * - * The stream cannot be started until it has been configured with - * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI - * device can be started. - */ -int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed) -{ - static const struct { - unsigned int data_block; - unsigned int syt_offset; - } initial_state[] = { - [CIP_SFC_32000] = { 4, 3072 }, - [CIP_SFC_48000] = { 6, 1024 }, - [CIP_SFC_96000] = { 12, 1024 }, - [CIP_SFC_192000] = { 24, 1024 }, - [CIP_SFC_44100] = { 0, 67 }, - [CIP_SFC_88200] = { 0, 67 }, - [CIP_SFC_176400] = { 0, 67 }, - }; - unsigned int header_size; - enum dma_data_direction dir; - int type, tag, err; - - mutex_lock(&s->mutex); - - if (WARN_ON(amdtp_stream_running(s) || - (s->data_block_quadlets < 1))) { - err = -EBADFD; - goto err_unlock; - } - - 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; - - /* initialize packet buffer */ - if (s->direction == AMDTP_IN_STREAM) { - dir = DMA_FROM_DEVICE; - type = FW_ISO_CONTEXT_RECEIVE; - header_size = IN_PACKET_HEADER_SIZE; - } else { - dir = DMA_TO_DEVICE; - type = FW_ISO_CONTEXT_TRANSMIT; - header_size = OUT_PACKET_HEADER_SIZE; - } - err = iso_packets_buffer_init(&s->buffer, s->unit, QUEUE_LENGTH, - 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, - type, channel, speed, header_size, - amdtp_stream_first_callback, s); - if (IS_ERR(s->context)) { - err = PTR_ERR(s->context); - if (err == -EBUSY) - dev_err(&s->unit->device, - "no free stream on this controller\n"); - goto err_buffer; - } - - amdtp_stream_update(s); - - s->packet_index = 0; - do { - 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); - - /* 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, tag); - if (err < 0) - goto err_context; - - mutex_unlock(&s->mutex); - - return 0; - -err_context: - fw_iso_context_destroy(s->context); - s->context = ERR_PTR(-1); -err_buffer: - iso_packets_buffer_destroy(&s->buffer, s->unit); -err_unlock: - mutex_unlock(&s->mutex); - - return err; -} -EXPORT_SYMBOL(amdtp_stream_start); - -/** - * 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_stream_pcm_pointer(struct amdtp_stream *s) -{ - /* this optimization is allowed to be racy */ - if (s->pointer_flush && amdtp_stream_running(s)) - fw_iso_context_flush_completions(s->context); - else - s->pointer_flush = true; - - return ACCESS_ONCE(s->pcm_buffer_pointer); -} -EXPORT_SYMBOL(amdtp_stream_pcm_pointer); - -/** - * amdtp_stream_update - update the stream after a bus reset - * @s: the AMDTP stream - */ -void amdtp_stream_update(struct amdtp_stream *s) -{ - /* Precomputing. */ - ACCESS_ONCE(s->source_node_id_field) = - (fw_parent_device(s->unit)->card->node_id << CIP_SID_SHIFT) & - CIP_SID_MASK; -} -EXPORT_SYMBOL(amdtp_stream_update); - -/** - * 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_stream_stop(struct amdtp_stream *s) -{ - mutex_lock(&s->mutex); - - if (!amdtp_stream_running(s)) { - mutex_unlock(&s->mutex); - return; - } - - tasklet_kill(&s->period_tasklet); - fw_iso_context_stop(s->context); - fw_iso_context_destroy(s->context); - 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); - -/** - * 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_stream_pcm_abort(struct amdtp_stream *s) -{ - struct snd_pcm_substream *pcm; - - pcm = ACCESS_ONCE(s->pcm); - if (pcm) - snd_pcm_stop_xrun(pcm); -} -EXPORT_SYMBOL(amdtp_stream_pcm_abort); diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h deleted file mode 100644 index 26b9093..0000000 --- a/sound/firewire/amdtp.h +++ /dev/null @@ -1,296 +0,0 @@ -#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED -#define SOUND_FIREWIRE_AMDTP_H_INCLUDED - -#include <linux/err.h> -#include <linux/interrupt.h> -#include <linux/mutex.h> -#include <sound/asound.h> -#include "packets-buffer.h" - -/** - * 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 - * be used if supported by the device. - * @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_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. - * @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. - * @CIP_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. - * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty - * packet is wrong but the others are correct. - * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an - * packet is larger than IEC 61883-6 defines. Current implementation - * allows 5 times as large as IEC 61883-6 defines. - */ -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, - CIP_WRONG_DBS = 0x10, - CIP_SKIP_DBC_ZERO_CHECK = 0x20, - CIP_SKIP_INIT_DBC_CHECK = 0x40, - CIP_EMPTY_HAS_WRONG_DBC = 0x80, - CIP_JUMBO_PAYLOAD = 0x100, -}; - -/** - * enum cip_sfc - supported Sampling Frequency Codes (SFCs) - * @CIP_SFC_32000: 32,000 data blocks - * @CIP_SFC_44100: 44,100 data blocks - * @CIP_SFC_48000: 48,000 data blocks - * @CIP_SFC_88200: 88,200 data blocks - * @CIP_SFC_96000: 96,000 data blocks - * @CIP_SFC_176400: 176,400 data blocks - * @CIP_SFC_192000: 192,000 data blocks - * @CIP_SFC_COUNT: the number of supported SFCs - * - * These values are used to show nominal Sampling Frequency Code in - * Format Dependent Field (FDF) of AMDTP packet header. In IEC 61883-6:2002, - * this code means the number of events per second. Actually the code - * represents the number of data blocks transferred per second in an AMDTP - * stream. - * - * In IEC 61883-6:2005, some extensions were added to support more types of - * data such as 'One Bit LInear Audio', therefore the meaning of SFC became - * different depending on the types. - * - * Currently our implementation is compatible with IEC 61883-6:2002. - */ -enum cip_sfc { - CIP_SFC_32000 = 0, - CIP_SFC_44100 = 1, - CIP_SFC_48000 = 2, - CIP_SFC_88200 = 3, - CIP_SFC_96000 = 4, - CIP_SFC_176400 = 5, - CIP_SFC_192000 = 6, - 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) - - -/* - * 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. - * - * 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_pcm_runtime; -struct snd_rawmidi_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; - - enum cip_sfc sfc; - unsigned int data_block_quadlets; - unsigned int pcm_channels; - unsigned int midi_ports; - 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; - unsigned int source_node_id_field; - struct iso_packets_buffer buffer; - - struct snd_pcm_substream *pcm; - struct tasklet_struct period_tasklet; - - int packet_index; - unsigned int data_block_counter; - - unsigned int data_block_state; - - unsigned int last_syt_offset; - unsigned int syt_offset_state; - - unsigned int pcm_buffer_pointer; - unsigned int pcm_period_pointer; - bool pointer_flush; - bool double_pcm_frames; - - struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; - int midi_fifo_limit; - int midi_fifo_used[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; -}; - -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); - -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_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); -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 - * @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_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_streaming_error(struct amdtp_stream *s) -{ - return s->packet_index < 0; -} - -/** - * 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 - * - * 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_stream_pcm_trigger(struct amdtp_stream *s, - struct snd_pcm_substream *pcm) -{ - 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; -} - -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 diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index d23caca..72a1c5e 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -31,7 +31,7 @@ #include "../fcp.h" #include "../packets-buffer.h" #include "../iso-resources.h" -#include "../amdtp.h" +#include "../amdtp-stream.h" #include "../cmp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */ diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index ecf5dc8..29578c1 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -34,7 +34,7 @@ #include <sound/pcm_params.h> #include <sound/rawmidi.h>
-#include "../amdtp.h" +#include "../amdtp-stream.h" #include "../iso-resources.h" #include "../lib.h" #include "dice-interface.h" diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index 0619597..cce1976 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -17,7 +17,7 @@ #include <linux/delay.h> #include "fcp.h" #include "lib.h" -#include "amdtp.h" +#include "amdtp-stream.h"
#define CTS_AVC 0x00
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index 4f0201a..f6f9ae6 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -29,7 +29,7 @@
#include "../packets-buffer.h" #include "../iso-resources.h" -#include "../amdtp.h" +#include "../amdtp-stream.h" #include "../cmp.h" #include "../lib.h"
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index cace5ad..2c3d20b 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -28,7 +28,7 @@ #include "../fcp.h" #include "../packets-buffer.h" #include "../iso-resources.h" -#include "../amdtp.h" +#include "../amdtp-stream.h" #include "../cmp.h"
struct device_info {
There're some models which is not compliant to IEC 61883-6. Such devices use its own data format, while they're applications of Audio. This means that they use the same way for data transmission rate as IEC 61883-6, especially one data block represents one event and the number of transferred data blocks in a second is the same as sampling frequency. On the other hand, these models don't use AM824 format for its data blocks.
Supporting these models requires transmittion layer and packetization layer. This commit implements this idea to split current amdtp layer to 'stream' and 'am824' layer, and allows each driver to implement own packetization layer.
I note about the overhead which this commit takes. The streaming layer calls protocol layer every packetization. Therefore, the frequency is 8,000 times per second. This makes additional 8,000 function calls for AMDTP/AM824 processing.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Makefile | 2 +- sound/firewire/amdtp-am824.c | 430 ++++++++++++++++++++++++++++ sound/firewire/amdtp-am824.h | 45 +++ sound/firewire/amdtp-stream.c | 373 ++++-------------------- sound/firewire/amdtp-stream.h | 105 ++----- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_midi.c | 16 +- sound/firewire/bebob/bebob_pcm.c | 10 +- sound/firewire/bebob/bebob_stream.c | 34 ++- sound/firewire/dice/dice-midi.c | 16 +- sound/firewire/dice/dice-pcm.c | 10 +- sound/firewire/dice/dice-stream.c | 41 +-- sound/firewire/dice/dice.h | 2 +- sound/firewire/fireworks/fireworks.c | 12 +- sound/firewire/fireworks/fireworks.h | 2 +- sound/firewire/fireworks/fireworks_midi.c | 16 +- sound/firewire/fireworks/fireworks_pcm.c | 8 +- sound/firewire/fireworks/fireworks_stream.c | 8 +- sound/firewire/oxfw/oxfw-midi.c | 16 +- sound/firewire/oxfw/oxfw-pcm.c | 8 +- sound/firewire/oxfw/oxfw-stream.c | 11 +- sound/firewire/oxfw/oxfw.h | 2 +- 22 files changed, 682 insertions(+), 487 deletions(-) create mode 100644 sound/firewire/amdtp-am824.c create mode 100644 sound/firewire/amdtp-am824.h
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 102e342..6a8a713 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -1,5 +1,5 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ - fcp.o cmp.o amdtp-stream.o + fcp.o cmp.o amdtp-stream.o amdtp-am824.o snd-oxfw-objs := oxfw.o snd-isight-objs := isight.o snd-scs1x-objs := scs1x.o diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c new file mode 100644 index 0000000..37541bd --- /dev/null +++ b/sound/firewire/amdtp-am824.c @@ -0,0 +1,430 @@ +/* + * AM824 format in Audio and Music Data Transmission Protocol (IEC 61883-6) + * + * Copyright (c) Clemens Ladisch clemens@ladisch.de + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/rawmidi.h> +#include "amdtp-am824.h" + +#define CIP_FMT_AM 0x10 +#define AMDTP_FDF_AM824 0x00 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +struct amdtp_am824 { + struct snd_rawmidi_substream *midi[AM824_MAX_CHANNELS_FOR_MIDI * 8]; + int midi_fifo_limit; + int midi_fifo_used[AM824_MAX_CHANNELS_FOR_MIDI * 8]; + unsigned int pcm_channels; + unsigned int midi_ports; + + u8 pcm_positions[AM824_MAX_CHANNELS_FOR_PCM]; + u8 midi_position; + + void (*transfer_samples)(struct amdtp_stream *s, + struct snd_pcm_runtime *pcm, + __be32 *buffer, unsigned int frames); + + /* + * In IEC 61883-6, one data block represents one event. In ALSA, one + * event equals to one PCM frame. But Dice has a quirk to transfer + * two PCM frames in one data block. + */ + unsigned int frame_multiplier; +}; + +/** + * amdtp_am824_set_parameters - set stream parameters + * @s: the CIP 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 + * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) + * + * The parameters must be set before the stream is started, and must not be + * changed while the stream is running. + */ +int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports, + bool double_pcm_frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int midi_channels; + unsigned int i; + int err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + if (double_pcm_frames) + p->frame_multiplier = 2; + else + p->frame_multiplier = 1; + + if (pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) + return -EINVAL; + + midi_channels = DIV_ROUND_UP(midi_ports, 8); + if (midi_channels > AM824_MAX_CHANNELS_FOR_MIDI) + return -EINVAL; + + err = amdtp_stream_set_parameters(s, rate, + pcm_channels + midi_channels); + if (err < 0) + return err; + + s->fdf = AMDTP_FDF_AM824 | s->sfc; + + p->pcm_channels = pcm_channels; + p->midi_ports = midi_ports; + + /* init the position map for PCM and MIDI channels */ + for (i = 0; i < pcm_channels; i++) + p->pcm_positions[i] = i; + p->midi_position = pcm_channels; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + return 0; +} +EXPORT_SYMBOL(amdtp_am824_set_parameters); + +void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index, + unsigned int position) +{ + struct amdtp_am824 *p = s->protocol; + + if (index < p->pcm_channels) + p->pcm_positions[index] = position; +} +EXPORT_SYMBOL(amdtp_am824_set_pcm_position); + +void amdtp_am824_set_midi_position(struct amdtp_stream *s, + unsigned int position) +{ + struct amdtp_am824 *p = s->protocol; + + p->midi_position = position; +} +EXPORT_SYMBOL(amdtp_am824_set_midi_position); + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (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) { + buffer[p->pcm_positions[c]] = + cpu_to_be32((*src >> 8) | 0x40000000); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + const u16 *src; + + channels = p->pcm_channels; + src = (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) { + buffer[p->pcm_positions[c]] = + cpu_to_be32((*src << 8) | 0x42000000); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->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[p->pcm_positions[c]]) << 8; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[p->pcm_positions[c]] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +/* + * To avoid sending MIDI bytes at too high a rate, assume that the receiving + * device has a FIFO, and track how much it is filled. This values increases + * by one whenever we send one byte in a packet, but the FIFO empties at + * a constant rate independent of our packet rate. One packet has syt_interval + * samples, so the number of bytes that empty out of the FIFO, per packet(!), + * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing + * fractional values, the values in midi_fifo_used[] are measured in bytes + * multiplied by the sample rate. + */ +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_am824 *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) /* common shortcut */ + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_am824 *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int f, port; + u8 *b; + + for (f = 0; f < frames; f++) { + b = (u8 *)&buffer[p->midi_position]; + + port = (s->data_block_counter + f) % 8; + if (f < MAX_MIDI_RX_BLOCKS && + midi_ratelimit_per_packet(s, port) && + p->midi[port] != NULL && + snd_rawmidi_transmit(p->midi[port], &b[1], 1) == 1) { + midi_rate_use_one_byte(s, port); + b[0] = 0x81; + } else { + b[0] = 0x80; + b[1] = 0; + } + b[2] = 0; + b[3] = 0; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int f, port; + int len; + u8 *b; + + for (f = 0; f < frames; f++) { + port = (s->data_block_counter + f) % 8; + b = (u8 *)&buffer[p->midi_position]; + + len = b[0] - 0x80; + if ((1 <= len) && (len <= 3) && (p->midi[port])) + snd_rawmidi_receive(p->midi[port], b + 1, len); + + buffer += s->data_block_quadlets; + } +} + +/** + * amdtp_am824_set_pcm_format - set the PCM format + * @s: the CIP stream to configure + * @format: the format of the ALSA PCM device + * + * The sample format must be set after the other parameters (rate/PCM channels/ + * MIDI) and before the stream is started, and must not be changed while the + * stream is running. + */ +void amdtp_am824_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) +{ + struct amdtp_am824 *p = s->protocol; + + if (WARN_ON(amdtp_stream_pcm_running(s))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + if (s->direction == AMDTP_OUT_STREAM) { + p->transfer_samples = write_pcm_s16; + break; + } + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S32: + if (s->direction == AMDTP_OUT_STREAM) + p->transfer_samples = write_pcm_s32; + else + p->transfer_samples = read_pcm_s32; + break; + } +} +EXPORT_SYMBOL(amdtp_am824_set_pcm_format); + +/** + * amdtp_am824_midi_trigger - start/stop playback/capture with a MIDI device + * @s: the CIP 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. + */ +void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_am824 *p = s->protocol; + + if (port < p->midi_ports) + ACCESS_ONCE(p->midi[port]) = midi; +} +EXPORT_SYMBOL(amdtp_am824_midi_trigger); + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_am824 *p = (struct amdtp_am824 *)s->protocol; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) { + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + return pcm_frames * p->frame_multiplier; +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_am824 *p = (struct amdtp_am824 *)s->protocol; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) { + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, buffer, data_blocks); + pcm_frames = 0; + } + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + return pcm_frames * p->frame_multiplier; +} + +int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags) +{ + struct amdtp_am824 *p; + int err; + + err = amdtp_stream_init(s, unit, dir, flags, + sizeof(struct amdtp_am824)); + if (err < 0) + return err; + + s->fmt = CIP_FMT_AM; + + if (dir == AMDTP_IN_STREAM) + s->process_data_blocks = process_tx_data_blocks; + else + s->process_data_blocks = process_rx_data_blocks; + + p = s->protocol; + p->frame_multiplier = 1; + + return 0; +} +EXPORT_SYMBOL(amdtp_am824_init); diff --git a/sound/firewire/amdtp-am824.h b/sound/firewire/amdtp-am824.h new file mode 100644 index 0000000..70c6d71 --- /dev/null +++ b/sound/firewire/amdtp-am824.h @@ -0,0 +1,45 @@ +#ifndef SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED + +#include "amdtp-stream.h" + +#define AM824_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32 + +#define AM824_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \ + SNDRV_PCM_FMTBIT_S32) + +/* + * This module supports maximum 64 PCM channels for one PCM stream + * This is for our convenience. + */ +#define AM824_MAX_CHANNELS_FOR_PCM 64 + +/* + * AM824 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 AM824 packets can transfer maximum 8 MIDI data streams. + */ +#define AM824_MAX_CHANNELS_FOR_MIDI 1 + +int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports, + bool double_pcm_frames); + +void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index, + unsigned int position); +void amdtp_am824_set_midi_position(struct amdtp_stream *s, + unsigned int position); + +void amdtp_am824_set_pcm_format(struct amdtp_stream *s, + snd_pcm_format_t format); + +void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags); +#endif diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 9965984..c7fbdd3 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -11,7 +11,6 @@ #include <linux/firewire.h> #include <linux/module.h> #include <linux/slab.h> -#include <linux/sched.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/rawmidi.h> @@ -21,18 +20,6 @@ #define CYCLES_PER_SECOND 8000 #define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
-/* - * Nominally 3125 bytes/second, but the MIDI port's clock might be - * 1% too slow, and the bus clock 100 ppm too fast. - */ -#define MIDI_BYTES_PER_SECOND 3093 - -/* - * Several devices look only at the first eight data blocks. - * In any case, this is more than enough for the MIDI data rate. - */ -#define MAX_MIDI_RX_BLOCKS 8 - #define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */
/* isochronous header parameters */ @@ -78,17 +65,23 @@ static void pcm_period_tasklet(unsigned long data); * @unit: the target of the stream * @dir: the direction of stream * @flags: the packet transmission method to use + * @protocol_size: the size to allocate newly for protocol */ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, - enum amdtp_stream_direction dir, enum cip_flags flags) + enum amdtp_stream_direction dir, enum cip_flags flags, + unsigned int protocol_size) { + s->protocol = kzalloc(protocol_size, GFP_KERNEL); + if (!s->protocol) + return -ENOMEM; + s->unit = unit; s->direction = dir; s->flags = flags; - s->context = ERR_PTR(-1); mutex_init(&s->mutex); - tasklet_init(&s->period_tasklet, pcm_period_tasklet, (unsigned long)s); + s->context = ERR_PTR(-1); s->packet_index = 0; + tasklet_init(&s->period_tasklet, pcm_period_tasklet, (unsigned long)s);
init_waitqueue_head(&s->callback_wait); s->callbacked = false; @@ -106,6 +99,7 @@ void amdtp_stream_destroy(struct amdtp_stream *s) { WARN_ON(amdtp_stream_running(s)); mutex_destroy(&s->mutex); + kfree(s->protocol); } EXPORT_SYMBOL(amdtp_stream_destroy);
@@ -141,11 +135,6 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, { 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 @@ -190,59 +179,35 @@ 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 - * @pcm_channels: the number of PCM samples in each data block, to be encoded - * as AM824 multi-bit linear audio - * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) + * @data_block_quadlets: the number of quadlets in a data block * * The parameters must be set before the stream is started, and must not be * changed while the stream is running. */ -void amdtp_stream_set_parameters(struct amdtp_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports) +int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int data_block_quadlets) { - unsigned int i, sfc, midi_channels; + unsigned int sfc;
- midi_channels = DIV_ROUND_UP(midi_ports, 8); - - 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; - - for (sfc = 0; sfc < ARRAY_SIZE(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); - return; + break; + } + if (sfc == ARRAY_SIZE(amdtp_rate_table)) + return -EINVAL;
-sfc_found: - s->pcm_channels = pcm_channels; + s->data_block_quadlets = data_block_quadlets; s->sfc = sfc; - s->data_block_quadlets = s->pcm_channels + midi_channels; - s->midi_ports = midi_ports; - s->syt_interval = amdtp_syt_intervals[sfc];
/* default buffering in the device */ s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; - if (s->flags & CIP_BLOCKING) + 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; - - /* - * We do not know the actual MIDI FIFO size of most devices. Just - * assume two bytes, i.e., one byte can be received over the bus while - * the previous one is transmitted over MIDI. - * (The value here is adjusted for midi_ratelimit_per_packet().) - */ - s->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + return 0; } EXPORT_SYMBOL(amdtp_stream_set_parameters);
@@ -264,52 +229,6 @@ unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) } EXPORT_SYMBOL(amdtp_stream_get_max_payload);
-static void write_pcm_s16(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); -static void write_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); -static void read_pcm_s32(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames); - -/** - * 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 parameters (rate/PCM channels/ - * MIDI) and before the stream is started, and must not be changed while the - * stream is running. - */ -void amdtp_stream_set_pcm_format(struct amdtp_stream *s, - snd_pcm_format_t format) -{ - if (WARN_ON(amdtp_stream_pcm_running(s))) - return; - - switch (format) { - default: - WARN_ON(1); - /* fall through */ - case SNDRV_PCM_FORMAT_S16: - if (s->direction == AMDTP_OUT_STREAM) { - s->transfer_samples = write_pcm_s16; - break; - } - WARN_ON(1); - /* fall through */ - case SNDRV_PCM_FORMAT_S32: - if (s->direction == AMDTP_OUT_STREAM) - s->transfer_samples = write_pcm_s32; - else - s->transfer_samples = read_pcm_s32; - break; - } -} -EXPORT_SYMBOL(amdtp_stream_set_pcm_format); - /** * amdtp_stream_pcm_prepare - prepare PCM device for running * @s: the AMDTP stream @@ -412,182 +331,12 @@ static unsigned int calculate_syt(struct amdtp_stream *s, } }
-static void write_pcm_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; - 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; - - for (i = 0; i < frames; ++i) { - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c]] = - cpu_to_be32((*src >> 8) | 0x40000000); - src++; - } - buffer += s->data_block_quadlets; - if (--remaining_frames == 0) - src = (void *)runtime->dma_area; - } -} - -static void write_pcm_s16(struct amdtp_stream *s, - struct snd_pcm_substream *pcm, - __be32 *buffer, unsigned int frames) -{ - struct snd_pcm_runtime *runtime = pcm->runtime; - unsigned int channels, remaining_frames, 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; - - for (i = 0; i < frames; ++i) { - for (c = 0; c < channels; ++c) { - buffer[s->pcm_positions[c]] = - cpu_to_be32((*src << 8) | 0x42000000); - src++; - } - buffer += s->data_block_quadlets; - if (--remaining_frames == 0) - src = (void *)runtime->dma_area; - } -} - -static void read_pcm_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[s->pcm_positions[c]]) << 8; - dst++; - } - buffer += s->data_block_quadlets; - if (--remaining_frames == 0) - dst = (void *)runtime->dma_area; - } -} - -static void write_pcm_silence(struct amdtp_stream *s, - __be32 *buffer, unsigned int frames) -{ - unsigned int i, c; - - for (i = 0; i < frames; ++i) { - for (c = 0; c < s->pcm_channels; ++c) - buffer[s->pcm_positions[c]] = cpu_to_be32(0x40000000); - buffer += s->data_block_quadlets; - } -} - -/* - * To avoid sending MIDI bytes at too high a rate, assume that the receiving - * device has a FIFO, and track how much it is filled. This values increases - * by one whenever we send one byte in a packet, but the FIFO empties at - * a constant rate independent of our packet rate. One packet has syt_interval - * samples, so the number of bytes that empty out of the FIFO, per packet(!), - * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing - * fractional values, the values in midi_fifo_used[] are measured in bytes - * multiplied by the sample rate. - */ -static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) -{ - int used; - - used = s->midi_fifo_used[port]; - if (used == 0) /* common shortcut */ - return true; - - used -= MIDI_BYTES_PER_SECOND * s->syt_interval; - used = max(used, 0); - s->midi_fifo_used[port] = used; - - return used < s->midi_fifo_limit; -} - -static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) -{ - s->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; -} - -static void write_midi_messages(struct amdtp_stream *s, - __be32 *buffer, unsigned int frames) -{ - unsigned int f, port; - u8 *b; - - for (f = 0; f < frames; f++) { - b = (u8 *)&buffer[s->midi_position]; - - port = (s->data_block_counter + f) % 8; - if (f < MAX_MIDI_RX_BLOCKS && - midi_ratelimit_per_packet(s, port) && - s->midi[port] != NULL && - snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) { - midi_rate_use_one_byte(s, port); - b[0] = 0x81; - } else { - b[0] = 0x80; - b[1] = 0; - } - b[2] = 0; - b[3] = 0; - - buffer += s->data_block_quadlets; - } -} - -static void read_midi_messages(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->midi_position]; - - 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, struct snd_pcm_substream *pcm, unsigned int frames) { unsigned int ptr;
- /* - * In IEC 61883-6, one data block represents one event. In ALSA, one - * event equals to one PCM frame. But Dice has a quirk to transfer - * two PCM frames in one data block. - */ - if (s->double_pcm_frames) - frames *= 2; - ptr = s->pcm_buffer_pointer + frames; if (ptr >= pcm->runtime->buffer_size) ptr -= pcm->runtime->buffer_size; @@ -656,32 +405,31 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks, { __be32 *buffer; unsigned int payload_length; + unsigned int pcm_frames; struct snd_pcm_substream *pcm;
+ /* CIP processing. */ buffer = s->buffer.packets[s->packet_index].buffer; + pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | s->data_block_counter); - buffer[1] = cpu_to_be32(CIP_EOH | CIP_FMT_AM | AMDTP_FDF_AM824 | - (s->sfc << CIP_FDF_SHIFT) | syt); - buffer += 2; - - pcm = ACCESS_ONCE(s->pcm); - if (pcm) - s->transfer_samples(s, pcm, buffer, data_blocks); - else - write_pcm_silence(s, buffer, data_blocks); - if (s->midi_ports) - write_midi_messages(s, buffer, data_blocks); - - s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + buffer[1] = cpu_to_be32(CIP_EOH | + ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | + ((s->fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | + (syt & CIP_SYT_MASK));
payload_length = 8 + data_blocks * 4 * s->data_block_quadlets; if (queue_out_packet(s, payload_length, false) < 0) return -EIO;
- if (pcm) - update_pcm_pointers(s, pcm, data_blocks); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + /* PCM runtime processing. */ + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames);
/* No need to return the number of handled data blocks. */ return 0; @@ -692,31 +440,39 @@ static int handle_in_packet(struct amdtp_stream *s, unsigned int *data_blocks) { u32 cip_header[2]; + unsigned int fmt, fdf, syt; unsigned int data_block_quadlets, data_block_counter, dbc_interval; - struct snd_pcm_substream *pcm = NULL; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; bool lost;
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 convenience, also check FMT field is AM824 or not. - */ + /* This module supports 'Two-quadlet CIP header with SYT field'. */ 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)) { + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) { dev_info_ratelimited(&s->unit->device, - "Invalid CIP header for AMDTP: %08X:%08X\n", + "Invalid CIP header: %08X:%08X\n", cip_header[0], cip_header[1]); *data_blocks = 0; + pcm_frames = 0; goto end; }
+ /* Check valid protocol or not. */ + fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; + if (fmt != s->fmt) { + dev_err(&s->unit->device, + "Detect unexpected protocol: %08x %08x\n", + cip_header[0], cip_header[1]); + return -EIO; + } + /* Calculate data blocks */ + fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; if (payload_quadlets < 3 || - ((cip_header[1] & CIP_FDF_MASK) == - (AMDTP_FDF_NO_DATA << CIP_FDF_SHIFT))) { + (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { *data_blocks = 0; } else { data_block_quadlets = @@ -762,16 +518,9 @@ static int handle_in_packet(struct amdtp_stream *s, return -EIO; }
- if (*data_blocks > 0) { - buffer += 2; - - pcm = ACCESS_ONCE(s->pcm); - if (pcm) - s->transfer_samples(s, pcm, buffer, *data_blocks); - - if (s->midi_ports) - read_midi_messages(s, buffer, *data_blocks); - } + /* CIP processing. */ + syt = cip_header[1] & CIP_SYT_MASK; + pcm_frames = s->process_data_blocks(s, buffer + 2, *data_blocks, &syt);
if (s->flags & CIP_DBC_IS_END_EVENT) s->data_block_counter = data_block_counter; @@ -782,8 +531,10 @@ end: if (queue_in_packet(s) < 0) return -EIO;
- if (pcm) - update_pcm_pointers(s, pcm, *data_blocks); + /* PCM runtime processing. */ + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames);
return 0; } diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index f7a6f5f..d2354f0 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -4,6 +4,7 @@ #include <linux/err.h> #include <linux/interrupt.h> #include <linux/mutex.h> +#include <linux/sched.h> #include <sound/asound.h> #include "packets-buffer.h"
@@ -80,28 +81,6 @@ 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) - - -/* - * 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. - * - * 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; @@ -117,61 +96,57 @@ struct amdtp_stream { struct fw_unit *unit; enum cip_flags flags; enum amdtp_stream_direction direction; - struct fw_iso_context *context; struct mutex mutex;
- enum cip_sfc sfc; - unsigned int data_block_quadlets; - unsigned int pcm_channels; - unsigned int midi_ports; - 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; - unsigned int source_node_id_field; + /* For packet processing. */ + struct fw_iso_context *context; struct iso_packets_buffer buffer; - - struct snd_pcm_substream *pcm; - struct tasklet_struct period_tasklet; - int packet_index; + + /* For CIP headers. */ + unsigned int source_node_id_field; + unsigned int data_block_quadlets; unsigned int data_block_counter; + unsigned int fmt; + unsigned int fdf; + /* quirk: fixed interval of dbc between previos/current packets. */ + unsigned int tx_dbc_interval;
+ /* Internal flags. */ + enum cip_sfc sfc; + unsigned int syt_interval; + unsigned int transfer_delay; unsigned int data_block_state; - unsigned int last_syt_offset; unsigned int syt_offset_state;
+ /* For backends to process data blocks. */ + void *protocol; + unsigned int (*process_data_blocks)(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt); + + /* For one PCM runtime processing. */ + struct snd_pcm_substream *pcm; + struct tasklet_struct period_tasklet; unsigned int pcm_buffer_pointer; unsigned int pcm_period_pointer; bool pointer_flush; - bool double_pcm_frames; - - struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; - int midi_fifo_limit; - int midi_fifo_used[AMDTP_MAX_CHANNELS_FOR_MIDI * 8]; - - /* quirk: fixed interval of dbc between previos/current packets. */ - unsigned int tx_dbc_interval;
+ /* To wait for first packet. */ 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, - enum amdtp_stream_direction dir, - enum cip_flags flags); + enum amdtp_stream_direction dir, enum cip_flags flags, + unsigned int protocol_size); void amdtp_stream_destroy(struct amdtp_stream *s);
-void amdtp_stream_set_parameters(struct amdtp_stream *s, - unsigned int rate, - unsigned int pcm_channels, - unsigned int midi_ports); +int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int data_block_quadlets); unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s);
int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed); @@ -180,8 +155,6 @@ 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); unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s); void amdtp_stream_pcm_abort(struct amdtp_stream *s); @@ -238,24 +211,6 @@ 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; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 72a1c5e..d3c9d8d 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -31,7 +31,7 @@ #include "../fcp.h" #include "../packets-buffer.h" #include "../iso-resources.h" -#include "../amdtp-stream.h" +#include "../amdtp-am824.h" #include "../cmp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */ diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c index 5681143..9444ad4 100644 --- a/sound/firewire/bebob/bebob_midi.c +++ b/sound/firewire/bebob/bebob_midi.c @@ -72,11 +72,11 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&bebob->lock, flags);
if (up) - amdtp_stream_midi_trigger(&bebob->tx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&bebob->tx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&bebob->tx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&bebob->tx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&bebob->lock, flags); } @@ -89,11 +89,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&bebob->lock, flags);
if (up) - amdtp_stream_midi_trigger(&bebob->rx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&bebob->rx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&bebob->rx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&bebob->rx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&bebob->lock, flags); } diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c index 7a2c1f5..6c7c05f 100644 --- a/sound/firewire/bebob/bebob_pcm.c +++ b/sound/firewire/bebob/bebob_pcm.c @@ -122,11 +122,11 @@ pcm_init_hw_params(struct snd_bebob *bebob, SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; s = &bebob->tx_stream; formations = bebob->tx_stream_formations; } else { - runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; s = &bebob->rx_stream; formations = bebob->rx_stream_formations; } @@ -214,8 +214,7 @@ pcm_capture_hw_params(struct snd_pcm_substream *substream,
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) atomic_inc(&bebob->substreams_counter); - amdtp_stream_set_pcm_format(&bebob->tx_stream, - params_format(hw_params)); + amdtp_am824_set_pcm_format(&bebob->tx_stream, params_format(hw_params)); return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); } @@ -227,8 +226,7 @@ pcm_playback_hw_params(struct snd_pcm_substream *substream,
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) atomic_inc(&bebob->substreams_counter); - amdtp_stream_set_pcm_format(&bebob->rx_stream, - params_format(hw_params)); + amdtp_am824_set_pcm_format(&bebob->rx_stream, params_format(hw_params)); return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); } diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 5be5242..f3ab9e2 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -333,12 +333,12 @@ map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) switch (type) { /* for MIDI conformant data channel */ case 0x0a: - /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */ + /* AM824_MAX_CHANNELS_FOR_MIDI is 1. */ if ((midi > 0) && (stm_pos != midi)) { err = -ENOSYS; goto end; } - s->midi_position = stm_pos; + amdtp_am824_set_midi_position(s, stm_pos); midi = stm_pos; break; /* for PCM data channel */ @@ -354,11 +354,12 @@ map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) case 0x09: /* Digital */ default: location = pcm + sec_loc; - if (location >= AMDTP_MAX_CHANNELS_FOR_PCM) { + if (location >= AM824_MAX_CHANNELS_FOR_PCM) { err = -ENOSYS; goto end; } - s->pcm_positions[location] = stm_pos; + amdtp_am824_set_pcm_position(s, + location, stm_pos); break; } } @@ -427,12 +428,17 @@ make_both_connections(struct snd_bebob *bebob, unsigned int rate) 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); + err = amdtp_am824_set_parameters(&bebob->tx_stream, rate, + pcm_channels, midi_channels * 8, false); + if (err < 0) + goto end; + 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); + err = amdtp_am824_set_parameters(&bebob->rx_stream, rate, + pcm_channels, midi_channels * 8, false); + if (err < 0) + goto end;
/* establish connections for both streams */ err = cmp_connection_establish(&bebob->out_conn, @@ -530,8 +536,8 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) if (err < 0) goto end;
- err = amdtp_stream_init(&bebob->tx_stream, bebob->unit, - AMDTP_IN_STREAM, CIP_BLOCKING); + err = amdtp_am824_init(&bebob->tx_stream, bebob->unit, AMDTP_IN_STREAM, + CIP_BLOCKING); if (err < 0) { amdtp_stream_destroy(&bebob->tx_stream); destroy_both_connections(bebob); @@ -559,8 +565,8 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) 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); + err = amdtp_am824_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); @@ -864,8 +870,8 @@ parse_stream_formation(u8 *buf, unsigned int len, } }
- if (formation[i].pcm > AMDTP_MAX_CHANNELS_FOR_PCM || - formation[i].midi > AMDTP_MAX_CHANNELS_FOR_MIDI) + if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI) return -ENOSYS;
return 0; diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c index fe43ce7..5f97dda 100644 --- a/sound/firewire/dice/dice-midi.c +++ b/sound/firewire/dice/dice-midi.c @@ -52,11 +52,11 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&dice->lock, flags);
if (up) - amdtp_stream_midi_trigger(&dice->tx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&dice->tx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&dice->tx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&dice->tx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&dice->lock, flags); } @@ -69,11 +69,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&dice->lock, flags);
if (up) - amdtp_stream_midi_trigger(&dice->rx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&dice->rx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&dice->rx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&dice->rx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&dice->lock, flags); } diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c index f7771451..fd0b7a3 100644 --- a/sound/firewire/dice/dice-pcm.c +++ b/sound/firewire/dice/dice-pcm.c @@ -133,11 +133,11 @@ static int init_hw_info(struct snd_dice *dice, SNDRV_PCM_INFO_BLOCK_TRANSFER;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - hw->formats = AMDTP_IN_PCM_FORMAT_BITS; + hw->formats = AM824_IN_PCM_FORMAT_BITS; stream = &dice->tx_stream; pcm_channels = dice->tx_channels; } else { - hw->formats = AMDTP_OUT_PCM_FORMAT_BITS; + hw->formats = AM824_OUT_PCM_FORMAT_BITS; stream = &dice->rx_stream; pcm_channels = dice->rx_channels; } @@ -237,8 +237,7 @@ static int capture_hw_params(struct snd_pcm_substream *substream, mutex_unlock(&dice->mutex); }
- amdtp_stream_set_pcm_format(&dice->tx_stream, - params_format(hw_params)); + amdtp_am824_set_pcm_format(&dice->tx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); @@ -254,8 +253,7 @@ static int playback_hw_params(struct snd_pcm_substream *substream, mutex_unlock(&dice->mutex); }
- amdtp_stream_set_pcm_format(&dice->rx_stream, - params_format(hw_params)); + amdtp_am824_set_pcm_format(&dice->rx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c index 07dbd01..f366854 100644 --- a/sound/firewire/dice/dice-stream.c +++ b/sound/firewire/dice/dice-stream.c @@ -100,11 +100,12 @@ static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream, { struct fw_iso_resources *resources; unsigned int i, mode, pcm_chs, midi_ports; + bool double_pcm_frames; int err;
err = snd_dice_stream_get_rate_mode(dice, rate, &mode); if (err < 0) - goto end; + return err; if (stream == &dice->tx_stream) { resources = &dice->tx_resources; pcm_chs = dice->tx_channels[mode]; @@ -125,21 +126,23 @@ static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream, * For this quirk, blocking mode is required and PCM buffer size should * be aligned to SYT_INTERVAL. */ - if (mode > 1) { + double_pcm_frames = mode > 1; + if (double_pcm_frames) { rate /= 2; pcm_chs *= 2; - stream->double_pcm_frames = true; - } else { - stream->double_pcm_frames = false; }
- amdtp_stream_set_parameters(stream, rate, pcm_chs, midi_ports); - if (mode > 1) { - pcm_chs /= 2; + err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports, + double_pcm_frames); + if (err < 0) + return err;
+ if (double_pcm_frames) { + pcm_chs /= 2; for (i = 0; i < pcm_chs; i++) { - stream->pcm_positions[i] = i * 2; - stream->pcm_positions[i + pcm_chs] = i * 2 + 1; + amdtp_am824_set_pcm_position(stream, i, i * 2); + amdtp_am824_set_pcm_position(stream, i + pcm_chs, + i * 2 + 1); } }
@@ -148,14 +151,14 @@ static int start_stream(struct snd_dice *dice, struct amdtp_stream *stream, if (err < 0) { dev_err(&dice->unit->device, "fail to keep isochronous resources\n"); - goto end; + return err; }
err = amdtp_stream_start(stream, resources->channel, fw_parent_device(dice->unit)->max_speed); if (err < 0) release_resources(dice, resources); -end: + return err; }
@@ -208,8 +211,10 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) }
/* Some packet queueing errors. */ - if (amdtp_streaming_error(master) || amdtp_streaming_error(slave)) + if (amdtp_streaming_error(master) || amdtp_streaming_error(slave)) { stop_stream(dice, master); + stop_stream(dice, slave); + }
/* Stop stream if rate is different. */ err = snd_dice_transaction_get_rate(dice, &curr_rate); @@ -220,8 +225,10 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) } if (rate == 0) rate = curr_rate; - if (rate != curr_rate) + if (rate != curr_rate) { stop_stream(dice, master); + stop_stream(dice, slave); + }
if (!amdtp_stream_running(master)) { stop_stream(dice, slave); @@ -299,15 +306,15 @@ static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream)
err = fw_iso_resources_init(resources, dice->unit); if (err < 0) - goto end; + return err; resources->channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_stream_init(stream, dice->unit, dir, CIP_BLOCKING); + err = amdtp_am824_init(stream, dice->unit, dir, CIP_BLOCKING); if (err < 0) { amdtp_stream_destroy(stream); fw_iso_resources_destroy(resources); } -end: + return err; }
diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h index 29578c1..101550ac 100644 --- a/sound/firewire/dice/dice.h +++ b/sound/firewire/dice/dice.h @@ -34,7 +34,7 @@ #include <sound/pcm_params.h> #include <sound/rawmidi.h>
-#include "../amdtp-stream.h" +#include "../amdtp-am824.h" #include "../iso-resources.h" #include "../lib.h" #include "dice-interface.h" diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c index 2682e7e..5a48945 100644 --- a/sound/firewire/fireworks/fireworks.c +++ b/sound/firewire/fireworks/fireworks.c @@ -138,12 +138,12 @@ get_hardware_info(struct snd_efw *efw) 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) { + if (hwinfo->amdtp_tx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM) { err = -ENOSYS; goto end; } diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h index f6f9ae6..5fdb5f9 100644 --- a/sound/firewire/fireworks/fireworks.h +++ b/sound/firewire/fireworks/fireworks.h @@ -29,7 +29,7 @@
#include "../packets-buffer.h" #include "../iso-resources.h" -#include "../amdtp-stream.h" +#include "../amdtp-am824.h" #include "../cmp.h" #include "../lib.h"
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c index cf9c652..8134fa4 100644 --- a/sound/firewire/fireworks/fireworks_midi.c +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -73,11 +73,11 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&efw->lock, flags);
if (up) - amdtp_stream_midi_trigger(&efw->tx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&efw->tx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&efw->tx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&efw->tx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&efw->lock, flags); } @@ -90,11 +90,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&efw->lock, flags);
if (up) - amdtp_stream_midi_trigger(&efw->rx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&efw->rx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&efw->rx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&efw->rx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&efw->lock, flags); } diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c index 8a34753..9f75cf5 100644 --- a/sound/firewire/fireworks/fireworks_pcm.c +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -159,11 +159,11 @@ pcm_init_hw_params(struct snd_efw *efw, SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; s = &efw->tx_stream; pcm_channels = efw->pcm_capture_channels; } else { - runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; s = &efw->rx_stream; pcm_channels = efw->pcm_playback_channels; } @@ -247,7 +247,7 @@ static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
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)); + amdtp_am824_set_pcm_format(&efw->tx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); @@ -259,7 +259,7 @@ static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
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)); + amdtp_am824_set_pcm_format(&efw->rx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c index c55db1b..7557fbd 100644 --- a/sound/firewire/fireworks/fireworks_stream.c +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -31,7 +31,7 @@ init_stream(struct snd_efw *efw, struct amdtp_stream *stream) if (err < 0) goto end;
- err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING); + err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING); if (err < 0) { amdtp_stream_destroy(stream); cmp_connection_destroy(conn); @@ -73,8 +73,10 @@ start_stream(struct snd_efw *efw, struct amdtp_stream *stream, midi_ports = efw->midi_in_ports; }
- amdtp_stream_set_parameters(stream, sampling_rate, - pcm_channels, midi_ports); + err = amdtp_am824_set_parameters(stream, sampling_rate, + pcm_channels, midi_ports, false); + if (err < 0) + goto end;
/* establish connection via CMP */ err = cmp_connection_establish(conn, diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c index 540a303..f8f442f 100644 --- a/sound/firewire/oxfw/oxfw-midi.c +++ b/sound/firewire/oxfw/oxfw-midi.c @@ -90,11 +90,11 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&oxfw->lock, flags);
if (up) - amdtp_stream_midi_trigger(&oxfw->tx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&oxfw->tx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&oxfw->tx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&oxfw->tx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&oxfw->lock, flags); } @@ -107,11 +107,11 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) spin_lock_irqsave(&oxfw->lock, flags);
if (up) - amdtp_stream_midi_trigger(&oxfw->rx_stream, - substrm->number, substrm); + amdtp_am824_midi_trigger(&oxfw->rx_stream, substrm->number, + substrm); else - amdtp_stream_midi_trigger(&oxfw->rx_stream, - substrm->number, NULL); + amdtp_am824_midi_trigger(&oxfw->rx_stream, substrm->number, + NULL);
spin_unlock_irqrestore(&oxfw->lock, flags); } diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c index 67ade07..b08b94a 100644 --- a/sound/firewire/oxfw/oxfw-pcm.c +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -134,11 +134,11 @@ static int init_hw_params(struct snd_oxfw *oxfw, SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; stream = &oxfw->tx_stream; formats = oxfw->tx_stream_formats; } else { - runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; stream = &oxfw->rx_stream; formats = oxfw->rx_stream_formats; } @@ -239,7 +239,7 @@ static int pcm_capture_hw_params(struct snd_pcm_substream *substream, mutex_unlock(&oxfw->mutex); }
- amdtp_stream_set_pcm_format(&oxfw->tx_stream, params_format(hw_params)); + amdtp_am824_set_pcm_format(&oxfw->tx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); @@ -255,7 +255,7 @@ static int pcm_playback_hw_params(struct snd_pcm_substream *substream, mutex_unlock(&oxfw->mutex); }
- amdtp_stream_set_pcm_format(&oxfw->rx_stream, params_format(hw_params)); + amdtp_am824_set_pcm_format(&oxfw->rx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c index 873d40f..9018e78 100644 --- a/sound/firewire/oxfw/oxfw-stream.c +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -155,7 +155,10 @@ static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream, err = -EINVAL; goto end; } - amdtp_stream_set_parameters(stream, rate, pcm_channels, midi_ports); + err = amdtp_am824_set_parameters(stream, rate, + pcm_channels, midi_ports, false); + if (err < 0) + goto end;
err = cmp_connection_establish(conn, amdtp_stream_get_max_payload(stream)); @@ -225,7 +228,7 @@ int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, if (err < 0) goto end;
- err = amdtp_stream_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); + err = amdtp_am824_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); if (err < 0) { amdtp_stream_destroy(stream); cmp_connection_destroy(conn); @@ -480,8 +483,8 @@ int snd_oxfw_stream_parse_format(u8 *format, } }
- if (formation->pcm > AMDTP_MAX_CHANNELS_FOR_PCM || - formation->midi > AMDTP_MAX_CHANNELS_FOR_MIDI) + if (formation->pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation->midi > AM824_MAX_CHANNELS_FOR_MIDI) return -ENOSYS;
return 0; diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 2c3d20b..2441459 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -28,7 +28,7 @@ #include "../fcp.h" #include "../packets-buffer.h" #include "../iso-resources.h" -#include "../amdtp-stream.h" +#include "../amdtp-am824.h" #include "../cmp.h"
struct device_info {
Some devices receive MIDI messages via IEEE 1394 asynchronous transactions with fixed length of payload data. It's nice for firewire-lib module to have common helper functions.
This commit implements this idea. Each driver adds 'struct snd_fw_async_midi_port' in its data structure. In probing, it should call snd_fw_async_midi_port_init() to initialize the structure with some parameters such as target address, the length of data for a transaction and a pointer for callback function to fill the data buffer. When 'struct snd_rawmidi_ops.trigger()' callback, it should call 'snd_fw_async_midi_port_run()' to start transactions. Each driver should ensure that the lifetime of MIDI substream continues till calling 'snd_fw_async_midi_port_finish()'.
These helper functions support retries to transferring MIDI messages when transmission errors occur. Therefore, these helper functions expect each driver to use 'snd_rawmidi_transmit_peek()'. When transmissions are successful, these helper functions call 'snd_rawmidi_transmit_ack()' to consume MIDI bytes in the buffer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sound/firewire/lib.h | 46 +++++++++++++++++++++++++ 2 files changed, 141 insertions(+)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 7409edb..f5b5526 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -9,6 +9,7 @@ #include <linux/device.h> #include <linux/firewire.h> #include <linux/module.h> +#include <linux/slab.h> #include "lib.h"
#define ERROR_RETRY_DELAY_MS 20 @@ -66,6 +67,100 @@ int snd_fw_transaction(struct fw_unit *unit, int tcode, } EXPORT_SYMBOL(snd_fw_transaction);
+static void async_midi_port_callback(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_fw_async_midi_port *port = callback_data; + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + + if (rcode == RCODE_COMPLETE && substream != NULL) + snd_rawmidi_transmit_ack(substream, port->consume_bytes); +} + +static void midi_port_tasklet(unsigned long data) +{ + struct snd_fw_async_midi_port *port = + (struct snd_fw_async_midi_port *)data; + struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream); + int generation; + int type; + + /* Nothing to do. */ + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + /* + * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). + * Later, snd_rawmidi_transmit_ack() may be called. + */ + memset(port->buf, 0, port->len); + port->consume_bytes = port->packetize(substream, port->buf); + if (port->consume_bytes <= 0) { + /* Do it in next chance, immediately. */ + if (port->consume_bytes == 0) + tasklet_schedule(&port->tasklet); + return; + } + + /* Calculate type of transaction. */ + if (port->len == 4) + type = TCODE_WRITE_QUADLET_REQUEST; + else + type = TCODE_WRITE_BLOCK_REQUEST; + + /* Start this transaction. */ + generation = port->parent->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(port->parent->card, &port->transaction, type, + port->parent->node_id, generation, + port->parent->max_speed, port->addr, + port->buf, port->len, async_midi_port_callback, + port); +} + +/** + * snd_fw_async_midi_port_init - initialize asynchronous MIDI port structure + * @port: the asynchronous MIDI port to initialize + * @unit: the target of the asynchronous transaction + * @addr: the address to which transactions are transferred + * @len: the length of transaction + * @packetize: the callback function to fill given buffer, and returns the + * number of consumed bytes for MIDI message. + * + */ +int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, + struct fw_unit *unit, __u64 addr, unsigned int len, + int (*packetize)(struct snd_rawmidi_substream *substream, + __u8 *buf)) +{ + port->len = DIV_ROUND_UP(len, 4) * 4; + port->buf = kzalloc(port->len, GFP_KERNEL); + if (port->buf == NULL) + return -ENOMEM; + + port->parent = fw_parent_device(unit); + port->addr = addr; + port->packetize = packetize; + + tasklet_init(&port->tasklet, midi_port_tasklet, (unsigned long)port); + + return 0; +} +EXPORT_SYMBOL(snd_fw_async_midi_port_init); + +/** + * snd_fw_async_midi_port_destroy - free asynchronous MIDI port structure + * @port: the asynchronous MIDI port structure + */ +void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port) +{ + snd_fw_async_midi_port_finish(port); + tasklet_kill(&port->tasklet); + kfree(port->buf); +} +EXPORT_SYMBOL(snd_fw_async_midi_port_destroy); + MODULE_DESCRIPTION("FireWire audio helper functions"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index 02cfabc..c662bf9 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -3,6 +3,8 @@
#include <linux/firewire-constants.h> #include <linux/types.h> +#include <linux/sched.h> +#include <sound/rawmidi.h>
struct fw_unit;
@@ -20,4 +22,48 @@ static inline bool rcode_is_permanent_error(int rcode) return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR; }
+struct snd_fw_async_midi_port { + struct fw_device *parent; + struct tasklet_struct tasklet; + + __u64 addr; + struct fw_transaction transaction; + + __u8 *buf; + unsigned int len; + + struct snd_rawmidi_substream *substream; + int (*packetize)(struct snd_rawmidi_substream *substream, __u8 *buf); + unsigned int consume_bytes; +}; + +int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, + struct fw_unit *unit, __u64 addr, unsigned int len, + int (*packetize)(struct snd_rawmidi_substream *substream, + __u8 *buf)); +void snd_fw_async_midi_port_destroy(struct snd_fw_async_midi_port *port); + +/** + * snd_fw_async_midi_port_run - run transactions for the async MIDI port + * @port: the asynchronous MIDI port + * @substream: the MIDI substream + */ +static inline void +snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + port->substream = substream; + tasklet_schedule(&port->tasklet); +} + +/** + * snd_fw_async_midi_port_finish - finish the asynchronous MIDI port + * @port: the asynchronous MIDI port + */ +static inline void +snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) +{ + port->substream = NULL; +} + #endif
The helper functions for asynchronous MIDI port use 'tasklet' to run IEEE 1394 asynchronous transactions, while running transactions need to wait for response. In this reason, one transaction should be transferred at one tasklet scheduling, because tasklet runs one kernel thread available for the other tasks
For these reasons, this commit serialize request/response transactions, by adding one boolean member.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 8 ++++++++ sound/firewire/lib.h | 1 + 2 files changed, 9 insertions(+)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index f5b5526..a66e011 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -76,6 +76,8 @@ static void async_midi_port_callback(struct fw_card *card, int rcode,
if (rcode == RCODE_COMPLETE && substream != NULL) snd_rawmidi_transmit_ack(substream, port->consume_bytes); + + port->idling = true; }
static void midi_port_tasklet(unsigned long data) @@ -86,6 +88,10 @@ static void midi_port_tasklet(unsigned long data) int generation; int type;
+ /* Under transacting. */ + if (!port->idling) + return; + /* Nothing to do. */ if (substream == NULL || snd_rawmidi_transmit_empty(substream)) return; @@ -110,6 +116,7 @@ static void midi_port_tasklet(unsigned long data) type = TCODE_WRITE_BLOCK_REQUEST;
/* Start this transaction. */ + port->idling = false; generation = port->parent->generation; smp_rmb(); /* node_id vs. generation */ fw_send_request(port->parent->card, &port->transaction, type, @@ -142,6 +149,7 @@ int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, port->parent = fw_parent_device(unit); port->addr = addr; port->packetize = packetize; + port->idling = true;
tasklet_init(&port->tasklet, midi_port_tasklet, (unsigned long)port);
diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index c662bf9..9d76f5c 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -25,6 +25,7 @@ static inline bool rcode_is_permanent_error(int rcode) struct snd_fw_async_midi_port { struct fw_device *parent; struct tasklet_struct tasklet; + bool idling;
__u64 addr; struct fw_transaction transaction;
Two MIDI trigger callbacks can be called immediately. This can postpone second MIDI transaction till next trigger callback.
To avoid this delay, this commit schedules tasklet again at response handling callback if the MIDI substream still includes untransferred MIDI messages.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index a66e011..e309b9b 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -78,6 +78,9 @@ static void async_midi_port_callback(struct fw_card *card, int rcode, snd_rawmidi_transmit_ack(substream, port->consume_bytes);
port->idling = true; + + if (!snd_rawmidi_transmit_empty(substream)) + tasklet_schedule(&port->tasklet); }
static void midi_port_tasklet(unsigned long data)
Typically, the target devices has internal buffer to adjust output of received MIDI messages for MIDI serial bus. The capacity of the buffer is limited, while IEEE 1394 transactions can transfer more MIDI messages than MIDI serial bus can. For this reason, the MIDI transmission rate should be constrained.
This commit adds throttle to limit MIDI data rate as 31,250 bits per seconds. The data rate is enough slower than jiffies granuarity, thus this commit utilize jiffies count to calculate data rate.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 16 +++++++++++++++- sound/firewire/lib.h | 1 + 2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index e309b9b..f9c1873 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -76,6 +76,9 @@ static void async_midi_port_callback(struct fw_card *card, int rcode,
if (rcode == RCODE_COMPLETE && substream != NULL) snd_rawmidi_transmit_ack(substream, port->consume_bytes); + else if (!rcode_is_permanent_error(rcode)) + /* To start next transaction immediately for recovery. */ + port->next_tick = 0;
port->idling = true;
@@ -99,6 +102,10 @@ static void midi_port_tasklet(unsigned long data) if (substream == NULL || snd_rawmidi_transmit_empty(substream)) return;
+ /* Do it in next chance. */ + if (time_is_after_jiffies(port->next_tick)) + tasklet_schedule(&port->tasklet); + /* * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). * Later, snd_rawmidi_transmit_ack() may be called. @@ -107,8 +114,10 @@ static void midi_port_tasklet(unsigned long data) port->consume_bytes = port->packetize(substream, port->buf); if (port->consume_bytes <= 0) { /* Do it in next chance, immediately. */ - if (port->consume_bytes == 0) + if (port->consume_bytes == 0) { + port->next_tick = 0; tasklet_schedule(&port->tasklet); + } return; }
@@ -118,6 +127,10 @@ static void midi_port_tasklet(unsigned long data) else type = TCODE_WRITE_BLOCK_REQUEST;
+ /* Set interval to next transaction. */ + port->next_tick = + jiffies_64 + msecs_to_jiffies(port->consume_bytes * 8 / 31250); + /* Start this transaction. */ port->idling = false; generation = port->parent->generation; @@ -153,6 +166,7 @@ int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, port->addr = addr; port->packetize = packetize; port->idling = true; + port->next_tick = 0;
tasklet_init(&port->tasklet, midi_port_tasklet, (unsigned long)port);
diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index 9d76f5c..e4b00e2 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -26,6 +26,7 @@ struct snd_fw_async_midi_port { struct fw_device *parent; struct tasklet_struct tasklet; bool idling; + unsigned long next_tick;
__u64 addr; struct fw_transaction transaction;
Hi again Takashi
On Sat, Jul 11, 2015 at 11:12:17PM +0900, Takashi Sakamoto wrote:
Typically, the target devices has internal buffer to adjust output of received MIDI messages for MIDI serial bus. The capacity of the buffer is limited, while IEEE 1394 transactions can transfer more MIDI messages than MIDI serial bus can. For this reason, the MIDI transmission rate should be constrained.
It is good that the core has rate throttling. I haven't looked at the code in enough detail but it is important for MOTU devices that it is possible to make the throttling code do the right thing on the assumption that the firewire device does not actually have a buffer at all. Experiments done in the past have shown that at least some MOTU models are not able to cope with MIDI data that is sent any faster than the literal line rate of 31250 bps. That is, if the bytes of even a single MIDI message are sent faster than 31250 bps then the MIDI bitstream output by the device will be corrupted due to the dropping of one or more bytes from the message (or other random creative changes to the data bytes).
In other words, it will need to be possible to tell the throttling code that the buffer size in the device is 1 byte long and have the code only send to the MOTU at 31250 bps. MOTUs cannot be assumed to have any internal buffer for MIDI bytes.
Obviously software is very likely to send MIDI bytes faster than 31250 bps, so in the FFADO MOTU MIDI code I needed to implement a short circular buffer in FFADO in order to match these two rates. In FFADO this buffer is sized at 2^10 bytes and this seems to work reliably: so long as the average rate sent by software stays below 31250 bps, everything is ok. The relatively large size is required in order to deal with sysex messages. I imagine it will be necessary to provide similar functionality in this implementation of that driver in ALSA.
Regards jonathan
When asynchronous transactions finish in error state and retries, tasklet scheduling and tasklet running also continues.
This commit cancels transferring MIDI messages wnen transactions encounter fatal error, by setting error state.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 11 +++++++++-- sound/firewire/lib.h | 8 ++++++-- 2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index f9c1873..b568059c 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -79,6 +79,9 @@ static void async_midi_port_callback(struct fw_card *card, int rcode, else if (!rcode_is_permanent_error(rcode)) /* To start next transaction immediately for recovery. */ port->next_tick = 0; + else + /* Don't continue processing. */ + port->error = true;
port->idling = true;
@@ -94,8 +97,8 @@ static void midi_port_tasklet(unsigned long data) int generation; int type;
- /* Under transacting. */ - if (!port->idling) + /* Under transacting or error state. */ + if (!port->idling || port->error) return;
/* Nothing to do. */ @@ -117,6 +120,9 @@ static void midi_port_tasklet(unsigned long data) if (port->consume_bytes == 0) { port->next_tick = 0; tasklet_schedule(&port->tasklet); + } else { + /* Fatal error. */ + port->error = true; } return; } @@ -167,6 +173,7 @@ int snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port, port->packetize = packetize; port->idling = true; port->next_tick = 0; + port->error = false;
tasklet_init(&port->tasklet, midi_port_tasklet, (unsigned long)port);
diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index e4b00e2..c9ea52b 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -27,6 +27,7 @@ struct snd_fw_async_midi_port { struct tasklet_struct tasklet; bool idling; unsigned long next_tick; + bool error;
__u64 addr; struct fw_transaction transaction; @@ -54,8 +55,10 @@ static inline void snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, struct snd_rawmidi_substream *substream) { - port->substream = substream; - tasklet_schedule(&port->tasklet); + if (port->error) { + port->substream = substream; + tasklet_schedule(&port->tasklet); + } }
/** @@ -66,6 +69,7 @@ static inline void snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) { port->substream = NULL; + port->error = false; }
#endif
This commit adds a new driver for Digidesign 002/003 family. This commit just creates/removes card instance according to bus event. More functions will be added in following commits.
Digidesign 002/003 family consists of: * Agere FW802B for IEEE 1394 PHY layer * PDI 1394L40 for IEEE 1394 LINK layer and IEC 61883 interface * ALTERA ACEX EP1K50 for IEC 61883 layer and DSP controller * ADSP-21065L for signal processing
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 14 ++++ sound/firewire/Makefile | 1 + sound/firewire/digi00x/Makefile | 2 + sound/firewire/digi00x/digi00x.c | 141 +++++++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.h | 32 +++++++++ 5 files changed, 190 insertions(+) create mode 100644 sound/firewire/digi00x/Makefile create mode 100644 sound/firewire/digi00x/digi00x.c create mode 100644 sound/firewire/digi00x/digi00x.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 8850b7d..c6b8828 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -120,4 +120,18 @@ config SND_BEBOB To compile this driver as a module, choose M here: the module will be called snd-bebob.
+config SND_FIREWIRE_DIGI00X + tristate "Digidesign 002/003 family support" + select SND_FIREWIRE_LIB + help + Say Y here to include support for Digidesign 002/003 family. + * Digi 002 Console + * Digi 002 Rack + * Digi 003 Console + * Digi 003 Rack + * Digi 003 Rack+ + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-digi00x. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 6a8a713..5325d15 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ +obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile new file mode 100644 index 0000000..b5176b6 --- /dev/null +++ b/sound/firewire/digi00x/Makefile @@ -0,0 +1,2 @@ +snd-firewire-digi00x-objs := digi00x.o +obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c new file mode 100644 index 0000000..2bdf987 --- /dev/null +++ b/sound/firewire/digi00x/digi00x.c @@ -0,0 +1,141 @@ +/* + * digi00x.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +MODULE_DESCRIPTION("Digidesign 002/003 Driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +#define VENDOR_DIGIDESIGN 0x00a07e +#define MODEL_DIGI00X 0x000002 + +static int name_card(struct snd_dg00x *dg00x) +{ + struct fw_device *fw_dev = fw_parent_device(dg00x->unit); + char name[32] = {0}; + char *model; + int err; + + err = fw_csr_string(dg00x->unit->directory, CSR_MODEL, name, + sizeof(name)); + if (err < 0) + return err; + + model = name; + if (model[0] == ' ') + model = strchr(model, ' ') + 1; + + strcpy(dg00x->card->driver, "Digi00x"); + strcpy(dg00x->card->shortname, model); + strcpy(dg00x->card->mixername, model); + snprintf(dg00x->card->longname, sizeof(dg00x->card->longname), + "Digidesign %s, GUID %08x%08x at %s, S%d", model, + cpu_to_be32(fw_dev->config_rom[3]), + cpu_to_be32(fw_dev->config_rom[4]), + dev_name(&dg00x->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void dg00x_card_free(struct snd_card *card) +{ + struct snd_dg00x *dg00x = card->private_data; + + fw_unit_put(dg00x->unit); + + mutex_destroy(&dg00x->mutex); +} + +static int snd_dg00x_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_dg00x *dg00x; + int err; + + /* create card */ + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(struct snd_dg00x), &card); + if (err < 0) + return err; + card->private_free = dg00x_card_free; + + /* initialize myself */ + dg00x = card->private_data; + dg00x->card = card; + dg00x->unit = fw_unit_get(unit); + + mutex_init(&dg00x->mutex); + + err = name_card(dg00x); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, dg00x); + + return err; +error: + snd_card_free(card); + return err; +} + +static void snd_dg00x_update(struct fw_unit *unit) +{ + return; +} + +static void snd_dg00x_remove(struct fw_unit *unit) +{ + struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(dg00x->card); +} + + +static const struct ieee1394_device_id snd_dg00x_id_table[] = { + /* Both of 002/003 use the same ID. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .model_id = MODEL_DIGI00X, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_dg00x_id_table); + +static struct fw_driver dg00x_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-firewire-digi00x", + .bus = &fw_bus_type, + }, + .probe = snd_dg00x_probe, + .update = snd_dg00x_update, + .remove = snd_dg00x_remove, + .id_table = snd_dg00x_id_table, +}; + +static int __init snd_dg00x_init(void) +{ + return driver_register(&dg00x_driver.driver); +} + +static void __exit snd_dg00x_exit(void) +{ + driver_unregister(&dg00x_driver.driver); +} + +module_init(snd_dg00x_init); +module_exit(snd_dg00x_exit); diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h new file mode 100644 index 0000000..0bb2ca3 --- /dev/null +++ b/sound/firewire/digi00x/digi00x.h @@ -0,0 +1,32 @@ +/* + * digi00x.h - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_DIGI00X_H_INCLUDED +#define SOUND_DIGI00X_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.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" + +struct snd_dg00x { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; +}; + +#endif
Digi 002/003 family uses its own format for data blocks. The format is quite similar to AM824 in IEC 61883-6, while there're some differences:
* The Valid Bit Length (VBL) code always 0x40 in Multi-bit Linear Audio (MBLA) data channel. * The first data channel includes MIDI messages, against IEC 61883-6 reccomendation. * The counter field always 0x80 in MIDI conformant data channel. * Sequence multiplexing in IEC 61883-6 is not applied to the MIDI conformant data channel. * PCM samples are scrambled in received AMDTP packets. We call the way as Doublei-Oh-Three (DOT). The algorithm was discovered by Robin Gareus and Damien Zammit in 2012.
This commit adds protocol layer to satisfy these requirements.
There's a quirk about transmission mode for received packets. When this driver applies non-blocking mode to outgoing packets with isochronous channel 2 or more, after 15 to 20 seconds since playbacking, any PCM samples causes noisy sound. With isochronous channel 0 or 1, this doesn't occur. Therefore, this driver applies blocking mode to the packets against non-blocking mode in incoming packets.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/digi00x/Makefile | 2 +- sound/firewire/digi00x/amdtp-dot.c | 423 +++++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.h | 16 ++ 3 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/digi00x/amdtp-dot.c
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index b5176b6..a5d1a60 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,2 +1,2 @@ -snd-firewire-digi00x-objs := digi00x.o +snd-firewire-digi00x-objs := amdtp-dot.o digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c new file mode 100644 index 0000000..338e884 --- /dev/null +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -0,0 +1,423 @@ +/* + * amdtp-dot.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * Copyright (C) 2012 Robin Gareus robin@gareus.org + * Copyright (C) 2012 Damien Zammit damien@zamaudio.com + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm.h> +#include "digi00x.h" + +#define CIP_FMT_AM 0x10 +#define AMDTP_FDF_AM824 0x00 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +/* + * The double-oh-three algorithm was discovered by Robin Gareus and Damien + * Zammit in 2012, with reverse-engineering for Digi 003 Rack. + */ +struct dot_state { + __u8 carry; + __u8 idx; + unsigned int off; +}; + +struct amdtp_dot { + unsigned int pcm_channels; + struct dot_state state; + + unsigned int midi_ports; + struct snd_rawmidi_substream *midi[8]; + int midi_fifo_limit; + int midi_fifo_used[8]; + + void (*transfer_samples)(struct amdtp_stream *s, + struct snd_pcm_runtime *pcm, + __be32 *buffer, unsigned int frames); +}; + +/* + * double-oh-three look up table + * + * @param idx index byte (audio-sample data) 0x00..0xff + * @param off channel offset shift + * @return salt to XOR with given data + */ +#define BYTE_PER_SAMPLE (4) +#define MAGIC_DOT_BYTE (2) +#define MAGIC_BYTE_OFF(x) (((x) * BYTE_PER_SAMPLE) + MAGIC_DOT_BYTE) +static const __u8 dot_scrt(const __u8 idx, const unsigned int off) +{ + /* + * the length of the added pattern only depends on the lower nibble + * of the last non-zero data + */ + static const __u8 len[16] = {0, 1, 3, 5, 7, 9, 11, 13, 14, + 12, 10, 8, 6, 4, 2, 0}; + + /* + * the lower nibble of the salt. Interleaved sequence. + * this is walked backwards according to len[] + */ + static const __u8 nib[15] = {0x8, 0x7, 0x9, 0x6, 0xa, 0x5, 0xb, 0x4, + 0xc, 0x3, 0xd, 0x2, 0xe, 0x1, 0xf}; + + /* circular list for the salt's hi nibble. */ + static const __u8 hir[15] = {0x0, 0x6, 0xf, 0x8, 0x7, 0x5, 0x3, 0x4, + 0xc, 0xd, 0xe, 0x1, 0x2, 0xb, 0xa}; + + /* + * start offset for upper nibble mapping. + * note: 9 is /special/. In the case where the high nibble == 0x9, + * hir[] is not used and - coincidentally - the salt's hi nibble is + * 0x09 regardless of the offset. + */ + static const __u8 hio[16] = {0, 11, 12, 6, 7, 5, 1, 4, + 3, 0x00, 14, 13, 8, 9, 10, 2}; + + const __u8 ln = idx & 0xf; + const __u8 hn = (idx >> 4) & 0xf; + const __u8 hr = (hn == 0x9) ? 0x9 : hir[(hio[hn] + off) % 15]; + + if (len[ln] < off) + return 0x00; + + return ((nib[14 + off - len[ln]]) | (hr << 4)); +} + +static void dot_encode_step(struct dot_state *state, __be32 *const buffer) +{ + __u8 * const data = (__u8 *) buffer; + + if (data[MAGIC_DOT_BYTE] != 0x00) { + state->off = 0; + state->idx = data[MAGIC_DOT_BYTE] ^ state->carry; + } + data[MAGIC_DOT_BYTE] ^= state->carry; + state->carry = dot_scrt(state->idx, ++(state->off)); +} + +int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, unsigned int midi_ports) +{ + struct amdtp_dot *p = s->protocol; + int err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); + if (err < 0) + return err; + + s->fmt = CIP_FMT_AM; + s->fdf = AMDTP_FDF_AM824 | s->sfc; + + p->pcm_channels = pcm_channels; + p->midi_ports = midi_ports; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + return 0; +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + buffer++; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32((*src >> 8) | 0x40000000); + dot_encode_step(&p->state, &buffer[c]); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + const u16 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + buffer++; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32((*src << 8) | 0x40000000); + dot_encode_step(&p->state, &buffer[c]); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + buffer++; + 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 write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + buffer++; + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_dot *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static inline void midi_use_bytes(struct amdtp_stream *s, + unsigned int port, unsigned int count) +{ + struct amdtp_dot *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc] * count; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int f, port; + u8 *b; + + for (f = 0; f < data_blocks; f++) { + port = (s->data_block_counter + f) % 8; + b = (u8 *)&buffer[0]; + + if (midi_ratelimit_per_packet(s, port) && + p->midi[port] != NULL && + snd_rawmidi_transmit(p->midi[port], b + 1, 1) == 1) { + b[3] = (0x10 << port) | 1; + midi_use_bytes(s, port, 1); + } else { + b[1] = 0; + b[3] = 0; + } + b[0] = 0x80; + b[2] = 0; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int f, port, len; + u8 *b; + + for (f = 0; f < data_blocks; f++) { + b = (u8 *)&buffer[0]; + port = b[3] >> 4; + len = b[3] & 0x0f; + + if (p->midi[port] && (len > 0)) + snd_rawmidi_receive(p->midi[port], b + 1, len); + + buffer += s->data_block_quadlets; + } +} + +void amdtp_dot_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) +{ + struct amdtp_dot *p = s->protocol; + + if (WARN_ON(amdtp_stream_pcm_running(s))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + if (s->direction == AMDTP_OUT_STREAM) { + p->transfer_samples = write_pcm_s16; + break; + } + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S32: + if (s->direction == AMDTP_OUT_STREAM) + p->transfer_samples = write_pcm_s32; + else + p->transfer_samples = read_pcm_s32; + break; + } +} + +void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_dot *p = s->protocol; + + if (port < p->midi_ports) + ACCESS_ONCE(p->midi[port]) = midi; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_dot *p = (struct amdtp_dot *)s->protocol; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) { + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + return pcm_frames; +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_dot *p = (struct amdtp_dot *)s->protocol; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) { + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, buffer, data_blocks); + pcm_frames = 0; + } + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + return pcm_frames; +} + +int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + struct amdtp_dot *p; + enum cip_flags flags; + int err; + + /* Use different mode between incoming/outgoing. */ + if (dir == AMDTP_IN_STREAM) + flags = CIP_NONBLOCKING | CIP_SKIP_INIT_DBC_CHECK; + else + flags = CIP_BLOCKING; + + err = amdtp_stream_init(s, unit, dir, flags, sizeof(struct amdtp_dot)); + if (err < 0) + return err; + p = s->protocol; + + if (dir == AMDTP_IN_STREAM) + s->process_data_blocks = process_tx_data_blocks; + else + s->process_data_blocks = process_rx_data_blocks; + + return 0; +} + +void amdtp_dot_reset(struct amdtp_stream *s) +{ + struct amdtp_dot *p = s->protocol; + + p->state.carry = 0x00; + p->state.idx = 0x00; + p->state.off = 0; +} diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 0bb2ca3..06ba197 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -19,8 +19,14 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rawmidi.h>
#include "../lib.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-stream.h"
struct snd_dg00x { struct snd_card *card; @@ -29,4 +35,14 @@ struct snd_dg00x { struct mutex mutex; };
+int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); +int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports); +void amdtp_dot_reset(struct amdtp_stream *s); +void amdtp_dot_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); +void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + #endif
This commit adds a functionality to manage streaming.
The streaming is not controlled by CMP, against IEC 61883-1. It's controlled by writing to certain addresses.
Several clock sources are available, while there're no differences about packet transmission among clock sources. The value of SYT field in transferred packets is always zero. Thus, streams in both direction don't build synchronization.
And the device always requires received packets to transmit packets, thus this driver keep to transfer outgoing packets even if it's not required.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/digi00x/Makefile | 2 +- sound/firewire/digi00x/digi00x-stream.c | 383 ++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.c | 12 +- sound/firewire/digi00x/digi00x.h | 74 ++++++ 4 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/digi00x/digi00x-stream.c
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index a5d1a60..b7c783d 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,2 +1,2 @@ -snd-firewire-digi00x-objs := amdtp-dot.o digi00x.o +snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c new file mode 100644 index 0000000..2956283 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-stream.c @@ -0,0 +1,383 @@ +/* + * digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +#define CALLBACK_TIMEOUT 500 + +const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = { + [SND_DG00X_RATE_44100] = 44100, + [SND_DG00X_RATE_48000] = 48000, + [SND_DG00X_RATE_88200] = 88200, + [SND_DG00X_RATE_96000] = 96000, +}; + +/* Multi Bit Linear Audio data channels for each sampling transfer frequency. */ +const unsigned int +snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT] = { + /* Analog/ADAT/SPDIF */ + [SND_DG00X_RATE_44100] = (8 + 8 + 2), + [SND_DG00X_RATE_48000] = (8 + 8 + 2), + /* Analog/SPDIF */ + [SND_DG00X_RATE_88200] = (8 + 2), + [SND_DG00X_RATE_96000] = (8 + 2), +}; + +int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, unsigned int *rate) +{ + u32 data; + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = be32_to_cpu(reg) & 0x0f; + if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) + *rate = snd_dg00x_stream_rates[data]; + else + err = -EIO; + + return err; +} + +int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate) +{ + __be32 reg; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) { + if (rate == snd_dg00x_stream_rates[i]) + break; + } + if (i == ARRAY_SIZE(snd_dg00x_stream_rates)) + return -EIO; + + reg = cpu_to_be32(i); + return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, + ®, sizeof(reg), 0); +} + +int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, + enum snd_dg00x_clock *clock) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + *clock = be32_to_cpu(reg) & 0x0f; + if (*clock >= SND_DG00X_CLOCK_COUNT) + err = -EIO; + + return err; +} + +int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, bool *detect) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_DETECT_EXTERNAL, + ®, sizeof(reg), 0); + if (err >= 0) + *detect = be32_to_cpu(reg) > 0; + + return err; +} + +int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, + unsigned int *rate) +{ + u32 data; + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_EXTERNAL_RATE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = be32_to_cpu(reg) & 0x0f; + if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) + *rate = snd_dg00x_stream_rates[data]; + /* This means desync. */ + else + err = -EBUSY; + + return err; +} + +static void finish_session(struct snd_dg00x *dg00x) +{ + __be32 data = cpu_to_be32(0x00000003); + + snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET, + &data, sizeof(data), 0); +} + +static int begin_session(struct snd_dg00x *dg00x) +{ + __be32 data; + u32 curr; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE, + &data, sizeof(data), 0); + if (err < 0) + goto error; + curr = be32_to_cpu(data); + + if (curr == 0) + curr = 2; + + curr--; + while (curr > 0) { + data = cpu_to_be32(curr); + err = snd_fw_transaction(dg00x->unit, + TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + + DG00X_OFFSET_STREAMING_SET, + &data, sizeof(data), 0); + if (err < 0) + goto error; + + msleep(20); + curr--; + } + + return 0; +error: + finish_session(dg00x); + return err; +} + +static void release_resources(struct snd_dg00x *dg00x) +{ + __be32 data = 0; + + /* Unregister isochronous channels for both direction. */ + snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, + &data, sizeof(data), 0); + + /* Release isochronous resources. */ + fw_iso_resources_free(&dg00x->tx_resources); + fw_iso_resources_free(&dg00x->rx_resources); +} + +static int keep_resources(struct snd_dg00x *dg00x, unsigned int rate) +{ + unsigned int i; + __be32 data; + int err; + + /* Check sampling rate. */ + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (snd_dg00x_stream_rates[i] == rate) + break; + } + if (i == SND_DG00X_RATE_COUNT) + return -EINVAL; + + /* Keep resources for out-stream. */ + amdtp_dot_set_parameters(&dg00x->rx_stream, rate, + snd_dg00x_stream_pcm_channels[i], 2); + err = fw_iso_resources_allocate(&dg00x->rx_resources, + amdtp_stream_get_max_payload(&dg00x->rx_stream), + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + amdtp_dot_set_parameters(&dg00x->tx_stream, rate, + snd_dg00x_stream_pcm_channels[i], 1); + err = fw_iso_resources_allocate(&dg00x->tx_resources, + amdtp_stream_get_max_payload(&dg00x->tx_stream), + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + goto error; + + /* Register isochronous channels for both direction. */ + data = cpu_to_be32((dg00x->tx_resources.channel << 16) | + dg00x->rx_resources.channel); + err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, + &data, sizeof(data), 0); + if (err < 0) + goto error; + + return 0; +error: + release_resources(dg00x); + return err; +} + +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x) +{ + int err; + + /* For out-stream. */ + err = fw_iso_resources_init(&dg00x->rx_resources, dg00x->unit); + if (err < 0) + goto error; + err = amdtp_dot_init(&dg00x->rx_stream, dg00x->unit, AMDTP_OUT_STREAM); + if (err < 0) + goto error; + + /* For in-stream. */ + err = fw_iso_resources_init(&dg00x->tx_resources, dg00x->unit); + if (err < 0) + goto error; + err = amdtp_dot_init(&dg00x->tx_stream, dg00x->unit, AMDTP_IN_STREAM); + if (err < 0) + goto error; + + return 0; +error: + amdtp_stream_destroy(&dg00x->tx_stream); + amdtp_stream_destroy(&dg00x->rx_stream); + fw_iso_resources_destroy(&dg00x->tx_resources); + fw_iso_resources_destroy(&dg00x->rx_resources); + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x) +{ + amdtp_stream_destroy(&dg00x->rx_stream); + fw_iso_resources_destroy(&dg00x->rx_resources); + + amdtp_stream_destroy(&dg00x->tx_stream); + fw_iso_resources_destroy(&dg00x->tx_resources); +} + +int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate) +{ + unsigned int curr_rate; + int err = 0; + + if (dg00x->substreams_counter == 0) + goto end; + + /* Check current sampling rate. */ + err = snd_dg00x_stream_get_local_rate(dg00x, &curr_rate); + if (err < 0) + goto error; + /* For MIDI substreams. */ + if (rate == 0) + rate = curr_rate; + if (curr_rate != rate || + amdtp_streaming_error(&dg00x->tx_stream) || + amdtp_streaming_error(&dg00x->rx_stream)) { + finish_session(dg00x); + + amdtp_stream_stop(&dg00x->tx_stream); + amdtp_stream_stop(&dg00x->rx_stream); + release_resources(dg00x); + } + + /* + * No packets are transmitted without receiving packets, reagardless of + * which sources of clock is used. + */ + if (!amdtp_stream_running(&dg00x->rx_stream)) { + err = snd_dg00x_stream_set_local_rate(dg00x, rate); + if (err < 0) + goto error; + + err = keep_resources(dg00x, rate); + if (err < 0) + goto error; + + err = begin_session(dg00x); + if (err < 0) + goto error; + + err = amdtp_stream_start(&dg00x->rx_stream, + dg00x->rx_resources.channel, + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&dg00x->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + /* + * The value of SYT field in transmitted packets is always 0x0000. Thus, + * duplex streams with timestamp synchronization cannot be built. + */ + if (!amdtp_stream_running(&dg00x->tx_stream)) { + err = amdtp_stream_start(&dg00x->tx_stream, + dg00x->tx_resources.channel, + fw_parent_device(dg00x->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&dg00x->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } +end: + return err; +error: + finish_session(dg00x); + + amdtp_stream_stop(&dg00x->tx_stream); + amdtp_stream_stop(&dg00x->rx_stream); + release_resources(dg00x); + + return err; +} + +void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x) +{ + if (dg00x->substreams_counter > 0) + return; + + amdtp_stream_stop(&dg00x->tx_stream); + amdtp_stream_stop(&dg00x->rx_stream); + finish_session(dg00x); + release_resources(dg00x); + + /* + * Just after finishing the session, the device may lost transmitting + * functionality for a short time. + */ + msleep(50); +} + +void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x) +{ + fw_iso_resources_update(&dg00x->tx_resources); + fw_iso_resources_update(&dg00x->rx_resources); + + amdtp_stream_update(&dg00x->tx_stream); + amdtp_stream_update(&dg00x->rx_stream); +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 2bdf987..2c517ec 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -47,6 +47,8 @@ static void dg00x_card_free(struct snd_card *card) { struct snd_dg00x *dg00x = card->private_data;
+ snd_dg00x_stream_destroy_duplex(dg00x); + fw_unit_put(dg00x->unit);
mutex_destroy(&dg00x->mutex); @@ -77,6 +79,10 @@ static int snd_dg00x_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_dg00x_stream_init_duplex(dg00x); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -91,7 +97,11 @@ error:
static void snd_dg00x_update(struct fw_unit *unit) { - return; + struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device); + + mutex_lock(&dg00x->mutex); + snd_dg00x_stream_update_duplex(dg00x); + mutex_unlock(&dg00x->mutex); }
static void snd_dg00x_remove(struct fw_unit *unit) diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 06ba197..dd9b39a 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -33,6 +33,63 @@ struct snd_dg00x { struct fw_unit *unit;
struct mutex mutex; + + struct amdtp_stream tx_stream; + struct fw_iso_resources tx_resources; + + struct amdtp_stream rx_stream; + struct fw_iso_resources rx_resources; + + unsigned int substreams_counter; +}; + +#define DG00X_ADDR_BASE 0xffffe0000000ull + +#define DG00X_OFFSET_STREAMING_STATE 0x0000 +#define DG00X_OFFSET_STREAMING_SET 0x0004 +#define DG00X_OFFSET_MIDI_CTL_ADDR 0x0008 +/* For LSB of the address 0x000c */ +/* unknown 0x0010 */ +#define DG00X_OFFSET_MESSAGE_ADDR 0x0014 +/* For LSB of the address 0x0018 */ +/* unknown 0x001c */ +/* unknown 0x0020 */ +/* not used 0x0024--0x00ff */ +#define DG00X_OFFSET_ISOC_CHANNELS 0x0100 +/* unknown 0x0104 */ +/* unknown 0x0108 */ +/* unknown 0x010c */ +#define DG00X_OFFSET_LOCAL_RATE 0x0110 +#define DG00X_OFFSET_EXTERNAL_RATE 0x0114 +#define DG00X_OFFSET_CLOCK_SOURCE 0x0118 +#define DG00X_OFFSET_OPT_IFACE_MODE 0x011c +/* unknown 0x0120 */ +/* Mixer control on/off 0x0124 */ +/* unknown 0x0128 */ +#define DG00X_OFFSET_DETECT_EXTERNAL 0x012c +/* unknown 0x0138 */ +#define DG00X_OFFSET_MMC 0x0400 + +enum snd_dg00x_rate { + SND_DG00X_RATE_44100 = 0, + SND_DG00X_RATE_48000, + SND_DG00X_RATE_88200, + SND_DG00X_RATE_96000, + SND_DG00X_RATE_COUNT, +}; + +enum snd_dg00x_clock { + SND_DG00X_CLOCK_INTERNAL = 0, + SND_DG00X_CLOCK_SPDIF, + SND_DG00X_CLOCK_ADAT, + SND_DG00X_CLOCK_WORD, + SND_DG00X_CLOCK_COUNT, +}; + +enum snd_dg00x_optical_mode { + SND_DG00X_OPT_IFACE_MODE_ADAT = 0, + SND_DG00X_OPT_IFACE_MODE_SPDIF, + SND_DG00X_OPT_IFACE_MODE_COUNT, };
int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, @@ -45,4 +102,21 @@ void amdtp_dot_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, struct snd_rawmidi_substream *midi);
+extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT]; +extern const unsigned int snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT]; +int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, + unsigned int *rate); +int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, + unsigned int *rate); +int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate); +int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, + enum snd_dg00x_clock *clock); +int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, + bool *detect); +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x); +int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate); +void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x); + #endif
This commit adds proc node to show current clock status for debugging.
As long as testing Digi 002 rack, registers can show local clock rate, local clock source. When external clock input such as S/PDIF is connected, the registers show detection and external clock rate.
Additionally, the registers show the mode of optical digital input interface. On the other hand, a tester with Digi 003 rack reports this makes no sence. Further investigation is required.
Besides, in Digi 002 rack, the S/PDIF format must be IEC 60958-4, so-called professional.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/digi00x/Makefile | 3 +- sound/firewire/digi00x/digi00x-proc.c | 99 +++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.c | 2 + sound/firewire/digi00x/digi00x.h | 2 + 4 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/digi00x/digi00x-proc.c
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index b7c783d..6c9bc37 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,2 +1,3 @@ -snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x.o +snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ + digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x-proc.c b/sound/firewire/digi00x/digi00x-proc.c new file mode 100644 index 0000000..280d621 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-proc.c @@ -0,0 +1,99 @@ +/* + * digi00x-proc.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./digi00x.h" + +static int get_optical_iface_mode(struct snd_dg00x *dg00x, + enum snd_dg00x_optical_mode *mode) +{ + __be32 data; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_OPT_IFACE_MODE, + &data, sizeof(data), 0); + if (err >= 0) + *mode = be32_to_cpu(data) & 0x01; + + return err; +} + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buf) +{ + static const char *const source_name[] = { + [SND_DG00X_CLOCK_INTERNAL] = "internal", + [SND_DG00X_CLOCK_SPDIF] = "s/pdif", + [SND_DG00X_CLOCK_ADAT] = "adat", + [SND_DG00X_CLOCK_WORD] = "word clock", + }; + static const char *const optical_name[] = { + [SND_DG00X_OPT_IFACE_MODE_ADAT] = "adat", + [SND_DG00X_OPT_IFACE_MODE_SPDIF] = "s/pdif", + }; + struct snd_dg00x *dg00x = entry->private_data; + enum snd_dg00x_optical_mode mode; + unsigned int rate; + enum snd_dg00x_clock clock; + bool detect; + + if (get_optical_iface_mode(dg00x, &mode) < 0) + return; + if (snd_dg00x_stream_get_local_rate(dg00x, &rate) < 0) + return; + if (snd_dg00x_stream_get_clock(dg00x, &clock) < 0) + return; + + snd_iprintf(buf, "Optical mode: %s\n", optical_name[mode]); + snd_iprintf(buf, "Sampling Rate: %d\n", rate); + snd_iprintf(buf, "Clock Source: %s\n", source_name[clock]); + + if (clock == SND_DG00X_CLOCK_INTERNAL) + return; + + if (snd_dg00x_stream_check_external_clock(dg00x, &detect) < 0) + return; + snd_iprintf(buf, "External source: %s\n", detect ? "detected" : "not"); + if (!detect) + return; + + if (snd_dg00x_stream_get_external_rate(dg00x, &rate) >= 0) + snd_iprintf(buf, "External sampling rate: %d\n", rate); +} + +void snd_dg00x_proc_init(struct snd_dg00x *dg00x) +{ + struct snd_info_entry *root, *entry; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(dg00x->card, "firewire", + dg00x->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; + } + + entry = snd_info_create_card_entry(dg00x->card, "clock", root); + if (entry == NULL) { + snd_info_free_entry(root); + return; + } + + snd_info_set_text_ops(entry, dg00x, proc_read_clock); + if (snd_info_register(entry) < 0) { + snd_info_free_entry(entry); + snd_info_free_entry(root); + } +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 2c517ec..130f569 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -83,6 +83,8 @@ static int snd_dg00x_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_dg00x_proc_init(dg00x); + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index dd9b39a..da6950e 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -19,6 +19,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/info.h> #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/rawmidi.h> @@ -119,4 +120,5 @@ void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x); void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x); void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
+void snd_dg00x_proc_init(struct snd_dg00x *dg00x); #endif
This commit adds PCM functionality to transmit/receive PCM samples.
When one of PCM substream is running or external clock source is selected, current sampling rate is used. Else, the sampling rate is changed as an userspace application requests.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/digi00x/Makefile | 2 +- sound/firewire/digi00x/digi00x-pcm.c | 344 +++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.c | 4 + sound/firewire/digi00x/digi00x.h | 3 + 4 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/digi00x/digi00x-pcm.c
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index 6c9bc37..629ae16 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,3 +1,3 @@ snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ - digi00x.o + digi00x-pcm.o digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c new file mode 100644 index 0000000..274ac66 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-pcm.c @@ -0,0 +1,344 @@ +/* + * digi00x-pcm.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + 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_DG00X_RATE_COUNT; i++) { + if (!snd_interval_test(c, + snd_dg00x_stream_pcm_channels[i])) + continue; + + t.min = min(t.min, snd_dg00x_stream_rates[i]); + t.max = max(t.max, snd_dg00x_stream_rates[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_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_DG00X_RATE_COUNT; i++) { + if (!snd_interval_test(r, snd_dg00x_stream_rates[i])) + continue; + + t.min = min(t.min, snd_dg00x_stream_pcm_channels[i]); + t.max = max(t.max, snd_dg00x_stream_pcm_channels[i]); + } + + return snd_interval_refine(c, &t); +} + +static int pcm_init_hw_params(struct snd_dg00x *dg00x, + struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .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, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 10, + .channels_max = 18, + .period_bytes_min = 4 * 18, + .period_bytes_max = 4 * 18 * 2048, + .buffer_bytes_max = 4 * 18 * 2048 * 2, + .periods_min = 2, + .periods_max = UINT_MAX, + }; + struct amdtp_stream *s; + int err; + + substream->runtime->hw = hardware; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &dg00x->tx_stream; + } else { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S32; + s = &dg00x->rx_stream; + } + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_stream_add_pcm_hw_constraints(s, substream->runtime); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + enum snd_dg00x_clock clock; + bool detect; + unsigned int rate; + int err; + + err = pcm_init_hw_params(dg00x, substream); + if (err < 0) + return err; + + /* Check current clock source. */ + err = snd_dg00x_stream_get_clock(dg00x, &clock); + if (err < 0) + return err; + if (clock != SND_DG00X_CLOCK_INTERNAL) { + err = snd_dg00x_stream_check_external_clock(dg00x, &detect); + if (err < 0) + return err; + if (!detect) { + err = -EBUSY; + return err; + } + } + + if ((clock != SND_DG00X_CLOCK_INTERNAL) || + amdtp_stream_pcm_running(&dg00x->rx_stream) || + amdtp_stream_pcm_running(&dg00x->tx_stream)) { + err = snd_dg00x_stream_get_external_rate(dg00x, &rate); + if (err < 0) + return err; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + 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_dg00x *dg00x = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + mutex_unlock(&dg00x->mutex); + } + amdtp_dot_set_pcm_format(&dg00x->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_dg00x *dg00x = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + mutex_unlock(&dg00x->mutex); + } + amdtp_dot_set_pcm_format(&dg00x->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_dg00x *dg00x = substream->private_data; + + mutex_lock(&dg00x->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dg00x->substreams_counter--; + + snd_dg00x_stream_stop_duplex(dg00x); + + mutex_unlock(&dg00x->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + mutex_lock(&dg00x->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + dg00x->substreams_counter--; + + snd_dg00x_stream_stop_duplex(dg00x); + + mutex_unlock(&dg00x->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&dg00x->mutex); + + err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&dg00x->tx_stream); + + mutex_unlock(&dg00x->mutex); + + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&dg00x->mutex); + + err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate); + if (err >= 0) { + amdtp_stream_pcm_prepare(&dg00x->rx_stream); + amdtp_dot_reset(&dg00x->rx_stream); + } + + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dg00x *dg00x = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dg00x->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dg00x->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dg00x *dg00x = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dg00x->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dg00x->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_dg00x *dg00x = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&dg00x->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_dg00x *dg00x = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&dg00x->rx_stream); +} + +static 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 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_dg00x_create_pcm_devices(struct snd_dg00x *dg00x) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(dg00x->card, dg00x->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = dg00x; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", dg00x->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); + + return 0; +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 130f569..0db2024 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -85,6 +85,10 @@ static int snd_dg00x_probe(struct fw_unit *unit,
snd_dg00x_proc_init(dg00x);
+ err = snd_dg00x_create_pcm_devices(dg00x); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index da6950e..07dce7d 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -121,4 +121,7 @@ void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x); void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
void snd_dg00x_proc_init(struct snd_dg00x *dg00x); + +int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x); + #endif
This commit adds MIDI functionality to transfer/receive MIDI messages.
Digi 002/003 console also has a set of MIDI port for physical control. These ports are added in later commits.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/digi00x/Makefile | 2 +- sound/firewire/digi00x/digi00x-midi.c | 149 ++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.c | 5 ++ sound/firewire/digi00x/digi00x.h | 2 + 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/digi00x/digi00x-midi.c
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index 629ae16..0aea9dd 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,3 +1,3 @@ snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ - digi00x-pcm.o digi00x.o + digi00x-pcm.o digi00x-midi.o digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c new file mode 100644 index 0000000..09489fc --- /dev/null +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -0,0 +1,149 @@ +/* + * digi00x-midi.h - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "digi00x.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + int err; + + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + err = snd_dg00x_stream_start_duplex(dg00x, 0); + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + int err; + + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter++; + err = snd_dg00x_stream_start_duplex(dg00x, 0); + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter--; + snd_dg00x_stream_stop_duplex(dg00x); + mutex_unlock(&dg00x->mutex); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + + mutex_lock(&dg00x->mutex); + dg00x->substreams_counter--; + snd_dg00x_stream_stop_duplex(dg00x); + mutex_unlock(&dg00x->mutex); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dg00x *dg00x = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dg00x->lock, flags); + + if (up) + amdtp_dot_midi_trigger(&dg00x->tx_stream, + substrm->number - 1, substrm); + else + amdtp_dot_midi_trigger(&dg00x->tx_stream, + substrm->number - 1, NULL); + + spin_unlock_irqrestore(&dg00x->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dg00x *dg00x = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dg00x->lock, flags); + + if (up) + amdtp_dot_midi_trigger(&dg00x->rx_stream, + substrm->number - 1, substrm); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, + substrm->number - 1, NULL); + + spin_unlock_irqrestore(&dg00x->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_dg00x *dg00x, + 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", + dg00x->card->shortname, subs->number); + } +} + +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0, + 3, 2, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", dg00x->card->shortname); + rmidi->private_data = dg00x; + + 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(dg00x, str); + + 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(dg00x, str); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 0db2024..0aa036c 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -74,6 +74,7 @@ static int snd_dg00x_probe(struct fw_unit *unit, dg00x->unit = fw_unit_get(unit);
mutex_init(&dg00x->mutex); + spin_lock_init(&dg00x->lock);
err = name_card(dg00x); if (err < 0) @@ -89,6 +90,10 @@ static int snd_dg00x_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_dg00x_create_midi_devices(dg00x); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 07dce7d..df6d39c 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -34,6 +34,7 @@ struct snd_dg00x { struct fw_unit *unit;
struct mutex mutex; + spinlock_t lock;
struct amdtp_stream tx_stream; struct fw_iso_resources tx_resources; @@ -124,4 +125,5 @@ void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
+int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x); #endif
This commit adds hwdep interface so as the other firewire sound devices has.
This interface is designed for mixer/control applications. By using this interface, an application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org 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/digi00x/Makefile | 3 +- sound/firewire/digi00x/digi00x-hwdep.c | 193 ++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x-midi.c | 14 +++ sound/firewire/digi00x/digi00x-pcm.c | 23 +++- sound/firewire/digi00x/digi00x-stream.c | 39 +++++++ sound/firewire/digi00x/digi00x.c | 5 + sound/firewire/digi00x/digi00x.h | 14 +++ 10 files changed, 288 insertions(+), 8 deletions(-) create mode 100644 sound/firewire/digi00x/digi00x-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index a45be6b..aa32913 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -100,9 +100,10 @@ enum { SNDRV_HWDEP_IFACE_FW_FIREWORKS, /* Echo Audio Fireworks based device */ SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */ SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */ + SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DIGI00X };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 49122df..f67d228 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -56,6 +56,7 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_FIREWORKS 2 #define SNDRV_FIREWIRE_TYPE_BEBOB 3 #define SNDRV_FIREWIRE_TYPE_OXFW 4 +#define SNDRV_FIREWIRE_TYPE_DIGI00X 5 /* RME, MOTU, ... */
struct snd_firewire_get_info { diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index c6b8828..80f3b57 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -123,6 +123,7 @@ config SND_BEBOB config SND_FIREWIRE_DIGI00X tristate "Digidesign 002/003 family support" select SND_FIREWIRE_LIB + select SND_HWDEP help Say Y here to include support for Digidesign 002/003 family. * Digi 002 Console diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index 0aea9dd..91e6671 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,3 +1,4 @@ snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ - digi00x-pcm.o digi00x-midi.o digi00x.o + digi00x-pcm.o digi00x-midi.o digi00x-hwdep.o \ + digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c new file mode 100644 index 0000000..6c157dd --- /dev/null +++ b/sound/firewire/digi00x/digi00x-hwdep.c @@ -0,0 +1,193 @@ +/* + * digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + * 4.get asynchronous messaging + */ + +#include "digi00x.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&dg00x->lock); + + while (!dg00x->dev_lock_changed) { + prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&dg00x->lock); + schedule(); + finish_wait(&dg00x->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dg00x->lock); + } + + memset(&event, 0, sizeof(event)); + if (dg00x->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (dg00x->dev_lock_count > 0); + dg00x->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&dg00x->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_dg00x *dg00x = hwdep->private_data; + unsigned int events; + + poll_wait(file, &dg00x->hwdep_wait, wait); + + spin_lock_irq(&dg00x->lock); + if (dg00x->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&dg00x->lock); + + return events; +} + +static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(dg00x->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_DIGI00X; + 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_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + if (dg00x->dev_lock_count == 0) { + dg00x->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&dg00x->lock); + + return err; +} + +static int hwdep_unlock(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + if (dg00x->dev_lock_count == -1) { + dg00x->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&dg00x->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + + spin_lock_irq(&dg00x->lock); + if (dg00x->dev_lock_count == -1) + dg00x->dev_lock_count = 0; + spin_unlock_irq(&dg00x->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(dg00x, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(dg00x); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(dg00x); + 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_dg00x_create_hwdep_device(struct snd_dg00x *dg00x) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Digi00x"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X; + hwdep->ops = hwdep_ops; + hwdep->private_data = dg00x; + hwdep->exclusive = true; + + return err; +} diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c index 09489fc..241d93a 100644 --- a/sound/firewire/digi00x/digi00x-midi.c +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -13,10 +13,16 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) struct snd_dg00x *dg00x = substream->rmidi->private_data; int err;
+ err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + return err; + mutex_lock(&dg00x->mutex); dg00x->substreams_counter++; err = snd_dg00x_stream_start_duplex(dg00x, 0); mutex_unlock(&dg00x->mutex); + if (err < 0) + snd_dg00x_stream_lock_release(dg00x);
return err; } @@ -26,10 +32,16 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) struct snd_dg00x *dg00x = substream->rmidi->private_data; int err;
+ err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + return err; + mutex_lock(&dg00x->mutex); dg00x->substreams_counter++; err = snd_dg00x_stream_start_duplex(dg00x, 0); mutex_unlock(&dg00x->mutex); + if (err < 0) + snd_dg00x_stream_lock_release(dg00x);
return err; } @@ -43,6 +55,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) snd_dg00x_stream_stop_duplex(dg00x); mutex_unlock(&dg00x->mutex);
+ snd_dg00x_stream_lock_release(dg00x); return 0; }
@@ -55,6 +68,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream) snd_dg00x_stream_stop_duplex(dg00x); mutex_unlock(&dg00x->mutex);
+ snd_dg00x_stream_lock_release(dg00x); return 0; }
diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c index 274ac66..293df5c 100644 --- a/sound/firewire/digi00x/digi00x-pcm.c +++ b/sound/firewire/digi00x/digi00x-pcm.c @@ -120,21 +120,25 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int rate; int err;
+ err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + goto end; + err = pcm_init_hw_params(dg00x, substream); if (err < 0) - return err; + goto err_locked;
/* Check current clock source. */ err = snd_dg00x_stream_get_clock(dg00x, &clock); if (err < 0) - return err; + goto err_locked; if (clock != SND_DG00X_CLOCK_INTERNAL) { err = snd_dg00x_stream_check_external_clock(dg00x, &detect); if (err < 0) - return err; + goto err_locked; if (!detect) { err = -EBUSY; - return err; + goto err_locked; } }
@@ -143,18 +147,25 @@ static int pcm_open(struct snd_pcm_substream *substream) amdtp_stream_pcm_running(&dg00x->tx_stream)) { err = snd_dg00x_stream_get_external_rate(dg00x, &rate); if (err < 0) - return err; + goto err_locked; substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; }
snd_pcm_set_sync(substream); - +end: + return err; +err_locked: + snd_dg00x_stream_lock_release(dg00x); return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_dg00x *dg00x = substream->private_data; + + snd_dg00x_stream_lock_release(dg00x); + return 0; }
diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c index 2956283..dd2f4b8 100644 --- a/sound/firewire/digi00x/digi00x-stream.c +++ b/sound/firewire/digi00x/digi00x-stream.c @@ -381,3 +381,42 @@ void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x) amdtp_stream_update(&dg00x->tx_stream); amdtp_stream_update(&dg00x->rx_stream); } + +void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x) +{ + dg00x->dev_lock_changed = true; + wake_up(&dg00x->hwdep_wait); +} + +int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + /* user land lock this */ + if (dg00x->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (dg00x->dev_lock_count++ == 0) + snd_dg00x_stream_lock_changed(dg00x); + err = 0; +end: + spin_unlock_irq(&dg00x->lock); + return err; +} + +void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x) +{ + spin_lock_irq(&dg00x->lock); + + if (WARN_ON(dg00x->dev_lock_count <= 0)) + goto end; + if (--dg00x->dev_lock_count == 0) + snd_dg00x_stream_lock_changed(dg00x); +end: + spin_unlock_irq(&dg00x->lock); +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 0aa036c..0f2c331 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -75,6 +75,7 @@ static int snd_dg00x_probe(struct fw_unit *unit,
mutex_init(&dg00x->mutex); spin_lock_init(&dg00x->lock); + init_waitqueue_head(&dg00x->hwdep_wait);
err = name_card(dg00x); if (err < 0) @@ -94,6 +95,10 @@ static int snd_dg00x_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_dg00x_create_hwdep_device(dg00x); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index df6d39c..2776408 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -23,6 +23,8 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../lib.h" #include "../packets-buffer.h" @@ -43,6 +45,12 @@ struct snd_dg00x { struct fw_iso_resources rx_resources;
unsigned int substreams_counter; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + };
#define DG00X_ADDR_BASE 0xffffe0000000ull @@ -121,9 +129,15 @@ void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x); void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x); void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
+void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x); +int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x); +void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x); + void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x); + +int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x); #endif
Digi 002/003 family uses asynchronous transaction for messaging. The address to transmit this message is stored on a certain register.
This commit allocates a certain range of address on OHCI 1394 host controller, to handle the messaging. Currently, the purpose of this message seems to notify losts of synchronization.
Actual examples of this messaging: * Clock source is set as internal: - 0x00007051 - 0x00007052 - 0x00007054 - 0x00007057 - 0x00007058 * Clock source is set as somewhat external: - 0x00009000 - 0x00009010 - 0x00009020 - 0x00009021 - 0x00009022
The meaning of these contents is unknown.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/firewire.h | 7 +++ sound/firewire/digi00x/Makefile | 2 +- sound/firewire/digi00x/digi00x-hwdep.c | 11 +++- sound/firewire/digi00x/digi00x-transaction.c | 81 ++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.c | 7 +++ sound/firewire/digi00x/digi00x.h | 7 +++ 6 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 sound/firewire/digi00x/digi00x-transaction.c
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index f67d228..deb041c 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -9,6 +9,7 @@ #define SNDRV_FIREWIRE_EVENT_LOCK_STATUS 0x000010cc #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE 0x4e617475 +#define SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE 0x746e736c
struct snd_firewire_event_common { unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */ @@ -40,11 +41,17 @@ struct snd_firewire_event_efw_response { __be32 response[0]; /* some responses */ };
+struct snd_firewire_event_digi00x_message { + unsigned int type; + __u32 message; /* Digi00x-specific message */ +}; + 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; + struct snd_firewire_event_digi00x_message digi00x_message; };
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile index 91e6671..f5dbc20 100644 --- a/sound/firewire/digi00x/Makefile +++ b/sound/firewire/digi00x/Makefile @@ -1,4 +1,4 @@ snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ digi00x-pcm.o digi00x-midi.o digi00x-hwdep.o \ - digi00x.o + digi00x-transaction.o digi00x.o obj-m += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c index 6c157dd..f188e47 100644 --- a/sound/firewire/digi00x/digi00x-hwdep.c +++ b/sound/firewire/digi00x/digi00x-hwdep.c @@ -26,7 +26,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
spin_lock_irq(&dg00x->lock);
- while (!dg00x->dev_lock_changed) { + while (!dg00x->dev_lock_changed && dg00x->msg == 0) { prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE); spin_unlock_irq(&dg00x->lock); schedule(); @@ -43,6 +43,13 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, dg00x->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.digi00x_message.type = + SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE; + event.digi00x_message.message = dg00x->msg; + dg00x->msg = 0; + + count = min_t(long, count, sizeof(event.digi00x_message)); }
spin_unlock_irq(&dg00x->lock); @@ -62,7 +69,7 @@ static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_wait(file, &dg00x->hwdep_wait, wait);
spin_lock_irq(&dg00x->lock); - if (dg00x->dev_lock_changed) + if (dg00x->dev_lock_changed || dg00x->msg) events = POLLIN | POLLRDNORM; else events = 0; diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c new file mode 100644 index 0000000..4568d98 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -0,0 +1,81 @@ +/* + * digi00x-transaction.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/asound.h> +#include "digi00x.h" + +static void handle_unknown_message(struct snd_dg00x *dg00x, + unsigned long long offset, u32 *buf) +{ + unsigned long flags; + + spin_lock_irqsave(&dg00x->lock, flags); + dg00x->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&dg00x->lock, flags); + + wake_up(&dg00x->hwdep_wait); +} + +static void handle_message(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 snd_dg00x *dg00x = callback_data; + u32 *buf = (__be32 *)data; + + if (offset == dg00x->async_handler.offset) + handle_unknown_message(dg00x, offset, buf); + + fw_send_response(card, request, RCODE_COMPLETE); +} + +int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) +{ + struct fw_device *device = fw_parent_device(dg00x->unit); + __be32 data[2]; + + /* Unknown. 4bytes. */ + data[0] = cpu_to_be32((device->card->node_id << 16) | + (dg00x->async_handler.offset >> 32)); + data[1] = cpu_to_be32(dg00x->async_handler.offset); + return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, + &data, sizeof(data), 0); +} + +int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + dg00x->async_handler.length = 4; + dg00x->async_handler.address_callback = handle_message; + dg00x->async_handler.callback_data = dg00x; + + err = fw_core_add_address_handler(&dg00x->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_dg00x_transaction_reregister(dg00x); + if (err < 0) { + fw_core_remove_address_handler(&dg00x->async_handler); + dg00x->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) +{ + fw_core_remove_address_handler(&dg00x->async_handler); +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c index 0f2c331..c93cb2827 100644 --- a/sound/firewire/digi00x/digi00x.c +++ b/sound/firewire/digi00x/digi00x.c @@ -48,6 +48,7 @@ static void dg00x_card_free(struct snd_card *card) struct snd_dg00x *dg00x = card->private_data;
snd_dg00x_stream_destroy_duplex(dg00x); + snd_dg00x_transaction_unregister(dg00x);
fw_unit_put(dg00x->unit);
@@ -99,6 +100,10 @@ static int snd_dg00x_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_dg00x_transaction_register(dg00x); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -115,6 +120,8 @@ static void snd_dg00x_update(struct fw_unit *unit) { struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
+ snd_dg00x_transaction_reregister(dg00x); + mutex_lock(&dg00x->mutex); snd_dg00x_stream_update_duplex(dg00x); mutex_unlock(&dg00x->mutex); diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 2776408..1ecc4a7 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -51,6 +51,9 @@ struct snd_dg00x { bool dev_lock_changed; wait_queue_head_t hwdep_wait;
+ /* For asynchronous messages. */ + struct fw_address_handler async_handler; + u32 msg; };
#define DG00X_ADDR_BASE 0xffffe0000000ull @@ -112,6 +115,10 @@ void amdtp_dot_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, struct snd_rawmidi_substream *midi);
+int snd_dg00x_transaction_register(struct snd_dg00x *dg00x); +int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x); +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x); + extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT]; extern const unsigned int snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT]; int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x,
Digi 002/003 family uses asynchronous transactions for MIDI device control message. The address to receive the messages is 0xffffe0000040.
This commit supports these MIDI ports.
Cc: Damien Zammit damien@zamaudio.com Cc: Robin Gareus robin@gareus.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/digi00x/digi00x-midi.c | 65 ++++++++++++++++++++------ sound/firewire/digi00x/digi00x-transaction.c | 70 +++++++++++++++++++++++++--- sound/firewire/digi00x/digi00x.h | 5 ++ 3 files changed, 118 insertions(+), 22 deletions(-)
diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c index 241d93a..9051902 100644 --- a/sound/firewire/digi00x/digi00x-midi.c +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -13,6 +13,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) struct snd_dg00x *dg00x = substream->rmidi->private_data; int err;
+ /* This port is for Asynchronous transaction. */ + if (substream->number == 0) + return 0; + err = snd_dg00x_stream_lock_try(dg00x); if (err < 0) return err; @@ -32,6 +36,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) struct snd_dg00x *dg00x = substream->rmidi->private_data; int err;
+ /* This port is for Asynchronous transaction. */ + if (substream->number == 0) + return 0; + err = snd_dg00x_stream_lock_try(dg00x); if (err < 0) return err; @@ -50,6 +58,9 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data;
+ if (substream->number == 0) + return 0; + mutex_lock(&dg00x->mutex); dg00x->substreams_counter--; snd_dg00x_stream_stop_duplex(dg00x); @@ -63,6 +74,11 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream) { struct snd_dg00x *dg00x = substream->rmidi->private_data;
+ if (substream->number == 0) { + snd_fw_async_midi_port_finish(&dg00x->out_control); + return 0; + } + mutex_lock(&dg00x->mutex); dg00x->substreams_counter--; snd_dg00x_stream_stop_duplex(dg00x); @@ -79,12 +95,19 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
spin_lock_irqsave(&dg00x->lock, flags);
- if (up) - amdtp_dot_midi_trigger(&dg00x->tx_stream, - substrm->number - 1, substrm); - else - amdtp_dot_midi_trigger(&dg00x->tx_stream, - substrm->number - 1, NULL); + if (substrm->number == 0) { + if (up) + dg00x->in_control = substrm; + else + dg00x->in_control = NULL; + } else { + if (up) + amdtp_dot_midi_trigger(&dg00x->tx_stream, + substrm->number - 1, substrm); + else + amdtp_dot_midi_trigger(&dg00x->tx_stream, + substrm->number - 1, NULL); + }
spin_unlock_irqrestore(&dg00x->lock, flags); } @@ -96,12 +119,18 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
spin_lock_irqsave(&dg00x->lock, flags);
- if (up) - amdtp_dot_midi_trigger(&dg00x->rx_stream, - substrm->number - 1, substrm); - else - amdtp_dot_midi_trigger(&dg00x->rx_stream, - substrm->number - 1, NULL); + if (substrm->number == 0) { + if (up) + snd_fw_async_midi_port_run(&dg00x->out_control, + substrm); + } else { + if (up) + amdtp_dot_midi_trigger(&dg00x->rx_stream, + substrm->number - 1, substrm); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, + substrm->number - 1, NULL); + }
spin_unlock_irqrestore(&dg00x->lock, flags); } @@ -124,9 +153,15 @@ static void set_midi_substream_names(struct snd_dg00x *dg00x, struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) { - snprintf(subs->name, sizeof(subs->name), - "%s MIDI %d", - dg00x->card->shortname, subs->number); + /* This port is for device control. */ + if (subs->number == 0) { + snprintf(subs->name, sizeof(subs->name), + "%s control", dg00x->card->shortname); + } else { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + dg00x->card->shortname, subs->number); + } } }
diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c index 4568d98..5c66b17 100644 --- a/sound/firewire/digi00x/digi00x-transaction.c +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -9,6 +9,18 @@ #include <sound/asound.h> #include "digi00x.h"
+static int packetize_message(struct snd_rawmidi_substream *substream, __u8 *buf) +{ + int bytes; + + buf[0] = 0x80; + bytes = snd_rawmidi_transmit_peek(substream, buf + 1, 2); + if (bytes >= 0) + buf[3] = 0xc0 | bytes; + + return bytes; +} + static void handle_unknown_message(struct snd_dg00x *dg00x, unsigned long long offset, u32 *buf) { @@ -21,6 +33,28 @@ static void handle_unknown_message(struct snd_dg00x *dg00x, wake_up(&dg00x->hwdep_wait); }
+static void handle_midi_control(struct snd_dg00x *dg00x, u32 *buf, + unsigned int length) +{ + struct snd_rawmidi_substream *substream; + unsigned int i; + unsigned int len; + u8 *b; + + substream = ACCESS_ONCE(dg00x->in_control); + if (substream == NULL) + return; + + length /= 4; + + for (i = 0; i < length; i++) { + b = (u8 *)&buf[i]; + len = b[3] & 0xf; + if (len > 0) + snd_rawmidi_receive(dg00x->in_control, b + 1, len); + } +} + static void handle_message(struct fw_card *card, struct fw_request *request, int tcode, int destination, int source, int generation, unsigned long long offset, @@ -31,6 +65,8 @@ static void handle_message(struct fw_card *card, struct fw_request *request,
if (offset == dg00x->async_handler.offset) handle_unknown_message(dg00x, offset, buf); + else if (offset == dg00x->async_handler.offset + 4) + handle_midi_control(dg00x, buf, length);
fw_send_response(card, request, RCODE_COMPLETE); } @@ -39,14 +75,25 @@ int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) { struct fw_device *device = fw_parent_device(dg00x->unit); __be32 data[2]; + int err;
/* Unknown. 4bytes. */ data[0] = cpu_to_be32((device->card->node_id << 16) | (dg00x->async_handler.offset >> 32)); data[1] = cpu_to_be32(dg00x->async_handler.offset); + err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, + &data, sizeof(data), 0); + if (err < 0) + return err; + + /* Asynchronous transactions for MIDI control message. */ + data[0] = cpu_to_be32((device->card->node_id << 16) | + (dg00x->async_handler.offset >> 32)); + data[1] = cpu_to_be32(dg00x->async_handler.offset + 4); return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, - DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, - &data, sizeof(data), 0); + DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR, + &data, sizeof(data), 0); }
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) @@ -57,7 +104,7 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) }; int err;
- dg00x->async_handler.length = 4; + dg00x->async_handler.length = 12; dg00x->async_handler.address_callback = handle_message; dg00x->async_handler.callback_data = dg00x;
@@ -67,15 +114,24 @@ int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) return err;
err = snd_dg00x_transaction_reregister(dg00x); - if (err < 0) { - fw_core_remove_address_handler(&dg00x->async_handler); - dg00x->async_handler.address_callback = NULL; - } + if (err < 0) + goto error; + + err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit, + DG00X_ADDR_BASE + DG00X_OFFSET_MMC, + 4, packetize_message); + if (err < 0) + goto error;
return err; +error: + fw_core_remove_address_handler(&dg00x->async_handler); + dg00x->async_handler.address_callback = NULL; + return err; }
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) { + snd_fw_async_midi_port_destroy(&dg00x->out_control); fw_core_remove_address_handler(&dg00x->async_handler); } diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h index 1ecc4a7..0d133ac 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -54,6 +54,11 @@ struct snd_dg00x { /* For asynchronous messages. */ struct fw_address_handler async_handler; u32 msg; + + /* For asynchronous MIDI controls. */ + struct tasklet_struct tasklet; + struct snd_rawmidi_substream *in_control; + struct snd_fw_async_midi_port out_control; };
#define DG00X_ADDR_BASE 0xffffe0000000ull
This commit adds a new driver for TASCAM FireWire series. In this commit, this driver just creates/removes card instance according to bus event. More functionalities will be added in following commits.
TASCAM FireWire series consists of: * PDI 1394P23 for IEEE 1394 PHY layer * PDI 1394L40 for IEEE 1394 LINK layer and IEC 61883 interface * XILINX XC9536XL * XILINX Spartan-II XC2S100 * ATMEL AT91M42800A
I observed that these models don't work properly with 1394 OHCI controller based on VT6315. It may be due to the PHY/LINK layer issues. Using 1394 OHCI controller produced by the other vendors such as Texas Instruments may enable to work. Or adding another node on the bus.
Ilya Zimnovich had investigated TASCAM FireWire series in 2011, and discover some features of his FW-1804. You can see a part of his research in FFADO project. http://subversion.ffado.org/wiki/Tascam
Some of my work are based on Ilya's investigation, while this series doesn't support the FW_1804, because of a lack of config ROM information and its protocol detail, especially for PCM channels.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 11 +++ sound/firewire/Makefile | 1 + sound/firewire/tascam/Makefile | 2 + sound/firewire/tascam/tascam.c | 155 +++++++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 29 ++++++++ 5 files changed, 198 insertions(+) create mode 100644 sound/firewire/tascam/Makefile create mode 100644 sound/firewire/tascam/tascam.c create mode 100644 sound/firewire/tascam/tascam.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 80f3b57..ce51fdd 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -135,4 +135,15 @@ config SND_FIREWIRE_DIGI00X To compile this driver as a module, choose M here: the module will be called snd-firewire-digi00x.
+config SND_FIREWIRE_TASCAM + tristate "TASCAM FireWire series support" + select SND_FIREWIRE_LIB + help + Say Y here to include support for TASCAM. + * FW-1884 + * FW-1804 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-tascam. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 5325d15..6ae50f5 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile new file mode 100644 index 0000000..29bdbce --- /dev/null +++ b/sound/firewire/tascam/Makefile @@ -0,0 +1,2 @@ +snd-firewire-tascam-objs := tascam.o +obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c new file mode 100644 index 0000000..9f2d2a3 --- /dev/null +++ b/sound/firewire/tascam/tascam.c @@ -0,0 +1,155 @@ +/* + * tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +MODULE_DESCRIPTION("TASCAM FireWire series Driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static int check_name(struct snd_tscm *tscm) +{ + struct fw_device *fw_dev = fw_parent_device(tscm->unit); + char vendor[8]; + char model[8]; + __u32 data; + + /* Retrieve model name. */ + data = be32_to_cpu(fw_dev->config_rom[28]); + memcpy(model, &data, 4); + data = be32_to_cpu(fw_dev->config_rom[29]); + memcpy(model + 4, &data, 4); + model[7] = '\0'; + + /* Retrieve vendor name. */ + data = be32_to_cpu(fw_dev->config_rom[23]); + memcpy(vendor, &data, 4); + data = be32_to_cpu(fw_dev->config_rom[24]); + memcpy(vendor + 4, &data, 4); + vendor[7] = '\0'; + + strcpy(tscm->card->driver, "FW-TASCAM"); + strcpy(tscm->card->shortname, model); + strcpy(tscm->card->mixername, model); + snprintf(tscm->card->longname, sizeof(tscm->card->longname), + "%s %s, GUID %08x%08x at %s, S%d", vendor, model, + cpu_to_be32(fw_dev->config_rom[3]), + cpu_to_be32(fw_dev->config_rom[4]), + dev_name(&tscm->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void tscm_card_free(struct snd_card *card) +{ + struct snd_tscm *tscm = card->private_data; + + fw_unit_put(tscm->unit); + + mutex_destroy(&tscm->mutex); +} + +static int snd_tscm_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_tscm *tscm; + int err; + + /* create card */ + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(struct snd_tscm), &card); + if (err < 0) + return err; + card->private_free = tscm_card_free; + + /* initialize myself */ + tscm = card->private_data; + tscm->card = card; + tscm->unit = fw_unit_get(unit); + + mutex_init(&tscm->mutex); + + err = check_name(tscm); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, tscm); + + return err; +error: + snd_card_free(card); + return err; +} + +static void snd_tscm_update(struct fw_unit *unit) +{ + return; +} + +static void snd_tscm_remove(struct fw_unit *unit) +{ + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(tscm->card); +} + +static const struct ieee1394_device_id snd_tscm_id_table[] = { + /* FW-1082 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800003, + }, + /* FW-1884 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800000, + }, + /* FW-1804 mey be supported if IDs are clear. */ + /* FE-08 requires reverse-engineering because it just has faders. */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_tscm_id_table); + +static struct fw_driver tscm_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-firewire-tascam", + .bus = &fw_bus_type, + }, + .probe = snd_tscm_probe, + .update = snd_tscm_update, + .remove = snd_tscm_remove, + .id_table = snd_tscm_id_table, +}; + +static int __init snd_tscm_init(void) +{ + return driver_register(&tscm_driver.driver); +} + +static void __exit snd_tscm_exit(void) +{ + driver_unregister(&tscm_driver.driver); +} + +module_init(snd_tscm_init); +module_exit(snd_tscm_exit); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h new file mode 100644 index 0000000..8aa5852 --- /dev/null +++ b/sound/firewire/tascam/tascam.h @@ -0,0 +1,29 @@ +/* + * tascam.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/firewire.h> + +#include "../lib.h" + +struct snd_tscm { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; +};
TASCAM FireWire series doesn't tell drivers their capabilities, thus the drivers should have model-dependent parameters and apply it to detected models.
This commit adds a structure to represent such parameters.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/tascam.c | 44 ++++++++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 13 +++++++++++++ 2 files changed, 57 insertions(+)
diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 9f2d2a3..73205077 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -12,12 +12,46 @@ MODULE_DESCRIPTION("TASCAM FireWire series Driver"); MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); MODULE_LICENSE("GPL v2");
+static struct snd_tscm_spec model_specs[] = { + { + .name = "FW-1884", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 8, + .midi_capture_ports = 4, + .midi_playback_ports = 4, + .is_controller = true, + }, + { + .name = "FW-1804", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 4, + .is_controller = false, + }, + { + .name = "FW-1082", + .has_adat = false, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 5, + .midi_playback_ports = 5, + .is_controller = true, + }, +}; + static int check_name(struct snd_tscm *tscm) { struct fw_device *fw_dev = fw_parent_device(tscm->unit); char vendor[8]; char model[8]; __u32 data; + unsigned int i;
/* Retrieve model name. */ data = be32_to_cpu(fw_dev->config_rom[28]); @@ -26,6 +60,16 @@ static int check_name(struct snd_tscm *tscm) memcpy(model + 4, &data, 4); model[7] = '\0';
+ /* Check the name and set spec. */ + for (i = 0; i < ARRAY_SIZE(model_specs); i++) { + if (strcmp(model, model_specs[i].name) == 0) { + tscm->spec = &model_specs[i]; + break; + } + } + if (i == ARRAY_SIZE(model_specs)) + return -ENODEV; + /* Retrieve vendor name. */ data = be32_to_cpu(fw_dev->config_rom[23]); memcpy(vendor, &data, 4); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 8aa5852..dfe08be 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -21,9 +21,22 @@
#include "../lib.h"
+struct snd_tscm_spec { + const char *const name; + bool has_adat; + bool has_spdif; + unsigned int pcm_capture_analog_channels; + unsigned int pcm_playback_analog_channels; + unsigned int midi_capture_ports; + unsigned int midi_playback_ports; + bool is_controller; +}; + struct snd_tscm { struct snd_card *card; struct fw_unit *unit;
struct mutex mutex; + + struct snd_tscm_spec *spec; };
TASCAM FireWire series has certain registers for firmware information.
This commit adds proc node to show the information.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-proc.c | 88 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 2 + sound/firewire/tascam/tascam.h | 10 +++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/tascam-proc.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 29bdbce..63f08c6 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,2 +1,2 @@ -snd-firewire-tascam-objs := tascam.o +snd-firewire-tascam-objs := tascam-proc.o tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-proc.c b/sound/firewire/tascam/tascam-proc.c new file mode 100644 index 0000000..bfd4a4c --- /dev/null +++ b/sound/firewire/tascam/tascam-proc.c @@ -0,0 +1,88 @@ +/* + * tascam-proc.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./tascam.h" + +static void proc_read_firmware(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_tscm *tscm = entry->private_data; + __be32 data; + unsigned int reg, fpga, arm, hw; + int err; + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_REGISTER, + &data, sizeof(data), 0); + if (err < 0) + return; + reg = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_FPGA, + &data, sizeof(data), 0); + if (err < 0) + return; + fpga = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_ARM, + &data, sizeof(data), 0); + if (err < 0) + return; + arm = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_HW, + &data, sizeof(data), 0); + if (err < 0) + return; + hw = be32_to_cpu(data); + + snd_iprintf(buffer, "Register: %d (0x%08x)\n", reg & 0xffff, reg); + snd_iprintf(buffer, "FPGA: %d (0x%08x)\n", fpga & 0xffff, fpga); + snd_iprintf(buffer, "ARM: %d (0x%08x)\n", arm & 0xffff, arm); + snd_iprintf(buffer, "Hardware: %d (0x%08x)\n", hw >> 16, hw); +} + +static void add_node(struct snd_tscm *tscm, 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(tscm->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, tscm, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_tscm_proc_init(struct snd_tscm *tscm) +{ + 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(tscm->card, "firewire", + tscm->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(tscm, root, "firmware", proc_read_firmware); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 73205077..e4685c6 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -123,6 +123,8 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_tscm_proc_init(tscm); + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index dfe08be..2a79f1b 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -18,6 +18,7 @@ #include <sound/core.h> #include <sound/initval.h> #include <sound/firewire.h> +#include <sound/info.h>
#include "../lib.h"
@@ -40,3 +41,12 @@ struct snd_tscm {
struct snd_tscm_spec *spec; }; + +#define TSCM_ADDR_BASE 0xffff00000000ull + +#define TSCM_OFFSET_FIRMWARE_REGISTER 0x0000 +#define TSCM_OFFSET_FIRMWARE_FPGA 0x0004 +#define TSCM_OFFSET_FIRMWARE_ARM 0x0008 +#define TSCM_OFFSET_FIRMWARE_HW 0x000c + +void snd_tscm_proc_init(struct snd_tscm *tscm);
TASCAM FireWire series uses non-blocking transmission for AMDTP packet streaming, while the format of data blocks is unique.
The CIP headers includes specific value in FMT field and no SYT information.
In transmitted packets, the first data channel represents event counter, and the last data channel has status and control information. The rest has 24bit PCM samples with right padding.
In received packets, all of data channels include 16, 24, 32bit PCM samples. There's no the other kind of information.
This commit adds support for this protocol. For convinience, the size of PCM samples in outgoing packet is 16, 24bit. The status and control information will be supported in a future commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/amdtp-tascam.c | 232 +++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 16 +++ 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/amdtp-tascam.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 63f08c6..5d11cad 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,2 +1,2 @@ -snd-firewire-tascam-objs := tascam-proc.o tascam.o +snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c new file mode 100644 index 0000000..22c2eaf --- /dev/null +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -0,0 +1,232 @@ +/* + * amdtp-tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm.h> +#include "tascam.h" + +#define AMDTP_FMT_TSCM_TX 0x1e +#define AMDTP_FMT_TSCM_RX 0x3e + +struct amdtp_tscm { + unsigned int pcm_channels; + + void (*transfer_samples)(struct amdtp_stream *s, + struct snd_pcm_runtime *pcm, + __be32 *buffer, unsigned int frames); +}; + +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + data_channels = p->pcm_channels; + if (s->direction == AMDTP_IN_STREAM) + data_channels += 2; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (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) { + buffer[c] = cpu_to_be32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + const u16 *src; + + channels = p->pcm_channels; + src = (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) { + buffer[c] = cpu_to_be32(*src << 16); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + /* The first data channel is for event counter. */ + buffer += 1; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[c]); + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = 0x00000000; + buffer += s->data_block_quadlets; + } +} + +void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) +{ + struct amdtp_tscm *p = s->protocol; + + if (WARN_ON(amdtp_stream_pcm_running(s))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + if (s->direction == AMDTP_OUT_STREAM) { + p->transfer_samples = write_pcm_s16; + break; + } + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S32: + if (s->direction == AMDTP_OUT_STREAM) + p->transfer_samples = write_pcm_s32; + else + p->transfer_samples = read_pcm_s32; + break; + } +} + +static void read_control_messages(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks) +{ + unsigned int first, index = 0; + unsigned int i; + + first = (buffer[0] >> 15) % 64; + + /* TODO */ + for (i = 0; i < data_blocks; i++) { + index = be32_to_cpu(buffer[0]) % 64; + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_tscm *p = (struct amdtp_tscm *)s->protocol; + struct snd_pcm_substream *pcm; + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + + read_control_messages(s, buffer, data_blocks); + + return data_blocks; +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct amdtp_tscm *p = (struct amdtp_tscm *)s->protocol; + struct snd_pcm_substream *pcm; + + /* This field is not used. */ + *syt = 0x0000; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + return data_blocks; +} + +int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels) +{ + struct amdtp_tscm *p; + int err; + + err = amdtp_stream_init(s, unit, dir, + CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, + sizeof(struct amdtp_tscm)); + if (err < 0) + return 0; + + if (dir == AMDTP_IN_STREAM) { + s->fmt = AMDTP_FMT_TSCM_TX; + s->process_data_blocks = process_tx_data_blocks; + } else { + s->fmt = AMDTP_FMT_TSCM_RX; + s->process_data_blocks = process_rx_data_blocks; + } + s->fdf = 0x00; + + p = s->protocol; + p->pcm_channels = pcm_channels; + + return 0; +} diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 2a79f1b..47f679a 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -19,8 +19,13 @@ #include <sound/initval.h> #include <sound/firewire.h> #include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h>
#include "../lib.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-stream.h"
struct snd_tscm_spec { const char *const name; @@ -40,6 +45,12 @@ struct snd_tscm { struct mutex mutex;
struct snd_tscm_spec *spec; + + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int substreams_counter; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -49,4 +60,9 @@ struct snd_tscm { #define TSCM_OFFSET_FIRMWARE_ARM 0x0008 #define TSCM_OFFSET_FIRMWARE_HW 0x000c
+int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels); +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate); +void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); + void snd_tscm_proc_init(struct snd_tscm *tscm);
This commit adds streaming functionality for both direction. To utilize the sequence of the number of data blocks in packets, full duplex with synchronization is applied.
TASCAM FireWire series allows drivers to decide which PCM data channels are enabled. For convinience, this driver always enable whole the data channels.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 3 +- sound/firewire/tascam/tascam-stream.c | 458 ++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 12 +- sound/firewire/tascam/tascam.h | 29 +++ 4 files changed, 500 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/tascam/tascam-stream.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 5d11cad..3be9434 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,2 +1,3 @@ -snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam.o +snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ + tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c new file mode 100644 index 0000000..deef60b --- /dev/null +++ b/sound/firewire/tascam/tascam-stream.c @@ -0,0 +1,458 @@ +/* + * tascam-stream.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "tascam.h" + +#define CALLBACK_TIMEOUT 500 + +static int get_clock(struct snd_tscm *tscm, __u32 *data) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err >= 0) + *data = be32_to_cpu(reg); + + return err; +} + +static int set_clock(struct snd_tscm *tscm, unsigned int rate, + enum snd_tscm_clock clock) +{ + __u32 data; + __be32 reg; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + data &= 0x0000ffff; + + if (rate > 0) { + data &= 0x000000ff; + /* Base rate. */ + if ((rate % 44100) == 0) { + data |= 0x00000100; + /* Multiplier. */ + if (rate / 44100 == 2) + data |= 0x00008000; + } else if ((rate % 48000) == 0) { + data |= 0x00000200; + /* Multiplier. */ + if (rate / 48000 == 2) + data |= 0x00008000; + } else { + return -EAGAIN; + } + } + + if (clock != INT_MAX) { + data &= 0x0000ff00; + data |= clock + 1; + } + + reg = cpu_to_be32(data); + + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + if (data & 0x00008000) + reg = cpu_to_be32(0x0000001a); + else + reg = cpu_to_be32(0x0000000d); + + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE, + ®, sizeof(reg), 0); +} + +int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate) +{ + __u32 data = 0x0; + unsigned int count = 0; + int err; + + while (data == 0x0 || count++ < 5) { + err = get_clock(tscm, &data); + if (err < 0) + return err; + + data = (data & 0xff000000) >> 24; + } + + /* Check base rate. */ + if ((data & 0x0f) == 0x01) + *rate = 44100; + else if ((data & 0x0f) == 0x02) + *rate = 48000; + else + return -EAGAIN; + + /* Check multiplier. */ + if ((data & 0xf0) == 0x80) + *rate *= 2; + else if ((data & 0xf0) != 0x00) + return -EAGAIN; + + return err; +} + +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock) +{ + __u32 data; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + + *clock = ((data & 0x00ff0000) >> 16) - 1; + if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT) + return -EIO; + + return 0; +} + +static int enable_data_channels(struct snd_tscm *tscm) +{ + struct snd_tscm_spec *spec = tscm->spec; + __be32 reg; + u32 data; + unsigned int i; + int err; + + data = 0; + for (i = 0; i < spec->pcm_capture_analog_channels; ++i) + data |= BIT(i); + if (spec->has_adat) + data |= 0x0000ff00; + if (spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = 0; + for (i = 0; i < spec->pcm_playback_analog_channels; ++i) + data |= BIT(i); + if (spec->has_adat) + data |= 0x0000ff00; + if (spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS, + ®, sizeof(reg), 0); +} + +static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + /* Set an option for unknown purpose. */ + reg = cpu_to_be32(0x00200000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + err = enable_data_channels(tscm); + if (err < 0) + return err; + + return set_clock(tscm, rate, INT_MAX); +} + +static void finish_session(struct snd_tscm *tscm) +{ + __be32 reg; + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + +} + +static int begin_session(struct snd_tscm *tscm) +{ + __be32 reg; + int err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Set an option for unknown purpose. */ + reg = cpu_to_be32(0x00002000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Start multiplexing PCM samples on packets. */ + reg = cpu_to_be32(0x00000001); + return snd_fw_transaction(tscm->unit, + TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON, + ®, sizeof(reg), 0); +} + +static void release_resources(struct snd_tscm *tscm) +{ + __be32 reg; + + /* Unregister channels. */ + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + + /* Release isochronous resources. */ + fw_iso_resources_free(&tscm->tx_resources); + fw_iso_resources_free(&tscm->rx_resources); +} + +static int keep_resources(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + /* Keep resources for in-stream. */ + err = amdtp_tscm_set_parameters(&tscm->tx_stream, rate); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&tscm->tx_resources, + amdtp_stream_get_max_payload(&tscm->tx_stream), + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + /* Keep resources for out-stream. */ + err = amdtp_tscm_set_parameters(&tscm->rx_stream, rate); + if (err < 0) + return err; + err = fw_iso_resources_allocate(&tscm->rx_resources, + amdtp_stream_get_max_payload(&tscm->rx_stream), + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + return err; + + /* Register the isochronous channel for transmitting stream. */ + reg = cpu_to_be32(tscm->tx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + /* Unknown */ + reg = cpu_to_be32(0x00000002); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + /* Register the isochronous channel for receiving stream. */ + reg = cpu_to_be32(tscm->rx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + if (err < 0) + goto error; + + return 0; +error: + release_resources(tscm); + return err; +} + +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) +{ + unsigned int pcm_channels; + int err; + + /* For out-stream. */ + err = fw_iso_resources_init(&tscm->rx_resources, tscm->unit); + if (err < 0) + return err; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + err = amdtp_tscm_init(&tscm->rx_stream, tscm->unit, AMDTP_OUT_STREAM, + pcm_channels); + if (err < 0) + return err; + + /* For in-stream. */ + err = fw_iso_resources_init(&tscm->tx_resources, tscm->unit); + if (err < 0) + return err; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + err = amdtp_tscm_init(&tscm->tx_stream, tscm->unit, AMDTP_IN_STREAM, + pcm_channels); + if (err < 0) + amdtp_stream_destroy(&tscm->rx_stream); + + return 0; +} + +/* At bus reset, streaming is stopped and some registers are clear. */ +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) +{ + amdtp_stream_pcm_abort(&tscm->tx_stream); + amdtp_stream_stop(&tscm->tx_stream); + + amdtp_stream_pcm_abort(&tscm->rx_stream); + amdtp_stream_stop(&tscm->rx_stream); +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) +{ + amdtp_stream_destroy(&tscm->rx_stream); + amdtp_stream_destroy(&tscm->tx_stream); + + fw_iso_resources_destroy(&tscm->rx_resources); + fw_iso_resources_destroy(&tscm->tx_resources); +} + +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) +{ + unsigned int curr_rate; + int err; + + if (tscm->substreams_counter == 0) + return 0; + + err = snd_tscm_stream_get_rate(tscm, &curr_rate); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&tscm->tx_stream) || + amdtp_streaming_error(&tscm->rx_stream)) { + finish_session(tscm); + + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + release_resources(tscm); + } + + if (!amdtp_stream_running(&tscm->tx_stream)) { + amdtp_stream_set_sync(CIP_SYNC_TO_DEVICE, + &tscm->tx_stream, &tscm->rx_stream); + err = keep_resources(tscm, rate); + if (err < 0) + goto error; + + err = set_stream_formats(tscm, rate); + if (err < 0) + goto error; + + err = begin_session(tscm); + if (err < 0) + goto error; + + err = amdtp_stream_start(&tscm->tx_stream, + tscm->tx_resources.channel, + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + if (!amdtp_stream_running(&tscm->rx_stream)) { + err = amdtp_stream_start(&tscm->rx_stream, + tscm->rx_resources.channel, + fw_parent_device(tscm->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + finish_session(tscm); + release_resources(tscm); + + return err; +} + +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) +{ + if (tscm->substreams_counter > 0) + return; + + amdtp_stream_stop(&tscm->tx_stream); + amdtp_stream_stop(&tscm->rx_stream); + + finish_session(tscm); + release_resources(tscm); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index e4685c6..c56b0a4 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -93,6 +93,8 @@ static void tscm_card_free(struct snd_card *card) { struct snd_tscm *tscm = card->private_data;
+ snd_tscm_stream_destroy_duplex(tscm); + fw_unit_put(tscm->unit);
mutex_destroy(&tscm->mutex); @@ -125,6 +127,10 @@ static int snd_tscm_probe(struct fw_unit *unit,
snd_tscm_proc_init(tscm);
+ err = snd_tscm_stream_init_duplex(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -139,7 +145,11 @@ error:
static void snd_tscm_update(struct fw_unit *unit) { - return; + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + mutex_lock(&tscm->mutex); + snd_tscm_stream_update_duplex(tscm); + mutex_unlock(&tscm->mutex); }
static void snd_tscm_remove(struct fw_unit *unit) diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 47f679a..729c9c8 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -60,9 +60,38 @@ struct snd_tscm { #define TSCM_OFFSET_FIRMWARE_ARM 0x0008 #define TSCM_OFFSET_FIRMWARE_HW 0x000c
+#define TSCM_OFFSET_ISOC_TX_CH 0x0200 +#define TSCM_OFFSET_UNKNOWN 0x0204 +#define TSCM_OFFSET_START_STREAMING 0x0208 +#define TSCM_OFFSET_ISOC_RX_CH 0x020c +#define TSCM_OFFSET_ISOC_RX_ON 0x0210 /* little conformance */ +#define TSCM_OFFSET_TX_PCM_CHANNELS 0x0214 +#define TSCM_OFFSET_RX_PCM_CHANNELS 0x0218 +#define TSCM_OFFSET_MULTIPLEX_MODE 0x021c +#define TSCM_OFFSET_ISOC_TX_ON 0x0220 +/* Unknown 0x0224 */ +#define TSCM_OFFSET_CLOCK_STATUS 0x0228 +#define TSCM_OFFSET_SET_OPTION 0x022c + +enum snd_tscm_clock { + SND_TSCM_CLOCK_INTERNAL = 0, + SND_TSCM_CLOCK_WORD = 1, + SND_TSCM_CLOCK_SPDIF = 2, + SND_TSCM_CLOCK_ADAT = 3, +}; + int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir, unsigned int pcm_channels); int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate); void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format);
+int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate); +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, + enum snd_tscm_clock *clock); +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm); + void snd_tscm_proc_init(struct snd_tscm *tscm);
This commit adds PCM functionality to transmit/receive PCM samples.
When one of PCM substreams are running or external clock source is selected, current sampling rate is used. Else, the sampling rate is changed as an userspace application requests.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-pcm.c | 295 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 2 + 4 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/tascam-pcm.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 3be9434..84391ac 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,3 +1,3 @@ snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ - tascam.o + tascam-pcm.o tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c new file mode 100644 index 0000000..b1bdb9f --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,295 @@ +/* + * tascam-pcm.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +static void set_buffer_params(struct snd_pcm_hardware *hw) +{ + hw->period_bytes_min = 4 * hw->channels_min; + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * 2; + + hw->periods_min = 2; + hw->periods_max = UINT_MAX; +} + +static int pcm_init_hw_params(struct snd_tscm *tscm, + struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .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, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000, + .rate_min = 44100, + .rate_max = 96000, + .channels_min = 10, + .channels_max = 18, + }; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int pcm_channels; + int err; + + runtime->hw = hardware; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + runtime->hw.channels_min = + runtime->hw.channels_max = pcm_channels; + } else { + runtime->hw.formats = + SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + runtime->hw.channels_min = + runtime->hw.channels_max = pcm_channels; + } + + set_buffer_params(&runtime->hw); + + /* Deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + goto end; + + /* 16 packets are handled in one callback. This is for safe. */ + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 5000, UINT_MAX); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + enum snd_tscm_clock clock; + unsigned int rate; + int err; + + err = pcm_init_hw_params(tscm, substream); + if (err < 0) + return err; + + err = snd_tscm_stream_get_clock(tscm, &clock); + if ((clock != SND_TSCM_CLOCK_INTERNAL) | + amdtp_stream_pcm_running(&tscm->rx_stream) | + amdtp_stream_pcm_running(&tscm->tx_stream)) { + err = snd_tscm_stream_get_rate(tscm, &rate); + if (err < 0) + return err; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + 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_tscm *tscm = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&tscm->mutex); + tscm->substreams_counter++; + mutex_unlock(&tscm->mutex); + } + amdtp_tscm_set_pcm_format(&tscm->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_tscm *tscm = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&tscm->mutex); + tscm->substreams_counter++; + mutex_unlock(&tscm->mutex); + } + amdtp_tscm_set_pcm_format(&tscm->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_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + tscm->substreams_counter--; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + tscm->substreams_counter--; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->tx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->rx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&tscm->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&tscm->rx_stream); +} + +static 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 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_tscm_create_pcm_devices(struct snd_tscm *tscm) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(tscm->card, tscm->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = tscm; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", tscm->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); + + return 0; +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index c56b0a4..ca3af62 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -131,6 +131,10 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_tscm_create_pcm_devices(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 729c9c8..feaf7e1 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -95,3 +95,5 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm);
void snd_tscm_proc_init(struct snd_tscm *tscm); + +int snd_tscm_create_pcm_devices(struct snd_tscm *tscm);
TASCAM FireWire series use asynchronous transaction for transmittion of MIDI messages. The messages in the transaction include quirks: * One transaction transfers one MIDI message. * MIDI running status is disallowed, thus it always have status byte.
This commit supports the transmittion.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-transaction.c | 234 +++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 7 + sound/firewire/tascam/tascam.h | 25 +++ 4 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/tascam-transaction.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 84391ac..1f39e02 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,3 +1,3 @@ snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ - tascam-pcm.o tascam.o + tascam-pcm.o tascam-transaction.o tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c new file mode 100644 index 0000000..c27e585 --- /dev/null +++ b/sound/firewire/tascam/tascam-transaction.c @@ -0,0 +1,234 @@ +/* + * tascam-transaction.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +static int packetize_message(struct snd_rawmidi_substream *substream, __u8 *buf) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + unsigned int len; + + /* Construct payload. */ + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + len = snd_rawmidi_transmit_peek(substream, buf + 1, 3); + + /* 2 or 3 bytes are required to construct 3 byte message. */ + if (len < 2) + return 0; + + /* Running-status is not allowed. */ + if (!(buf[1] & 0x80)) { + buf[3] = buf[2]; + buf[2] = buf[1]; + buf[1] = tscm->running_status[substream->number]; + len = 2; + } else { + tscm->running_status[substream->number] = buf[1]; + } + + /* 2 byte messages are handled as 3 byte message. */ + if ((buf[1] & 0xc0) == 0xc0 || (buf[1] & 0xd0) == 0xd0) { + buf[3] = 0x00; + len = 2; + } + + buf[0] = (substream->number << 4) | (buf[1] >> 4); + + return len; +} + +/* Import from scs1x.c */ +static inline bool is_one_byte_cmd(u8 status) +{ + return status == 0xf6 || + status >= 0xf8; +} +static inline bool is_two_bytes_cmd(u8 status) +{ + return (status >= 0xc0 && status <= 0xdf) || + status == 0xf1 || + status == 0xf3; +} +static inline bool is_three_bytes_cmd(u8 status) +{ + return (status >= 0x80 && status <= 0xbf) || + (status >= 0xe0 && status <= 0xef) || + status == 0xf2; +} +static inline bool is_invalid_cmd(u8 status) +{ + return status == 0xf4 || + status == 0xf5 || + status == 0xf9 || + status == 0xfd; +} + +/* + * Use the same range of address for asynchronous messages from any devices, to + * save resources on host controller. + */ +static void handle_midi_tx(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 snd_tscm *tscm = callback_data; + __u32 *buf = (__u32 *)data; + unsigned int messages; + unsigned int i; + unsigned int port; + struct snd_rawmidi_substream *substream; + __u8 *b; + unsigned int bytes; + + if (offset != tscm->async_handler.offset) + goto end; + + messages = length / 8; + for (i = 0; i < messages; i++) { + b = (__u8 *)(buf + i * 2); + + if (is_invalid_cmd(b[1])) + goto end; + else if (is_one_byte_cmd(b[1])) + bytes = 1; + else if (is_two_bytes_cmd(b[1])) + bytes = 2; + else if (is_three_bytes_cmd(b[1])) + bytes = 3; + else + goto end; + + port = b[0] >> 4; + if (port >= tscm->spec->midi_capture_ports) { + /* 0x5 = VP1, 0x6 = VP2. */ + port = tscm->spec->midi_capture_ports + port - 5; + if (port >= TSCM_MIDI_IN_PORT_MAX) + goto end; + } + + substream = ACCESS_ONCE(tscm->tx_midi_substreams[port]); + if (substream != NULL) + snd_rawmidi_receive(substream, b + 1, bytes); + } +end: + fw_send_response(card, request, RCODE_COMPLETE); +} + +int snd_tscm_transaction_register(struct snd_tscm *tscm) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + unsigned int i; + int err; + + /* + * Usually, two quadlets are transferred by one transaction. The first + * quadlet has MIDI messages, the rest includes timestamp. + * Sometimes, 8 set of the data is transferred by a block transaction. + */ + tscm->async_handler.length = 8 * 8; + tscm->async_handler.address_callback = handle_midi_tx; + tscm->async_handler.callback_data = tscm; + + err = fw_core_add_address_handler(&tscm->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_tscm_transaction_reregister(tscm); + if (err < 0) { + fw_core_remove_address_handler(&tscm->async_handler); + return err; + } + + for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { + err = snd_fw_async_midi_port_init( + &tscm->out_ports[i], tscm->unit, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, + 4, packetize_message); + if (err < 0) + break; + } + + return err; +} + +/* At bus reset, these registers are cleared. */ +int snd_tscm_transaction_reregister(struct snd_tscm *tscm) +{ + struct fw_device *device = fw_parent_device(tscm->unit); + __be32 reg; + int err; + + /* Register messaging address. Block transaction is not allowed. */ + reg = cpu_to_be32((device->card->node_id << 16) | + (tscm->async_handler.offset >> 32)); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(tscm->async_handler.offset); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Turn on messaging. */ + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Turn on FireWire LED. */ + reg = cpu_to_be32(0x0001008e); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_LED_TURN_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(0x000100f2); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_LED_TURN_ON, + ®, sizeof(reg), 0); +} + +void snd_tscm_transaction_unregister(struct snd_tscm *tscm) +{ + unsigned int i; + __be32 reg; + + for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) + snd_fw_async_midi_port_destroy(&tscm->out_ports[i]); + + fw_core_remove_address_handler(&tscm->async_handler); + + /* Turn off messaging. */ + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, + ®, sizeof(reg), 0); + + /* Unregister the address. */ + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, + ®, sizeof(reg), 0); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, + ®, sizeof(reg), 0); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index ca3af62..944c4ce 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -93,6 +93,7 @@ static void tscm_card_free(struct snd_card *card) { struct snd_tscm *tscm = card->private_data;
+ snd_tscm_transaction_unregister(tscm); snd_tscm_stream_destroy_duplex(tscm);
fw_unit_put(tscm->unit); @@ -135,6 +136,10 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_tscm_transaction_register(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -151,6 +156,8 @@ static void snd_tscm_update(struct fw_unit *unit) { struct snd_tscm *tscm = dev_get_drvdata(&unit->device);
+ snd_tscm_transaction_reregister(tscm); + mutex_lock(&tscm->mutex); snd_tscm_stream_update_duplex(tscm); mutex_unlock(&tscm->mutex); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index feaf7e1..5b0ae75 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -21,6 +21,7 @@ #include <sound/info.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/rawmidi.h>
#include "../lib.h" #include "../packets-buffer.h" @@ -38,6 +39,9 @@ struct snd_tscm_spec { bool is_controller; };
+#define TSCM_MIDI_IN_PORT_MAX 7 +#define TSCM_MIDI_OUT_PORT_MAX 4 + struct snd_tscm { struct snd_card *card; struct fw_unit *unit; @@ -51,6 +55,15 @@ struct snd_tscm { struct amdtp_stream tx_stream; struct amdtp_stream rx_stream; unsigned int substreams_counter; + + /* For MIDI message incoming transactions. */ + struct fw_address_handler async_handler; + struct snd_rawmidi_substream *tx_midi_substreams[TSCM_MIDI_IN_PORT_MAX]; + u8 program_change_quirk[TSCM_MIDI_IN_PORT_MAX]; + + /* For MIDI message outgoing transactions. */ + struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; + u8 running_status[TSCM_MIDI_OUT_PORT_MAX]; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -73,6 +86,14 @@ struct snd_tscm { #define TSCM_OFFSET_CLOCK_STATUS 0x0228 #define TSCM_OFFSET_SET_OPTION 0x022c
+#define TSCM_OFFSET_MIDI_TX_ON 0x0300 +#define TSCM_OFFSET_MIDI_TX_ADDR_HI 0x0304 +#define TSCM_OFFSET_MIDI_TX_ADDR_LO 0x0308 + +#define TSCM_OFFSET_LED_TURN_ON 0x0404 + +#define TSCM_OFFSET_MIDI_RX_QUAD 0x4000 + enum snd_tscm_clock { SND_TSCM_CLOCK_INTERNAL = 0, SND_TSCM_CLOCK_WORD = 1, @@ -94,6 +115,10 @@ void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm);
+int snd_tscm_transaction_register(struct snd_tscm *tscm); +int snd_tscm_transaction_reregister(struct snd_tscm *tscm); +void snd_tscm_transaction_unregister(struct snd_tscm *tscm); + void snd_tscm_proc_init(struct snd_tscm *tscm);
int snd_tscm_create_pcm_devices(struct snd_tscm *tscm);
This commit adds MIDI functionality to transfer/receive MIDI messages.
Console models supports virtual MIDI ports additional to physical MIDI ports. Once physical controls are assigned to the virtual MIDI ports, userspace applications can receive/transmit MIDI messages from/to the MIDI ports. Additionally, without assignment, physical controls can transmit control events in isochronous packets. This messages are supported in later commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/tascam/Makefile | 3 +- sound/firewire/tascam/tascam-midi.c | 158 ++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 3 + 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/tascam/tascam-midi.c
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index 1f39e02..fcfe125 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,3 +1,4 @@ snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ - tascam-pcm.o tascam-transaction.o tascam.o + tascam-pcm.o tascam-transaction.o tascam-midi.o \ + tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c new file mode 100644 index 0000000..fe3384b --- /dev/null +++ b/sound/firewire/tascam/tascam-midi.c @@ -0,0 +1,158 @@ +/* + * tascam-midi.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "tascam.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* TODO: pick up hardware control messages as MIDI? */ + if (substream->pstr->substream_count == substream->number) + return -1; + + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + /* Nothing to do. */ + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* TODO: pick up hardware control messages as MIDI? */ + if (substream->pstr->substream_count == substream->number) + return -1; + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + + snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_tscm *tscm = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&tscm->lock, flags); + + /* TODO: pick up hardware control messages as MIDI? */ + if (substrm->pstr->substream_count == substrm->number) { + return; + } else { + if (up) + tscm->tx_midi_substreams[substrm->number] = substrm; + else + tscm->tx_midi_substreams[substrm->number] = NULL; + } + + spin_unlock_irqrestore(&tscm->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_tscm *tscm = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&tscm->lock, flags); + + if (up) + snd_fw_async_midi_port_run(&tscm->out_ports[substrm->number], + substrm); + + spin_unlock_irqrestore(&tscm->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, +}; + +int snd_tscm_create_midi_devices(struct snd_tscm *tscm) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + unsigned in_ports, out_ports; + struct snd_rawmidi_substream *subs; + int err; + + in_ports = tscm->spec->midi_capture_ports; + out_ports = tscm->spec->midi_playback_ports; + + /* Controllers have additional 3 ports for output. */ + if (tscm->spec->is_controller) + in_ports += 3; + + err = snd_rawmidi_new(tscm->card, tscm->card->driver, 0, + out_ports, in_ports, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", tscm->card->shortname); + rmidi->private_data = tscm; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + /* Set port names for MIDI input. */ + list_for_each_entry(subs, &stream->substreams, list) { + if (subs->number < tscm->spec->midi_capture_ports) { + /* Hardware MIDI ports. */ + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + tscm->card->shortname, subs->number + 1); + } else if (subs->number < tscm->spec->midi_capture_ports + 2) { + /* Virtual MIDI ports. */ + snprintf(subs->name, sizeof(subs->name), + "%s virtual MIDI %d", + tscm->card->shortname, + subs->number - + tscm->spec->midi_capture_ports + 1); + } else if (tscm->spec->is_controller) { + /* Hardware controls. */ + snprintf(subs->name, sizeof(subs->name), + "%s control", tscm->card->shortname); + } + } + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + /* Set port names for MIDI ourput. */ + list_for_each_entry(subs, &stream->substreams, list) { + if (subs->number < tscm->spec->midi_capture_ports) { + /* Hardware MIDI ports only. */ + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + tscm->card->shortname, subs->number + 1); + } + } + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 944c4ce..5523a57 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -140,6 +140,10 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_tscm_create_midi_devices(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 5b0ae75..732b410 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -47,6 +47,7 @@ struct snd_tscm { struct fw_unit *unit;
struct mutex mutex; + spinlock_t lock;
struct snd_tscm_spec *spec;
@@ -122,3 +123,5 @@ void snd_tscm_transaction_unregister(struct snd_tscm *tscm); void snd_tscm_proc_init(struct snd_tscm *tscm);
int snd_tscm_create_pcm_devices(struct snd_tscm *tscm); + +int snd_tscm_create_midi_devices(struct snd_tscm *tscm);
This commit adds hwdep interface so as the other firewire sound devices has.
This interface is designed for mixer/control applications. By using this interface, an 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/tascam/Makefile | 2 +- sound/firewire/tascam/tascam-hwdep.c | 202 ++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam-pcm.c | 17 ++- sound/firewire/tascam/tascam-stream.c | 39 +++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 11 ++ 9 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 sound/firewire/tascam/tascam-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index aa32913..a82108e 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -101,9 +101,10 @@ enum { SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based device */ SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */ SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ + SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DIGI00X + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_TASCAM };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index deb041c..db79a12 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -64,6 +64,7 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_BEBOB 3 #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 +#define SNDRV_FIREWIRE_TYPE_TASCAM 6 /* RME, MOTU, ... */
struct snd_firewire_get_info { diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index ce51fdd..12993fa 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -138,6 +138,7 @@ config SND_FIREWIRE_DIGI00X config SND_FIREWIRE_TASCAM tristate "TASCAM FireWire series support" select SND_FIREWIRE_LIB + select SND_HWDEP help Say Y here to include support for TASCAM. * FW-1884 diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile index fcfe125..983d20d 100644 --- a/sound/firewire/tascam/Makefile +++ b/sound/firewire/tascam/Makefile @@ -1,4 +1,4 @@ snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ tascam-pcm.o tascam-transaction.o tascam-midi.o \ - tascam.o + tascam-hwdep.o tascam.o obj-m += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c new file mode 100644 index 0000000..4b539f5 --- /dev/null +++ b/sound/firewire/tascam/tascam-hwdep.c @@ -0,0 +1,202 @@ +/* + * tascam-hwdep.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + * 4.get asynchronous messaging + */ + +#include "tascam.h" + +static long hwdep_read_locked(struct snd_tscm *tscm, char __user *buf, + long count) +{ + union snd_firewire_event event; + + memset(&event, 0, sizeof(event)); + + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (tscm->dev_lock_count > 0); + tscm->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_tscm *tscm = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&tscm->lock); + + while (!tscm->dev_lock_changed) { + prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&tscm->lock); + schedule(); + finish_wait(&tscm->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&tscm->lock); + } + + memset(&event, 0, sizeof(event)); + count = hwdep_read_locked(tscm, buf, count); + spin_unlock_irq(&tscm->lock); + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_tscm *tscm = hwdep->private_data; + unsigned int events; + + poll_wait(file, &tscm->hwdep_wait, wait); + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&tscm->lock); + + return events; +} + +static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(tscm->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_TASCAM; + 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_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == 0) { + tscm->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_unlock(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == -1) { + tscm->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_tscm *tscm = hwdep->private_data; + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_count == -1) + tscm->dev_lock_count = 0; + spin_unlock_irq(&tscm->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_tscm *tscm = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(tscm, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(tscm); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(tscm); + 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_tscm_create_hwdep_device(struct snd_tscm *tscm) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(tscm->card, "Digi00x", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Digi00x"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM; + hwdep->ops = hwdep_ops; + hwdep->private_data = tscm; + hwdep->exclusive = true; + + return err; +} diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c index b1bdb9f..70ab68b 100644 --- a/sound/firewire/tascam/tascam-pcm.c +++ b/sound/firewire/tascam/tascam-pcm.c @@ -86,9 +86,13 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int rate; int err;
+ err = snd_tscm_stream_lock_try(tscm); + if (err < 0) + goto end; + err = pcm_init_hw_params(tscm, substream); if (err < 0) - return err; + goto err_locked;
err = snd_tscm_stream_get_clock(tscm, &clock); if ((clock != SND_TSCM_CLOCK_INTERNAL) | @@ -96,18 +100,25 @@ static int pcm_open(struct snd_pcm_substream *substream) amdtp_stream_pcm_running(&tscm->tx_stream)) { err = snd_tscm_stream_get_rate(tscm, &rate); if (err < 0) - return err; + goto err_locked; substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; }
snd_pcm_set_sync(substream); - +end: + return err; +err_locked: + snd_tscm_stream_lock_release(tscm); return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_tscm *tscm = substream->private_data; + + snd_tscm_stream_lock_release(tscm); + return 0; }
diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index deef60b..23124fb 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -456,3 +456,42 @@ void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) finish_session(tscm); release_resources(tscm); } + +void snd_tscm_stream_lock_changed(struct snd_tscm *tscm) +{ + tscm->dev_lock_changed = true; + wake_up(&tscm->hwdep_wait); +} + +int snd_tscm_stream_lock_try(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + /* user land lock this */ + if (tscm->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (tscm->dev_lock_count++ == 0) + snd_tscm_stream_lock_changed(tscm); + err = 0; +end: + spin_unlock_irq(&tscm->lock); + return err; +} + +void snd_tscm_stream_lock_release(struct snd_tscm *tscm) +{ + spin_lock_irq(&tscm->lock); + + if (WARN_ON(tscm->dev_lock_count <= 0)) + goto end; + if (--tscm->dev_lock_count == 0) + snd_tscm_stream_lock_changed(tscm); +end: + spin_unlock_irq(&tscm->lock); +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 5523a57..2afb291 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -144,6 +144,10 @@ static int snd_tscm_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_tscm_create_hwdep_device(tscm); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 732b410..053a8bf 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -22,6 +22,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/rawmidi.h> +#include <sound/hwdep.h>
#include "../lib.h" #include "../packets-buffer.h" @@ -65,6 +66,10 @@ struct snd_tscm { /* For MIDI message outgoing transactions. */ struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; u8 running_status[TSCM_MIDI_OUT_PORT_MAX]; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -116,6 +121,10 @@ void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm);
+void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); +int snd_tscm_stream_lock_try(struct snd_tscm *tscm); +void snd_tscm_stream_lock_release(struct snd_tscm *tscm); + int snd_tscm_transaction_register(struct snd_tscm *tscm); int snd_tscm_transaction_reregister(struct snd_tscm *tscm); void snd_tscm_transaction_unregister(struct snd_tscm *tscm); @@ -125,3 +134,5 @@ void snd_tscm_proc_init(struct snd_tscm *tscm); int snd_tscm_create_pcm_devices(struct snd_tscm *tscm);
int snd_tscm_create_midi_devices(struct snd_tscm *tscm); + +int snd_tscm_create_hwdep_device(struct snd_tscm *tscm);
TASCAM FireWire series transfers status and control messages in isochronous packets. One packet includes one message, and the message is periodic every 64 packets. As a result, the messages construct image with 64 quadlets. For example:
00 00000000, 32 00000000 01 00000000, 33 00000000 02 00000000, 34 00000000 03 00020000, 35 00000000 04 00000000, 36 25ea0000 05 ffff0000, 37 27dfff00 06 ffffffff, 38 1d9d0000 07 ffffffff, 39 00000000 08 ffffffff, 40 25ea0000 09 ffffffff, 41 27dfff00 10 00000000, 42 00000000 11 00000000, 43 00000000 12 00000000, 44 00000000 13 00000000, 45 00000000 14 00000000, 46 00000000 15 00010000, 47 00000000 16 00012e00, 48 00000000 17 00010400, 49 00000000 18 00011e00, 50 00000000 19 00011200, 51 00000000 20 00014900, 52 01010101 21 00011e00, 53 00000000 22 00010c00, 54 25ea0000 23 00013000, 55 27dfff00 24 00000000, 56 00000000 25 00000000, 57 00000000 26 00000000, 58 00000000 27 00000000, 59 00000001 28 00000000, 60 00000000 29 00000000, 61 00000000 30 00000000, 62 00000000 31 00000000, 63 00000000
Quadlet 00-15 show control messages. Quadlet 16-23 show analog input level. Quadlet 24-31 shows digital ADAT input level. Quadlet 32-33 shows digital S/PDIF input level. Quadlet 34-35 is unknown. Quadlet 36-43 shows analog output level. The other quadlets are unknown.
This image is updated every 1 msec or less, depending on current sampling transfer frequency.
This commit adds MMAP support to show this image via hwdep interface. An userspace application can map a page frame to its virtual address space with read-only flag. This driver write every control and status messages into the page frame, and the userspace application can parse them.
The control messages are both of edge-triggered/level-trigerred, according to physical implementation. For example, the control message corresponding to toggle switch acts as edge-trigger, while the control message corresponding to level fader acts as level-trigger. These control messages are updated every 1 msec or less, alghough the scheduling granuality in Linux operating system is not always so small. This may cause the userspace parser misses some messages when many tasks are running without voluntary programs.
Typically, such messages should be converted to MIDI control change messages for userspace applications. Thus, this driver should have a converter with appropriate MIDI map. Currently I have no good idea for the map and it is not implemented yet. Developers helps are required.
Cc: Yoshifuji Hideaki yoshfuji@linux-ipv6.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/firewire.h | 4 ++++ sound/firewire/tascam/amdtp-tascam.c | 4 +++- sound/firewire/tascam/tascam-hwdep.c | 30 ++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam-stream.c | 15 +++++++++++++++ sound/firewire/tascam/tascam.h | 3 +++ 5 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index db79a12..c5cb86d 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -79,4 +79,8 @@ struct snd_firewire_get_info { * Returns -EBUSY if the driver is already streaming. */
+struct snd_firewire_tascam_status { + __u32 status[64]; +}; + #endif /* _UAPI_SOUND_FIREWIRE_H_INCLUDED */ diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c index 22c2eaf..ca54b85 100644 --- a/sound/firewire/tascam/amdtp-tascam.c +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -155,14 +155,16 @@ void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) static void read_control_messages(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks) { + struct snd_tscm *tscm = container_of(s, struct snd_tscm, tx_stream); unsigned int first, index = 0; unsigned int i;
first = (buffer[0] >> 15) % 64;
- /* TODO */ for (i = 0; i < data_blocks; i++) { index = be32_to_cpu(buffer[0]) % 64; + tscm->status->status[index] = + be32_to_cpu(buffer[s->data_block_quadlets - 1]); buffer += s->data_block_quadlets; } } diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c index 4b539f5..fe51fee 100644 --- a/sound/firewire/tascam/tascam-hwdep.c +++ b/sound/firewire/tascam/tascam-hwdep.c @@ -175,12 +175,42 @@ static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, #define hwdep_compat_ioctl NULL #endif
+static int hwdep_vm_fault(struct vm_area_struct *area, struct vm_fault *vmf) +{ + struct snd_tscm *tscm = area->vm_private_data; + void *virt_addr; + struct page *page; + + /* The page is already allocated by streaming layer. */ + virt_addr = (char *)(tscm->status) + (vmf->pgoff << PAGE_SHIFT); + page = virt_to_page(virt_addr); + get_page(page); + vmf->page = page; + + return 0; +} + +static const struct vm_operations_struct hwdep_vm_ops = { + .fault = hwdep_vm_fault, +}; + +static int hwdep_mmap(struct snd_hwdep *hwdep, struct file *filp, + struct vm_area_struct *area) +{ + area->vm_ops = &hwdep_vm_ops; + area->vm_flags = VM_READ | VM_RAND_READ | VM_DONTEXPAND; + area->vm_private_data = hwdep->private_data; + + return 0; +} + 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, + .mmap = hwdep_mmap, };
int snd_tscm_create_hwdep_device(struct snd_tscm *tscm) diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index 23124fb..83f437e 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -314,6 +314,7 @@ error: int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) { unsigned int pcm_channels; + unsigned int size; int err;
/* For out-stream. */ @@ -344,6 +345,15 @@ int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) if (err < 0) amdtp_stream_destroy(&tscm->rx_stream);
+ /* For control messages. */ + size = sizeof(struct snd_firewire_tascam_status); + tscm->status = snd_malloc_pages(size, GFP_KERNEL); + if (tscm->status == NULL) { + amdtp_stream_destroy(&tscm->tx_stream); + amdtp_stream_destroy(&tscm->rx_stream); + return -ENOMEM; + } + return 0; }
@@ -363,11 +373,16 @@ void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) */ void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) { + unsigned int size; + amdtp_stream_destroy(&tscm->rx_stream); amdtp_stream_destroy(&tscm->tx_stream);
fw_iso_resources_destroy(&tscm->rx_resources); fw_iso_resources_destroy(&tscm->tx_resources); + + size = sizeof(struct snd_firewire_tascam_status); + snd_free_pages(tscm->status, size); }
int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 053a8bf..0340693 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -70,6 +70,9 @@ struct snd_tscm { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait; + + /* For control messages. */ + struct snd_firewire_tascam_status *status; };
#define TSCM_ADDR_BASE 0xffff00000000ull
On Sat, 11 Jul 2015 16:12:37 +0200, Takashi Sakamoto wrote:
TASCAM FireWire series transfers status and control messages in isochronous packets. One packet includes one message, and the message is periodic every 64 packets. As a result, the messages construct image with 64 quadlets. For example:
00 00000000, 32 00000000 01 00000000, 33 00000000 02 00000000, 34 00000000 03 00020000, 35 00000000 04 00000000, 36 25ea0000 05 ffff0000, 37 27dfff00 06 ffffffff, 38 1d9d0000 07 ffffffff, 39 00000000 08 ffffffff, 40 25ea0000 09 ffffffff, 41 27dfff00 10 00000000, 42 00000000 11 00000000, 43 00000000 12 00000000, 44 00000000 13 00000000, 45 00000000 14 00000000, 46 00000000 15 00010000, 47 00000000 16 00012e00, 48 00000000 17 00010400, 49 00000000 18 00011e00, 50 00000000 19 00011200, 51 00000000 20 00014900, 52 01010101 21 00011e00, 53 00000000 22 00010c00, 54 25ea0000 23 00013000, 55 27dfff00 24 00000000, 56 00000000 25 00000000, 57 00000000 26 00000000, 58 00000000 27 00000000, 59 00000001 28 00000000, 60 00000000 29 00000000, 61 00000000 30 00000000, 62 00000000 31 00000000, 63 00000000
Quadlet 00-15 show control messages. Quadlet 16-23 show analog input level. Quadlet 24-31 shows digital ADAT input level. Quadlet 32-33 shows digital S/PDIF input level. Quadlet 34-35 is unknown. Quadlet 36-43 shows analog output level. The other quadlets are unknown.
This image is updated every 1 msec or less, depending on current sampling transfer frequency.
This commit adds MMAP support to show this image via hwdep interface. An userspace application can map a page frame to its virtual address space with read-only flag. This driver write every control and status messages into the page frame, and the userspace application can parse them.
The control messages are both of edge-triggered/level-trigerred, according to physical implementation. For example, the control message corresponding to toggle switch acts as edge-trigger, while the control message corresponding to level fader acts as level-trigger. These control messages are updated every 1 msec or less, alghough the scheduling granuality in Linux operating system is not always so small. This may cause the userspace parser misses some messages when many tasks are running without voluntary programs.
Typically, such messages should be converted to MIDI control change messages for userspace applications. Thus, this driver should have a converter with appropriate MIDI map. Currently I have no good idea for the map and it is not implemented yet. Developers helps are required.
Cc: Yoshifuji Hideaki yoshfuji@linux-ipv6.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
Using mmap for accessing this kind of message isn't good for various reasons. First off, it's volatile, and your implementation keeps only a single record, thus it will be overridden at each input.
Second, the access is racy.
Third, the mmap implementation can be complex if a cache coherency plays a role. It's easy on x86, but for others...
And the rest... there must be something else I overlooked obviously (I don't count a bug here, e.g. the lack of offset value check in vm fault handler :)
thanks,
Takashi
include/uapi/sound/firewire.h | 4 ++++ sound/firewire/tascam/amdtp-tascam.c | 4 +++- sound/firewire/tascam/tascam-hwdep.c | 30 ++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam-stream.c | 15 +++++++++++++++ sound/firewire/tascam/tascam.h | 3 +++ 5 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index db79a12..c5cb86d 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -79,4 +79,8 @@ struct snd_firewire_get_info {
- Returns -EBUSY if the driver is already streaming.
*/
+struct snd_firewire_tascam_status {
- __u32 status[64];
+};
#endif /* _UAPI_SOUND_FIREWIRE_H_INCLUDED */ diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c index 22c2eaf..ca54b85 100644 --- a/sound/firewire/tascam/amdtp-tascam.c +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -155,14 +155,16 @@ void amdtp_tscm_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) static void read_control_messages(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks) {
struct snd_tscm *tscm = container_of(s, struct snd_tscm, tx_stream); unsigned int first, index = 0; unsigned int i;
first = (buffer[0] >> 15) % 64;
- /* TODO */ for (i = 0; i < data_blocks; i++) { index = be32_to_cpu(buffer[0]) % 64;
tscm->status->status[index] =
buffer += s->data_block_quadlets; }be32_to_cpu(buffer[s->data_block_quadlets - 1]);
} diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c index 4b539f5..fe51fee 100644 --- a/sound/firewire/tascam/tascam-hwdep.c +++ b/sound/firewire/tascam/tascam-hwdep.c @@ -175,12 +175,42 @@ static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, #define hwdep_compat_ioctl NULL #endif
+static int hwdep_vm_fault(struct vm_area_struct *area, struct vm_fault *vmf) +{
- struct snd_tscm *tscm = area->vm_private_data;
- void *virt_addr;
- struct page *page;
- /* The page is already allocated by streaming layer. */
- virt_addr = (char *)(tscm->status) + (vmf->pgoff << PAGE_SHIFT);
- page = virt_to_page(virt_addr);
- get_page(page);
- vmf->page = page;
- return 0;
+}
+static const struct vm_operations_struct hwdep_vm_ops = {
- .fault = hwdep_vm_fault,
+};
+static int hwdep_mmap(struct snd_hwdep *hwdep, struct file *filp,
struct vm_area_struct *area)
+{
- area->vm_ops = &hwdep_vm_ops;
- area->vm_flags = VM_READ | VM_RAND_READ | VM_DONTEXPAND;
- area->vm_private_data = hwdep->private_data;
- return 0;
+}
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,
- .mmap = hwdep_mmap,
};
int snd_tscm_create_hwdep_device(struct snd_tscm *tscm) diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c index 23124fb..83f437e 100644 --- a/sound/firewire/tascam/tascam-stream.c +++ b/sound/firewire/tascam/tascam-stream.c @@ -314,6 +314,7 @@ error: int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) { unsigned int pcm_channels;
unsigned int size; int err;
/* For out-stream. */
@@ -344,6 +345,15 @@ int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) if (err < 0) amdtp_stream_destroy(&tscm->rx_stream);
- /* For control messages. */
- size = sizeof(struct snd_firewire_tascam_status);
- tscm->status = snd_malloc_pages(size, GFP_KERNEL);
- if (tscm->status == NULL) {
amdtp_stream_destroy(&tscm->tx_stream);
amdtp_stream_destroy(&tscm->rx_stream);
return -ENOMEM;
- }
- return 0;
}
@@ -363,11 +373,16 @@ void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) */ void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) {
unsigned int size;
amdtp_stream_destroy(&tscm->rx_stream); amdtp_stream_destroy(&tscm->tx_stream);
fw_iso_resources_destroy(&tscm->rx_resources); fw_iso_resources_destroy(&tscm->tx_resources);
size = sizeof(struct snd_firewire_tascam_status);
snd_free_pages(tscm->status, size);
}
int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 053a8bf..0340693 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -70,6 +70,9 @@ struct snd_tscm { int dev_lock_count; bool dev_lock_changed; wait_queue_head_t hwdep_wait;
- /* For control messages. */
- struct snd_firewire_tascam_status *status;
};
#define TSCM_ADDR_BASE 0xffff00000000ull
2.1.4
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Jul 20 2015 23:23, Takashi Iwai wrote:
On Sat, 11 Jul 2015 16:12:37 +0200, Takashi Sakamoto wrote:
TASCAM FireWire series transfers status and control messages in isochronous packets. One packet includes one message, and the message is periodic every 64 packets. As a result, the messages construct image with 64 quadlets. For example:
00 00000000, 32 00000000 01 00000000, 33 00000000 02 00000000, 34 00000000 03 00020000, 35 00000000 04 00000000, 36 25ea0000 05 ffff0000, 37 27dfff00 06 ffffffff, 38 1d9d0000 07 ffffffff, 39 00000000 08 ffffffff, 40 25ea0000 09 ffffffff, 41 27dfff00 10 00000000, 42 00000000 11 00000000, 43 00000000 12 00000000, 44 00000000 13 00000000, 45 00000000 14 00000000, 46 00000000 15 00010000, 47 00000000 16 00012e00, 48 00000000 17 00010400, 49 00000000 18 00011e00, 50 00000000 19 00011200, 51 00000000 20 00014900, 52 01010101 21 00011e00, 53 00000000 22 00010c00, 54 25ea0000 23 00013000, 55 27dfff00 24 00000000, 56 00000000 25 00000000, 57 00000000 26 00000000, 58 00000000 27 00000000, 59 00000001 28 00000000, 60 00000000 29 00000000, 61 00000000 30 00000000, 62 00000000 31 00000000, 63 00000000
Quadlet 00-15 show control messages. Quadlet 16-23 show analog input level. Quadlet 24-31 shows digital ADAT input level. Quadlet 32-33 shows digital S/PDIF input level. Quadlet 34-35 is unknown. Quadlet 36-43 shows analog output level. The other quadlets are unknown.
This image is updated every 1 msec or less, depending on current sampling transfer frequency.
This commit adds MMAP support to show this image via hwdep interface. An userspace application can map a page frame to its virtual address space with read-only flag. This driver write every control and status messages into the page frame, and the userspace application can parse them.
The control messages are both of edge-triggered/level-trigerred, according to physical implementation. For example, the control message corresponding to toggle switch acts as edge-trigger, while the control message corresponding to level fader acts as level-trigger. These control messages are updated every 1 msec or less, alghough the scheduling granuality in Linux operating system is not always so small. This may cause the userspace parser misses some messages when many tasks are running without voluntary programs.
Typically, such messages should be converted to MIDI control change messages for userspace applications. Thus, this driver should have a converter with appropriate MIDI map. Currently I have no good idea for the map and it is not implemented yet. Developers helps are required.
Cc: Yoshifuji Hideaki yoshfuji@linux-ipv6.org Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp
Using mmap for accessing this kind of message isn't good for various reasons. First off, it's volatile, and your implementation keeps only a single record, thus it will be overridden at each input.
Second, the access is racy.
Third, the mmap implementation can be complex if a cache coherency plays a role. It's easy on x86, but for others...
Indeed. Now I investigate about the converter between the bitmap and MIDI messages.
But anyway, input/output level should be exposed to userspace because it doesn't matter to miss reading the information sometimes.
And the rest... there must be something else I overlooked obviously (I don't count a bug here, e.g. the lack of offset value check in vm fault handler :)
Thanks
Takashi Sakamoto
Takashi Sakamoto wrote:
Typically, such messages should be converted to MIDI control change messages for userspace applications.
It would be possible to have ALSA mixer controls for these, but this would make sense only if these controls are suitable to be displayed in a generic mixer application. I guess this isn't the case for most of them.
Thus, this driver should have a converter with appropriate MIDI map. Currently I have no good idea for the map and it is not implemented yet.
I guess the Windows driver does not makes this information available in any way?
If all the data is device-specific, the actual mapping chosen does not really matter, because the mixer/control application needs to have special code for parsing the data from this particular Linux driver anyway.
Regards, Clemens
On Jul 21 2015 23:06, Clemens Ladisch wrote:
Takashi Sakamoto wrote:
Typically, such messages should be converted to MIDI control change messages for userspace applications.
It would be possible to have ALSA mixer controls for these, but this would make sense only if these controls are suitable to be displayed in a generic mixer application. I guess this isn't the case for most of them.
Indeed. The main purpose of these physical controls is to control DAW softwares or the other devices. On the other hand, the major usage of ALSA control elements is to control the device itself.
Thus, this driver should have a converter with appropriate MIDI map. Currently I have no good idea for the map and it is not implemented yet.
I guess the Windows driver does not makes this information available in any way?
Windows driver gives GUI to select the way to parse the control information with three options. All of the options are based on MIDI messaging. Therefore, the driver perform to convert to MIDI messages, I think.
Furthermore, two of the options are somewhat compliant to Mackie something. In Open Source Software, Ardour Gtk+ can handle the Mackie something. In this reason, I think it's users' advantage to apply MIDI converter for the control messages.
If all the data is device-specific, the actual mapping chosen does not really matter, because the mixer/control application needs to have special code for parsing the data from this particular Linux driver anyway.
Exactly, with ignoring the updating interval.
Regards
Takashi Sakamoto
In IEC 61883-1, CIP headers have a SPH field. When a packet has 1 in the SPH field, the packet has a source packet headers. A source packet header consists of 32 bit field (= 1 quadlet) and it transfers timestamp, which is the same value as the lower 25 bits of the IEEE 1394 CYCLE_TIMER register and the rest is zero.
This commit just supports source packet header field because IEC 61883-1 includes ambiguity the position of this header and the number. Each protocol layer can have actual implementation according its requirements.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-stream.c | 8 ++++++-- sound/firewire/amdtp-stream.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index c7fbdd3..58f6ae0 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -34,6 +34,8 @@ #define CIP_SID_MASK 0x3f000000 #define CIP_DBS_MASK 0x00ff0000 #define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 #define CIP_DBC_MASK 0x000000ff #define CIP_FMT_SHIFT 24 #define CIP_FMT_MASK 0x3f000000 @@ -414,6 +416,7 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | + ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | s->data_block_counter); buffer[1] = cpu_to_be32(CIP_EOH | ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | @@ -440,7 +443,7 @@ static int handle_in_packet(struct amdtp_stream *s, unsigned int *data_blocks) { u32 cip_header[2]; - unsigned int fmt, fdf, syt; + unsigned int sph, fmt, fdf, syt; unsigned int data_block_quadlets, data_block_counter, dbc_interval; struct snd_pcm_substream *pcm; unsigned int pcm_frames; @@ -461,8 +464,9 @@ static int handle_in_packet(struct amdtp_stream *s, }
/* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; - if (fmt != s->fmt) { + if (sph != s->sph || fmt != s->fmt) { dev_err(&s->unit->device, "Detect unexpected protocol: %08x %08x\n", cip_header[0], cip_header[1]); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index d2354f0..aa5d04d 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -107,6 +107,7 @@ struct amdtp_stream { unsigned int source_node_id_field; unsigned int data_block_quadlets; unsigned int data_block_counter; + unsigned int sph; unsigned int fmt; unsigned int fdf; /* quirk: fixed interval of dbc between previos/current packets. */
The previous commit off-loads the work to generate source packet header to each protocol implementation. In IEC 61883-1, the source packet header shows timestamps, thus the protocol layer needs current cycle count.
This commit allows the protocol layer to get the cycle count.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-am824.c | 2 ++ sound/firewire/amdtp-stream.c | 54 ++++++++++++++++++++++++------------ sound/firewire/amdtp-stream.h | 1 + sound/firewire/digi00x/amdtp-dot.c | 2 ++ sound/firewire/tascam/amdtp-tascam.c | 2 ++ 5 files changed, 44 insertions(+), 17 deletions(-)
diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c index 37541bd..211654b 100644 --- a/sound/firewire/amdtp-am824.c +++ b/sound/firewire/amdtp-am824.c @@ -360,6 +360,7 @@ EXPORT_SYMBOL(amdtp_am824_midi_trigger); static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycles, unsigned int *syt) { struct amdtp_am824 *p = (struct amdtp_am824 *)s->protocol; @@ -383,6 +384,7 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycles, unsigned int *syt) { struct amdtp_am824 *p = (struct amdtp_am824 *)s->protocol; diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 58f6ae0..8555b84 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -403,7 +403,7 @@ static inline int queue_in_packet(struct amdtp_stream *s) }
static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks, - unsigned int syt) + unsigned int cycle, unsigned int syt) { __be32 *buffer; unsigned int payload_length; @@ -412,7 +412,8 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
/* CIP processing. */ buffer = s->buffer.packets[s->packet_index].buffer; - pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, &syt); + pcm_frames = s->process_data_blocks(s, buffer + 2, data_blocks, cycle, + &syt);
buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | (s->data_block_quadlets << CIP_DBS_SHIFT) | @@ -440,10 +441,11 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
static int handle_in_packet(struct amdtp_stream *s, unsigned int payload_quadlets, __be32 *buffer, - unsigned int *data_blocks) + unsigned int *data_blocks, unsigned int cycle, + unsigned int syt) { u32 cip_header[2]; - unsigned int sph, fmt, fdf, syt; + unsigned int sph, fmt, fdf; unsigned int data_block_quadlets, data_block_counter, dbc_interval; struct snd_pcm_substream *pcm; unsigned int pcm_frames; @@ -523,8 +525,8 @@ static int handle_in_packet(struct amdtp_stream *s, }
/* CIP processing. */ - syt = cip_header[1] & CIP_SYT_MASK; - pcm_frames = s->process_data_blocks(s, buffer + 2, *data_blocks, &syt); + pcm_frames = s->process_data_blocks(s, buffer + 2, *data_blocks, + cycle, &syt);
if (s->flags & CIP_DBC_IS_END_EVENT) s->data_block_counter = data_block_counter; @@ -543,6 +545,18 @@ end: return 0; }
+#define INCREMENT_CYCLE(cycle, addend, seconds, counts) \ + do { \ + seconds = cycle >> 13; \ + counts = (cycle & 0x0fff) + (addend); \ + if (counts >= 8000) { \ + counts %= 8000; \ + if (++seconds >= 8) \ + seconds %= 8; \ + } \ + cycle = (seconds << 13) | counts; \ + } while (0) + static void out_stream_callback(struct fw_iso_context *context, u32 cycle, size_t header_length, void *header, void *private_data) @@ -550,26 +564,27 @@ static void out_stream_callback(struct fw_iso_context *context, u32 cycle, struct amdtp_stream *s = private_data; unsigned int i, syt, packets = header_length / 4; unsigned int data_blocks; + unsigned int seconds; + unsigned int counts;
if (s->packet_index < 0) return;
- /* - * Compute the cycle of the last queued packet. - * (We need only the four lowest bits for the SYT, so we can ignore - * that bits 0-11 must wrap around at 3072.) - */ - cycle += QUEUE_LENGTH - packets; + /* Compute the cycle count for the first packet in this time. */ + INCREMENT_CYCLE(cycle, QUEUE_LENGTH - packets + 1, seconds, counts);
for (i = 0; i < packets; ++i) { - syt = calculate_syt(s, ++cycle); + syt = calculate_syt(s, cycle); data_blocks = calculate_data_blocks(s, syt);
- if (handle_out_packet(s, data_blocks, syt) < 0) { + if (handle_out_packet(s, data_blocks, cycle, syt) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; } + + /* Increment cycle count. */ + INCREMENT_CYCLE(cycle, 1, seconds, counts); }
fw_iso_context_queue_flush(s->context); @@ -584,6 +599,8 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle, unsigned int payload_quadlets, max_payload_quadlets; unsigned int data_blocks; __be32 *buffer, *headers = header; + unsigned int seconds; + unsigned int counts;
if (s->packet_index < 0) return; @@ -607,22 +624,25 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle, s->packet_index = -1; break; } + syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
if (handle_in_packet(s, payload_quadlets, buffer, - &data_blocks) < 0) { + &data_blocks, cycle, syt) < 0) { s->packet_index = -1; break; }
/* Process sync slave stream */ if (s->sync_slave && s->sync_slave->callbacked) { - syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK; if (handle_out_packet(s->sync_slave, - data_blocks, syt) < 0) { + data_blocks, cycle, syt) < 0) { s->packet_index = -1; break; } } + + /* Increment cycle count. */ + INCREMENT_CYCLE(cycle, 1, seconds, counts); }
/* Queueing error or detecting discontinuity */ diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index aa5d04d..4bcd2e4 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -126,6 +126,7 @@ struct amdtp_stream { unsigned int (*process_data_blocks)(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycle, unsigned int *syt);
/* For one PCM runtime processing. */ diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c index 338e884..43dba43 100644 --- a/sound/firewire/digi00x/amdtp-dot.c +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -343,6 +343,7 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycle, unsigned int *syt) { struct amdtp_dot *p = (struct amdtp_dot *)s->protocol; @@ -366,6 +367,7 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycle, unsigned int *syt) { struct amdtp_dot *p = (struct amdtp_dot *)s->protocol; diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c index ca54b85..a1a9337 100644 --- a/sound/firewire/tascam/amdtp-tascam.c +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -172,6 +172,7 @@ static void read_control_messages(struct amdtp_stream *s, static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycle, unsigned int *syt) { struct amdtp_tscm *p = (struct amdtp_tscm *)s->protocol; @@ -189,6 +190,7 @@ static unsigned int process_tx_data_blocks(struct amdtp_stream *s, static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer, unsigned int data_blocks, + unsigned int cycle, unsigned int *syt) { struct amdtp_tscm *p = (struct amdtp_tscm *)s->protocol;
This commit adds an new driver for MOTU FireWire series. In this commit, this driver just creates/removes card instance according to bus event. More functionalities will be added in following commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 11 ++++ sound/firewire/Makefile | 1 + sound/firewire/motu/Makefile | 2 + sound/firewire/motu/motu.c | 127 +++++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.h | 34 ++++++++++++ 5 files changed, 175 insertions(+) create mode 100644 sound/firewire/motu/Makefile create mode 100644 sound/firewire/motu/motu.c create mode 100644 sound/firewire/motu/motu.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 12993fa..7ee8e3e 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -147,4 +147,15 @@ config SND_FIREWIRE_TASCAM To compile this driver as a module, choose M here: the module will be called snd-firewire-tascam.
+config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + * 828 Mk II + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 6ae50f5..8923abf 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_SND_FIREWORKS) += fireworks/ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 0000000..f4cfd14 --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,2 @@ +snd-firewire-motu-objs := motu.o +obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 0000000..929899dd --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,127 @@ +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_VERSION: + version = be32_to_cpu(val); + break; + } + } + + strcpy(motu->card->driver, "MOTU"); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU (version:%d), GUID %08x%08x at %s, S%d", + be32_to_cpu(version), + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_card_free(struct snd_card *card) +{ + struct snd_motu *motu = card->private_data; + + fw_unit_put(motu->unit); + + mutex_destroy(&motu->mutex); +} + +static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) +{ + struct snd_card *card; + struct snd_motu *motu; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(*motu), &card); + if (err < 0) + return err; + + motu = card->private_data; + motu->card = card; + motu->unit = fw_unit_get(unit); + card->private_free = motu_card_free; + + mutex_init(&motu->mutex); + + name_card(motu); + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, motu); + + return 0; +error: + snd_card_free(card); + return err; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(motu->card); +} + +static void motu_bus_reset(struct fw_unit *unit) +{ + return; +} + +static const struct ieee1394_device_id motu_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID, + .vendor_id = 0x0001f2, + .specifier_id = 0x0001f2, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_reset, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 0000000..df16750 --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,34 @@ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <sound/control.h> +#include <sound/core.h> + +#include "../amdtp-stream.h" +#include "../iso-resources.h" +#include "../lib.h" + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; +}; + +#endif
MOTU FireWire series doesn't tell drivers their capabilities, thus the drivers should have model-dependent parameters and apply it to detected models.
This commit adds a structure to represent such parameters.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/motu.c | 169 ++++++++++++++++++++++++++++++++++++++++++--- sound/firewire/motu/motu.h | 16 +++++ 2 files changed, 176 insertions(+), 9 deletions(-)
diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 929899dd..eb83e87 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -28,9 +28,11 @@ static void name_card(struct snd_motu *motu) }
strcpy(motu->card->driver, "MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); snprintf(motu->card->longname, sizeof(motu->card->longname), - "MOTU (version:%d), GUID %08x%08x at %s, S%d", - be32_to_cpu(version), + "MOTU %s (version:%d), GUID %08x%08x at %s, S%d", + motu->spec->name, be32_to_cpu(version), fw_dev->config_rom[3], fw_dev->config_rom[4], dev_name(&motu->unit->device), 100 << fw_dev->max_speed); } @@ -89,14 +91,163 @@ static void motu_bus_reset(struct fw_unit *unit) return; }
+static struct snd_motu_spec motu_828mk2 = { + .name = "828Mk2", + .generation = 2, + + .tx_common_pcm_channels = {14, 14, 0}, + .tx_optical_ifaces = 1, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14, 14, 0}, + .rx_optical_ifaces = 1, + .rx_midi_ports = 1, +}; + +static struct snd_motu_spec motu_8pre = { + .name = "8PRE", + .generation = 2, + + .tx_common_pcm_channels = {10 , 10, 0}, + .tx_optical_ifaces = 1, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {6 , 6, 0}, + .rx_optical_ifaces = 1, + .rx_midi_ports = 1 +}; + +static struct snd_motu_spec motu_traveler = { + .name = "Traveler", + .generation = 2, + + .tx_common_pcm_channels = {14, 14, 8}, + .tx_optical_ifaces = 1, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14, 14, 8}, + .rx_optical_ifaces = 1, + .rx_midi_ports = 1, +}; + +static struct snd_motu_spec motu_ultralite = { + .name = "UltraLite", + .generation = 2, + + .tx_common_pcm_channels = {12, 12, 0}, + .tx_optical_ifaces = 0, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14, 14, 0}, + .rx_optical_ifaces = 0, + .rx_midi_ports = 1, +}; + +static struct snd_motu_spec motu_896hd = { + .name = "896HD", + .generation = 2, + + .tx_common_pcm_channels = {12, 12, 8}, + .tx_optical_ifaces = 1, + .tx_midi_ports = 0, + + .rx_common_pcm_channels = {12, 12, 8}, + .rx_optical_ifaces = 1, + .rx_midi_ports = 0, +}; + +static struct snd_motu_spec motu_4pre = { + .name = "4pre", + .generation = 3, + + .tx_common_pcm_channels = {8, 8, 6}, + .tx_optical_ifaces = 0, + .tx_midi_ports = 0, + + .rx_common_pcm_channels = {8, 8, 6}, + .rx_optical_ifaces = 0, + .rx_midi_ports = 0, +}; + +static struct snd_motu_spec motu_828mk3 = { + .name = "828Mk3", + .generation = 3, + + .tx_common_pcm_channels = {18, 18, 12}, + .tx_optical_ifaces = 2, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14, 14, 12}, + .rx_optical_ifaces = 2, + .rx_midi_ports = 1, +}; + +static struct snd_motu_spec motu_travelermk3 = { + .name = "TravelerMk3", + .generation = 3, + + .tx_common_pcm_channels = {18 , 16, 10}, + .tx_optical_ifaces = 2, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14 , 14, 10}, + .rx_optical_ifaces = 2, + .rx_midi_ports = 1, +}; + +static struct snd_motu_spec motu_896mk3 = { + .name = "896Mk3", + .generation = 3, + + .tx_common_pcm_channels = {18, 14, 10}, + .tx_optical_ifaces = 2, + .tx_midi_ports = 0, + + .rx_common_pcm_channels = {18, 14, 10}, + .rx_optical_ifaces = 2, + .rx_midi_ports = 0, +}; + +static struct snd_motu_spec motu_ultralitemk3 = { + .name = "UltraLiteMk3", + .generation = 3, + + .tx_common_pcm_channels = {18, 14, 10}, + .tx_optical_ifaces = 2, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14, 14, 14}, + .rx_optical_ifaces = 2, + .rx_midi_ports = 1, +}; + +static struct snd_motu_spec motu_ultralitemk3hybrid = { + .name = "UltraLiteMk3Hybrid", + .generation = 3, + + .tx_common_pcm_channels = {18, 14, 10}, + .tx_optical_ifaces = 0, + .tx_midi_ports = 1, + + .rx_common_pcm_channels = {14, 14, 12}, + .rx_optical_ifaces = 0, + .rx_midi_ports = 1, +}; + +#define SND_MOTU_DEV_ENTRY(model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID | \ + IEEE1394_MATCH_SPECIFIER_ID, \ + .vendor_id = 0x0001f2, \ + .model_id = model, \ + .specifier_id = 0x0001f2, \ + .driver_data = (kernel_ulong_t)data, \ +} + static const struct ieee1394_device_id motu_id_table[] = { - { - .match_flags = IEEE1394_MATCH_VENDOR_ID | - IEEE1394_MATCH_MODEL_ID | - IEEE1394_MATCH_SPECIFIER_ID, - .vendor_id = 0x0001f2, - .specifier_id = 0x0001f2, - }, + /* 828 mk2 */ + SND_MOTU_DEV_ENTRY(0x101800, &motu_828mk2), { } }; MODULE_DEVICE_TABLE(ieee1394, motu_id_table); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index df16750..d5dddbd 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -24,11 +24,27 @@ #include "../iso-resources.h" #include "../lib.h"
+struct snd_motu_spec { + const char *const name; + unsigned char generation; + + unsigned int tx_common_pcm_channels[3]; + unsigned int tx_optical_ifaces; + unsigned int tx_midi_ports; + + unsigned int rx_common_pcm_channels[3]; + unsigned int rx_optical_ifaces; + unsigned int rx_midi_ports; +}; + struct snd_motu { struct snd_card *card; struct fw_unit *unit;
struct mutex mutex; + + /* Model dependent information. */ + struct snd_motu_spec *spec; };
#endif
MOTU FireWire series uses blocking transmission for AMDTP packet streaming, while the format of data blocks is unique.
The CIP headers includes 0x82 in FMT field and 0x22ffff in FDF field.
The first data channel in each data block is used for source packet header, thus the lower 25 bits show timestamp.
The rest of data channels are used to transfer PCM samples, MIDI data and status/control information. Each data is in 24 bits data chunk. This is similar to '24-bit * 4 Audio Pack' in IEC 61883-6.
The first two data chunks have MIDI messages and status/control information. The rest has PCM samples up to 24 bits.
Currently, this protocol layer causes noise like rectanglar wave, due to timestampling perhaps. The theory to implement timestamping is:
* Using pre-computed table for the number of ticks per event: * 44,1kHz: (557 + 123/441) * 48.0kHz: (512 + 0/441) * 88.2kHz: (278 + 282/441) * 96.0kHz: (256 + 0/441) * 176.4kHz: (139 + 141/441) * 192.0kHz: (128 + 0/441) * Accumulate the ticks and set the value to SPH for every events. * This way makes sence for blocking transmission because this mode transfers fixed number or none of events.
The reason is not clear.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/amdtp-motu.c | 442 +++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.h | 12 ++ 3 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/amdtp-motu.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index f4cfd14..5c35d77 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := motu.o +snd-firewire-motu-objs := amdtp-motu.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 0000000..a10e57c --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,442 @@ +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include "motu.h" + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_offsets; + unsigned int next_accumulated; + + unsigned int pcm_channels; + + unsigned int midi_ports; + struct snd_rawmidi_substream *midi[8]; + int midi_fifo_limit; + int midi_fifo_used[8]; + + void (*transfer_samples)(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks); +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int data_block_quadlets; + int err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + /* Use 24-bit audio pack in IEC 61883-6, and SPH in IEC 61883-1. */ + data_block_quadlets = DIV_ROUND_UP((pcm_channels + 2) * 3, 4) + 1; + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_channels = pcm_channels; + p->midi_ports = midi_ports; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_offsets = 0; + p->next_accumulated = 0; + + return 0; +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + /* Skip SPH. */ + buffer++; + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer; + + /* Skip MIDI and control messages. */ + byte += 6; + + for (c = 0; c < channels; ++c) { + byte[0] = ((*src) >> 8) & 0xff; + byte[1] = ((*src) >> 16) & 0xff; + byte[2] = ((*src) >> 24) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_s16(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + const u16 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + /* Skip SPH. */ + buffer++; + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer; + + /* Skip MIDI and control messages. */ + byte += 6; + + for (c = 0; c < channels; ++c) { + byte[0] = ((*src) >> 8) & 0xff; + byte[1] = (*src) & 0xff; + byte[2] = 0; + byte += 3; + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime, + __be32 *buffer, unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, remaining_frames, i, c; + u8 *byte; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + /* Skip SPH. */ + buffer++; + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer; + + /* Skip MIDI and control message. */ + byte += 6; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | (byte[1] << 16) | byte[2]; + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_channels; + + /* Skip SPH. */ + buffer++; + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer; + + /* Skip MIDI and control messages. */ + byte += 6; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +void amdtp_motu_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format) +{ + struct amdtp_motu *p = s->protocol; + + if (WARN_ON(amdtp_stream_pcm_running(s))) + return; + + switch (format) { + default: + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S16: + if (s->direction == AMDTP_OUT_STREAM) { + p->transfer_samples = write_pcm_s16; + break; + } + WARN_ON(1); + /* fall through */ + case SNDRV_PCM_FORMAT_S32: + if (s->direction == AMDTP_OUT_STREAM) + p->transfer_samples = write_pcm_s32; + else + p->transfer_samples = read_pcm_s32; + break; + } +} + +/* + * To avoid sending MIDI bytes at too high a rate, assume that the receiving + * device has a FIFO, and track how much it is filled. This values increases + * by one whenever we send one byte in a packet, but the FIFO empties at + * a constant rate independent of our packet rate. One packet has syt_interval + * samples, so the number of bytes that empty out of the FIFO, per packet(!), + * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing + * fractional values, the values in midi_fifo_used[] are measured in bytes + * multiplied by the sample rate. + */ +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_motu *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) /* common shortcut */ + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_motu *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int f; + u8 *b; + + /* Skip SPH. */ + buffer++; + for (f = 0; f < data_blocks; f++) { + b = (u8 *)buffer; + + if (p->midi[0] && + midi_ratelimit_per_packet(s, 0) && + snd_rawmidi_transmit(p->midi[0], b + 2, 1) == 1) { + midi_rate_use_one_byte(s, 0); + b[0] |= 0x01; + } else { + b[2] = 0x00; + } + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int f; + u8 *b; + + /* Skip SPH. */ + buffer++; + for (f = 0; f < data_blocks; f++) { + b = (u8 *)buffer; + + if ((b[0] & 0x01) && p->midi[0]) + snd_rawmidi_receive(p->midi[0], b + 2, 1); + + buffer += s->data_block_quadlets; + } +} + +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + ACCESS_ONCE(p->midi[port]) = midi; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int cycle, + unsigned int *syt) +{ + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; + struct snd_pcm_substream *pcm; + + /* + * NOTE: The gap of data in two successive SPH fields has jitters. For + * example, at 44.1kHz: + * 022d, 022f, 022d, 022d, 022d, 022e, 022c, 022d, 022d, 022e, 022d + * + * Therefore, it's better not to reuse it., + */ + + /* TODO: how to interact control messages between userspace? */ + + if (p->midi_ports) + read_midi_messages(s, buffer, data_blocks); + + pcm = ACCESS_ONCE(s->pcm); + if (data_blocks > 0 && pcm) + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + + return data_blocks; +} + +static void write_sph(struct amdtp_motu *p, u32 *buffer, unsigned int cycle, + unsigned int data_blocks) +{ + unsigned int seconds; + unsigned int counts; + __u32 sph; + unsigned int i; + + seconds = cycle >> 13; + counts = cycle % 8000; + + for (i = 0; i < data_blocks; i++) { + sph = (seconds << 25) | (counts << 12) | p->next_offsets; + buffer[2] = cpu_to_be32(sph); + + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_offsets++; + } + + p->next_offsets += p->quotient_ticks_per_event; + if (p->next_offsets >= 3072) { + p->next_offsets -= 3072; + if (++counts >= 8000) { + counts -= 8000; + seconds = (seconds + 1) % 8; + } + } + } +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int cycle, + unsigned int *syt) +{ + struct amdtp_motu *p = (struct amdtp_motu *)s->protocol; + struct snd_pcm_substream *pcm; + + /* Not used. */ + *syt = 0xffff; + + /* TODO: how to interact control messages between userspace? */ + + write_sph(p, buffer, cycle, data_blocks); + + if (p->midi_ports) + write_midi_messages(s, buffer, data_blocks); + + pcm = ACCESS_ONCE(s->pcm); + if (pcm) + p->transfer_samples(s, pcm->runtime, buffer, data_blocks); + else + write_pcm_silence(s, buffer, data_blocks); + + return data_blocks; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + struct amdtp_motu *p; + int err; + + err = amdtp_stream_init(s, unit, dir, CIP_BLOCKING, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + p = s->protocol; + + if (dir == AMDTP_IN_STREAM) + s->process_data_blocks = process_tx_data_blocks; + else + s->process_data_blocks = process_rx_data_blocks; + + s->sph = 0x1; + s->fmt = 0x02; + s->fdf = 0x22; + + return 0; +} diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index d5dddbd..690818f 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -19,6 +19,10 @@
#include <sound/control.h> #include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rawmidi.h>
#include "../amdtp-stream.h" #include "../iso-resources.h" @@ -47,4 +51,12 @@ struct snd_motu { struct snd_motu_spec *spec; };
+int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports); +void amdtp_motu_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); #endif
Hi Takashi
Thanks for coding up a starting point of the MOTU firewire streaming driver. I have been seriously short of time this year and have not had a chance to look at this yet.
On Sat, Jul 11, 2015 at 11:12:42PM +0900, Takashi Sakamoto wrote:
Currently, this protocol layer causes noise like rectanglar wave, due to timestampling perhaps. The theory to implement timestamping is:
- Using pre-computed table for the number of ticks per event:
- 44,1kHz: (557 + 123/441)
- 48.0kHz: (512 + 0/441)
- 88.2kHz: (278 + 282/441)
- 96.0kHz: (256 + 0/441)
- 176.4kHz: (139 + 141/441)
- 192.0kHz: (128 + 0/441)
- Accumulate the ticks and set the value to SPH for every events.
- This way makes sence for blocking transmission because this mode transfers fixed number or none of events.
The reason is not clear.
The above approach will definitely not work and the resulting noise is not unexpected. The reason is that the MOTU audio clock is not locked to the firewire bus clock in any way at all. This means that you cannot base the time stamps of the audio samples on the assumption that there is, for example exactly 512 ticks per sample at 48 kHz. Instead one must determine the audio clock rate relative to the firewire cycle timer based on the incoming audio packets from the device. What this means is that the "ticks per sample" is not a constant, is fractional (and possibly irrational), and will vary from device to device (even on a single device depending on things such as temperature of the internal timebase components).
In FFADO the generation of the audio clock used for timestamping tx packets is done by locking an internal DLL to the incoming timestamps. However, I did it this way only because that was the way the rest of the FFADO subsystem expected it to be done. An alternative (which I had working very early on with an out-of-tree proof of concept) is to base the advancing of the tx timestamps on the differences seen in the rx timestamps. Note that there is an offset between the two due to pipeline delays. I would have to look up my early development notes on the MOTU driver to find out what that was.
An alternative approach might be to simply use the arrival time of the packet as a proxy for the internal timestamp. However, since the timestamp within the rx packet is likely to be more accurate for the purposes of synchronising to the internal audio clock I would strongly suggest using that.
The plan would therefore be as follows. After starting streaming on the device, wait for a few packets to arrive and store their timestamps. During this time it might be necessary to send "silence" packets - I would have to check. Then start transmission, with the tx timestamps being determined from the timestamp queue and using a fixed offset as explained earlier. I would have to look up my old code to determine exactly how this worked in practice, but it was stable and is a whole lot simpler than messing with DLLs and other similar synchronisation mechanisms.
Another general comment about the MOTU streaming: when shutting streaming down it is necessary to send a series of "silence" packets to the device before disabling streaming. If this is not done the device can end up in a state where it outputs high amplitude oscillations (around 1-2 KHz) across random channels. I have comments within the FFADO driver source which refer to this issue.
I don't have time to look through the rest of the patch right now but I will try to do so in the next few days.
Regards jonathan
Jonathan,
On JUl 12 2015 10:05, Jonathan Woithe wrote:
Thanks for coding up a starting point of the MOTU firewire streaming driver. I have been seriously short of time this year and have not had a chance to look at this yet.
Your comments, itself, is helpful for developing of MOTU driver, while in this developing cycle, I focus on the other things.
The main theme of this patchset is to expand current streaming engine to non-AMDTP protocols, with enough compatibility and reasonable overhead for in-tree drivers. My intention to add under-developing MOTU driver is to assist this intention, not for developing MOTU driver itself.
Therefore, in this developing cycle, I have little responses to comments which doesn't relate to the theme, sorry.
Thanks
Takashi Sakamoto
This commit adds a functionality to manage streaming.
The streaming is not controlled by CMP, against IEC 61883-1. It's controlled by writing to certain addresses.
Several clock sources are available, while there're no differences about packet transmission among clock sources. The value of SYT field in transferred packets is always zero. Thus, streams in both direction don't build synchronization.
The number of data chunks for PCM samples is decided according to current sampling rate, optical mode for digital input/output interfaces. For PCM functionality, the number of data blocks is cached in device instance.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-stream.c | 632 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 15 +- sound/firewire/motu/motu.h | 39 +++ 4 files changed, 684 insertions(+), 4 deletions(-) create mode 100644 sound/firewire/motu/motu-stream.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 5c35d77..1547a98 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := amdtp-motu.o motu.o +snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 0000000..d6fc84f --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,632 @@ +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define CLOCK_STATUS_OFFSET 0x0b14 +#define G2_CLOCK_RATE_SHIFT 3 +#define G2_CLOCK_RATE_MASK 0x00000038 +#define G2_CLOCK_SRC_SHIFT 0 +#define G2_CLOCK_SRC_MASK 0x00000007 + +const unsigned int snd_motu_rates[SND_MOTU_RATES_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + +static int get_rate_g2(struct snd_motu *motu, unsigned int *rate) +{ + __be32 data; + unsigned int index; + int err; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + &data, sizeof(data), 0); + if (err < 0) + return err; + + index = (be32_to_cpu(data) & G2_CLOCK_RATE_MASK) >> G2_CLOCK_RATE_SHIFT; + *rate = snd_motu_rates[index]; + + return 0; +} + +static int set_rate_g2(struct snd_motu *motu, unsigned int rate) +{ + __u32 data; + __be32 reg; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_rates); i++) { + if (snd_motu_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_rates)) + return -EINVAL; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + ®, sizeof(reg), 0); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~G2_CLOCK_RATE_MASK; + data |= i << G2_CLOCK_RATE_SHIFT; + data |= 0x07000000; + + reg = cpu_to_be32(data); + return snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + ®, sizeof(reg), 0); +} + +static int get_rate_g3(struct snd_motu *motu, unsigned int *rate) +{ + __be32 data; + unsigned int index; + int err; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + &data, sizeof(data), 0); + if (err < 0) + return err; + + index = (be32_to_cpu(data) & 0x00000700) >> 8; + if (index > SND_MOTU_RATES_COUNT) + return -EIO; + + *rate = snd_motu_rates[index]; + + return 0; +} + +static int set_rate_g3(struct snd_motu *motu, unsigned int rate) +{ + __u32 reg; + __be32 data; + unsigned int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_rates); i++) { + if (snd_motu_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_rates)) + return -EINVAL; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + &data, sizeof(data), 0); + if (err < 0) + return err; + reg = be32_to_cpu(data); + + reg &= 0xf8fff8ff; + reg |= i << 8; + reg |= 0x07000000; + + data = cpu_to_be32(reg); + return snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + &data, sizeof(data), 0); +} + +static int get_clock_g2(struct snd_motu *motu, enum snd_motu_clock *src) +{ + __be32 data; + unsigned int index; + int err; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + &data, sizeof(data), 0); + if (err < 0) + return err; + + index = be32_to_cpu(data) & G2_CLOCK_SRC_MASK; + if (index > 5) + return -EIO; + + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_INTERNAL; + break; + case 1: + *src = SND_MOTU_CLOCK_OPT; + break; + case 2: + *src = SND_MOTU_CLOCK_SPDIF_COAX; + break; + case 3: + *src = SND_MOTU_CLOCK_SPH; + break; + case 4: + *src = SND_MOTU_CLOCK_WORD_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_ADAT_DSUB; + break; + case 6: + *src = SND_MOTU_CLOCK_AESEBU_XLR; + break; + default: + return -EIO; + } + + return 0; +} + +static int get_clock_g3(struct snd_motu *motu, enum snd_motu_clock *src) +{ + __be32 data; + unsigned int index; + int err; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + MOTU_REG_BASE + CLOCK_STATUS_OFFSET, + &data, sizeof(data), 0); + if (err < 0) + return err; + + index = be32_to_cpu(data) & 0x0000001b; + switch (index) { + case 0: + *src = SND_MOTU_CLOCK_INTERNAL; + break; + case 1: + *src = SND_MOTU_CLOCK_WORD_BNC; + break; + case 2: + *src = SND_MOTU_CLOCK_SPH; + break; + case 16: + *src = SND_MOTU_CLOCK_SPDIF_COAX; + break; + case 18: + case 19: + *src = SND_MOTU_CLOCK_OPT; + break; + default: + return -EIO; + } + + return 0; +} + +int snd_motu_stream_get_rate(struct snd_motu *motu, unsigned int *rate) +{ + if (motu->spec->generation == 2) + return get_rate_g2(motu, rate); + else + return get_rate_g3(motu, rate); +} + +int snd_motu_stream_set_rate(struct snd_motu *motu, unsigned int rate) +{ + if (motu->spec->generation == 2) + return set_rate_g2(motu, rate); + else + return set_rate_g3(motu, rate); +} + +int snd_motu_stream_get_clock(struct snd_motu *motu, + enum snd_motu_clock *src) +{ + if (motu->spec->generation == 2) + return get_clock_g2(motu, src); + else + return get_clock_g3(motu, src); +} + +static int update_channel_cache_g2(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + 0xfffff0000c04, + ®, sizeof(reg), 0); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + /* ADAT is available in input optical interface. */ + if ((data & 0x00000100) && motu->spec->tx_optical_ifaces == 1) { + motu->tx_pcm_channels[0] += 8; + motu->tx_pcm_channels[1] += 4; + } + + /* ADAT is available in output optical interface. */ + if ((data & 0x00000400) && motu->spec->rx_optical_ifaces == 1) { + motu->rx_pcm_channels[0] += 8; + motu->rx_pcm_channels[1] += 4; + } + + return 0; +} + +static int update_channel_cache_g3(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + 0xfffff0000c04, + ®, sizeof(reg), 0); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (motu->spec->tx_optical_ifaces > 0) { + /* The optical input interface A is enabled. */ + if (data & 0x00000001) { + /* It's S/PDIF. */ + if (data & 0x00010000) { + motu->tx_pcm_channels[0] += 4; + motu->tx_pcm_channels[1] += 4; + /* It's ADAT. */ + } else { + motu->tx_pcm_channels[0] += 8; + motu->tx_pcm_channels[1] += 4; + } + } + + /* The optical input interface B is enabled. */ + if (data & 0x00000002) { + /* It's S/PDIF. */ + if (data & 0x00100000) { + motu->tx_pcm_channels[0] += 4; + motu->tx_pcm_channels[1] += 4; + /* It's ADAT. */ + } else { + motu->tx_pcm_channels[0] += 8; + motu->tx_pcm_channels[1] += 4; + } + } + } + + if (motu->spec->rx_optical_ifaces > 0) { + /* The optical output interface A is enabled. */ + if (data & 0x00000100) { + /* It's S/PDIF. */ + if (data & 0x00040000) { + motu->rx_pcm_channels[0] += 4; + motu->rx_pcm_channels[1] += 4; + /* It's ADAT. */ + } else { + motu->rx_pcm_channels[0] += 8; + motu->rx_pcm_channels[1] += 4; + } + } + + /* The optical output interface B is enabled. */ + if (data & 0x00000200) { + /* It's S/PDIF. */ + if (data & 0x00400000) { + motu->rx_pcm_channels[0] += 4; + motu->rx_pcm_channels[1] += 4; + /* It's ADAT. */ + } else { + motu->rx_pcm_channels[0] += 8; + motu->rx_pcm_channels[1] += 4; + } + } + } + + return 0; +} + +int snd_motu_stream_update_current_channels(struct snd_motu *motu) +{ + struct snd_motu_spec *spec = motu->spec; + unsigned int i; + int err; + + /* Initializing. */ + for (i = 0; i < 3; i++) { + motu->tx_pcm_channels[i] = spec->tx_common_pcm_channels[i]; + motu->rx_pcm_channels[i] = spec->rx_common_pcm_channels[i]; + } + + if (spec->generation == 2) + err = update_channel_cache_g2(motu); + else + err = update_channel_cache_g3(motu); + + return err; +} + +static void stop_stream(struct snd_motu *motu, struct amdtp_stream *stream) +{ + __u32 reg; + __be32 data; + struct fw_iso_resources *resources; + int err; + + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream); + + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + 0xfffff0000b00ull, + &data, sizeof(data), 0); + if (err < 0) + return; + + if (stream == &motu->tx_stream) { + reg = be32_to_cpu(data) & 0x00ffffff; + reg |= 0x80000000; + resources = &motu->tx_resources; + } else { + reg = be32_to_cpu(data) & 0xff00ffff; + reg |= 0x00800000; + resources = &motu->rx_resources; + } + + data = cpu_to_be32(reg); + snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + 0xfffff0000b00ull, + &data, sizeof(data), 0); + + fw_iso_resources_free(resources); +} + +static int start_stream(struct snd_motu *motu, struct amdtp_stream *stream, + unsigned int rate) +{ + __u32 reg; + __be32 data; + struct fw_iso_resources *resources; + unsigned int pcm_chs, midi_ports; + unsigned int i, mode; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_rates); i++) { + if (snd_motu_rates[i] == rate) { + mode = i / 2; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_rates)) + return -EINVAL; + + if (stream == &motu->tx_stream) { + resources = &motu->tx_resources; + pcm_chs = motu->tx_pcm_channels[mode]; + midi_ports = motu->spec->tx_midi_ports; + } else { + resources = &motu->rx_resources; + pcm_chs = motu->rx_pcm_channels[mode]; + midi_ports = motu->spec->rx_midi_ports; + } + + err = amdtp_motu_set_parameters(stream, rate, pcm_chs, midi_ports); + if (err < 0) + return err; + + /* Get current status. */ + err = snd_fw_transaction(motu->unit, TCODE_READ_QUADLET_REQUEST, + 0xfffff0000b00ull, + &data, sizeof(data), 0); + if (err < 0) + return err; + + /* Get isochronous resources. */ + err = fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + return err; + + /* Start isochronous context. */ + err = amdtp_stream_start(stream, resources->channel, + fw_parent_device(motu->unit)->max_speed); + if (err < 0) + goto error; + + /* Start packet stream with the isochronous channel. */ + if (resources == &motu->tx_resources) { + reg = be32_to_cpu(data) & 0xff00ffff; + reg |= resources->channel << 16; + reg |= 0x00c00000; + } else { + reg = be32_to_cpu(data) & 0x00ffffff; + reg |= resources->channel << 24; + reg |= 0xc0000000; + } + data = cpu_to_be32(reg); + err = snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + 0xfffff0000b00ull, + &data, sizeof(data), 0); + if (err < 0) { + amdtp_stream_stop(stream); + goto error; + } + + return 0; +error: + fw_iso_resources_free(resources); + return err; +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate) +{ + unsigned int curr_rate; + int err = 0; + + if (motu->substreams_counter == 0) + goto end; + + /* Some packet queueing errors. */ + if (amdtp_streaming_error(&motu->tx_stream) || + amdtp_streaming_error(&motu->rx_stream)) { + stop_stream(motu, &motu->tx_stream); + stop_stream(motu, &motu->rx_stream); + } + + err = snd_motu_stream_update_current_channels(motu); + if (err < 0) + return err; + + /* Stop stream if rate is different. */ + err = snd_motu_stream_get_rate(motu, &curr_rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to get sampling rate\n"); + goto end; + } + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + stop_stream(motu, &motu->tx_stream); + stop_stream(motu, &motu->rx_stream); + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + err = snd_motu_stream_set_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate\n"); + goto end; + } + + /* Start both streams. */ + err = start_stream(motu, &motu->rx_stream, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start AMDTP master stream\n"); + goto end; + } + + if (!amdtp_stream_wait_callback(&motu->rx_stream, + CALLBACK_TIMEOUT)) { + stop_stream(motu, &motu->rx_stream); + err = -ETIMEDOUT; + goto end; + } + } + + if (!amdtp_stream_running(&motu->tx_stream)) { + err = start_stream(motu, &motu->tx_stream, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start AMDTP slave stream\n"); + stop_stream(motu, &motu->rx_stream); + goto end; + } + if (!amdtp_stream_wait_callback(&motu->tx_stream, + CALLBACK_TIMEOUT)) { + stop_stream(motu, &motu->rx_stream); + stop_stream(motu, &motu->tx_stream); + err = -ETIMEDOUT; + } + } +end: + return err; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->substreams_counter > 0) + return; + + stop_stream(motu, &motu->tx_stream); + stop_stream(motu, &motu->rx_stream); +} + +static int init_stream(struct snd_motu *motu, struct amdtp_stream *stream) +{ + int err; + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + + if (stream == &motu->tx_stream) { + resources = &motu->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &motu->rx_resources; + dir = AMDTP_OUT_STREAM; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(stream, motu->unit, dir); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +static void destroy_stream(struct snd_motu *motu, struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + + if (stream == &motu->tx_stream) + resources = &motu->tx_resources; + else + resources = &motu->rx_resources; + + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + motu->substreams_counter = 0; + + err = init_stream(motu, &motu->tx_stream); + if (err < 0) + return err; + + err = init_stream(motu, &motu->rx_stream); + if (err < 0) + destroy_stream(motu, &motu->tx_stream); + + return err; +} + +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + destroy_stream(motu, &motu->tx_stream); + destroy_stream(motu, &motu->rx_stream); + + motu->substreams_counter = 0; +} + +void snd_motu_stream_update_duplex(struct snd_motu *motu) +{ + stop_stream(motu, &motu->tx_stream); + stop_stream(motu, &motu->rx_stream); + + fw_iso_resources_update(&motu->tx_resources); + fw_iso_resources_update(&motu->rx_resources); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index eb83e87..8666819 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -41,6 +41,7 @@ static void motu_card_free(struct snd_card *card) { struct snd_motu *motu = card->private_data;
+ snd_motu_stream_destroy_duplex(motu); fw_unit_put(motu->unit);
mutex_destroy(&motu->mutex); @@ -66,6 +67,10 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
name_card(motu);
+ err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -86,9 +91,13 @@ static void motu_remove(struct fw_unit *unit) snd_card_free_when_closed(motu->card); }
-static void motu_bus_reset(struct fw_unit *unit) +static void motu_bus_update(struct fw_unit *unit) { - return; + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + mutex_lock(&motu->mutex); + snd_motu_stream_update_duplex(motu); + mutex_unlock(&motu->mutex); }
static struct snd_motu_spec motu_828mk2 = { @@ -259,7 +268,7 @@ static struct fw_driver motu_driver = { .bus = &fw_bus_type, }, .probe = motu_probe, - .update = motu_bus_reset, + .update = motu_bus_update, .remove = motu_remove, .id_table = motu_id_table, }; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 690818f..b3fc558 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -49,6 +49,32 @@ struct snd_motu {
/* Model dependent information. */ struct snd_motu_spec *spec; + + /* For streaming */ + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int substreams_counter; + + /* Between pcm.open() and pcm.prepare(). */ + unsigned int tx_pcm_channels[3]; + unsigned int rx_pcm_channels[3]; +}; + +#define MOTU_REG_BASE 0xfffff0000000ull + +#define SND_MOTU_RATES_COUNT 6 +extern const unsigned int snd_motu_rates[SND_MOTU_RATES_COUNT]; + +enum snd_motu_clock { + SND_MOTU_CLOCK_INTERNAL = 0, + SND_MOTU_CLOCK_OPT, /* S/PDIF or ADAT */ + SND_MOTU_CLOCK_SPDIF_COAX, + SND_MOTU_CLOCK_SPH, /* SMPTE */ + SND_MOTU_CLOCK_ADAT_DSUB, + SND_MOTU_CLOCK_AESEBU_XLR, + SND_MOTU_CLOCK_WORD_BNC, };
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, @@ -59,4 +85,17 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, void amdtp_motu_set_pcm_format(struct amdtp_stream *s, snd_pcm_format_t format); void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, struct snd_rawmidi_substream *midi); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); +void snd_motu_stream_update_duplex(struct snd_motu *motu); + +int snd_motu_stream_get_rate(struct snd_motu *motu, unsigned int *rate); +int snd_motu_stream_set_rate(struct snd_motu *motu, unsigned int rate); +int snd_motu_stream_get_clock(struct snd_motu *motu, + enum snd_motu_clock *src); +int snd_motu_stream_update_current_channels(struct snd_motu *motu); + #endif
On Sat, Jul 11, 2015 at 11:12:43PM +0900, Takashi Sakamoto wrote:
Several clock sources are available, while there're no differences about packet transmission among clock sources. The value of SYT field in transferred packets is always zero. Thus, streams in both direction don't build synchronization.
Perhaps I've misinterpreted what's meant here, but the above doesn't seem correct to me. MOTU devices to synchronise tx streams with rx streams. They use the timestamps in the rx packets to synchronise the tx packets to the device's audio clock as I outlined in an earlier message. This of course makes the tx side dependent on the rx side, something which might not be allowed for in this version of the driver.
The number of data chunks for PCM samples is decided according to current sampling rate, optical mode for digital input/output interfaces. For PCM functionality, the number of data blocks is cached in device instance.
As far as I can tell there is no allowance in this driver for channel naming. I do not know whether the ALSA API allows for such things, but with multichannel interfaces with different types of I/O it is very helpful to the user that there is some indication as to what each stream does. Can you comment on this?
Another thing which has proven very useful for FFADO users is to have the first two output channels mapped to something is is generally useful, rather than simply ordering the channels in the way defined by the data format of the packets. Exactly what this looks like depends on the interface and the precise mix of channels provided. For example, sometimes it's the headphone outputs, other times it's the designated "Main" outputs. Again, I'd be interested in your thoughts about this and how something along these lines might be done using this driver. Would it come down to the writing of a suitable .asoundrc file?
Regards jonathan
On Sat, Jul 11, 2015 at 11:12:43PM +0900, Takashi Sakamoto wrote:
+enum snd_motu_clock {
- SND_MOTU_CLOCK_INTERNAL = 0,
- SND_MOTU_CLOCK_OPT, /* S/PDIF or ADAT */
- SND_MOTU_CLOCK_SPDIF_COAX,
- SND_MOTU_CLOCK_SPH, /* SMPTE */
- SND_MOTU_CLOCK_ADAT_DSUB,
- SND_MOTU_CLOCK_AESEBU_XLR,
- SND_MOTU_CLOCK_WORD_BNC,
};
I'm not convinced that perpending the physical interface connection is all that necessary because generally speaking the clock source uniquely implies the physical connection. Word clock is always on BNC, AES/EBU is always on XRL, an SPDIF clock is assumed to be on coax and an "ADAT clock" is always on the D-sub (it is common knowledge that this is different from an optical clock). I will concede that for qualifying the ADAT clock is justifiable to avoid confusion (in FFADO I used ADAT_9PIN). I also think that it is unnecessary to abbreviate "optical" to "opt": there's no reason not to use 4 additional letters and "OPTICAL" is much clearer than "OPT" (the latter is ambiguous and could be taken to be short for "optional" for example). Finally, the SMTPE clock should be called just this: the fact that it happens to be transported via the SPH is largely irrelevant. Using SMTPE in the identifer makes the source entirely clear as to what's being done, whereas SPH is rather vague.
Therefore I would suggest the following set of clock identifiers would be perfectly adequate:
SND_MOTU_CLOCK_INTERNAL SND_MOTU_CLOCK_OPTICAL SND_MOTU_CLOCK_SPDIF SND_MOTU_CLOCK_SMPTE SND_MOTU_CLOCK_ADAT_DSUB SND_MOTU_CLOCK_AESEBU SND_MOTU_CLOCK_WORDCLOCK
Regards jonathan
MOTU FireWire series can transfer messages to registered address. These messages are transferred for the status of internal clock synchronization just after starting streams.
When the synchronization is stable, it's 0x01ffffff. Else, it's 0x05ffffff.
This commit adds a functionality to receive the message. Currently, the received message are output to system logging.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-transaction.c | 97 ++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 8 +++ sound/firewire/motu/motu.h | 7 +++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-transaction.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index 1547a98..e41c693 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,2 @@ -snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu.o +snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu-transaction.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 0000000..af08ecd --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,97 @@ +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +static void handle_message(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 snd_motu *motu = callback_data; + u32 *buf = (__be32 *)data; + + /* + if (offset == motu->async_handler.offset) { + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + + wake_up(&motu->hwdep_wait); + } + */ + + snd_printk(KERN_INFO"%08x, %ld\n", be32_to_cpu(buf[0]), length / 4); + + fw_send_response(card, request, RCODE_COMPLETE); +} +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + MOTU_REG_BASE + 0x0b04, + &data, sizeof(data), 0); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + MOTU_REG_BASE + 0x0b08, + &data, sizeof(data), 0); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = 0; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + MOTU_REG_BASE + 0x0b04, + &data, sizeof(data), 0); + snd_fw_transaction(motu->unit, TCODE_WRITE_QUADLET_REQUEST, + MOTU_REG_BASE + 0x0b08, + &data, sizeof(data), 0); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 8666819..3c7a51f 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -42,6 +42,7 @@ static void motu_card_free(struct snd_card *card) struct snd_motu *motu = card->private_data;
snd_motu_stream_destroy_duplex(motu); + snd_motu_transaction_unregister(motu); fw_unit_put(motu->unit);
mutex_destroy(&motu->mutex); @@ -71,6 +72,10 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) if (err < 0) goto error;
+ err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; @@ -95,6 +100,9 @@ static void motu_bus_update(struct fw_unit *unit) { struct snd_motu *motu = dev_get_drvdata(&unit->device);
+ /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); + mutex_lock(&motu->mutex); snd_motu_stream_update_duplex(motu); mutex_unlock(&motu->mutex); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index b3fc558..1597e99 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -60,6 +60,9 @@ struct snd_motu { /* Between pcm.open() and pcm.prepare(). */ unsigned int tx_pcm_channels[3]; unsigned int rx_pcm_channels[3]; + + /* For messaging. */ + struct fw_address_handler async_handler; };
#define MOTU_REG_BASE 0xfffff0000000ull @@ -98,4 +101,8 @@ int snd_motu_stream_get_clock(struct snd_motu *motu, enum snd_motu_clock *src); int snd_motu_stream_update_current_channels(struct snd_motu *motu);
+int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); + #endif
This commit adds a proc node for debugging purpose.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 3 +- sound/firewire/motu/motu-proc.c | 73 +++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 2 ++ sound/firewire/motu/motu.h | 3 ++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-proc.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index e41c693..de6ca3b 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,2 +1,3 @@ -snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu-transaction.o motu.o +snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu-transaction.o \ + motu-proc.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 0000000..bc84537 --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,73 @@ +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_INTERNAL] = "internal", + [SND_MOTU_CLOCK_OPT] = "optical interface with ADAT or S/PDIF", + [SND_MOTU_CLOCK_SPDIF_COAX] = "coaxial interface with S/PDIF", + [SND_MOTU_CLOCK_SPH] = "SMPTE in SPH", + [SND_MOTU_CLOCK_ADAT_DSUB] = "D-Sub interface with ADAT", + [SND_MOTU_CLOCK_AESEBU_XLR] = "XLR interface with AES/EBU", + [SND_MOTU_CLOCK_WORD_BNC] = "BNC interface with Word", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + unsigned int rate; + enum snd_motu_clock src; + + if (snd_motu_stream_get_rate(motu, &rate) < 0) + return; + if (snd_motu_stream_get_clock(motu, &src) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[src]); +} + +static void add_node(struct snd_motu *motu, 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(motu->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, motu, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + 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(motu->card, "firewire", + motu->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(motu, root, "clock", proc_read_clock); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 3c7a51f..5717854 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -76,6 +76,8 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) if (err < 0) goto error;
+ snd_motu_proc_init(motu); + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 1597e99..8486dd8 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -23,6 +23,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/rawmidi.h> +#include <sound/info.h>
#include "../amdtp-stream.h" #include "../iso-resources.h" @@ -105,4 +106,6 @@ int snd_motu_transaction_register(struct snd_motu *motu); int snd_motu_transaction_reregister(struct snd_motu *motu); void snd_motu_transaction_unregister(struct snd_motu *motu);
+void snd_motu_proc_init(struct snd_motu *motu); + #endif
On Sat, Jul 11, 2015 at 11:12:45PM +0900, Takashi Sakamoto wrote:
This commit adds a proc node for debugging purpose. : +static void proc_read_clock(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
+{
- struct snd_motu *motu = entry->private_data;
- unsigned int rate;
- enum snd_motu_clock src;
- if (snd_motu_stream_get_rate(motu, &rate) < 0)
return;
- if (snd_motu_stream_get_clock(motu, &src) < 0)
return;
- snd_iprintf(buffer, "Rate:\t%d\n", rate);
- snd_iprintf(buffer, "Source:\t%s\n", clock_names[src]);
+}
Is it necessary to define a proc node for this information? It is trivial to obtain the status of MOTU devices directly from the device regardless of the state of the streaming system. In fact, while the above code seems to be linked to a running stream, the concept of the device clock persists even when no streams are running. Or is the intent here to verify the internal state of any running ALSA stream structures rather than the physical device itself?
Regards jonathan
This commit adds PCM functionality to transmit/receive PCM samples.
When one of PCM substreams are running or external clock source is selected, current sampling rate is used. Else, the sampling rate is changed as an userspace application requests.
The number of data chunks for PCM samples should not be changed during snd_pcm_open() to snd_pcm_hw_params(), for correct result of matching PCM substream format.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-pcm.c | 385 +++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 4 + sound/firewire/motu/motu.h | 2 + 4 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-pcm.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index de6ca3b..c1c9888 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu-transaction.o \ - motu-proc.o motu.o + motu-proc.o motu-pcm.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 0000000..b6c201f --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,385 @@ +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_motu *motu = substream->private_data; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, rate, mode, *pcm_channels; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = motu->tx_pcm_channels; + else + pcm_channels = motu->rx_pcm_channels; + + for (i = 0; i < ARRAY_SIZE(snd_motu_rates); ++i) { + rate = snd_motu_rates[i]; + mode = i / 2; + + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_motu *motu = substream->private_data; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, rate, mode, *pcm_channels; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = motu->tx_pcm_channels; + else + pcm_channels = motu->rx_pcm_channels; + + for (i = 0; i < ARRAY_SIZE(snd_motu_rates); ++i) { + rate = snd_motu_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + channels.min = min(channels.min, pcm_channels[mode]); + channels.max = max(channels.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + unsigned int *pcm_channels) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_rates); ++i) { + rate = snd_motu_rates[i]; + mode = i / 2; + if (pcm_channels[mode] == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels[mode]); + hw->channels_max = max(hw->channels_max, pcm_channels[mode]); + } + + snd_pcm_limit_hw_rates(runtime); +} + +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; /* byte 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 init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + unsigned int *pcm_channels; + int err; + + hw->info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_BLOCK_TRANSFER; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + pcm_channels = motu->tx_pcm_channels; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32 | + SNDRV_PCM_FMTBIT_S16; + stream = &motu->rx_stream; + pcm_channels = motu->rx_pcm_channels; + } + + limit_channels_and_rates(motu, runtime, pcm_channels); + limit_period_and_buffer(hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, substream, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, substream, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + enum snd_motu_clock src; + unsigned int rate; + int err; + + mutex_lock(&motu->mutex); + + err = snd_motu_stream_update_current_channels(motu); + if (err < 0) + goto end; + + err = init_hw_info(motu, substream); + if (err < 0) + goto end; + + err = snd_motu_stream_get_clock(motu, &src); + 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 (src != SND_MOTU_CLOCK_INTERNAL || src != SND_MOTU_CLOCK_INTERNAL || + amdtp_stream_pcm_running(&motu->tx_stream) || + amdtp_stream_pcm_running(&motu->rx_stream)) { + err = snd_motu_stream_get_rate(motu, &rate); + if (err < 0) + goto end; + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); +end: + mutex_unlock(&motu->mutex); + + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->substreams_counter++; + mutex_unlock(&motu->mutex); + } + + amdtp_motu_set_pcm_format(&motu->tx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&motu->mutex); + motu->substreams_counter++; + mutex_unlock(&motu->mutex); + } + + amdtp_motu_set_pcm_format(&motu->rx_stream, params_format(hw_params)); + + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->substreams_counter--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + motu->substreams_counter--; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu, substream->runtime->rate); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_stream_pcm_pointer(&motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_hw_params, + .hw_free = capture_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + static struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_hw_params, + .hw_free = playback_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, "MOTU", 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + + return 0; +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 5717854..5deb592 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -78,6 +78,10 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
snd_motu_proc_init(motu);
+ err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 8486dd8..50c19a5 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -108,4 +108,6 @@ void snd_motu_transaction_unregister(struct snd_motu *motu);
void snd_motu_proc_init(struct snd_motu *motu);
+int snd_motu_create_pcm_devices(struct snd_motu *motu); + #endif
MOTU FireWire series uses isochronous packets to transfer MIDI messages.
This commit adds MIDI functionality to transfer/receive MIDI messages.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/motu/Makefile | 2 +- sound/firewire/motu/motu-midi.c | 143 ++++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu.c | 5 ++ sound/firewire/motu/motu.h | 3 + sound/firewire/tascam/tascam.c | 1 + 5 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/motu/motu-midi.c
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index c1c9888..e8055b9 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu-transaction.o \ - motu-proc.o motu-pcm.o motu.o + motu-proc.o motu-pcm.o motu-midi.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 0000000..ab40fb2 --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,143 @@ +/* + * motu-miti.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ +#include "motu.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + mutex_lock(&motu->mutex); + + motu->substreams_counter++; + err = snd_motu_stream_start_duplex(motu, 0); + + mutex_unlock(&motu->mutex); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + motu->substreams_counter--; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_motu *motu, + 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", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + struct snd_motu_spec *spec = motu->spec; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + if (spec->tx_midi_ports + spec->rx_midi_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, + spec->rx_midi_ports, spec->tx_midi_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + if (spec->tx_midi_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(motu, str); + } + + if (spec->rx_midi_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(motu, str); + } + + if (spec->tx_midi_ports > 0 && spec->rx_midi_ports) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 5deb592..6c539ed 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -65,6 +65,7 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) card->private_free = motu_card_free;
mutex_init(&motu->mutex); + spin_lock_init(&motu->lock);
name_card(motu);
@@ -82,6 +83,10 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) if (err < 0) goto error;
+ err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 50c19a5..7ca8542 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -47,6 +47,7 @@ struct snd_motu { struct fw_unit *unit;
struct mutex mutex; + spinlock_t lock;
/* Model dependent information. */ struct snd_motu_spec *spec; @@ -110,4 +111,6 @@ void snd_motu_proc_init(struct snd_motu *motu);
int snd_motu_create_pcm_devices(struct snd_motu *motu);
+int snd_motu_create_midi_devices(struct snd_motu *motu); + #endif diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 2afb291..80e8780 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -121,6 +121,7 @@ static int snd_tscm_probe(struct fw_unit *unit, tscm->unit = fw_unit_get(unit);
mutex_init(&tscm->mutex); + spin_lock_init(&tscm->lock);
err = check_name(tscm); if (err < 0)
This commit adds hwdep interface so as the other firewire sound devices has.
This interface is designed for mixer/control applications. By using this interface, an 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/motu/Makefile | 2 +- sound/firewire/motu/motu-hwdep.c | 210 ++++++++++++++++++++++++++++++++++++++ sound/firewire/motu/motu-midi.c | 8 ++ sound/firewire/motu/motu-pcm.c | 22 +++- sound/firewire/motu/motu-stream.c | 38 +++++++ sound/firewire/motu/motu.c | 5 + sound/firewire/motu/motu.h | 11 ++ sound/firewire/tascam/tascam.c | 1 + 10 files changed, 295 insertions(+), 8 deletions(-) create mode 100644 sound/firewire/motu/motu-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index a82108e..31bc352 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -102,9 +102,10 @@ enum { SNDRV_HWDEP_IFACE_FW_OXFW, /* Oxford OXFW970/971 based device */ SNDRV_HWDEP_IFACE_FW_DIGI00X, /* Digidesign Digi 002/003 family */ SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ + SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_TASCAM + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_MOTU };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index c5cb86d..1fe4c87 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -65,7 +65,8 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_OXFW 4 #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 -/* RME, MOTU, ... */ +#define SNDRV_FIREWIRE_TYPE_MOTU 7 +/* RME, ... */
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index e8055b9..b137da6 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -1,3 +1,3 @@ snd-firewire-motu-objs := amdtp-motu.o motu-stream.o motu-transaction.o \ - motu-proc.o motu-pcm.o motu-midi.o motu.o + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o motu.o obj-m += snd-firewire-motu.o diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 0000000..eb94e47 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,210 @@ +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read_locked(struct snd_motu *motu, 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 = (motu->dev_lock_count > 0); + motu->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_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + if (motu->dev_lock_changed) + count = hwdep_read_locked(motu, buf, count, offset); + + spin_unlock_irq(&motu->lock); + + return count; +} + +static long hwdep_write(struct snd_hwdep *hwdep, const char __user *data, + long count, loff_t *offset) +{ + /* TODO: Do something. */ + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + unsigned int events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | POLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->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_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + 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, + .write = hwdep_write, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, +}; + +int snd_motu_create_hwdep_device(struct snd_motu *motu) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, "MOTU", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; + hwdep->ops = hwdep_ops; + hwdep->private_data = motu; + hwdep->exclusive = true; +end: + return err; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c index ab40fb2..c58d249 100644 --- a/sound/firewire/motu/motu-midi.c +++ b/sound/firewire/motu/motu-midi.c @@ -12,6 +12,10 @@ static int midi_open(struct snd_rawmidi_substream *substream) struct snd_motu *motu = substream->rmidi->private_data; int err;
+ err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex);
motu->substreams_counter++; @@ -19,6 +23,9 @@ static int midi_open(struct snd_rawmidi_substream *substream)
mutex_unlock(&motu->mutex);
+ if (err < 0) + snd_motu_stream_lock_release(motu); + return err; }
@@ -33,6 +40,7 @@ static int midi_close(struct snd_rawmidi_substream *substream)
mutex_unlock(&motu->mutex);
+ snd_motu_stream_lock_release(motu); return 0; }
diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c index b6c201f..b6e8ef9 100644 --- a/sound/firewire/motu/motu-pcm.c +++ b/sound/firewire/motu/motu-pcm.c @@ -163,19 +163,23 @@ static int pcm_open(struct snd_pcm_substream *substream) unsigned int rate; int err;
+ err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + mutex_lock(&motu->mutex);
err = snd_motu_stream_update_current_channels(motu); if (err < 0) - goto end; + return err;
err = init_hw_info(motu, substream); if (err < 0) - goto end; + goto err_locked;
err = snd_motu_stream_get_clock(motu, &src); if (err < 0) - goto end; + goto err_locked;
/* * When source of clock is not internal or any PCM streams are running, @@ -186,20 +190,28 @@ static int pcm_open(struct snd_pcm_substream *substream) amdtp_stream_pcm_running(&motu->rx_stream)) { err = snd_motu_stream_get_rate(motu, &rate); if (err < 0) - goto end; + goto err_locked; substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; }
snd_pcm_set_sync(substream); -end: + mutex_unlock(&motu->mutex);
return err; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + return 0; }
diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c index d6fc84f..104fdc4 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -630,3 +630,41 @@ void snd_motu_stream_update_duplex(struct snd_motu *motu) fw_iso_resources_update(&motu->tx_resources); fw_iso_resources_update(&motu->rx_resources); } + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index 6c539ed..b3f3728 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -66,6 +66,7 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
mutex_init(&motu->mutex); spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait);
name_card(motu);
@@ -87,6 +88,10 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) if (err < 0) goto error;
+ err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) goto error; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index 7ca8542..76347b2 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -16,6 +16,7 @@ #include <linux/mod_devicetable.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/compat.h>
#include <sound/control.h> #include <sound/core.h> @@ -24,6 +25,8 @@ #include <sound/pcm_params.h> #include <sound/rawmidi.h> #include <sound/info.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../amdtp-stream.h" #include "../iso-resources.h" @@ -65,6 +68,11 @@ struct snd_motu {
/* For messaging. */ struct fw_address_handler async_handler; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
#define MOTU_REG_BASE 0xfffff0000000ull @@ -96,6 +104,8 @@ void snd_motu_stream_destroy_duplex(struct snd_motu *motu); int snd_motu_stream_start_duplex(struct snd_motu *motu, unsigned int rate); void snd_motu_stream_stop_duplex(struct snd_motu *motu); void snd_motu_stream_update_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu);
int snd_motu_stream_get_rate(struct snd_motu *motu, unsigned int *rate); int snd_motu_stream_set_rate(struct snd_motu *motu, unsigned int rate); @@ -113,4 +123,5 @@ int snd_motu_create_pcm_devices(struct snd_motu *motu);
int snd_motu_create_midi_devices(struct snd_motu *motu);
+int snd_motu_create_hwdep_device(struct snd_motu *motu); #endif diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c index 80e8780..5bb27ea 100644 --- a/sound/firewire/tascam/tascam.c +++ b/sound/firewire/tascam/tascam.c @@ -122,6 +122,7 @@ static int snd_tscm_probe(struct fw_unit *unit,
mutex_init(&tscm->mutex); spin_lock_init(&tscm->lock); + init_waitqueue_head(&tscm->hwdep_wait);
err = check_name(tscm); if (err < 0)
On Sat, Jul 11, 2015 at 11:12:48PM +0900, Takashi Sakamoto wrote:
This commit adds hwdep interface so as the other firewire sound devices has. :
+static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{
- struct fw_device *dev = fw_parent_device(motu->unit);
- struct snd_firewire_get_info info;
- memset(&info, 0, sizeof(info));
- info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
A SNDRV_FIREWIRE_TYPE_MOTU type identifier was defined, so should it not be used here?
+int snd_motu_create_hwdep_device(struct snd_motu *motu) +{
- struct snd_hwdep *hwdep;
- int err;
- err = snd_hwdep_new(motu->card, "MOTU", 0, &hwdep);
- if (err < 0)
goto end;
- strcpy(hwdep->name, "MOTU");
- hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
Similarly, SNDRV_HWDEP_IFACE_FW_MOTU is defined and it would seem to me that it should be used here instead of SNDRV_HWDEP_IFACE_FW_FIREWORKS. Am I missing something?
Regards jonathan
On Jul 11 2015 23:12, Takashi Sakamoto wrote:
Takashi Sakamoto (37): ALSA: firewire-lib: rename 'amdtp' to 'amdtp-stream' for functional separation
Oops. I forgot to use '--find-renames' to this patchset... I'm sorry but would you please grant to posting the first patch?
Thanks
Takashi Sakamoto
Hi Takashi
On Sat, Jul 11, 2015 at 11:12:11PM +0900, Takashi Sakamoto wrote:
This patchset is for three new drivers and related modification to firewire-lib module. :
- Patch 29-37: for firewire-motu, a new driver for MOTU FireWire series
:
- firewire-motu:
- experimental, for preview to developers
A few general comments about the MOTU driver.
1) Unlike the FFADO driver (where I imagine much of the detail has been taken) there are a lot of magic numbers in this driver both in terms of the registers being written to and the values written to those registers. I appreciate that ALSA may have different coding conventions, but I think it would make sense to use defines to document what these magic values are, especially since in a vast majority of cases we do know what the relevant bits and registers are doing.
2) As has been mentioned before, the streaming drivers for firewire devices in ALSA are a successor to those in the FFADO project. In the case of some of the other firewire drivers (DICE etc) I understand that the ALSA code is predominantly a reimplementation based on published standards. However, given the structure and terminology used within the the experimental MOTU driver, it seems to be based fairly heavily on my FFADO MOTU driver which itself has come about due to a significant time investment on my part over many years to decode and analyse the protocol used by these interfaces. I have no problem with this reuse of knowledge since this is precisely how open source should work. However, it would be appreciated if this prior work by myself within FFADO was at least acknowledged in some way in the ALSA MOTU driver files.
Similarly, knowledge of the requirements of many MOTU devices have been contributed by others as a result of directed experimentation. Those individuals are acknowledged in FFADO source code and in our repo. Where this information has been utilised in the ALSA code I think it is fair that their contribution is noted somehow. I don't know what the ALSA convention is with regard to such things - such as single-line comments to this effect within the code.
As mentioned earlier I will continue to work through the patchset and provide comments and suggestions as time allows over the coming few days.
Regards jonathan
participants (4)
-
Clemens Ladisch
-
Jonathan Woithe
-
Takashi Iwai
-
Takashi Sakamoto