[alsa-devel] [PATCH 00/25 v2] ALSA: support AMDTP variants
This patchset for Linux 4.3 updates my previous one:
[alsa-devel] [PATCH 00/25] ALSA: support AMDTP variants http://mailman.alsa-project.org/pipermail/alsa-devel/2015-August/096338.html
As the same as the previous, the second patch is too large to be blasted by mailing list server. I request server administrators to grant it.
Changes: * Enable to select as kernel module or the others. * Add a comment for memory barriers in firewire-lib. * Add a comment about using jiffies in firewire-lib. * Use jiffies_64 helpers instead of jiffies helpers in firewire-lib.
Rest of work: * Using proper clocksource for MIDI throttle in asynchronous transaction helper. * Handling control/status messages for TASCAM driver. * Supporting MIDI exclusive message in outgoing packets for TASCAM driver. * Researching a mechanism to de-synchronize sometimes for Digi00x family.
You can see this patchset as a part in this branch. https://github.com/takaswie/sound/tree/amdtp-variants
Takashi Sakamoto (25): ALSA: firewire-lib: rename 'amdtp' to 'amdtp-stream' for functional separation ALSA: firewire-lib: functional separation for packet transmission layer and data processing layer ALSA: firewire-lib: add helper functions for asynchronous MIDI port ALSA: firewire-lib: add a restriction for a transaction at once 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 skeleton for Digi 002/003 family ALSA: firewire-digi00x: add data processing 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 data processing 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
include/uapi/sound/asound.h | 4 +- include/uapi/sound/firewire.h | 9 + sound/firewire/Kconfig | 27 ++ sound/firewire/Makefile | 4 +- sound/firewire/amdtp-am824.c | 451 ++++++++++++++++++++++++ sound/firewire/amdtp-am824.h | 47 +++ sound/firewire/{amdtp.c => amdtp-stream.c} | 375 ++++---------------- sound/firewire/{amdtp.h => amdtp-stream.h} | 113 ++---- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_midi.c | 16 +- sound/firewire/bebob/bebob_pcm.c | 12 +- sound/firewire/bebob/bebob_stream.c | 34 +- sound/firewire/dice/dice-midi.c | 16 +- sound/firewire/dice/dice-pcm.c | 12 +- sound/firewire/dice/dice-stream.c | 41 ++- sound/firewire/dice/dice.h | 2 +- sound/firewire/digi00x/Makefile | 4 + sound/firewire/digi00x/amdtp-dot.c | 436 +++++++++++++++++++++++ sound/firewire/digi00x/digi00x-hwdep.c | 200 +++++++++++ sound/firewire/digi00x/digi00x-midi.c | 198 +++++++++++ sound/firewire/digi00x/digi00x-pcm.c | 353 +++++++++++++++++++ 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 | 157 +++++++++ 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 | 10 +- sound/firewire/fireworks/fireworks_stream.c | 8 +- sound/firewire/lib.c | 147 ++++++++ sound/firewire/lib.h | 52 +++ sound/firewire/oxfw/oxfw-midi.c | 16 +- sound/firewire/oxfw/oxfw-pcm.c | 10 +- sound/firewire/oxfw/oxfw-stream.c | 11 +- sound/firewire/oxfw/oxfw.h | 2 +- sound/firewire/tascam/Makefile | 4 + sound/firewire/tascam/amdtp-tascam.c | 248 +++++++++++++ sound/firewire/tascam/tascam-hwdep.c | 202 +++++++++++ sound/firewire/tascam/tascam-midi.c | 162 +++++++++ sound/firewire/tascam/tascam-pcm.c | 298 ++++++++++++++++ sound/firewire/tascam/tascam-proc.c | 88 +++++ sound/firewire/tascam/tascam-stream.c | 497 +++++++++++++++++++++++++++ sound/firewire/tascam/tascam-transaction.c | 291 ++++++++++++++++ sound/firewire/tascam/tascam.c | 230 +++++++++++++ sound/firewire/tascam/tascam.h | 140 ++++++++ 48 files changed, 5295 insertions(+), 498 deletions(-) create mode 100644 sound/firewire/amdtp-am824.c create mode 100644 sound/firewire/amdtp-am824.h rename sound/firewire/{amdtp.c => amdtp-stream.c} (70%) rename sound/firewire/{amdtp.h => amdtp-stream.h} (77%) 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/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 packet transmission layer and data processing layer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Makefile | 2 +- sound/firewire/{amdtp.c => amdtp-stream.c} | 2 +- sound/firewire/{amdtp.h => amdtp-stream.h} | 4 ++-- 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 +- 8 files changed, 9 insertions(+), 9 deletions(-) rename sound/firewire/{amdtp.c => amdtp-stream.c} (99%) rename sound/firewire/{amdtp.h => amdtp-stream.h} (99%)
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.c b/sound/firewire/amdtp-stream.c similarity index 99% rename from sound/firewire/amdtp.c rename to sound/firewire/amdtp-stream.c index 2a153d2..50e4d40 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp-stream.c @@ -15,7 +15,7 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/rawmidi.h> -#include "amdtp.h" +#include "amdtp-stream.h"
#define TICKS_PER_CYCLE 3072 #define CYCLES_PER_SECOND 8000 diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp-stream.h similarity index 99% rename from sound/firewire/amdtp.h rename to sound/firewire/amdtp-stream.h index b2cf9e7..adab54f 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp-stream.h @@ -1,5 +1,5 @@ -#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED -#define SOUND_FIREWIRE_AMDTP_H_INCLUDED +#ifndef SOUND_FIREWIRE_AMDTP_STREAM_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_STREAM_H_INCLUDED
#include <linux/err.h> #include <linux/interrupt.h> 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 084d414..d54f171 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 are applications of Audio, while they use their own data format. This means that they use the same way for packet transmission as IEC 61883-6, especially one data block represents one event (PCM frame in ALSA). 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 for their data blocks format.
Supporting these models requires splitting to packet transmittion layer and data processing layer. This commit implements this idea and allows each driver to implement own data processing layer.
I note about the overhead which this commit takes. The packet transmission layer calls data processing layer every packetization. Therefore, the frequency is 8,000 times per second. This makes additional 8,000 function calls against current AMDTP/AM824 processing.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Makefile | 2 +- sound/firewire/amdtp-am824.c | 451 ++++++++++++++++++++++++++++ sound/firewire/amdtp-am824.h | 47 +++ sound/firewire/amdtp-stream.c | 373 ++++------------------- sound/firewire/amdtp-stream.h | 109 ++----- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_midi.c | 16 +- sound/firewire/bebob/bebob_pcm.c | 12 +- sound/firewire/bebob/bebob_stream.c | 34 ++- sound/firewire/dice/dice-midi.c | 16 +- sound/firewire/dice/dice-pcm.c | 12 +- 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 | 10 +- sound/firewire/fireworks/fireworks_stream.c | 8 +- sound/firewire/oxfw/oxfw-midi.c | 16 +- sound/firewire/oxfw/oxfw-pcm.c | 10 +- sound/firewire/oxfw/oxfw-stream.c | 11 +- sound/firewire/oxfw/oxfw.h | 2 +- 22 files changed, 711 insertions(+), 493 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..08da1e6 --- /dev/null +++ b/sound/firewire/amdtp-am824.c @@ -0,0 +1,451 @@ +/* + * 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 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. + */ +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_add_pcm_hw_constraints - add hw constraint to the PCM runtime + * @s: the AMDTP stream to configure + * @runtime: the PCM runtime + * + * The AMDTP stream must be initialized, especially for transmission mode. + */ +int amdtp_am824_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) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} +EXPORT_SYMBOL(amdtp_am824_add_pcm_hw_constraints); + +/** + * amdtp_am824_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_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 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. + */ +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..27be392 --- /dev/null +++ b/sound/firewire/amdtp-am824.h @@ -0,0 +1,47 @@ +#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); + +int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +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 50e4d40..d44a31b 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 = @@ -763,16 +519,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; @@ -783,8 +532,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 adab54f..58d80c5 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,63 +96,59 @@ 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; + /* quirk: indicate the value of dbc field in a first packet. */ + unsigned int tx_first_dbc;
+ /* 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; - /* quirk: indicate the value of dbc field in a first packet. */ - unsigned int tx_first_dbc;
+ /* 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); @@ -182,8 +157,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); @@ -240,24 +213,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..f43d17e 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; } @@ -146,7 +146,7 @@ pcm_init_hw_params(struct snd_bebob *bebob, if (err < 0) goto end;
- err = amdtp_stream_add_pcm_hw_constraints(s, runtime); + err = amdtp_am824_add_pcm_hw_constraints(s, runtime); end: return err; } @@ -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..3ed9a70 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; } @@ -156,7 +156,7 @@ static int init_hw_info(struct snd_dice *dice, if (err < 0) goto end;
- err = amdtp_stream_add_pcm_hw_constraints(stream, runtime); + err = amdtp_am824_add_pcm_hw_constraints(stream, runtime); end: return err; } @@ -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 c94a432..d5b19bc 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 d54f171..c7cb7de 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..e35c81e 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; } @@ -187,7 +187,7 @@ pcm_init_hw_params(struct snd_efw *efw, if (err < 0) goto end;
- err = amdtp_stream_add_pcm_hw_constraints(s, runtime); + err = amdtp_am824_add_pcm_hw_constraints(s, runtime); end: return err; } @@ -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 7e353f1..759f6e3 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..bfd0672 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; } @@ -158,7 +158,7 @@ static int init_hw_params(struct snd_oxfw *oxfw, if (err < 0) goto end;
- err = amdtp_stream_add_pcm_hw_constraints(stream, runtime); + err = amdtp_am824_add_pcm_hw_constraints(stream, runtime); end: return err; } @@ -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 77ad5b9..65732a3 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. In this case, MIDI messages are transferred in fixed-length payload. 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 payload for a transaction and a pointer for callback function to fill the payload 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()'.
The helper functions support retries to transferring MIDI messages when transmission errors occur. When transactions are successful, the helper functions call 'snd_rawmidi_transmit_ack()' to consume MIDI bytes in the buffer. Therefore, Each driver is expected to use 'snd_rawmidi_transmit_peek()'.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ sound/firewire/lib.h | 46 +++++++++++++++++++++++ 2 files changed, 150 insertions(+)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 7409edb..1ec4f52 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,109 @@ 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() is 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. */ + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = port->parent->generation; + smp_rmb(); + + 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
Currently, when waiting for a response, callers can start another transaction by scheduling another tasklet. This is not good for error processing.
This commit serialize request/response transactions, by adding one boolean member to represent idling state.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 9 +++++++++ sound/firewire/lib.h | 1 + 2 files changed, 10 insertions(+)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 1ec4f52..9da8894 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,8 @@ static void midi_port_tasklet(unsigned long data) type = TCODE_WRITE_BLOCK_REQUEST;
/* Start this transaction. */ + port->idling = false; + /* * In Linux FireWire core, when generation is updated with memory * barrier, node id has already been updated. In this module, After @@ -151,6 +159,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;
When two MIDI trigger callbacks can be called immediately, transactions for the second MIDI messages can be postpone till next trigger callback.
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 9da8894..83d20b6 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 have internal buffer to adjust output of received MIDI messages for MIDI serial bus, while the capacity of the buffer is limited. IEEE 1394 transactions can transfer more MIDI messages than MIDI serial bus can. This can cause buffer over flow in device side.
This commit adds throttle to limit MIDI data rate by counting intervals by jiffies between two MIDI messages. Usual MIDI messages consists of two or three bytes. This requires 1.302 to 1.953 mili-seconds interval between these messages. Using jiffies for this purpose is not perfect idea. Further work is needed for better implementation.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/lib.c | 26 +++++++++++++++++++++++++- sound/firewire/lib.h | 1 + 2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 83d20b6..5d04632 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,12 @@ static void midi_port_tasklet(unsigned long data) if (substream == NULL || snd_rawmidi_transmit_empty(substream)) return;
+ /* Do it in next chance. */ + if (time_before64(get_jiffies_64(), port->next_tick)) { + tasklet_schedule(&port->tasklet); + return; + } + /* * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). * Later, snd_rawmidi_transmit_ack() is called. @@ -107,8 +116,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 +129,18 @@ static void midi_port_tasklet(unsigned long data) else type = TCODE_WRITE_BLOCK_REQUEST;
+ /* + * Set interval to next transaction. + * + * TODO: Using jiffies to count 31,250 bits per second may be a bad + * idea. The jiffies is assumed to count up constantly according to HZ, + * while recent Linux goes to dynamic ticks or tickless system. Further + * work is required and the interval should be set with the other + * services, i.e. by using monotonic time with higher resolution. + */ + port->next_tick = jiffies_64 + + msecs_to_jiffies(port->consume_bytes * 8 * 1000 / 31250); + /* Start this transaction. */ port->idling = false;
@@ -163,6 +186,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..bfc353b 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; + __u64 next_tick;
__u64 addr; struct fw_transaction transaction;
Currently, when asynchronous transactions finish in error state and retries, tasklet scheduling and tasklet running also continues. This should be canceled at fatal error because it can cause endless loop.
This commit cancels transferring MIDI messages when 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 5d04632..013d40d 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. */ @@ -119,6 +122,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; } @@ -187,6 +193,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 bfc353b..84db912 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; __u64 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..13fec5b --- /dev/null +++ b/sound/firewire/digi00x/Makefile @@ -0,0 +1,2 @@ +snd-firewire-digi00x-objs := digi00x.o +obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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 is 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 is always zero 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 data processing layer to satisfy these differences.
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 on the device. With isochronous channel 0 or 1, this doesn't occur. As long as I investigated, this quirk is not observed when applying blocking mode to the received packets. 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 | 436 +++++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.h | 18 ++ 3 files changed, 455 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 13fec5b..87c4cfd 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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..069e089 --- /dev/null +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -0,0 +1,436 @@ +/* + * 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; + } +} + +int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* This protocol delivers 24 bit data in 32bit data channel. */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +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..f761d74 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,16 @@ 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); +int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +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 IEEE 1394 write transaction 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 transmitted 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 87c4cfd..32d8ebe 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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 f761d74..f4ce90e 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, @@ -47,4 +104,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 32d8ebe..3e0f028 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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 f4ce90e..e8eadc9 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> @@ -121,4 +122,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 | 342 +++++++++++++++++++++++++++++++++++ sound/firewire/digi00x/digi00x.c | 4 + sound/firewire/digi00x/digi00x.h | 3 + 4 files changed, 350 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 3e0f028..3c0ff6f 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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..465f022 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-pcm.c @@ -0,0 +1,342 @@ +/* + * 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) + return err; + + 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) + return err; + + return amdtp_dot_add_pcm_hw_constraints(s, substream->runtime); +} + +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 e8eadc9..c4c1746 100644 --- a/sound/firewire/digi00x/digi00x.h +++ b/sound/firewire/digi00x/digi00x.h @@ -123,4 +123,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 3c0ff6f..65a8089 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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 c4c1746..3715785 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; @@ -126,4 +127,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 65a8089..55e0b19 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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 465f022..ae96aff 100644 --- a/sound/firewire/digi00x/digi00x-pcm.c +++ b/sound/firewire/digi00x/digi00x-pcm.c @@ -118,21 +118,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; } }
@@ -141,18 +145,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 3715785..b0c00b2 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 @@ -123,9 +131,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: * When clock source is set as internal: - 0x00007051 - 0x00007052 - 0x00007054 - 0x00007057 - 0x00007058 * When clock source is set as somewhat external: - 0x00009000 - 0x00009010 - 0x00009020 - 0x00009021 - 0x00009022
The meaning of these contents is unknown.
Currently, playbacking at 44.1 kHz sometimes causes the de-synchronization. In this case, users can hear sounds with quite short gap every several minutes. The mechanism is not clear.
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 55e0b19..d7b1bcd 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-$(CONFIG_SND_FIREWIRE_DIGI00X) += 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 b0c00b2..bd759bf 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 @@ -114,6 +117,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 messages to control the devices. 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 bd759bf..9b54b5b 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
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
A part of my work is 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.
I observed that FW-1884 and FW-1082 don't work properly with 1394 OHCI controller based on VT6315. The controller can actually communicate packets to these models, while these models generate no sounds. 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.
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 | 28 ++++++++ 5 files changed, 197 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..605cb91 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-1082 + + 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..627129b --- /dev/null +++ b/sound/firewire/tascam/Makefile @@ -0,0 +1,2 @@ +snd-firewire-tascam-objs := tascam.o +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..ca6b6b1 --- /dev/null +++ b/sound/firewire/tascam/tascam.h @@ -0,0 +1,28 @@ +/* + * 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 "../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 devices.
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..29924ff 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 = 2, + .midi_playback_ports = 2, + .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 ca6b6b1..9c225c3 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -20,9 +20,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 627129b..1555206 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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 29924ff..5bf3290 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 9c225c3..8d4eb3f 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -17,6 +17,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/info.h>
#include "../lib.h"
@@ -39,3 +40,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 | 248 +++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.h | 18 +++ 3 files changed, 267 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 1555206..d06c737 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..6c15b98 --- /dev/null +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -0,0 +1,248 @@ +/* + * 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; + } +} + +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* + * Our implementation allows this protocol to deliver 24 bit sample in + * 32bit data channel. + */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +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 8d4eb3f..7f8ec3d 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -18,8 +18,13 @@ #include <sound/core.h> #include <sound/initval.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; @@ -39,6 +44,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 @@ -48,4 +59,11 @@ 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); +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +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 d06c737..0a1c387 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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 5bf3290..875a74c 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 7f8ec3d..2c935f7 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -59,6 +59,26 @@ 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); @@ -66,4 +86,13 @@ int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, struct snd_pcm_runtime *runtime); 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 | 287 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 2 + 4 files changed, 294 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 0a1c387..f075b9b 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..b1aa980 --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,287 @@ +/* + * 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; + struct amdtp_stream *stream; + unsigned int pcm_channels; + + runtime->hw = hardware; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + stream = &tscm->tx_stream; + 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; + stream = &tscm->rx_stream; + 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); + + return amdtp_tscm_add_pcm_hw_constraints(stream, runtime); +} + +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 875a74c..18a7e70 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 2c935f7..ab922d8 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -96,3 +96,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);
On Sat, 22 Aug 2015 11:19:38 +0200, Takashi Sakamoto wrote:
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 | 287 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 2 + 4 files changed, 294 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 0a1c387..f075b9b 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..b1aa980 --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,287 @@ +/*
- 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;
- struct amdtp_stream *stream;
- unsigned int pcm_channels;
- runtime->hw = hardware;
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
stream = &tscm->tx_stream;
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;
stream = &tscm->rx_stream;
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);
- return amdtp_tscm_add_pcm_hw_constraints(stream, runtime);
+}
+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)) {
Must be '||'.
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);
- }
This looks dangerous. For example...
- 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));
... if the buffer allocation fails, this return an error. And snd_pcm_hw_params() calls hw_free() at the error path. But hw_free() decreases the counter only when the state is not OPEN. At this moment, however, the state is still OPEN, so the counter is unbalanced.
+} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
Put a blank line (also in many places in this file).
Takashi
On Aug 25 2015 06:12, Takashi Iwai wrote:
On Sat, 22 Aug 2015 11:19:38 +0200, Takashi Sakamoto wrote:
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 | 287 +++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 2 + 4 files changed, 294 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 0a1c387..f075b9b 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
obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.otascam-pcm.o tascam.o
diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c new file mode 100644 index 0000000..b1aa980 --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,287 @@ +/*
- 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;
- struct amdtp_stream *stream;
- unsigned int pcm_channels;
- runtime->hw = hardware;
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
stream = &tscm->tx_stream;
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;
stream = &tscm->rx_stream;
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);
- return amdtp_tscm_add_pcm_hw_constraints(stream, runtime);
+}
+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)) {
Must be '||'.
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);
- }
This looks dangerous. For example...
- 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));
... if the buffer allocation fails, this return an error. And snd_pcm_hw_params() calls hw_free() at the error path. But hw_free() decreases the counter only when the state is not OPEN. At this moment, however, the state is still OPEN, so the counter is unbalanced.
Indeed. I missed this case. I think moving the increment of substream counter aftre keeping buffer.
And Fireworks/BeBoB/OXFW/Dice drivers have the same issue.
+} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
Put a blank line (also in many places in this file).
Thanks
Takashi Sakamoto
TASCAM FireWire series use asynchronous transaction for transmittion of MIDI messages.
The messages in the transferred transaction include quirks: * Several MIDI messages are transferred in one block transaction, up to 8. * Two quadlets are used for one MIDI message and one timestamp. * A quadlet includes bytes up to 3, the first byte is used to indicate MIDI port and MSB 4 bit of MIDI status.
The messages in the received transaction includes quirks: * One MIDI message is transferred in one quadlet transaction. * MIDI running status is not allowed, thus transactions always include status byte. * The protocol for MIDI exclusive message is not clear. Therefore, this commit drop the message.
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 | 291 +++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 7 + sound/firewire/tascam/tascam.h | 25 +++ 4 files changed, 324 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 f075b9b..2c3d101 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..084d7d9 --- /dev/null +++ b/sound/firewire/tascam/tascam-transaction.c @@ -0,0 +1,291 @@ +/* + * 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" + +/* + * When return minus value, given argument is not MIDI status. + * When return 0, given argument is a beginning of system exclusive. + * When return the others, given argument is MIDI data. + */ +static inline int calculate_message_bytes(u8 status) +{ + switch (status) { + case 0xf6: /* Tune request. */ + case 0xf8: /* Timing clock. */ + case 0xfa: /* Start. */ + case 0xfb: /* Continue. */ + case 0xfc: /* Stop. */ + case 0xfe: /* Active sensing. */ + case 0xff: /* System reset. */ + return 1; + case 0xf1: /* MIDI time code quarter frame. */ + case 0xf3: /* Song select. */ + return 2; + case 0xf2: /* Song position pointer. */ + return 3; + case 0xf0: /* Exclusive. */ + return 0; + case 0xf7: /* End of exclusive. */ + break; + case 0xf4: /* Undefined. */ + case 0xf5: /* Undefined. */ + case 0xf9: /* Undefined. */ + case 0xfd: /* Undefined. */ + break; + default: + switch (status & 0xf0) { + case 0x80: /* Note on. */ + case 0x90: /* Note off. */ + case 0xa0: /* Polyphonic key pressure. */ + case 0xb0: /* Control change and Mode change. */ + case 0xe0: /* Pitch bend change. */ + return 3; + case 0xc0: /* Program change. */ + case 0xd0: /* Channel pressure. */ + return 2; + default: + break; + } + break; + } + + return -EINVAL; +} + +static int packetize_message(struct snd_rawmidi_substream *substream, __u8 *buf) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + unsigned int port = substream->number; + unsigned int len; + unsigned int i; + u8 status; + int consume; + + buf[0] = buf[1] = buf[2] = buf[3] = 0x00; + + len = snd_rawmidi_transmit_peek(substream, buf + 1, 3); + if (len == 0) + return 0; + + /* On exclusive message. */ + if (tscm->on_sysex[port] > 0) { + /* Seek the end of exclusives. */ + for (i = 1; i < 4; ++i) { + if (buf[i] == 0xf7) { + tscm->on_sysex[port] = 0; + break; + } + } + if (i == 4) + i = 3; + /* Currently, the exclusives are not supported. */ + snd_rawmidi_transmit_ack(substream, i); + return 0; + } else { + /* The beginning of exclusives. */ + if (buf[1] == 0xf0) { + /* Currently, the exclusives are not supported. */ + snd_rawmidi_transmit_ack(substream, 1); + tscm->on_sysex[port] = 1; + return 0; + } else { + /* On running-status. */ + if ((buf[1] & 0x80) != 0x80) + status = tscm->running_status[port]; + else + status = buf[1]; + + /* Calculate consume bytes. */ + consume = calculate_message_bytes(status); + if (consume <= 0) + return 0; + + /* On running-status. */ + if ((buf[1] & 0x80) != 0x80) { + buf[3] = buf[2]; + buf[2] = buf[1]; + buf[1] = tscm->running_status[port]; + consume--; + } else { + tscm->running_status[port] = buf[1]; + } + + /* Confirm length. */ + if (len < consume) + return 0; + if (len > consume) + len = consume; + } + } + + buf[0] = (port << 4) | (buf[1] >> 4); + + return len; +} + +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; + int bytes; + + if (offset != tscm->async_handler.offset) + goto end; + + messages = length / 8; + for (i = 0; i < messages; i++) { + b = (__u8 *)(buf + i * 2); + + 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; + } + + /* Assume the message length. */ + bytes = calculate_message_bytes(b[1]); + /* On MIDI data or exclusives. */ + if (bytes <= 0) { + /* Seek the end of exclusives. */ + for (bytes = 1; bytes < 4; bytes++) { + if (b[bytes] == 0xf7) + break; + } + if (bytes == 4) + bytes = 3; + } + + 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 18a7e70..5e770e5 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 ab922d8..920bf30 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -20,6 +20,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" @@ -37,6 +38,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; @@ -50,6 +54,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]; + + /* 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]; + u8 on_sysex[TSCM_MIDI_OUT_PORT_MAX]; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -72,6 +85,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, @@ -95,6 +116,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 | 162 ++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 3 + 4 files changed, 171 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 2c3d101..905fdc1 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..f0344b3 --- /dev/null +++ b/sound/firewire/tascam/tascam-midi.c @@ -0,0 +1,162 @@ +/* + * 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) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + + /* Initialize internal status. */ + tscm->running_status[substream->number] = 0; + tscm->on_sysex[substream->number] = 0; + 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 5e770e5..99622e1 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 920bf30..0c0b753 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -46,6 +46,7 @@ struct snd_tscm { struct fw_unit *unit;
struct mutex mutex; + spinlock_t lock;
struct snd_tscm_spec *spec;
@@ -123,3 +124,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);
On Sat, 22 Aug 2015 11:19:40 +0200, Takashi Sakamoto wrote:
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 | 162 ++++++++++++++++++++++++++++++++++++ sound/firewire/tascam/tascam.c | 4 + sound/firewire/tascam/tascam.h | 3 + 4 files changed, 171 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 2c3d101..905fdc1 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..f0344b3 --- /dev/null +++ b/sound/firewire/tascam/tascam-midi.c @@ -0,0 +1,162 @@ +/*
- 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;
No error code?
- return 0;
+}
+static int midi_playback_open(struct snd_rawmidi_substream *substream) +{
- struct snd_tscm *tscm = substream->rmidi->private_data;
- /* Initialize internal status. */
- tscm->running_status[substream->number] = 0;
- tscm->on_sysex[substream->number] = 0;
- 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;
Ditto.
- 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;
Missing spin unlock.
- } else {
if (up)
tscm->tx_midi_substreams[substrm->number] = substrm;
else
tscm->tx_midi_substreams[substrm->number] = NULL;
- }
- spin_unlock_irqrestore(&tscm->lock, flags);
+}
(snip)
diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h index 920bf30..0c0b753 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -46,6 +46,7 @@ struct snd_tscm { struct fw_unit *unit;
struct mutex mutex;
- spinlock_t lock;
Where is it initialized?
Takashi
This commit adds hwdep interface so as the other IEEE 1394 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 | 12 ++ 9 files changed, 276 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 605cb91..fbf2d12 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 905fdc1..11c58a6 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-$(CONFIG_SND_FIREWIRE_TASCAM) += 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..f87d5fa --- /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, "Tascam", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Tascam"); + 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 b1aa980..bb2c0fa 100644 --- a/sound/firewire/tascam/tascam-pcm.c +++ b/sound/firewire/tascam/tascam-pcm.c @@ -78,9 +78,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) | @@ -88,18 +92,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 99622e1..4071391 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 0c0b753..67c5210 100644 --- a/sound/firewire/tascam/tascam.h +++ b/sound/firewire/tascam/tascam.h @@ -21,6 +21,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" @@ -64,6 +66,10 @@ struct snd_tscm { struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; u8 running_status[TSCM_MIDI_OUT_PORT_MAX]; u8 on_sysex[TSCM_MIDI_OUT_PORT_MAX]; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
#define TSCM_ADDR_BASE 0xffff00000000ull @@ -117,6 +123,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); @@ -126,3 +136,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);
On Sat, 22 Aug 2015 11:19:16 +0200, Takashi Sakamoto wrote:
This patchset for Linux 4.3 updates my previous one:
Sorry this seems too late for 4.3. If this got reviewed and acked by Clemens or other, I would take it. But otherwise it's too intrusive to take in the last day of the development cycle.
Takashi
[alsa-devel] [PATCH 00/25] ALSA: support AMDTP variants http://mailman.alsa-project.org/pipermail/alsa-devel/2015-August/096338.html
As the same as the previous, the second patch is too large to be blasted by mailing list server. I request server administrators to grant it.
Changes:
- Enable to select as kernel module or the others.
- Add a comment for memory barriers in firewire-lib.
- Add a comment about using jiffies in firewire-lib.
- Use jiffies_64 helpers instead of jiffies helpers in firewire-lib.
Rest of work:
- Using proper clocksource for MIDI throttle in asynchronous transaction helper.
- Handling control/status messages for TASCAM driver.
- Supporting MIDI exclusive message in outgoing packets for TASCAM driver.
- Researching a mechanism to de-synchronize sometimes for Digi00x family.
You can see this patchset as a part in this branch. https://github.com/takaswie/sound/tree/amdtp-variants
Takashi Sakamoto (25): ALSA: firewire-lib: rename 'amdtp' to 'amdtp-stream' for functional separation ALSA: firewire-lib: functional separation for packet transmission layer and data processing layer ALSA: firewire-lib: add helper functions for asynchronous MIDI port ALSA: firewire-lib: add a restriction for a transaction at once 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 skeleton for Digi 002/003 family ALSA: firewire-digi00x: add data processing 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 data processing 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
include/uapi/sound/asound.h | 4 +- include/uapi/sound/firewire.h | 9 + sound/firewire/Kconfig | 27 ++ sound/firewire/Makefile | 4 +- sound/firewire/amdtp-am824.c | 451 ++++++++++++++++++++++++ sound/firewire/amdtp-am824.h | 47 +++ sound/firewire/{amdtp.c => amdtp-stream.c} | 375 ++++---------------- sound/firewire/{amdtp.h => amdtp-stream.h} | 113 ++---- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_midi.c | 16 +- sound/firewire/bebob/bebob_pcm.c | 12 +- sound/firewire/bebob/bebob_stream.c | 34 +- sound/firewire/dice/dice-midi.c | 16 +- sound/firewire/dice/dice-pcm.c | 12 +- sound/firewire/dice/dice-stream.c | 41 ++- sound/firewire/dice/dice.h | 2 +- sound/firewire/digi00x/Makefile | 4 + sound/firewire/digi00x/amdtp-dot.c | 436 +++++++++++++++++++++++ sound/firewire/digi00x/digi00x-hwdep.c | 200 +++++++++++ sound/firewire/digi00x/digi00x-midi.c | 198 +++++++++++ sound/firewire/digi00x/digi00x-pcm.c | 353 +++++++++++++++++++ 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 | 157 +++++++++ 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 | 10 +- sound/firewire/fireworks/fireworks_stream.c | 8 +- sound/firewire/lib.c | 147 ++++++++ sound/firewire/lib.h | 52 +++ sound/firewire/oxfw/oxfw-midi.c | 16 +- sound/firewire/oxfw/oxfw-pcm.c | 10 +- sound/firewire/oxfw/oxfw-stream.c | 11 +- sound/firewire/oxfw/oxfw.h | 2 +- sound/firewire/tascam/Makefile | 4 + sound/firewire/tascam/amdtp-tascam.c | 248 +++++++++++++ sound/firewire/tascam/tascam-hwdep.c | 202 +++++++++++ sound/firewire/tascam/tascam-midi.c | 162 +++++++++ sound/firewire/tascam/tascam-pcm.c | 298 ++++++++++++++++ sound/firewire/tascam/tascam-proc.c | 88 +++++ sound/firewire/tascam/tascam-stream.c | 497 +++++++++++++++++++++++++++ sound/firewire/tascam/tascam-transaction.c | 291 ++++++++++++++++ sound/firewire/tascam/tascam.c | 230 +++++++++++++ sound/firewire/tascam/tascam.h | 140 ++++++++ 48 files changed, 5295 insertions(+), 498 deletions(-) create mode 100644 sound/firewire/amdtp-am824.c create mode 100644 sound/firewire/amdtp-am824.h rename sound/firewire/{amdtp.c => amdtp-stream.c} (70%) rename sound/firewire/{amdtp.h => amdtp-stream.h} (77%) 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/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
-- 2.1.4
On 2015年08月23日 17:04, Takashi Iwai wrote:
On Sat, 22 Aug 2015 11:19:16 +0200, Takashi Sakamoto wrote:
This patchset for Linux 4.3 updates my previous one:
Sorry this seems too late for 4.3. If this got reviewed and acked by Clemens or other, I would take it. But otherwise it's too intrusive to take in the last day of the development cycle.
Takashi
OK.
Regards
Takashi Sakamoto
[alsa-devel] [PATCH 00/25] ALSA: support AMDTP variants http://mailman.alsa-project.org/pipermail/alsa-devel/2015-August/096338.html
As the same as the previous, the second patch is too large to be blasted by mailing list server. I request server administrators to grant it.
Changes:
- Enable to select as kernel module or the others.
- Add a comment for memory barriers in firewire-lib.
- Add a comment about using jiffies in firewire-lib.
- Use jiffies_64 helpers instead of jiffies helpers in firewire-lib.
Rest of work:
- Using proper clocksource for MIDI throttle in asynchronous transaction helper.
- Handling control/status messages for TASCAM driver.
- Supporting MIDI exclusive message in outgoing packets for TASCAM driver.
- Researching a mechanism to de-synchronize sometimes for Digi00x family.
You can see this patchset as a part in this branch. https://github.com/takaswie/sound/tree/amdtp-variants
Takashi Sakamoto (25): ALSA: firewire-lib: rename 'amdtp' to 'amdtp-stream' for functional separation ALSA: firewire-lib: functional separation for packet transmission layer and data processing layer ALSA: firewire-lib: add helper functions for asynchronous MIDI port ALSA: firewire-lib: add a restriction for a transaction at once 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 skeleton for Digi 002/003 family ALSA: firewire-digi00x: add data processing 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 data processing 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
include/uapi/sound/asound.h | 4 +- include/uapi/sound/firewire.h | 9 + sound/firewire/Kconfig | 27 ++ sound/firewire/Makefile | 4 +- sound/firewire/amdtp-am824.c | 451 ++++++++++++++++++++++++ sound/firewire/amdtp-am824.h | 47 +++ sound/firewire/{amdtp.c => amdtp-stream.c} | 375 ++++---------------- sound/firewire/{amdtp.h => amdtp-stream.h} | 113 ++---- sound/firewire/bebob/bebob.h | 2 +- sound/firewire/bebob/bebob_midi.c | 16 +- sound/firewire/bebob/bebob_pcm.c | 12 +- sound/firewire/bebob/bebob_stream.c | 34 +- sound/firewire/dice/dice-midi.c | 16 +- sound/firewire/dice/dice-pcm.c | 12 +- sound/firewire/dice/dice-stream.c | 41 ++- sound/firewire/dice/dice.h | 2 +- sound/firewire/digi00x/Makefile | 4 + sound/firewire/digi00x/amdtp-dot.c | 436 +++++++++++++++++++++++ sound/firewire/digi00x/digi00x-hwdep.c | 200 +++++++++++ sound/firewire/digi00x/digi00x-midi.c | 198 +++++++++++ sound/firewire/digi00x/digi00x-pcm.c | 353 +++++++++++++++++++ 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 | 157 +++++++++ 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 | 10 +- sound/firewire/fireworks/fireworks_stream.c | 8 +- sound/firewire/lib.c | 147 ++++++++ sound/firewire/lib.h | 52 +++ sound/firewire/oxfw/oxfw-midi.c | 16 +- sound/firewire/oxfw/oxfw-pcm.c | 10 +- sound/firewire/oxfw/oxfw-stream.c | 11 +- sound/firewire/oxfw/oxfw.h | 2 +- sound/firewire/tascam/Makefile | 4 + sound/firewire/tascam/amdtp-tascam.c | 248 +++++++++++++ sound/firewire/tascam/tascam-hwdep.c | 202 +++++++++++ sound/firewire/tascam/tascam-midi.c | 162 +++++++++ sound/firewire/tascam/tascam-pcm.c | 298 ++++++++++++++++ sound/firewire/tascam/tascam-proc.c | 88 +++++ sound/firewire/tascam/tascam-stream.c | 497 +++++++++++++++++++++++++++ sound/firewire/tascam/tascam-transaction.c | 291 ++++++++++++++++ sound/firewire/tascam/tascam.c | 230 +++++++++++++ sound/firewire/tascam/tascam.h | 140 ++++++++ 48 files changed, 5295 insertions(+), 498 deletions(-) create mode 100644 sound/firewire/amdtp-am824.c create mode 100644 sound/firewire/amdtp-am824.h rename sound/firewire/{amdtp.c => amdtp-stream.c} (70%) rename sound/firewire/{amdtp.h => amdtp-stream.h} (77%) 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/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
-- 2.1.4
On Sun, 23 Aug 2015 16:58:44 +0200, Takashi Sakamoto wrote:
On 2015年08月23日 17:04, Takashi Iwai wrote:
On Sat, 22 Aug 2015 11:19:16 +0200, Takashi Sakamoto wrote:
This patchset for Linux 4.3 updates my previous one:
Sorry this seems too late for 4.3. If this got reviewed and acked by Clemens or other, I would take it. But otherwise it's too intrusive to take in the last day of the development cycle.
Takashi
OK.
Now 4.2-rc8 was released unexpectedly, so we have a chance to review for one more week. Let's see.
Takashi
On Mon, 24 Aug 2015 08:26:56 +0200, Takashi Iwai wrote:
On Sun, 23 Aug 2015 16:58:44 +0200, Takashi Sakamoto wrote:
On 2015年08月23日 17:04, Takashi Iwai wrote:
On Sat, 22 Aug 2015 11:19:16 +0200, Takashi Sakamoto wrote:
This patchset for Linux 4.3 updates my previous one:
Sorry this seems too late for 4.3. If this got reviewed and acked by Clemens or other, I would take it. But otherwise it's too intrusive to take in the last day of the development cycle.
Takashi
OK.
Now 4.2-rc8 was released unexpectedly, so we have a chance to review for one more week. Let's see.
So I started a quick review from the bottom, and as already posted, there are obvious errors, and I stopped reading at that point.
Here are a few suggestions:
- In general, the series is way too big and contains a wide variety of changes. Better to split to smaller patch series. This makes much easier to review, and more importantly to merge. Otherwise a failure in one patch would block the whole series.
- __u32 or such is basically for API exposed to user space. In kernel, better to use u32 instead.
- When you write a new code, consider using EXPORT_SYMBOL_GPL() instead of EXPORT_SYMBOL().
- Re-read each patch once before submission as if you were a reviewer; this often helps to catch the typos or simple mistakes.
Also, I see quite a few code duplications in the series. For example, PCM read/write/silence loop is almost identical except for a few lines of differences inside the loop. Ditto for MIDI accessors. Can they be shared somehow better?
thanks,
Takashi
Hi,
On Aug 25 2015 14:24, Takashi Iwai wrote:
On Mon, 24 Aug 2015 08:26:56 +0200, Takashi Iwai wrote:
On Sun, 23 Aug 2015 16:58:44 +0200, Takashi Sakamoto wrote:
On 2015年08月23日 17:04, Takashi Iwai wrote:
On Sat, 22 Aug 2015 11:19:16 +0200, Takashi Sakamoto wrote:
This patchset for Linux 4.3 updates my previous one:
Sorry this seems too late for 4.3. If this got reviewed and acked by Clemens or other, I would take it. But otherwise it's too intrusive to take in the last day of the development cycle.
Takashi
OK.
Now 4.2-rc8 was released unexpectedly, so we have a chance to review for one more week. Let's see.
So I started a quick review from the bottom, and as already posted, there are obvious errors, and I stopped reading at that point.
Here are a few suggestions:
- In general, the series is way too big and contains a wide variety of changes. Better to split to smaller patch series. This makes much easier to review, and more importantly to merge. Otherwise a failure in one patch would block the whole series.
I agree with it, of course. If possible, I'd like to post small changes because a large patchset really exhausts me.
But this patchset is for new device drivers, sigh. The changes are just for devices supported by this patchset, and have no advantages to the other devices already supported by the other drivers. What is even worse, it can bring disadvantages or overhead to current stack. Therefore, it's meaning less to apply a part of the patchset.
__u32 or such is basically for API exposed to user space. In kernel, better to use u32 instead.
When you write a new code, consider using EXPORT_SYMBOL_GPL() instead of EXPORT_SYMBOL().
Re-read each patch once before submission as if you were a reviewer; this often helps to catch the typos or simple mistakes.
I always have several nights for self-reviewing, nevertheless patchset includes such a trivial mistakes... (This is one of reasons that I don't like to post such a large patchset.)
For all that, the last part of this patchset is a bot worse. I concide it, sorry. MIDI functionality of TASCAM FireWire series is enough complicated to me and I always have a headache at considering about is (then make some easy mistakes).
Also, I see quite a few code duplications in the series. For example, PCM read/write/silence loop is almost identical except for a few lines of differences inside the loop. Ditto for MIDI accessors. Can they be shared somehow better?
I think you mention about the codes in data block processing, such as read_pcm_s32() in sound/firewire/amdtp-am824.c (originally in amdtp.c). These functions are called as frequently as 8,000 times per second, thus it's preferrable to keep the size as small as possible for CPU usage.
While, each data block processing includes quite similar codes. In this meaning, your idea is valid, indeed. But currently, I adhere to the way to implement different data block processing as what the shape is. There's no public specification about it and I prefer to keep the codes as easy to read as possible.
Thanks
Takashi Sakamoto
On Tue, 25 Aug 2015 13:47:23 +0200, Takashi Sakamoto wrote:
Hi,
On Aug 25 2015 14:24, Takashi Iwai wrote:
On Mon, 24 Aug 2015 08:26:56 +0200, Takashi Iwai wrote:
On Sun, 23 Aug 2015 16:58:44 +0200, Takashi Sakamoto wrote:
On 2015年08月23日 17:04, Takashi Iwai wrote:
On Sat, 22 Aug 2015 11:19:16 +0200, Takashi Sakamoto wrote:
This patchset for Linux 4.3 updates my previous one:
Sorry this seems too late for 4.3. If this got reviewed and acked by Clemens or other, I would take it. But otherwise it's too intrusive to take in the last day of the development cycle.
Takashi
OK.
Now 4.2-rc8 was released unexpectedly, so we have a chance to review for one more week. Let's see.
So I started a quick review from the bottom, and as already posted, there are obvious errors, and I stopped reading at that point.
Here are a few suggestions:
- In general, the series is way too big and contains a wide variety of changes. Better to split to smaller patch series. This makes much easier to review, and more importantly to merge. Otherwise a failure in one patch would block the whole series.
I agree with it, of course. If possible, I'd like to post small changes because a large patchset really exhausts me.
But this patchset is for new device drivers, sigh. The changes are just for devices supported by this patchset, and have no advantages to the other devices already supported by the other drivers. What is even worse, it can bring disadvantages or overhead to current stack. Therefore, it's meaning less to apply a part of the patchset.
Well, if the patch makes the existing things significant worse from the performance POV, it's anyway not acceptable. So, it's invalid as a counter argument to split patchsets.
If the whole patchset is designed to implement one thing, then it's unavoidable to have them as a single patchset. But your patchset looks more than that -- it starts from a rename patch, restructuring patches, then patches for digi00x and tascam, at least. (Also there are PCM support, MIDI support, etc, which can be separated too.)
__u32 or such is basically for API exposed to user space. In kernel, better to use u32 instead.
When you write a new code, consider using EXPORT_SYMBOL_GPL() instead of EXPORT_SYMBOL().
Re-read each patch once before submission as if you were a reviewer; this often helps to catch the typos or simple mistakes.
I always have several nights for self-reviewing, nevertheless patchset includes such a trivial mistakes... (This is one of reasons that I don't like to post such a large patchset.)
The same applied to review -- errors can be overlooked more in a larger patchset.
For all that, the last part of this patchset is a bot worse. I concide it, sorry. MIDI functionality of TASCAM FireWire series is enough complicated to me and I always have a headache at considering about is (then make some easy mistakes).
Also, I see quite a few code duplications in the series. For example, PCM read/write/silence loop is almost identical except for a few lines of differences inside the loop. Ditto for MIDI accessors. Can they be shared somehow better?
I think you mention about the codes in data block processing, such as read_pcm_s32() in sound/firewire/amdtp-am824.c (originally in amdtp.c). These functions are called as frequently as 8,000 times per second, thus it's preferrable to keep the size as small as possible for CPU usage.
While, each data block processing includes quite similar codes. In this meaning, your idea is valid, indeed. But currently, I adhere to the way to implement different data block processing as what the shape is. There's no public specification about it and I prefer to keep the codes as easy to read as possible.
Well, if you provide simple ops like write_s32(), read_s32(), silence_s32(), etc, all codes can be same. The inner loop calls one of this ops, which returns a BE32 value or convert to a native value, etc. The performance cost by such a function call isn't too tragic.
thanks,
Takashi
On 2015å¹´08æ25æ¥ 21:03, Takashi Iwai wrote:
For all that, the last part of this patchset is a bot worse. I concide it, sorry. MIDI functionality of TASCAM FireWire series is enough complicated to me and I always have a headache at considering about is (then make some easy mistakes).
Also, I see quite a few code duplications in the series. For example, PCM read/write/silence loop is almost identical except for a few lines of differences inside the loop. Ditto for MIDI accessors. Can they be shared somehow better?
I think you mention about the codes in data block processing, such as read_pcm_s32() in sound/firewire/amdtp-am824.c (originally in amdtp.c). These functions are called as frequently as 8,000 times per second, thus it's preferrable to keep the size as small as possible for CPU usage.
While, each data block processing includes quite similar codes. In this meaning, your idea is valid, indeed. But currently, I adhere to the way to implement different data block processing as what the shape is. There's no public specification about it and I prefer to keep the codes as easy to read as possible.
Well, if you provide simple ops like write_s32(), read_s32(), silence_s32(), etc, all codes can be same. The inner loop calls one of this ops, which returns a BE32 value or convert to a native value, etc. The performance cost by such a function call isn't too tragic.
I don't like to discuss without actual patches, for this kind of issues.
Regards
Takashi Sakamoto
participants (2)
-
Takashi Iwai
-
Takashi Sakamoto