summaryrefslogtreecommitdiff
path: root/sound/firewire
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2016-01-20 14:01:31 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2016-01-20 14:01:31 -0300
commitb4b7ff4b08e691656c9d77c758fc355833128ac0 (patch)
tree82fcb00e6b918026dc9f2d1f05ed8eee83874cc0 /sound/firewire
parent35acfa0fc609f2a2cd95cef4a6a9c3a5c38f1778 (diff)
Linux-libre 4.4-gnupck-4.4-gnu
Diffstat (limited to 'sound/firewire')
-rw-r--r--sound/firewire/Kconfig28
-rw-r--r--sound/firewire/Makefile5
-rw-r--r--sound/firewire/amdtp-am824.c465
-rw-r--r--sound/firewire/amdtp-am824.h52
-rw-r--r--sound/firewire/amdtp-stream.c (renamed from sound/firewire/amdtp.c)379
-rw-r--r--sound/firewire/amdtp-stream.h (renamed from sound/firewire/amdtp.h)116
-rw-r--r--sound/firewire/bebob/Makefile2
-rw-r--r--sound/firewire/bebob/bebob.c9
-rw-r--r--sound/firewire/bebob/bebob.h34
-rw-r--r--sound/firewire/bebob/bebob_focusrite.c26
-rw-r--r--sound/firewire/bebob/bebob_maudio.c34
-rw-r--r--sound/firewire/bebob/bebob_midi.c16
-rw-r--r--sound/firewire/bebob/bebob_pcm.c16
-rw-r--r--sound/firewire/bebob/bebob_proc.c6
-rw-r--r--sound/firewire/bebob/bebob_stream.c40
-rw-r--r--sound/firewire/bebob/bebob_terratec.c10
-rw-r--r--sound/firewire/bebob/bebob_yamaha.c6
-rw-r--r--sound/firewire/dice/Makefile2
-rw-r--r--sound/firewire/dice/dice-midi.c12
-rw-r--r--sound/firewire/dice/dice-pcm.c12
-rw-r--r--sound/firewire/dice/dice-stream.c34
-rw-r--r--sound/firewire/dice/dice.c7
-rw-r--r--sound/firewire/dice/dice.h2
-rw-r--r--sound/firewire/digi00x/Makefile4
-rw-r--r--sound/firewire/digi00x/amdtp-dot.c442
-rw-r--r--sound/firewire/digi00x/digi00x-hwdep.c200
-rw-r--r--sound/firewire/digi00x/digi00x-midi.c223
-rw-r--r--sound/firewire/digi00x/digi00x-pcm.c373
-rw-r--r--sound/firewire/digi00x/digi00x-proc.c99
-rw-r--r--sound/firewire/digi00x/digi00x-stream.c422
-rw-r--r--sound/firewire/digi00x/digi00x-transaction.c137
-rw-r--r--sound/firewire/digi00x/digi00x.c170
-rw-r--r--sound/firewire/digi00x/digi00x.h157
-rw-r--r--sound/firewire/fcp.c2
-rw-r--r--sound/firewire/fireworks/Makefile2
-rw-r--r--sound/firewire/fireworks/fireworks.c12
-rw-r--r--sound/firewire/fireworks/fireworks.h2
-rw-r--r--sound/firewire/fireworks/fireworks_command.c2
-rw-r--r--sound/firewire/fireworks/fireworks_midi.c12
-rw-r--r--sound/firewire/fireworks/fireworks_pcm.c12
-rw-r--r--sound/firewire/fireworks/fireworks_stream.c8
-rw-r--r--sound/firewire/lib.c142
-rw-r--r--sound/firewire/lib.h56
-rw-r--r--sound/firewire/oxfw/Makefile2
-rw-r--r--sound/firewire/oxfw/oxfw-midi.c40
-rw-r--r--sound/firewire/oxfw/oxfw-pcm.c10
-rw-r--r--sound/firewire/oxfw/oxfw-stream.c53
-rw-r--r--sound/firewire/oxfw/oxfw.c46
-rw-r--r--sound/firewire/oxfw/oxfw.h3
-rw-r--r--sound/firewire/tascam/Makefile4
-rw-r--r--sound/firewire/tascam/amdtp-tascam.c243
-rw-r--r--sound/firewire/tascam/tascam-hwdep.c201
-rw-r--r--sound/firewire/tascam/tascam-midi.c135
-rw-r--r--sound/firewire/tascam/tascam-pcm.c312
-rw-r--r--sound/firewire/tascam/tascam-proc.c88
-rw-r--r--sound/firewire/tascam/tascam-stream.c496
-rw-r--r--sound/firewire/tascam/tascam-transaction.c302
-rw-r--r--sound/firewire/tascam/tascam.c209
-rw-r--r--sound/firewire/tascam/tascam.h147
59 files changed, 5496 insertions, 585 deletions
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 8850b7de1..e92a6d949 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -38,6 +38,7 @@ config SND_OXFW
* Mackie(Loud) Tapco Link.Firewire
* Mackie(Loud) d.2 pro/d.4 pro
* Mackie(Loud) U.420/U.420d
+ * TASCAM FireOne
To compile this driver as a module, choose M here: the module
will be called snd-oxfw.
@@ -120,4 +121,31 @@ 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 Digi 002/003 family support"
+ select SND_FIREWIRE_LIB
+ select SND_HWDEP
+ help
+ Say Y here to include support for Digidesign Digi 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.
+
+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
+ * 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 8b37f084b..f5fb62551 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -1,6 +1,5 @@
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
- fcp.o cmp.o amdtp.o
-snd-oxfw-objs := oxfw.o
+ fcp.o cmp.o amdtp-stream.o amdtp-am824.o
snd-isight-objs := isight.o
snd-scs1x-objs := scs1x.o
@@ -11,3 +10,5 @@ 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/
+obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/
diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c
new file mode 100644
index 000000000..bebddc60f
--- /dev/null
+++ b/sound/firewire/amdtp-am824.c
@@ -0,0 +1,465 @@
+/*
+ * 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 "amdtp-am824.h"
+
+#define CIP_FMT_AM 0x10
+
+/* "Clock-based rate control mode" is just supported. */
+#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_substream *pcm,
+ __be32 *buffer, unsigned int frames);
+
+ 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)
+ * @double_pcm_frames: one data block transfers two PCM frames
+ *
+ * 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 -EINVAL;
+
+ 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;
+
+ if (WARN_ON(amdtp_stream_running(s)) ||
+ WARN_ON(pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) ||
+ WARN_ON(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;
+
+ /*
+ * In IEC 61883-6, one data block represents one event. In ALSA, one
+ * event equals to one PCM frame. But Dice has a quirk at higher
+ * sampling rate to transfer two PCM frames in one data block.
+ */
+ if (double_pcm_frames)
+ p->frame_multiplier = 2;
+ else
+ p->frame_multiplier = 1;
+
+ /* init the position map for PCM and MIDI channels */
+ for (i = 0; i < pcm_channels; i++)
+ p->pcm_positions[i] = i;
+ p->midi_position = p->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_GPL(amdtp_am824_set_parameters);
+
+/**
+ * amdtp_am824_set_pcm_position - set an index of data channel for a channel
+ * of PCM frame
+ * @s: the AMDTP stream
+ * @index: the index of data channel in an data block
+ * @position: the channel of PCM frame
+ */
+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_GPL(amdtp_am824_set_pcm_position);
+
+/**
+ * amdtp_am824_set_midi_position - set a index of data channel for MIDI
+ * conformant data channel
+ * @s: the AMDTP stream
+ * @position: the index of data channel in an data block
+ */
+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_GPL(amdtp_am824_set_midi_position);
+
+static void write_pcm_s32(struct amdtp_stream *s,
+ struct snd_pcm_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_am824 *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_am824 *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_am824 *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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 frames)
+{
+ struct amdtp_am824 *p = s->protocol;
+ unsigned int i, c, channels = p->pcm_channels;
+
+ for (i = 0; i < frames; ++i) {
+ for (c = 0; c < channels; ++c)
+ buffer[p->pcm_positions[c]] = cpu_to_be32(0x40000000);
+ buffer += s->data_block_quadlets;
+ }
+}
+
+/**
+ * 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_GPL(amdtp_am824_set_pcm_format);
+
+/**
+ * amdtp_am824_add_pcm_hw_constraints - add hw constraints for PCM substream
+ * @s: the AMDTP stream for AM824 data block, must be initialized.
+ * @runtime: the PCM substream runtime
+ *
+ */
+int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s,
+ struct snd_pcm_runtime *runtime)
+{
+ int err;
+
+ err = amdtp_stream_add_pcm_hw_constraints(s, runtime);
+ if (err < 0)
+ return err;
+
+ /* AM824 in IEC 61883-6 can deliver 24bit data. */
+ return snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+}
+EXPORT_SYMBOL_GPL(amdtp_am824_add_pcm_hw_constraints);
+
+/**
+ * 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_GPL(amdtp_am824_midi_trigger);
+
+/*
+ * 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;
+ }
+}
+
+static unsigned int process_rx_data_blocks(struct amdtp_stream *s, __be32 *buffer,
+ unsigned int data_blocks, unsigned int *syt)
+{
+ struct amdtp_am824 *p = s->protocol;
+ struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
+ unsigned int pcm_frames;
+
+ if (pcm) {
+ p->transfer_samples(s, pcm, buffer, data_blocks);
+ pcm_frames = data_blocks * p->frame_multiplier;
+ } 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;
+}
+
+static unsigned int process_tx_data_blocks(struct amdtp_stream *s, __be32 *buffer,
+ unsigned int data_blocks, unsigned int *syt)
+{
+ struct amdtp_am824 *p = s->protocol;
+ struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
+ unsigned int pcm_frames;
+
+ if (pcm) {
+ p->transfer_samples(s, pcm, buffer, data_blocks);
+ pcm_frames = data_blocks * p->frame_multiplier;
+ } else {
+ pcm_frames = 0;
+ }
+
+ if (p->midi_ports)
+ read_midi_messages(s, buffer, data_blocks);
+
+ return pcm_frames;
+}
+
+/**
+ * amdtp_am824_init - initialize an AMDTP stream structure to handle AM824
+ * data block
+ * @s: the AMDTP stream to initialize
+ * @unit: the target of the stream
+ * @dir: the direction of stream
+ * @flags: the packet transmission method to use
+ */
+int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit,
+ enum amdtp_stream_direction dir, enum cip_flags flags)
+{
+ amdtp_stream_process_data_blocks_t process_data_blocks;
+
+ if (dir == AMDTP_IN_STREAM)
+ process_data_blocks = process_tx_data_blocks;
+ else
+ process_data_blocks = process_rx_data_blocks;
+
+ return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM,
+ process_data_blocks,
+ sizeof(struct amdtp_am824));
+}
+EXPORT_SYMBOL_GPL(amdtp_am824_init);
diff --git a/sound/firewire/amdtp-am824.h b/sound/firewire/amdtp-am824.h
new file mode 100644
index 000000000..73b07b310
--- /dev/null
+++ b/sound/firewire/amdtp-am824.h
@@ -0,0 +1,52 @@
+#ifndef SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED
+#define SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED
+
+#include <sound/pcm.h>
+#include <sound/rawmidi.h>
+
+#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
+
+/*
+ * 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 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.c b/sound/firewire/amdtp-stream.c
index 2a153d260..ed2902609 100644
--- a/sound/firewire/amdtp.c
+++ b/sound/firewire/amdtp-stream.c
@@ -11,28 +11,14 @@
#include <linux/firewire.h>
#include <linux/module.h>
#include <linux/slab.h>
-#include <linux/sched.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
-#include <sound/rawmidi.h>
-#include "amdtp.h"
+#include "amdtp-stream.h"
#define TICKS_PER_CYCLE 3072
#define CYCLES_PER_SECOND 8000
#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
-/*
- * Nominally 3125 bytes/second, but the MIDI port's clock might be
- * 1% too slow, and the bus clock 100 ppm too fast.
- */
-#define MIDI_BYTES_PER_SECOND 3093
-
-/*
- * Several devices look only at the first eight data blocks.
- * In any case, this is more than enough for the MIDI data rate.
- */
-#define MAX_MIDI_RX_BLOCKS 8
-
#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */
/* isochronous header parameters */
@@ -55,12 +41,8 @@
#define CIP_SYT_MASK 0x0000ffff
#define CIP_SYT_NO_INFO 0xffff
-/*
- * Audio and Music transfer protocol specific parameters
- * only "Clock-based rate control mode" is supported
- */
-#define CIP_FMT_AM (0x10 << CIP_FMT_SHIFT)
-#define AMDTP_FDF_AM824 (0 << (CIP_FDF_SHIFT + 3))
+/* Audio and Music transfer protocol specific parameters */
+#define CIP_FMT_AM 0x10
#define AMDTP_FDF_NO_DATA 0xff
/* TODO: make these configurable */
@@ -78,10 +60,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
+ * @fmt: the value of fmt field in CIP header
+ * @process_data_blocks: callback handler to process data blocks
+ * @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 fmt,
+ amdtp_stream_process_data_blocks_t process_data_blocks,
+ unsigned int protocol_size)
{
+ if (process_data_blocks == NULL)
+ return -EINVAL;
+
+ s->protocol = kzalloc(protocol_size, GFP_KERNEL);
+ if (!s->protocol)
+ return -ENOMEM;
+
s->unit = unit;
s->direction = dir;
s->flags = flags;
@@ -94,6 +89,9 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
s->callbacked = false;
s->sync_slave = NULL;
+ s->fmt = fmt;
+ s->process_data_blocks = process_data_blocks;
+
return 0;
}
EXPORT_SYMBOL(amdtp_stream_init);
@@ -105,6 +103,7 @@ EXPORT_SYMBOL(amdtp_stream_init);
void amdtp_stream_destroy(struct amdtp_stream *s)
{
WARN_ON(amdtp_stream_running(s));
+ kfree(s->protocol);
mutex_destroy(&s->mutex);
}
EXPORT_SYMBOL(amdtp_stream_destroy);
@@ -141,11 +140,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,39 +184,25 @@ 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 size of a data block in quadlet unit
*
* 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->sfc = sfc;
- s->data_block_quadlets = s->pcm_channels + midi_channels;
- s->midi_ports = midi_ports;
-
+ s->data_block_quadlets = data_block_quadlets;
s->syt_interval = amdtp_syt_intervals[sfc];
/* default buffering in the device */
@@ -231,18 +211,7 @@ sfc_found:
/* 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 +233,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 +335,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,23 +409,19 @@ 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;
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);
+ 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));
s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
@@ -680,8 +429,9 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
if (queue_out_packet(s, payload_length, false) < 0)
return -EIO;
- if (pcm)
- update_pcm_pointers(s, pcm, data_blocks);
+ 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;
@@ -689,11 +439,13 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int data_blocks,
static int handle_in_packet(struct amdtp_stream *s,
unsigned int payload_quadlets, __be32 *buffer,
- unsigned int *data_blocks)
+ unsigned int *data_blocks, unsigned int syt)
{
u32 cip_header[2];
+ unsigned int fmt, fdf;
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]);
@@ -704,19 +456,30 @@ static int handle_in_packet(struct amdtp_stream *s,
* For convenience, also check FMT field is AM824 or not.
*/
if (((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) ||
- ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH) ||
- ((cip_header[1] & CIP_FMT_MASK) != CIP_FMT_AM)) {
+ ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) {
dev_info_ratelimited(&s->unit->device,
"Invalid CIP header for AMDTP: %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_info_ratelimited(&s->unit->device,
+ "Detect unexpected protocol: %08x %08x\n",
+ cip_header[0], cip_header[1]);
+ *data_blocks = 0;
+ pcm_frames = 0;
goto end;
}
/* 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 +526,7 @@ 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);
- }
+ 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 +537,9 @@ end:
if (queue_in_packet(s) < 0)
return -EIO;
- if (pcm)
- update_pcm_pointers(s, pcm, *data_blocks);
+ pcm = ACCESS_ONCE(s->pcm);
+ if (pcm && pcm_frames > 0)
+ update_pcm_pointers(s, pcm, pcm_frames);
return 0;
}
@@ -854,15 +609,15 @@ static void in_stream_callback(struct fw_iso_context *context, u32 cycle,
break;
}
+ syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
if (handle_in_packet(s, payload_quadlets, buffer,
- &data_blocks) < 0) {
+ &data_blocks, syt) < 0) {
s->packet_index = -1;
break;
}
/* Process sync slave stream */
if (s->sync_slave && s->sync_slave->callbacked) {
- syt = be32_to_cpu(buffer[1]) & CIP_SYT_MASK;
if (handle_out_packet(s->sync_slave,
data_blocks, syt) < 0) {
s->packet_index = -1;
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp-stream.h
index b2cf9e756..8775704a3 100644
--- a/sound/firewire/amdtp.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,100 +81,78 @@ 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;
struct snd_pcm_runtime;
-struct snd_rawmidi_substream;
enum amdtp_stream_direction {
AMDTP_OUT_STREAM = 0,
AMDTP_IN_STREAM
};
+struct amdtp_stream;
+typedef unsigned int (*amdtp_stream_process_data_blocks_t)(
+ struct amdtp_stream *s,
+ __be32 *buffer,
+ unsigned int data_blocks,
+ unsigned int *syt);
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 a PCM substream 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;
+
+ /* For backends to process data blocks. */
+ void *protocol;
+ amdtp_stream_process_data_blocks_t process_data_blocks;
};
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 fmt,
+ amdtp_stream_process_data_blocks_t process_data_blocks,
+ 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 +161,7 @@ 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 +218,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/Makefile b/sound/firewire/bebob/Makefile
index 6cf470c80..af7ed6643 100644
--- a/sound/firewire/bebob/Makefile
+++ b/sound/firewire/bebob/Makefile
@@ -1,4 +1,4 @@
snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \
bebob_focusrite.o bebob_maudio.o bebob.o
-obj-m += snd-bebob.o
+obj-$(CONFIG_SND_BEBOB) += snd-bebob.o
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c
index 27a04ac8f..091290d1f 100644
--- a/sound/firewire/bebob/bebob.c
+++ b/sound/firewire/bebob/bebob.c
@@ -41,7 +41,8 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
#define VEN_EDIROL 0x000040ab
#define VEN_PRESONUS 0x00000a92
#define VEN_BRIDGECO 0x000007f5
-#define VEN_MACKIE 0x0000000f
+#define VEN_MACKIE1 0x0000000f
+#define VEN_MACKIE2 0x00000ff2
#define VEN_STANTON 0x00001260
#define VEN_TASCAM 0x0000022e
#define VEN_BEHRINGER 0x00001564
@@ -334,7 +335,7 @@ static void bebob_remove(struct fw_unit *unit)
snd_card_free_when_closed(bebob->card);
}
-static struct snd_bebob_rate_spec normal_rate_spec = {
+static const struct snd_bebob_rate_spec normal_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate
};
@@ -360,9 +361,9 @@ static const struct ieee1394_device_id bebob_id_table[] = {
/* BridgeCo, Audio5 */
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal),
/* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */
- SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE2, 0x00010065, &spec_normal),
/* Mackie, d.2 (Firewire Option) */
- SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE1, 0x00010067, &spec_normal),
/* Stanton, ScratchAmp */
SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
/* Tascam, IF-FW DM */
diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h
index d23caca7f..4d8fcc78e 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-am824.h"
#include "../cmp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */
@@ -70,9 +70,9 @@ struct snd_bebob_meter_spec {
int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
};
struct snd_bebob_spec {
- struct snd_bebob_clock_spec *clock;
- struct snd_bebob_rate_spec *rate;
- struct snd_bebob_meter_spec *meter;
+ const struct snd_bebob_clock_spec *clock;
+ const struct snd_bebob_rate_spec *rate;
+ const struct snd_bebob_meter_spec *meter;
};
struct snd_bebob {
@@ -235,19 +235,19 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
/* model specific operations */
-extern struct snd_bebob_spec phase88_rack_spec;
-extern struct snd_bebob_spec phase24_series_spec;
-extern struct snd_bebob_spec yamaha_go_spec;
-extern struct snd_bebob_spec saffirepro_26_spec;
-extern struct snd_bebob_spec saffirepro_10_spec;
-extern struct snd_bebob_spec saffire_le_spec;
-extern struct snd_bebob_spec saffire_spec;
-extern struct snd_bebob_spec maudio_fw410_spec;
-extern struct snd_bebob_spec maudio_audiophile_spec;
-extern struct snd_bebob_spec maudio_solo_spec;
-extern struct snd_bebob_spec maudio_ozonic_spec;
-extern struct snd_bebob_spec maudio_nrv10_spec;
-extern struct snd_bebob_spec maudio_special_spec;
+extern const struct snd_bebob_spec phase88_rack_spec;
+extern const struct snd_bebob_spec phase24_series_spec;
+extern const struct snd_bebob_spec yamaha_go_spec;
+extern const struct snd_bebob_spec saffirepro_26_spec;
+extern const struct snd_bebob_spec saffirepro_10_spec;
+extern const struct snd_bebob_spec saffire_le_spec;
+extern const struct snd_bebob_spec saffire_spec;
+extern const struct snd_bebob_spec maudio_fw410_spec;
+extern const struct snd_bebob_spec maudio_audiophile_spec;
+extern const struct snd_bebob_spec maudio_solo_spec;
+extern const struct snd_bebob_spec maudio_ozonic_spec;
+extern const struct snd_bebob_spec maudio_nrv10_spec;
+extern const struct snd_bebob_spec maudio_special_spec;
int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c
index a1a39494e..f11090057 100644
--- a/sound/firewire/bebob/bebob_focusrite.c
+++ b/sound/firewire/bebob/bebob_focusrite.c
@@ -200,7 +200,7 @@ end:
return err;
}
-struct snd_bebob_spec saffire_le_spec;
+const struct snd_bebob_spec saffire_le_spec;
static enum snd_bebob_clock_type saffire_both_clk_src_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL,
SND_BEBOB_CLOCK_TYPE_EXTERNAL,
@@ -229,7 +229,7 @@ static const char *const saffire_meter_labels[] = {
static int
saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
{
- struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
unsigned int channels;
u64 offset;
int err;
@@ -260,60 +260,60 @@ saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
return err;
}
-static struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
+static const struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
.get = &saffirepro_both_clk_freq_get,
.set = &saffirepro_both_clk_freq_set,
};
/* Saffire Pro 26 I/O */
-static struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
+static const struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
.num = ARRAY_SIZE(saffirepro_26_clk_src_types),
.types = saffirepro_26_clk_src_types,
.get = &saffirepro_both_clk_src_get,
};
-struct snd_bebob_spec saffirepro_26_spec = {
+const struct snd_bebob_spec saffirepro_26_spec = {
.clock = &saffirepro_26_clk_spec,
.rate = &saffirepro_both_rate_spec,
.meter = NULL
};
/* Saffire Pro 10 I/O */
-static struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
+static const struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
.num = ARRAY_SIZE(saffirepro_10_clk_src_types),
.types = saffirepro_10_clk_src_types,
.get = &saffirepro_both_clk_src_get,
};
-struct snd_bebob_spec saffirepro_10_spec = {
+const struct snd_bebob_spec saffirepro_10_spec = {
.clock = &saffirepro_10_clk_spec,
.rate = &saffirepro_both_rate_spec,
.meter = NULL
};
-static struct snd_bebob_rate_spec saffire_both_rate_spec = {
+static const struct snd_bebob_rate_spec saffire_both_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
-static struct snd_bebob_clock_spec saffire_both_clk_spec = {
+static const struct snd_bebob_clock_spec saffire_both_clk_spec = {
.num = ARRAY_SIZE(saffire_both_clk_src_types),
.types = saffire_both_clk_src_types,
.get = &saffire_both_clk_src_get,
};
/* Saffire LE */
-static struct snd_bebob_meter_spec saffire_le_meter_spec = {
+static const struct snd_bebob_meter_spec saffire_le_meter_spec = {
.num = ARRAY_SIZE(saffire_le_meter_labels),
.labels = saffire_le_meter_labels,
.get = &saffire_meter_get,
};
-struct snd_bebob_spec saffire_le_spec = {
+const struct snd_bebob_spec saffire_le_spec = {
.clock = &saffire_both_clk_spec,
.rate = &saffire_both_rate_spec,
.meter = &saffire_le_meter_spec
};
/* Saffire */
-static struct snd_bebob_meter_spec saffire_meter_spec = {
+static const struct snd_bebob_meter_spec saffire_meter_spec = {
.num = ARRAY_SIZE(saffire_meter_labels),
.labels = saffire_meter_labels,
.get = &saffire_meter_get,
};
-struct snd_bebob_spec saffire_spec = {
+const struct snd_bebob_spec saffire_spec = {
.clock = &saffire_both_clk_spec,
.rate = &saffire_both_rate_spec,
.meter = &saffire_meter_spec
diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c
index 057495d54..07e5abdbc 100644
--- a/sound/firewire/bebob/bebob_maudio.c
+++ b/sound/firewire/bebob/bebob_maudio.c
@@ -628,7 +628,7 @@ static const char *const special_meter_labels[] = {
static int
special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)
{
- u16 *buf;
+ __be16 *buf;
unsigned int i, c, channels;
int err;
@@ -687,7 +687,7 @@ static const char *const nrv10_meter_labels[] = {
static int
normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
{
- struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
unsigned int c, channels;
int err;
@@ -712,85 +712,85 @@ end:
}
/* for special customized devices */
-static struct snd_bebob_rate_spec special_rate_spec = {
+static const struct snd_bebob_rate_spec special_rate_spec = {
.get = &special_get_rate,
.set = &special_set_rate,
};
-static struct snd_bebob_clock_spec special_clk_spec = {
+static const struct snd_bebob_clock_spec special_clk_spec = {
.num = ARRAY_SIZE(special_clk_types),
.types = special_clk_types,
.get = &special_clk_get,
};
-static struct snd_bebob_meter_spec special_meter_spec = {
+static const struct snd_bebob_meter_spec special_meter_spec = {
.num = ARRAY_SIZE(special_meter_labels),
.labels = special_meter_labels,
.get = &special_meter_get
};
-struct snd_bebob_spec maudio_special_spec = {
+const struct snd_bebob_spec maudio_special_spec = {
.clock = &special_clk_spec,
.rate = &special_rate_spec,
.meter = &special_meter_spec
};
/* Firewire 410 specification */
-static struct snd_bebob_rate_spec usual_rate_spec = {
+static const struct snd_bebob_rate_spec usual_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
-static struct snd_bebob_meter_spec fw410_meter_spec = {
+static const struct snd_bebob_meter_spec fw410_meter_spec = {
.num = ARRAY_SIZE(fw410_meter_labels),
.labels = fw410_meter_labels,
.get = &normal_meter_get
};
-struct snd_bebob_spec maudio_fw410_spec = {
+const struct snd_bebob_spec maudio_fw410_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &fw410_meter_spec
};
/* Firewire Audiophile specification */
-static struct snd_bebob_meter_spec audiophile_meter_spec = {
+static const struct snd_bebob_meter_spec audiophile_meter_spec = {
.num = ARRAY_SIZE(audiophile_meter_labels),
.labels = audiophile_meter_labels,
.get = &normal_meter_get
};
-struct snd_bebob_spec maudio_audiophile_spec = {
+const struct snd_bebob_spec maudio_audiophile_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &audiophile_meter_spec
};
/* Firewire Solo specification */
-static struct snd_bebob_meter_spec solo_meter_spec = {
+static const struct snd_bebob_meter_spec solo_meter_spec = {
.num = ARRAY_SIZE(solo_meter_labels),
.labels = solo_meter_labels,
.get = &normal_meter_get
};
-struct snd_bebob_spec maudio_solo_spec = {
+const struct snd_bebob_spec maudio_solo_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &solo_meter_spec
};
/* Ozonic specification */
-static struct snd_bebob_meter_spec ozonic_meter_spec = {
+static const struct snd_bebob_meter_spec ozonic_meter_spec = {
.num = ARRAY_SIZE(ozonic_meter_labels),
.labels = ozonic_meter_labels,
.get = &normal_meter_get
};
-struct snd_bebob_spec maudio_ozonic_spec = {
+const struct snd_bebob_spec maudio_ozonic_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &ozonic_meter_spec
};
/* NRV10 specification */
-static struct snd_bebob_meter_spec nrv10_meter_spec = {
+static const struct snd_bebob_meter_spec nrv10_meter_spec = {
.num = ARRAY_SIZE(nrv10_meter_labels),
.labels = nrv10_meter_labels,
.get = &normal_meter_get
};
-struct snd_bebob_spec maudio_nrv10_spec = {
+const struct snd_bebob_spec maudio_nrv10_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &nrv10_meter_spec
diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c
index 568114392..90d95be49 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 c0f018a61..ef224d6f5 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;
}
@@ -155,7 +155,7 @@ static int
pcm_open(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
- struct snd_bebob_rate_spec *spec = bebob->spec->rate;
+ const struct snd_bebob_rate_spec *spec = bebob->spec->rate;
unsigned int sampling_rate;
enum snd_bebob_clock_type src;
int err;
@@ -220,8 +220,8 @@ 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 0;
}
@@ -239,8 +239,8 @@ 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 0;
}
diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c
index 301cc6a93..ec24f9679 100644
--- a/sound/firewire/bebob/bebob_proc.c
+++ b/sound/firewire/bebob/bebob_proc.c
@@ -73,7 +73,7 @@ proc_read_meters(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_bebob *bebob = entry->private_data;
- struct snd_bebob_meter_spec *spec = bebob->spec->meter;
+ const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
u32 *buf;
unsigned int i, c, channels, size;
@@ -138,8 +138,8 @@ proc_read_clock(struct snd_info_entry *entry,
"SYT-Match",
};
struct snd_bebob *bebob = entry->private_data;
- struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
- struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
+ const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
enum snd_bebob_clock_type src;
unsigned int rate;
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c
index 5be5242e1..926e5dcbb 100644
--- a/sound/firewire/bebob/bebob_stream.c
+++ b/sound/firewire/bebob/bebob_stream.c
@@ -119,7 +119,7 @@ end:
int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
enum snd_bebob_clock_type *src)
{
- struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7];
unsigned int id;
enum avc_bridgeco_plug_type type;
@@ -338,7 +338,7 @@ map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s)
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,19 @@ 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 +538,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 +567,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);
@@ -572,7 +580,7 @@ end:
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate)
{
- struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
+ const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
struct amdtp_stream *master, *slave;
enum cip_flags sync_mode;
unsigned int curr_rate;
@@ -864,8 +872,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;
@@ -959,7 +967,7 @@ end:
int snd_bebob_stream_discover(struct snd_bebob *bebob)
{
- struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
+ const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
enum avc_bridgeco_plug_type type;
unsigned int i;
diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c
index 9242e33d2..c38358b82 100644
--- a/sound/firewire/bebob/bebob_terratec.c
+++ b/sound/firewire/bebob/bebob_terratec.c
@@ -55,30 +55,30 @@ phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
return 0;
}
-static struct snd_bebob_rate_spec phase_series_rate_spec = {
+static const struct snd_bebob_rate_spec phase_series_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
/* PHASE 88 Rack FW */
-static struct snd_bebob_clock_spec phase88_rack_clk = {
+static const struct snd_bebob_clock_spec phase88_rack_clk = {
.num = ARRAY_SIZE(phase88_rack_clk_src_types),
.types = phase88_rack_clk_src_types,
.get = &phase88_rack_clk_src_get,
};
-struct snd_bebob_spec phase88_rack_spec = {
+const struct snd_bebob_spec phase88_rack_spec = {
.clock = &phase88_rack_clk,
.rate = &phase_series_rate_spec,
.meter = NULL
};
/* 'PHASE 24 FW' and 'PHASE X24 FW' */
-static struct snd_bebob_clock_spec phase24_series_clk = {
+static const struct snd_bebob_clock_spec phase24_series_clk = {
.num = ARRAY_SIZE(phase24_series_clk_src_types),
.types = phase24_series_clk_src_types,
.get = &phase24_series_clk_src_get,
};
-struct snd_bebob_spec phase24_series_spec = {
+const struct snd_bebob_spec phase24_series_spec = {
.clock = &phase24_series_clk,
.rate = &phase_series_rate_spec,
.meter = NULL
diff --git a/sound/firewire/bebob/bebob_yamaha.c b/sound/firewire/bebob/bebob_yamaha.c
index 581017024..90d4404f7 100644
--- a/sound/firewire/bebob/bebob_yamaha.c
+++ b/sound/firewire/bebob/bebob_yamaha.c
@@ -46,16 +46,16 @@ clk_src_get(struct snd_bebob *bebob, unsigned int *id)
return 0;
}
-static struct snd_bebob_clock_spec clock_spec = {
+static const struct snd_bebob_clock_spec clock_spec = {
.num = ARRAY_SIZE(clk_src_types),
.types = clk_src_types,
.get = &clk_src_get,
};
-static struct snd_bebob_rate_spec rate_spec = {
+static const struct snd_bebob_rate_spec rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
-struct snd_bebob_spec yamaha_go_spec = {
+const struct snd_bebob_spec yamaha_go_spec = {
.clock = &clock_spec,
.rate = &rate_spec,
.meter = NULL
diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile
index 9ef228ef7..55b4be9b0 100644
--- a/sound/firewire/dice/Makefile
+++ b/sound/firewire/dice/Makefile
@@ -1,3 +1,3 @@
snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \
dice-pcm.o dice-hwdep.o dice.o
-obj-m += snd-dice.o
+obj-$(CONFIG_SND_DICE) += snd-dice.o
diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c
index fe43ce791..151b09f24 100644
--- a/sound/firewire/dice/dice-midi.c
+++ b/sound/firewire/dice/dice-midi.c
@@ -52,10 +52,10 @@ 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,
+ amdtp_am824_midi_trigger(&dice->tx_stream,
substrm->number, substrm);
else
- amdtp_stream_midi_trigger(&dice->tx_stream,
+ 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 4e67b1da0..9b3431999 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;
}
@@ -243,8 +243,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 0;
}
@@ -265,8 +264,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 0;
}
diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c
index 07dbd01d7..a6a39f7ef 100644
--- a/sound/firewire/dice/dice-stream.c
+++ b/sound/firewire/dice/dice-stream.c
@@ -44,16 +44,16 @@ int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate,
static void release_resources(struct snd_dice *dice,
struct fw_iso_resources *resources)
{
- unsigned int channel;
+ __be32 channel;
/* Reset channel number */
channel = cpu_to_be32((u32)-1);
if (resources == &dice->tx_resources)
snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
- &channel, 4);
+ &channel, sizeof(channel));
else
snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
- &channel, 4);
+ &channel, sizeof(channel));
fw_iso_resources_free(resources);
}
@@ -62,7 +62,7 @@ static int keep_resources(struct snd_dice *dice,
struct fw_iso_resources *resources,
unsigned int max_payload_bytes)
{
- unsigned int channel;
+ __be32 channel;
int err;
err = fw_iso_resources_allocate(resources, max_payload_bytes,
@@ -74,10 +74,10 @@ static int keep_resources(struct snd_dice *dice,
channel = cpu_to_be32(resources->channel);
if (resources == &dice->tx_resources)
err = snd_dice_transaction_write_tx(dice, TX_ISOCHRONOUS,
- &channel, 4);
+ &channel, sizeof(channel));
else
err = snd_dice_transaction_write_rx(dice, RX_ISOCHRONOUS,
- &channel, 4);
+ &channel, sizeof(channel));
if (err < 0)
release_resources(dice, resources);
end:
@@ -100,6 +100,7 @@ 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);
@@ -125,21 +126,24 @@ 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) {
+ err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports,
+ double_pcm_frames);
+ if (err < 0)
+ goto end;
+
+ 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);
}
}
@@ -302,7 +306,7 @@ static int init_stream(struct snd_dice *dice, struct amdtp_stream *stream)
goto end;
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);
diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c
index 70a111d7f..0cda05c72 100644
--- a/sound/firewire/dice/dice.c
+++ b/sound/firewire/dice/dice.c
@@ -12,9 +12,11 @@ MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");
#define OUI_WEISS 0x001c6a
+#define OUI_LOUD 0x000ff2
#define DICE_CATEGORY_ID 0x04
#define WEISS_CATEGORY_ID 0x00
+#define LOUD_CATEGORY_ID 0x10
static int dice_interface_check(struct fw_unit *unit)
{
@@ -29,7 +31,8 @@ static int dice_interface_check(struct fw_unit *unit)
struct fw_csr_iterator it;
int key, val, vendor = -1, model = -1, err;
unsigned int category, i;
- __be32 *pointers, value;
+ __be32 *pointers;
+ u32 value;
__be32 version;
pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
@@ -56,6 +59,8 @@ static int dice_interface_check(struct fw_unit *unit)
}
if (vendor == OUI_WEISS)
category = WEISS_CATEGORY_ID;
+ else if (vendor == OUI_LOUD)
+ category = LOUD_CATEGORY_ID;
else
category = DICE_CATEGORY_ID;
if (device->config_rom[3] != ((vendor << 8) | category) ||
diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h
index ecf5dc862..101550ac1 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-am824.h"
#include "../iso-resources.h"
#include "../lib.h"
#include "dice-interface.h"
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
new file mode 100644
index 000000000..1123e68c8
--- /dev/null
+++ b/sound/firewire/digi00x/Makefile
@@ -0,0 +1,4 @@
+snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \
+ digi00x-pcm.o digi00x-hwdep.o \
+ digi00x-transaction.o digi00x-midi.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 000000000..b02a5e8ca
--- /dev/null
+++ b/sound/firewire/digi00x/amdtp-dot.c
@@ -0,0 +1,442 @@
+/*
+ * 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
+
+/* 'Clock-based rate control mode' is just supported. */
+#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;
+ /* 2 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) */
+ struct snd_rawmidi_substream *midi[2];
+ int midi_fifo_used[2];
+ int midi_fifo_limit;
+
+ void (*transfer_samples)(struct amdtp_stream *s,
+ struct snd_pcm_substream *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)
+{
+ struct amdtp_dot *p = s->protocol;
+ int err;
+
+ if (amdtp_stream_running(s))
+ return -EBUSY;
+
+ /*
+ * A first data channel is for MIDI conformant data channel, the rest is
+ * Multi Bit Linear Audio data channel.
+ */
+ err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1);
+ if (err < 0)
+ return err;
+
+ s->fdf = AMDTP_FDF_AM824 | s->sfc;
+
+ p->pcm_channels = pcm_channels;
+
+ if (s->direction == AMDTP_IN_STREAM)
+ p->midi_ports = DOT_MIDI_IN_PORTS;
+ else
+ p->midi_ports = DOT_MIDI_OUT_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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_dot *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_dot *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_dot *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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;
+ int len;
+ u8 *b;
+
+ for (f = 0; f < data_blocks; f++) {
+ port = (s->data_block_counter + f) % 8;
+ b = (u8 *)&buffer[0];
+
+ len = 0;
+ if (port < p->midi_ports &&
+ midi_ratelimit_per_packet(s, port) &&
+ p->midi[port] != NULL)
+ len = snd_rawmidi_transmit(p->midi[port], b + 1, 2);
+
+ if (len > 0) {
+ b[3] = (0x10 << port) | len;
+ midi_use_bytes(s, port, len);
+ } else {
+ b[1] = 0;
+ b[2] = 0;
+ b[3] = 0;
+ }
+ b[0] = 0x80;
+
+ 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 (port < p->midi_ports && 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 (pcm) {
+ p->transfer_samples(s, pcm, buffer, data_blocks);
+ pcm_frames = data_blocks;
+ } else {
+ pcm_frames = 0;
+ }
+
+ 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, buffer, data_blocks);
+ pcm_frames = data_blocks;
+ } else {
+ write_pcm_silence(s, buffer, data_blocks);
+ pcm_frames = 0;
+ }
+
+ 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)
+{
+ amdtp_stream_process_data_blocks_t process_data_blocks;
+ enum cip_flags flags;
+
+ /* Use different mode between incoming/outgoing. */
+ if (dir == AMDTP_IN_STREAM) {
+ flags = CIP_NONBLOCKING | CIP_SKIP_INIT_DBC_CHECK;
+ process_data_blocks = process_tx_data_blocks;
+ } else {
+ flags = CIP_BLOCKING;
+ process_data_blocks = process_rx_data_blocks;
+ }
+
+ return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM,
+ process_data_blocks, sizeof(struct amdtp_dot));
+}
+
+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-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c
new file mode 100644
index 000000000..f188e4758
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-hwdep.c
@@ -0,0 +1,200 @@
+/*
+ * 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 && dg00x->msg == 0) {
+ 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));
+ } 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);
+
+ 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 || dg00x->msg)
+ 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
new file mode 100644
index 000000000..1a72a382b
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-midi.c
@@ -0,0 +1,223 @@
+/*
+ * 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_phys_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;
+}
+
+static int midi_phys_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);
+
+ snd_dg00x_stream_lock_release(dg00x);
+ return 0;
+}
+
+static void midi_phys_capture_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_dg00x *dg00x = substream->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dg00x->lock, flags);
+
+ if (up)
+ amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
+ substream);
+ else
+ amdtp_dot_midi_trigger(&dg00x->tx_stream, substream->number,
+ NULL);
+
+ spin_unlock_irqrestore(&dg00x->lock, flags);
+}
+
+static void midi_phys_playback_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_dg00x *dg00x = substream->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dg00x->lock, flags);
+
+ if (up)
+ amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
+ substream);
+ else
+ amdtp_dot_midi_trigger(&dg00x->rx_stream, substream->number,
+ NULL);
+
+ spin_unlock_irqrestore(&dg00x->lock, flags);
+}
+
+static struct snd_rawmidi_ops midi_phys_capture_ops = {
+ .open = midi_phys_open,
+ .close = midi_phys_close,
+ .trigger = midi_phys_capture_trigger,
+};
+
+static struct snd_rawmidi_ops midi_phys_playback_ops = {
+ .open = midi_phys_open,
+ .close = midi_phys_close,
+ .trigger = midi_phys_playback_trigger,
+};
+
+static int midi_ctl_open(struct snd_rawmidi_substream *substream)
+{
+ /* Do nothing. */
+ return 0;
+}
+
+static int midi_ctl_capture_close(struct snd_rawmidi_substream *substream)
+{
+ /* Do nothing. */
+ return 0;
+}
+
+static int midi_ctl_playback_close(struct snd_rawmidi_substream *substream)
+{
+ struct snd_dg00x *dg00x = substream->rmidi->private_data;
+
+ snd_fw_async_midi_port_finish(&dg00x->out_control);
+
+ return 0;
+}
+
+static void midi_ctl_capture_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_dg00x *dg00x = substream->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dg00x->lock, flags);
+
+ if (up)
+ dg00x->in_control = substream;
+ else
+ dg00x->in_control = NULL;
+
+ spin_unlock_irqrestore(&dg00x->lock, flags);
+}
+
+static void midi_ctl_playback_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct snd_dg00x *dg00x = substream->rmidi->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dg00x->lock, flags);
+
+ if (up)
+ snd_fw_async_midi_port_run(&dg00x->out_control, substream);
+
+ spin_unlock_irqrestore(&dg00x->lock, flags);
+}
+
+static struct snd_rawmidi_ops midi_ctl_capture_ops = {
+ .open = midi_ctl_open,
+ .close = midi_ctl_capture_close,
+ .trigger = midi_ctl_capture_trigger,
+};
+
+static struct snd_rawmidi_ops midi_ctl_playback_ops = {
+ .open = midi_ctl_open,
+ .close = midi_ctl_playback_close,
+ .trigger = midi_ctl_playback_trigger,
+};
+
+static void set_midi_substream_names(struct snd_dg00x *dg00x,
+ struct snd_rawmidi_str *str,
+ bool is_ctl)
+{
+ struct snd_rawmidi_substream *subs;
+
+ list_for_each_entry(subs, &str->substreams, list) {
+ if (!is_ctl)
+ snprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ dg00x->card->shortname, subs->number + 1);
+ else
+ /* This port is for asynchronous transaction. */
+ snprintf(subs->name, sizeof(subs->name),
+ "%s control",
+ dg00x->card->shortname);
+ }
+}
+
+int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
+{
+ struct snd_rawmidi *rmidi[2];
+ struct snd_rawmidi_str *str;
+ unsigned int i;
+ int err;
+
+ /* Add physical midi ports. */
+ err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
+ DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, &rmidi[0]);
+ if (err < 0)
+ return err;
+
+ snprintf(rmidi[0]->name, sizeof(rmidi[0]->name),
+ "%s MIDI", dg00x->card->shortname);
+
+ snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_INPUT,
+ &midi_phys_capture_ops);
+ snd_rawmidi_set_ops(rmidi[0], SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &midi_phys_playback_ops);
+
+ /* Add a pair of control midi ports. */
+ err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 1,
+ 1, 1, &rmidi[1]);
+ if (err < 0)
+ return err;
+
+ snprintf(rmidi[1]->name, sizeof(rmidi[1]->name),
+ "%s control", dg00x->card->shortname);
+
+ snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_INPUT,
+ &midi_ctl_capture_ops);
+ snd_rawmidi_set_ops(rmidi[1], SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &midi_ctl_playback_ops);
+
+ for (i = 0; i < ARRAY_SIZE(rmidi); i++) {
+ rmidi[i]->private_data = dg00x;
+
+ rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+ set_midi_substream_names(dg00x, str, i);
+
+ rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ str = &rmidi[i]->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+ set_midi_substream_names(dg00x, str, i);
+
+ rmidi[i]->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+ }
+
+ return 0;
+}
diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c
new file mode 100644
index 000000000..cac28f70a
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-pcm.c
@@ -0,0 +1,373 @@
+/*
+ * 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 = snd_dg00x_stream_lock_try(dg00x);
+ if (err < 0)
+ goto end;
+
+ err = pcm_init_hw_params(dg00x, substream);
+ if (err < 0)
+ goto err_locked;
+
+ /* Check current clock source. */
+ err = snd_dg00x_stream_get_clock(dg00x, &clock);
+ if (err < 0)
+ goto err_locked;
+ if (clock != SND_DG00X_CLOCK_INTERNAL) {
+ err = snd_dg00x_stream_check_external_clock(dg00x, &detect);
+ if (err < 0)
+ goto err_locked;
+ if (!detect) {
+ err = -EBUSY;
+ goto err_locked;
+ }
+ }
+
+ 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)
+ 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;
+}
+
+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;
+ int err;
+
+ err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ return err;
+
+ 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 0;
+}
+
+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;
+ int err;
+
+ err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ return err;
+
+ 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 0;
+}
+
+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-proc.c b/sound/firewire/digi00x/digi00x-proc.c
new file mode 100644
index 000000000..a1d601f31
--- /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-stream.c b/sound/firewire/digi00x/digi00x-stream.c
new file mode 100644
index 000000000..4d3b4ebbd
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-stream.c
@@ -0,0 +1,422 @@
+/*
+ * 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,
+ &reg, 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 -EINVAL;
+
+ reg = cpu_to_be32(i);
+ return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
+ DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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. */
+ err = amdtp_dot_set_parameters(&dg00x->rx_stream, rate,
+ snd_dg00x_stream_pcm_channels[i]);
+ if (err < 0)
+ return err;
+ 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. */
+ err = amdtp_dot_set_parameters(&dg00x->tx_stream, rate,
+ snd_dg00x_stream_pcm_channels[i]);
+ if (err < 0)
+ return err;
+ 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:
+ snd_dg00x_stream_destroy_duplex(dg00x);
+ 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;
+ 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 source 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);
+}
+
+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-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c
new file mode 100644
index 000000000..554324d8c
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-transaction.c
@@ -0,0 +1,137 @@
+/*
+ * 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 int fill_midi_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_midi_control(struct snd_dg00x *dg00x, __be32 *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_unknown_message(struct snd_dg00x *dg00x,
+ unsigned long long offset, __be32 *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;
+ __be32 *buf = (__be32 *)data;
+
+ 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);
+}
+
+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_MIDI_CTL_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 = 12;
+ 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)
+ goto error;
+
+ err = snd_fw_async_midi_port_init(&dg00x->out_control, dg00x->unit,
+ DG00X_ADDR_BASE + DG00X_OFFSET_MMC,
+ 4, fill_midi_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.c b/sound/firewire/digi00x/digi00x.c
new file mode 100644
index 000000000..1f33b7a1f
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x.c
@@ -0,0 +1,170 @@
+/*
+ * 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 Digi 002/003 family 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 = skip_spaces(name);
+
+ 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,
+ fw_dev->config_rom[3], 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;
+
+ snd_dg00x_stream_destroy_duplex(dg00x);
+ snd_dg00x_transaction_unregister(dg00x);
+
+ 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);
+ spin_lock_init(&dg00x->lock);
+ init_waitqueue_head(&dg00x->hwdep_wait);
+
+ err = name_card(dg00x);
+ if (err < 0)
+ goto error;
+
+ err = snd_dg00x_stream_init_duplex(dg00x);
+ if (err < 0)
+ goto error;
+
+ snd_dg00x_proc_init(dg00x);
+
+ err = snd_dg00x_create_pcm_devices(dg00x);
+ if (err < 0)
+ goto error;
+
+ err = snd_dg00x_create_midi_devices(dg00x);
+ if (err < 0)
+ goto error;
+
+ err = snd_dg00x_create_hwdep_device(dg00x);
+ 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;
+
+ dev_set_drvdata(&unit->device, dg00x);
+
+ return err;
+error:
+ snd_card_free(card);
+ return err;
+}
+
+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);
+}
+
+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 000000000..907e73993
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x.h
@@ -0,0 +1,157 @@
+/*
+ * 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 <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
+#include <sound/rawmidi.h>
+
+#include "../lib.h"
+#include "../iso-resources.h"
+#include "../amdtp-stream.h"
+
+struct snd_dg00x {
+ struct snd_card *card;
+ struct fw_unit *unit;
+
+ struct mutex mutex;
+ spinlock_t lock;
+
+ 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;
+
+ /* for uapi */
+ int dev_lock_count;
+ bool dev_lock_changed;
+ wait_queue_head_t hwdep_wait;
+
+ /* For asynchronous messages. */
+ struct fw_address_handler async_handler;
+ u32 msg;
+
+ /* For asynchronous MIDI controls. */
+ struct snd_rawmidi_substream *in_control;
+ struct snd_fw_async_midi_port out_control;
+};
+
+#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,
+};
+
+#define DOT_MIDI_IN_PORTS 1
+#define DOT_MIDI_OUT_PORTS 2
+
+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);
+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);
+
+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,
+ 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);
+
+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
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c
index 0619597e3..cce19768f 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/Makefile b/sound/firewire/fireworks/Makefile
index 0c7440826..15ef7f75a 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,4 +1,4 @@
snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
fireworks_stream.o fireworks_proc.o fireworks_midi.o \
fireworks_pcm.o fireworks_hwdep.o fireworks.o
-obj-m += snd-fireworks.o
+obj-$(CONFIG_SND_FIREWORKS) += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index c94a432f7..d5b19bc11 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 084d414b2..c7cb7deaf 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-am824.h"
#include "../cmp.h"
#include "../lib.h"
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c
index 166f80584..94bab0476 100644
--- a/sound/firewire/fireworks/fireworks_command.c
+++ b/sound/firewire/fireworks/fireworks_command.c
@@ -257,7 +257,7 @@ int snd_efw_command_get_phys_meters(struct snd_efw *efw,
struct snd_efw_phys_meters *meters,
unsigned int len)
{
- __be32 *buf = (__be32 *)meters;
+ u32 *buf = (u32 *)meters;
unsigned int i;
int err;
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c
index cf9c65260..fba01bbba 100644
--- a/sound/firewire/fireworks/fireworks_midi.c
+++ b/sound/firewire/fireworks/fireworks_midi.c
@@ -73,10 +73,10 @@ 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,
+ amdtp_am824_midi_trigger(&efw->tx_stream,
substrm->number, substrm);
else
- amdtp_stream_midi_trigger(&efw->tx_stream,
+ 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 c30b2ffa8..d27135bac 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;
}
@@ -253,7 +253,8 @@ 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 0;
}
@@ -270,7 +271,8 @@ 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 0;
}
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
index 7e353f1f7..759f6e3ed 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/lib.c b/sound/firewire/lib.c
index 7409edba9..f80aafa44 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,147 @@ 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);
+
+ /* This port is closed. */
+ if (substream == NULL)
+ return;
+
+ if (rcode == RCODE_COMPLETE)
+ snd_rawmidi_transmit_ack(substream, port->consume_bytes);
+ else if (!rcode_is_permanent_error(rcode))
+ /* To start next transaction immediately for recovery. */
+ port->next_ktime = ktime_set(0, 0);
+ else
+ /* Don't continue processing. */
+ port->error = true;
+
+ port->idling = true;
+
+ if (!snd_rawmidi_transmit_empty(substream))
+ schedule_work(&port->work);
+}
+
+static void midi_port_work(struct work_struct *work)
+{
+ struct snd_fw_async_midi_port *port =
+ container_of(work, struct snd_fw_async_midi_port, work);
+ struct snd_rawmidi_substream *substream = ACCESS_ONCE(port->substream);
+ int generation;
+ int type;
+
+ /* Under transacting or error state. */
+ if (!port->idling || port->error)
+ return;
+
+ /* Nothing to do. */
+ if (substream == NULL || snd_rawmidi_transmit_empty(substream))
+ return;
+
+ /* Do it in next chance. */
+ if (ktime_after(port->next_ktime, ktime_get())) {
+ schedule_work(&port->work);
+ 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->fill(substream, port->buf);
+ if (port->consume_bytes <= 0) {
+ /* Do it in next chance, immediately. */
+ if (port->consume_bytes == 0) {
+ port->next_ktime = ktime_set(0, 0);
+ schedule_work(&port->work);
+ } else {
+ /* Fatal error. */
+ port->error = true;
+ }
+ return;
+ }
+
+ /* Calculate type of transaction. */
+ if (port->len == 4)
+ type = TCODE_WRITE_QUADLET_REQUEST;
+ else
+ type = TCODE_WRITE_BLOCK_REQUEST;
+
+ /* Set interval to next transaction. */
+ port->next_ktime = ktime_add_ns(ktime_get(),
+ port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
+
+ /* 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
+ * 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
+ * @fill: 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,
+ snd_fw_async_midi_port_fill fill)
+{
+ 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->fill = fill;
+ port->idling = true;
+ port->next_ktime = ktime_set(0, 0);
+ port->error = false;
+
+ INIT_WORK(&port->work, midi_port_work);
+
+ 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);
+ cancel_work_sync(&port->work);
+ 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 02cfabc9c..f3f6f84c4 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,58 @@ static inline bool rcode_is_permanent_error(int rcode)
return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
}
+struct snd_fw_async_midi_port;
+typedef int (*snd_fw_async_midi_port_fill)(
+ struct snd_rawmidi_substream *substream,
+ u8 *buf);
+
+struct snd_fw_async_midi_port {
+ struct fw_device *parent;
+ struct work_struct work;
+ bool idling;
+ ktime_t next_ktime;
+ bool error;
+
+ u64 addr;
+ struct fw_transaction transaction;
+
+ u8 *buf;
+ unsigned int len;
+
+ struct snd_rawmidi_substream *substream;
+ snd_fw_async_midi_port_fill fill;
+ 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,
+ snd_fw_async_midi_port_fill fill);
+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)
+{
+ if (!port->error) {
+ port->substream = substream;
+ schedule_work(&port->work);
+ }
+}
+
+/**
+ * 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;
+ port->error = false;
+}
+
#endif
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index a92685086..06ff50f4e 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,3 +1,3 @@
snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \
oxfw-proc.o oxfw-midi.o oxfw-hwdep.o oxfw.o
-obj-m += snd-oxfw.o
+obj-$(CONFIG_SND_OXFW) += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c
index 540a30338..8665e1043 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);
}
@@ -142,29 +142,11 @@ static void set_midi_substream_names(struct snd_oxfw *oxfw,
int snd_oxfw_create_midi(struct snd_oxfw *oxfw)
{
- struct snd_oxfw_stream_formation formation;
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *str;
- u8 *format;
- int i, err;
-
- /* If its stream has MIDI conformant data channel, add one MIDI port */
- for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
- format = oxfw->tx_stream_formats[i];
- if (format != NULL) {
- err = snd_oxfw_stream_parse_format(format, &formation);
- if (err >= 0 && formation.midi > 0)
- oxfw->midi_input_ports = 1;
- }
-
- format = oxfw->rx_stream_formats[i];
- if (format != NULL) {
- err = snd_oxfw_stream_parse_format(format, &formation);
- if (err >= 0 && formation.midi > 0)
- oxfw->midi_output_ports = 1;
- }
- }
- if ((oxfw->midi_input_ports == 0) && (oxfw->midi_output_ports == 0))
+ int err;
+
+ if (oxfw->midi_input_ports == 0 && oxfw->midi_output_ports == 0)
return 0;
/* create midi ports */
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c
index 9c73930d0..8d2334176 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;
}
@@ -244,7 +244,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 0;
}
@@ -265,7 +265,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 0;
}
diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c
index 77ad5b98e..7cb5743c0 100644
--- a/sound/firewire/oxfw/oxfw-stream.c
+++ b/sound/firewire/oxfw/oxfw-stream.c
@@ -148,14 +148,17 @@ static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream,
}
pcm_channels = formation.pcm;
- midi_ports = DIV_ROUND_UP(formation.midi, 8);
+ midi_ports = formation.midi * 8;
/* The stream should have one pcm channels at least */
if (pcm_channels == 0) {
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);
@@ -238,9 +241,12 @@ int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw,
* packets. As a result, next isochronous packet includes more data
* blocks than IEC 61883-6 defines.
*/
- if (stream == &oxfw->tx_stream)
+ if (stream == &oxfw->tx_stream) {
oxfw->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK |
CIP_JUMBO_PAYLOAD;
+ if (oxfw->wrong_dbs)
+ oxfw->tx_stream.flags |= CIP_WRONG_DBS;
+ }
end:
return err;
}
@@ -480,8 +486,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;
@@ -623,6 +629,9 @@ end:
int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
{
u8 plugs[AVC_PLUG_INFO_BUF_BYTES];
+ struct snd_oxfw_stream_formation formation;
+ u8 *format;
+ unsigned int i;
int err;
/* the number of plugs for isoc in/out, ext in/out */
@@ -642,12 +651,42 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0);
if (err < 0)
goto end;
+
+ for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+ format = oxfw->tx_stream_formats[i];
+ if (format == NULL)
+ continue;
+ err = snd_oxfw_stream_parse_format(format, &formation);
+ if (err < 0)
+ continue;
+
+ /* Add one MIDI port. */
+ if (formation.midi > 0)
+ oxfw->midi_input_ports = 1;
+ }
+
oxfw->has_output = true;
}
/* use iPCR[0] if exists */
- if (plugs[0] > 0)
+ if (plugs[0] > 0) {
err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0);
+ if (err < 0)
+ goto end;
+
+ for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+ format = oxfw->rx_stream_formats[i];
+ if (format == NULL)
+ continue;
+ err = snd_oxfw_stream_parse_format(format, &formation);
+ if (err < 0)
+ continue;
+
+ /* Add one MIDI port. */
+ if (formation.midi > 0)
+ oxfw->midi_output_ports = 1;
+ }
+ }
end:
return err;
}
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 8c6ce019f..588b93f20 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -18,6 +18,9 @@
#define VENDOR_GRIFFIN 0x001292
#define VENDOR_BEHRINGER 0x001564
#define VENDOR_LACIE 0x00d04b
+#define VENDOR_TASCAM 0x00022e
+
+#define MODEL_SATELLITE 0x00200f
#define SPECIFIER_1394TA 0x00a02d
#define VERSION_AVC 0x010001
@@ -129,6 +132,40 @@ static void oxfw_card_free(struct snd_card *card)
mutex_destroy(&oxfw->mutex);
}
+static void detect_quirks(struct snd_oxfw *oxfw)
+{
+ struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
+ struct fw_csr_iterator it;
+ int key, val;
+ int vendor, model;
+
+ /* Seek from Root Directory of Config ROM. */
+ vendor = model = 0;
+ fw_csr_iterator_init(&it, fw_dev->config_rom + 5);
+ while (fw_csr_iterator_next(&it, &key, &val)) {
+ if (key == CSR_VENDOR)
+ vendor = val;
+ else if (key == CSR_MODEL)
+ model = val;
+ }
+
+ /*
+ * Mackie Onyx Satellite with base station has a quirk to report a wrong
+ * value in 'dbs' field of CIP header against its format information.
+ */
+ if (vendor == VENDOR_LOUD && model == MODEL_SATELLITE)
+ oxfw->wrong_dbs = true;
+
+ /*
+ * TASCAM FireOne has physical control and requires a pair of additional
+ * MIDI ports.
+ */
+ if (vendor == VENDOR_TASCAM) {
+ oxfw->midi_input_ports++;
+ oxfw->midi_output_ports++;
+ }
+}
+
static int oxfw_probe(struct fw_unit *unit,
const struct ieee1394_device_id *id)
{
@@ -157,6 +194,8 @@ static int oxfw_probe(struct fw_unit *unit,
if (err < 0)
goto error;
+ detect_quirks(oxfw);
+
err = name_card(oxfw);
if (err < 0)
goto error;
@@ -294,6 +333,13 @@ static const struct ieee1394_device_id oxfw_id_table[] = {
.specifier_id = SPECIFIER_1394TA,
.version = VERSION_AVC,
},
+ /* TASCAM, FireOne */
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = VENDOR_TASCAM,
+ .model_id = 0x800007,
+ },
{ }
};
MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index cace5ad4f..8392c424a 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-am824.h"
#include "../cmp.h"
struct device_info {
@@ -49,6 +49,7 @@ struct snd_oxfw {
struct mutex mutex;
spinlock_t lock;
+ bool wrong_dbs;
bool has_output;
u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES];
u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES];
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile
new file mode 100644
index 000000000..0fc955d5b
--- /dev/null
+++ b/sound/firewire/tascam/Makefile
@@ -0,0 +1,4 @@
+snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \
+ tascam-pcm.o tascam-hwdep.o tascam-transaction.o \
+ tascam-midi.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 000000000..9dd0fccd5
--- /dev/null
+++ b/sound/firewire/tascam/amdtp-tascam.c
@@ -0,0 +1,243 @@
+/*
+ * 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_substream *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;
+
+ /* Packets in in-stream have extra 2 data 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_tscm *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_tscm *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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_substream *pcm,
+ __be32 *buffer, unsigned int frames)
+{
+ struct amdtp_tscm *p = s->protocol;
+ struct snd_pcm_runtime *runtime = pcm->runtime;
+ 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 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, buffer, data_blocks);
+
+ /* A place holder for control messages. */
+
+ 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, 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)
+{
+ amdtp_stream_process_data_blocks_t process_data_blocks;
+ struct amdtp_tscm *p;
+ unsigned int fmt;
+ int err;
+
+ if (dir == AMDTP_IN_STREAM) {
+ fmt = AMDTP_FMT_TSCM_TX;
+ process_data_blocks = process_tx_data_blocks;
+ } else {
+ fmt = AMDTP_FMT_TSCM_RX;
+ process_data_blocks = process_rx_data_blocks;
+ }
+
+ err = amdtp_stream_init(s, unit, dir,
+ CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt,
+ process_data_blocks, sizeof(struct amdtp_tscm));
+ if (err < 0)
+ return 0;
+
+ /* Use fixed value for FDF field. */
+ s->fdf = 0x00;
+
+ /* This protocol uses fixed number of data channels for PCM samples. */
+ p = s->protocol;
+ p->pcm_channels = pcm_channels;
+
+ return 0;
+}
diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c
new file mode 100644
index 000000000..131267c3a
--- /dev/null
+++ b/sound/firewire/tascam/tascam-hwdep.c
@@ -0,0 +1,201 @@
+/*
+ * 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
+ */
+
+#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-midi.c b/sound/firewire/tascam/tascam-midi.c
new file mode 100644
index 000000000..41f842079
--- /dev/null
+++ b/sound/firewire/tascam/tascam-midi.c
@@ -0,0 +1,135 @@
+/*
+ * 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)
+{
+ /* Do nothing. */
+ 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)
+{
+ /* Do nothing. */
+ 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);
+
+ 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;
+ struct snd_rawmidi_substream *subs;
+ int err;
+
+ err = snd_rawmidi_new(tscm->card, tscm->card->driver, 0,
+ tscm->spec->midi_playback_ports,
+ tscm->spec->midi_capture_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) {
+ /* TODO: support virtual MIDI ports. */
+ 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);
+ }
+ }
+
+ 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_playback_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-pcm.c b/sound/firewire/tascam/tascam-pcm.c
new file mode 100644
index 000000000..380d3db96
--- /dev/null
+++ b/sound/firewire/tascam/tascam-pcm.c
@@ -0,0 +1,312 @@
+/*
+ * 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;
+ } 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 = snd_tscm_stream_lock_try(tscm);
+ if (err < 0)
+ goto end;
+
+ err = pcm_init_hw_params(tscm, substream);
+ if (err < 0)
+ goto err_locked;
+
+ 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)
+ 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;
+}
+
+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;
+ int err;
+
+ err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ return err;
+
+ 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 0;
+}
+
+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;
+ int err;
+
+ err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ return err;
+
+ 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 0;
+}
+
+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-proc.c b/sound/firewire/tascam/tascam-proc.c
new file mode 100644
index 000000000..bfd4a4c06
--- /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-stream.c b/sound/firewire/tascam/tascam-stream.c
new file mode 100644
index 000000000..0e6dd5c61
--- /dev/null
+++ b/sound/firewire/tascam/tascam-stream.c
@@ -0,0 +1,496 @@
+/*
+ * 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, sizeof(reg), 0);
+}
+
+int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate)
+{
+ u32 data = 0x0;
+ unsigned int trials = 0;
+ int err;
+
+ while (data == 0x0 || trials++ < 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)
+{
+ __be32 reg;
+ u32 data;
+ unsigned int i;
+ int err;
+
+ data = 0;
+ for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i)
+ data |= BIT(i);
+ if (tscm->spec->has_adat)
+ data |= 0x0000ff00;
+ if (tscm->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,
+ &reg, sizeof(reg), 0);
+ if (err < 0)
+ return err;
+
+ data = 0;
+ for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i)
+ data |= BIT(i);
+ if (tscm->spec->has_adat)
+ data |= 0x0000ff00;
+ if (tscm->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,
+ &reg, 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,
+ &reg, 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,
+ &reg, sizeof(reg), 0);
+
+ reg = 0;
+ snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+ TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, sizeof(reg), 0);
+ reg = cpu_to_be32(0x00000000);
+ snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+ TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, 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);
+}
+
+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-transaction.c b/sound/firewire/tascam/tascam-transaction.c
new file mode 100644
index 000000000..904ce0329
--- /dev/null
+++ b/sound/firewire/tascam/tascam-transaction.c
@@ -0,0 +1,302 @@
+/*
+ * 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 fill_message(struct snd_rawmidi_substream *substream, u8 *buf)
+{
+ struct snd_tscm *tscm = substream->rmidi->private_data;
+ unsigned int port = substream->number;
+ int i, len, consume;
+ u8 *label, *msg;
+ u8 status;
+
+ /* The first byte is used for label, the rest for MIDI bytes. */
+ label = buf;
+ msg = buf + 1;
+
+ consume = snd_rawmidi_transmit_peek(substream, msg, 3);
+ if (consume == 0)
+ return 0;
+
+ /* On exclusive message. */
+ if (tscm->on_sysex[port]) {
+ /* Seek the end of exclusives. */
+ for (i = 0; i < consume; ++i) {
+ if (msg[i] == 0xf7) {
+ tscm->on_sysex[port] = false;
+ break;
+ }
+ }
+
+ /* At the end of exclusive message, use label 0x07. */
+ if (!tscm->on_sysex[port]) {
+ consume = i + 1;
+ *label = (port << 4) | 0x07;
+ /* During exclusive message, use label 0x04. */
+ } else if (consume == 3) {
+ *label = (port << 4) | 0x04;
+ /* We need to fill whole 3 bytes. Go to next change. */
+ } else {
+ return 0;
+ }
+
+ len = consume;
+ } else {
+ /* The beginning of exclusives. */
+ if (msg[0] == 0xf0) {
+ /* Transfer it in next chance in another condition. */
+ tscm->on_sysex[port] = true;
+ return 0;
+ } else {
+ /* On running-status. */
+ if ((msg[0] & 0x80) != 0x80)
+ status = tscm->running_status[port];
+ else
+ status = msg[0];
+
+ /* Calculate consume bytes. */
+ len = calculate_message_bytes(status);
+ if (len <= 0)
+ return 0;
+
+ /* On running-status. */
+ if ((msg[0] & 0x80) != 0x80) {
+ /* Enough MIDI bytes were not retrieved. */
+ if (consume < len - 1)
+ return 0;
+ consume = len - 1;
+
+ msg[2] = msg[1];
+ msg[1] = msg[0];
+ msg[0] = tscm->running_status[port];
+ } else {
+ /* Enough MIDI bytes were not retrieved. */
+ if (consume < len)
+ return 0;
+ consume = len;
+
+ tscm->running_status[port] = msg[0];
+ }
+ }
+
+ *label = (port << 4) | (msg[0] >> 4);
+ }
+
+ if (len > 0 && len < 3)
+ memset(msg + len, 0, 3 - len);
+
+ return consume;
+}
+
+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;
+ /* TODO: support virtual MIDI ports. */
+ if (port >= tscm->spec->midi_capture_ports)
+ 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)
+ goto error;
+
+ 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, fill_message);
+ if (err < 0)
+ goto error;
+ }
+
+ return err;
+error:
+ fw_core_remove_address_handler(&tscm->async_handler);
+ 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,
+ &reg, 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,
+ &reg, 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,
+ &reg, sizeof(reg), 0);
+ if (err < 0)
+ return err;
+
+ /* Turn on FireWire LED. */
+ reg = cpu_to_be32(0x0001008e);
+ return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+ TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER,
+ &reg, sizeof(reg), 0);
+}
+
+void snd_tscm_transaction_unregister(struct snd_tscm *tscm)
+{
+ __be32 reg;
+ unsigned int i;
+
+ /* Turn off FireWire LED. */
+ reg = cpu_to_be32(0x0000008e);
+ snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+ TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER,
+ &reg, sizeof(reg), 0);
+
+ /* 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,
+ &reg, sizeof(reg), 0);
+
+ /* Unregister the address. */
+ snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+ TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI,
+ &reg, sizeof(reg), 0);
+ snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,
+ TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO,
+ &reg, sizeof(reg), 0);
+
+ fw_core_remove_address_handler(&tscm->async_handler);
+ for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++)
+ snd_fw_async_midi_port_destroy(&tscm->out_ports[i]);
+}
diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c
new file mode 100644
index 000000000..ee0bc1839
--- /dev/null
+++ b/sound/firewire/tascam/tascam.c
@@ -0,0 +1,209 @@
+/*
+ * 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 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-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,
+ },
+ /* FW-1804 may be supported. */
+};
+
+static int identify_model(struct snd_tscm *tscm)
+{
+ struct fw_device *fw_dev = fw_parent_device(tscm->unit);
+ const u32 *config_rom = fw_dev->config_rom;
+ char model[9];
+ unsigned int i;
+ u8 c;
+
+ if (fw_dev->config_rom_length < 30) {
+ dev_err(&tscm->unit->device,
+ "Configuration ROM is too short.\n");
+ return -ENODEV;
+ }
+
+ /* Pick up model name from certain addresses. */
+ for (i = 0; i < 8; i++) {
+ c = config_rom[28 + i / 4] >> (24 - 8 * (i % 4));
+ if (c == '\0')
+ break;
+ model[i] = c;
+ }
+ model[i] = '\0';
+
+ for (i = 0; i < ARRAY_SIZE(model_specs); i++) {
+ if (strcmp(model, model_specs[i].name) == 0) {
+ tscm->spec = &model_specs[i];
+ break;
+ }
+ }
+ if (tscm->spec == NULL)
+ return -ENODEV;
+
+ strcpy(tscm->card->driver, "FW-TASCAM");
+ strcpy(tscm->card->shortname, model);
+ strcpy(tscm->card->mixername, model);
+ snprintf(tscm->card->longname, sizeof(tscm->card->longname),
+ "TASCAM %s, GUID %08x%08x at %s, S%d", model,
+ fw_dev->config_rom[3], 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;
+
+ snd_tscm_transaction_unregister(tscm);
+ snd_tscm_stream_destroy_duplex(tscm);
+
+ 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);
+ spin_lock_init(&tscm->lock);
+ init_waitqueue_head(&tscm->hwdep_wait);
+
+ err = identify_model(tscm);
+ if (err < 0)
+ goto error;
+
+ snd_tscm_proc_init(tscm);
+
+ err = snd_tscm_stream_init_duplex(tscm);
+ if (err < 0)
+ goto error;
+
+ err = snd_tscm_create_pcm_devices(tscm);
+ if (err < 0)
+ goto error;
+
+ err = snd_tscm_transaction_register(tscm);
+ if (err < 0)
+ goto error;
+
+ err = snd_tscm_create_midi_devices(tscm);
+ 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;
+
+ dev_set_drvdata(&unit->device, tscm);
+
+ return err;
+error:
+ snd_card_free(card);
+ return err;
+}
+
+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);
+}
+
+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[] = {
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_SPECIFIER_ID,
+ .vendor_id = 0x00022e,
+ .specifier_id = 0x00022e,
+ },
+ /* 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 000000000..2d028d2bd
--- /dev/null
+++ b/sound/firewire/tascam/tascam.h
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#ifndef SOUND_TASCAM_H_INCLUDED
+#define SOUND_TASCAM_H_INCLUDED
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
+#include <sound/rawmidi.h>
+
+#include "../lib.h"
+#include "../amdtp-stream.h"
+#include "../iso-resources.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;
+};
+
+#define TSCM_MIDI_IN_PORT_MAX 4
+#define TSCM_MIDI_OUT_PORT_MAX 4
+
+struct snd_tscm {
+ struct snd_card *card;
+ struct fw_unit *unit;
+
+ struct mutex mutex;
+ spinlock_t lock;
+
+ const 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;
+
+ int dev_lock_count;
+ bool dev_lock_changed;
+ wait_queue_head_t hwdep_wait;
+
+ /* 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];
+ bool on_sysex[TSCM_MIDI_OUT_PORT_MAX];
+
+ /* For control messages. */
+ struct snd_firewire_tascam_status *status;
+};
+
+#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
+
+#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 conviction. */
+#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
+
+#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_POWER 0x0404
+
+#define TSCM_OFFSET_MIDI_RX_QUAD 0x4000
+
+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);
+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_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);
+
+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);
+
+#endif