aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/sound/firewire
diff options
context:
space:
mode:
Diffstat (limited to 'sound/firewire')
-rw-r--r--sound/firewire/Kconfig35
-rw-r--r--sound/firewire/Makefile4
-rw-r--r--sound/firewire/amdtp-am824.c64
-rw-r--r--sound/firewire/amdtp-am824.h2
-rw-r--r--sound/firewire/amdtp-stream-trace.h13
-rw-r--r--sound/firewire/amdtp-stream.c1446
-rw-r--r--sound/firewire/amdtp-stream.h132
-rw-r--r--sound/firewire/bebob/Makefile2
-rw-r--r--sound/firewire/bebob/bebob.c290
-rw-r--r--sound/firewire/bebob/bebob.h24
-rw-r--r--sound/firewire/bebob/bebob_command.c36
-rw-r--r--sound/firewire/bebob/bebob_hwdep.c15
-rw-r--r--sound/firewire/bebob/bebob_midi.c6
-rw-r--r--sound/firewire/bebob/bebob_pcm.c5
-rw-r--r--sound/firewire/bebob/bebob_stream.c249
-rw-r--r--sound/firewire/cmp.c47
-rw-r--r--sound/firewire/cmp.h1
-rw-r--r--sound/firewire/dice/Makefile5
-rw-r--r--sound/firewire/dice/dice-alesis.c2
-rw-r--r--sound/firewire/dice/dice-focusrite.c23
-rw-r--r--sound/firewire/dice/dice-harman.c24
-rw-r--r--sound/firewire/dice/dice-hwdep.c4
-rw-r--r--sound/firewire/dice/dice-midi.c4
-rw-r--r--sound/firewire/dice/dice-pcm.c11
-rw-r--r--sound/firewire/dice/dice-presonus.c2
-rw-r--r--sound/firewire/dice/dice-stream.c57
-rw-r--r--sound/firewire/dice/dice-tcelectronic.c4
-rw-r--r--sound/firewire/dice/dice-transaction.c2
-rw-r--r--sound/firewire/dice/dice-weiss.c104
-rw-r--r--sound/firewire/dice/dice.c243
-rw-r--r--sound/firewire/dice/dice.h10
-rw-r--r--sound/firewire/digi00x/Makefile2
-rw-r--r--sound/firewire/digi00x/amdtp-dot.c37
-rw-r--r--sound/firewire/digi00x/digi00x-hwdep.c4
-rw-r--r--sound/firewire/digi00x/digi00x-midi.c14
-rw-r--r--sound/firewire/digi00x/digi00x-pcm.c5
-rw-r--r--sound/firewire/digi00x/digi00x-stream.c17
-rw-r--r--sound/firewire/digi00x/digi00x.c109
-rw-r--r--sound/firewire/digi00x/digi00x.h3
-rw-r--r--sound/firewire/fcp.c4
-rw-r--r--sound/firewire/fireface/Makefile2
-rw-r--r--sound/firewire/fireface/amdtp-ff.c30
-rw-r--r--sound/firewire/fireface/ff-hwdep.c45
-rw-r--r--sound/firewire/fireface/ff-midi.c4
-rw-r--r--sound/firewire/fireface/ff-pcm.c5
-rw-r--r--sound/firewire/fireface/ff-protocol-former.c198
-rw-r--r--sound/firewire/fireface/ff-protocol-latter.c128
-rw-r--r--sound/firewire/fireface/ff-stream.c13
-rw-r--r--sound/firewire/fireface/ff-transaction.c19
-rw-r--r--sound/firewire/fireface/ff.c110
-rw-r--r--sound/firewire/fireface/ff.h12
-rw-r--r--sound/firewire/fireworks/Makefile2
-rw-r--r--sound/firewire/fireworks/fireworks.c138
-rw-r--r--sound/firewire/fireworks/fireworks.h13
-rw-r--r--sound/firewire/fireworks/fireworks_hwdep.c5
-rw-r--r--sound/firewire/fireworks/fireworks_midi.c4
-rw-r--r--sound/firewire/fireworks/fireworks_pcm.c7
-rw-r--r--sound/firewire/fireworks/fireworks_stream.c29
-rw-r--r--sound/firewire/fireworks/fireworks_transaction.c4
-rw-r--r--sound/firewire/isight.c11
-rw-r--r--sound/firewire/lib.c34
-rw-r--r--sound/firewire/lib.h3
-rw-r--r--sound/firewire/motu/Makefile6
-rw-r--r--sound/firewire/motu/amdtp-motu.c199
-rw-r--r--sound/firewire/motu/motu-command-dsp-message-parser.c184
-rw-r--r--sound/firewire/motu/motu-hwdep.c129
-rw-r--r--sound/firewire/motu/motu-midi.c4
-rw-r--r--sound/firewire/motu/motu-pcm.c7
-rw-r--r--sound/firewire/motu/motu-protocol-v1.c467
-rw-r--r--sound/firewire/motu/motu-protocol-v2.c209
-rw-r--r--sound/firewire/motu/motu-protocol-v3.c142
-rw-r--r--sound/firewire/motu/motu-register-dsp-message-parser.c423
-rw-r--r--sound/firewire/motu/motu-stream.c44
-rw-r--r--sound/firewire/motu/motu.c114
-rw-r--r--sound/firewire/motu/motu.h74
-rw-r--r--sound/firewire/oxfw/Makefile2
-rw-r--r--sound/firewire/oxfw/oxfw-hwdep.c14
-rw-r--r--sound/firewire/oxfw/oxfw-midi.c6
-rw-r--r--sound/firewire/oxfw/oxfw-pcm.c11
-rw-r--r--sound/firewire/oxfw/oxfw-stream.c167
-rw-r--r--sound/firewire/oxfw/oxfw.c311
-rw-r--r--sound/firewire/oxfw/oxfw.h36
-rw-r--r--sound/firewire/tascam/Makefile2
-rw-r--r--sound/firewire/tascam/amdtp-tascam.c36
-rw-r--r--sound/firewire/tascam/tascam-hwdep.c4
-rw-r--r--sound/firewire/tascam/tascam-midi.c12
-rw-r--r--sound/firewire/tascam/tascam-pcm.c5
-rw-r--r--sound/firewire/tascam/tascam-stream.c28
-rw-r--r--sound/firewire/tascam/tascam-transaction.c2
-rw-r--r--sound/firewire/tascam/tascam.c100
-rw-r--r--sound/firewire/tascam/tascam.h3
91 files changed, 4422 insertions, 2196 deletions
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 25778765cbfe..5973c25c2add 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -18,8 +18,25 @@ config SND_DICE
select SND_HWDEP
select SND_FIREWIRE_LIB
help
- Say Y here to include support for many DACs based on the DICE
- chip family (DICE-II/Jr/Mini) which TC Applied Technologies produces.
+ Say Y here to include support for devices based on the DICE chip family
+ (DICE-II/TCD2210(Mini)/TCD2220(Jr.)) which TC Applied Technologies (TCAT) produced.
+ * Allen and Heath Zed R16
+ * Alesis iO 14/26 FireWire, MasterControl, MultiMix 8/12/16 FireWire
+ * Avid Mbox 3 Pro
+ * FlexRadio Systems FLEX-3000, FLEX-5000
+ * Focusrite Liquid Saffire 56
+ * Focusrite Saffire Pro 14, Pro 24, Pro 24 DSP, Pro 26, Pro 40(TCD2220)
+ * Harman Music Group Lexicon I-ONIX FW810S
+ * Loud Technologies Mackie Onyx Blackbird, Onyx 820i/1220i/1620i/1640i (latter models)
+ * M-Audio ProFire 610/2626
+ * Mytek Stereo192-DSD DAC
+ * Midas Klark Teknik VeniceF series
+ * PreSonus FireStudio, FireStudio Mobile, FireStudio Project, FireStudio Tube
+ * PreSonus StudioLive 16.4.2, 16.0.2, 24.4.2, 32.4.2
+ * Solid State Logic Duende Classic, Duende Mini
+ * TC Electronic Studio Konnekt 48, Konnekt 24D, Konnekt Live, Impact Twin
+ * TC Electronic Digital Konnekt x32, Desktop Konnekt 6
+ * Weiss Engineering ADC2, Vesta, Minerva, AFI1, DAC1, INT202, DAC202
To compile this driver as a module, choose M here: the module
will be called snd-dice.
@@ -38,7 +55,7 @@ config SND_OXFW
* Mackie(Loud) Onyx 1640i (former model)
* Mackie(Loud) Onyx Satellite
* Mackie(Loud) Tapco Link.Firewire
- * Mackie(Loud) d.2 pro/d.4 pro
+ * Mackie(Loud) d.2 pro/d.4 pro (built-in FireWire card with OXFW971 ASIC)
* Mackie(Loud) U.420/U.420d
* TASCAM FireOne
* Stanton Controllers & Systems 1 Deck/Mixer
@@ -84,7 +101,7 @@ config SND_BEBOB
* PreSonus FIREBOX/FIREPOD/FP10/Inspire1394
* BridgeCo RDAudio1/Audio5
* Mackie Onyx 1220/1620/1640 (FireWire I/O Card)
- * Mackie d.2 (FireWire Option)
+ * Mackie d.2 (optional FireWire card with DM1000 ASIC)
* Stanton FinalScratch 2 (ScratchAmp)
* Tascam IF-FW/DM
* Behringer XENIX UFX 1204/1604
@@ -110,6 +127,7 @@ config SND_BEBOB
* M-Audio Ozonic/NRV10/ProfireLightBridge
* M-Audio FireWire 1814/ProjectMix IO
* Digidesign Mbox 2 Pro
+ * ToneWeal FW66
To compile this driver as a module, choose M here: the module
will be called snd-bebob.
@@ -148,13 +166,22 @@ config SND_FIREWIRE_MOTU
select SND_HWDEP
help
Say Y here to enable support for FireWire devices which MOTU produced:
+ * 828
+ * 896
* 828mk2
+ * 896hd
* Traveler
* Ultralite
* 8pre
* 828mk3 (FireWire only)
* 828mk3 (Hybrid)
+ * 896mk3 (FireWire only)
+ * 896mk3 (Hybrid)
+ * Ultralite mk3 (FireWire only)
+ * Ultralite mk3 (Hybrid)
+ * Traveler mk3
* Audio Express
+ * Track 16
* 4pre
To compile this driver as a module, choose M here: the module
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index 44a7b510b75b..45018a5c224f 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -2,9 +2,9 @@
# To find a header included by define_trace.h.
CFLAGS_amdtp-stream.o := -I$(src)
-snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
+snd-firewire-lib-y := lib.o iso-resources.o packets-buffer.o \
fcp.o cmp.o amdtp-stream.o amdtp-am824.o
-snd-isight-objs := isight.o
+snd-isight-y := isight.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
obj-$(CONFIG_SND_DICE) += dice/
diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c
index fea92e148790..3660c312bf33 100644
--- a/sound/firewire/amdtp-am824.c
+++ b/sound/firewire/amdtp-am824.c
@@ -36,8 +36,6 @@ struct amdtp_am824 {
u8 pcm_positions[AM824_MAX_CHANNELS_FOR_PCM];
u8 midi_position;
-
- unsigned int frame_multiplier;
};
/**
@@ -59,8 +57,8 @@ int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate,
{
struct amdtp_am824 *p = s->protocol;
unsigned int midi_channels;
- unsigned int i;
- int err;
+ unsigned int pcm_frame_multiplier;
+ int i, err;
if (amdtp_stream_running(s))
return -EINVAL;
@@ -77,8 +75,18 @@ int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate,
WARN_ON(midi_channels > AM824_MAX_CHANNELS_FOR_MIDI))
return -EINVAL;
- err = amdtp_stream_set_parameters(s, rate,
- pcm_channels + midi_channels);
+ /*
+ * 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)
+ pcm_frame_multiplier = 2;
+ else
+ pcm_frame_multiplier = 1;
+
+ err = amdtp_stream_set_parameters(s, rate, pcm_channels + midi_channels,
+ pcm_frame_multiplier);
if (err < 0)
return err;
@@ -88,16 +96,6 @@ int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate,
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;
@@ -346,23 +344,20 @@ static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
}
}
-static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_it_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
struct amdtp_am824 *p = s->protocol;
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
if (pcm) {
write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames);
- pcm_frames += data_blocks * p->frame_multiplier;
+ pcm_frames += data_blocks * s->pcm_frame_multiplier;
} else {
write_pcm_silence(s, buf, data_blocks);
}
@@ -371,37 +366,34 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
write_midi_messages(s, buf, data_blocks,
desc->data_block_counter);
}
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
-static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_ir_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
struct amdtp_am824 *p = s->protocol;
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
if (pcm) {
read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames);
- pcm_frames += data_blocks * p->frame_multiplier;
+ pcm_frames += data_blocks * s->pcm_frame_multiplier;
}
if (p->midi_ports) {
read_midi_messages(s, buf, data_blocks,
desc->data_block_counter);
}
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
/**
@@ -410,10 +402,10 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
* @s: the AMDTP stream to initialize
* @unit: the target of the stream
* @dir: the direction of stream
- * @flags: the packet transmission method to use
+ * @flags: the details of the streaming protocol consist of cip_flags enumeration-constants.
*/
int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit,
- enum amdtp_stream_direction dir, enum cip_flags flags)
+ enum amdtp_stream_direction dir, unsigned int flags)
{
amdtp_stream_process_ctx_payloads_t process_ctx_payloads;
diff --git a/sound/firewire/amdtp-am824.h b/sound/firewire/amdtp-am824.h
index 06d280783581..2b092b1061ba 100644
--- a/sound/firewire/amdtp-am824.h
+++ b/sound/firewire/amdtp-am824.h
@@ -45,5 +45,5 @@ 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);
+ enum amdtp_stream_direction dir, unsigned int flags);
#endif
diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h
index 26e7cb555d3c..208f97cf8de6 100644
--- a/sound/firewire/amdtp-stream-trace.h
+++ b/sound/firewire/amdtp-stream-trace.h
@@ -14,9 +14,10 @@
#include <linux/tracepoint.h>
TRACE_EVENT(amdtp_packet,
- TP_PROTO(const struct amdtp_stream *s, u32 cycles, const __be32 *cip_header, unsigned int payload_length, unsigned int data_blocks, unsigned int data_block_counter, unsigned int index),
- TP_ARGS(s, cycles, cip_header, payload_length, data_blocks, data_block_counter, index),
+ TP_PROTO(const struct amdtp_stream *s, u32 cycles, const __be32 *cip_header, unsigned int payload_length, unsigned int data_blocks, unsigned int data_block_counter, unsigned int packet_index, unsigned int index, u32 curr_cycle_time),
+ TP_ARGS(s, cycles, cip_header, payload_length, data_blocks, data_block_counter, packet_index, index, curr_cycle_time),
TP_STRUCT__entry(
+ __field(unsigned int, cycle_time)
__field(unsigned int, second)
__field(unsigned int, cycle)
__field(int, channel)
@@ -31,6 +32,7 @@ TRACE_EVENT(amdtp_packet,
__field(unsigned int, index)
),
TP_fast_assign(
+ __entry->cycle_time = curr_cycle_time;
__entry->second = cycles / CYCLES_PER_SECOND;
__entry->cycle = cycles % CYCLES_PER_SECOND;
__entry->channel = s->context->channel;
@@ -48,12 +50,13 @@ TRACE_EVENT(amdtp_packet,
__entry->payload_quadlets = payload_length / sizeof(__be32);
__entry->data_blocks = data_blocks;
__entry->data_block_counter = data_block_counter,
- __entry->packet_index = s->packet_index;
- __entry->irq = !!in_interrupt();
+ __entry->packet_index = packet_index;
+ __entry->irq = !!in_softirq();
__entry->index = index;
),
TP_printk(
- "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u %s",
+ "%08x %02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u %s",
+ __entry->cycle_time,
__entry->second,
__entry->cycle,
__entry->src,
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c
index ee1c428b1fd3..7fc51f829ecc 100644
--- a/sound/firewire/amdtp-stream.c
+++ b/sound/firewire/amdtp-stream.c
@@ -20,7 +20,7 @@
#define CYCLES_PER_SECOND 8000
#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
-#define OHCI_MAX_SECOND 8
+#define OHCI_SECOND_MODULUS 8
/* Always support Linux tracing subsystem. */
#define CREATE_TRACE_POINTS
@@ -33,7 +33,8 @@
#define TAG_NO_CIP_HEADER 0
#define TAG_CIP 1
-/* common isochronous packet header parameters */
+// Common Isochronous Packet (CIP) header parameters. Use two quadlets CIP header when supported.
+#define CIP_HEADER_QUADLETS 2
#define CIP_EOH_SHIFT 31
#define CIP_EOH (1u << CIP_EOH_SHIFT)
#define CIP_EOH_MASK 0x80000000
@@ -48,36 +49,48 @@
#define CIP_FMT_MASK 0x3f000000
#define CIP_FDF_MASK 0x00ff0000
#define CIP_FDF_SHIFT 16
+#define CIP_FDF_NO_DATA 0xff
#define CIP_SYT_MASK 0x0000ffff
#define CIP_SYT_NO_INFO 0xffff
+#define CIP_SYT_CYCLE_MODULUS 16
+#define CIP_NO_DATA ((CIP_FDF_NO_DATA << CIP_FDF_SHIFT) | CIP_SYT_NO_INFO)
+
+#define CIP_HEADER_SIZE (sizeof(__be32) * CIP_HEADER_QUADLETS)
/* Audio and Music transfer protocol specific parameters */
#define CIP_FMT_AM 0x10
#define AMDTP_FDF_NO_DATA 0xff
-// For iso header, tstamp and 2 CIP header.
-#define IR_CTX_HEADER_SIZE_CIP 16
// For iso header and tstamp.
-#define IR_CTX_HEADER_SIZE_NO_CIP 8
+#define IR_CTX_HEADER_DEFAULT_QUADLETS 2
+// Add nothing.
+#define IR_CTX_HEADER_SIZE_NO_CIP (sizeof(__be32) * IR_CTX_HEADER_DEFAULT_QUADLETS)
+// Add two quadlets CIP header.
+#define IR_CTX_HEADER_SIZE_CIP (IR_CTX_HEADER_SIZE_NO_CIP + CIP_HEADER_SIZE)
#define HEADER_TSTAMP_MASK 0x0000ffff
-#define IT_PKT_HEADER_SIZE_CIP 8 // For 2 CIP header.
+#define IT_PKT_HEADER_SIZE_CIP CIP_HEADER_SIZE
#define IT_PKT_HEADER_SIZE_NO_CIP 0 // Nothing.
-static void pcm_period_tasklet(struct tasklet_struct *t);
+// The initial firmware of OXFW970 can postpone transmission of packet during finishing
+// asynchronous transaction. This module accepts 5 cycles to skip as maximum to avoid buffer
+// overrun. Actual device can skip more, then this module stops the packet streaming.
+#define IR_JUMBO_PAYLOAD_MAX_SKIP_CYCLES 5
+
+static void pcm_period_work(struct work_struct *work);
/**
* amdtp_stream_init - initialize an AMDTP stream structure
* @s: the AMDTP stream to initialize
* @unit: the target of the stream
* @dir: the direction of stream
- * @flags: the packet transmission method to use
+ * @flags: the details of the streaming protocol consist of cip_flags enumeration-constants.
* @fmt: the value of fmt field in CIP header
* @process_ctx_payloads: callback handler to process payloads of isoc context
* @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, unsigned int flags,
unsigned int fmt,
amdtp_stream_process_ctx_payloads_t process_ctx_payloads,
unsigned int protocol_size)
@@ -94,18 +107,14 @@ int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
s->flags = flags;
s->context = ERR_PTR(-1);
mutex_init(&s->mutex);
- tasklet_setup(&s->period_tasklet, pcm_period_tasklet);
+ INIT_WORK(&s->period_work, pcm_period_work);
s->packet_index = 0;
- init_waitqueue_head(&s->callback_wait);
- s->callbacked = false;
+ init_waitqueue_head(&s->ready_wait);
s->fmt = fmt;
s->process_ctx_payloads = process_ctx_payloads;
- if (dir == AMDTP_OUT_STREAM)
- s->ctx_data.rx.syt_override = -1;
-
return 0;
}
EXPORT_SYMBOL(amdtp_stream_init);
@@ -163,6 +172,9 @@ static int apply_constraint_to_size(struct snd_pcm_hw_params *params,
step = max(step, amdtp_syt_intervals[i]);
}
+ if (step == 0)
+ return -EINVAL;
+
t.min = roundup(s->min, step);
t.max = rounddown(s->max, step);
t.integer = 1;
@@ -183,14 +195,13 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
unsigned int maximum_usec_per_period;
int err;
- hw->info = SNDRV_PCM_INFO_BATCH |
- SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ hw->info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_JOINT_DUPLEX |
SNDRV_PCM_INFO_MMAP |
- SNDRV_PCM_INFO_MMAP_VALID;
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP;
- /* SNDRV_PCM_INFO_BATCH */
hw->periods_min = 2;
hw->periods_max = UINT_MAX;
@@ -203,7 +214,7 @@ int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
// Linux driver for 1394 OHCI controller voluntarily flushes isoc
// context when total size of accumulated context header reaches
- // PAGE_SIZE. This kicks tasklet for the isoc context and brings
+ // PAGE_SIZE. This kicks work for the isoc context and brings
// callback in the middle of scheduled interrupts.
// Although AMDTP streams in the same domain use the same events per
// IRQ, use the largest size of context header between IT/IR contexts.
@@ -266,12 +277,14 @@ EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints);
* @s: the AMDTP stream to configure
* @rate: the sample rate
* @data_block_quadlets: the size of a data block in quadlet unit
+ * @pcm_frame_multiplier: the multiplier to compute the number of PCM frames by the number of AMDTP
+ * events.
*
* The parameters must be set before the stream is started, and must not be
* changed while the stream is running.
*/
int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate,
- unsigned int data_block_quadlets)
+ unsigned int data_block_quadlets, unsigned int pcm_frame_multiplier)
{
unsigned int sfc;
@@ -287,22 +300,31 @@ int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate,
s->syt_interval = amdtp_syt_intervals[sfc];
// default buffering in the device.
- if (s->direction == AMDTP_OUT_STREAM) {
- s->ctx_data.rx.transfer_delay =
- TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE;
-
- if (s->flags & CIP_BLOCKING) {
- // additional buffering needed to adjust for no-data
- // packets.
- s->ctx_data.rx.transfer_delay +=
- TICKS_PER_SECOND * s->syt_interval / rate;
- }
- }
+ s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE;
+
+ // additional buffering needed to adjust for no-data packets.
+ if (s->flags & CIP_BLOCKING)
+ s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate;
+
+ s->pcm_frame_multiplier = pcm_frame_multiplier;
return 0;
}
EXPORT_SYMBOL(amdtp_stream_set_parameters);
+// The CIP header is processed in context header apart from context payload.
+static int amdtp_stream_get_max_ctx_payload_size(struct amdtp_stream *s)
+{
+ unsigned int multiplier;
+
+ if (s->flags & CIP_JUMBO_PAYLOAD)
+ multiplier = IR_JUMBO_PAYLOAD_MAX_SKIP_CYCLES;
+ else
+ multiplier = 1;
+
+ return s->syt_interval * s->data_block_quadlets * sizeof(__be32) * multiplier;
+}
+
/**
* amdtp_stream_get_max_payload - get the stream's packet size
* @s: the AMDTP stream
@@ -312,16 +334,14 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters);
*/
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
{
- unsigned int multiplier = 1;
- unsigned int cip_header_size = 0;
+ unsigned int cip_header_size;
- if (s->flags & CIP_JUMBO_PAYLOAD)
- multiplier = 5;
if (!(s->flags & CIP_NO_HEADER))
- cip_header_size = sizeof(__be32) * 2;
+ cip_header_size = CIP_HEADER_SIZE;
+ else
+ cip_header_size = 0;
- return cip_header_size +
- s->syt_interval * s->data_block_quadlets * sizeof(__be32) * multiplier;
+ return cip_header_size + amdtp_stream_get_max_ctx_payload_size(s);
}
EXPORT_SYMBOL(amdtp_stream_get_max_payload);
@@ -333,32 +353,49 @@ EXPORT_SYMBOL(amdtp_stream_get_max_payload);
*/
void amdtp_stream_pcm_prepare(struct amdtp_stream *s)
{
- tasklet_kill(&s->period_tasklet);
+ cancel_work_sync(&s->period_work);
s->pcm_buffer_pointer = 0;
s->pcm_period_pointer = 0;
}
EXPORT_SYMBOL(amdtp_stream_pcm_prepare);
-static unsigned int calculate_data_blocks(unsigned int *data_block_state,
- bool is_blocking, bool is_no_info,
- unsigned int syt_interval, enum cip_sfc sfc)
+#define prev_packet_desc(s, desc) \
+ list_prev_entry_circular(desc, &s->packet_descs_list, link)
+
+static void pool_blocking_data_blocks(struct amdtp_stream *s, struct seq_desc *descs,
+ unsigned int size, unsigned int pos, unsigned int count)
{
- unsigned int data_blocks;
+ const unsigned int syt_interval = s->syt_interval;
+ int i;
+
+ for (i = 0; i < count; ++i) {
+ struct seq_desc *desc = descs + pos;
- /* Blocking mode. */
- if (is_blocking) {
- /* This module generate empty packet for 'no data'. */
- if (is_no_info)
- data_blocks = 0;
+ if (desc->syt_offset != CIP_SYT_NO_INFO)
+ desc->data_blocks = syt_interval;
else
- data_blocks = syt_interval;
- /* Non-blocking mode. */
- } else {
+ desc->data_blocks = 0;
+
+ pos = (pos + 1) % size;
+ }
+}
+
+static void pool_ideal_nonblocking_data_blocks(struct amdtp_stream *s, struct seq_desc *descs,
+ unsigned int size, unsigned int pos,
+ unsigned int count)
+{
+ const enum cip_sfc sfc = s->sfc;
+ unsigned int state = s->ctx_data.rx.data_block_state;
+ int i;
+
+ for (i = 0; i < count; ++i) {
+ struct seq_desc *desc = descs + pos;
+
if (!cip_sfc_is_base_44100(sfc)) {
// Sample_rate / 8000 is an integer, and precomputed.
- data_blocks = *data_block_state;
+ desc->data_blocks = state;
} else {
- unsigned int phase = *data_block_state;
+ unsigned int phase = state;
/*
* This calculates the number of data blocks per packet so that
@@ -370,18 +407,19 @@ static unsigned int calculate_data_blocks(unsigned int *data_block_state,
*/
if (sfc == CIP_SFC_44100)
/* 6 6 5 6 5 6 5 ... */
- data_blocks = 5 + ((phase & 1) ^
- (phase == 0 || phase >= 40));
+ desc->data_blocks = 5 + ((phase & 1) ^ (phase == 0 || phase >= 40));
else
/* 12 11 11 11 11 ... or 23 22 22 22 22 ... */
- data_blocks = 11 * (sfc >> 1) + (phase == 0);
+ desc->data_blocks = 11 * (sfc >> 1) + (phase == 0);
if (++phase >= (80 >> (sfc >> 1)))
phase = 0;
- *data_block_state = phase;
+ state = phase;
}
+
+ pos = (pos + 1) % size;
}
- return data_blocks;
+ s->ctx_data.rx.data_block_state = state;
}
static unsigned int calculate_syt_offset(unsigned int *last_syt_offset,
@@ -423,6 +461,145 @@ static unsigned int calculate_syt_offset(unsigned int *last_syt_offset,
return syt_offset;
}
+static void pool_ideal_syt_offsets(struct amdtp_stream *s, struct seq_desc *descs,
+ unsigned int size, unsigned int pos, unsigned int count)
+{
+ const enum cip_sfc sfc = s->sfc;
+ unsigned int last = s->ctx_data.rx.last_syt_offset;
+ unsigned int state = s->ctx_data.rx.syt_offset_state;
+ int i;
+
+ for (i = 0; i < count; ++i) {
+ struct seq_desc *desc = descs + pos;
+
+ desc->syt_offset = calculate_syt_offset(&last, &state, sfc);
+
+ pos = (pos + 1) % size;
+ }
+
+ s->ctx_data.rx.last_syt_offset = last;
+ s->ctx_data.rx.syt_offset_state = state;
+}
+
+static unsigned int compute_syt_offset(unsigned int syt, unsigned int cycle,
+ unsigned int transfer_delay)
+{
+ unsigned int cycle_lo = (cycle % CYCLES_PER_SECOND) & 0x0f;
+ unsigned int syt_cycle_lo = (syt & 0xf000) >> 12;
+ unsigned int syt_offset;
+
+ // Round up.
+ if (syt_cycle_lo < cycle_lo)
+ syt_cycle_lo += CIP_SYT_CYCLE_MODULUS;
+ syt_cycle_lo -= cycle_lo;
+
+ // Subtract transfer delay so that the synchronization offset is not so large
+ // at transmission.
+ syt_offset = syt_cycle_lo * TICKS_PER_CYCLE + (syt & 0x0fff);
+ if (syt_offset < transfer_delay)
+ syt_offset += CIP_SYT_CYCLE_MODULUS * TICKS_PER_CYCLE;
+
+ return syt_offset - transfer_delay;
+}
+
+// Both of the producer and consumer of the queue runs in the same clock of IEEE 1394 bus.
+// Additionally, the sequence of tx packets is severely checked against any discontinuity
+// before filling entries in the queue. The calculation is safe even if it looks fragile by
+// overrun.
+static unsigned int calculate_cached_cycle_count(struct amdtp_stream *s, unsigned int head)
+{
+ const unsigned int cache_size = s->ctx_data.tx.cache.size;
+ unsigned int cycles = s->ctx_data.tx.cache.pos;
+
+ if (cycles < head)
+ cycles += cache_size;
+ cycles -= head;
+
+ return cycles;
+}
+
+static void cache_seq(struct amdtp_stream *s, const struct pkt_desc *src, unsigned int desc_count)
+{
+ const unsigned int transfer_delay = s->transfer_delay;
+ const unsigned int cache_size = s->ctx_data.tx.cache.size;
+ struct seq_desc *cache = s->ctx_data.tx.cache.descs;
+ unsigned int cache_pos = s->ctx_data.tx.cache.pos;
+ bool aware_syt = !(s->flags & CIP_UNAWARE_SYT);
+ int i;
+
+ for (i = 0; i < desc_count; ++i) {
+ struct seq_desc *dst = cache + cache_pos;
+
+ if (aware_syt && src->syt != CIP_SYT_NO_INFO)
+ dst->syt_offset = compute_syt_offset(src->syt, src->cycle, transfer_delay);
+ else
+ dst->syt_offset = CIP_SYT_NO_INFO;
+ dst->data_blocks = src->data_blocks;
+
+ cache_pos = (cache_pos + 1) % cache_size;
+ src = amdtp_stream_next_packet_desc(s, src);
+ }
+
+ s->ctx_data.tx.cache.pos = cache_pos;
+}
+
+static void pool_ideal_seq_descs(struct amdtp_stream *s, struct seq_desc *descs, unsigned int size,
+ unsigned int pos, unsigned int count)
+{
+ pool_ideal_syt_offsets(s, descs, size, pos, count);
+
+ if (s->flags & CIP_BLOCKING)
+ pool_blocking_data_blocks(s, descs, size, pos, count);
+ else
+ pool_ideal_nonblocking_data_blocks(s, descs, size, pos, count);
+}
+
+static void pool_replayed_seq(struct amdtp_stream *s, struct seq_desc *descs, unsigned int size,
+ unsigned int pos, unsigned int count)
+{
+ struct amdtp_stream *target = s->ctx_data.rx.replay_target;
+ const struct seq_desc *cache = target->ctx_data.tx.cache.descs;
+ const unsigned int cache_size = target->ctx_data.tx.cache.size;
+ unsigned int cache_pos = s->ctx_data.rx.cache_pos;
+ int i;
+
+ for (i = 0; i < count; ++i) {
+ descs[pos] = cache[cache_pos];
+ cache_pos = (cache_pos + 1) % cache_size;
+ pos = (pos + 1) % size;
+ }
+
+ s->ctx_data.rx.cache_pos = cache_pos;
+}
+
+static void pool_seq_descs(struct amdtp_stream *s, struct seq_desc *descs, unsigned int size,
+ unsigned int pos, unsigned int count)
+{
+ struct amdtp_domain *d = s->domain;
+ void (*pool_seq_descs)(struct amdtp_stream *s, struct seq_desc *descs, unsigned int size,
+ unsigned int pos, unsigned int count);
+
+ if (!d->replay.enable || !s->ctx_data.rx.replay_target) {
+ pool_seq_descs = pool_ideal_seq_descs;
+ } else {
+ if (!d->replay.on_the_fly) {
+ pool_seq_descs = pool_replayed_seq;
+ } else {
+ struct amdtp_stream *tx = s->ctx_data.rx.replay_target;
+ const unsigned int cache_size = tx->ctx_data.tx.cache.size;
+ const unsigned int cache_pos = s->ctx_data.rx.cache_pos;
+ unsigned int cached_cycles = calculate_cached_cycle_count(tx, cache_pos);
+
+ if (cached_cycles > count && cached_cycles > cache_size / 2)
+ pool_seq_descs = pool_replayed_seq;
+ else
+ pool_seq_descs = pool_ideal_seq_descs;
+ }
+ }
+
+ pool_seq_descs(s, descs, size, pos, count);
+}
+
static void update_pcm_pointers(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
unsigned int frames)
@@ -437,13 +614,35 @@ static void update_pcm_pointers(struct amdtp_stream *s,
s->pcm_period_pointer += frames;
if (s->pcm_period_pointer >= pcm->runtime->period_size) {
s->pcm_period_pointer -= pcm->runtime->period_size;
- tasklet_hi_schedule(&s->period_tasklet);
+
+ // The program in user process should periodically check the status of intermediate
+ // buffer associated to PCM substream to process PCM frames in the buffer, instead
+ // of receiving notification of period elapsed by poll wait.
+ //
+ // Use another work item for period elapsed event to prevent the following AB/BA
+ // deadlock:
+ //
+ // thread 1 thread 2
+ // ================================= =================================
+ // A.work item (process) pcm ioctl (process)
+ // v v
+ // process_rx_packets() B.PCM stream lock
+ // process_tx_packets() v
+ // v callbacks in snd_pcm_ops
+ // update_pcm_pointers() v
+ // snd_pcm_elapsed() fw_iso_context_flush_completions()
+ // snd_pcm_stream_lock_irqsave() disable_work_sync()
+ // v v
+ // wait until release of B wait until A exits
+ if (!pcm->runtime->no_period_wakeup)
+ queue_work(system_highpri_wq, &s->period_work);
}
}
-static void pcm_period_tasklet(struct tasklet_struct *t)
+static void pcm_period_work(struct work_struct *work)
{
- struct amdtp_stream *s = from_tasklet(s, t, period_tasklet);
+ struct amdtp_stream *s = container_of(work, struct amdtp_stream,
+ period_work);
struct snd_pcm_substream *pcm = READ_ONCE(s->pcm);
if (pcm)
@@ -504,10 +703,10 @@ static void generate_cip_header(struct amdtp_stream *s, __be32 cip_header[2],
}
static void build_it_pkt_header(struct amdtp_stream *s, unsigned int cycle,
- struct fw_iso_packet *params,
+ struct fw_iso_packet *params, unsigned int header_length,
unsigned int data_blocks,
unsigned int data_block_counter,
- unsigned int syt, unsigned int index)
+ unsigned int syt, unsigned int index, u32 curr_cycle_time)
{
unsigned int payload_length;
__be32 *cip_header;
@@ -515,17 +714,16 @@ static void build_it_pkt_header(struct amdtp_stream *s, unsigned int cycle,
payload_length = data_blocks * sizeof(__be32) * s->data_block_quadlets;
params->payload_length = payload_length;
- if (!(s->flags & CIP_NO_HEADER)) {
+ if (header_length > 0) {
cip_header = (__be32 *)params->header;
generate_cip_header(s, cip_header, data_block_counter, syt);
- params->header_length = 2 * sizeof(__be32);
- payload_length += params->header_length;
+ params->header_length = header_length;
} else {
cip_header = NULL;
}
- trace_amdtp_packet(s, cycle, cip_header, payload_length, data_blocks,
- data_block_counter, index);
+ trace_amdtp_packet(s, cycle, cip_header, payload_length + header_length, data_blocks,
+ data_block_counter, s->packet_index, index, curr_cycle_time);
}
static int check_cip_header(struct amdtp_stream *s, const __be32 *buf,
@@ -568,8 +766,7 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf,
/* Calculate data blocks */
fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT;
- if (payload_length < sizeof(__be32) * 2 ||
- (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
+ if (payload_length == 0 || (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) {
*data_blocks = 0;
} else {
unsigned int data_block_quadlets =
@@ -584,8 +781,7 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf,
if (s->flags & CIP_WRONG_DBS)
data_block_quadlets = s->data_block_quadlets;
- *data_blocks = (payload_length / sizeof(__be32) - 2) /
- data_block_quadlets;
+ *data_blocks = payload_length / sizeof(__be32) / data_block_quadlets;
}
/* Check data block counter continuity */
@@ -602,10 +798,14 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf,
} else {
unsigned int dbc_interval;
- if (*data_blocks > 0 && s->ctx_data.tx.dbc_interval > 0)
- dbc_interval = s->ctx_data.tx.dbc_interval;
- else
- dbc_interval = *data_blocks;
+ if (!(s->flags & CIP_DBC_IS_PAYLOAD_QUADLETS)) {
+ if (*data_blocks > 0 && s->ctx_data.tx.dbc_interval > 0)
+ dbc_interval = s->ctx_data.tx.dbc_interval;
+ else
+ dbc_interval = *data_blocks;
+ } else {
+ dbc_interval = payload_length / sizeof(__be32);
+ }
lost = dbc != ((*data_block_counter + dbc_interval) & 0xff);
}
@@ -619,102 +819,178 @@ static int check_cip_header(struct amdtp_stream *s, const __be32 *buf,
*data_block_counter = dbc;
- *syt = cip_header[1] & CIP_SYT_MASK;
+ if (!(s->flags & CIP_UNAWARE_SYT))
+ *syt = cip_header[1] & CIP_SYT_MASK;
return 0;
}
static int parse_ir_ctx_header(struct amdtp_stream *s, unsigned int cycle,
const __be32 *ctx_header,
- unsigned int *payload_length,
unsigned int *data_blocks,
unsigned int *data_block_counter,
- unsigned int *syt, unsigned int index)
+ unsigned int *syt, unsigned int packet_index, unsigned int index,
+ u32 curr_cycle_time)
{
+ unsigned int payload_length;
const __be32 *cip_header;
- int err;
+ unsigned int cip_header_size;
+
+ payload_length = be32_to_cpu(ctx_header[0]) >> ISO_DATA_LENGTH_SHIFT;
+
+ if (!(s->flags & CIP_NO_HEADER))
+ cip_header_size = CIP_HEADER_SIZE;
+ else
+ cip_header_size = 0;
- *payload_length = be32_to_cpu(ctx_header[0]) >> ISO_DATA_LENGTH_SHIFT;
- if (*payload_length > s->ctx_data.tx.ctx_header_size +
- s->ctx_data.tx.max_ctx_payload_length) {
+ if (payload_length > cip_header_size + s->ctx_data.tx.max_ctx_payload_length) {
dev_err(&s->unit->device,
"Detect jumbo payload: %04x %04x\n",
- *payload_length, s->ctx_data.tx.max_ctx_payload_length);
+ payload_length, cip_header_size + s->ctx_data.tx.max_ctx_payload_length);
return -EIO;
}
- if (!(s->flags & CIP_NO_HEADER)) {
- cip_header = ctx_header + 2;
- err = check_cip_header(s, cip_header, *payload_length,
- data_blocks, data_block_counter, syt);
- if (err < 0)
- return err;
+ if (cip_header_size > 0) {
+ if (payload_length >= cip_header_size) {
+ int err;
+
+ cip_header = ctx_header + IR_CTX_HEADER_DEFAULT_QUADLETS;
+ err = check_cip_header(s, cip_header, payload_length - cip_header_size,
+ data_blocks, data_block_counter, syt);
+ if (err < 0)
+ return err;
+ } else {
+ // Handle the cycle so that empty packet arrives.
+ cip_header = NULL;
+ *data_blocks = 0;
+ *syt = 0;
+ }
} else {
cip_header = NULL;
- err = 0;
- *data_blocks = *payload_length / sizeof(__be32) /
- s->data_block_quadlets;
+ *data_blocks = payload_length / sizeof(__be32) / s->data_block_quadlets;
*syt = 0;
if (*data_block_counter == UINT_MAX)
*data_block_counter = 0;
}
- trace_amdtp_packet(s, cycle, cip_header, *payload_length, *data_blocks,
- *data_block_counter, index);
+ trace_amdtp_packet(s, cycle, cip_header, payload_length, *data_blocks,
+ *data_block_counter, packet_index, index, curr_cycle_time);
- return err;
+ return 0;
}
// In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On
// the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent
// it. Thus, via Linux firewire subsystem, we can get the 3 bits for second.
-static inline u32 compute_cycle_count(__be32 ctx_header_tstamp)
+static inline u32 compute_ohci_iso_ctx_cycle_count(u32 tstamp)
+{
+ return (((tstamp >> 13) & 0x07) * CYCLES_PER_SECOND) + (tstamp & 0x1fff);
+}
+
+static inline u32 compute_ohci_cycle_count(__be32 ctx_header_tstamp)
{
u32 tstamp = be32_to_cpu(ctx_header_tstamp) & HEADER_TSTAMP_MASK;
- return (((tstamp >> 13) & 0x07) * 8000) + (tstamp & 0x1fff);
+ return compute_ohci_iso_ctx_cycle_count(tstamp);
}
-static inline u32 increment_cycle_count(u32 cycle, unsigned int addend)
+static inline u32 increment_ohci_cycle_count(u32 cycle, unsigned int addend)
{
cycle += addend;
- if (cycle >= OHCI_MAX_SECOND * CYCLES_PER_SECOND)
- cycle -= OHCI_MAX_SECOND * CYCLES_PER_SECOND;
+ if (cycle >= OHCI_SECOND_MODULUS * CYCLES_PER_SECOND)
+ cycle -= OHCI_SECOND_MODULUS * CYCLES_PER_SECOND;
return cycle;
}
+static inline u32 decrement_ohci_cycle_count(u32 minuend, u32 subtrahend)
+{
+ if (minuend < subtrahend)
+ minuend += OHCI_SECOND_MODULUS * CYCLES_PER_SECOND;
+
+ return minuend - subtrahend;
+}
+
+static int compare_ohci_cycle_count(u32 lval, u32 rval)
+{
+ if (lval == rval)
+ return 0;
+ else if (lval < rval && rval - lval < OHCI_SECOND_MODULUS * CYCLES_PER_SECOND / 2)
+ return -1;
+ else
+ return 1;
+}
+
// Align to actual cycle count for the packet which is going to be scheduled.
// This module queued the same number of isochronous cycle as the size of queue
// to kip isochronous cycle, therefore it's OK to just increment the cycle by
// the size of queue for scheduled cycle.
-static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp,
- unsigned int queue_size)
+static inline u32 compute_ohci_it_cycle(const __be32 ctx_header_tstamp,
+ unsigned int queue_size)
{
- u32 cycle = compute_cycle_count(ctx_header_tstamp);
- return increment_cycle_count(cycle, queue_size);
+ u32 cycle = compute_ohci_cycle_count(ctx_header_tstamp);
+ return increment_ohci_cycle_count(cycle, queue_size);
}
-static int generate_device_pkt_descs(struct amdtp_stream *s,
- struct pkt_desc *descs,
- const __be32 *ctx_header,
- unsigned int packets)
+static int generate_tx_packet_descs(struct amdtp_stream *s, struct pkt_desc *desc,
+ const __be32 *ctx_header, unsigned int packet_count,
+ unsigned int *desc_count)
{
+ unsigned int next_cycle = s->next_cycle;
unsigned int dbc = s->data_block_counter;
+ unsigned int packet_index = s->packet_index;
+ unsigned int queue_size = s->queue_size;
+ u32 curr_cycle_time = 0;
int i;
int err;
- for (i = 0; i < packets; ++i) {
- struct pkt_desc *desc = descs + i;
- unsigned int index = (s->packet_index + i) % s->queue_size;
+ if (trace_amdtp_packet_enabled())
+ (void)fw_card_read_cycle_time(fw_parent_device(s->unit)->card, &curr_cycle_time);
+
+ *desc_count = 0;
+ for (i = 0; i < packet_count; ++i) {
unsigned int cycle;
- unsigned int payload_length;
+ bool lost;
unsigned int data_blocks;
unsigned int syt;
- cycle = compute_cycle_count(ctx_header[1]);
+ cycle = compute_ohci_cycle_count(ctx_header[1]);
+ lost = (next_cycle != cycle);
+ if (lost) {
+ if (s->flags & CIP_NO_HEADER) {
+ // Fireface skips transmission just for an isoc cycle corresponding
+ // to empty packet.
+ unsigned int prev_cycle = next_cycle;
+
+ next_cycle = increment_ohci_cycle_count(next_cycle, 1);
+ lost = (next_cycle != cycle);
+ if (!lost) {
+ // Prepare a description for the skipped cycle for
+ // sequence replay.
+ desc->cycle = prev_cycle;
+ desc->syt = 0;
+ desc->data_blocks = 0;
+ desc->data_block_counter = dbc;
+ desc->ctx_payload = NULL;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ ++(*desc_count);
+ }
+ } else if (s->flags & CIP_JUMBO_PAYLOAD) {
+ // OXFW970 skips transmission for several isoc cycles during
+ // asynchronous transaction. The sequence replay is impossible due
+ // to the reason.
+ unsigned int safe_cycle = increment_ohci_cycle_count(next_cycle,
+ IR_JUMBO_PAYLOAD_MAX_SKIP_CYCLES);
+ lost = (compare_ohci_cycle_count(safe_cycle, cycle) < 0);
+ }
+ if (lost) {
+ dev_err(&s->unit->device, "Detect discontinuity of cycle: %d %d\n",
+ next_cycle, cycle);
+ return -EIO;
+ }
+ }
- err = parse_ir_ctx_header(s, cycle, ctx_header, &payload_length,
- &data_blocks, &dbc, &syt, i);
+ err = parse_ir_ctx_header(s, cycle, ctx_header, &data_blocks, &dbc, &syt,
+ packet_index, i, curr_cycle_time);
if (err < 0)
return err;
@@ -722,15 +998,19 @@ static int generate_device_pkt_descs(struct amdtp_stream *s,
desc->syt = syt;
desc->data_blocks = data_blocks;
desc->data_block_counter = dbc;
- desc->ctx_payload = s->buffer.packets[index].buffer;
+ desc->ctx_payload = s->buffer.packets[packet_index].buffer;
if (!(s->flags & CIP_DBC_IS_END_EVENT))
dbc = (dbc + desc->data_blocks) & 0xff;
- ctx_header +=
- s->ctx_data.tx.ctx_header_size / sizeof(*ctx_header);
+ next_cycle = increment_ohci_cycle_count(next_cycle, 1);
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ ++(*desc_count);
+ ctx_header += s->ctx_data.tx.ctx_header_size / sizeof(*ctx_header);
+ packet_index = (packet_index + 1) % queue_size;
}
+ s->next_cycle = next_cycle;
s->data_block_counter = dbc;
return 0;
@@ -747,29 +1027,29 @@ static unsigned int compute_syt(unsigned int syt_offset, unsigned int cycle,
return syt & CIP_SYT_MASK;
}
-static void generate_pkt_descs(struct amdtp_stream *s, struct pkt_desc *descs,
- const __be32 *ctx_header, unsigned int packets,
- const struct seq_desc *seq_descs,
- unsigned int seq_size)
+static void generate_rx_packet_descs(struct amdtp_stream *s, struct pkt_desc *desc,
+ const __be32 *ctx_header, unsigned int packet_count)
{
+ struct seq_desc *seq_descs = s->ctx_data.rx.seq.descs;
+ unsigned int seq_size = s->ctx_data.rx.seq.size;
+ unsigned int seq_pos = s->ctx_data.rx.seq.pos;
unsigned int dbc = s->data_block_counter;
- unsigned int seq_index = s->ctx_data.rx.seq_index;
+ bool aware_syt = !(s->flags & CIP_UNAWARE_SYT);
int i;
- for (i = 0; i < packets; ++i) {
- struct pkt_desc *desc = descs + i;
+ pool_seq_descs(s, seq_descs, seq_size, seq_pos, packet_count);
+
+ for (i = 0; i < packet_count; ++i) {
unsigned int index = (s->packet_index + i) % s->queue_size;
- const struct seq_desc *seq = seq_descs + seq_index;
- unsigned int syt;
+ const struct seq_desc *seq = seq_descs + seq_pos;
- desc->cycle = compute_it_cycle(*ctx_header, s->queue_size);
+ desc->cycle = compute_ohci_it_cycle(*ctx_header, s->queue_size);
+
+ if (aware_syt && seq->syt_offset != CIP_SYT_NO_INFO)
+ desc->syt = compute_syt(seq->syt_offset, desc->cycle, s->transfer_delay);
+ else
+ desc->syt = CIP_SYT_NO_INFO;
- syt = seq->syt_offset;
- if (syt != CIP_SYT_NO_INFO) {
- syt = compute_syt(syt, desc->cycle,
- s->ctx_data.rx.transfer_delay);
- }
- desc->syt = syt;
desc->data_blocks = seq->data_blocks;
if (s->flags & CIP_DBC_IS_END_EVENT)
@@ -782,46 +1062,125 @@ static void generate_pkt_descs(struct amdtp_stream *s, struct pkt_desc *descs,
desc->ctx_payload = s->buffer.packets[index].buffer;
- seq_index = (seq_index + 1) % seq_size;
+ seq_pos = (seq_pos + 1) % seq_size;
+ desc = amdtp_stream_next_packet_desc(s, desc);
++ctx_header;
}
s->data_block_counter = dbc;
- s->ctx_data.rx.seq_index = seq_index;
+ s->ctx_data.rx.seq.pos = seq_pos;
}
static inline void cancel_stream(struct amdtp_stream *s)
{
+ struct work_struct *work = current_work();
+
s->packet_index = -1;
- if (in_interrupt())
+
+ // Detect work items for any isochronous context. The work item for pcm_period_work()
+ // should be avoided since the call of snd_pcm_period_elapsed() can reach via
+ // snd_pcm_ops.pointer() under acquiring PCM stream(group) lock and causes dead lock at
+ // snd_pcm_stop_xrun().
+ if (work && work != &s->period_work)
amdtp_stream_pcm_abort(s);
WRITE_ONCE(s->pcm_buffer_pointer, SNDRV_PCM_POS_XRUN);
}
+static snd_pcm_sframes_t compute_pcm_extra_delay(struct amdtp_stream *s,
+ const struct pkt_desc *desc, unsigned int count)
+{
+ unsigned int data_block_count = 0;
+ u32 latest_cycle;
+ u32 cycle_time;
+ u32 curr_cycle;
+ u32 cycle_gap;
+ int i, err;
+
+ if (count == 0)
+ goto end;
+
+ // Forward to the latest record.
+ for (i = 0; i < count - 1; ++i)
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ latest_cycle = desc->cycle;
+
+ err = fw_card_read_cycle_time(fw_parent_device(s->unit)->card, &cycle_time);
+ if (err < 0)
+ goto end;
+
+ // Compute cycle count with lower 3 bits of second field and cycle field like timestamp
+ // format of 1394 OHCI isochronous context.
+ curr_cycle = compute_ohci_iso_ctx_cycle_count((cycle_time >> 12) & 0x0000ffff);
+
+ if (s->direction == AMDTP_IN_STREAM) {
+ // NOTE: The AMDTP packet descriptor should be for the past isochronous cycle since
+ // it corresponds to arrived isochronous packet.
+ if (compare_ohci_cycle_count(latest_cycle, curr_cycle) > 0)
+ goto end;
+ cycle_gap = decrement_ohci_cycle_count(curr_cycle, latest_cycle);
+
+ // NOTE: estimate delay by recent history of arrived AMDTP packets. The estimated
+ // value expectedly corresponds to a few packets (0-2) since the packet arrived at
+ // the most recent isochronous cycle has been already processed.
+ for (i = 0; i < cycle_gap; ++i) {
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ data_block_count += desc->data_blocks;
+ }
+ } else {
+ // NOTE: The AMDTP packet descriptor should be for the future isochronous cycle
+ // since it was already scheduled.
+ if (compare_ohci_cycle_count(latest_cycle, curr_cycle) < 0)
+ goto end;
+ cycle_gap = decrement_ohci_cycle_count(latest_cycle, curr_cycle);
+
+ // NOTE: use history of scheduled packets.
+ for (i = 0; i < cycle_gap; ++i) {
+ data_block_count += desc->data_blocks;
+ desc = prev_packet_desc(s, desc);
+ }
+ }
+end:
+ return data_block_count * s->pcm_frame_multiplier;
+}
+
static void process_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets)
+ const struct pkt_desc *desc,
+ unsigned int count)
{
struct snd_pcm_substream *pcm;
- unsigned int pcm_frames;
+ int i;
pcm = READ_ONCE(s->pcm);
- pcm_frames = s->process_ctx_payloads(s, descs, packets, pcm);
- if (pcm)
- update_pcm_pointers(s, pcm, pcm_frames);
+ s->process_ctx_payloads(s, desc, count, pcm);
+
+ if (pcm) {
+ unsigned int data_block_count = 0;
+
+ pcm->runtime->delay = compute_pcm_extra_delay(s, desc, count);
+
+ for (i = 0; i < count; ++i) {
+ data_block_count += desc->data_blocks;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
+
+ update_pcm_pointers(s, pcm, data_block_count * s->pcm_frame_multiplier);
+ }
}
-static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
- size_t header_length, void *header,
- void *private_data)
+static void process_rx_packets(struct fw_iso_context *context, u32 tstamp, size_t header_length,
+ void *header, void *private_data)
{
struct amdtp_stream *s = private_data;
const struct amdtp_domain *d = s->domain;
const __be32 *ctx_header = header;
- unsigned int events_per_period = s->ctx_data.rx.events_per_period;
+ const unsigned int events_per_period = d->events_per_period;
unsigned int event_count = s->ctx_data.rx.event_count;
+ struct pkt_desc *desc = s->packet_descs_cursor;
+ unsigned int pkt_header_length;
unsigned int packets;
+ u32 curr_cycle_time;
+ bool need_hw_irq;
int i;
if (s->packet_index < 0)
@@ -830,53 +1189,153 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
// Calculate the number of packets in buffer and check XRUN.
packets = header_length / sizeof(*ctx_header);
- generate_pkt_descs(s, s->pkt_descs, ctx_header, packets, d->seq_descs,
- d->seq_size);
+ generate_rx_packet_descs(s, desc, ctx_header, packets);
- process_ctx_payloads(s, s->pkt_descs, packets);
+ process_ctx_payloads(s, desc, packets);
+
+ if (!(s->flags & CIP_NO_HEADER))
+ pkt_header_length = IT_PKT_HEADER_SIZE_CIP;
+ else
+ pkt_header_length = 0;
+
+ if (s == d->irq_target) {
+ // At NO_PERIOD_WAKEUP mode, the packets for all IT/IR contexts are processed by
+ // the tasks of user process operating ALSA PCM character device by calling ioctl(2)
+ // with some requests, instead of scheduled hardware IRQ of an IT context.
+ struct snd_pcm_substream *pcm = READ_ONCE(s->pcm);
+ need_hw_irq = !pcm || !pcm->runtime->no_period_wakeup;
+ } else {
+ need_hw_irq = false;
+ }
+
+ if (trace_amdtp_packet_enabled())
+ (void)fw_card_read_cycle_time(fw_parent_device(s->unit)->card, &curr_cycle_time);
for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = s->pkt_descs + i;
- unsigned int syt;
- struct {
- struct fw_iso_packet params;
- __be32 header[IT_PKT_HEADER_SIZE_CIP / sizeof(__be32)];
- } template = { {0}, {0} };
+ DEFINE_RAW_FLEX(struct fw_iso_packet, template, header, CIP_HEADER_QUADLETS);
bool sched_irq = false;
- if (s->ctx_data.rx.syt_override < 0)
- syt = desc->syt;
- else
- syt = s->ctx_data.rx.syt_override;
-
- build_it_pkt_header(s, desc->cycle, &template.params,
+ build_it_pkt_header(s, desc->cycle, template, pkt_header_length,
desc->data_blocks, desc->data_block_counter,
- syt, i);
+ desc->syt, i, curr_cycle_time);
if (s == s->domain->irq_target) {
event_count += desc->data_blocks;
if (event_count >= events_per_period) {
event_count -= events_per_period;
- sched_irq = true;
+ sched_irq = need_hw_irq;
}
}
- if (queue_out_packet(s, &template.params, sched_irq) < 0) {
+ if (queue_out_packet(s, template, sched_irq) < 0) {
cancel_stream(s);
return;
}
+
+ desc = amdtp_stream_next_packet_desc(s, desc);
}
s->ctx_data.rx.event_count = event_count;
+ s->packet_descs_cursor = desc;
+}
+
+static void skip_rx_packets(struct fw_iso_context *context, u32 tstamp, size_t header_length,
+ void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
+ const __be32 *ctx_header = header;
+ unsigned int packets;
+ unsigned int cycle;
+ int i;
+
+ if (s->packet_index < 0)
+ return;
+
+ packets = header_length / sizeof(*ctx_header);
+
+ cycle = compute_ohci_it_cycle(ctx_header[packets - 1], s->queue_size);
+ s->next_cycle = increment_ohci_cycle_count(cycle, 1);
+
+ for (i = 0; i < packets; ++i) {
+ struct fw_iso_packet params = {
+ .header_length = 0,
+ .payload_length = 0,
+ };
+ bool sched_irq = (s == d->irq_target && i == packets - 1);
+
+ if (queue_out_packet(s, &params, sched_irq) < 0) {
+ cancel_stream(s);
+ return;
+ }
+ }
}
-static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
- size_t header_length, void *header,
- void *private_data)
+static void irq_target_callback(struct fw_iso_context *context, u32 tstamp, size_t header_length,
+ void *header, void *private_data);
+
+static void process_rx_packets_intermediately(struct fw_iso_context *context, u32 tstamp,
+ size_t header_length, void *header, void *private_data)
{
struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
__be32 *ctx_header = header;
+ const unsigned int queue_size = s->queue_size;
unsigned int packets;
+ unsigned int offset;
+
+ if (s->packet_index < 0)
+ return;
+
+ packets = header_length / sizeof(*ctx_header);
+
+ offset = 0;
+ while (offset < packets) {
+ unsigned int cycle = compute_ohci_it_cycle(ctx_header[offset], queue_size);
+
+ if (compare_ohci_cycle_count(cycle, d->processing_cycle.rx_start) >= 0)
+ break;
+
+ ++offset;
+ }
+
+ if (offset > 0) {
+ unsigned int length = sizeof(*ctx_header) * offset;
+
+ skip_rx_packets(context, tstamp, length, ctx_header, private_data);
+ if (amdtp_streaming_error(s))
+ return;
+
+ ctx_header += offset;
+ header_length -= length;
+ }
+
+ if (offset < packets) {
+ s->ready_processing = true;
+ wake_up(&s->ready_wait);
+
+ if (d->replay.enable)
+ s->ctx_data.rx.cache_pos = 0;
+
+ process_rx_packets(context, tstamp, header_length, ctx_header, private_data);
+ if (amdtp_streaming_error(s))
+ return;
+
+ if (s == d->irq_target)
+ s->context->callback.sc = irq_target_callback;
+ else
+ s->context->callback.sc = process_rx_packets;
+ }
+}
+
+static void process_tx_packets(struct fw_iso_context *context, u32 tstamp, size_t header_length,
+ void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+ __be32 *ctx_header = header;
+ struct pkt_desc *desc = s->packet_descs_cursor;
+ unsigned int packet_count;
+ unsigned int desc_count;
int i;
int err;
@@ -884,17 +1343,55 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
return;
// Calculate the number of packets in buffer and check XRUN.
- packets = header_length / s->ctx_data.tx.ctx_header_size;
+ packet_count = header_length / s->ctx_data.tx.ctx_header_size;
- err = generate_device_pkt_descs(s, s->pkt_descs, ctx_header, packets);
+ desc_count = 0;
+ err = generate_tx_packet_descs(s, desc, ctx_header, packet_count, &desc_count);
if (err < 0) {
if (err != -EAGAIN) {
cancel_stream(s);
return;
}
} else {
- process_ctx_payloads(s, s->pkt_descs, packets);
+ struct amdtp_domain *d = s->domain;
+
+ process_ctx_payloads(s, desc, desc_count);
+
+ if (d->replay.enable)
+ cache_seq(s, desc, desc_count);
+
+ for (i = 0; i < desc_count; ++i)
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ s->packet_descs_cursor = desc;
+ }
+
+ for (i = 0; i < packet_count; ++i) {
+ struct fw_iso_packet params = {0};
+
+ if (queue_in_packet(s, &params) < 0) {
+ cancel_stream(s);
+ return;
+ }
}
+}
+
+static void drop_tx_packets(struct fw_iso_context *context, u32 tstamp, size_t header_length,
+ void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+ const __be32 *ctx_header = header;
+ unsigned int packets;
+ unsigned int cycle;
+ int i;
+
+ if (s->packet_index < 0)
+ return;
+
+ packets = header_length / s->ctx_data.tx.ctx_header_size;
+
+ ctx_header += (packets - 1) * s->ctx_data.tx.ctx_header_size / sizeof(*ctx_header);
+ cycle = compute_ohci_cycle_count(ctx_header[1]);
+ s->next_cycle = increment_ohci_cycle_count(cycle, 1);
for (i = 0; i < packets; ++i) {
struct fw_iso_packet params = {0};
@@ -906,79 +1403,166 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
}
}
-static void pool_ideal_seq_descs(struct amdtp_domain *d, unsigned int packets)
+static void process_tx_packets_intermediately(struct fw_iso_context *context, u32 tstamp,
+ size_t header_length, void *header, void *private_data)
{
- struct amdtp_stream *irq_target = d->irq_target;
- unsigned int seq_tail = d->seq_tail;
- unsigned int seq_size = d->seq_size;
- unsigned int min_avail;
- struct amdtp_stream *s;
+ struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
+ __be32 *ctx_header;
+ unsigned int packets;
+ unsigned int offset;
- min_avail = d->seq_size;
- list_for_each_entry(s, &d->streams, list) {
- unsigned int seq_index;
- unsigned int avail;
+ if (s->packet_index < 0)
+ return;
+
+ packets = header_length / s->ctx_data.tx.ctx_header_size;
- if (s->direction == AMDTP_IN_STREAM)
- continue;
+ offset = 0;
+ ctx_header = header;
+ while (offset < packets) {
+ unsigned int cycle = compute_ohci_cycle_count(ctx_header[1]);
- seq_index = s->ctx_data.rx.seq_index;
- avail = d->seq_tail;
- if (seq_index > avail)
- avail += d->seq_size;
- avail -= seq_index;
+ if (compare_ohci_cycle_count(cycle, d->processing_cycle.tx_start) >= 0)
+ break;
- if (avail < min_avail)
- min_avail = avail;
+ ctx_header += s->ctx_data.tx.ctx_header_size / sizeof(__be32);
+ ++offset;
}
- while (min_avail < packets) {
- struct seq_desc *desc = d->seq_descs + seq_tail;
+ ctx_header = header;
- desc->syt_offset = calculate_syt_offset(&d->last_syt_offset,
- &d->syt_offset_state, irq_target->sfc);
- desc->data_blocks = calculate_data_blocks(&d->data_block_state,
- !!(irq_target->flags & CIP_BLOCKING),
- desc->syt_offset == CIP_SYT_NO_INFO,
- irq_target->syt_interval, irq_target->sfc);
+ if (offset > 0) {
+ size_t length = s->ctx_data.tx.ctx_header_size * offset;
- ++seq_tail;
- seq_tail %= seq_size;
+ drop_tx_packets(context, tstamp, length, ctx_header, s);
+ if (amdtp_streaming_error(s))
+ return;
- ++min_avail;
+ ctx_header += length / sizeof(*ctx_header);
+ header_length -= length;
}
- d->seq_tail = seq_tail;
+ if (offset < packets) {
+ s->ready_processing = true;
+ wake_up(&s->ready_wait);
+
+ process_tx_packets(context, tstamp, header_length, ctx_header, s);
+ if (amdtp_streaming_error(s))
+ return;
+
+ context->callback.sc = process_tx_packets;
+ }
}
-static void irq_target_callback(struct fw_iso_context *context, u32 tstamp,
- size_t header_length, void *header,
- void *private_data)
+static void drop_tx_packets_initially(struct fw_iso_context *context, u32 tstamp,
+ size_t header_length, void *header, void *private_data)
{
- struct amdtp_stream *irq_target = private_data;
- struct amdtp_domain *d = irq_target->domain;
- unsigned int packets = header_length / sizeof(__be32);
- struct amdtp_stream *s;
+ struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
+ __be32 *ctx_header;
+ unsigned int count;
+ unsigned int events;
+ int i;
+
+ if (s->packet_index < 0)
+ return;
+
+ count = header_length / s->ctx_data.tx.ctx_header_size;
+
+ // Attempt to detect any event in the batch of packets.
+ events = 0;
+ ctx_header = header;
+ for (i = 0; i < count; ++i) {
+ unsigned int payload_quads =
+ (be32_to_cpu(*ctx_header) >> ISO_DATA_LENGTH_SHIFT) / sizeof(__be32);
+ unsigned int data_blocks;
+
+ if (s->flags & CIP_NO_HEADER) {
+ data_blocks = payload_quads / s->data_block_quadlets;
+ } else {
+ __be32 *cip_headers = ctx_header + IR_CTX_HEADER_DEFAULT_QUADLETS;
+
+ if (payload_quads < CIP_HEADER_QUADLETS) {
+ data_blocks = 0;
+ } else {
+ payload_quads -= CIP_HEADER_QUADLETS;
+
+ if (s->flags & CIP_UNAWARE_SYT) {
+ data_blocks = payload_quads / s->data_block_quadlets;
+ } else {
+ u32 cip1 = be32_to_cpu(cip_headers[1]);
+
+ // NODATA packet can includes any data blocks but they are
+ // not available as event.
+ if ((cip1 & CIP_NO_DATA) == CIP_NO_DATA)
+ data_blocks = 0;
+ else
+ data_blocks = payload_quads / s->data_block_quadlets;
+ }
+ }
+ }
+
+ events += data_blocks;
+
+ ctx_header += s->ctx_data.tx.ctx_header_size / sizeof(__be32);
+ }
+
+ drop_tx_packets(context, tstamp, header_length, header, s);
+
+ if (events > 0)
+ s->ctx_data.tx.event_starts = true;
+
+ // Decide the cycle count to begin processing content of packet in IR contexts.
+ {
+ unsigned int stream_count = 0;
+ unsigned int event_starts_count = 0;
+ unsigned int cycle = UINT_MAX;
+
+ list_for_each_entry(s, &d->streams, list) {
+ if (s->direction == AMDTP_IN_STREAM) {
+ ++stream_count;
+ if (s->ctx_data.tx.event_starts)
+ ++event_starts_count;
+ }
+ }
+
+ if (stream_count == event_starts_count) {
+ unsigned int next_cycle;
- // Record enough entries with extra 3 cycles at least.
- pool_ideal_seq_descs(d, packets + 3);
+ list_for_each_entry(s, &d->streams, list) {
+ if (s->direction != AMDTP_IN_STREAM)
+ continue;
- out_stream_callback(context, tstamp, header_length, header, irq_target);
- if (amdtp_streaming_error(irq_target))
- goto error;
+ next_cycle = increment_ohci_cycle_count(s->next_cycle,
+ d->processing_cycle.tx_init_skip);
+ if (cycle == UINT_MAX ||
+ compare_ohci_cycle_count(next_cycle, cycle) > 0)
+ cycle = next_cycle;
+
+ s->context->callback.sc = process_tx_packets_intermediately;
+ }
+
+ d->processing_cycle.tx_start = cycle;
+ }
+ }
+}
+
+static void process_ctxs_in_domain(struct amdtp_domain *d)
+{
+ struct amdtp_stream *s;
list_for_each_entry(s, &d->streams, list) {
- if (s != irq_target && amdtp_stream_running(s)) {
+ if (s != d->irq_target && amdtp_stream_running(s))
fw_iso_context_flush_completions(s->context);
- if (amdtp_streaming_error(s))
- goto error;
- }
+
+ if (amdtp_streaming_error(s))
+ goto error;
}
return;
error:
- if (amdtp_stream_running(irq_target))
- cancel_stream(irq_target);
+ if (amdtp_stream_running(d->irq_target))
+ cancel_stream(d->irq_target);
list_for_each_entry(s, &d->streams, list) {
if (amdtp_stream_running(s))
@@ -986,37 +1570,99 @@ error:
}
}
-// this is executed one time.
+static void irq_target_callback(struct fw_iso_context *context, u32 tstamp, size_t header_length,
+ void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
+
+ process_rx_packets(context, tstamp, header_length, header, private_data);
+ process_ctxs_in_domain(d);
+}
+
+static void irq_target_callback_intermediately(struct fw_iso_context *context, u32 tstamp,
+ size_t header_length, void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
+
+ process_rx_packets_intermediately(context, tstamp, header_length, header, private_data);
+ process_ctxs_in_domain(d);
+}
+
+static void irq_target_callback_skip(struct fw_iso_context *context, u32 tstamp,
+ size_t header_length, void *header, void *private_data)
+{
+ struct amdtp_stream *s = private_data;
+ struct amdtp_domain *d = s->domain;
+ bool ready_to_start;
+
+ skip_rx_packets(context, tstamp, header_length, header, private_data);
+ process_ctxs_in_domain(d);
+
+ if (d->replay.enable && !d->replay.on_the_fly) {
+ unsigned int rx_count = 0;
+ unsigned int rx_ready_count = 0;
+ struct amdtp_stream *rx;
+
+ list_for_each_entry(rx, &d->streams, list) {
+ struct amdtp_stream *tx;
+ unsigned int cached_cycles;
+
+ if (rx->direction != AMDTP_OUT_STREAM)
+ continue;
+ ++rx_count;
+
+ tx = rx->ctx_data.rx.replay_target;
+ cached_cycles = calculate_cached_cycle_count(tx, 0);
+ if (cached_cycles > tx->ctx_data.tx.cache.size / 2)
+ ++rx_ready_count;
+ }
+
+ ready_to_start = (rx_count == rx_ready_count);
+ } else {
+ ready_to_start = true;
+ }
+
+ // Decide the cycle count to begin processing content of packet in IT contexts. All of IT
+ // contexts are expected to start and get callback when reaching here.
+ if (ready_to_start) {
+ unsigned int cycle = s->next_cycle;
+ list_for_each_entry(s, &d->streams, list) {
+ if (s->direction != AMDTP_OUT_STREAM)
+ continue;
+
+ if (compare_ohci_cycle_count(s->next_cycle, cycle) > 0)
+ cycle = s->next_cycle;
+
+ if (s == d->irq_target)
+ s->context->callback.sc = irq_target_callback_intermediately;
+ else
+ s->context->callback.sc = process_rx_packets_intermediately;
+ }
+
+ d->processing_cycle.rx_start = cycle;
+ }
+}
+
+// This is executed one time. For in-stream, first packet has come. For out-stream, prepared to
+// transmit first packet.
static void amdtp_stream_first_callback(struct fw_iso_context *context,
u32 tstamp, size_t header_length,
void *header, void *private_data)
{
struct amdtp_stream *s = private_data;
- const __be32 *ctx_header = header;
- u32 cycle;
-
- /*
- * For in-stream, first packet has come.
- * For out-stream, prepared to transmit first packet
- */
- s->callbacked = true;
- wake_up(&s->callback_wait);
+ struct amdtp_domain *d = s->domain;
if (s->direction == AMDTP_IN_STREAM) {
- cycle = compute_cycle_count(ctx_header[1]);
-
- context->callback.sc = in_stream_callback;
+ context->callback.sc = drop_tx_packets_initially;
} else {
- cycle = compute_it_cycle(*ctx_header, s->queue_size);
-
- if (s == s->domain->irq_target)
- context->callback.sc = irq_target_callback;
+ if (s == d->irq_target)
+ context->callback.sc = irq_target_callback_skip;
else
- context->callback.sc = out_stream_callback;
+ context->callback.sc = skip_rx_packets;
}
- s->start_cycle = cycle;
-
context->callback.sc(context, tstamp, header_length, header, s);
}
@@ -1025,8 +1671,6 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
* @s: the AMDTP stream to start
* @channel: the isochronous channel on the bus
* @speed: firewire speed code
- * @start_cycle: the isochronous cycle to start the context. Start immediately
- * if negative value is given.
* @queue_size: The number of packets in the queue.
* @idle_irq_interval: the interval to queue packet during initial state.
*
@@ -1035,14 +1679,14 @@ static void amdtp_stream_first_callback(struct fw_iso_context *context,
* device can be started.
*/
static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
- int start_cycle, unsigned int queue_size,
- unsigned int idle_irq_interval)
+ unsigned int queue_size, unsigned int idle_irq_interval)
{
bool is_irq_target = (s == s->domain->irq_target);
unsigned int ctx_header_size;
unsigned int max_ctx_payload_size;
enum dma_data_direction dir;
- int type, tag, err;
+ struct pkt_desc *descs;
+ int i, type, tag, err;
mutex_lock(&s->mutex);
@@ -1064,7 +1708,7 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
s->data_block_counter = 0;
}
- /* initialize packet buffer */
+ // initialize packet buffer.
if (s->direction == AMDTP_IN_STREAM) {
dir = DMA_FROM_DEVICE;
type = FW_ISO_CONTEXT_RECEIVE;
@@ -1072,21 +1716,14 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
ctx_header_size = IR_CTX_HEADER_SIZE_CIP;
else
ctx_header_size = IR_CTX_HEADER_SIZE_NO_CIP;
-
- max_ctx_payload_size = amdtp_stream_get_max_payload(s) -
- ctx_header_size;
} else {
dir = DMA_TO_DEVICE;
type = FW_ISO_CONTEXT_TRANSMIT;
ctx_header_size = 0; // No effect for IT context.
-
- max_ctx_payload_size = amdtp_stream_get_max_payload(s);
- if (!(s->flags & CIP_NO_HEADER))
- max_ctx_payload_size -= IT_PKT_HEADER_SIZE_CIP;
}
+ max_ctx_payload_size = amdtp_stream_get_max_ctx_payload_size(s);
- err = iso_packets_buffer_init(&s->buffer, s->unit, queue_size,
- max_ctx_payload_size, dir);
+ err = iso_packets_buffer_init(&s->buffer, s->unit, queue_size, max_ctx_payload_size, dir);
if (err < 0)
goto err_unlock;
s->queue_size = queue_size;
@@ -1107,6 +1744,49 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
if (s->direction == AMDTP_IN_STREAM) {
s->ctx_data.tx.max_ctx_payload_length = max_ctx_payload_size;
s->ctx_data.tx.ctx_header_size = ctx_header_size;
+ s->ctx_data.tx.event_starts = false;
+
+ if (s->domain->replay.enable) {
+ // struct fw_iso_context.drop_overflow_headers is false therefore it's
+ // possible to cache much unexpectedly.
+ s->ctx_data.tx.cache.size = max_t(unsigned int, s->syt_interval * 2,
+ queue_size * 3 / 2);
+ s->ctx_data.tx.cache.pos = 0;
+ s->ctx_data.tx.cache.descs = kcalloc(s->ctx_data.tx.cache.size,
+ sizeof(*s->ctx_data.tx.cache.descs), GFP_KERNEL);
+ if (!s->ctx_data.tx.cache.descs) {
+ err = -ENOMEM;
+ goto err_context;
+ }
+ }
+ } else {
+ static const struct {
+ unsigned int data_block;
+ unsigned int syt_offset;
+ } *entry, initial_state[] = {
+ [CIP_SFC_32000] = { 4, 3072 },
+ [CIP_SFC_48000] = { 6, 1024 },
+ [CIP_SFC_96000] = { 12, 1024 },
+ [CIP_SFC_192000] = { 24, 1024 },
+ [CIP_SFC_44100] = { 0, 67 },
+ [CIP_SFC_88200] = { 0, 67 },
+ [CIP_SFC_176400] = { 0, 67 },
+ };
+
+ s->ctx_data.rx.seq.descs = kcalloc(queue_size, sizeof(*s->ctx_data.rx.seq.descs), GFP_KERNEL);
+ if (!s->ctx_data.rx.seq.descs) {
+ err = -ENOMEM;
+ goto err_context;
+ }
+ s->ctx_data.rx.seq.size = queue_size;
+ s->ctx_data.rx.seq.pos = 0;
+
+ entry = &initial_state[s->sfc];
+ s->ctx_data.rx.data_block_state = entry->data_block;
+ s->ctx_data.rx.syt_offset_state = entry->syt_offset;
+ s->ctx_data.rx.last_syt_offset = TICKS_PER_CYCLE;
+
+ s->ctx_data.rx.event_count = 0;
}
if (s->flags & CIP_NO_HEADER)
@@ -1114,12 +1794,24 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
else
s->tag = TAG_CIP;
- s->pkt_descs = kcalloc(s->queue_size, sizeof(*s->pkt_descs),
- GFP_KERNEL);
- if (!s->pkt_descs) {
+ // NOTE: When operating without hardIRQ/softIRQ, applications tends to call ioctl request
+ // for runtime of PCM substream in the interval equivalent to the size of PCM buffer. It
+ // could take a round over queue of AMDTP packet descriptors and small loss of history. For
+ // safe, keep more 8 elements for the queue, equivalent to 1 ms.
+ descs = kcalloc(s->queue_size + 8, sizeof(*descs), GFP_KERNEL);
+ if (!descs) {
err = -ENOMEM;
goto err_context;
}
+ s->packet_descs = descs;
+
+ INIT_LIST_HEAD(&s->packet_descs_list);
+ for (i = 0; i < s->queue_size; ++i) {
+ INIT_LIST_HEAD(&descs->link);
+ list_add_tail(&descs->link, &s->packet_descs_list);
+ ++descs;
+ }
+ s->packet_descs_cursor = list_first_entry(&s->packet_descs_list, struct pkt_desc, link);
s->packet_index = 0;
do {
@@ -1149,8 +1841,8 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER))
tag |= FW_ISO_CONTEXT_MATCH_TAG0;
- s->callbacked = false;
- err = fw_iso_context_start(s->context, start_cycle, 0, tag);
+ s->ready_processing = false;
+ err = fw_iso_context_start(s->context, -1, 0, tag);
if (err < 0)
goto err_pkt_descs;
@@ -1158,8 +1850,15 @@ static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed,
return 0;
err_pkt_descs:
- kfree(s->pkt_descs);
+ kfree(s->packet_descs);
+ s->packet_descs = NULL;
err_context:
+ if (s->direction == AMDTP_OUT_STREAM) {
+ kfree(s->ctx_data.rx.seq.descs);
+ } else {
+ if (s->domain->replay.enable)
+ kfree(s->ctx_data.tx.cache.descs);
+ }
fw_iso_context_destroy(s->context);
s->context = ERR_PTR(-1);
err_buffer:
@@ -1183,27 +1882,11 @@ unsigned long amdtp_domain_stream_pcm_pointer(struct amdtp_domain *d,
struct amdtp_stream *irq_target = d->irq_target;
if (irq_target && amdtp_stream_running(irq_target)) {
- // This function is called in software IRQ context of
- // period_tasklet or process context.
- //
- // When the software IRQ context was scheduled by software IRQ
- // context of IT contexts, queued packets were already handled.
- // Therefore, no need to flush the queue in buffer furthermore.
- //
- // When the process context reach here, some packets will be
- // already queued in the buffer. These packets should be handled
- // immediately to keep better granularity of PCM pointer.
- //
- // Later, the process context will sometimes schedules software
- // IRQ context of the period_tasklet. Then, no need to flush the
- // queue by the same reason as described in the above
- if (!in_interrupt()) {
- // Queued packet should be processed without any kernel
- // preemption to keep latency against bus cycle.
- preempt_disable();
+ // The work item to call snd_pcm_period_elapsed() can reach here by the call of
+ // snd_pcm_ops.pointer(), however less packets would be available then. Therefore
+ // the following call is just for user process contexts.
+ if (current_work() != &s->period_work)
fw_iso_context_flush_completions(irq_target->context);
- preempt_enable();
- }
}
return READ_ONCE(s->pcm_buffer_pointer);
@@ -1223,13 +1906,8 @@ int amdtp_domain_stream_pcm_ack(struct amdtp_domain *d, struct amdtp_stream *s)
// Process isochronous packets for recent isochronous cycle to handle
// queued PCM frames.
- if (irq_target && amdtp_stream_running(irq_target)) {
- // Queued packet should be processed without any kernel
- // preemption to keep latency against bus cycle.
- preempt_disable();
+ if (irq_target && amdtp_stream_running(irq_target))
fw_iso_context_flush_completions(irq_target->context);
- preempt_enable();
- }
return 0;
}
@@ -1263,14 +1941,20 @@ static void amdtp_stream_stop(struct amdtp_stream *s)
return;
}
- tasklet_kill(&s->period_tasklet);
+ cancel_work_sync(&s->period_work);
fw_iso_context_stop(s->context);
fw_iso_context_destroy(s->context);
s->context = ERR_PTR(-1);
iso_packets_buffer_destroy(&s->buffer, s->unit);
- kfree(s->pkt_descs);
+ kfree(s->packet_descs);
+ s->packet_descs = NULL;
- s->callbacked = false;
+ if (s->direction == AMDTP_OUT_STREAM) {
+ kfree(s->ctx_data.rx.seq.descs);
+ } else {
+ if (s->domain->replay.enable)
+ kfree(s->ctx_data.tx.cache.descs);
+ }
mutex_unlock(&s->mutex);
}
@@ -1302,8 +1986,6 @@ int amdtp_domain_init(struct amdtp_domain *d)
d->events_per_period = 0;
- d->seq_descs = NULL;
-
return 0;
}
EXPORT_SYMBOL_GPL(amdtp_domain_init);
@@ -1346,26 +2028,48 @@ int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s,
}
EXPORT_SYMBOL_GPL(amdtp_domain_add_stream);
-static int get_current_cycle_time(struct fw_card *fw_card, int *cur_cycle)
+// Make the reference from rx stream to tx stream for sequence replay. When the number of tx streams
+// is less than the number of rx streams, the first tx stream is selected.
+static int make_association(struct amdtp_domain *d)
{
- int generation;
- int rcode;
- __be32 reg;
- u32 data;
-
- // This is a request to local 1394 OHCI controller and expected to
- // complete without any event waiting.
- generation = fw_card->generation;
- smp_rmb(); // node_id vs. generation.
- rcode = fw_run_transaction(fw_card, TCODE_READ_QUADLET_REQUEST,
- fw_card->node_id, generation, SCODE_100,
- CSR_REGISTER_BASE + CSR_CYCLE_TIME,
- &reg, sizeof(reg));
- if (rcode != RCODE_COMPLETE)
- return -EIO;
+ unsigned int dst_index = 0;
+ struct amdtp_stream *rx;
+
+ // Make association to replay target.
+ list_for_each_entry(rx, &d->streams, list) {
+ if (rx->direction == AMDTP_OUT_STREAM) {
+ unsigned int src_index = 0;
+ struct amdtp_stream *tx = NULL;
+ struct amdtp_stream *s;
+
+ list_for_each_entry(s, &d->streams, list) {
+ if (s->direction == AMDTP_IN_STREAM) {
+ if (dst_index == src_index) {
+ tx = s;
+ break;
+ }
+
+ ++src_index;
+ }
+ }
+ if (!tx) {
+ // Select the first entry.
+ list_for_each_entry(s, &d->streams, list) {
+ if (s->direction == AMDTP_IN_STREAM) {
+ tx = s;
+ break;
+ }
+ }
+ // No target is available to replay sequence.
+ if (!tx)
+ return -EINVAL;
+ }
- data = be32_to_cpu(reg);
- *cur_cycle = data >> 12;
+ rx->ctx_data.rx.replay_target = tx;
+
+ ++dst_index;
+ }
+ }
return 0;
}
@@ -1373,39 +2077,44 @@ static int get_current_cycle_time(struct fw_card *fw_card, int *cur_cycle)
/**
* amdtp_domain_start - start sending packets for isoc context in the domain.
* @d: the AMDTP domain.
- * @ir_delay_cycle: the cycle delay to start all IR contexts.
+ * @tx_init_skip_cycles: the number of cycles to skip processing packets at initial stage of IR
+ * contexts.
+ * @replay_seq: whether to replay the sequence of packet in IR context for the sequence of packet in
+ * IT context.
+ * @replay_on_the_fly: transfer rx packets according to nominal frequency, then begin to replay
+ * according to arrival of events in tx packets.
*/
-int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle)
+int amdtp_domain_start(struct amdtp_domain *d, unsigned int tx_init_skip_cycles, bool replay_seq,
+ bool replay_on_the_fly)
{
- static const struct {
- unsigned int data_block;
- unsigned int syt_offset;
- } *entry, initial_state[] = {
- [CIP_SFC_32000] = { 4, 3072 },
- [CIP_SFC_48000] = { 6, 1024 },
- [CIP_SFC_96000] = { 12, 1024 },
- [CIP_SFC_192000] = { 24, 1024 },
- [CIP_SFC_44100] = { 0, 67 },
- [CIP_SFC_88200] = { 0, 67 },
- [CIP_SFC_176400] = { 0, 67 },
- };
unsigned int events_per_buffer = d->events_per_buffer;
unsigned int events_per_period = d->events_per_period;
- unsigned int idle_irq_interval;
unsigned int queue_size;
struct amdtp_stream *s;
- int cycle;
+ bool found = false;
int err;
+ if (replay_seq) {
+ err = make_association(d);
+ if (err < 0)
+ return err;
+ }
+ d->replay.enable = replay_seq;
+ d->replay.on_the_fly = replay_on_the_fly;
+
// Select an IT context as IRQ target.
list_for_each_entry(s, &d->streams, list) {
- if (s->direction == AMDTP_OUT_STREAM)
+ if (s->direction == AMDTP_OUT_STREAM) {
+ found = true;
break;
+ }
}
- if (!s)
+ if (!found)
return -ENXIO;
d->irq_target = s;
+ d->processing_cycle.tx_init_skip = tx_init_skip_cycles;
+
// This is a case that AMDTP streams in domain run just for MIDI
// substream. Use the number of events equivalent to 10 msec as
// interval of hardware IRQ.
@@ -1417,82 +2126,24 @@ int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle)
queue_size = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_buffer,
amdtp_rate_table[d->irq_target->sfc]);
- d->seq_descs = kcalloc(queue_size, sizeof(*d->seq_descs), GFP_KERNEL);
- if (!d->seq_descs)
- return -ENOMEM;
- d->seq_size = queue_size;
- d->seq_tail = 0;
-
- entry = &initial_state[s->sfc];
- d->data_block_state = entry->data_block;
- d->syt_offset_state = entry->syt_offset;
- d->last_syt_offset = TICKS_PER_CYCLE;
-
- if (ir_delay_cycle > 0) {
- struct fw_card *fw_card = fw_parent_device(s->unit)->card;
-
- err = get_current_cycle_time(fw_card, &cycle);
- if (err < 0)
- goto error;
-
- // No need to care overflow in cycle field because of enough
- // width.
- cycle += ir_delay_cycle;
-
- // Round up to sec field.
- if ((cycle & 0x00001fff) >= CYCLES_PER_SECOND) {
- unsigned int sec;
-
- // The sec field can overflow.
- sec = (cycle & 0xffffe000) >> 13;
- cycle = (++sec << 13) |
- ((cycle & 0x00001fff) / CYCLES_PER_SECOND);
- }
-
- // In OHCI 1394 specification, lower 2 bits are available for
- // sec field.
- cycle &= 0x00007fff;
- } else {
- cycle = -1;
- }
-
list_for_each_entry(s, &d->streams, list) {
- int cycle_match;
+ unsigned int idle_irq_interval = 0;
- if (s->direction == AMDTP_IN_STREAM) {
- cycle_match = cycle;
- } else {
- // IT context starts immediately.
- cycle_match = -1;
- s->ctx_data.rx.seq_index = 0;
+ if (s->direction == AMDTP_OUT_STREAM && s == d->irq_target) {
+ idle_irq_interval = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_period,
+ amdtp_rate_table[d->irq_target->sfc]);
}
- if (s != d->irq_target) {
- err = amdtp_stream_start(s, s->channel, s->speed,
- cycle_match, queue_size, 0);
- if (err < 0)
- goto error;
- }
+ // Starts immediately but actually DMA context starts several hundred cycles later.
+ err = amdtp_stream_start(s, s->channel, s->speed, queue_size, idle_irq_interval);
+ if (err < 0)
+ goto error;
}
- s = d->irq_target;
- s->ctx_data.rx.events_per_period = events_per_period;
- s->ctx_data.rx.event_count = 0;
- s->ctx_data.rx.seq_index = 0;
-
- idle_irq_interval = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_period,
- amdtp_rate_table[d->irq_target->sfc]);
- err = amdtp_stream_start(s, s->channel, s->speed, -1, queue_size,
- idle_irq_interval);
- if (err < 0)
- goto error;
-
return 0;
error:
list_for_each_entry(s, &d->streams, list)
amdtp_stream_stop(s);
- kfree(d->seq_descs);
- d->seq_descs = NULL;
return err;
}
EXPORT_SYMBOL_GPL(amdtp_domain_start);
@@ -1517,8 +2168,5 @@ void amdtp_domain_stop(struct amdtp_domain *d)
d->events_per_period = 0;
d->irq_target = NULL;
-
- kfree(d->seq_descs);
- d->seq_descs = NULL;
}
EXPORT_SYMBOL_GPL(amdtp_domain_stop);
diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h
index 703b710aaf7f..775db3fc4959 100644
--- a/sound/firewire/amdtp-stream.h
+++ b/sound/firewire/amdtp-stream.h
@@ -35,6 +35,11 @@
* @CIP_NO_HEADERS: a lack of headers in packets
* @CIP_UNALIGHED_DBC: Only for in-stream. The value of dbc is not alighed to
* the value of current SYT_INTERVAL; e.g. initial value is not zero.
+ * @CIP_UNAWARE_SYT: For outgoing packet, the value in SYT field of CIP is 0xffff.
+ * For incoming packet, the value in SYT field of CIP is not handled.
+ * @CIP_DBC_IS_PAYLOAD_QUADLETS: Available for incoming packet, and only effective with
+ * CIP_DBC_IS_END_EVENT flag. The value of dbc field is the number of accumulated quadlets
+ * in CIP payload, instead of the number of accumulated data blocks.
*/
enum cip_flags {
CIP_NONBLOCKING = 0x00,
@@ -48,6 +53,8 @@ enum cip_flags {
CIP_HEADER_WITHOUT_EOH = 0x80,
CIP_NO_HEADER = 0x100,
CIP_UNALIGHED_DBC = 0x200,
+ CIP_UNAWARE_SYT = 0x400,
+ CIP_DBC_IS_PAYLOAD_QUADLETS = 0x800,
};
/**
@@ -100,19 +107,20 @@ struct pkt_desc {
unsigned int data_blocks;
unsigned int data_block_counter;
__be32 *ctx_payload;
+ struct list_head link;
};
struct amdtp_stream;
-typedef unsigned int (*amdtp_stream_process_ctx_payloads_t)(
- struct amdtp_stream *s,
- const struct pkt_desc *desc,
- unsigned int packets,
- struct snd_pcm_substream *pcm);
+typedef void (*amdtp_stream_process_ctx_payloads_t)(struct amdtp_stream *s,
+ const struct pkt_desc *desc,
+ unsigned int count,
+ struct snd_pcm_substream *pcm);
struct amdtp_domain;
struct amdtp_stream {
struct fw_unit *unit;
- enum cip_flags flags;
+ // The combination of cip_flags enumeration-constants.
+ unsigned int flags;
enum amdtp_stream_direction direction;
struct mutex mutex;
@@ -121,7 +129,9 @@ struct amdtp_stream {
struct iso_packets_buffer buffer;
unsigned int queue_size;
int packet_index;
- struct pkt_desc *pkt_descs;
+ struct pkt_desc *packet_descs;
+ struct list_head packet_descs_list;
+ struct pkt_desc *packet_descs_cursor;
int tag;
union {
struct {
@@ -134,19 +144,36 @@ struct amdtp_stream {
// Fixed interval of dbc between previos/current
// packets.
unsigned int dbc_interval;
+
+ // The device starts multiplexing events to the packet.
+ bool event_starts;
+
+ struct {
+ struct seq_desc *descs;
+ unsigned int size;
+ unsigned int pos;
+ } cache;
} tx;
struct {
- // To calculate CIP data blocks and tstamp.
- unsigned int transfer_delay;
- unsigned int seq_index;
-
// To generate CIP header.
unsigned int fdf;
- int syt_override;
// To generate constant hardware IRQ.
unsigned int event_count;
- unsigned int events_per_period;
+
+ // To calculate CIP data blocks and tstamp.
+ struct {
+ struct seq_desc *descs;
+ unsigned int size;
+ unsigned int pos;
+ } seq;
+
+ unsigned int data_block_state;
+ unsigned int syt_offset_state;
+ unsigned int last_syt_offset;
+
+ struct amdtp_stream *replay_target;
+ unsigned int cache_pos;
} rx;
} ctx_data;
@@ -157,20 +184,23 @@ struct amdtp_stream {
unsigned int sph;
unsigned int fmt;
- /* Internal flags. */
+ // Internal flags.
+ unsigned int transfer_delay;
enum cip_sfc sfc;
unsigned int syt_interval;
/* For a PCM substream processing. */
struct snd_pcm_substream *pcm;
- struct tasklet_struct period_tasklet;
+ struct work_struct period_work;
snd_pcm_uframes_t pcm_buffer_pointer;
unsigned int pcm_period_pointer;
+ unsigned int pcm_frame_multiplier;
- /* To wait for first packet. */
- bool callbacked;
- wait_queue_head_t callback_wait;
- u32 start_cycle;
+ // To start processing content of packets at the same cycle in several contexts for
+ // each direction.
+ bool ready_processing;
+ wait_queue_head_t ready_wait;
+ unsigned int next_cycle;
/* For backends to process data blocks. */
void *protocol;
@@ -184,14 +214,14 @@ struct amdtp_stream {
};
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, unsigned int flags,
unsigned int fmt,
amdtp_stream_process_ctx_payloads_t process_ctx_payloads,
unsigned int protocol_size);
void amdtp_stream_destroy(struct amdtp_stream *s);
int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate,
- unsigned int data_block_quadlets);
+ unsigned int data_block_quadlets, unsigned int pcm_frame_multiplier);
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s);
void amdtp_stream_update(struct amdtp_stream *s);
@@ -254,24 +284,19 @@ static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s,
WRITE_ONCE(s->pcm, pcm);
}
-static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
-{
- return sfc & 1;
-}
-
/**
- * amdtp_stream_wait_callback - sleep till callbacked or timeout
+ * amdtp_stream_next_packet_desc - retrieve next descriptor for amdtp packet.
* @s: the AMDTP stream
- * @timeout: msec till timeout
+ * @desc: the descriptor of packet
*
- * If this function return false, the AMDTP stream should be stopped.
+ * This macro computes next descriptor so that the list of descriptors behaves circular queue.
*/
-static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s,
- unsigned int timeout)
+#define amdtp_stream_next_packet_desc(s, desc) \
+ list_next_entry_circular(desc, &s->packet_descs_list, link)
+
+static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
{
- return wait_event_timeout(s->callback_wait,
- s->callbacked == true,
- msecs_to_jiffies(timeout)) > 0;
+ return sfc & 1;
}
struct seq_desc {
@@ -287,13 +312,16 @@ struct amdtp_domain {
struct amdtp_stream *irq_target;
- struct seq_desc *seq_descs;
- unsigned int seq_size;
- unsigned int seq_tail;
+ struct {
+ unsigned int tx_init_skip;
+ unsigned int tx_start;
+ unsigned int rx_start;
+ } processing_cycle;
- unsigned int data_block_state;
- unsigned int syt_offset_state;
- unsigned int last_syt_offset;
+ struct {
+ bool enable:1;
+ bool on_the_fly:1;
+ } replay;
};
int amdtp_domain_init(struct amdtp_domain *d);
@@ -302,7 +330,8 @@ void amdtp_domain_destroy(struct amdtp_domain *d);
int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s,
int channel, int speed);
-int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle);
+int amdtp_domain_start(struct amdtp_domain *d, unsigned int tx_init_skip_cycles, bool replay_seq,
+ bool replay_on_the_fly);
void amdtp_domain_stop(struct amdtp_domain *d);
static inline int amdtp_domain_set_events_per_period(struct amdtp_domain *d,
@@ -319,4 +348,25 @@ unsigned long amdtp_domain_stream_pcm_pointer(struct amdtp_domain *d,
struct amdtp_stream *s);
int amdtp_domain_stream_pcm_ack(struct amdtp_domain *d, struct amdtp_stream *s);
+/**
+ * amdtp_domain_wait_ready - sleep till being ready to process packets or timeout
+ * @d: the AMDTP domain
+ * @timeout_ms: msec till timeout
+ *
+ * If this function return false, the AMDTP domain should be stopped.
+ */
+static inline bool amdtp_domain_wait_ready(struct amdtp_domain *d, unsigned int timeout_ms)
+{
+ struct amdtp_stream *s;
+
+ list_for_each_entry(s, &d->streams, list) {
+ unsigned int j = msecs_to_jiffies(timeout_ms);
+
+ if (wait_event_interruptible_timeout(s->ready_wait, s->ready_processing, j) <= 0)
+ return false;
+ }
+
+ return true;
+}
+
#endif
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile
index 14bc84c51ef5..b913e805bd7a 100644
--- a/sound/firewire/bebob/Makefile
+++ b/sound/firewire/bebob/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
+snd-bebob-y := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
bebob_pcm.o bebob_hwdep.o bebob_terratec.o \
bebob_yamaha_terratec.o bebob_focusrite.o bebob_maudio.o \
bebob.o
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c
index 2c8e3392a490..4ebaeff16455 100644
--- a/sound/firewire/bebob/bebob.c
+++ b/sound/firewire/bebob/bebob.c
@@ -15,7 +15,7 @@
MODULE_DESCRIPTION("BridgeCo BeBoB driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
@@ -40,14 +40,12 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
#define VEN_EDIROL 0x000040ab
#define VEN_PRESONUS 0x00000a92
#define VEN_BRIDGECO 0x000007f5
-#define VEN_MACKIE1 0x0000000f
-#define VEN_MACKIE2 0x00000ff2
+#define VEN_MACKIE 0x00000ff2
#define VEN_STANTON 0x00001260
#define VEN_TASCAM 0x0000022e
#define VEN_BEHRINGER 0x00001564
#define VEN_APOGEE 0x000003db
#define VEN_ESI 0x00000f1b
-#define VEN_ACOUSTIC 0x00000002
#define VEN_CME 0x0000000a
#define VEN_PHONIC 0x00001496
#define VEN_LYNX 0x000019e5
@@ -56,14 +54,15 @@ static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
#define VEN_TERRATEC 0x00000aac
#define VEN_YAMAHA 0x0000a0de
#define VEN_FOCUSRITE 0x0000130e
-#define VEN_MAUDIO1 0x00000d6c
-#define VEN_MAUDIO2 0x000007f5
+#define VEN_MAUDIO 0x00000d6c
#define VEN_DIGIDESIGN 0x00a07e
+#define OUI_SHOUYO 0x002327
#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060
#define MODEL_MAUDIO_FW1814 0x00010071
#define MODEL_MAUDIO_PROJECTMIX 0x00010091
+#define MODEL_MAUDIO_PROFIRELIGHTBRIDGE 0x000100a1
static int
name_device(struct snd_bebob *bebob)
@@ -74,7 +73,6 @@ name_device(struct snd_bebob *bebob)
u32 hw_id;
u32 data[2] = {0};
u32 revision;
- u32 version;
int err;
/* get vendor name from root directory */
@@ -107,15 +105,9 @@ name_device(struct snd_bebob *bebob)
if (err < 0)
goto end;
- err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_BEBOB_VERSION,
- &version);
- if (err < 0)
- goto end;
- bebob->version = version;
-
- strcpy(bebob->card->driver, "BeBoB");
- strcpy(bebob->card->shortname, model);
- strcpy(bebob->card->mixername, model);
+ strscpy(bebob->card->driver, "BeBoB");
+ strscpy(bebob->card->shortname, model);
+ strscpy(bebob->card->mixername, model);
snprintf(bebob->card->longname, sizeof(bebob->card->longname),
"%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d",
vendor, model, hw_id, revision,
@@ -135,6 +127,9 @@ bebob_card_free(struct snd_card *card)
mutex_unlock(&devices_mutex);
snd_bebob_stream_destroy_duplex(bebob);
+
+ mutex_destroy(&bebob->mutex);
+ fw_unit_put(bebob->unit);
}
static const struct snd_bebob_spec *
@@ -162,16 +157,55 @@ check_audiophile_booted(struct fw_unit *unit)
return strncmp(name, "FW Audiophile Bootloader", 24) != 0;
}
-static void
-do_registration(struct work_struct *work)
+static int detect_quirks(struct snd_bebob *bebob, const struct ieee1394_device_id *entry)
+{
+ if (entry->vendor_id == VEN_MAUDIO) {
+ switch (entry->model_id) {
+ case MODEL_MAUDIO_PROFIRELIGHTBRIDGE:
+ // M-Audio ProFire Lightbridge has a quirk to transfer packets with
+ // discontinuous cycle or data block counter in early stage of packet
+ // streaming. The cycle span from the first packet with event is variable.
+ bebob->quirks |= SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC;
+ break;
+ case MODEL_MAUDIO_FW1814:
+ case MODEL_MAUDIO_PROJECTMIX:
+ // At high sampling rate, M-Audio special firmware transmits empty packet
+ // with the value of dbc incremented by 8.
+ bebob->quirks |= SND_BEBOB_QUIRK_WRONG_DBC;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_bebob *bebob =
- container_of(work, struct snd_bebob, dwork.work);
unsigned int card_index;
+ struct snd_card *card;
+ struct snd_bebob *bebob;
+ const struct snd_bebob_spec *spec;
int err;
- if (bebob->registered)
- return;
+ if (entry->vendor_id == VEN_FOCUSRITE &&
+ entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)
+ spec = get_saffire_spec(unit);
+ else if (entry->vendor_id == VEN_MAUDIO &&
+ entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH &&
+ !check_audiophile_booted(unit))
+ spec = NULL;
+ else
+ spec = (const struct snd_bebob_spec *)entry->driver_data;
+
+ if (spec == NULL) {
+ // To boot up M-Audio models.
+ if (entry->vendor_id == VEN_MAUDIO || entry->vendor_id == VEN_BRIDGECO)
+ return snd_bebob_maudio_load_firmware(unit);
+ else
+ return -ENODEV;
+ }
mutex_lock(&devices_mutex);
for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
@@ -180,27 +214,40 @@ do_registration(struct work_struct *work)
}
if (card_index >= SNDRV_CARDS) {
mutex_unlock(&devices_mutex);
- return;
+ return -ENOENT;
}
- err = snd_card_new(&bebob->unit->device, index[card_index],
- id[card_index], THIS_MODULE, 0, &bebob->card);
+ err = snd_card_new(&unit->device, index[card_index], id[card_index], THIS_MODULE,
+ sizeof(*bebob), &card);
if (err < 0) {
mutex_unlock(&devices_mutex);
- return;
+ return err;
}
+ card->private_free = bebob_card_free;
set_bit(card_index, devices_used);
mutex_unlock(&devices_mutex);
- bebob->card->private_free = bebob_card_free;
- bebob->card->private_data = bebob;
+ bebob = card->private_data;
+ bebob->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, bebob);
+ bebob->card = card;
+ bebob->card_index = card_index;
+
+ bebob->spec = spec;
+ mutex_init(&bebob->mutex);
+ spin_lock_init(&bebob->lock);
+ init_waitqueue_head(&bebob->hwdep_wait);
err = name_device(bebob);
if (err < 0)
goto error;
+ err = detect_quirks(bebob, entry);
+ if (err < 0)
+ goto error;
+
if (bebob->spec == &maudio_special_spec) {
- if (bebob->entry->model_id == MODEL_MAUDIO_FW1814)
+ if (entry->model_id == MODEL_MAUDIO_FW1814)
err = snd_bebob_maudio_special_discover(bebob, true);
else
err = snd_bebob_maudio_special_discover(bebob, false);
@@ -230,80 +277,26 @@ do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(bebob->card);
+ err = snd_card_register(card);
if (err < 0)
goto error;
- bebob->registered = true;
-
- return;
-error:
- snd_card_free(bebob->card);
- dev_info(&bebob->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int
-bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
-{
- struct snd_bebob *bebob;
- const struct snd_bebob_spec *spec;
-
- if (entry->vendor_id == VEN_FOCUSRITE &&
- entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)
- spec = get_saffire_spec(unit);
- else if (entry->vendor_id == VEN_MAUDIO1 &&
- entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH &&
- !check_audiophile_booted(unit))
- spec = NULL;
- else
- spec = (const struct snd_bebob_spec *)entry->driver_data;
-
- if (spec == NULL) {
- if (entry->vendor_id == VEN_MAUDIO1 ||
- entry->vendor_id == VEN_MAUDIO2)
- return snd_bebob_maudio_load_firmware(unit);
- else
- return -ENODEV;
- }
-
- /* Allocate this independent of sound card instance. */
- bebob = devm_kzalloc(&unit->device, sizeof(struct snd_bebob),
- GFP_KERNEL);
- if (!bebob)
- return -ENOMEM;
- bebob->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, bebob);
-
- bebob->entry = entry;
- bebob->spec = spec;
- mutex_init(&bebob->mutex);
- spin_lock_init(&bebob->lock);
- init_waitqueue_head(&bebob->hwdep_wait);
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&bebob->dwork, do_registration);
-
- if (entry->vendor_id != VEN_MAUDIO1 ||
- (entry->model_id != MODEL_MAUDIO_FW1814 &&
- entry->model_id != MODEL_MAUDIO_PROJECTMIX)) {
- snd_fw_schedule_registration(unit, &bebob->dwork);
- } else {
- /*
- * This is a workaround. This bus reset seems to have an effect
- * to make devices correctly handling transactions. Without
- * this, the devices have gap_count mismatch. This causes much
- * failure of transaction.
- *
- * Just after registration, user-land application receive
- * signals from dbus and starts I/Os. To avoid I/Os till the
- * future bus reset, registration is done in next update().
- */
- fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card,
- false, true);
+ if (entry->vendor_id == VEN_MAUDIO &&
+ (entry->model_id == MODEL_MAUDIO_FW1814 || entry->model_id == MODEL_MAUDIO_PROJECTMIX)) {
+ // This is a workaround. This bus reset seems to have an effect to make devices
+ // correctly handling transactions. Without this, the devices have gap_count
+ // mismatch. This causes much failure of transaction.
+ //
+ // Just after registration, user-land application receive signals from dbus and
+ // starts I/Os. To avoid I/Os till the future bus reset, registration is done in
+ // next update().
+ fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, false, true);
}
return 0;
+error:
+ snd_card_free(card);
+ return err;
}
/*
@@ -330,11 +323,7 @@ bebob_update(struct fw_unit *unit)
if (bebob == NULL)
return;
- /* Postpone a workqueue for deferred registration. */
- if (!bebob->registered)
- snd_fw_schedule_registration(unit, &bebob->dwork);
- else
- fcp_bus_reset(bebob->unit);
+ fcp_bus_reset(bebob->unit);
}
static void bebob_remove(struct fw_unit *unit)
@@ -344,20 +333,8 @@ static void bebob_remove(struct fw_unit *unit)
if (bebob == NULL)
return;
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&bebob->dwork);
-
- if (bebob->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(bebob->card);
- }
-
- mutex_destroy(&bebob->mutex);
- fw_unit_put(bebob->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(bebob->card);
}
static const struct snd_bebob_rate_spec normal_rate_spec = {
@@ -370,6 +347,22 @@ static const struct snd_bebob_spec spec_normal = {
.meter = NULL
};
+#define SPECIFIER_1394TA 0x00a02d
+
+// The immediate entry for version in unit directory differs depending on models:
+// * 0x010001
+// * 0x014001
+#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \
+{ \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID | \
+ IEEE1394_MATCH_SPECIFIER_ID, \
+ .vendor_id = vendor, \
+ .model_id = model, \
+ .specifier_id = SPECIFIER_1394TA, \
+ .driver_data = (kernel_ulong_t)data \
+}
+
static const struct ieee1394_device_id bebob_id_table[] = {
/* Edirol, FA-66 */
SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal),
@@ -386,9 +379,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_MACKIE2, 0x00010065, &spec_normal),
- /* Mackie, d.2 (Firewire Option) */
- SND_BEBOB_DEV_ENTRY(VEN_MACKIE1, 0x00010067, &spec_normal),
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
+ // Mackie, d.2 (optional Firewire card with DM1000).
+ SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
/* Stanton, ScratchAmp */
SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
/* Tascam, IF-FW DM */
@@ -410,17 +403,18 @@ static const struct ieee1394_device_id bebob_id_table[] = {
SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x01eeee, &spec_normal),
/* ESI, Quatafire610 */
SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal),
- /* AcousticReality, eARMasterOne */
- SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal),
/* CME, MatrixKFW */
SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal),
- /* Phonic, Helix Board 12 MkII */
+ // Phonic Helix Board 12 FireWire MkII.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal),
- /* Phonic, Helix Board 18 MkII */
+ // Phonic Helix Board 18 FireWire MkII.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal),
- /* Phonic, Helix Board 24 MkII */
+ // Phonic Helix Board 24 FireWire MkII.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal),
- /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */
+ // Phonic FireFly 808 FireWire.
+ SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00080000, &spec_normal),
+ // Phonic FireFly 202, 302, 808 Universal.
+ // Phinic Helix Board 12/18/24 FireWire, 12/18/24 Universal
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal),
/* Lynx, Aurora 8/16 (LT-FW) */
SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal),
@@ -438,7 +432,8 @@ static const struct ieee1394_device_id bebob_id_table[] = {
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &yamaha_terratec_spec),
/* TerraTec Electronic GmbH, EWS MIC2/MIC8 */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal),
- /* Terratec Electronic GmbH, Aureon 7.1 Firewire */
+ // Terratec Electronic GmbH, Aureon 7.1 Firewire.
+ // AcousticReality, eAR Master One, Eroica, Figaro, and Ciaccona. Perhaps Terratec OEM.
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal),
/* Yamaha, GO44 */
SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_terratec_spec),
@@ -447,45 +442,35 @@ static const struct ieee1394_device_id bebob_id_table[] = {
/* Focusrite, SaffirePro 26 I/O */
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec),
/* Focusrite, SaffirePro 10 I/O */
- {
- // The combination of vendor_id and model_id is the same as the
- // same as the one of Liquid Saffire 56.
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID |
- IEEE1394_MATCH_SPECIFIER_ID |
- IEEE1394_MATCH_VERSION,
- .vendor_id = VEN_FOCUSRITE,
- .model_id = 0x000006,
- .specifier_id = 0x00a02d,
- .version = 0x010001,
- .driver_data = (kernel_ulong_t)&saffirepro_10_spec,
- },
+ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x000006, &saffirepro_10_spec),
/* Focusrite, Saffire(no label and LE) */
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH,
&saffire_spec),
- /* M-Audio, Firewire 410 */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec),
+ // M-Audio, Firewire 410. The vendor field is left as BridgeCo. AG.
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010058, NULL),
+ SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010046, &maudio_fw410_spec),
/* M-Audio, Firewire Audiophile */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH,
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_AUDIOPHILE_BOTH,
&maudio_audiophile_spec),
/* M-Audio, Firewire Solo */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec),
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010062, &maudio_solo_spec),
/* M-Audio, Ozonic */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec),
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x0000000a, &maudio_ozonic_spec),
/* M-Audio NRV10 */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec),
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010081, &maudio_nrv10_spec),
/* M-Audio, ProFireLightbridge */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal),
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_PROFIRELIGHTBRIDGE, &spec_normal),
/* Firewire 1814 */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814,
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010070, NULL), /* bootloader */
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_FW1814,
&maudio_special_spec),
/* M-Audio ProjectMix */
- SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX,
+ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_PROJECTMIX,
&maudio_special_spec),
/* Digidesign Mbox 2 Pro */
SND_BEBOB_DEV_ENTRY(VEN_DIGIDESIGN, 0x0000a9, &spec_normal),
+ // Toneweal FW66.
+ SND_BEBOB_DEV_ENTRY(OUI_SHOUYO, 0x020002, &spec_normal),
/* IDs are unknown but able to be supported */
/* Apogee, Mini-ME Firewire */
/* Apogee, Mini-DAC Firewire */
@@ -496,11 +481,6 @@ static const struct ieee1394_device_id bebob_id_table[] = {
/* Infrasonic, Windy6 */
/* Mackie, Digital X Bus x.200 */
/* Mackie, Digital X Bus x.400 */
- /* Phonic, HB 12 */
- /* Phonic, HB 24 */
- /* Phonic, HB 18 */
- /* Phonic, FireFly 202 */
- /* Phonic, FireFly 302 */
/* Rolf Spuler, Firewire Guitar */
{}
};
diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h
index d1ad9a8451bc..4d73ecb30d79 100644
--- a/sound/firewire/bebob/bebob.h
+++ b/sound/firewire/bebob/bebob.h
@@ -75,6 +75,11 @@ struct snd_bebob_spec {
const struct snd_bebob_meter_spec *meter;
};
+enum snd_bebob_quirk {
+ SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC = (1 << 0),
+ SND_BEBOB_QUIRK_WRONG_DBC = (1 << 1),
+};
+
struct snd_bebob {
struct snd_card *card;
struct fw_unit *unit;
@@ -83,11 +88,8 @@ struct snd_bebob {
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
-
- const struct ieee1394_device_id *entry;
const struct snd_bebob_spec *spec;
+ unsigned int quirks; // Combination of snd_bebob_quirk enumerations.
unsigned int midi_input_ports;
unsigned int midi_output_ports;
@@ -113,9 +115,6 @@ struct snd_bebob {
/* for M-Audio special devices */
void *maudio_special_quirk;
- /* For BeBoB version quirk. */
- unsigned int version;
-
struct amdtp_domain domain;
};
@@ -200,6 +199,8 @@ int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
int avc_bridgeco_get_plug_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_type *type);
+int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int *ch_count);
int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int id, u8 *type);
@@ -252,13 +253,4 @@ 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);
-#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \
-{ \
- .match_flags = IEEE1394_MATCH_VENDOR_ID | \
- IEEE1394_MATCH_MODEL_ID, \
- .vendor_id = vendor, \
- .model_id = model, \
- .driver_data = (kernel_ulong_t)data \
-}
-
#endif
diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c
index e276ab8f9006..022df09c68ff 100644
--- a/sound/firewire/bebob/bebob_command.c
+++ b/sound/firewire/bebob/bebob_command.c
@@ -143,6 +143,42 @@ end:
return err;
}
+int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ unsigned int *ch_count)
+{
+ u8 *buf;
+ int err;
+
+ buf = kzalloc(12, GFP_KERNEL);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ // Info type is 'plug type'.
+ avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x02);
+
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12,
+ BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+ BIT(6) | BIT(7) | BIT(9));
+ if (err < 0)
+ ;
+ else if (err < 11)
+ err = -EIO;
+ else if (buf[0] == 0x08) // NOT IMPLEMENTED
+ err = -ENOSYS;
+ else if (buf[0] == 0x0a) // REJECTED
+ err = -EINVAL;
+ else if (buf[0] == 0x0b) // IN TRANSITION
+ err = -EAGAIN;
+ if (err < 0)
+ goto end;
+
+ *ch_count = buf[10];
+ err = 0;
+end:
+ kfree(buf);
+ return err;
+}
+
int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 *buf, unsigned int len)
diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c
index 45b740f44c45..5779e99a6bb2 100644
--- a/sound/firewire/bebob/bebob_hwdep.c
+++ b/sound/firewire/bebob/bebob_hwdep.c
@@ -36,13 +36,10 @@ hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
}
memset(&event, 0, sizeof(event));
- if (bebob->dev_lock_changed) {
- event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
- event.lock_status.status = (bebob->dev_lock_count > 0);
- bebob->dev_lock_changed = false;
-
- count = min_t(long, count, sizeof(event.lock_status));
- }
+ count = min_t(long, count, sizeof(event.lock_status));
+ event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+ event.lock_status.status = (bebob->dev_lock_count > 0);
+ bebob->dev_lock_changed = false;
spin_unlock_irq(&bebob->lock);
@@ -81,7 +78,7 @@ hwdep_get_info(struct snd_bebob *bebob, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -186,7 +183,7 @@ int snd_bebob_create_hwdep_device(struct snd_bebob *bebob)
err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep);
if (err < 0)
goto end;
- strcpy(hwdep->name, "BeBoB");
+ strscpy(hwdep->name, "BeBoB");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB;
hwdep->ops = ops;
hwdep->private_data = bebob;
diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c
index 6f597d03e7c1..b1425bf98c3b 100644
--- a/sound/firewire/bebob/bebob_midi.c
+++ b/sound/firewire/bebob/bebob_midi.c
@@ -84,9 +84,9 @@ static void set_midi_substream_names(struct snd_bebob *bebob,
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
- snprintf(subs->name, sizeof(subs->name),
- "%s MIDI %d",
- bebob->card->shortname, subs->number + 1);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ bebob->card->shortname, subs->number + 1);
}
}
diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c
index f8d9a2041264..360ebf3c4ca2 100644
--- a/sound/firewire/bebob/bebob_pcm.c
+++ b/sound/firewire/bebob/bebob_pcm.c
@@ -214,7 +214,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_bebob *bebob = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
@@ -236,7 +236,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&bebob->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
bebob->substreams_counter--;
snd_bebob_stream_stop_duplex(bebob);
@@ -367,6 +367,7 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
goto end;
pcm->private_data = bebob;
+ pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", bebob->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c
index bbae04793c50..8629b14ded76 100644
--- a/sound/firewire/bebob/bebob_stream.c
+++ b/sound/firewire/bebob/bebob_stream.c
@@ -7,8 +7,7 @@
#include "./bebob.h"
-#define CALLBACK_TIMEOUT 2500
-#define FW_ISO_RESOURCE_DELAY 1000
+#define READY_TIMEOUT_MS 4000
/*
* NOTE;
@@ -402,12 +401,6 @@ static void break_both_connections(struct snd_bebob *bebob)
{
cmp_connection_break(&bebob->in_conn);
cmp_connection_break(&bebob->out_conn);
-
- // These models seem to be in transition state for a longer time. When
- // accessing in the state, any transactions is corrupted. In the worst
- // case, the device is going to reboot.
- if (bebob->version < 2)
- msleep(600);
}
static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
@@ -437,6 +430,7 @@ static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
{
+ unsigned int flags = CIP_BLOCKING;
enum amdtp_stream_direction dir_stream;
struct cmp_connection *conn;
enum cmp_direction dir_conn;
@@ -452,32 +446,21 @@ static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
dir_conn = CMP_INPUT;
}
+ if (stream == &bebob->tx_stream) {
+ if (bebob->quirks & SND_BEBOB_QUIRK_WRONG_DBC)
+ flags |= CIP_EMPTY_HAS_WRONG_DBC;
+ }
+
err = cmp_connection_init(conn, bebob->unit, dir_conn, 0);
if (err < 0)
return err;
- err = amdtp_am824_init(stream, bebob->unit, dir_stream, CIP_BLOCKING);
+ err = amdtp_am824_init(stream, bebob->unit, dir_stream, flags);
if (err < 0) {
cmp_connection_destroy(conn);
return err;
}
- if (stream == &bebob->tx_stream) {
- // BeBoB v3 transfers packets with these qurks:
- // - In the beginning of streaming, the value of dbc is
- // incremented even if no data blocks are transferred.
- // - The value of dbc is reset suddenly.
- if (bebob->version > 2)
- bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC |
- CIP_SKIP_DBC_ZERO_CHECK;
-
- // At high sampling rate, M-Audio special firmware transmits
- // empty packet with the value of dbc incremented by 8 but the
- // others are valid to IEC 61883-1.
- if (bebob->maudio_special_quirk)
- bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC;
- }
-
return 0;
}
@@ -517,20 +500,22 @@ int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream,
unsigned int rate, unsigned int index)
{
- struct snd_bebob_stream_formation *formation;
+ unsigned int pcm_channels;
+ unsigned int midi_ports;
struct cmp_connection *conn;
int err;
if (stream == &bebob->tx_stream) {
- formation = bebob->tx_stream_formations + index;
+ pcm_channels = bebob->tx_stream_formations[index].pcm;
+ midi_ports = bebob->midi_input_ports;
conn = &bebob->out_conn;
} else {
- formation = bebob->rx_stream_formations + index;
+ pcm_channels = bebob->rx_stream_formations[index].pcm;
+ midi_ports = bebob->midi_output_ports;
conn = &bebob->in_conn;
}
- err = amdtp_am824_set_parameters(stream, rate, formation->pcm,
- formation->midi, false);
+ err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, false);
if (err < 0)
return err;
@@ -622,9 +607,8 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
if (!amdtp_stream_running(&bebob->rx_stream)) {
enum snd_bebob_clock_type src;
- struct amdtp_stream *master, *slave;
unsigned int curr_rate;
- unsigned int ir_delay_cycle;
+ unsigned int tx_init_skip_cycles;
if (bebob->maudio_special_quirk) {
err = bebob->spec->rate->get(bebob, &curr_rate);
@@ -636,36 +620,28 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
if (err < 0)
return err;
- if (src != SND_BEBOB_CLOCK_TYPE_SYT) {
- master = &bebob->tx_stream;
- slave = &bebob->rx_stream;
- } else {
- master = &bebob->rx_stream;
- slave = &bebob->tx_stream;
- }
-
- err = start_stream(bebob, master);
+ err = start_stream(bebob, &bebob->rx_stream);
if (err < 0)
goto error;
- err = start_stream(bebob, slave);
+ err = start_stream(bebob, &bebob->tx_stream);
if (err < 0)
goto error;
- // The device postpones start of transmission mostly for 1 sec
- // after receives packets firstly. For safe, IR context starts
- // 0.4 sec (=3200 cycles) later to version 1 or 2 firmware,
- // 2.0 sec (=16000 cycles) for version 3 firmware. This is
- // within 2.5 sec (=CALLBACK_TIMEOUT).
- // Furthermore, some devices transfer isoc packets with
- // discontinuous counter in the beginning of packet streaming.
- // The delay has an effect to avoid detection of this
- // discontinuity.
- if (bebob->version < 2)
- ir_delay_cycle = 3200;
+ if (!(bebob->quirks & SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC))
+ tx_init_skip_cycles = 0;
else
- ir_delay_cycle = 16000;
- err = amdtp_domain_start(&bebob->domain, ir_delay_cycle);
+ tx_init_skip_cycles = 16000;
+
+ // MEMO: Some devices start packet transmission long enough after establishment of
+ // CMP connection. In the early stage of packet streaming, any device transfers
+ // NODATA packets. After several hundred cycles, it begins to multiplex event into
+ // the packet with adequate value of syt field in CIP header. Some devices are
+ // strictly to generate any discontinuity in the sequence of tx packet when they
+ // receives inadequate sequence of value in syt field of CIP header. In the case,
+ // the request to break CMP connection is often corrupted, then any transaction
+ // results in unrecoverable error, sometimes generate bus-reset.
+ err = amdtp_domain_start(&bebob->domain, tx_init_skip_cycles, true, false);
if (err < 0)
goto error;
@@ -682,10 +658,9 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
}
}
- if (!amdtp_stream_wait_callback(&bebob->rx_stream,
- CALLBACK_TIMEOUT) ||
- !amdtp_stream_wait_callback(&bebob->tx_stream,
- CALLBACK_TIMEOUT)) {
+ // Some devices postpone start of transmission mostly for 1 sec after receives
+ // packets firstly.
+ if (!amdtp_domain_wait_ready(&bebob->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
@@ -796,42 +771,42 @@ parse_stream_formation(u8 *buf, unsigned int len,
return 0;
}
-static int
-fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir,
- unsigned short pid)
+static int fill_stream_formations(struct snd_bebob *bebob, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
+ enum avc_bridgeco_plug_dir plug_dir, unsigned int plug_id,
+ struct snd_bebob_stream_formation *formations)
{
+ enum avc_bridgeco_plug_type plug_type;
u8 *buf;
- struct snd_bebob_stream_formation *formations;
unsigned int len, eid;
- u8 addr[AVC_BRIDGECO_ADDR_BYTES];
int err;
+ avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id);
+
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "Fail to get type for isoc %d plug 0: %d\n", plug_dir, err);
+ return err;
+ } else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_ISOC)
+ return -ENXIO;
+
buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
- if (dir == AVC_BRIDGECO_PLUG_DIR_IN)
- formations = bebob->rx_stream_formations;
- else
- formations = bebob->tx_stream_formations;
+ for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; ++eid) {
+ avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id);
- for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) {
len = FORMAT_MAXIMUM_LENGTH;
- avc_bridgeco_fill_unit_addr(addr, dir,
- AVC_BRIDGECO_PLUG_UNIT_ISOC, pid);
- err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf,
- &len, eid);
- /* No entries remained. */
+ err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, &len, eid);
+ // No entries remained.
if (err == -EINVAL && eid > 0) {
err = 0;
break;
} else if (err < 0) {
dev_err(&bebob->unit->device,
- "fail to get stream format %d for isoc %s plug %d:%d\n",
- eid,
- (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" :
- "out",
- pid, err);
+ "fail to get stream format %d for isoc %d plug %d:%d\n",
+ eid, plug_dir, plug_id, err);
break;
}
@@ -844,6 +819,54 @@ fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir,
return err;
}
+static int detect_midi_ports(struct snd_bebob *bebob,
+ const struct snd_bebob_stream_formation *formats,
+ u8 addr[AVC_BRIDGECO_ADDR_BYTES], enum avc_bridgeco_plug_dir plug_dir,
+ unsigned int plug_count, unsigned int *midi_ports)
+{
+ int i;
+ int err = 0;
+
+ *midi_ports = 0;
+
+ /// Detect the number of available MIDI ports when packet has MIDI conformant data channel.
+ for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; ++i) {
+ if (formats[i].midi > 0)
+ break;
+ }
+ if (i >= SND_BEBOB_STRM_FMT_ENTRIES)
+ return 0;
+
+ for (i = 0; i < plug_count; ++i) {
+ enum avc_bridgeco_plug_type plug_type;
+ unsigned int ch_count;
+
+ avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_EXT, i);
+
+ err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type);
+ if (err < 0) {
+ dev_err(&bebob->unit->device,
+ "fail to get type for external %d plug %d: %d\n",
+ plug_dir, i, err);
+ break;
+ } else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_MIDI) {
+ continue;
+ }
+
+ err = avc_bridgeco_get_plug_ch_count(bebob->unit, addr, &ch_count);
+ if (err < 0)
+ break;
+ // Yamaha GO44, GO46, Terratec Phase 24, Phase x24 reports 0 for the number of
+ // channels in external output plug 3 (MIDI type) even if it has a pair of physical
+ // MIDI jacks. As a workaround, assume it as one.
+ if (ch_count == 0)
+ ch_count = 1;
+ *midi_ports += ch_count;
+ }
+
+ return err;
+}
+
static int
seek_msu_sync_input_plug(struct snd_bebob *bebob)
{
@@ -886,8 +909,6 @@ int snd_bebob_stream_discover(struct snd_bebob *bebob)
{
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;
int err;
/* the number of plugs for isoc in/out, ext in/out */
@@ -908,67 +929,25 @@ int snd_bebob_stream_discover(struct snd_bebob *bebob)
goto end;
}
- avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
- AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
- err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
- if (err < 0) {
- dev_err(&bebob->unit->device,
- "fail to get type for isoc in plug 0: %d\n", err);
- goto end;
- } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) {
- err = -ENOSYS;
- goto end;
- }
- err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0);
+ err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_IN, 0,
+ bebob->rx_stream_formations);
if (err < 0)
goto end;
- avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT,
- AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
- err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
- if (err < 0) {
- dev_err(&bebob->unit->device,
- "fail to get type for isoc out plug 0: %d\n", err);
- goto end;
- } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) {
- err = -ENOSYS;
- goto end;
- }
- err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0);
+ err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_OUT, 0,
+ bebob->tx_stream_formations);
if (err < 0)
goto end;
- /* count external input plugs for MIDI */
- bebob->midi_input_ports = 0;
- for (i = 0; i < plugs[2]; i++) {
- avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
- AVC_BRIDGECO_PLUG_UNIT_EXT, i);
- err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
- if (err < 0) {
- dev_err(&bebob->unit->device,
- "fail to get type for external in plug %d: %d\n",
- i, err);
- goto end;
- } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) {
- bebob->midi_input_ports++;
- }
- }
+ err = detect_midi_ports(bebob, bebob->tx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_IN,
+ plugs[2], &bebob->midi_input_ports);
+ if (err < 0)
+ goto end;
- /* count external output plugs for MIDI */
- bebob->midi_output_ports = 0;
- for (i = 0; i < plugs[3]; i++) {
- avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT,
- AVC_BRIDGECO_PLUG_UNIT_EXT, i);
- err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
- if (err < 0) {
- dev_err(&bebob->unit->device,
- "fail to get type for external out plug %d: %d\n",
- i, err);
- goto end;
- } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) {
- bebob->midi_output_ports++;
- }
- }
+ err = detect_midi_ports(bebob, bebob->rx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_OUT,
+ plugs[3], &bebob->midi_output_ports);
+ if (err < 0)
+ goto end;
/* for check source of clock later */
if (!clk_spec)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c
index b596bec19774..f5028a061a91 100644
--- a/sound/firewire/cmp.c
+++ b/sound/firewire/cmp.c
@@ -333,53 +333,6 @@ retry_after_bus_reset:
}
EXPORT_SYMBOL(cmp_connection_establish);
-/**
- * cmp_connection_update - update the connection after a bus reset
- * @c: the connection manager
- *
- * This function must be called from the driver's .update handler to
- * reestablish any connection that might have been active.
- *
- * Returns zero on success, or a negative error code. On an error, the
- * connection is broken and the caller must stop transmitting iso packets.
- */
-int cmp_connection_update(struct cmp_connection *c)
-{
- int err;
-
- mutex_lock(&c->mutex);
-
- if (!c->connected) {
- mutex_unlock(&c->mutex);
- return 0;
- }
-
- err = fw_iso_resources_update(&c->resources);
- if (err < 0)
- goto err_unconnect;
-
- if (c->direction == CMP_OUTPUT)
- err = pcr_modify(c, opcr_set_modify, pcr_set_check,
- SUCCEED_ON_BUS_RESET);
- else
- err = pcr_modify(c, ipcr_set_modify, pcr_set_check,
- SUCCEED_ON_BUS_RESET);
-
- if (err < 0)
- goto err_unconnect;
-
- mutex_unlock(&c->mutex);
-
- return 0;
-
-err_unconnect:
- c->connected = false;
- mutex_unlock(&c->mutex);
-
- return err;
-}
-EXPORT_SYMBOL(cmp_connection_update);
-
static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr)
{
return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK);
diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h
index 26ab88000e34..66fc08b742d2 100644
--- a/sound/firewire/cmp.h
+++ b/sound/firewire/cmp.h
@@ -47,7 +47,6 @@ int cmp_connection_reserve(struct cmp_connection *connection,
void cmp_connection_release(struct cmp_connection *connection);
int cmp_connection_establish(struct cmp_connection *connection);
-int cmp_connection_update(struct cmp_connection *connection);
void cmp_connection_break(struct cmp_connection *connection);
#endif
diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile
index 7a62dafd0f78..36e25a3cf3c6 100644
--- a/sound/firewire/dice/Makefile
+++ b/sound/firewire/dice/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \
+snd-dice-y := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \
dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o \
- dice-alesis.o dice-extension.o dice-mytek.o dice-presonus.o
+ dice-alesis.o dice-extension.o dice-mytek.o dice-presonus.o \
+ dice-harman.o dice-focusrite.o dice-weiss.o
obj-$(CONFIG_SND_DICE) += snd-dice.o
diff --git a/sound/firewire/dice/dice-alesis.c b/sound/firewire/dice/dice-alesis.c
index 0916864511d5..27c13b9cc9ef 100644
--- a/sound/firewire/dice/dice-alesis.c
+++ b/sound/firewire/dice/dice-alesis.c
@@ -16,7 +16,7 @@ alesis_io14_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = {
static const unsigned int
alesis_io26_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = {
{10, 10, 4}, /* Tx0 = Analog + S/PDIF. */
- {16, 8, 0}, /* Tx1 = ADAT1 + ADAT2. */
+ {16, 4, 0}, /* Tx1 = ADAT1 + ADAT2 (available at low rate). */
};
int snd_dice_detect_alesis_formats(struct snd_dice *dice)
diff --git a/sound/firewire/dice/dice-focusrite.c b/sound/firewire/dice/dice-focusrite.c
new file mode 100644
index 000000000000..ea27cfb01cc0
--- /dev/null
+++ b/sound/firewire/dice/dice-focusrite.c
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+// dice-focusrite.c - a part of driver for DICE based devices
+//
+// Copyright (c) 2022 Takashi Sakamoto
+
+#include "dice.h"
+
+int snd_dice_detect_focusrite_pro40_tcd3070_formats(struct snd_dice *dice)
+{
+ // Focusrite shipped several variants of Saffire Pro 40. One of them is based on TCD3070-CH
+ // apart from the others with TCD2220. It doesn't support TCAT protocol extension.
+ dice->tx_pcm_chs[0][0] = 20;
+ dice->tx_midi_ports[0] = 1;
+ dice->rx_pcm_chs[0][0] = 20;
+ dice->rx_midi_ports[0] = 1;
+
+ dice->tx_pcm_chs[0][1] = 16;
+ dice->tx_midi_ports[1] = 1;
+ dice->rx_pcm_chs[0][1] = 16;
+ dice->rx_midi_ports[1] = 1;
+
+ return 0;
+}
diff --git a/sound/firewire/dice/dice-harman.c b/sound/firewire/dice/dice-harman.c
new file mode 100644
index 000000000000..212ae77dfca2
--- /dev/null
+++ b/sound/firewire/dice/dice-harman.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+// dice-harman.c - a part of driver for DICE based devices
+//
+// Copyright (c) 2021 Takashi Sakamoto
+
+#include "dice.h"
+
+int snd_dice_detect_harman_formats(struct snd_dice *dice)
+{
+ int i;
+
+ // Lexicon I-ONYX FW810s supports sampling transfer frequency up to
+ // 96.0 kHz, 12 PCM channels and 1 MIDI channel in its first tx stream
+ // , 10 PCM channels and 1 MIDI channel in its first rx stream for all
+ // of the frequencies.
+ for (i = 0; i < 2; ++i) {
+ dice->tx_pcm_chs[0][i] = 12;
+ dice->tx_midi_ports[0] = 1;
+ dice->rx_pcm_chs[0][i] = 10;
+ dice->rx_midi_ports[0] = 1;
+ }
+
+ return 0;
+}
diff --git a/sound/firewire/dice/dice-hwdep.c b/sound/firewire/dice/dice-hwdep.c
index f69f7996762f..d165dd427bd3 100644
--- a/sound/firewire/dice/dice-hwdep.c
+++ b/sound/firewire/dice/dice-hwdep.c
@@ -79,7 +79,7 @@ static int hwdep_get_info(struct snd_dice *dice, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -179,7 +179,7 @@ int snd_dice_create_hwdep(struct snd_dice *dice)
err = snd_hwdep_new(dice->card, "DICE", 0, &hwdep);
if (err < 0)
return err;
- strcpy(hwdep->name, "DICE");
+ strscpy(hwdep->name, "DICE");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE;
hwdep->ops = ops;
hwdep->private_data = dice;
diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c
index 4c2998034313..78988e44b8bc 100644
--- a/sound/firewire/dice/dice-midi.c
+++ b/sound/firewire/dice/dice-midi.c
@@ -88,8 +88,8 @@ static void set_midi_substream_names(struct snd_dice *dice,
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
- snprintf(subs->name, sizeof(subs->name),
- "%s MIDI %d", dice->card->shortname, subs->number + 1);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d", dice->card->shortname, subs->number + 1);
}
}
diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c
index af8a90ee40f3..cfc19bd0d5dd 100644
--- a/sound/firewire/dice/dice-pcm.c
+++ b/sound/firewire/dice/dice-pcm.c
@@ -218,7 +218,7 @@ static int pcm_open(struct snd_pcm_substream *substream)
if (frames_per_period > 0) {
// For double_pcm_frame quirk.
- if (rate > 96000) {
+ if (rate > 96000 && !dice->disable_double_pcm_frames) {
frames_per_period *= 2;
frames_per_buffer *= 2;
}
@@ -266,14 +266,14 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_dice *dice = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int events_per_period = params_period_size(hw_params);
unsigned int events_per_buffer = params_buffer_size(hw_params);
mutex_lock(&dice->mutex);
// For double_pcm_frame quirk.
- if (rate > 96000) {
+ if (rate > 96000 && !dice->disable_double_pcm_frames) {
events_per_period /= 2;
events_per_buffer /= 2;
}
@@ -293,7 +293,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&dice->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--dice->substreams_counter;
snd_dice_stream_stop_duplex(dice);
@@ -441,7 +441,8 @@ int snd_dice_create_pcm(struct snd_dice *dice)
if (err < 0)
return err;
pcm->private_data = dice;
- strcpy(pcm->name, dice->card->shortname);
+ pcm->nonatomic = true;
+ strscpy(pcm->name, dice->card->shortname);
if (capture > 0)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
diff --git a/sound/firewire/dice/dice-presonus.c b/sound/firewire/dice/dice-presonus.c
index 503f462a83f4..967cc3119a64 100644
--- a/sound/firewire/dice/dice-presonus.c
+++ b/sound/firewire/dice/dice-presonus.c
@@ -2,8 +2,6 @@
// dice-presonus.c - a part of driver for DICE based devices
//
// Copyright (c) 2019 Takashi Sakamoto
-//
-// Licensed under the terms of the GNU General Public License, version 2.
#include "dice.h"
diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c
index 8e0c0380b4c4..4c677c8546c7 100644
--- a/sound/firewire/dice/dice-stream.c
+++ b/sound/firewire/dice/dice-stream.c
@@ -8,8 +8,8 @@
#include "dice.h"
-#define CALLBACK_TIMEOUT 200
-#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC)
+#define READY_TIMEOUT_MS 200
+#define NOTIFICATION_TIMEOUT_MS 100
struct reg_params {
unsigned int count;
@@ -57,13 +57,9 @@ int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate,
return -EINVAL;
}
-/*
- * This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE
- * to GLOBAL_STATUS. Especially, just after powering on, these are different.
- */
-static int ensure_phase_lock(struct snd_dice *dice, unsigned int rate)
+static int select_clock(struct snd_dice *dice, unsigned int rate)
{
- __be32 reg, nominal;
+ __be32 reg, new;
u32 data;
int i;
int err;
@@ -87,24 +83,15 @@ static int ensure_phase_lock(struct snd_dice *dice, unsigned int rate)
if (completion_done(&dice->clock_accepted))
reinit_completion(&dice->clock_accepted);
- reg = cpu_to_be32(data);
+ new = cpu_to_be32(data);
err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
- &reg, sizeof(reg));
+ &new, sizeof(new));
if (err < 0)
return err;
if (wait_for_completion_timeout(&dice->clock_accepted,
msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) {
- /*
- * Old versions of Dice firmware transfer no notification when
- * the same clock status as current one is set. In this case,
- * just check current clock status.
- */
- err = snd_dice_transaction_read_global(dice, GLOBAL_STATUS,
- &nominal, sizeof(nominal));
- if (err < 0)
- return err;
- if (!(be32_to_cpu(nominal) & STATUS_SOURCE_LOCKED))
+ if (reg != new)
return -ETIMEDOUT;
}
@@ -181,7 +168,7 @@ static int keep_resources(struct snd_dice *dice, struct amdtp_stream *stream,
// as 'Dual Wire'.
// For this quirk, blocking mode is required and PCM buffer size should
// be aligned to SYT_INTERVAL.
- double_pcm_frames = rate > 96000;
+ double_pcm_frames = (rate > 96000 && !dice->disable_double_pcm_frames);
if (double_pcm_frames) {
rate /= 2;
pcm_chs *= 2;
@@ -304,7 +291,7 @@ int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate,
// Just after owning the unit (GLOBAL_OWNER), the unit can
// return invalid stream formats. Selecting clock parameters
// have an effect for the unit to refine it.
- err = ensure_phase_lock(dice, rate);
+ err = select_clock(dice, rate);
if (err < 0)
return err;
@@ -459,20 +446,17 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice)
goto error;
}
- err = amdtp_domain_start(&dice->domain, 0);
+ // MEMO: The device immediately starts packet transmission when enabled. Some
+ // devices are strictly to generate any discontinuity in the sequence of tx packet
+ // when they receives invalid sequence of presentation time in CIP header. The
+ // sequence replay for media clock recovery can suppress the behaviour.
+ err = amdtp_domain_start(&dice->domain, 0, true, false);
if (err < 0)
goto error;
- for (i = 0; i < MAX_STREAMS; i++) {
- if ((i < tx_params.count &&
- !amdtp_stream_wait_callback(&dice->tx_stream[i],
- CALLBACK_TIMEOUT)) ||
- (i < rx_params.count &&
- !amdtp_stream_wait_callback(&dice->rx_stream[i],
- CALLBACK_TIMEOUT))) {
- err = -ETIMEDOUT;
- goto error;
- }
+ if (!amdtp_domain_wait_ready(&dice->domain, READY_TIMEOUT_MS)) {
+ err = -ETIMEDOUT;
+ goto error;
}
}
@@ -493,11 +477,10 @@ void snd_dice_stream_stop_duplex(struct snd_dice *dice)
struct reg_params tx_params, rx_params;
if (dice->substreams_counter == 0) {
- if (get_register_params(dice, &tx_params, &rx_params) >= 0) {
- amdtp_domain_stop(&dice->domain);
+ if (get_register_params(dice, &tx_params, &rx_params) >= 0)
finish_session(dice, &tx_params, &rx_params);
- }
+ amdtp_domain_stop(&dice->domain);
release_resources(dice);
}
}
@@ -654,7 +637,7 @@ int snd_dice_stream_detect_current_formats(struct snd_dice *dice)
* invalid stream formats. Selecting clock parameters have an effect
* for the unit to refine it.
*/
- err = ensure_phase_lock(dice, rate);
+ err = select_clock(dice, rate);
if (err < 0)
return err;
diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c
index a8875d24ba2a..43a3bcb15b3d 100644
--- a/sound/firewire/dice/dice-tcelectronic.c
+++ b/sound/firewire/dice/dice-tcelectronic.c
@@ -38,8 +38,8 @@ static const struct dice_tc_spec konnekt_24d = {
};
static const struct dice_tc_spec konnekt_live = {
- .tx_pcm_chs = {{16, 16, 16}, {0, 0, 0} },
- .rx_pcm_chs = {{16, 16, 16}, {0, 0, 0} },
+ .tx_pcm_chs = {{16, 16, 6}, {0, 0, 0} },
+ .rx_pcm_chs = {{16, 16, 6}, {0, 0, 0} },
.has_midi = true,
};
diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c
index 2c0dde29a024..92941ef83cd5 100644
--- a/sound/firewire/dice/dice-transaction.c
+++ b/sound/firewire/dice/dice-transaction.c
@@ -155,7 +155,7 @@ static void dice_notification(struct fw_card *card, struct fw_request *request,
fw_send_response(card, request, RCODE_COMPLETE);
- if (bits & NOTIFY_LOCK_CHG)
+ if (bits & NOTIFY_CLOCK_ACCEPTED)
complete(&dice->clock_accepted);
wake_up(&dice->hwdep_wait);
}
diff --git a/sound/firewire/dice/dice-weiss.c b/sound/firewire/dice/dice-weiss.c
new file mode 100644
index 000000000000..129d43408956
--- /dev/null
+++ b/sound/firewire/dice/dice-weiss.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+// dice-weiss.c - a part of driver for DICE based devices
+//
+// Copyright (c) 2023 Rolf Anderegg and Michele Perrone
+
+#include "dice.h"
+
+struct dice_weiss_spec {
+ unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT];
+ unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT];
+};
+
+// Weiss DAC202: 192kHz 2-channel DAC
+static const struct dice_weiss_spec dac202 = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss MAN301: 192kHz 2-channel music archive network player
+static const struct dice_weiss_spec man301 = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss INT202: 192kHz unidirectional 2-channel digital Firewire nterface
+static const struct dice_weiss_spec int202 = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss INT203: 192kHz bidirectional 2-channel digital Firewire nterface
+static const struct dice_weiss_spec int203 = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss ADC2: 192kHz A/D converter with microphone preamps and line nputs
+static const struct dice_weiss_spec adc2 = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss DAC2/Minerva: 192kHz 2-channel DAC
+static const struct dice_weiss_spec dac2_minerva = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss Vesta: 192kHz 2-channel Firewire to AES/EBU interface
+static const struct dice_weiss_spec vesta = {
+ .tx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+ .rx_pcm_chs = {{2, 2, 2}, {0, 0, 0} },
+};
+
+// Weiss AFI1: 192kHz 24-channel Firewire to ADAT or AES/EBU interface
+static const struct dice_weiss_spec afi1 = {
+ .tx_pcm_chs = {{24, 16, 8}, {0, 0, 0} },
+ .rx_pcm_chs = {{24, 16, 8}, {0, 0, 0} },
+};
+
+int snd_dice_detect_weiss_formats(struct snd_dice *dice)
+{
+ static const struct {
+ u32 model_id;
+ const struct dice_weiss_spec *spec;
+ } *entry, entries[] = {
+ {0x000007, &dac202},
+ {0x000008, &dac202}, // Maya edition: same audio I/O as DAC202.
+ {0x000006, &int202},
+ {0x00000a, &int203},
+ {0x00000b, &man301},
+ {0x000001, &adc2},
+ {0x000003, &dac2_minerva},
+ {0x000002, &vesta},
+ {0x000004, &afi1},
+ };
+ struct fw_csr_iterator it;
+ int key, val, model_id;
+ int i;
+
+ model_id = 0;
+ fw_csr_iterator_init(&it, dice->unit->directory);
+ while (fw_csr_iterator_next(&it, &key, &val)) {
+ if (key == CSR_MODEL) {
+ model_id = val;
+ break;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(entries); ++i) {
+ entry = entries + i;
+ if (entry->model_id == model_id)
+ break;
+ }
+ if (i == ARRAY_SIZE(entries))
+ return -ENODEV;
+
+ memcpy(dice->tx_pcm_chs, entry->spec->tx_pcm_chs,
+ MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int));
+ memcpy(dice->rx_pcm_chs, entry->spec->rx_pcm_chs,
+ MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int));
+
+ return 0;
+}
diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c
index 06c94f009dfb..9675ec14271d 100644
--- a/sound/firewire/dice/dice.c
+++ b/sound/firewire/dice/dice.c
@@ -9,7 +9,7 @@
MODULE_DESCRIPTION("DICE driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
#define OUI_WEISS 0x001c6a
#define OUI_LOUD 0x000ff2
@@ -20,10 +20,13 @@ MODULE_LICENSE("GPL v2");
#define OUI_MYTEK 0x001ee8
#define OUI_SSL 0x0050c2 // Actually ID reserved by IEEE.
#define OUI_PRESONUS 0x000a92
+#define OUI_HARMAN 0x000fd7
+#define OUI_AVID 0x00a07e
#define DICE_CATEGORY_ID 0x04
#define WEISS_CATEGORY_ID 0x00
#define LOUD_CATEGORY_ID 0x10
+#define HARMAN_CATEGORY_ID 0x20
#define MODEL_ALESIS_IO_BOTH 0x000001
@@ -56,6 +59,8 @@ static int check_dice_category(struct fw_unit *unit)
category = WEISS_CATEGORY_ID;
else if (vendor == OUI_LOUD)
category = LOUD_CATEGORY_ID;
+ else if (vendor == OUI_HARMAN)
+ category = HARMAN_CATEGORY_ID;
else
category = DICE_CATEGORY_ID;
if (device->config_rom[3] != ((vendor << 8) | category) ||
@@ -97,9 +102,9 @@ static void dice_card_strings(struct snd_dice *dice)
unsigned int i;
int err;
- strcpy(card->driver, "DICE");
+ strscpy(card->driver, "DICE");
- strcpy(card->shortname, "DICE");
+ strscpy(card->shortname, "DICE");
BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname));
err = snd_dice_transaction_read_global(dice, GLOBAL_NICK_NAME,
card->shortname,
@@ -112,16 +117,16 @@ static void dice_card_strings(struct snd_dice *dice)
card->shortname[sizeof(card->shortname) - 1] = '\0';
}
- strcpy(vendor, "?");
+ strscpy(vendor, "?");
fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor));
- strcpy(model, "?");
+ strscpy(model, "?");
fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model));
snprintf(card->longname, sizeof(card->longname),
"%s %s (serial %u) at %s, S%d",
vendor, model, dev->config_rom[4] & 0x3fffff,
dev_name(&dice->unit->device), 100 << dev->max_speed);
- strcpy(card->mixername, "DICE");
+ strscpy(card->mixername, "DICE");
}
static void dice_card_free(struct snd_card *card)
@@ -130,22 +135,51 @@ static void dice_card_free(struct snd_card *card)
snd_dice_stream_destroy_duplex(dice);
snd_dice_transaction_destroy(dice);
+
+ mutex_destroy(&dice->mutex);
+ fw_unit_put(dice->unit);
}
-static void do_registration(struct work_struct *work)
+static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_dice *dice = container_of(work, struct snd_dice, dwork.work);
+ struct snd_card *card;
+ struct snd_dice *dice;
+ snd_dice_detect_formats_t detect_formats;
int err;
- if (dice->registered)
- return;
+ if (!entry->driver_data && entry->vendor_id != OUI_SSL) {
+ err = check_dice_category(unit);
+ if (err < 0)
+ return -ENODEV;
+ }
- err = snd_card_new(&dice->unit->device, -1, NULL, THIS_MODULE, 0,
- &dice->card);
+ err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*dice), &card);
if (err < 0)
- return;
- dice->card->private_free = dice_card_free;
- dice->card->private_data = dice;
+ return err;
+ card->private_free = dice_card_free;
+
+ dice = card->private_data;
+ dice->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, dice);
+ dice->card = card;
+
+ if (!entry->driver_data)
+ detect_formats = snd_dice_stream_detect_current_formats;
+ else
+ detect_formats = (snd_dice_detect_formats_t)entry->driver_data;
+
+ // Below models are compliant to IEC 61883-1/6 and have no quirk at high sampling transfer
+ // frequency.
+ // * Avid M-Box 3 Pro
+ // * M-Audio Profire 610
+ // * M-Audio Profire 2626
+ if (entry->vendor_id == OUI_MAUDIO || entry->vendor_id == OUI_AVID)
+ dice->disable_double_pcm_frames = true;
+
+ spin_lock_init(&dice->lock);
+ mutex_init(&dice->mutex);
+ init_completion(&dice->clock_accepted);
+ init_waitqueue_head(&dice->hwdep_wait);
err = snd_dice_transaction_init(dice);
if (err < 0)
@@ -157,7 +191,7 @@ static void do_registration(struct work_struct *work)
dice_card_strings(dice);
- err = dice->detect_formats(dice);
+ err = detect_formats(dice);
if (err < 0)
goto error;
@@ -179,102 +213,54 @@ static void do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(dice->card);
+ err = snd_card_register(card);
if (err < 0)
goto error;
- dice->registered = true;
-
- return;
-error:
- snd_card_free(dice->card);
- dev_info(&dice->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int dice_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *entry)
-{
- struct snd_dice *dice;
- int err;
-
- if (!entry->driver_data && entry->vendor_id != OUI_SSL) {
- err = check_dice_category(unit);
- if (err < 0)
- return -ENODEV;
- }
-
- /* Allocate this independent of sound card instance. */
- dice = devm_kzalloc(&unit->device, sizeof(struct snd_dice), GFP_KERNEL);
- if (!dice)
- return -ENOMEM;
- dice->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, dice);
-
- if (!entry->driver_data) {
- dice->detect_formats = snd_dice_stream_detect_current_formats;
- } else {
- dice->detect_formats =
- (snd_dice_detect_formats_t)entry->driver_data;
- }
-
- spin_lock_init(&dice->lock);
- mutex_init(&dice->mutex);
- init_completion(&dice->clock_accepted);
- init_waitqueue_head(&dice->hwdep_wait);
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&dice->dwork, do_registration);
- snd_fw_schedule_registration(unit, &dice->dwork);
-
return 0;
+error:
+ snd_card_free(card);
+ return err;
}
static void dice_remove(struct fw_unit *unit)
{
struct snd_dice *dice = dev_get_drvdata(&unit->device);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&dice->dwork);
-
- if (dice->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(dice->card);
- }
-
- mutex_destroy(&dice->mutex);
- fw_unit_put(dice->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(dice->card);
}
static void dice_bus_reset(struct fw_unit *unit)
{
struct snd_dice *dice = dev_get_drvdata(&unit->device);
- /* Postpone a workqueue for deferred registration. */
- if (!dice->registered)
- snd_fw_schedule_registration(unit, &dice->dwork);
-
/* The handler address register becomes initialized. */
snd_dice_transaction_reinit(dice);
- /*
- * After registration, userspace can start packet streaming, then this
- * code block works fine.
- */
- if (dice->registered) {
- mutex_lock(&dice->mutex);
- snd_dice_stream_update_duplex(dice);
- mutex_unlock(&dice->mutex);
- }
+ mutex_lock(&dice->mutex);
+ snd_dice_stream_update_duplex(dice);
+ mutex_unlock(&dice->mutex);
}
#define DICE_INTERFACE 0x000001
+#define DICE_DEV_ENTRY_TYPICAL(vendor, model, data) \
+ { \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID | \
+ IEEE1394_MATCH_SPECIFIER_ID | \
+ IEEE1394_MATCH_VERSION, \
+ .vendor_id = (vendor), \
+ .model_id = (model), \
+ .specifier_id = (vendor), \
+ .version = DICE_INTERFACE, \
+ .driver_data = (kernel_ulong_t)(data), \
+ }
+
static const struct ieee1394_device_id dice_id_table[] = {
+ // Avid M-Box 3 Pro. To match in probe function.
+ DICE_DEV_ENTRY_TYPICAL(OUI_AVID, 0x000004, snd_dice_detect_extension_formats),
/* M-Audio Profire 2626 has a different value in version field. */
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
@@ -388,6 +374,87 @@ static const struct ieee1394_device_id dice_id_table[] = {
.model_id = 0x000008,
.driver_data = (kernel_ulong_t)snd_dice_detect_presonus_formats,
},
+ // Lexicon I-ONYX FW810S.
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_HARMAN,
+ .model_id = 0x000001,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_harman_formats,
+ },
+ // Focusrite Saffire Pro 40 with TCD3070-CH.
+ // The model has quirk in its GUID, in which model field is 0x000013 and different from
+ // model ID (0x0000de) in its root/unit directory.
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_FOCUSRITE,
+ .model_id = 0x0000de,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_focusrite_pro40_tcd3070_formats,
+ },
+ // Weiss DAC202: 192kHz 2-channel DAC
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000007,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss DAC202: 192kHz 2-channel DAC (Maya edition)
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000008,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss MAN301: 192kHz 2-channel music archive network player
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x00000b,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss INT202: 192kHz unidirectional 2-channel digital Firewire face
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000006,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss INT203: 192kHz bidirectional 2-channel digital Firewire face
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x00000a,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss ADC2: 192kHz A/D converter with microphone preamps and inputs
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000001,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss DAC2/Minerva: 192kHz 2-channel DAC
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000003,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss Vesta: 192kHz 2-channel Firewire to AES/EBU interface
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000002,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
+ // Weiss AFI1: 192kHz 24-channel Firewire to ADAT or AES/EBU face
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_WEISS,
+ .model_id = 0x000004,
+ .driver_data = (kernel_ulong_t)snd_dice_detect_weiss_formats,
+ },
{
.match_flags = IEEE1394_MATCH_VERSION,
.version = DICE_INTERFACE,
diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h
index 7fbffcab94c2..4c0ad7335998 100644
--- a/sound/firewire/dice/dice.h
+++ b/sound/firewire/dice/dice.h
@@ -78,9 +78,6 @@ struct snd_dice {
spinlock_t lock;
struct mutex mutex;
- bool registered;
- struct delayed_work dwork;
-
/* Offsets for sub-addresses */
unsigned int global_offset;
unsigned int rx_offset;
@@ -93,7 +90,6 @@ struct snd_dice {
unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT];
unsigned int tx_midi_ports[MAX_STREAMS];
unsigned int rx_midi_ports[MAX_STREAMS];
- snd_dice_detect_formats_t detect_formats;
struct fw_address_handler notification_handler;
int owner_generation;
@@ -109,7 +105,8 @@ struct snd_dice {
struct fw_iso_resources rx_resources[MAX_STREAMS];
struct amdtp_stream tx_stream[MAX_STREAMS];
struct amdtp_stream rx_stream[MAX_STREAMS];
- bool global_enabled;
+ bool global_enabled:1;
+ bool disable_double_pcm_frames:1;
struct completion clock_accepted;
unsigned int substreams_counter;
@@ -233,5 +230,8 @@ int snd_dice_detect_alesis_mastercontrol_formats(struct snd_dice *dice);
int snd_dice_detect_extension_formats(struct snd_dice *dice);
int snd_dice_detect_mytek_formats(struct snd_dice *dice);
int snd_dice_detect_presonus_formats(struct snd_dice *dice);
+int snd_dice_detect_harman_formats(struct snd_dice *dice);
+int snd_dice_detect_focusrite_pro40_tcd3070_formats(struct snd_dice *dice);
+int snd_dice_detect_weiss_formats(struct snd_dice *dice);
#endif
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index 8add0cd9af3a..6dc18bd2e186 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \
+snd-firewire-digi00x-y := 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
index d613642a2ce3..7db0024495b7 100644
--- a/sound/firewire/digi00x/amdtp-dot.c
+++ b/sound/firewire/digi00x/amdtp-dot.c
@@ -123,7 +123,7 @@ int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
* A first data channel is for MIDI messages, the rest is Multi Bit
* Linear Audio data channel.
*/
- err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1);
+ err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1, 1);
if (err < 0)
return err;
@@ -341,16 +341,13 @@ void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
WRITE_ONCE(p->midi[port], midi);
}
-static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_ir_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -360,21 +357,18 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
}
read_midi_messages(s, buf, data_blocks);
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
-static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_it_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -387,25 +381,22 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
write_midi_messages(s, buf, data_blocks,
desc->data_block_counter);
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir)
{
amdtp_stream_process_ctx_payloads_t process_ctx_payloads;
- enum cip_flags flags;
+ unsigned int flags = CIP_NONBLOCKING | CIP_UNAWARE_SYT;
// Use different mode between incoming/outgoing.
- if (dir == AMDTP_IN_STREAM) {
- flags = CIP_NONBLOCKING;
+ if (dir == AMDTP_IN_STREAM)
process_ctx_payloads = process_ir_ctx_payloads;
- } else {
- flags = CIP_BLOCKING;
+ else
process_ctx_payloads = process_it_ctx_payloads;
- }
return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM,
process_ctx_payloads, sizeof(struct amdtp_dot));
diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c
index 41c5857c612e..b150607c0a0d 100644
--- a/sound/firewire/digi00x/digi00x-hwdep.c
+++ b/sound/firewire/digi00x/digi00x-hwdep.c
@@ -87,7 +87,7 @@ static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -188,7 +188,7 @@ int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x)
if (err < 0)
return err;
- strcpy(hwdep->name, "Digi00x");
+ strscpy(hwdep->name, "Digi00x");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X;
hwdep->ops = ops;
hwdep->private_data = dg00x;
diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c
index 68eb8c39afa6..8f4bace16050 100644
--- a/sound/firewire/digi00x/digi00x-midi.c
+++ b/sound/firewire/digi00x/digi00x-midi.c
@@ -100,14 +100,14 @@ static void set_substream_names(struct snd_dg00x *dg00x,
list_for_each_entry(subs, &str->substreams, list) {
if (!is_console) {
- snprintf(subs->name, sizeof(subs->name),
- "%s MIDI %d",
- dg00x->card->shortname,
- subs->number + 1);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ dg00x->card->shortname,
+ subs->number + 1);
} else {
- snprintf(subs->name, sizeof(subs->name),
- "%s control",
- dg00x->card->shortname);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s control",
+ dg00x->card->shortname);
}
}
}
diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c
index b7f6eda09f9f..85e65cbc00c4 100644
--- a/sound/firewire/digi00x/digi00x-pcm.c
+++ b/sound/firewire/digi00x/digi00x-pcm.c
@@ -190,7 +190,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_dg00x *dg00x = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
@@ -212,7 +212,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&dg00x->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--dg00x->substreams_counter;
snd_dg00x_stream_stop_duplex(dg00x);
@@ -350,6 +350,7 @@ int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
return err;
pcm->private_data = dg00x;
+ pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", dg00x->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c
index 405d6903bfbc..295163bb8abb 100644
--- a/sound/firewire/digi00x/digi00x-stream.c
+++ b/sound/firewire/digi00x/digi00x-stream.c
@@ -7,7 +7,7 @@
#include "digi00x.h"
-#define CALLBACK_TIMEOUT 500
+#define READY_TIMEOUT_MS 200
const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = {
[SND_DG00X_RATE_44100] = 44100,
@@ -259,8 +259,10 @@ int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x)
return err;
err = init_stream(dg00x, &dg00x->tx_stream);
- if (err < 0)
+ if (err < 0) {
destroy_stream(dg00x, &dg00x->rx_stream);
+ return err;
+ }
err = amdtp_domain_init(&dg00x->domain);
if (err < 0) {
@@ -375,14 +377,15 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x)
if (err < 0)
goto error;
- err = amdtp_domain_start(&dg00x->domain, 0);
+ // NOTE: The device doesn't start packet transmission till receiving any packet.
+ // It ignores presentation time expressed by the value of syt field of CIP header
+ // in received packets. The sequence of the number of data blocks per packet is
+ // important for media clock recovery.
+ err = amdtp_domain_start(&dg00x->domain, 0, true, true);
if (err < 0)
goto error;
- if (!amdtp_stream_wait_callback(&dg00x->rx_stream,
- CALLBACK_TIMEOUT) ||
- !amdtp_stream_wait_callback(&dg00x->tx_stream,
- CALLBACK_TIMEOUT)) {
+ if (!amdtp_domain_wait_ready(&dg00x->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index ab8408966ec3..cebc35dcf8cd 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -9,7 +9,7 @@
MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
#define VENDOR_DIGIDESIGN 0x00a07e
#define MODEL_CONSOLE 0x000001
@@ -30,9 +30,9 @@ static int name_card(struct snd_dg00x *dg00x)
model = skip_spaces(name);
- strcpy(dg00x->card->driver, "Digi00x");
- strcpy(dg00x->card->shortname, model);
- strcpy(dg00x->card->mixername, model);
+ strscpy(dg00x->card->driver, "Digi00x");
+ strscpy(dg00x->card->shortname, model);
+ strscpy(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],
@@ -47,23 +47,32 @@ static void dg00x_card_free(struct snd_card *card)
snd_dg00x_stream_destroy_duplex(dg00x);
snd_dg00x_transaction_unregister(dg00x);
+
+ mutex_destroy(&dg00x->mutex);
+ fw_unit_put(dg00x->unit);
}
-static void do_registration(struct work_struct *work)
+static int snd_dg00x_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_dg00x *dg00x =
- container_of(work, struct snd_dg00x, dwork.work);
+ struct snd_card *card;
+ struct snd_dg00x *dg00x;
int err;
- if (dg00x->registered)
- return;
-
- err = snd_card_new(&dg00x->unit->device, -1, NULL, THIS_MODULE, 0,
- &dg00x->card);
+ err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*dg00x), &card);
if (err < 0)
- return;
- dg00x->card->private_free = dg00x_card_free;
- dg00x->card->private_data = dg00x;
+ return err;
+ card->private_free = dg00x_card_free;
+
+ dg00x = card->private_data;
+ dg00x->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, dg00x);
+ dg00x->card = card;
+
+ mutex_init(&dg00x->mutex);
+ spin_lock_init(&dg00x->lock);
+ init_waitqueue_head(&dg00x->hwdep_wait);
+
+ dg00x->is_console = entry->model_id == MODEL_CONSOLE;
err = name_card(dg00x);
if (err < 0)
@@ -91,85 +100,33 @@ static void do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(dg00x->card);
+ err = snd_card_register(card);
if (err < 0)
goto error;
- dg00x->registered = true;
-
- return;
-error:
- snd_card_free(dg00x->card);
- dev_info(&dg00x->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int snd_dg00x_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *entry)
-{
- struct snd_dg00x *dg00x;
-
- /* Allocate this independent of sound card instance. */
- dg00x = devm_kzalloc(&unit->device, sizeof(struct snd_dg00x),
- GFP_KERNEL);
- if (!dg00x)
- return -ENOMEM;
-
- dg00x->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, dg00x);
-
- mutex_init(&dg00x->mutex);
- spin_lock_init(&dg00x->lock);
- init_waitqueue_head(&dg00x->hwdep_wait);
-
- dg00x->is_console = entry->model_id == MODEL_CONSOLE;
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration);
- snd_fw_schedule_registration(unit, &dg00x->dwork);
-
return 0;
+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);
- /* Postpone a workqueue for deferred registration. */
- if (!dg00x->registered)
- snd_fw_schedule_registration(unit, &dg00x->dwork);
-
snd_dg00x_transaction_reregister(dg00x);
- /*
- * After registration, userspace can start packet streaming, then this
- * code block works fine.
- */
- if (dg00x->registered) {
- mutex_lock(&dg00x->mutex);
- snd_dg00x_stream_update_duplex(dg00x);
- mutex_unlock(&dg00x->mutex);
- }
+ 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);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&dg00x->dwork);
-
- if (dg00x->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(dg00x->card);
- }
-
- mutex_destroy(&dg00x->mutex);
- fw_unit_put(dg00x->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(dg00x->card);
}
static const struct ieee1394_device_id snd_dg00x_id_table[] = {
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 129de8edd5ea..82b647d383c5 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -37,9 +37,6 @@ struct snd_dg00x {
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
-
struct amdtp_stream tx_stream;
struct fw_iso_resources tx_resources;
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c
index bbfbebf4affb..df44dd5dc4b2 100644
--- a/sound/firewire/fcp.c
+++ b/sound/firewire/fcp.c
@@ -240,9 +240,7 @@ int fcp_avc_transaction(struct fw_unit *unit,
t.response_match_bytes = response_match_bytes;
t.state = STATE_PENDING;
init_waitqueue_head(&t.wait);
-
- if (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03)
- t.deferrable = true;
+ t.deferrable = (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03);
spin_lock_irq(&transactions_lock);
list_add_tail(&t.list, &transactions);
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index 3aef221ce4b0..b397d95877a0 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
+snd-fireface-y := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-former.o \
ff-protocol-latter.o
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c
index 119c0076b17a..76c9d33ed572 100644
--- a/sound/firewire/fireface/amdtp-ff.c
+++ b/sound/firewire/fireface/amdtp-ff.c
@@ -24,7 +24,7 @@ int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
p->pcm_channels = pcm_channels;
data_channels = pcm_channels;
- return amdtp_stream_set_parameters(s, rate, data_channels);
+ return amdtp_stream_set_parameters(s, rate, data_channels, 1);
}
static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
@@ -112,16 +112,13 @@ int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
}
-static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_it_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__le32 *buf = (__le32 *)desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -131,21 +128,18 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
} else {
write_pcm_silence(s, buf, data_blocks);
}
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
-static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_ir_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__le32 *buf = (__le32 *)desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -153,9 +147,9 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames);
pcm_frames += data_blocks;
}
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
@@ -168,6 +162,6 @@ int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
else
process_ctx_payloads = process_it_ctx_payloads;
- return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0,
+ return amdtp_stream_init(s, unit, dir, CIP_BLOCKING | CIP_UNAWARE_SYT | CIP_NO_HEADER, 0,
process_ctx_payloads, sizeof(struct amdtp_ff));
}
diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c
index e73e8d2865a5..ca5c5dee71f2 100644
--- a/sound/firewire/fireface/ff-hwdep.c
+++ b/sound/firewire/fireface/ff-hwdep.c
@@ -15,16 +15,23 @@
#include "ff.h"
+static bool has_msg(struct snd_ff *ff)
+{
+ if (ff->spec->protocol->has_msg)
+ return ff->spec->protocol->has_msg(ff);
+ else
+ return 0;
+}
+
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_ff *ff = hwdep->private_data;
DEFINE_WAIT(wait);
- union snd_firewire_event event;
spin_lock_irq(&ff->lock);
- while (!ff->dev_lock_changed) {
+ while (!ff->dev_lock_changed && !has_msg(ff)) {
prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&ff->lock);
schedule();
@@ -34,19 +41,29 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
spin_lock_irq(&ff->lock);
}
- memset(&event, 0, sizeof(event));
- if (ff->dev_lock_changed) {
- event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
- event.lock_status.status = (ff->dev_lock_count > 0);
+ if (ff->dev_lock_changed && count >= sizeof(struct snd_firewire_event_lock_status)) {
+ struct snd_firewire_event_lock_status ev = {
+ .type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
+ .status = (ff->dev_lock_count > 0),
+ };
+
ff->dev_lock_changed = false;
- count = min_t(long, count, sizeof(event.lock_status));
- }
+ spin_unlock_irq(&ff->lock);
- spin_unlock_irq(&ff->lock);
+ if (copy_to_user(buf, &ev, sizeof(ev)))
+ return -EFAULT;
+ count = sizeof(ev);
+ } else if (has_msg(ff)) {
+ // NOTE: Acquired spin lock should be released before accessing to user space in the
+ // callback since the access can cause page fault.
+ count = ff->spec->protocol->copy_msg_to_user(ff, buf, count);
+ spin_unlock_irq(&ff->lock);
+ } else {
+ spin_unlock_irq(&ff->lock);
- if (copy_to_user(buf, &event, count))
- return -EFAULT;
+ count = 0;
+ }
return count;
}
@@ -60,7 +77,7 @@ static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_wait(file, &ff->hwdep_wait, wait);
spin_lock_irq(&ff->lock);
- if (ff->dev_lock_changed)
+ if (ff->dev_lock_changed || has_msg(ff))
events = EPOLLIN | EPOLLRDNORM;
else
events = 0;
@@ -79,7 +96,7 @@ static int hwdep_get_info(struct snd_ff *ff, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -180,7 +197,7 @@ int snd_ff_create_hwdep_devices(struct snd_ff *ff)
if (err < 0)
return err;
- strcpy(hwdep->name, ff->card->driver);
+ strscpy(hwdep->name, ff->card->driver);
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE;
hwdep->ops = hwdep_ops;
hwdep->private_data = ff;
diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c
index 25821d186b87..da3054fdcc7d 100644
--- a/sound/firewire/fireface/ff-midi.c
+++ b/sound/firewire/fireface/ff-midi.c
@@ -79,8 +79,8 @@ static void set_midi_substream_names(struct snd_rawmidi_str *stream,
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream, &stream->substreams, list) {
- snprintf(substream->name, sizeof(substream->name),
- "%s MIDI %d", name, substream->number + 1);
+ scnprintf(substream->name, sizeof(substream->name),
+ "%s MIDI %d", name, substream->number + 1);
}
}
diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c
index f978cc2fed7d..63457d24a288 100644
--- a/sound/firewire/fireface/ff-pcm.c
+++ b/sound/firewire/fireface/ff-pcm.c
@@ -230,7 +230,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_ff *ff = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
@@ -252,7 +252,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&ff->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--ff->substreams_counter;
snd_ff_stream_stop_duplex(ff);
@@ -390,6 +390,7 @@ int snd_ff_create_pcm_devices(struct snd_ff *ff)
return err;
pcm->private_data = ff;
+ pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", ff->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
diff --git a/sound/firewire/fireface/ff-protocol-former.c b/sound/firewire/fireface/ff-protocol-former.c
index bf44cad7985e..0907d0a2296f 100644
--- a/sound/firewire/fireface/ff-protocol-former.c
+++ b/sound/firewire/fireface/ff-protocol-former.c
@@ -2,8 +2,6 @@
// ff-protocol-former.c - a part of driver for RME Fireface series
//
// Copyright (c) 2019 Takashi Sakamoto
-//
-// Licensed under the terms of the GNU General Public License, version 2.
#include <linux/delay.h>
@@ -137,13 +135,13 @@ static void dump_clock_config(struct snd_ff *ff, struct snd_info_buffer *buffer)
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
(data & 0x00000020) ? "Professional" : "Consumer",
- (data & 0x00000040) ? "on" : "off");
+ str_on_off(data & 0x00000040));
snd_iprintf(buffer, "Optical output interface format: %s\n",
(data & 0x00000100) ? "S/PDIF" : "ADAT");
snd_iprintf(buffer, "Word output single speed: %s\n",
- (data & 0x00002000) ? "on" : "off");
+ str_on_off(data & 0x00002000));
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
(data & 0x00000200) ? "Optical" : "Coaxial");
@@ -404,8 +402,8 @@ static void ff800_finish_session(struct snd_ff *ff)
// address.
// A write transaction to clear registered higher 4 bytes of destination address
// has an effect to suppress asynchronous transaction from device.
-static void ff800_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
- __le32 *buf, size_t length)
+static void ff800_handle_midi_msg(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
+ size_t length, u32 tstamp)
{
int i;
@@ -420,7 +418,7 @@ static void ff800_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
}
const struct snd_ff_protocol snd_ff_protocol_ff800 = {
- .handle_midi_msg = ff800_handle_midi_msg,
+ .handle_msg = ff800_handle_midi_msg,
.fill_midi_msg = former_fill_midi_msg,
.get_clock = former_get_clock,
.switch_fetching_mode = former_switch_fetching_mode,
@@ -536,6 +534,35 @@ static void ff400_finish_session(struct snd_ff *ff)
FF400_ISOC_COMM_STOP, &reg, sizeof(reg), 0);
}
+static void parse_midi_msg(struct snd_ff *ff, u32 quad, unsigned int port)
+{
+ struct snd_rawmidi_substream *substream = READ_ONCE(ff->tx_midi_substreams[port]);
+
+ if (substream != NULL) {
+ u8 byte = (quad >> (16 * port)) & 0x000000ff;
+
+ snd_rawmidi_receive(substream, &byte, 1);
+ }
+}
+
+#define FF400_QUEUE_SIZE 32
+
+struct ff400_msg_parser {
+ struct {
+ u32 msg;
+ u32 tstamp;
+ } msgs[FF400_QUEUE_SIZE];
+ size_t push_pos;
+ size_t pull_pos;
+};
+
+static bool ff400_has_msg(struct snd_ff *ff)
+{
+ struct ff400_msg_parser *parser = ff->msg_parser;
+
+ return (parser->push_pos != parser->pull_pos);
+}
+
// For Fireface 400, lower 4 bytes of destination address is configured by bit
// flag in quadlet register (little endian) at 0x'0000'801'0051c. Drivers can
// select one of 4 options:
@@ -555,46 +582,147 @@ static void ff400_finish_session(struct snd_ff *ff)
// input attenuation. This driver allocates destination address with '0000'0000
// in its lower offset and expects userspace application to configure the
// register for it.
-static void ff400_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
- __le32 *buf, size_t length)
+
+// When the message is for signal level operation, the upper 4 bits in MSB expresses the pair of
+// stereo physical port.
+// - 0: Microphone input 0/1
+// - 1: Line input 0/1
+// - [2-4]: Line output 0-5
+// - 5: Headphone output 0/1
+// - 6: S/PDIF output 0/1
+// - [7-10]: ADAT output 0-7
+//
+// The value of signal level can be detected by mask of 0x00fffc00. For signal level of microphone
+// input:
+//
+// - 0: 0.0 dB
+// - 10: +10.0 dB
+// - 11: +11.0 dB
+// - 12: +12.0 dB
+// - ...
+// - 63: +63.0 dB:
+// - 64: +64.0 dB:
+// - 65: +65.0 dB:
+//
+// For signal level of line input:
+//
+// - 0: 0.0 dB
+// - 1: +0.5 dB
+// - 2: +1.0 dB
+// - 3: +1.5 dB
+// - ...
+// - 34: +17.0 dB:
+// - 35: +17.5 dB:
+// - 36: +18.0 dB:
+//
+// For signal level of any type of output:
+//
+// - 63: -infinite
+// - 62: -58.0 dB
+// - 61: -56.0 dB
+// - 60: -54.0 dB
+// - 59: -53.0 dB
+// - 58: -52.0 dB
+// - ...
+// - 7: -1.0 dB
+// - 6: 0.0 dB
+// - 5: +1.0 dB
+// - ...
+// - 2: +4.0 dB
+// - 1: +5.0 dB
+// - 0: +6.0 dB
+//
+// When the message is not for signal level operation, it's for MIDI bytes. When matching to
+// FF400_MSG_FLAG_IS_MIDI_PORT_0, one MIDI byte can be detected by mask of 0x000000ff. When
+// matching to FF400_MSG_FLAG_IS_MIDI_PORT_1, one MIDI byte can be detected by mask of 0x00ff0000.
+#define FF400_MSG_FLAG_IS_SIGNAL_LEVEL 0x04000000
+#define FF400_MSG_FLAG_IS_RIGHT_CHANNEL 0x08000000
+#define FF400_MSG_FLAG_IS_STEREO_PAIRED 0x02000000
+#define FF400_MSG_MASK_STEREO_PAIR 0xf0000000
+#define FF400_MSG_MASK_SIGNAL_LEVEL 0x00fffc00
+#define FF400_MSG_FLAG_IS_MIDI_PORT_0 0x00000100
+#define FF400_MSG_MASK_MIDI_PORT_0 0x000000ff
+#define FF400_MSG_FLAG_IS_MIDI_PORT_1 0x01000000
+#define FF400_MSG_MASK_MIDI_PORT_1 0x00ff0000
+
+static void ff400_handle_msg(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
+ size_t length, u32 tstamp)
{
+ bool need_hwdep_wake_up = false;
int i;
for (i = 0; i < length / 4; i++) {
u32 quad = le32_to_cpu(buf[i]);
- u8 byte;
- unsigned int index;
- struct snd_rawmidi_substream *substream;
- /* Message in first port. */
- /*
- * This value may represent the index of this unit when the same
- * units are on the same IEEE 1394 bus. This driver doesn't use
- * it.
- */
- index = (quad >> 8) & 0xff;
- if (index > 0) {
- substream = READ_ONCE(ff->tx_midi_substreams[0]);
- if (substream != NULL) {
- byte = quad & 0xff;
- snd_rawmidi_receive(substream, &byte, 1);
- }
- }
+ if (quad & FF400_MSG_FLAG_IS_SIGNAL_LEVEL) {
+ struct ff400_msg_parser *parser = ff->msg_parser;
- /* Message in second port. */
- index = (quad >> 24) & 0xff;
- if (index > 0) {
- substream = READ_ONCE(ff->tx_midi_substreams[1]);
- if (substream != NULL) {
- byte = (quad >> 16) & 0xff;
- snd_rawmidi_receive(substream, &byte, 1);
- }
+ parser->msgs[parser->push_pos].msg = quad;
+ parser->msgs[parser->push_pos].tstamp = tstamp;
+ ++parser->push_pos;
+ if (parser->push_pos >= FF400_QUEUE_SIZE)
+ parser->push_pos = 0;
+
+ need_hwdep_wake_up = true;
+ } else if (quad & FF400_MSG_FLAG_IS_MIDI_PORT_0) {
+ parse_midi_msg(ff, quad, 0);
+ } else if (quad & FF400_MSG_FLAG_IS_MIDI_PORT_1) {
+ parse_midi_msg(ff, quad, 1);
}
}
+
+ if (need_hwdep_wake_up)
+ wake_up(&ff->hwdep_wait);
+}
+
+static long ff400_copy_msg_to_user(struct snd_ff *ff, char __user *buf, long count)
+{
+ struct snd_firewire_event_ff400_message ev = {
+ .type = SNDRV_FIREWIRE_EVENT_FF400_MESSAGE,
+ .message_count = 0,
+ };
+ struct ff400_msg_parser *parser = ff->msg_parser;
+ long consumed = 0;
+ long ret = 0;
+
+ if (count < sizeof(ev) || parser->pull_pos == parser->push_pos)
+ return 0;
+
+ count -= sizeof(ev);
+ consumed += sizeof(ev);
+
+ while (count >= sizeof(*parser->msgs) && parser->pull_pos != parser->push_pos) {
+ spin_unlock_irq(&ff->lock);
+ if (copy_to_user(buf + consumed, parser->msgs + parser->pull_pos,
+ sizeof(*parser->msgs)))
+ ret = -EFAULT;
+ spin_lock_irq(&ff->lock);
+ if (ret)
+ return ret;
+
+ ++parser->pull_pos;
+ if (parser->pull_pos >= FF400_QUEUE_SIZE)
+ parser->pull_pos = 0;
+ ++ev.message_count;
+ count -= sizeof(*parser->msgs);
+ consumed += sizeof(*parser->msgs);
+ }
+
+ spin_unlock_irq(&ff->lock);
+ if (copy_to_user(buf, &ev, sizeof(ev)))
+ ret = -EFAULT;
+ spin_lock_irq(&ff->lock);
+ if (ret)
+ return ret;
+
+ return consumed;
}
const struct snd_ff_protocol snd_ff_protocol_ff400 = {
- .handle_midi_msg = ff400_handle_midi_msg,
+ .msg_parser_size = sizeof(struct ff400_msg_parser),
+ .has_msg = ff400_has_msg,
+ .copy_msg_to_user = ff400_copy_msg_to_user,
+ .handle_msg = ff400_handle_msg,
.fill_midi_msg = former_fill_midi_msg,
.get_clock = former_get_clock,
.switch_fetching_mode = former_switch_fetching_mode,
diff --git a/sound/firewire/fireface/ff-protocol-latter.c b/sound/firewire/fireface/ff-protocol-latter.c
index 8d3b23778eb2..9947e0c2e0aa 100644
--- a/sound/firewire/fireface/ff-protocol-latter.c
+++ b/sound/firewire/fireface/ff-protocol-latter.c
@@ -1,9 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
-// ff-protocol-latter - a part of driver for RME Fireface series
+// ff-protocol-latter.c - a part of driver for RME Fireface series
//
// Copyright (c) 2019 Takashi Sakamoto
-//
-// Licensed under the terms of the GNU General Public License, version 2.
#include <linux/delay.h>
@@ -15,6 +13,61 @@
#define LATTER_FETCH_MODE 0xffff00000010ULL
#define LATTER_SYNC_STATUS 0x0000801c0000ULL
+// The content of sync status register differs between models.
+//
+// Fireface UCX:
+// 0xf0000000: (unidentified)
+// 0x0f000000: effective rate of sampling clock
+// 0x00f00000: detected rate of word clock on BNC interface
+// 0x000f0000: detected rate of ADAT or S/PDIF on optical interface
+// 0x0000f000: detected rate of S/PDIF on coaxial interface
+// 0x00000e00: effective source of sampling clock
+// 0x00000e00: Internal
+// 0x00000800: (unidentified)
+// 0x00000600: Word clock on BNC interface
+// 0x00000400: ADAT on optical interface
+// 0x00000200: S/PDIF on coaxial or optical interface
+// 0x00000100: Optical interface is used for ADAT signal
+// 0x00000080: (unidentified)
+// 0x00000040: Synchronized to word clock on BNC interface
+// 0x00000020: Synchronized to ADAT or S/PDIF on optical interface
+// 0x00000010: Synchronized to S/PDIF on coaxial interface
+// 0x00000008: (unidentified)
+// 0x00000004: Lock word clock on BNC interface
+// 0x00000002: Lock ADAT or S/PDIF on optical interface
+// 0x00000001: Lock S/PDIF on coaxial interface
+//
+// Fireface 802 (and perhaps UFX):
+// 0xf0000000: effective rate of sampling clock
+// 0x0f000000: detected rate of ADAT-B on 2nd optical interface
+// 0x00f00000: detected rate of ADAT-A on 1st optical interface
+// 0x000f0000: detected rate of AES/EBU on XLR or coaxial interface
+// 0x0000f000: detected rate of word clock on BNC interface
+// 0x00000e00: effective source of sampling clock
+// 0x00000e00: internal
+// 0x00000800: ADAT-B
+// 0x00000600: ADAT-A
+// 0x00000400: AES/EBU
+// 0x00000200: Word clock
+// 0x00000080: Synchronized to ADAT-B on 2nd optical interface
+// 0x00000040: Synchronized to ADAT-A on 1st optical interface
+// 0x00000020: Synchronized to AES/EBU on XLR or 2nd optical interface
+// 0x00000010: Synchronized to word clock on BNC interface
+// 0x00000008: Lock ADAT-B on 2nd optical interface
+// 0x00000004: Lock ADAT-A on 1st optical interface
+// 0x00000002: Lock AES/EBU on XLR or 2nd optical interface
+// 0x00000001: Lock word clock on BNC interface
+//
+// The pattern for rate bits:
+// 0x00: 32.0 kHz
+// 0x01: 44.1 kHz
+// 0x02: 48.0 kHz
+// 0x04: 64.0 kHz
+// 0x05: 88.2 kHz
+// 0x06: 96.0 kHz
+// 0x08: 128.0 kHz
+// 0x09: 176.4 kHz
+// 0x0a: 192.0 kHz
static int parse_clock_bits(u32 data, unsigned int *rate,
enum snd_ff_clock_src *src,
enum snd_ff_unit_version unit_version)
@@ -23,35 +76,48 @@ static int parse_clock_bits(u32 data, unsigned int *rate,
unsigned int rate;
u32 flag;
} *rate_entry, rate_entries[] = {
- { 32000, 0x00000000, },
- { 44100, 0x01000000, },
- { 48000, 0x02000000, },
- { 64000, 0x04000000, },
- { 88200, 0x05000000, },
- { 96000, 0x06000000, },
- { 128000, 0x08000000, },
- { 176400, 0x09000000, },
- { 192000, 0x0a000000, },
+ { 32000, 0x00, },
+ { 44100, 0x01, },
+ { 48000, 0x02, },
+ { 64000, 0x04, },
+ { 88200, 0x05, },
+ { 96000, 0x06, },
+ { 128000, 0x08, },
+ { 176400, 0x09, },
+ { 192000, 0x0a, },
};
static const struct {
enum snd_ff_clock_src src;
u32 flag;
- } *clk_entry, clk_entries[] = {
+ } *clk_entry, *clk_entries, ucx_clk_entries[] = {
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000200, },
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000400, },
{ SND_FF_CLOCK_SRC_WORD, 0x00000600, },
{ SND_FF_CLOCK_SRC_INTERNAL, 0x00000e00, },
+ }, ufx_ff802_clk_entries[] = {
+ { SND_FF_CLOCK_SRC_WORD, 0x00000200, },
+ { SND_FF_CLOCK_SRC_SPDIF, 0x00000400, },
+ { SND_FF_CLOCK_SRC_ADAT1, 0x00000600, },
+ { SND_FF_CLOCK_SRC_ADAT2, 0x00000800, },
+ { SND_FF_CLOCK_SRC_INTERNAL, 0x00000e00, },
};
+ u32 rate_bits;
+ unsigned int clk_entry_count;
int i;
- if (unit_version != SND_FF_UNIT_VERSION_UCX) {
- // e.g. 0x00fe0f20 but expected 0x00eff002.
- data = ((data & 0xf0f0f0f0) >> 4) | ((data & 0x0f0f0f0f) << 4);
+ if (unit_version == SND_FF_UNIT_VERSION_UCX) {
+ rate_bits = (data & 0x0f000000) >> 24;
+ clk_entries = ucx_clk_entries;
+ clk_entry_count = ARRAY_SIZE(ucx_clk_entries);
+ } else {
+ rate_bits = (data & 0xf0000000) >> 28;
+ clk_entries = ufx_ff802_clk_entries;
+ clk_entry_count = ARRAY_SIZE(ufx_ff802_clk_entries);
}
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
rate_entry = rate_entries + i;
- if ((data & 0x0f000000) == rate_entry->flag) {
+ if (rate_bits == rate_entry->flag) {
*rate = rate_entry->rate;
break;
}
@@ -59,14 +125,14 @@ static int parse_clock_bits(u32 data, unsigned int *rate,
if (i == ARRAY_SIZE(rate_entries))
return -EIO;
- for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
+ for (i = 0; i < clk_entry_count; ++i) {
clk_entry = clk_entries + i;
if ((data & 0x000e00) == clk_entry->flag) {
*src = clk_entry->src;
break;
}
}
- if (i == ARRAY_SIZE(clk_entries))
+ if (i == clk_entry_count)
return -EIO;
return 0;
@@ -249,16 +315,22 @@ static void latter_dump_status(struct snd_ff *ff, struct snd_info_buffer *buffer
char *const label;
u32 locked_mask;
u32 synced_mask;
- } *clk_entry, clk_entries[] = {
+ } *clk_entry, *clk_entries, ucx_clk_entries[] = {
{ "S/PDIF", 0x00000001, 0x00000010, },
{ "ADAT", 0x00000002, 0x00000020, },
{ "WDClk", 0x00000004, 0x00000040, },
+ }, ufx_ff802_clk_entries[] = {
+ { "WDClk", 0x00000001, 0x00000010, },
+ { "AES/EBU", 0x00000002, 0x00000020, },
+ { "ADAT-A", 0x00000004, 0x00000040, },
+ { "ADAT-B", 0x00000008, 0x00000080, },
};
__le32 reg;
u32 data;
unsigned int rate;
enum snd_ff_clock_src src;
const char *label;
+ unsigned int clk_entry_count;
int i;
int err;
@@ -270,7 +342,15 @@ static void latter_dump_status(struct snd_ff *ff, struct snd_info_buffer *buffer
snd_iprintf(buffer, "External source detection:\n");
- for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
+ if (ff->unit_version == SND_FF_UNIT_VERSION_UCX) {
+ clk_entries = ucx_clk_entries;
+ clk_entry_count = ARRAY_SIZE(ucx_clk_entries);
+ } else {
+ clk_entries = ufx_ff802_clk_entries;
+ clk_entry_count = ARRAY_SIZE(ufx_ff802_clk_entries);
+ }
+
+ for (i = 0; i < clk_entry_count; ++i) {
clk_entry = clk_entries + i;
snd_iprintf(buffer, "%s: ", clk_entry->label);
if (data & clk_entry->locked_mask) {
@@ -313,8 +393,8 @@ static void latter_dump_status(struct snd_ff *ff, struct snd_info_buffer *buffer
// input attenuation. This driver allocates for the first option
// (0x'....'....'0000'0000) and expects userspace application to configure the
// register for it.
-static void latter_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
- __le32 *buf, size_t length)
+static void latter_handle_midi_msg(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
+ size_t length, u32 tstamp)
{
u32 data = le32_to_cpu(*buf);
unsigned int index = (data & 0x000000f0) >> 4;
@@ -449,7 +529,7 @@ static int latter_fill_midi_msg(struct snd_ff *ff,
}
const struct snd_ff_protocol snd_ff_protocol_latter = {
- .handle_midi_msg = latter_handle_midi_msg,
+ .handle_msg = latter_handle_midi_msg,
.fill_midi_msg = latter_fill_midi_msg,
.get_clock = latter_get_clock,
.switch_fetching_mode = latter_switch_fetching_mode,
diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c
index 5452115c0ef9..95bf405adb3d 100644
--- a/sound/firewire/fireface/ff-stream.c
+++ b/sound/firewire/fireface/ff-stream.c
@@ -7,7 +7,7 @@
#include "ff.h"
-#define CALLBACK_TIMEOUT_MS 200
+#define READY_TIMEOUT_MS 200
int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc,
enum snd_ff_stream_mode *mode)
@@ -199,14 +199,15 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
if (err < 0)
goto error;
- err = amdtp_domain_start(&ff->domain, 0);
+ // NOTE: The device doesn't transfer packets unless receiving any packet. The
+ // sequence of tx packets includes cycle skip corresponding to empty packet or
+ // NODATA packet in IEC 61883-1/6. The sequence of the number of data blocks per
+ // packet is important for media clock recovery.
+ err = amdtp_domain_start(&ff->domain, 0, true, true);
if (err < 0)
goto error;
- if (!amdtp_stream_wait_callback(&ff->rx_stream,
- CALLBACK_TIMEOUT_MS) ||
- !amdtp_stream_wait_callback(&ff->tx_stream,
- CALLBACK_TIMEOUT_MS)) {
+ if (!amdtp_domain_wait_ready(&ff->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c
index 7f82762ccc8c..6b89e39f4a43 100644
--- a/sound/firewire/fireface/ff-transaction.c
+++ b/sound/firewire/fireface/ff-transaction.c
@@ -88,7 +88,7 @@ static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
/* Set interval to next transaction. */
ff->next_ktime[port] = ktime_add_ns(ktime_get(),
- ff->rx_bytes[port] * 8 * NSEC_PER_SEC / 31250);
+ ff->rx_bytes[port] * 8 * (NSEC_PER_SEC / 31250));
if (quad_count == 1)
tcode = TCODE_WRITE_QUADLET_REQUEST;
@@ -125,19 +125,22 @@ static void transmit_midi1_msg(struct work_struct *work)
transmit_midi_msg(ff, 1);
}
-static void handle_midi_msg(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)
+static void handle_msg(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_ff *ff = callback_data;
__le32 *buf = data;
+ u32 tstamp = fw_request_get_timestamp(request);
+ unsigned long flag;
fw_send_response(card, request, RCODE_COMPLETE);
offset -= ff->async_handler.offset;
- ff->spec->protocol->handle_midi_msg(ff, (unsigned int)offset, buf,
- length);
+
+ spin_lock_irqsave(&ff->lock, flag);
+ ff->spec->protocol->handle_msg(ff, (unsigned int)offset, buf, length, tstamp);
+ spin_unlock_irqrestore(&ff->lock, flag);
}
static int allocate_own_address(struct snd_ff *ff, int i)
@@ -146,7 +149,7 @@ static int allocate_own_address(struct snd_ff *ff, int i)
int err;
ff->async_handler.length = ff->spec->midi_addr_range;
- ff->async_handler.address_callback = handle_midi_msg;
+ ff->async_handler.address_callback = handle_msg;
ff->async_handler.callback_data = ff;
midi_msg_region.start = 0x000100000000ull * i;
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index bc39269415d2..5d2c4fbf4434 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -11,12 +11,12 @@
MODULE_DESCRIPTION("RME Fireface series Driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
static void name_card(struct snd_ff *ff)
{
struct fw_device *fw_dev = fw_parent_device(ff->unit);
- const char *const names[] = {
+ static const char *const names[] = {
[SND_FF_UNIT_VERSION_FF800] = "Fireface800",
[SND_FF_UNIT_VERSION_FF400] = "Fireface400",
[SND_FF_UNIT_VERSION_UFX] = "FirefaceUFX",
@@ -27,9 +27,9 @@ static void name_card(struct snd_ff *ff)
name = names[ff->unit_version];
- strcpy(ff->card->driver, "Fireface");
- strcpy(ff->card->shortname, name);
- strcpy(ff->card->mixername, name);
+ strscpy(ff->card->driver, "Fireface");
+ strscpy(ff->card->shortname, name);
+ strscpy(ff->card->mixername, name);
snprintf(ff->card->longname, sizeof(ff->card->longname),
"RME %s, GUID %08x%08x at %s, S%d", name,
fw_dev->config_rom[3], fw_dev->config_rom[4],
@@ -42,22 +42,35 @@ static void ff_card_free(struct snd_card *card)
snd_ff_stream_destroy_duplex(ff);
snd_ff_transaction_unregister(ff);
+
+ kfree(ff->msg_parser);
+
+ mutex_destroy(&ff->mutex);
+ fw_unit_put(ff->unit);
}
-static void do_registration(struct work_struct *work)
+static int snd_ff_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work);
+ struct snd_card *card;
+ struct snd_ff *ff;
int err;
- if (ff->registered)
- return;
-
- err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0,
- &ff->card);
+ err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*ff), &card);
if (err < 0)
- return;
- ff->card->private_free = ff_card_free;
- ff->card->private_data = ff;
+ return err;
+ card->private_free = ff_card_free;
+
+ ff = card->private_data;
+ ff->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, ff);
+ ff->card = card;
+
+ mutex_init(&ff->mutex);
+ spin_lock_init(&ff->lock);
+ init_waitqueue_head(&ff->hwdep_wait);
+
+ ff->unit_version = entry->version;
+ ff->spec = (const struct snd_ff_spec *)entry->driver_data;
err = snd_ff_transaction_register(ff);
if (err < 0)
@@ -83,76 +96,39 @@ static void do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(ff->card);
+ if (ff->spec->protocol->msg_parser_size > 0) {
+ ff->msg_parser = kzalloc(ff->spec->protocol->msg_parser_size, GFP_KERNEL);
+ if (!ff->msg_parser) {
+ err = -ENOMEM;
+ goto error;
+ }
+ }
+
+ err = snd_card_register(card);
if (err < 0)
goto error;
- ff->registered = true;
-
- return;
-error:
- snd_card_free(ff->card);
- dev_info(&ff->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int snd_ff_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *entry)
-{
- struct snd_ff *ff;
-
- ff = devm_kzalloc(&unit->device, sizeof(struct snd_ff), GFP_KERNEL);
- if (!ff)
- return -ENOMEM;
- ff->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, ff);
-
- mutex_init(&ff->mutex);
- spin_lock_init(&ff->lock);
- init_waitqueue_head(&ff->hwdep_wait);
-
- ff->unit_version = entry->version;
- ff->spec = (const struct snd_ff_spec *)entry->driver_data;
-
- /* Register this sound card later. */
- INIT_DEFERRABLE_WORK(&ff->dwork, do_registration);
- snd_fw_schedule_registration(unit, &ff->dwork);
-
return 0;
+error:
+ snd_card_free(card);
+ return err;
}
static void snd_ff_update(struct fw_unit *unit)
{
struct snd_ff *ff = dev_get_drvdata(&unit->device);
- /* Postpone a workqueue for deferred registration. */
- if (!ff->registered)
- snd_fw_schedule_registration(unit, &ff->dwork);
-
snd_ff_transaction_reregister(ff);
- if (ff->registered)
- snd_ff_stream_update_duplex(ff);
+ snd_ff_stream_update_duplex(ff);
}
static void snd_ff_remove(struct fw_unit *unit)
{
struct snd_ff *ff = dev_get_drvdata(&unit->device);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_work_sync(&ff->dwork.work);
-
- if (ff->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(ff->card);
- }
-
- mutex_destroy(&ff->mutex);
- fw_unit_put(ff->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(ff->card);
}
static const struct snd_ff_spec spec_ff800 = {
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 705e7df4f929..7e42f5778a8a 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -69,9 +69,6 @@ struct snd_ff {
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
-
enum snd_ff_unit_version unit_version;
const struct snd_ff_spec *spec;
@@ -100,6 +97,8 @@ struct snd_ff {
wait_queue_head_t hwdep_wait;
struct amdtp_domain domain;
+
+ void *msg_parser;
};
enum snd_ff_clock_src {
@@ -113,8 +112,11 @@ enum snd_ff_clock_src {
};
struct snd_ff_protocol {
- void (*handle_midi_msg)(struct snd_ff *ff, unsigned int offset,
- __le32 *buf, size_t length);
+ size_t msg_parser_size;
+ bool (*has_msg)(struct snd_ff *ff);
+ long (*copy_msg_to_user)(struct snd_ff *ff, char __user *buf, long count);
+ void (*handle_msg)(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
+ size_t length, u32 tstamp);
int (*fill_midi_msg)(struct snd_ff *ff,
struct snd_rawmidi_substream *substream,
unsigned int port);
diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index 3386121b2a04..baaf3066c9b1 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
+snd-fireworks-y := fireworks_transaction.o fireworks_command.o \
fireworks_stream.o fireworks_proc.o fireworks_midi.o \
fireworks_pcm.o fireworks_hwdep.o fireworks.o
obj-$(CONFIG_SND_FIREWORKS) += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index b1cc013a3540..69f722244362 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -18,7 +18,7 @@
MODULE_DESCRIPTION("Echo Fireworks driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
@@ -90,14 +90,14 @@ get_hardware_info(struct snd_efw *efw)
(hwinfo->arm_version >> 16) & 0xff);
efw->firmware_version = hwinfo->arm_version;
- strcpy(efw->card->driver, "Fireworks");
- strcpy(efw->card->shortname, hwinfo->model_name);
- strcpy(efw->card->mixername, hwinfo->model_name);
- snprintf(efw->card->longname, sizeof(efw->card->longname),
- "%s %s v%s, GUID %08x%08x at %s, S%d",
- hwinfo->vendor_name, hwinfo->model_name, version,
- hwinfo->guid_hi, hwinfo->guid_lo,
- dev_name(&efw->unit->device), 100 << fw_dev->max_speed);
+ strscpy(efw->card->driver, "Fireworks");
+ strscpy(efw->card->shortname, hwinfo->model_name);
+ strscpy(efw->card->mixername, hwinfo->model_name);
+ scnprintf(efw->card->longname, sizeof(efw->card->longname),
+ "%s %s v%s, GUID %08x%08x at %s, S%d",
+ hwinfo->vendor_name, hwinfo->model_name, version,
+ hwinfo->guid_hi, hwinfo->guid_lo,
+ dev_name(&efw->unit->device), 100 << fw_dev->max_speed);
if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE))
efw->resp_addr_changable = true;
@@ -194,19 +194,19 @@ efw_card_free(struct snd_card *card)
snd_efw_stream_destroy_duplex(efw);
snd_efw_transaction_remove_instance(efw);
+
+ mutex_destroy(&efw->mutex);
+ fw_unit_put(efw->unit);
}
-static void
-do_registration(struct work_struct *work)
+static int efw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_efw *efw = container_of(work, struct snd_efw, dwork.work);
unsigned int card_index;
+ struct snd_card *card;
+ struct snd_efw *efw;
int err;
- if (efw->registered)
- return;
-
- /* check registered cards */
+ // check registered cards.
mutex_lock(&devices_mutex);
for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) {
if (!test_bit(card_index, devices_used) && enable[card_index])
@@ -214,26 +214,32 @@ do_registration(struct work_struct *work)
}
if (card_index >= SNDRV_CARDS) {
mutex_unlock(&devices_mutex);
- return;
+ return -ENOENT;
}
- err = snd_card_new(&efw->unit->device, index[card_index],
- id[card_index], THIS_MODULE, 0, &efw->card);
+ err = snd_card_new(&unit->device, index[card_index], id[card_index], THIS_MODULE,
+ sizeof(*efw), &card);
if (err < 0) {
mutex_unlock(&devices_mutex);
- return;
+ return err;
}
+ card->private_free = efw_card_free;
set_bit(card_index, devices_used);
mutex_unlock(&devices_mutex);
- efw->card->private_free = efw_card_free;
- efw->card->private_data = efw;
+ efw = card->private_data;
+ efw->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, efw);
+ efw->card = card;
+ efw->card_index = card_index;
+
+ mutex_init(&efw->mutex);
+ spin_lock_init(&efw->lock);
+ init_waitqueue_head(&efw->hwdep_wait);
- /* prepare response buffer */
- snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size,
- SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U);
- efw->resp_buf = devm_kzalloc(&efw->card->card_dev,
- snd_efw_resp_buf_size, GFP_KERNEL);
+ // prepare response buffer.
+ snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size, SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U);
+ efw->resp_buf = devm_kzalloc(&card->card_dev, snd_efw_resp_buf_size, GFP_KERNEL);
if (!efw->resp_buf) {
err = -ENOMEM;
goto error;
@@ -265,80 +271,48 @@ do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(efw->card);
+ err = snd_card_register(card);
if (err < 0)
goto error;
- efw->registered = true;
-
- return;
-error:
- snd_card_free(efw->card);
- dev_info(&efw->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int
-efw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
-{
- struct snd_efw *efw;
-
- efw = devm_kzalloc(&unit->device, sizeof(struct snd_efw), GFP_KERNEL);
- if (efw == NULL)
- return -ENOMEM;
- efw->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, efw);
-
- mutex_init(&efw->mutex);
- spin_lock_init(&efw->lock);
- init_waitqueue_head(&efw->hwdep_wait);
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&efw->dwork, do_registration);
- snd_fw_schedule_registration(unit, &efw->dwork);
-
return 0;
+error:
+ snd_card_free(card);
+ return err;
}
static void efw_update(struct fw_unit *unit)
{
struct snd_efw *efw = dev_get_drvdata(&unit->device);
- /* Postpone a workqueue for deferred registration. */
- if (!efw->registered)
- snd_fw_schedule_registration(unit, &efw->dwork);
-
snd_efw_transaction_bus_reset(efw->unit);
- /*
- * After registration, userspace can start packet streaming, then this
- * code block works fine.
- */
- if (efw->registered) {
- mutex_lock(&efw->mutex);
- snd_efw_stream_update_duplex(efw);
- mutex_unlock(&efw->mutex);
- }
+ mutex_lock(&efw->mutex);
+ snd_efw_stream_update_duplex(efw);
+ mutex_unlock(&efw->mutex);
}
static void efw_remove(struct fw_unit *unit)
{
struct snd_efw *efw = dev_get_drvdata(&unit->device);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&efw->dwork);
-
- if (efw->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(efw->card);
- }
+ // Block till all of ALSA character devices are released.
+ snd_card_free(efw->card);
+}
- mutex_destroy(&efw->mutex);
- fw_unit_put(efw->unit);
+#define SPECIFIER_1394TA 0x00a02d
+#define VERSION_EFW 0x010000
+
+#define SND_EFW_DEV_ENTRY(vendor, model) \
+{ \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID | \
+ IEEE1394_MATCH_SPECIFIER_ID | \
+ IEEE1394_MATCH_VERSION, \
+ .vendor_id = vendor,\
+ .model_id = model, \
+ .specifier_id = SPECIFIER_1394TA, \
+ .version = VERSION_EFW, \
}
static const struct ieee1394_device_id efw_id_table[] = {
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 654e28a6669f..c8d5879efe28 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -65,9 +65,6 @@ struct snd_efw {
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
-
/* for transaction */
u32 seqnum;
bool resp_addr_changable;
@@ -181,7 +178,7 @@ struct snd_efw_phys_meters {
} __packed;
enum snd_efw_clock_source {
SND_EFW_CLOCK_SOURCE_INTERNAL = 0,
- SND_EFW_CLOCK_SOURCE_SYTMATCH = 1,
+ // Unused.
SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2,
SND_EFW_CLOCK_SOURCE_SPDIF = 3,
SND_EFW_CLOCK_SOURCE_ADAT_1 = 4,
@@ -227,12 +224,4 @@ int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode);
int snd_efw_create_hwdep_device(struct snd_efw *efw);
-#define SND_EFW_DEV_ENTRY(vendor, model) \
-{ \
- .match_flags = IEEE1394_MATCH_VENDOR_ID | \
- IEEE1394_MATCH_MODEL_ID, \
- .vendor_id = vendor,\
- .model_id = model \
-}
-
#endif
diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c
index e93eb4616c5f..037833cd066e 100644
--- a/sound/firewire/fireworks/fireworks_hwdep.c
+++ b/sound/firewire/fireworks/fireworks_hwdep.c
@@ -34,6 +34,7 @@ hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;
if (copy_to_user(buf, &type, sizeof(type)))
return -EFAULT;
+ count += sizeof(type);
remained -= sizeof(type);
buf += sizeof(type);
@@ -212,7 +213,7 @@ hwdep_get_info(struct snd_efw *efw, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -318,7 +319,7 @@ int snd_efw_create_hwdep_device(struct snd_efw *efw)
err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
if (err < 0)
goto end;
- strcpy(hwdep->name, "Fireworks");
+ strscpy(hwdep->name, "Fireworks");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
hwdep->ops = ops;
hwdep->private_data = efw;
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c
index 84621e356848..350bf4d299c2 100644
--- a/sound/firewire/fireworks/fireworks_midi.c
+++ b/sound/firewire/fireworks/fireworks_midi.c
@@ -84,8 +84,8 @@ static void set_midi_substream_names(struct snd_efw *efw,
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
- snprintf(subs->name, sizeof(subs->name),
- "%s MIDI %d", efw->card->shortname, subs->number + 1);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d", efw->card->shortname, subs->number + 1);
}
}
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c
index 980580dfbb39..eaf7778211de 100644
--- a/sound/firewire/fireworks/fireworks_pcm.c
+++ b/sound/firewire/fireworks/fireworks_pcm.c
@@ -148,7 +148,7 @@ pcm_init_hw_params(struct snd_efw *efw,
}
/* limit rates */
- runtime->hw.rates = efw->supported_sampling_rate,
+ runtime->hw.rates = efw->supported_sampling_rate;
snd_pcm_limit_hw_rates(runtime);
limit_channels(&runtime->hw, pcm_channels);
@@ -250,7 +250,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_efw *efw = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
@@ -272,7 +272,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&efw->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--efw->substreams_counter;
snd_efw_stream_stop_duplex(efw);
@@ -397,6 +397,7 @@ int snd_efw_create_pcm_devices(struct snd_efw *efw)
goto end;
pcm->private_data = efw;
+ pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
index 2206af0fef42..53dbd4d4b0d0 100644
--- a/sound/firewire/fireworks/fireworks_stream.c
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -6,7 +6,7 @@
*/
#include "./fireworks.h"
-#define CALLBACK_TIMEOUT 100
+#define READY_TIMEOUT_MS 1000
static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
@@ -29,7 +29,7 @@ static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
if (err < 0)
return err;
- err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING);
+ err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING | CIP_UNAWARE_SYT);
if (err < 0) {
amdtp_stream_destroy(stream);
cmp_connection_destroy(conn);
@@ -50,8 +50,9 @@ static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
efw->firmware_version == 0x5070300 ||
efw->firmware_version == 0x5080000))
efw->tx_stream.flags |= CIP_UNALIGHED_DBC;
- // AudioFire9 always reports wrong dbs.
- if (efw->is_af9)
+ // AudioFire9 always reports wrong dbs. Onyx 1200F with the latest firmware (v4.6.0)
+ // also report wrong dbs at 88.2 kHz or greater.
+ if (efw->is_af9 || efw->firmware_version == 0x4060000)
efw->tx_stream.flags |= CIP_WRONG_DBS;
// Firmware version 5.5 reports fixed interval for dbc.
if (efw->firmware_version == 0x5050000)
@@ -264,6 +265,15 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw)
return err;
if (!amdtp_stream_running(&efw->rx_stream)) {
+ unsigned int tx_init_skip_cycles;
+
+ // Audiofire 2/4 skip an isochronous cycle several thousands after starting
+ // packet transmission.
+ if (efw->is_fireworks3 && !efw->is_af9)
+ tx_init_skip_cycles = 6000;
+ else
+ tx_init_skip_cycles = 0;
+
err = start_stream(efw, &efw->rx_stream, rate);
if (err < 0)
goto error;
@@ -272,15 +282,14 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw)
if (err < 0)
goto error;
- err = amdtp_domain_start(&efw->domain, 0);
+ // NOTE: The device ignores presentation time expressed by the value of syt field
+ // of CIP header in received packets. The sequence of the number of data blocks per
+ // packet is important for media clock recovery.
+ err = amdtp_domain_start(&efw->domain, tx_init_skip_cycles, true, false);
if (err < 0)
goto error;
- // Wait first callback.
- if (!amdtp_stream_wait_callback(&efw->rx_stream,
- CALLBACK_TIMEOUT) ||
- !amdtp_stream_wait_callback(&efw->tx_stream,
- CALLBACK_TIMEOUT)) {
+ if (!amdtp_domain_wait_ready(&efw->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c
index 0f533f5bd960..9f8c53b39f95 100644
--- a/sound/firewire/fireworks/fireworks_transaction.c
+++ b/sound/firewire/fireworks/fireworks_transaction.c
@@ -123,7 +123,7 @@ copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
t = (struct snd_efw_transaction *)data;
length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length);
- spin_lock_irq(&efw->lock);
+ spin_lock(&efw->lock);
if (efw->push_ptr < efw->pull_ptr)
capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr);
@@ -190,7 +190,7 @@ handle_resp_for_user(struct fw_card *card, int generation, int source,
copy_resp_to_buf(efw, data, length, rcode);
end:
- spin_unlock_irq(&instances_lock);
+ spin_unlock(&instances_lock);
}
static void
diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c
index 6655af53b367..ee574b5d7406 100644
--- a/sound/firewire/isight.c
+++ b/sound/firewire/isight.c
@@ -77,7 +77,7 @@ struct audio_payload {
MODULE_DESCRIPTION("iSight audio driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
static struct fw_iso_packet audio_packet = {
.payload_length = sizeof(struct audio_payload),
@@ -454,7 +454,8 @@ static int isight_create_pcm(struct isight *isight)
if (err < 0)
return err;
pcm->private_data = isight;
- strcpy(pcm->name, "iSight");
+ pcm->nonatomic = true;
+ strscpy(pcm->name, "iSight");
isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
isight->pcm->ops = &ops;
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
@@ -637,13 +638,13 @@ static int isight_probe(struct fw_unit *unit,
card->private_free = isight_card_free;
- strcpy(card->driver, "iSight");
- strcpy(card->shortname, "Apple iSight");
+ strscpy(card->driver, "iSight");
+ strscpy(card->shortname, "Apple iSight");
snprintf(card->longname, sizeof(card->longname),
"Apple iSight (GUID %08x%08x) at %s, S%d",
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&unit->device), 100 << fw_dev->max_speed);
- strcpy(card->mixername, "iSight");
+ strscpy(card->mixername, "iSight");
err = isight_create_pcm(isight);
if (err < 0)
diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c
index 85c4f4477c7f..654e1a6050a9 100644
--- a/sound/firewire/lib.c
+++ b/sound/firewire/lib.c
@@ -67,38 +67,6 @@ int snd_fw_transaction(struct fw_unit *unit, int tcode,
}
EXPORT_SYMBOL(snd_fw_transaction);
-#define PROBE_DELAY_MS (2 * MSEC_PER_SEC)
-
-/**
- * snd_fw_schedule_registration - schedule work for sound card registration
- * @unit: an instance for unit on IEEE 1394 bus
- * @dwork: delayed work with callback function
- *
- * This function is not designed for general purposes. When new unit is
- * connected to IEEE 1394 bus, the bus is under bus-reset state because of
- * topological change. In this state, units tend to fail both of asynchronous
- * and isochronous communication. To avoid this problem, this function is used
- * to postpone sound card registration after the state. The callers must
- * set up instance of delayed work in advance.
- */
-void snd_fw_schedule_registration(struct fw_unit *unit,
- struct delayed_work *dwork)
-{
- u64 now, delay;
-
- now = get_jiffies_64();
- delay = fw_parent_device(unit)->card->reset_jiffies
- + msecs_to_jiffies(PROBE_DELAY_MS);
-
- if (time_after64(delay, now))
- delay -= now;
- else
- delay = 0;
-
- mod_delayed_work(system_wq, dwork, delay);
-}
-EXPORT_SYMBOL(snd_fw_schedule_registration);
-
MODULE_DESCRIPTION("FireWire audio helper functions");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h
index dc815dc3933e..664dfdb9e58d 100644
--- a/sound/firewire/lib.h
+++ b/sound/firewire/lib.h
@@ -23,7 +23,4 @@ static inline bool rcode_is_permanent_error(int rcode)
return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
}
-void snd_fw_schedule_registration(struct fw_unit *unit,
- struct delayed_work *dwork);
-
#endif
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile
index 7c502d35103c..df0fe886dbc0 100644
--- a/sound/firewire/motu/Makefile
+++ b/sound/firewire/motu/Makefile
@@ -1,7 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS_amdtp-motu.o := -I$(src)
-snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \
+snd-firewire-motu-y := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \
motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \
- motu-protocol-v2.o motu-protocol-v3.o
+ motu-protocol-v2.o motu-protocol-v3.o \
+ motu-protocol-v1.o motu-register-dsp-message-parser.o \
+ motu-command-dsp-message-parser.o
obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o
diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c
index edb31ac26868..39ed57d2c5a0 100644
--- a/sound/firewire/motu/amdtp-motu.c
+++ b/sound/firewire/motu/amdtp-motu.c
@@ -16,6 +16,14 @@
#define CIP_FMT_MOTU_TX_V3 0x22
#define MOTU_FDF_AM824 0x22
+#define TICKS_PER_CYCLE 3072
+#define CYCLES_PER_SECOND 8000
+#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND)
+
+#define CIP_SPH_CYCLE_SHIFT 12
+#define CIP_SPH_CYCLE_MASK 0x01fff000
+#define CIP_SPH_OFFSET_MASK 0x00000fff
+
/*
* Nominally 3125 bytes/second, but the MIDI port's clock might be
* 1% too slow, and the bus clock 100 ppm too fast.
@@ -23,14 +31,6 @@
#define MIDI_BYTES_PER_SECOND 3093
struct amdtp_motu {
- /* For timestamp processing. */
- unsigned int quotient_ticks_per_event;
- unsigned int remainder_ticks_per_event;
- unsigned int next_ticks;
- unsigned int next_accumulated;
- unsigned int next_cycles;
- unsigned int next_seconds;
-
unsigned int pcm_chunks;
unsigned int pcm_byte_offset;
@@ -41,26 +41,16 @@ struct amdtp_motu {
int midi_db_count;
unsigned int midi_db_interval;
+
+ struct amdtp_motu_cache *cache;
};
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int midi_ports,
struct snd_motu_packet_format *formats)
{
- static const struct {
- unsigned int quotient_ticks_per_event;
- unsigned int remainder_ticks_per_event;
- } params[] = {
- [CIP_SFC_44100] = { 557, 123 },
- [CIP_SFC_48000] = { 512, 0 },
- [CIP_SFC_88200] = { 278, 282 },
- [CIP_SFC_96000] = { 256, 0 },
- [CIP_SFC_176400] = { 139, 141 },
- [CIP_SFC_192000] = { 128, 0 },
- };
struct amdtp_motu *p = s->protocol;
unsigned int pcm_chunks, data_chunks, data_block_quadlets;
- unsigned int delay;
unsigned int mode;
int i, err;
@@ -83,7 +73,7 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
data_chunks = formats->msg_chunks + pcm_chunks;
data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4);
- err = amdtp_stream_set_parameters(s, rate, data_block_quadlets);
+ err = amdtp_stream_set_parameters(s, rate, data_block_quadlets, 1);
if (err < 0)
return err;
@@ -97,19 +87,6 @@ int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
p->midi_db_count = 0;
p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND;
- /* IEEE 1394 bus requires. */
- delay = 0x2e00;
-
- /* For no-data or empty packets to adjust PCM sampling frequency. */
- delay += 8000 * 3072 * s->syt_interval / rate;
-
- p->next_seconds = 0;
- p->next_cycles = delay / 3072;
- p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event;
- p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event;
- p->next_ticks = delay % 3072;
- p->next_accumulated = 0;
-
return 0;
}
@@ -299,44 +276,77 @@ static void __maybe_unused copy_message(u64 *frames, __be32 *buffer,
/* This is just for v2/v3 protocol. */
for (i = 0; i < data_blocks; ++i) {
- *frames = (be32_to_cpu(buffer[1]) << 16) |
- (be32_to_cpu(buffer[2]) >> 16);
+ *frames = be32_to_cpu(buffer[1]);
+ *frames <<= 16;
+ *frames |= be32_to_cpu(buffer[2]) >> 16;
+ ++frames;
buffer += data_block_quadlets;
- frames++;
}
}
-static void probe_tracepoints_events(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets)
+static void probe_tracepoints_events(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count)
{
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
trace_data_block_sph(s, data_blocks, buf);
trace_data_block_message(s, data_blocks, buf);
+
+ desc = amdtp_stream_next_packet_desc(s, desc);
}
}
-static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void cache_event_offsets(struct amdtp_motu_cache *cache, const __be32 *buf,
+ unsigned int data_blocks, unsigned int data_block_quadlets)
{
+ unsigned int *event_offsets = cache->event_offsets;
+ const unsigned int cache_size = cache->size;
+ unsigned int cache_tail = cache->tail;
+ unsigned int base_tick = cache->tx_cycle_count * TICKS_PER_CYCLE;
+ int i;
+
+ for (i = 0; i < data_blocks; ++i) {
+ u32 sph = be32_to_cpu(*buf);
+ unsigned int tick;
+
+ tick = ((sph & CIP_SPH_CYCLE_MASK) >> CIP_SPH_CYCLE_SHIFT) * TICKS_PER_CYCLE +
+ (sph & CIP_SPH_OFFSET_MASK);
+
+ if (tick < base_tick)
+ tick += TICKS_PER_SECOND;
+ event_offsets[cache_tail] = tick - base_tick;
+
+ cache_tail = (cache_tail + 1) % cache_size;
+ buf += data_block_quadlets;
+ }
+
+ cache->tail = cache_tail;
+ cache->tx_cycle_count = (cache->tx_cycle_count + 1) % CYCLES_PER_SECOND;
+}
+
+static void process_ir_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
+{
+ struct snd_motu *motu = container_of(s, struct snd_motu, tx_stream);
struct amdtp_motu *p = s->protocol;
+ const struct pkt_desc *cursor = desc;
unsigned int pcm_frames = 0;
int i;
+ if (p->cache->tx_cycle_count == UINT_MAX)
+ p->cache->tx_cycle_count = (s->domain->processing_cycle.tx_start % CYCLES_PER_SECOND);
+
// For data block processing.
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
+ cache_event_offsets(p->cache, buf, data_blocks, s->data_block_quadlets);
+
if (pcm) {
read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames);
pcm_frames += data_blocks;
@@ -344,70 +354,57 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
if (p->midi_ports)
read_midi_messages(s, buf, data_blocks);
+
+ desc = amdtp_stream_next_packet_desc(s, desc);
}
+ desc = cursor;
+ if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP)
+ snd_motu_register_dsp_message_parser_parse(s, desc, count);
+ else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP)
+ snd_motu_command_dsp_message_parser_parse(s, desc, count);
+
// For tracepoints.
if (trace_data_block_sph_enabled() ||
trace_data_block_message_enabled())
- probe_tracepoints_events(s, descs, packets);
-
- return pcm_frames;
-}
-
-static inline void compute_next_elapse_from_start(struct amdtp_motu *p)
-{
- p->next_accumulated += p->remainder_ticks_per_event;
- if (p->next_accumulated >= 441) {
- p->next_accumulated -= 441;
- p->next_ticks++;
- }
-
- p->next_ticks += p->quotient_ticks_per_event;
- if (p->next_ticks >= 3072) {
- p->next_ticks -= 3072;
- p->next_cycles++;
- }
-
- if (p->next_cycles >= 8000) {
- p->next_cycles -= 8000;
- p->next_seconds++;
- }
-
- if (p->next_seconds >= 128)
- p->next_seconds -= 128;
+ probe_tracepoints_events(s, desc, count);
}
-static void write_sph(struct amdtp_stream *s, __be32 *buffer,
- unsigned int data_blocks)
+static void write_sph(struct amdtp_motu_cache *cache, __be32 *buffer, unsigned int data_blocks,
+ unsigned int data_block_quadlets)
{
- struct amdtp_motu *p = s->protocol;
- unsigned int next_cycles;
- unsigned int i;
- u32 sph;
+ unsigned int *event_offsets = cache->event_offsets;
+ const unsigned int cache_size = cache->size;
+ unsigned int cache_head = cache->head;
+ unsigned int base_tick = cache->rx_cycle_count * TICKS_PER_CYCLE;
+ int i;
for (i = 0; i < data_blocks; i++) {
- next_cycles = (s->start_cycle + p->next_cycles) % 8000;
- sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff;
+ unsigned int tick = (base_tick + event_offsets[cache_head]) % TICKS_PER_SECOND;
+ u32 sph = ((tick / TICKS_PER_CYCLE) << CIP_SPH_CYCLE_SHIFT) | (tick % TICKS_PER_CYCLE);
*buffer = cpu_to_be32(sph);
- compute_next_elapse_from_start(p);
-
- buffer += s->data_block_quadlets;
+ cache_head = (cache_head + 1) % cache_size;
+ buffer += data_block_quadlets;
}
+
+ cache->head = cache_head;
+ cache->rx_cycle_count = (cache->rx_cycle_count + 1) % CYCLES_PER_SECOND;
}
-static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_it_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
struct amdtp_motu *p = s->protocol;
+ const struct pkt_desc *cursor = desc;
unsigned int pcm_frames = 0;
int i;
+ if (p->cache->rx_cycle_count == UINT_MAX)
+ p->cache->rx_cycle_count = (s->domain->processing_cycle.rx_start % CYCLES_PER_SECOND);
+
// For data block processing.
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -421,26 +418,27 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
if (p->midi_ports)
write_midi_messages(s, buf, data_blocks);
- // TODO: how to interact control messages between userspace?
+ write_sph(p->cache, buf, data_blocks, s->data_block_quadlets);
- write_sph(s, buf, data_blocks);
+ desc = amdtp_stream_next_packet_desc(s, desc);
}
+ desc = cursor;
+
// For tracepoints.
if (trace_data_block_sph_enabled() ||
trace_data_block_message_enabled())
- probe_tracepoints_events(s, descs, packets);
-
- return pcm_frames;
+ probe_tracepoints_events(s, desc, count);
}
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir,
- const struct snd_motu_spec *spec)
+ const struct snd_motu_spec *spec, struct amdtp_motu_cache *cache)
{
amdtp_stream_process_ctx_payloads_t process_ctx_payloads;
int fmt = CIP_FMT_MOTU;
- int flags = CIP_BLOCKING;
+ unsigned int flags = CIP_BLOCKING | CIP_UNAWARE_SYT;
+ struct amdtp_motu *p;
int err;
if (dir == AMDTP_IN_STREAM) {
@@ -478,9 +476,10 @@ int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
if (dir == AMDTP_OUT_STREAM) {
// Use fixed value for FDF field.
s->ctx_data.rx.fdf = MOTU_FDF_AM824;
- // Not used.
- s->ctx_data.rx.syt_override = 0xffff;
}
+ p = s->protocol;
+ p->cache = cache;
+
return 0;
}
diff --git a/sound/firewire/motu/motu-command-dsp-message-parser.c b/sound/firewire/motu/motu-command-dsp-message-parser.c
new file mode 100644
index 000000000000..5d8a86a12f1f
--- /dev/null
+++ b/sound/firewire/motu/motu-command-dsp-message-parser.c
@@ -0,0 +1,184 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// motu-command-dsp-message-parser.c - a part of driver for MOTU FireWire series
+//
+// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+
+// Below models allow software to configure their DSP function by command transferred in
+// asynchronous transaction:
+// * 828 mk3 (FireWire only and Hybrid)
+// * 896 mk3 (FireWire only and Hybrid)
+// * Ultralite mk3 (FireWire only and Hybrid)
+// * Traveler mk3
+// * Track 16
+//
+// Isochronous packets from the above models includes messages to report state of hardware meter.
+
+#include "motu.h"
+
+enum msg_parser_state {
+ INITIALIZED,
+ FRAGMENT_DETECTED,
+ AVAILABLE,
+};
+
+struct msg_parser {
+ spinlock_t lock;
+ enum msg_parser_state state;
+ unsigned int interval;
+ unsigned int message_count;
+ unsigned int fragment_pos;
+ unsigned int value_index;
+ u64 value;
+ struct snd_firewire_motu_command_dsp_meter meter;
+};
+
+int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu)
+{
+ struct msg_parser *parser;
+
+ parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL);
+ if (!parser)
+ return -ENOMEM;
+ spin_lock_init(&parser->lock);
+ motu->message_parser = parser;
+
+ return 0;
+}
+
+int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc)
+{
+ struct msg_parser *parser = motu->message_parser;
+
+ parser->state = INITIALIZED;
+
+ // All of data blocks don't have messages with meaningful information.
+ switch (sfc) {
+ case CIP_SFC_176400:
+ case CIP_SFC_192000:
+ parser->interval = 4;
+ break;
+ case CIP_SFC_88200:
+ case CIP_SFC_96000:
+ parser->interval = 2;
+ break;
+ case CIP_SFC_32000:
+ case CIP_SFC_44100:
+ case CIP_SFC_48000:
+ default:
+ parser->interval = 1;
+ break;
+ }
+
+ return 0;
+}
+
+#define FRAGMENT_POS 6
+#define MIDI_BYTE_POS 7
+#define MIDI_FLAG_POS 8
+// One value of hardware meter consists of 4 messages.
+#define FRAGMENTS_PER_VALUE 4
+#define VALUES_AT_IMAGE_END 0xffffffffffffffff
+
+void snd_motu_command_dsp_message_parser_parse(const struct amdtp_stream *s,
+ const struct pkt_desc *desc, unsigned int count)
+{
+ struct snd_motu *motu = container_of(s, struct snd_motu, tx_stream);
+ unsigned int data_block_quadlets = s->data_block_quadlets;
+ struct msg_parser *parser = motu->message_parser;
+ unsigned int interval = parser->interval;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&parser->lock, flags);
+
+ for (i = 0; i < count; ++i) {
+ __be32 *buffer = desc->ctx_payload;
+ unsigned int data_blocks = desc->data_blocks;
+ int j;
+
+ desc = amdtp_stream_next_packet_desc(s, desc);
+
+ for (j = 0; j < data_blocks; ++j) {
+ u8 *b = (u8 *)buffer;
+ buffer += data_block_quadlets;
+
+ switch (parser->state) {
+ case INITIALIZED:
+ {
+ u8 fragment = b[FRAGMENT_POS];
+
+ if (fragment > 0) {
+ parser->value = fragment;
+ parser->message_count = 1;
+ parser->state = FRAGMENT_DETECTED;
+ }
+ break;
+ }
+ case FRAGMENT_DETECTED:
+ {
+ if (parser->message_count % interval == 0) {
+ u8 fragment = b[FRAGMENT_POS];
+
+ parser->value >>= 8;
+ parser->value |= (u64)fragment << 56;
+
+ if (parser->value == VALUES_AT_IMAGE_END) {
+ parser->state = AVAILABLE;
+ parser->fragment_pos = 0;
+ parser->value_index = 0;
+ parser->message_count = 0;
+ }
+ }
+ ++parser->message_count;
+ break;
+ }
+ case AVAILABLE:
+ default:
+ {
+ if (parser->message_count % interval == 0) {
+ u8 fragment = b[FRAGMENT_POS];
+
+ parser->value >>= 8;
+ parser->value |= (u64)fragment << 56;
+ ++parser->fragment_pos;
+
+ if (parser->fragment_pos == 4) {
+ // Skip the last two quadlets since they could be
+ // invalid value (0xffffffff) as floating point
+ // number.
+ if (parser->value_index <
+ SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT - 2) {
+ u32 val = (u32)(parser->value >> 32);
+ parser->meter.data[parser->value_index] = val;
+ }
+ ++parser->value_index;
+ parser->fragment_pos = 0;
+ }
+
+ if (parser->value == VALUES_AT_IMAGE_END) {
+ parser->value_index = 0;
+ parser->fragment_pos = 0;
+ parser->message_count = 0;
+ }
+ }
+ ++parser->message_count;
+ break;
+ }
+ }
+ }
+ }
+
+ spin_unlock_irqrestore(&parser->lock, flags);
+}
+
+void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu,
+ struct snd_firewire_motu_command_dsp_meter *meter)
+{
+ struct msg_parser *parser = motu->message_parser;
+ unsigned long flags;
+
+ spin_lock_irqsave(&parser->lock, flags);
+ memcpy(meter, &parser->meter, sizeof(*meter));
+ spin_unlock_irqrestore(&parser->lock, flags);
+}
diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c
index 0764a477052a..fa2685665db3 100644
--- a/sound/firewire/motu/motu-hwdep.c
+++ b/sound/firewire/motu/motu-hwdep.c
@@ -16,6 +16,14 @@
#include "motu.h"
+static bool has_dsp_event(struct snd_motu *motu)
+{
+ if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP)
+ return (snd_motu_register_dsp_message_parser_count_event(motu) > 0);
+ else
+ return false;
+}
+
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
@@ -25,7 +33,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
spin_lock_irq(&motu->lock);
- while (!motu->dev_lock_changed && motu->msg == 0) {
+ while (!motu->dev_lock_changed && motu->msg == 0 && !has_dsp_event(motu)) {
prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&motu->lock);
schedule();
@@ -40,20 +48,50 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (motu->dev_lock_count > 0);
motu->dev_lock_changed = false;
+ spin_unlock_irq(&motu->lock);
- count = min_t(long, count, sizeof(event.lock_status));
- } else {
+ count = min_t(long, count, sizeof(event));
+ if (copy_to_user(buf, &event, count))
+ return -EFAULT;
+ } else if (motu->msg > 0) {
event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION;
event.motu_notification.message = motu->msg;
motu->msg = 0;
+ spin_unlock_irq(&motu->lock);
- count = min_t(long, count, sizeof(event.motu_notification));
- }
+ count = min_t(long, count, sizeof(event));
+ if (copy_to_user(buf, &event, count))
+ return -EFAULT;
+ } else if (has_dsp_event(motu)) {
+ size_t consumed = 0;
+ u32 __user *ptr;
+ u32 ev;
- spin_unlock_irq(&motu->lock);
+ spin_unlock_irq(&motu->lock);
- if (copy_to_user(buf, &event, count))
- return -EFAULT;
+ // Header is filled later.
+ consumed += sizeof(event.motu_register_dsp_change);
+
+ while (consumed < count &&
+ snd_motu_register_dsp_message_parser_copy_event(motu, &ev)) {
+ ptr = (u32 __user *)(buf + consumed);
+ if (put_user(ev, ptr))
+ return -EFAULT;
+ consumed += sizeof(ev);
+ }
+
+ event.motu_register_dsp_change.type = SNDRV_FIREWIRE_EVENT_MOTU_REGISTER_DSP_CHANGE;
+ event.motu_register_dsp_change.count =
+ (consumed - sizeof(event.motu_register_dsp_change)) / 4;
+ if (copy_to_user(buf, &event, sizeof(event.motu_register_dsp_change)))
+ return -EFAULT;
+
+ count = consumed;
+ } else {
+ spin_unlock_irq(&motu->lock);
+
+ count = 0;
+ }
return count;
}
@@ -67,13 +105,13 @@ static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_wait(file, &motu->hwdep_wait, wait);
spin_lock_irq(&motu->lock);
- if (motu->dev_lock_changed || motu->msg)
+ if (motu->dev_lock_changed || motu->msg || has_dsp_event(motu))
events = EPOLLIN | EPOLLRDNORM;
else
events = 0;
spin_unlock_irq(&motu->lock);
- return events | EPOLLOUT;
+ return events;
}
static int hwdep_get_info(struct snd_motu *motu, void __user *arg)
@@ -86,7 +124,7 @@ static int hwdep_get_info(struct snd_motu *motu, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -155,6 +193,71 @@ static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
return hwdep_lock(motu);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(motu);
+ case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_METER:
+ {
+ struct snd_firewire_motu_register_dsp_meter *meter;
+ int err;
+
+ if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP))
+ return -ENXIO;
+
+ meter = kzalloc(sizeof(*meter), GFP_KERNEL);
+ if (!meter)
+ return -ENOMEM;
+
+ snd_motu_register_dsp_message_parser_copy_meter(motu, meter);
+
+ err = copy_to_user((void __user *)arg, meter, sizeof(*meter));
+ kfree(meter);
+
+ if (err)
+ return -EFAULT;
+
+ return 0;
+ }
+ case SNDRV_FIREWIRE_IOCTL_MOTU_COMMAND_DSP_METER:
+ {
+ struct snd_firewire_motu_command_dsp_meter *meter;
+ int err;
+
+ if (!(motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP))
+ return -ENXIO;
+
+ meter = kzalloc(sizeof(*meter), GFP_KERNEL);
+ if (!meter)
+ return -ENOMEM;
+
+ snd_motu_command_dsp_message_parser_copy_meter(motu, meter);
+
+ err = copy_to_user((void __user *)arg, meter, sizeof(*meter));
+ kfree(meter);
+
+ if (err)
+ return -EFAULT;
+
+ return 0;
+ }
+ case SNDRV_FIREWIRE_IOCTL_MOTU_REGISTER_DSP_PARAMETER:
+ {
+ struct snd_firewire_motu_register_dsp_parameter *param;
+ int err;
+
+ if (!(motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP))
+ return -ENXIO;
+
+ param = kzalloc(sizeof(*param), GFP_KERNEL);
+ if (!param)
+ return -ENOMEM;
+
+ snd_motu_register_dsp_message_parser_copy_parameter(motu, param);
+
+ err = copy_to_user((void __user *)arg, param, sizeof(*param));
+ kfree(param);
+ if (err)
+ return -EFAULT;
+
+ return 0;
+ }
default:
return -ENOIOCTLCMD;
}
@@ -187,11 +290,13 @@ int snd_motu_create_hwdep_device(struct snd_motu *motu)
if (err < 0)
return err;
- strcpy(hwdep->name, "MOTU");
+ strscpy(hwdep->name, "MOTU");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU;
hwdep->ops = ops;
hwdep->private_data = motu;
hwdep->exclusive = true;
+ motu->hwdep = hwdep;
+
return 0;
}
diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c
index 2365f7dfde26..eebc7e790ee2 100644
--- a/sound/firewire/motu/motu-midi.c
+++ b/sound/firewire/motu/motu-midi.c
@@ -88,8 +88,8 @@ static void set_midi_substream_names(struct snd_motu *motu,
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
- snprintf(subs->name, sizeof(subs->name),
- "%s MIDI %d", motu->card->shortname, subs->number + 1);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d", motu->card->shortname, subs->number + 1);
}
}
diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c
index 8e1437371263..7b4d476af348 100644
--- a/sound/firewire/motu/motu-pcm.c
+++ b/sound/firewire/motu/motu-pcm.c
@@ -210,7 +210,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_motu *motu = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
@@ -232,7 +232,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&motu->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--motu->substreams_counter;
snd_motu_stream_stop_duplex(motu);
@@ -360,7 +360,8 @@ int snd_motu_create_pcm_devices(struct snd_motu *motu)
if (err < 0)
return err;
pcm->private_data = motu;
- strcpy(pcm->name, motu->card->shortname);
+ pcm->nonatomic = true;
+ strscpy(pcm->name, motu->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
diff --git a/sound/firewire/motu/motu-protocol-v1.c b/sound/firewire/motu/motu-protocol-v1.c
new file mode 100644
index 000000000000..e811629f167b
--- /dev/null
+++ b/sound/firewire/motu/motu-protocol-v1.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// motu-protocol-v1.c - a part of driver for MOTU FireWire series
+//
+// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+
+#include "motu.h"
+
+#include <linux/delay.h>
+
+// Status register for MOTU 828 (0x'ffff'f000'0b00).
+//
+// 0xffff0000: ISOC_COMM_CONTROL_MASK in motu-stream.c.
+// 0x00008000: mode of optical input interface.
+// 0x00008000: for S/PDIF signal.
+// 0x00000000: disabled or for ADAT signal.
+// 0x00004000: mode of optical output interface.
+// 0x00004000: for S/PDIF signal.
+// 0x00000000: disabled or for ADAT signal.
+// 0x00003f00: monitor input mode.
+// 0x00000800: analog-1/2
+// 0x00001a00: analog-3/4
+// 0x00002c00: analog-5/6
+// 0x00003e00: analog-7/8
+// 0x00000000: analog-1
+// 0x00000900: analog-2
+// 0x00001200: analog-3
+// 0x00001b00: analog-4
+// 0x00002400: analog-5
+// 0x00002d00: analog-6
+// 0x00003600: analog-7
+// 0x00003f00: analog-8
+// 0x00000080: enable stream input.
+// 0x00000040: disable monitor input.
+// 0x00000008: enable main out.
+// 0x00000004: rate of sampling clock.
+// 0x00000004: 48.0 kHz
+// 0x00000000: 44.1 kHz
+// 0x00000023: source of sampling clock.
+// 0x00000003: source packet header (SPH)
+// 0x00000002: S/PDIF on optical/coaxial interface.
+// 0x00000021: ADAT on optical interface
+// 0x00000001: ADAT on Dsub 9pin
+// 0x00000000: internal
+
+#define CLK_828_STATUS_OFFSET 0x0b00
+#define CLK_828_STATUS_MASK 0x0000ffff
+#define CLK_828_STATUS_FLAG_OPT_IN_IFACE_IS_SPDIF 0x00008000
+#define CLK_828_STATUS_FLAG_OPT_OUT_IFACE_IS_SPDIF 0x00004000
+#define CLK_828_STATUS_FLAG_FETCH_PCM_FRAMES 0x00000080
+#define CLK_828_STATUS_FLAG_ENABLE_OUTPUT 0x00000008
+#define CLK_828_STATUS_FLAG_RATE_48000 0x00000004
+#define CLK_828_STATUS_MASK_SRC 0x00000023
+#define CLK_828_STATUS_FLAG_SRC_ADAT_ON_OPT 0x00000021
+#define CLK_828_STATUS_FLAG_SRC_SPH 0x00000003
+#define CLK_828_STATUS_FLAG_SRC_SPDIF 0x00000002
+#define CLK_828_STATUS_FLAG_SRC_ADAT_ON_DSUB 0x00000001
+#define CLK_828_STATUS_FLAG_SRC_INTERNAL 0x00000000
+
+// Status register for MOTU 896 (0x'ffff'f000'0b14).
+//
+// 0xf0000000: enable physical and stream input to DAC.
+// 0x80000000: disable
+// 0x40000000: disable
+// 0x20000000: enable (prior to the other bits)
+// 0x10000000: disable
+// 0x00000000: disable
+// 0x08000000: speed of word clock signal output on BNC interface.
+// 0x00000000: force to low rate (44.1/48.0 kHz).
+// 0x08000000: follow to system clock.
+// 0x04000000: something relevant to clock.
+// 0x03000000: enable output.
+// 0x02000000: enabled irreversibly once standing unless the device voluntarily disables it.
+// 0x01000000: enabled irreversibly once standing unless the device voluntarily disables it.
+// 0x00ffff00: monitor input mode.
+// 0x00000000: disabled
+// 0x00004800: analog-1/2
+// 0x00005a00: analog-3/4
+// 0x00006c00: analog-5/6
+// 0x00007e00: analog-7/8
+// 0x00104800: AES/EBU-1/2
+// 0x00004000: analog-1
+// 0x00004900: analog-2
+// 0x00005200: analog-3
+// 0x00005b00: analog-4
+// 0x00006400: analog-5
+// 0x00006d00: analog-6
+// 0x00007600: analog-7
+// 0x00007f00: analog-8
+// 0x00104000: AES/EBU-1
+// 0x00104900: AES/EBU-2
+// 0x00000060: sample rate conversion for AES/EBU input/output.
+// 0x00000000: None
+// 0x00000020: input signal is converted to system rate
+// 0x00000040: output is slave to input, ignoring system rate
+// 0x00000060: output is double rate than system rate
+// 0x00000018: nominal rate of sampling clock.
+// 0x00000000: 44.1 kHz
+// 0x00000008: 48.0 kHz
+// 0x00000010: 88.2 kHz
+// 0x00000018: 96.0 kHz
+// 0x00000007: source of sampling clock.
+// 0x00000000: internal
+// 0x00000001: ADAT on optical interface
+// 0x00000002: AES/EBU on XLR
+// 0x00000003: source packet header (SPH)
+// 0x00000004: word clock on BNC
+// 0x00000005: ADAT on Dsub 9pin
+
+#define CLK_896_STATUS_OFFSET 0x0b14
+#define CLK_896_STATUS_FLAG_FETCH_ENABLE 0x20000000
+#define CLK_896_STATUS_FLAG_OUTPUT_ON 0x03000000
+#define CLK_896_STATUS_MASK_SRC 0x00000007
+#define CLK_896_STATUS_FLAG_SRC_INTERNAL 0x00000000
+#define CLK_896_STATUS_FLAG_SRC_ADAT_ON_OPT 0x00000001
+#define CLK_896_STATUS_FLAG_SRC_AESEBU 0x00000002
+#define CLK_896_STATUS_FLAG_SRC_SPH 0x00000003
+#define CLK_896_STATUS_FLAG_SRC_WORD 0x00000004
+#define CLK_896_STATUS_FLAG_SRC_ADAT_ON_DSUB 0x00000005
+#define CLK_896_STATUS_MASK_RATE 0x00000018
+#define CLK_896_STATUS_FLAG_RATE_44100 0x00000000
+#define CLK_896_STATUS_FLAG_RATE_48000 0x00000008
+#define CLK_896_STATUS_FLAG_RATE_88200 0x00000010
+#define CLK_896_STATUS_FLAG_RATE_96000 0x00000018
+
+static void parse_clock_rate_828(u32 data, unsigned int *rate)
+{
+ if (data & CLK_828_STATUS_FLAG_RATE_48000)
+ *rate = 48000;
+ else
+ *rate = 44100;
+}
+
+static int get_clock_rate_828(struct snd_motu *motu, unsigned int *rate)
+{
+ __be32 reg;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ parse_clock_rate_828(be32_to_cpu(reg), rate);
+
+ return 0;
+}
+
+static int parse_clock_rate_896(u32 data, unsigned int *rate)
+{
+ switch (data & CLK_896_STATUS_MASK_RATE) {
+ case CLK_896_STATUS_FLAG_RATE_44100:
+ *rate = 44100;
+ break;
+ case CLK_896_STATUS_FLAG_RATE_48000:
+ *rate = 48000;
+ break;
+ case CLK_896_STATUS_FLAG_RATE_88200:
+ *rate = 88200;
+ break;
+ case CLK_896_STATUS_FLAG_RATE_96000:
+ *rate = 96000;
+ break;
+ default:
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int get_clock_rate_896(struct snd_motu *motu, unsigned int *rate)
+{
+ __be32 reg;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_896_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ return parse_clock_rate_896(be32_to_cpu(reg), rate);
+}
+
+int snd_motu_protocol_v1_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
+{
+ if (motu->spec == &snd_motu_spec_828)
+ return get_clock_rate_828(motu, rate);
+ else if (motu->spec == &snd_motu_spec_896)
+ return get_clock_rate_896(motu, rate);
+ else
+ return -ENXIO;
+}
+
+static int set_clock_rate_828(struct snd_motu *motu, unsigned int rate)
+{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
+
+ data &= ~CLK_828_STATUS_FLAG_RATE_48000;
+ if (rate == 48000)
+ data |= CLK_828_STATUS_FLAG_RATE_48000;
+
+ reg = cpu_to_be32(data);
+ return snd_motu_transaction_write(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+}
+
+static int set_clock_rate_896(struct snd_motu *motu, unsigned int rate)
+{
+ unsigned int flag;
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_896_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg);
+
+ switch (rate) {
+ case 44100:
+ flag = CLK_896_STATUS_FLAG_RATE_44100;
+ break;
+ case 48000:
+ flag = CLK_896_STATUS_FLAG_RATE_48000;
+ break;
+ case 88200:
+ flag = CLK_896_STATUS_FLAG_RATE_88200;
+ break;
+ case 96000:
+ flag = CLK_896_STATUS_FLAG_RATE_96000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ data &= ~CLK_896_STATUS_MASK_RATE;
+ data |= flag;
+
+ reg = cpu_to_be32(data);
+ return snd_motu_transaction_write(motu, CLK_896_STATUS_OFFSET, &reg, sizeof(reg));
+}
+
+int snd_motu_protocol_v1_set_clock_rate(struct snd_motu *motu, unsigned int rate)
+{
+ if (motu->spec == &snd_motu_spec_828)
+ return set_clock_rate_828(motu, rate);
+ else if (motu->spec == &snd_motu_spec_896)
+ return set_clock_rate_896(motu, rate);
+ else
+ return -ENXIO;
+}
+
+static int get_clock_source_828(struct snd_motu *motu, enum snd_motu_clock_source *src)
+{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
+
+ switch (data & CLK_828_STATUS_MASK_SRC) {
+ case CLK_828_STATUS_FLAG_SRC_ADAT_ON_OPT:
+ *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
+ break;
+ case CLK_828_STATUS_FLAG_SRC_SPH:
+ *src = SND_MOTU_CLOCK_SOURCE_SPH;
+ break;
+ case CLK_828_STATUS_FLAG_SRC_SPDIF:
+ {
+ if (data & CLK_828_STATUS_FLAG_OPT_IN_IFACE_IS_SPDIF)
+ *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
+ else
+ *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
+ break;
+ }
+ case CLK_828_STATUS_FLAG_SRC_ADAT_ON_DSUB:
+ *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
+ break;
+ case CLK_828_STATUS_FLAG_SRC_INTERNAL:
+ *src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
+ break;
+ default:
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+static int get_clock_source_896(struct snd_motu *motu, enum snd_motu_clock_source *src)
+{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_896_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg);
+
+ switch (data & CLK_896_STATUS_MASK_SRC) {
+ case CLK_896_STATUS_FLAG_SRC_INTERNAL:
+ *src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
+ break;
+ case CLK_896_STATUS_FLAG_SRC_ADAT_ON_OPT:
+ *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
+ break;
+ case CLK_896_STATUS_FLAG_SRC_AESEBU:
+ *src = SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR;
+ break;
+ case CLK_896_STATUS_FLAG_SRC_SPH:
+ *src = SND_MOTU_CLOCK_SOURCE_SPH;
+ break;
+ case CLK_896_STATUS_FLAG_SRC_WORD:
+ *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
+ break;
+ case CLK_896_STATUS_FLAG_SRC_ADAT_ON_DSUB:
+ *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
+ break;
+ default:
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+int snd_motu_protocol_v1_get_clock_source(struct snd_motu *motu, enum snd_motu_clock_source *src)
+{
+ if (motu->spec == &snd_motu_spec_828)
+ return get_clock_source_828(motu, src);
+ else if (motu->spec == &snd_motu_spec_896)
+ return get_clock_source_896(motu, src);
+ else
+ return -ENXIO;
+}
+
+static int switch_fetching_mode_828(struct snd_motu *motu, bool enable)
+{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
+
+ data &= ~(CLK_828_STATUS_FLAG_FETCH_PCM_FRAMES | CLK_828_STATUS_FLAG_ENABLE_OUTPUT);
+ if (enable) {
+ // This transaction should be initiated after the device receives batch of packets
+ // since the device voluntarily mutes outputs. As a workaround, yield processor over
+ // 100 msec.
+ msleep(100);
+ data |= CLK_828_STATUS_FLAG_FETCH_PCM_FRAMES | CLK_828_STATUS_FLAG_ENABLE_OUTPUT;
+ }
+
+ reg = cpu_to_be32(data);
+ return snd_motu_transaction_write(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+}
+
+static int switch_fetching_mode_896(struct snd_motu *motu, bool enable)
+{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, CLK_896_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg);
+
+ data &= ~CLK_896_STATUS_FLAG_FETCH_ENABLE;
+ if (enable)
+ data |= CLK_896_STATUS_FLAG_FETCH_ENABLE | CLK_896_STATUS_FLAG_OUTPUT_ON;
+
+ reg = cpu_to_be32(data);
+ return snd_motu_transaction_write(motu, CLK_896_STATUS_OFFSET, &reg, sizeof(reg));
+}
+
+int snd_motu_protocol_v1_switch_fetching_mode(struct snd_motu *motu, bool enable)
+{
+ if (motu->spec == &snd_motu_spec_828)
+ return switch_fetching_mode_828(motu, enable);
+ else if (motu->spec == &snd_motu_spec_896)
+ return switch_fetching_mode_896(motu, enable);
+ else
+ return -ENXIO;
+}
+
+static int detect_packet_formats_828(struct snd_motu *motu)
+{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ motu->tx_packet_formats.pcm_byte_offset = 4;
+ motu->tx_packet_formats.msg_chunks = 2;
+
+ motu->rx_packet_formats.pcm_byte_offset = 4;
+ motu->rx_packet_formats.msg_chunks = 0;
+
+ err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
+
+ // The number of chunks is just reduced when SPDIF is activated.
+ if (!(data & CLK_828_STATUS_FLAG_OPT_IN_IFACE_IS_SPDIF))
+ motu->tx_packet_formats.pcm_chunks[0] += 8;
+
+ if (!(data & CLK_828_STATUS_FLAG_OPT_OUT_IFACE_IS_SPDIF))
+ motu->rx_packet_formats.pcm_chunks[0] += 8;
+
+ return 0;
+}
+
+static int detect_packet_formats_896(struct snd_motu *motu)
+{
+ // 24bit PCM frames follow to source packet header without message chunk.
+ motu->tx_packet_formats.pcm_byte_offset = 4;
+ motu->rx_packet_formats.pcm_byte_offset = 4;
+
+ // No message chunk in data block.
+ motu->tx_packet_formats.msg_chunks = 0;
+ motu->rx_packet_formats.msg_chunks = 0;
+
+ // Always enable optical interface for ADAT signal since the device have no registers
+ // to refer to current configuration.
+ motu->tx_packet_formats.pcm_chunks[0] += 8;
+ motu->tx_packet_formats.pcm_chunks[1] += 8;
+
+ motu->rx_packet_formats.pcm_chunks[0] += 8;
+ motu->rx_packet_formats.pcm_chunks[1] += 8;
+
+ return 0;
+}
+
+int snd_motu_protocol_v1_cache_packet_formats(struct snd_motu *motu)
+{
+ memcpy(motu->tx_packet_formats.pcm_chunks, motu->spec->tx_fixed_pcm_chunks,
+ sizeof(motu->tx_packet_formats.pcm_chunks));
+ memcpy(motu->rx_packet_formats.pcm_chunks, motu->spec->rx_fixed_pcm_chunks,
+ sizeof(motu->rx_packet_formats.pcm_chunks));
+
+ if (motu->spec == &snd_motu_spec_828)
+ return detect_packet_formats_828(motu);
+ else if (motu->spec == &snd_motu_spec_896)
+ return detect_packet_formats_896(motu);
+ else
+ return 0;
+}
+
+const struct snd_motu_spec snd_motu_spec_828 = {
+ .name = "828",
+ .protocol_version = SND_MOTU_PROTOCOL_V1,
+ .tx_fixed_pcm_chunks = {10, 0, 0},
+ .rx_fixed_pcm_chunks = {10, 0, 0},
+};
+
+const struct snd_motu_spec snd_motu_spec_896 = {
+ .name = "896",
+ .tx_fixed_pcm_chunks = {10, 10, 0},
+ .rx_fixed_pcm_chunks = {10, 10, 0},
+};
diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c
index e59e69ab1538..a5f70efa2e88 100644
--- a/sound/firewire/motu/motu-protocol-v2.c
+++ b/sound/firewire/motu/motu-protocol-v2.c
@@ -12,6 +12,13 @@
#define V2_CLOCK_RATE_SHIFT 3
#define V2_CLOCK_SRC_MASK 0x00000007
#define V2_CLOCK_SRC_SHIFT 0
+#define V2_CLOCK_SRC_AESEBU_ON_XLR 0x07 // In Traveler.
+#define V2_CLOCK_SRC_ADAT_ON_DSUB 0x05
+#define V2_CLOCK_SRC_WORD_ON_BNC 0x04
+#define V2_CLOCK_SRC_SPH 0x03
+#define V2_CLOCK_SRC_SPDIF 0x02 // on either coaxial or optical. AES/EBU in 896HD.
+#define V2_CLOCK_SRC_ADAT_ON_OPT 0x01
+#define V2_CLOCK_SRC_INTERNAL 0x00
#define V2_CLOCK_FETCH_ENABLE 0x02000000
#define V2_CLOCK_MODEL_SPECIFIC 0x04000000
@@ -78,64 +85,54 @@ int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu,
sizeof(reg));
}
-static int detect_clock_source_optical_model(struct snd_motu *motu, u32 data,
- enum snd_motu_clock_source *src)
+static int get_clock_source(struct snd_motu *motu, u32 data,
+ enum snd_motu_clock_source *src)
{
- switch (data) {
- case 0:
+ switch (data & V2_CLOCK_SRC_MASK) {
+ case V2_CLOCK_SRC_INTERNAL:
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
break;
- case 1:
+ case V2_CLOCK_SRC_ADAT_ON_OPT:
+ *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
+ break;
+ case V2_CLOCK_SRC_SPDIF:
{
- __be32 reg;
-
- // To check the configuration of optical interface.
- int err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET,
- &reg, sizeof(reg));
- if (err < 0)
- return err;
-
- if (be32_to_cpu(reg) & 0x00000200)
- *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
- else
- *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
+ bool support_iec60958_on_opt = (motu->spec == &snd_motu_spec_828mk2 ||
+ motu->spec == &snd_motu_spec_traveler);
+
+ if (motu->spec == &snd_motu_spec_896hd) {
+ *src = SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR;
+ } else if (!support_iec60958_on_opt) {
+ *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
+ } else {
+ __be32 reg;
+
+ // To check the configuration of optical interface.
+ int err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, &reg,
+ sizeof(reg));
+ if (err < 0)
+ return err;
+
+ if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) ==
+ V2_OPT_IFACE_MODE_SPDIF)
+ *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
+ else
+ *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
+ }
break;
}
- case 2:
- *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
- break;
- case 3:
+ case V2_CLOCK_SRC_SPH:
*src = SND_MOTU_CLOCK_SOURCE_SPH;
break;
- case 4:
+ case V2_CLOCK_SRC_WORD_ON_BNC:
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
break;
- case 5:
+ case V2_CLOCK_SRC_ADAT_ON_DSUB:
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
break;
- default:
- *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
- break;
- }
-
- return 0;
-}
-
-static int v2_detect_clock_source(struct snd_motu *motu, u32 data,
- enum snd_motu_clock_source *src)
-{
- switch (data) {
- case 0:
- *src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
- break;
- case 2:
- *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
- break;
- case 3:
- *src = SND_MOTU_CLOCK_SOURCE_SPH;
- break;
- case 4:
- *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
+ case V2_CLOCK_SRC_AESEBU_ON_XLR:
+ // For Traveler.
+ *src = SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR;
break;
default:
*src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
@@ -145,17 +142,6 @@ static int v2_detect_clock_source(struct snd_motu *motu, u32 data,
return 0;
}
-static int get_clock_source(struct snd_motu *motu, u32 data,
- enum snd_motu_clock_source *src)
-{
- data &= V2_CLOCK_SRC_MASK;
- if (motu->spec == &snd_motu_spec_828mk2 ||
- motu->spec == &snd_motu_spec_traveler)
- return detect_clock_source_optical_model(motu, data, src);
- else
- return v2_detect_clock_source(motu, data, src);
-}
-
int snd_motu_protocol_v2_get_clock_source(struct snd_motu *motu,
enum snd_motu_clock_source *src)
{
@@ -170,7 +156,7 @@ int snd_motu_protocol_v2_get_clock_source(struct snd_motu *motu,
return get_clock_source(motu, be32_to_cpu(reg), src);
}
-// Expected for Traveler and 896HD, which implements Altera Cyclone EP1C3.
+// Expected for Traveler, which implements Altera Cyclone EP1C3.
static int switch_fetching_mode_cyclone(struct snd_motu *motu, u32 *data,
bool enable)
{
@@ -207,6 +193,9 @@ int snd_motu_protocol_v2_switch_fetching_mode(struct snd_motu *motu,
if (motu->spec == &snd_motu_spec_828mk2) {
// 828mkII implements Altera ACEX 1K EP1K30. Nothing to do.
return 0;
+ } else if (motu->spec == &snd_motu_spec_896hd) {
+ // 896HD implements Altera Cyclone EP1C3 but nothing to do.
+ return 0;
} else {
__be32 reg;
u32 data;
@@ -235,59 +224,9 @@ int snd_motu_protocol_v2_switch_fetching_mode(struct snd_motu *motu,
}
}
-static int detect_packet_formats_828mk2(struct snd_motu *motu, u32 data)
-{
- if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) ==
- V2_OPT_IFACE_MODE_ADAT) {
- motu->tx_packet_formats.pcm_chunks[0] += 8;
- motu->tx_packet_formats.pcm_chunks[1] += 4;
- }
-
- if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) ==
- V2_OPT_IFACE_MODE_ADAT) {
- motu->rx_packet_formats.pcm_chunks[0] += 8;
- motu->rx_packet_formats.pcm_chunks[1] += 4;
- }
-
- return 0;
-}
-
-static int detect_packet_formats_traveler(struct snd_motu *motu, u32 data)
-{
- if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) ==
- V2_OPT_IFACE_MODE_ADAT) {
- motu->tx_packet_formats.pcm_chunks[0] += 8;
- motu->tx_packet_formats.pcm_chunks[1] += 4;
- }
-
- if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) ==
- V2_OPT_IFACE_MODE_ADAT) {
- motu->rx_packet_formats.pcm_chunks[0] += 8;
- motu->rx_packet_formats.pcm_chunks[1] += 4;
- }
-
- return 0;
-}
-
-static int detect_packet_formats_8pre(struct snd_motu *motu, u32 data)
-{
- if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) ==
- V2_OPT_IFACE_MODE_ADAT) {
- motu->tx_packet_formats.pcm_chunks[0] += 8;
- motu->tx_packet_formats.pcm_chunks[1] += 8;
- }
-
- if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) ==
- V2_OPT_IFACE_MODE_ADAT) {
- motu->rx_packet_formats.pcm_chunks[0] += 8;
- motu->rx_packet_formats.pcm_chunks[1] += 8;
- }
-
- return 0;
-}
-
int snd_motu_protocol_v2_cache_packet_formats(struct snd_motu *motu)
{
+ bool has_two_opt_ifaces = (motu->spec == &snd_motu_spec_8pre);
__be32 reg;
u32 data;
int err;
@@ -311,30 +250,51 @@ int snd_motu_protocol_v2_cache_packet_formats(struct snd_motu *motu)
motu->spec->rx_fixed_pcm_chunks,
sizeof(motu->rx_packet_formats.pcm_chunks));
- if (motu->spec == &snd_motu_spec_828mk2)
- return detect_packet_formats_828mk2(motu, data);
- else if (motu->spec == &snd_motu_spec_traveler)
- return detect_packet_formats_traveler(motu, data);
- else if (motu->spec == &snd_motu_spec_8pre)
- return detect_packet_formats_8pre(motu, data);
- else
- return 0;
+ if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) == V2_OPT_IFACE_MODE_ADAT) {
+ motu->tx_packet_formats.pcm_chunks[0] += 8;
+
+ if (!has_two_opt_ifaces)
+ motu->tx_packet_formats.pcm_chunks[1] += 4;
+ else
+ motu->tx_packet_formats.pcm_chunks[1] += 8;
+ }
+
+ if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) == V2_OPT_IFACE_MODE_ADAT) {
+ motu->rx_packet_formats.pcm_chunks[0] += 8;
+
+ if (!has_two_opt_ifaces)
+ motu->rx_packet_formats.pcm_chunks[1] += 4;
+ else
+ motu->rx_packet_formats.pcm_chunks[1] += 8;
+ }
+
+ return 0;
}
const struct snd_motu_spec snd_motu_spec_828mk2 = {
.name = "828mk2",
.protocol_version = SND_MOTU_PROTOCOL_V2,
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
- SND_MOTU_SPEC_TX_MIDI_2ND_Q,
+ SND_MOTU_SPEC_TX_MIDI_2ND_Q |
+ SND_MOTU_SPEC_REGISTER_DSP,
.tx_fixed_pcm_chunks = {14, 14, 0},
.rx_fixed_pcm_chunks = {14, 14, 0},
};
+const struct snd_motu_spec snd_motu_spec_896hd = {
+ .name = "896HD",
+ .protocol_version = SND_MOTU_PROTOCOL_V2,
+ .flags = SND_MOTU_SPEC_REGISTER_DSP,
+ .tx_fixed_pcm_chunks = {14, 14, 8},
+ .rx_fixed_pcm_chunks = {14, 14, 8},
+};
+
const struct snd_motu_spec snd_motu_spec_traveler = {
.name = "Traveler",
.protocol_version = SND_MOTU_PROTOCOL_V2,
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
- SND_MOTU_SPEC_TX_MIDI_2ND_Q,
+ SND_MOTU_SPEC_TX_MIDI_2ND_Q |
+ SND_MOTU_SPEC_REGISTER_DSP,
.tx_fixed_pcm_chunks = {14, 14, 8},
.rx_fixed_pcm_chunks = {14, 14, 8},
};
@@ -343,7 +303,8 @@ const struct snd_motu_spec snd_motu_spec_ultralite = {
.name = "UltraLite",
.protocol_version = SND_MOTU_PROTOCOL_V2,
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
- SND_MOTU_SPEC_TX_MIDI_2ND_Q,
+ SND_MOTU_SPEC_TX_MIDI_2ND_Q |
+ SND_MOTU_SPEC_REGISTER_DSP,
.tx_fixed_pcm_chunks = {14, 14, 0},
.rx_fixed_pcm_chunks = {14, 14, 0},
};
@@ -352,7 +313,9 @@ const struct snd_motu_spec snd_motu_spec_8pre = {
.name = "8pre",
.protocol_version = SND_MOTU_PROTOCOL_V2,
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
- SND_MOTU_SPEC_TX_MIDI_2ND_Q,
- .tx_fixed_pcm_chunks = {10, 6, 0},
- .rx_fixed_pcm_chunks = {10, 6, 0},
+ SND_MOTU_SPEC_TX_MIDI_2ND_Q |
+ SND_MOTU_SPEC_REGISTER_DSP,
+ // Two dummy chunks always in the end of data block.
+ .tx_fixed_pcm_chunks = {10, 10, 0},
+ .rx_fixed_pcm_chunks = {6, 6, 0},
};
diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c
index 4e6b0e449ee4..7254fdfe046a 100644
--- a/sound/firewire/motu/motu-protocol-v3.c
+++ b/sound/firewire/motu/motu-protocol-v3.c
@@ -13,6 +13,13 @@
#define V3_CLOCK_RATE_MASK 0x0000ff00
#define V3_CLOCK_RATE_SHIFT 8
#define V3_CLOCK_SOURCE_MASK 0x000000ff
+#define V3_CLOCK_SRC_INTERNAL 0x00
+#define V3_CLOCK_SRC_WORD_ON_BNC 0x01
+#define V3_CLOCK_SRC_SPH 0x02
+#define V3_CLOCK_SRC_AESEBU_ON_XLR 0x08
+#define V3_CLOCK_SRC_SPDIF_ON_COAX 0x10
+#define V3_CLOCK_SRC_OPT_IFACE_A 0x18
+#define V3_CLOCK_SRC_OPT_IFACE_B 0x19
#define V3_OPT_IFACE_MODE_OFFSET 0x0c94
#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001
@@ -97,28 +104,40 @@ int snd_motu_protocol_v3_set_clock_rate(struct snd_motu *motu,
return 0;
}
-static int detect_clock_source_828mk3(struct snd_motu *motu, u32 data,
- enum snd_motu_clock_source *src)
+int snd_motu_protocol_v3_get_clock_source(struct snd_motu *motu,
+ enum snd_motu_clock_source *src)
{
+ __be32 reg;
+ u32 data;
+ int err;
+
+ err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, &reg,
+ sizeof(reg));
+ if (err < 0)
+ return err;
+ data = be32_to_cpu(reg) & V3_CLOCK_SOURCE_MASK;
+
switch (data) {
- case 0x00:
+ case V3_CLOCK_SRC_INTERNAL:
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
break;
- case 0x01:
+ case V3_CLOCK_SRC_WORD_ON_BNC:
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
break;
- case 0x02:
+ case V3_CLOCK_SRC_SPH:
*src = SND_MOTU_CLOCK_SOURCE_SPH;
break;
- case 0x10:
+ case V3_CLOCK_SRC_AESEBU_ON_XLR:
+ *src = SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR;
+ break;
+ case V3_CLOCK_SRC_SPDIF_ON_COAX:
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
break;
- case 0x18:
- case 0x19:
+ case V3_CLOCK_SRC_OPT_IFACE_A:
+ case V3_CLOCK_SRC_OPT_IFACE_B:
{
__be32 reg;
u32 options;
- int err;
err = snd_motu_transaction_read(motu,
V3_OPT_IFACE_MODE_OFFSET, &reg, sizeof(reg));
@@ -126,7 +145,7 @@ static int detect_clock_source_828mk3(struct snd_motu *motu, u32 data,
return err;
options = be32_to_cpu(reg);
- if (data == 0x18) {
+ if (data == V3_CLOCK_SRC_OPT_IFACE_A) {
if (options & V3_NO_ADAT_OPT_IN_IFACE_A)
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A;
else
@@ -137,33 +156,8 @@ static int detect_clock_source_828mk3(struct snd_motu *motu, u32 data,
else
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B;
}
-
- break;
- }
- default:
- *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
break;
}
-
- return 0;
-}
-
-static int v3_detect_clock_source(struct snd_motu *motu, u32 data,
- enum snd_motu_clock_source *src)
-{
- switch (data) {
- case 0x00:
- *src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
- break;
- case 0x01:
- *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
- break;
- case 0x02:
- *src = SND_MOTU_CLOCK_SOURCE_SPH;
- break;
- case 0x10:
- *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
- break;
default:
*src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
break;
@@ -172,25 +166,6 @@ static int v3_detect_clock_source(struct snd_motu *motu, u32 data,
return 0;
}
-int snd_motu_protocol_v3_get_clock_source(struct snd_motu *motu,
- enum snd_motu_clock_source *src)
-{
- __be32 reg;
- u32 data;
- int err;
-
- err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, &reg,
- sizeof(reg));
- if (err < 0)
- return err;
- data = be32_to_cpu(reg) & V3_CLOCK_SOURCE_MASK;
-
- if (motu->spec == &snd_motu_spec_828mk3)
- return detect_clock_source_828mk3(motu, data, src);
- else
- return v3_detect_clock_source(motu, data, src);
-}
-
int snd_motu_protocol_v3_switch_fetching_mode(struct snd_motu *motu,
bool enable)
{
@@ -214,7 +189,7 @@ int snd_motu_protocol_v3_switch_fetching_mode(struct snd_motu *motu,
sizeof(reg));
}
-static int detect_packet_formats_828mk3(struct snd_motu *motu, u32 data)
+static int detect_packet_formats_with_opt_ifaces(struct snd_motu *motu, u32 data)
{
if (data & V3_ENABLE_OPT_IN_IFACE_A) {
if (data & V3_NO_ADAT_OPT_IN_IFACE_A) {
@@ -284,19 +259,51 @@ int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu)
motu->spec->rx_fixed_pcm_chunks,
sizeof(motu->rx_packet_formats.pcm_chunks));
- if (motu->spec == &snd_motu_spec_828mk3)
- return detect_packet_formats_828mk3(motu, data);
+ if (motu->spec == &snd_motu_spec_828mk3_fw ||
+ motu->spec == &snd_motu_spec_828mk3_hybrid ||
+ motu->spec == &snd_motu_spec_896mk3 ||
+ motu->spec == &snd_motu_spec_traveler_mk3 ||
+ motu->spec == &snd_motu_spec_track16)
+ return detect_packet_formats_with_opt_ifaces(motu, data);
else
return 0;
}
+const struct snd_motu_spec snd_motu_spec_828mk3_fw = {
+ .name = "828mk3",
+ .protocol_version = SND_MOTU_PROTOCOL_V3,
+ .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_TX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_COMMAND_DSP,
+ .tx_fixed_pcm_chunks = {18, 18, 14},
+ .rx_fixed_pcm_chunks = {14, 14, 10},
+};
-const struct snd_motu_spec snd_motu_spec_828mk3 = {
+const struct snd_motu_spec snd_motu_spec_828mk3_hybrid = {
.name = "828mk3",
.protocol_version = SND_MOTU_PROTOCOL_V3,
.flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
- SND_MOTU_SPEC_TX_MIDI_3RD_Q,
+ SND_MOTU_SPEC_TX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_COMMAND_DSP,
.tx_fixed_pcm_chunks = {18, 18, 14},
+ .rx_fixed_pcm_chunks = {14, 14, 14}, // Additional 4 dummy chunks at higher rate.
+};
+
+const struct snd_motu_spec snd_motu_spec_896mk3 = {
+ .name = "896mk3",
+ .protocol_version = SND_MOTU_PROTOCOL_V3,
+ .flags = SND_MOTU_SPEC_COMMAND_DSP,
+ .tx_fixed_pcm_chunks = {18, 14, 10},
+ .rx_fixed_pcm_chunks = {18, 14, 10},
+};
+
+const struct snd_motu_spec snd_motu_spec_traveler_mk3 = {
+ .name = "TravelerMk3",
+ .protocol_version = SND_MOTU_PROTOCOL_V3,
+ .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_TX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_COMMAND_DSP,
+ .tx_fixed_pcm_chunks = {18, 14, 10},
.rx_fixed_pcm_chunks = {14, 14, 10},
};
@@ -304,7 +311,8 @@ const struct snd_motu_spec snd_motu_spec_ultralite_mk3 = {
.name = "UltraLiteMk3",
.protocol_version = SND_MOTU_PROTOCOL_V3,
.flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
- SND_MOTU_SPEC_TX_MIDI_3RD_Q,
+ SND_MOTU_SPEC_TX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_COMMAND_DSP,
.tx_fixed_pcm_chunks = {18, 14, 10},
.rx_fixed_pcm_chunks = {14, 14, 14},
};
@@ -313,14 +321,26 @@ const struct snd_motu_spec snd_motu_spec_audio_express = {
.name = "AudioExpress",
.protocol_version = SND_MOTU_PROTOCOL_V3,
.flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q |
- SND_MOTU_SPEC_TX_MIDI_3RD_Q,
+ SND_MOTU_SPEC_TX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_REGISTER_DSP,
.tx_fixed_pcm_chunks = {10, 10, 0},
.rx_fixed_pcm_chunks = {10, 10, 0},
};
+const struct snd_motu_spec snd_motu_spec_track16 = {
+ .name = "Track16",
+ .protocol_version = SND_MOTU_PROTOCOL_V3,
+ .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_TX_MIDI_3RD_Q |
+ SND_MOTU_SPEC_COMMAND_DSP,
+ .tx_fixed_pcm_chunks = {14, 14, 14},
+ .rx_fixed_pcm_chunks = {6, 6, 6},
+};
+
const struct snd_motu_spec snd_motu_spec_4pre = {
.name = "4pre",
.protocol_version = SND_MOTU_PROTOCOL_V3,
+ .flags = SND_MOTU_SPEC_REGISTER_DSP,
.tx_fixed_pcm_chunks = {10, 10, 0},
.rx_fixed_pcm_chunks = {10, 10, 0},
};
diff --git a/sound/firewire/motu/motu-register-dsp-message-parser.c b/sound/firewire/motu/motu-register-dsp-message-parser.c
new file mode 100644
index 000000000000..ef3b0b0f0dab
--- /dev/null
+++ b/sound/firewire/motu/motu-register-dsp-message-parser.c
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// motu-register-dsp-message-parser.c - a part of driver for MOTU FireWire series
+//
+// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+
+// Below models allow software to configure their DSP functions by asynchronous transaction
+// to access their internal registers.
+// * 828 mk2
+// * 896hd
+// * Traveler
+// * 8 pre
+// * Ultralite
+// * 4 pre
+// * Audio Express
+//
+// Additionally, isochronous packets from the above models include messages to notify state of
+// DSP. The messages are two set of 3 byte data in 2nd and 3rd quadlet of data block. When user
+// operates hardware components such as dial and switch, corresponding messages are transferred.
+// The messages include Hardware metering and MIDI messages as well.
+
+#include "motu.h"
+
+#define MSG_FLAG_POS 4
+#define MSG_FLAG_TYPE_MASK 0xf8
+#define MSG_FLAG_MIDI_MASK 0x01
+#define MSG_FLAG_MODEL_SPECIFIC_MASK 0x06
+#define MSG_FLAG_8PRE 0x00
+#define MSG_FLAG_ULTRALITE 0x04
+#define MSG_FLAG_TRAVELER 0x04
+#define MSG_FLAG_828MK2 0x04
+#define MSG_FLAG_896HD 0x04
+#define MSG_FLAG_4PRE 0x05 // MIDI mask is in 8th byte.
+#define MSG_FLAG_AUDIOEXPRESS 0x05 // MIDI mask is in 8th byte.
+#define MSG_FLAG_TYPE_SHIFT 3
+#define MSG_VALUE_POS 5
+#define MSG_MIDI_BYTE_POS 6
+#define MSG_METER_IDX_POS 7
+
+// In 4 pre and Audio express, meter index is in 6th byte. MIDI flag is in 8th byte and MIDI byte
+// is in 7th byte.
+#define MSG_METER_IDX_POS_4PRE_AE 6
+#define MSG_MIDI_BYTE_POS_4PRE_AE 7
+#define MSG_FLAG_MIDI_POS_4PRE_AE 8
+
+enum register_dsp_msg_type {
+ // Used for messages with no information.
+ INVALID = 0x00,
+ MIXER_SELECT = 0x01,
+ MIXER_SRC_GAIN = 0x02,
+ MIXER_SRC_PAN = 0x03,
+ MIXER_SRC_FLAG = 0x04,
+ MIXER_OUTPUT_PAIRED_VOLUME = 0x05,
+ MIXER_OUTPUT_PAIRED_FLAG = 0x06,
+ MAIN_OUTPUT_PAIRED_VOLUME = 0x07,
+ HP_OUTPUT_PAIRED_VOLUME = 0x08,
+ HP_OUTPUT_PAIRED_ASSIGNMENT = 0x09,
+ // Transferred by all models but the purpose is still unknown.
+ UNKNOWN_0 = 0x0a,
+ // Specific to 828mk2, 896hd, Traveler.
+ UNKNOWN_2 = 0x0c,
+ // Specific to 828mk2, Traveler, and 896hd (not functional).
+ LINE_INPUT_BOOST = 0x0d,
+ // Specific to 828mk2, Traveler, and 896hd (not functional).
+ LINE_INPUT_NOMINAL_LEVEL = 0x0e,
+ // Specific to Ultralite, 4 pre, Audio express, and 8 pre (not functional).
+ INPUT_GAIN_AND_INVERT = 0x15,
+ // Specific to 4 pre, and Audio express.
+ INPUT_FLAG = 0x16,
+ // Specific to 4 pre, and Audio express.
+ MIXER_SRC_PAIRED_BALANCE = 0x17,
+ // Specific to 4 pre, and Audio express.
+ MIXER_SRC_PAIRED_WIDTH = 0x18,
+ // Transferred by all models. This type of message interposes the series of the other
+ // messages. The message delivers signal level up to 96.0 kHz. In 828mk2, 896hd, and
+ // Traveler, one of physical outputs is selected for the message. The selection is done
+ // by LSB one byte in asynchronous write quadlet transaction to 0x'ffff'f000'0b2c.
+ METER = 0x1f,
+};
+
+#define EVENT_QUEUE_SIZE 16
+
+struct msg_parser {
+ spinlock_t lock;
+ struct snd_firewire_motu_register_dsp_meter meter;
+ bool meter_pos_quirk;
+
+ struct snd_firewire_motu_register_dsp_parameter param;
+ u8 prev_mixer_src_type;
+ u8 mixer_ch;
+ u8 mixer_src_ch;
+
+ u8 input_ch;
+ u8 prev_msg_type;
+
+ u32 event_queue[EVENT_QUEUE_SIZE];
+ unsigned int push_pos;
+ unsigned int pull_pos;
+};
+
+int snd_motu_register_dsp_message_parser_new(struct snd_motu *motu)
+{
+ struct msg_parser *parser;
+ parser = devm_kzalloc(&motu->card->card_dev, sizeof(*parser), GFP_KERNEL);
+ if (!parser)
+ return -ENOMEM;
+ spin_lock_init(&parser->lock);
+ if (motu->spec == &snd_motu_spec_4pre || motu->spec == &snd_motu_spec_audio_express)
+ parser->meter_pos_quirk = true;
+ motu->message_parser = parser;
+ return 0;
+}
+
+int snd_motu_register_dsp_message_parser_init(struct snd_motu *motu)
+{
+ struct msg_parser *parser = motu->message_parser;
+
+ parser->prev_mixer_src_type = INVALID;
+ parser->mixer_ch = 0xff;
+ parser->mixer_src_ch = 0xff;
+ parser->prev_msg_type = INVALID;
+
+ return 0;
+}
+
+// Rough implementaion of queue without overrun check.
+static void queue_event(struct snd_motu *motu, u8 msg_type, u8 identifier0, u8 identifier1, u8 val)
+{
+ struct msg_parser *parser = motu->message_parser;
+ unsigned int pos = parser->push_pos;
+ u32 entry;
+
+ if (!motu->hwdep || motu->hwdep->used == 0)
+ return;
+
+ entry = (msg_type << 24) | (identifier0 << 16) | (identifier1 << 8) | val;
+ parser->event_queue[pos] = entry;
+
+ ++pos;
+ if (pos >= EVENT_QUEUE_SIZE)
+ pos = 0;
+ parser->push_pos = pos;
+}
+
+void snd_motu_register_dsp_message_parser_parse(const struct amdtp_stream *s,
+ const struct pkt_desc *desc, unsigned int count)
+{
+ struct snd_motu *motu = container_of(s, struct snd_motu, tx_stream);
+ unsigned int data_block_quadlets = s->data_block_quadlets;
+ struct msg_parser *parser = motu->message_parser;
+ bool meter_pos_quirk = parser->meter_pos_quirk;
+ unsigned int pos = parser->push_pos;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&parser->lock, flags);
+
+ for (i = 0; i < count; ++i) {
+ __be32 *buffer = desc->ctx_payload;
+ unsigned int data_blocks = desc->data_blocks;
+ int j;
+
+ desc = amdtp_stream_next_packet_desc(s, desc);
+
+ for (j = 0; j < data_blocks; ++j) {
+ u8 *b = (u8 *)buffer;
+ u8 msg_type = (b[MSG_FLAG_POS] & MSG_FLAG_TYPE_MASK) >> MSG_FLAG_TYPE_SHIFT;
+ u8 val = b[MSG_VALUE_POS];
+
+ buffer += data_block_quadlets;
+
+ switch (msg_type) {
+ case MIXER_SELECT:
+ {
+ u8 mixer_ch = val / 0x20;
+ if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT) {
+ parser->mixer_src_ch = 0;
+ parser->mixer_ch = mixer_ch;
+ }
+ break;
+ }
+ case MIXER_SRC_GAIN:
+ case MIXER_SRC_PAN:
+ case MIXER_SRC_FLAG:
+ case MIXER_SRC_PAIRED_BALANCE:
+ case MIXER_SRC_PAIRED_WIDTH:
+ {
+ struct snd_firewire_motu_register_dsp_parameter *param = &parser->param;
+ u8 mixer_ch = parser->mixer_ch;
+ u8 mixer_src_ch = parser->mixer_src_ch;
+
+ if (msg_type != parser->prev_mixer_src_type)
+ mixer_src_ch = 0;
+ else
+ ++mixer_src_ch;
+ parser->prev_mixer_src_type = msg_type;
+
+ if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT &&
+ mixer_src_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_SRC_COUNT) {
+ u8 mixer_ch = parser->mixer_ch;
+
+ switch (msg_type) {
+ case MIXER_SRC_GAIN:
+ if (param->mixer.source[mixer_ch].gain[mixer_src_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
+ param->mixer.source[mixer_ch].gain[mixer_src_ch] = val;
+ }
+ break;
+ case MIXER_SRC_PAN:
+ if (param->mixer.source[mixer_ch].pan[mixer_src_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
+ param->mixer.source[mixer_ch].pan[mixer_src_ch] = val;
+ }
+ break;
+ case MIXER_SRC_FLAG:
+ if (param->mixer.source[mixer_ch].flag[mixer_src_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
+ param->mixer.source[mixer_ch].flag[mixer_src_ch] = val;
+ }
+ break;
+ case MIXER_SRC_PAIRED_BALANCE:
+ if (param->mixer.source[mixer_ch].paired_balance[mixer_src_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
+ param->mixer.source[mixer_ch].paired_balance[mixer_src_ch] = val;
+ }
+ break;
+ case MIXER_SRC_PAIRED_WIDTH:
+ if (param->mixer.source[mixer_ch].paired_width[mixer_src_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, mixer_src_ch, val);
+ param->mixer.source[mixer_ch].paired_width[mixer_src_ch] = val;
+ }
+ break;
+ default:
+ break;
+ }
+
+ parser->mixer_src_ch = mixer_src_ch;
+ }
+ break;
+ }
+ case MIXER_OUTPUT_PAIRED_VOLUME:
+ case MIXER_OUTPUT_PAIRED_FLAG:
+ {
+ struct snd_firewire_motu_register_dsp_parameter *param = &parser->param;
+ u8 mixer_ch = parser->mixer_ch;
+
+ if (mixer_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_MIXER_COUNT) {
+ switch (msg_type) {
+ case MIXER_OUTPUT_PAIRED_VOLUME:
+ if (param->mixer.output.paired_volume[mixer_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, 0, val);
+ param->mixer.output.paired_volume[mixer_ch] = val;
+ }
+ break;
+ case MIXER_OUTPUT_PAIRED_FLAG:
+ if (param->mixer.output.paired_flag[mixer_ch] != val) {
+ queue_event(motu, msg_type, mixer_ch, 0, val);
+ param->mixer.output.paired_flag[mixer_ch] = val;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ }
+ case MAIN_OUTPUT_PAIRED_VOLUME:
+ if (parser->param.output.main_paired_volume != val) {
+ queue_event(motu, msg_type, 0, 0, val);
+ parser->param.output.main_paired_volume = val;
+ }
+ break;
+ case HP_OUTPUT_PAIRED_VOLUME:
+ if (parser->param.output.hp_paired_volume != val) {
+ queue_event(motu, msg_type, 0, 0, val);
+ parser->param.output.hp_paired_volume = val;
+ }
+ break;
+ case HP_OUTPUT_PAIRED_ASSIGNMENT:
+ if (parser->param.output.hp_paired_assignment != val) {
+ queue_event(motu, msg_type, 0, 0, val);
+ parser->param.output.hp_paired_assignment = val;
+ }
+ break;
+ case LINE_INPUT_BOOST:
+ if (parser->param.line_input.boost_flag != val) {
+ queue_event(motu, msg_type, 0, 0, val);
+ parser->param.line_input.boost_flag = val;
+ }
+ break;
+ case LINE_INPUT_NOMINAL_LEVEL:
+ if (parser->param.line_input.nominal_level_flag != val) {
+ queue_event(motu, msg_type, 0, 0, val);
+ parser->param.line_input.nominal_level_flag = val;
+ }
+ break;
+ case INPUT_GAIN_AND_INVERT:
+ case INPUT_FLAG:
+ {
+ struct snd_firewire_motu_register_dsp_parameter *param = &parser->param;
+ u8 input_ch = parser->input_ch;
+
+ if (parser->prev_msg_type != msg_type)
+ input_ch = 0;
+ else
+ ++input_ch;
+
+ if (input_ch < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_INPUT_COUNT) {
+ switch (msg_type) {
+ case INPUT_GAIN_AND_INVERT:
+ if (param->input.gain_and_invert[input_ch] != val) {
+ queue_event(motu, msg_type, input_ch, 0, val);
+ param->input.gain_and_invert[input_ch] = val;
+ }
+ break;
+ case INPUT_FLAG:
+ if (param->input.flag[input_ch] != val) {
+ queue_event(motu, msg_type, input_ch, 0, val);
+ param->input.flag[input_ch] = val;
+ }
+ break;
+ default:
+ break;
+ }
+ parser->input_ch = input_ch;
+ }
+ break;
+ }
+ case UNKNOWN_0:
+ case UNKNOWN_2:
+ break;
+ case METER:
+ {
+ u8 pos;
+
+ if (!meter_pos_quirk)
+ pos = b[MSG_METER_IDX_POS];
+ else
+ pos = b[MSG_METER_IDX_POS_4PRE_AE];
+
+ if (pos < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT) {
+ parser->meter.data[pos] = val;
+ } else if (pos >= 0x80) {
+ pos -= (0x80 - SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_INPUT_COUNT);
+
+ if (pos < SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT)
+ parser->meter.data[pos] = val;
+ }
+
+ // The message for meter is interruptible to the series of other
+ // types of messages. Don't cache it.
+ fallthrough;
+ }
+ case INVALID:
+ default:
+ // Don't cache it.
+ continue;
+ }
+
+ parser->prev_msg_type = msg_type;
+ }
+ }
+
+ if (pos != parser->push_pos)
+ wake_up(&motu->hwdep_wait);
+
+ spin_unlock_irqrestore(&parser->lock, flags);
+}
+
+void snd_motu_register_dsp_message_parser_copy_meter(struct snd_motu *motu,
+ struct snd_firewire_motu_register_dsp_meter *meter)
+{
+ struct msg_parser *parser = motu->message_parser;
+ unsigned long flags;
+
+ spin_lock_irqsave(&parser->lock, flags);
+ memcpy(meter, &parser->meter, sizeof(*meter));
+ spin_unlock_irqrestore(&parser->lock, flags);
+}
+
+void snd_motu_register_dsp_message_parser_copy_parameter(struct snd_motu *motu,
+ struct snd_firewire_motu_register_dsp_parameter *param)
+{
+ struct msg_parser *parser = motu->message_parser;
+ unsigned long flags;
+
+ spin_lock_irqsave(&parser->lock, flags);
+ memcpy(param, &parser->param, sizeof(*param));
+ spin_unlock_irqrestore(&parser->lock, flags);
+}
+
+unsigned int snd_motu_register_dsp_message_parser_count_event(struct snd_motu *motu)
+{
+ struct msg_parser *parser = motu->message_parser;
+
+ if (parser->pull_pos > parser->push_pos)
+ return EVENT_QUEUE_SIZE - parser->pull_pos + parser->push_pos;
+ else
+ return parser->push_pos - parser->pull_pos;
+}
+
+bool snd_motu_register_dsp_message_parser_copy_event(struct snd_motu *motu, u32 *event)
+{
+ struct msg_parser *parser = motu->message_parser;
+ unsigned int pos = parser->pull_pos;
+ unsigned long flags;
+
+ if (pos == parser->push_pos)
+ return false;
+
+ spin_lock_irqsave(&parser->lock, flags);
+
+ *event = parser->event_queue[pos];
+
+ ++pos;
+ if (pos >= EVENT_QUEUE_SIZE)
+ pos = 0;
+ parser->pull_pos = pos;
+
+ spin_unlock_irqrestore(&parser->lock, flags);
+
+ return true;
+}
diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c
index 2028c5419f6f..64aec9c3eefd 100644
--- a/sound/firewire/motu/motu-stream.c
+++ b/sound/firewire/motu/motu-stream.c
@@ -7,7 +7,7 @@
#include "motu.h"
-#define CALLBACK_TIMEOUT 200
+#define READY_TIMEOUT_MS 200
#define ISOC_COMM_CONTROL_OFFSET 0x0b00
#define ISOC_COMM_CONTROL_MASK 0xffff0000
@@ -153,6 +153,9 @@ int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate,
fw_iso_resources_free(&motu->tx_resources);
fw_iso_resources_free(&motu->rx_resources);
+ kfree(motu->cache.event_offsets);
+ motu->cache.event_offsets = NULL;
+
err = snd_motu_protocol_set_clock_rate(motu, rate);
if (err < 0) {
dev_err(&motu->unit->device,
@@ -181,6 +184,15 @@ int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate,
fw_iso_resources_free(&motu->rx_resources);
return err;
}
+
+ motu->cache.size = motu->tx_stream.syt_interval * frames_per_buffer;
+ motu->cache.event_offsets = kcalloc(motu->cache.size, sizeof(*motu->cache.event_offsets),
+ GFP_KERNEL);
+ if (!motu->cache.event_offsets) {
+ fw_iso_resources_free(&motu->tx_resources);
+ fw_iso_resources_free(&motu->rx_resources);
+ return -ENOMEM;
+ }
}
return 0;
@@ -243,6 +255,16 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu)
if (err < 0)
return err;
+ if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) {
+ err = snd_motu_register_dsp_message_parser_init(motu);
+ if (err < 0)
+ return err;
+ } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) {
+ err = snd_motu_command_dsp_message_parser_init(motu, motu->tx_stream.sfc);
+ if (err < 0)
+ return err;
+ }
+
err = begin_session(motu);
if (err < 0) {
dev_err(&motu->unit->device,
@@ -260,14 +282,19 @@ int snd_motu_stream_start_duplex(struct snd_motu *motu)
if (err < 0)
goto stop_streams;
- err = amdtp_domain_start(&motu->domain, 0);
+ motu->cache.tail = 0;
+ motu->cache.tx_cycle_count = UINT_MAX;
+ motu->cache.head = 0;
+ motu->cache.rx_cycle_count = UINT_MAX;
+
+ // NOTE: The device requires both of replay; the sequence of the number of data
+ // blocks per packet, and the sequence of source packet header per data block as
+ // presentation time.
+ err = amdtp_domain_start(&motu->domain, 0, true, false);
if (err < 0)
goto stop_streams;
- if (!amdtp_stream_wait_callback(&motu->tx_stream,
- CALLBACK_TIMEOUT) ||
- !amdtp_stream_wait_callback(&motu->rx_stream,
- CALLBACK_TIMEOUT)) {
+ if (!amdtp_domain_wait_ready(&motu->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto stop_streams;
}
@@ -296,6 +323,9 @@ void snd_motu_stream_stop_duplex(struct snd_motu *motu)
fw_iso_resources_free(&motu->tx_resources);
fw_iso_resources_free(&motu->rx_resources);
+
+ kfree(motu->cache.event_offsets);
+ motu->cache.event_offsets = NULL;
}
}
@@ -317,7 +347,7 @@ static int init_stream(struct snd_motu *motu, struct amdtp_stream *s)
if (err < 0)
return err;
- err = amdtp_motu_init(s, motu->unit, dir, motu->spec);
+ err = amdtp_motu_init(s, motu->unit, dir, motu->spec, &motu->cache);
if (err < 0)
fw_iso_resources_destroy(resources);
diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c
index a4929c1302dc..fd2a9dddbfa6 100644
--- a/sound/firewire/motu/motu.c
+++ b/sound/firewire/motu/motu.c
@@ -11,7 +11,7 @@
MODULE_DESCRIPTION("MOTU FireWire driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = {
/* mode 0 */
@@ -41,9 +41,9 @@ static void name_card(struct snd_motu *motu)
}
}
- strcpy(motu->card->driver, "FW-MOTU");
- strcpy(motu->card->shortname, motu->spec->name);
- strcpy(motu->card->mixername, motu->spec->name);
+ strscpy(motu->card->driver, "FW-MOTU");
+ strscpy(motu->card->shortname, motu->spec->name);
+ strscpy(motu->card->mixername, motu->spec->name);
snprintf(motu->card->longname, sizeof(motu->card->longname),
"MOTU %s (version:%06x), GUID %08x%08x at %s, S%d",
motu->spec->name, version,
@@ -57,22 +57,31 @@ static void motu_card_free(struct snd_card *card)
snd_motu_transaction_unregister(motu);
snd_motu_stream_destroy_duplex(motu);
+
+ mutex_destroy(&motu->mutex);
+ fw_unit_put(motu->unit);
}
-static void do_registration(struct work_struct *work)
+static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work);
+ struct snd_card *card;
+ struct snd_motu *motu;
int err;
- if (motu->registered)
- return;
-
- err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0,
- &motu->card);
+ err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*motu), &card);
if (err < 0)
- return;
- motu->card->private_free = motu_card_free;
- motu->card->private_data = motu;
+ return err;
+ card->private_free = motu_card_free;
+
+ motu = card->private_data;
+ motu->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, motu);
+ motu->card = card;
+
+ motu->spec = (const struct snd_motu_spec *)entry->driver_data;
+ mutex_init(&motu->mutex);
+ spin_lock_init(&motu->lock);
+ init_waitqueue_head(&motu->hwdep_wait);
name_card(motu);
@@ -103,71 +112,38 @@ static void do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(motu->card);
+ if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) {
+ err = snd_motu_register_dsp_message_parser_new(motu);
+ if (err < 0)
+ goto error;
+ } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) {
+ err = snd_motu_command_dsp_message_parser_new(motu);
+ if (err < 0)
+ goto error;
+ }
+
+ err = snd_card_register(card);
if (err < 0)
goto error;
- motu->registered = true;
-
- return;
-error:
- snd_card_free(motu->card);
- dev_info(&motu->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int motu_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *entry)
-{
- struct snd_motu *motu;
-
- /* Allocate this independently of sound card instance. */
- motu = devm_kzalloc(&unit->device, sizeof(struct snd_motu), GFP_KERNEL);
- if (!motu)
- return -ENOMEM;
- motu->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, motu);
-
- motu->spec = (const struct snd_motu_spec *)entry->driver_data;
- mutex_init(&motu->mutex);
- spin_lock_init(&motu->lock);
- init_waitqueue_head(&motu->hwdep_wait);
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&motu->dwork, do_registration);
- snd_fw_schedule_registration(unit, &motu->dwork);
-
return 0;
+error:
+ snd_card_free(card);
+ return err;
}
static void motu_remove(struct fw_unit *unit)
{
struct snd_motu *motu = dev_get_drvdata(&unit->device);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&motu->dwork);
-
- if (motu->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(motu->card);
- }
-
- mutex_destroy(&motu->mutex);
- fw_unit_put(motu->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(motu->card);
}
static void motu_bus_update(struct fw_unit *unit)
{
struct snd_motu *motu = dev_get_drvdata(&unit->device);
- /* Postpone a workqueue for deferred registration. */
- if (!motu->registered)
- snd_fw_schedule_registration(unit, &motu->dwork);
-
/* The handler address register becomes initialized. */
snd_motu_transaction_reregister(motu);
}
@@ -184,14 +160,22 @@ static void motu_bus_update(struct fw_unit *unit)
}
static const struct ieee1394_device_id motu_id_table[] = {
+ SND_MOTU_DEV_ENTRY(0x000001, &snd_motu_spec_828),
+ SND_MOTU_DEV_ENTRY(0x000002, &snd_motu_spec_896),
SND_MOTU_DEV_ENTRY(0x000003, &snd_motu_spec_828mk2),
+ SND_MOTU_DEV_ENTRY(0x000005, &snd_motu_spec_896hd),
SND_MOTU_DEV_ENTRY(0x000009, &snd_motu_spec_traveler),
SND_MOTU_DEV_ENTRY(0x00000d, &snd_motu_spec_ultralite),
SND_MOTU_DEV_ENTRY(0x00000f, &snd_motu_spec_8pre),
- SND_MOTU_DEV_ENTRY(0x000015, &snd_motu_spec_828mk3), // FireWire only.
+ SND_MOTU_DEV_ENTRY(0x000015, &snd_motu_spec_828mk3_fw), // FireWire only.
+ SND_MOTU_DEV_ENTRY(0x000017, &snd_motu_spec_896mk3), // FireWire only.
SND_MOTU_DEV_ENTRY(0x000019, &snd_motu_spec_ultralite_mk3), // FireWire only.
- SND_MOTU_DEV_ENTRY(0x000035, &snd_motu_spec_828mk3), // Hybrid.
+ SND_MOTU_DEV_ENTRY(0x00001b, &snd_motu_spec_traveler_mk3),
+ SND_MOTU_DEV_ENTRY(0x000030, &snd_motu_spec_ultralite_mk3), // Hybrid.
+ SND_MOTU_DEV_ENTRY(0x000035, &snd_motu_spec_828mk3_hybrid), // Hybrid.
+ SND_MOTU_DEV_ENTRY(0x000037, &snd_motu_spec_896mk3), // Hybrid.
SND_MOTU_DEV_ENTRY(0x000033, &snd_motu_spec_audio_express),
+ SND_MOTU_DEV_ENTRY(0x000039, &snd_motu_spec_track16),
SND_MOTU_DEV_ENTRY(0x000045, &snd_motu_spec_4pre),
{ }
};
diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h
index 3d0236ee6716..c66be0a89ccf 100644
--- a/sound/firewire/motu/motu.h
+++ b/sound/firewire/motu/motu.h
@@ -39,15 +39,21 @@ struct snd_motu_packet_format {
unsigned char pcm_chunks[3];
};
+struct amdtp_motu_cache {
+ unsigned int *event_offsets;
+ unsigned int size;
+ unsigned int tail;
+ unsigned int tx_cycle_count;
+ unsigned int head;
+ unsigned int rx_cycle_count;
+};
+
struct snd_motu {
struct snd_card *card;
struct fw_unit *unit;
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
-
/* Model dependent information. */
const struct snd_motu_spec *spec;
@@ -68,8 +74,13 @@ struct snd_motu {
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
+ struct snd_hwdep *hwdep;
struct amdtp_domain domain;
+
+ struct amdtp_motu_cache cache;
+
+ void *message_parser;
};
enum snd_motu_spec_flags {
@@ -77,6 +88,8 @@ enum snd_motu_spec_flags {
SND_MOTU_SPEC_RX_MIDI_3RD_Q = 0x0002,
SND_MOTU_SPEC_TX_MIDI_2ND_Q = 0x0004,
SND_MOTU_SPEC_TX_MIDI_3RD_Q = 0x0008,
+ SND_MOTU_SPEC_REGISTER_DSP = 0x0010,
+ SND_MOTU_SPEC_COMMAND_DSP = 0x0020,
};
#define SND_MOTU_CLOCK_RATE_COUNT 6
@@ -99,6 +112,7 @@ enum snd_motu_clock_source {
};
enum snd_motu_protocol_version {
+ SND_MOTU_PROTOCOL_V1,
SND_MOTU_PROTOCOL_V2,
SND_MOTU_PROTOCOL_V3,
};
@@ -106,25 +120,35 @@ enum snd_motu_protocol_version {
struct snd_motu_spec {
const char *const name;
enum snd_motu_protocol_version protocol_version;
- enum snd_motu_spec_flags flags;
+ // The combination of snd_motu_spec_flags enumeration-constants.
+ unsigned int flags;
unsigned char tx_fixed_pcm_chunks[3];
unsigned char rx_fixed_pcm_chunks[3];
};
+extern const struct snd_motu_spec snd_motu_spec_828;
+extern const struct snd_motu_spec snd_motu_spec_896;
+
extern const struct snd_motu_spec snd_motu_spec_828mk2;
+extern const struct snd_motu_spec snd_motu_spec_896hd;
extern const struct snd_motu_spec snd_motu_spec_traveler;
extern const struct snd_motu_spec snd_motu_spec_ultralite;
extern const struct snd_motu_spec snd_motu_spec_8pre;
-extern const struct snd_motu_spec snd_motu_spec_828mk3;
+extern const struct snd_motu_spec snd_motu_spec_828mk3_fw;
+extern const struct snd_motu_spec snd_motu_spec_828mk3_hybrid;
+extern const struct snd_motu_spec snd_motu_spec_896mk3;
+extern const struct snd_motu_spec snd_motu_spec_traveler_mk3;
extern const struct snd_motu_spec snd_motu_spec_ultralite_mk3;
extern const struct snd_motu_spec snd_motu_spec_audio_express;
+extern const struct snd_motu_spec snd_motu_spec_track16;
extern const struct snd_motu_spec snd_motu_spec_4pre;
int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir,
- const struct snd_motu_spec *spec);
+ const struct snd_motu_spec *spec,
+ struct amdtp_motu_cache *cache);
int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int midi_ports,
struct snd_motu_packet_format *formats);
@@ -160,6 +184,16 @@ int snd_motu_create_midi_devices(struct snd_motu *motu);
int snd_motu_create_hwdep_device(struct snd_motu *motu);
+int snd_motu_protocol_v1_get_clock_rate(struct snd_motu *motu,
+ unsigned int *rate);
+int snd_motu_protocol_v1_set_clock_rate(struct snd_motu *motu,
+ unsigned int rate);
+int snd_motu_protocol_v1_get_clock_source(struct snd_motu *motu,
+ enum snd_motu_clock_source *src);
+int snd_motu_protocol_v1_switch_fetching_mode(struct snd_motu *motu,
+ bool enable);
+int snd_motu_protocol_v1_cache_packet_formats(struct snd_motu *motu);
+
int snd_motu_protocol_v2_get_clock_rate(struct snd_motu *motu,
unsigned int *rate);
int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu,
@@ -187,6 +221,8 @@ static inline int snd_motu_protocol_get_clock_rate(struct snd_motu *motu,
return snd_motu_protocol_v2_get_clock_rate(motu, rate);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_get_clock_rate(motu, rate);
+ else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
+ return snd_motu_protocol_v1_get_clock_rate(motu, rate);
else
return -ENXIO;
}
@@ -198,6 +234,8 @@ static inline int snd_motu_protocol_set_clock_rate(struct snd_motu *motu,
return snd_motu_protocol_v2_set_clock_rate(motu, rate);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_set_clock_rate(motu, rate);
+ else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
+ return snd_motu_protocol_v1_set_clock_rate(motu, rate);
else
return -ENXIO;
}
@@ -209,6 +247,8 @@ static inline int snd_motu_protocol_get_clock_source(struct snd_motu *motu,
return snd_motu_protocol_v2_get_clock_source(motu, source);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_get_clock_source(motu, source);
+ else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
+ return snd_motu_protocol_v1_get_clock_source(motu, source);
else
return -ENXIO;
}
@@ -220,6 +260,8 @@ static inline int snd_motu_protocol_switch_fetching_mode(struct snd_motu *motu,
return snd_motu_protocol_v2_switch_fetching_mode(motu, enable);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_switch_fetching_mode(motu, enable);
+ else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
+ return snd_motu_protocol_v1_switch_fetching_mode(motu, enable);
else
return -ENXIO;
}
@@ -230,8 +272,28 @@ static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu)
return snd_motu_protocol_v2_cache_packet_formats(motu);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_cache_packet_formats(motu);
+ else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
+ return snd_motu_protocol_v1_cache_packet_formats(motu);
else
return -ENXIO;
}
+int snd_motu_register_dsp_message_parser_new(struct snd_motu *motu);
+int snd_motu_register_dsp_message_parser_init(struct snd_motu *motu);
+void snd_motu_register_dsp_message_parser_parse(const struct amdtp_stream *s,
+ const struct pkt_desc *descs, unsigned int count);
+void snd_motu_register_dsp_message_parser_copy_meter(struct snd_motu *motu,
+ struct snd_firewire_motu_register_dsp_meter *meter);
+void snd_motu_register_dsp_message_parser_copy_parameter(struct snd_motu *motu,
+ struct snd_firewire_motu_register_dsp_parameter *params);
+unsigned int snd_motu_register_dsp_message_parser_count_event(struct snd_motu *motu);
+bool snd_motu_register_dsp_message_parser_copy_event(struct snd_motu *motu, u32 *event);
+
+int snd_motu_command_dsp_message_parser_new(struct snd_motu *motu);
+int snd_motu_command_dsp_message_parser_init(struct snd_motu *motu, enum cip_sfc sfc);
+void snd_motu_command_dsp_message_parser_parse(const struct amdtp_stream *s,
+ const struct pkt_desc *descs, unsigned int count);
+void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu,
+ struct snd_firewire_motu_command_dsp_meter *meter);
+
#endif
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 669d1e8238df..9ac8893a926f 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-pcm.o oxfw-proc.o \
+snd-oxfw-y := oxfw-command.o oxfw-stream.o oxfw-pcm.o oxfw-proc.o \
oxfw-midi.o oxfw-hwdep.o oxfw-spkr.o oxfw-scs1x.o oxfw.o
obj-$(CONFIG_SND_OXFW) += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw-hwdep.c b/sound/firewire/oxfw/oxfw-hwdep.c
index eba33d050060..3be214d8a922 100644
--- a/sound/firewire/oxfw/oxfw-hwdep.c
+++ b/sound/firewire/oxfw/oxfw-hwdep.c
@@ -35,13 +35,11 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
}
memset(&event, 0, sizeof(event));
- if (oxfw->dev_lock_changed) {
- event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
- event.lock_status.status = (oxfw->dev_lock_count > 0);
- oxfw->dev_lock_changed = false;
+ event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+ event.lock_status.status = (oxfw->dev_lock_count > 0);
+ oxfw->dev_lock_changed = false;
- count = min_t(long, count, sizeof(event.lock_status));
- }
+ count = min_t(long, count, sizeof(event.lock_status));
spin_unlock_irq(&oxfw->lock);
@@ -79,7 +77,7 @@ static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -179,7 +177,7 @@ int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw)
err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep);
if (err < 0)
goto end;
- strcpy(hwdep->name, oxfw->card->driver);
+ strscpy(hwdep->name, oxfw->card->driver);
hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW;
hwdep->ops = hwdep_ops;
hwdep->private_data = oxfw;
diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c
index 775cba3f1f02..c215fa6f7a03 100644
--- a/sound/firewire/oxfw/oxfw-midi.c
+++ b/sound/firewire/oxfw/oxfw-midi.c
@@ -129,9 +129,9 @@ static void set_midi_substream_names(struct snd_oxfw *oxfw,
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
- snprintf(subs->name, sizeof(subs->name),
- "%s MIDI %d",
- oxfw->card->shortname, subs->number + 1);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ oxfw->card->shortname, subs->number + 1);
}
}
diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c
index 2dfa7e179cb6..e13dc817fc28 100644
--- a/sound/firewire/oxfw/oxfw-pcm.c
+++ b/sound/firewire/oxfw/oxfw-pcm.c
@@ -239,7 +239,7 @@ static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_oxfw *oxfw = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int channels = params_channels(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
@@ -262,7 +262,7 @@ static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_oxfw *oxfw = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int channels = params_channels(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
@@ -286,7 +286,7 @@ static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&oxfw->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--oxfw->substreams_count;
snd_oxfw_stream_stop_duplex(oxfw);
@@ -301,7 +301,7 @@ static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&oxfw->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--oxfw->substreams_count;
snd_oxfw_stream_stop_duplex(oxfw);
@@ -440,7 +440,8 @@ int snd_oxfw_create_pcm(struct snd_oxfw *oxfw)
return err;
pcm->private_data = oxfw;
- strcpy(pcm->name, oxfw->card->shortname);
+ pcm->nonatomic = true;
+ strscpy(pcm->name, oxfw->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
if (cap > 0)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c
index 80c9dc13f1b5..00f7feb91f92 100644
--- a/sound/firewire/oxfw/oxfw-stream.c
+++ b/sound/firewire/oxfw/oxfw-stream.c
@@ -9,7 +9,7 @@
#include <linux/delay.h>
#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512
-#define CALLBACK_TIMEOUT 200
+#define READY_TIMEOUT_MS 600
/*
* According to datasheet of Oxford Semiconductor:
@@ -153,12 +153,32 @@ static int init_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
struct cmp_connection *conn;
enum cmp_direction c_dir;
enum amdtp_stream_direction s_dir;
+ unsigned int flags = 0;
int err;
+ if (!(oxfw->quirks & SND_OXFW_QUIRK_BLOCKING_TRANSMISSION))
+ flags |= CIP_NONBLOCKING;
+ else
+ flags |= CIP_BLOCKING;
+
+ // OXFW 970/971 has no function to generate playback timing according to the sequence
+ // of value in syt field, thus the packet should include NO_INFO value in the field.
+ // However, some models just ignore data blocks in packet with NO_INFO for audio data
+ // processing.
+ if (!(oxfw->quirks & SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET))
+ flags |= CIP_UNAWARE_SYT;
+
if (stream == &oxfw->tx_stream) {
conn = &oxfw->out_conn;
c_dir = CMP_OUTPUT;
s_dir = AMDTP_IN_STREAM;
+
+ if (oxfw->quirks & SND_OXFW_QUIRK_JUMBO_PAYLOAD)
+ flags |= CIP_JUMBO_PAYLOAD;
+ if (oxfw->quirks & SND_OXFW_QUIRK_WRONG_DBS)
+ flags |= CIP_WRONG_DBS;
+ if (oxfw->quirks & SND_OXFW_QUIRK_DBC_IS_TOTAL_PAYLOAD_QUADLETS)
+ flags |= CIP_DBC_IS_END_EVENT | CIP_DBC_IS_PAYLOAD_QUADLETS;
} else {
conn = &oxfw->in_conn;
c_dir = CMP_INPUT;
@@ -169,24 +189,12 @@ static int init_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
if (err < 0)
return err;
- err = amdtp_am824_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING);
+ err = amdtp_am824_init(stream, oxfw->unit, s_dir, flags);
if (err < 0) {
cmp_connection_destroy(conn);
return err;
}
- /*
- * OXFW starts to transmit packets with non-zero dbc.
- * OXFW postpone transferring packets till handling any asynchronous
- * packets. As a result, next isochronous packet includes more data
- * blocks than IEC 61883-6 defines.
- */
- if (stream == &oxfw->tx_stream) {
- oxfw->tx_stream.flags |= CIP_JUMBO_PAYLOAD;
- if (oxfw->wrong_dbs)
- oxfw->tx_stream.flags |= CIP_WRONG_DBS;
- }
-
return 0;
}
@@ -338,6 +346,9 @@ int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw)
}
if (!amdtp_stream_running(&oxfw->rx_stream)) {
+ unsigned int tx_init_skip_cycles = 0;
+ bool replay_seq = false;
+
err = start_stream(oxfw, &oxfw->rx_stream);
if (err < 0) {
dev_err(&oxfw->unit->device,
@@ -353,26 +364,32 @@ int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw)
"fail to prepare tx stream: %d\n", err);
goto error;
}
+
+ if (oxfw->quirks & SND_OXFW_QUIRK_JUMBO_PAYLOAD) {
+ // Just after changing sampling transfer frequency, many cycles are
+ // skipped for packet transmission.
+ tx_init_skip_cycles = 400;
+ } else if (oxfw->quirks & SND_OXFW_QUIRK_VOLUNTARY_RECOVERY) {
+ // It takes a bit time for target device to adjust event frequency
+ // according to nominal event frequency in isochronous packets from
+ // ALSA oxfw driver.
+ tx_init_skip_cycles = 4000;
+ } else {
+ replay_seq = true;
+ }
}
- err = amdtp_domain_start(&oxfw->domain, 0);
+ // NOTE: The device ignores presentation time expressed by the value of syt field
+ // of CIP header in received packets. The sequence of the number of data blocks per
+ // packet is important for media clock recovery.
+ err = amdtp_domain_start(&oxfw->domain, tx_init_skip_cycles, replay_seq, false);
if (err < 0)
goto error;
- // Wait first packet.
- if (!amdtp_stream_wait_callback(&oxfw->rx_stream,
- CALLBACK_TIMEOUT)) {
+ if (!amdtp_domain_wait_ready(&oxfw->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
-
- if (oxfw->has_output) {
- if (!amdtp_stream_wait_callback(&oxfw->tx_stream,
- CALLBACK_TIMEOUT)) {
- err = -ETIMEDOUT;
- goto error;
- }
- }
}
return 0;
@@ -471,26 +488,57 @@ int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw,
enum avc_general_plug_dir dir,
struct snd_oxfw_stream_formation *formation)
{
- u8 *format;
- unsigned int len;
int err;
- len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
- format = kmalloc(len, GFP_KERNEL);
- if (format == NULL)
- return -ENOMEM;
+ if (!(oxfw->quirks & SND_OXFW_QUIRK_STREAM_FORMAT_INFO_UNSUPPORTED)) {
+ u8 *format;
+ unsigned int len;
- err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len);
- if (err < 0)
- goto end;
- if (len < 3) {
- err = -EIO;
- goto end;
+ len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+ format = kmalloc(len, GFP_KERNEL);
+ if (format == NULL)
+ return -ENOMEM;
+
+ err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len);
+ if (err >= 0) {
+ if (len < 3)
+ err = -EIO;
+ else
+ err = snd_oxfw_stream_parse_format(format, formation);
+ }
+
+ kfree(format);
+ } else {
+ // Miglia Harmony Audio does not support Extended Stream Format Information
+ // command. Use the duplicated hard-coded format, instead.
+ unsigned int rate;
+ u8 *const *formats;
+ int i;
+
+ err = avc_general_get_sig_fmt(oxfw->unit, &rate, dir, 0);
+ if (err < 0)
+ return err;
+
+ if (dir == AVC_GENERAL_PLUG_DIR_IN)
+ formats = oxfw->rx_stream_formats;
+ else
+ formats = oxfw->tx_stream_formats;
+
+ for (i = 0; (i < SND_OXFW_STREAM_FORMAT_ENTRIES); ++i) {
+ if (!formats[i])
+ continue;
+
+ err = snd_oxfw_stream_parse_format(formats[i], formation);
+ if (err < 0)
+ continue;
+
+ if (formation->rate == rate)
+ break;
+ }
+ if (i == SND_OXFW_STREAM_FORMAT_ENTRIES)
+ return -EIO;
}
- err = snd_oxfw_stream_parse_format(format, formation);
-end:
- kfree(format);
return err;
}
@@ -500,7 +548,7 @@ end:
* in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
* Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005
*/
-int snd_oxfw_stream_parse_format(u8 *format,
+int snd_oxfw_stream_parse_format(const u8 *format,
struct snd_oxfw_stream_formation *formation)
{
unsigned int i, e, channels, type;
@@ -585,14 +633,33 @@ assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir,
unsigned int i, eid;
int err;
- /* get format at current sampling rate */
- err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len);
- if (err < 0) {
- dev_err(&oxfw->unit->device,
- "fail to get current stream format for isoc %s plug %d:%d\n",
- (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out",
- pid, err);
- goto end;
+ // get format at current sampling rate.
+ if (!(oxfw->quirks & SND_OXFW_QUIRK_STREAM_FORMAT_INFO_UNSUPPORTED)) {
+ err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len);
+ if (err < 0) {
+ dev_err(&oxfw->unit->device,
+ "fail to get current stream format for isoc %s plug %d:%d\n",
+ (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out",
+ pid, err);
+ goto end;
+ }
+ } else {
+ // Miglia Harmony Audio does not support Extended Stream Format Information
+ // command. Use the hard-coded format, instead.
+ buf[0] = 0x90;
+ buf[1] = 0x40;
+ buf[2] = avc_stream_rate_table[0];
+ buf[3] = 0x00;
+ buf[4] = 0x01;
+
+ if (dir == AVC_GENERAL_PLUG_DIR_IN)
+ buf[5] = 0x08;
+ else
+ buf[5] = 0x02;
+
+ buf[6] = 0x06;
+
+ *len = 7;
}
/* parse and set stream format */
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 1f1e3236efb8..7a985f3cb8f6 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -21,15 +21,19 @@
#define VENDOR_TASCAM 0x00022e
#define OUI_STANTON 0x001260
#define OUI_APOGEE 0x0003db
+#define OUI_OXFORD 0x0030e0
#define MODEL_SATELLITE 0x00200f
+#define MODEL_SCS1M 0x001000
+#define MODEL_DUET_FW 0x01dddd
+#define MODEL_ONYX_1640I 0x001640
#define SPECIFIER_1394TA 0x00a02d
#define VERSION_AVC 0x010001
MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
MODULE_ALIAS("snd-firewire-speakers");
MODULE_ALIAS("snd-scs1x");
@@ -41,13 +45,11 @@ struct compat_info {
static bool detect_loud_models(struct fw_unit *unit)
{
- const char *const models[] = {
+ static const char *const models[] = {
"Onyxi",
"Onyx-i",
"Onyx 1640i",
"d.Pro",
- "Mackie Onyx Satellite",
- "Tapco LINK.firewire 4x6",
"U.420"};
char model[32];
int err;
@@ -60,7 +62,7 @@ static bool detect_loud_models(struct fw_unit *unit)
return match_string(models, ARRAY_SIZE(models), model) >= 0;
}
-static int name_card(struct snd_oxfw *oxfw)
+static int name_card(struct snd_oxfw *oxfw, const struct ieee1394_device_id *entry)
{
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
const struct compat_info *info;
@@ -88,10 +90,12 @@ static int name_card(struct snd_oxfw *oxfw)
goto end;
be32_to_cpus(&firmware);
+ if (firmware >> 20 == 0x970)
+ oxfw->quirks |= SND_OXFW_QUIRK_JUMBO_PAYLOAD;
+
/* to apply card definitions */
- if (oxfw->entry->vendor_id == VENDOR_GRIFFIN ||
- oxfw->entry->vendor_id == VENDOR_LACIE) {
- info = (const struct compat_info *)oxfw->entry->driver_data;
+ if (entry->vendor_id == VENDOR_GRIFFIN || entry->vendor_id == VENDOR_LACIE) {
+ info = (const struct compat_info *)entry->driver_data;
d = info->driver_name;
v = info->vendor_name;
m = info->model_name;
@@ -101,15 +105,15 @@ static int name_card(struct snd_oxfw *oxfw)
m = model;
}
- strcpy(oxfw->card->driver, d);
- strcpy(oxfw->card->mixername, m);
- strcpy(oxfw->card->shortname, m);
+ strscpy(oxfw->card->driver, d);
+ strscpy(oxfw->card->mixername, m);
+ strscpy(oxfw->card->shortname, m);
- snprintf(oxfw->card->longname, sizeof(oxfw->card->longname),
- "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
- v, m, firmware >> 20, firmware & 0xffff,
- fw_dev->config_rom[3], fw_dev->config_rom[4],
- dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed);
+ scnprintf(oxfw->card->longname, sizeof(oxfw->card->longname),
+ "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
+ v, m, firmware >> 20, firmware & 0xffff,
+ fw_dev->config_rom[3], fw_dev->config_rom[4],
+ dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed);
end:
return err;
}
@@ -120,9 +124,12 @@ static void oxfw_card_free(struct snd_card *card)
if (oxfw->has_output || oxfw->has_input)
snd_oxfw_stream_destroy_duplex(oxfw);
+
+ mutex_destroy(&oxfw->mutex);
+ fw_unit_put(oxfw->unit);
}
-static int detect_quirks(struct snd_oxfw *oxfw)
+static int detect_quirks(struct snd_oxfw *oxfw, const struct ieee1394_device_id *entry)
{
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
struct fw_csr_iterator it;
@@ -133,28 +140,37 @@ static int detect_quirks(struct snd_oxfw *oxfw)
* Add ALSA control elements for two models to keep compatibility to
* old firewire-speaker module.
*/
- if (oxfw->entry->vendor_id == VENDOR_GRIFFIN)
+ if (entry->vendor_id == VENDOR_GRIFFIN)
return snd_oxfw_add_spkr(oxfw, false);
- if (oxfw->entry->vendor_id == VENDOR_LACIE)
+ if (entry->vendor_id == VENDOR_LACIE)
return snd_oxfw_add_spkr(oxfw, true);
/*
* Stanton models supports asynchronous transactions for unique MIDI
* messages.
*/
- if (oxfw->entry->vendor_id == OUI_STANTON) {
- /* No physical MIDI ports. */
+ if (entry->vendor_id == OUI_STANTON) {
+ oxfw->quirks |= SND_OXFW_QUIRK_SCS_TRANSACTION;
+ if (entry->model_id == MODEL_SCS1M)
+ oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION;
+
+ // No physical MIDI ports.
oxfw->midi_input_ports = 0;
oxfw->midi_output_ports = 0;
return snd_oxfw_scs1x_add(oxfw);
}
+ if (entry->vendor_id == OUI_APOGEE && entry->model_id == MODEL_DUET_FW) {
+ oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION |
+ SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET;
+ }
+
/*
* TASCAM FireOne has physical control and requires a pair of additional
* MIDI ports.
*/
- if (oxfw->entry->vendor_id == VENDOR_TASCAM) {
+ if (entry->vendor_id == VENDOR_TASCAM) {
oxfw->midi_input_ports++;
oxfw->midi_output_ports++;
return 0;
@@ -170,40 +186,63 @@ static int detect_quirks(struct snd_oxfw *oxfw)
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;
+ if (vendor == VENDOR_LOUD) {
+ // Mackie Onyx Satellite with base station has a quirk to report a wrong
+ // value in 'dbs' field of CIP header against its format information.
+ oxfw->quirks |= SND_OXFW_QUIRK_WRONG_DBS;
+
+ // OXFW971-based models may transfer events by blocking method.
+ if (!(oxfw->quirks & SND_OXFW_QUIRK_JUMBO_PAYLOAD))
+ oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION;
+
+ if (model == MODEL_ONYX_1640I) {
+ //Unless receiving packets without NOINFO packet, the device transfers
+ //mostly half of events in packets than expected.
+ oxfw->quirks |= SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET |
+ SND_OXFW_QUIRK_VOLUNTARY_RECOVERY;
+ }
+ }
return 0;
}
-static void do_registration(struct work_struct *work)
+static int oxfw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
- struct snd_oxfw *oxfw = container_of(work, struct snd_oxfw, dwork.work);
+ struct snd_card *card;
+ struct snd_oxfw *oxfw;
int err;
- if (oxfw->registered)
- return;
+ if (entry->vendor_id == VENDOR_LOUD && entry->model_id == 0 && !detect_loud_models(unit))
+ return -ENODEV;
- err = snd_card_new(&oxfw->unit->device, -1, NULL, THIS_MODULE, 0,
- &oxfw->card);
+ err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*oxfw), &card);
if (err < 0)
- return;
- oxfw->card->private_free = oxfw_card_free;
- oxfw->card->private_data = oxfw;
+ return err;
+ card->private_free = oxfw_card_free;
+
+ oxfw = card->private_data;
+ oxfw->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, oxfw);
+ oxfw->card = card;
+
+ mutex_init(&oxfw->mutex);
+ spin_lock_init(&oxfw->lock);
+ init_waitqueue_head(&oxfw->hwdep_wait);
- err = name_card(oxfw);
+ err = name_card(oxfw, entry);
if (err < 0)
goto error;
+ if (entry->vendor_id == OUI_OXFORD && entry->model_id == 0x00f970) {
+ oxfw->quirks |= SND_OXFW_QUIRK_STREAM_FORMAT_INFO_UNSUPPORTED |
+ SND_OXFW_QUIRK_DBC_IS_TOTAL_PAYLOAD_QUADLETS;
+ }
+
err = snd_oxfw_stream_discover(oxfw);
if (err < 0)
goto error;
- err = detect_quirks(oxfw);
+ err = detect_quirks(oxfw, entry);
if (err < 0)
goto error;
@@ -227,85 +266,38 @@ static void do_registration(struct work_struct *work)
goto error;
}
- err = snd_card_register(oxfw->card);
+ err = snd_card_register(card);
if (err < 0)
goto error;
- oxfw->registered = true;
-
- return;
-error:
- snd_card_free(oxfw->card);
- dev_info(&oxfw->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int oxfw_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *entry)
-{
- struct snd_oxfw *oxfw;
-
- if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit))
- return -ENODEV;
-
- /* Allocate this independent of sound card instance. */
- oxfw = devm_kzalloc(&unit->device, sizeof(struct snd_oxfw), GFP_KERNEL);
- if (!oxfw)
- return -ENOMEM;
- oxfw->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, oxfw);
-
- oxfw->entry = entry;
- mutex_init(&oxfw->mutex);
- spin_lock_init(&oxfw->lock);
- init_waitqueue_head(&oxfw->hwdep_wait);
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&oxfw->dwork, do_registration);
- snd_fw_schedule_registration(unit, &oxfw->dwork);
-
return 0;
+error:
+ snd_card_free(card);
+ return err;
}
static void oxfw_bus_reset(struct fw_unit *unit)
{
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
- if (!oxfw->registered)
- snd_fw_schedule_registration(unit, &oxfw->dwork);
-
fcp_bus_reset(oxfw->unit);
- if (oxfw->registered) {
- if (oxfw->has_output || oxfw->has_input) {
- mutex_lock(&oxfw->mutex);
- snd_oxfw_stream_update_duplex(oxfw);
- mutex_unlock(&oxfw->mutex);
- }
-
- if (oxfw->entry->vendor_id == OUI_STANTON)
- snd_oxfw_scs1x_update(oxfw);
+ if (oxfw->has_output || oxfw->has_input) {
+ mutex_lock(&oxfw->mutex);
+ snd_oxfw_stream_update_duplex(oxfw);
+ mutex_unlock(&oxfw->mutex);
}
+
+ if (oxfw->quirks & SND_OXFW_QUIRK_SCS_TRANSACTION)
+ snd_oxfw_scs1x_update(oxfw);
}
static void oxfw_remove(struct fw_unit *unit)
{
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&oxfw->dwork);
-
- if (oxfw->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(oxfw->card);
- }
-
- mutex_destroy(&oxfw->mutex);
- fw_unit_put(oxfw->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(oxfw->card);
}
static const struct compat_info griffin_firewave = {
@@ -320,82 +312,69 @@ static const struct compat_info lacie_speakers = {
.model_name = "FireWire Speakers",
};
+#define OXFW_DEV_ENTRY(vendor, model, data) \
+{ \
+ .match_flags = IEEE1394_MATCH_VENDOR_ID | \
+ IEEE1394_MATCH_MODEL_ID | \
+ IEEE1394_MATCH_SPECIFIER_ID | \
+ IEEE1394_MATCH_VERSION, \
+ .vendor_id = vendor, \
+ .model_id = model, \
+ .specifier_id = SPECIFIER_1394TA, \
+ .version = VERSION_AVC, \
+ .driver_data = (kernel_ulong_t)data, \
+}
+
static const struct ieee1394_device_id oxfw_id_table[] = {
- {
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID |
- IEEE1394_MATCH_SPECIFIER_ID |
- IEEE1394_MATCH_VERSION,
- .vendor_id = VENDOR_GRIFFIN,
- .model_id = 0x00f970,
- .specifier_id = SPECIFIER_1394TA,
- .version = VERSION_AVC,
- .driver_data = (kernel_ulong_t)&griffin_firewave,
- },
- {
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID |
- IEEE1394_MATCH_SPECIFIER_ID |
- IEEE1394_MATCH_VERSION,
- .vendor_id = VENDOR_LACIE,
- .model_id = 0x00f970,
- .specifier_id = SPECIFIER_1394TA,
- .version = VERSION_AVC,
- .driver_data = (kernel_ulong_t)&lacie_speakers,
- },
- /* Behringer,F-Control Audio 202 */
- {
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID,
- .vendor_id = VENDOR_BEHRINGER,
- .model_id = 0x00fc22,
- },
- /*
- * Any Mackie(Loud) models (name string/model id):
- * Onyx-i series (former models): 0x081216
- * Mackie Onyx Satellite: 0x00200f
- * Tapco LINK.firewire 4x6: 0x000460
- * d.2 pro: Unknown
- * d.4 pro: Unknown
- * U.420: Unknown
- * U.420d: Unknown
- */
+ //
+ // OXFW970 devices:
+ // Initial firmware has a quirk to postpone isoc packet transmission during finishing async
+ // transaction. As a result, several isochronous cycles are skipped to transfer the packets
+ // and the audio data frames which should have been transferred during the cycles are put
+ // into packet at the first isoc cycle after the postpone. Furthermore, the value of SYT
+ // field in CIP header is not reliable as synchronization timing,
+ //
+ OXFW_DEV_ENTRY(VENDOR_GRIFFIN, 0x00f970, &griffin_firewave),
+ OXFW_DEV_ENTRY(VENDOR_LACIE, 0x00f970, &lacie_speakers),
+ // Miglia HarmonyAudio (HA02). The numeric vendor ID is ASIC vendor and the model ID is the
+ // default value of ASIC.
+ OXFW_DEV_ENTRY(OUI_OXFORD, 0x00f970, NULL),
+ // Behringer,F-Control Audio 202. The value of SYT field is not reliable at all.
+ OXFW_DEV_ENTRY(VENDOR_BEHRINGER, 0x00fc22, NULL),
+ // Loud Technologies, Tapco Link.FireWire 4x6. The value of SYT field is always 0xffff.
+ OXFW_DEV_ENTRY(VENDOR_LOUD, 0x000460, NULL),
+ // Loud Technologies, Mackie Onyx Satellite. Although revised version of firmware is
+ // installed to avoid the postpone, the value of SYT field is always 0xffff.
+ OXFW_DEV_ENTRY(VENDOR_LOUD, MODEL_SATELLITE, NULL),
+
+ //
+ // OXFW971 devices:
+ // The value of SYT field in CIP header is enough reliable. Both of blocking and non-blocking
+ // transmission methods are available.
+ //
+ // Any Mackie(Loud) models (name string/model id):
+ // Onyx-i series (former models): 0x081216
+ // Onyx 1640i: 0x001640
+ // d.2 pro/d.4 pro (built-in card): Unknown
+ // U.420: Unknown
+ // U.420d: Unknown
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION,
.vendor_id = VENDOR_LOUD,
+ .model_id = 0,
.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,
- },
- /* Stanton, Stanton Controllers & Systems 1 Mixer (SCS.1m) */
- {
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID,
- .vendor_id = OUI_STANTON,
- .model_id = 0x001000,
- },
- /* Stanton, Stanton Controllers & Systems 1 Deck (SCS.1d) */
- {
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID,
- .vendor_id = OUI_STANTON,
- .model_id = 0x002000,
- },
- // APOGEE, duet FireWire
- {
- .match_flags = IEEE1394_MATCH_VENDOR_ID |
- IEEE1394_MATCH_MODEL_ID,
- .vendor_id = OUI_APOGEE,
- .model_id = 0x01dddd,
- },
+ // TASCAM, FireOne.
+ OXFW_DEV_ENTRY(VENDOR_TASCAM, 0x800007, NULL),
+ // Stanton, Stanton Controllers & Systems 1 Mixer (SCS.1m).
+ OXFW_DEV_ENTRY(OUI_STANTON, MODEL_SCS1M, NULL),
+ // Stanton, Stanton Controllers & Systems 1 Deck (SCS.1d).
+ OXFW_DEV_ENTRY(OUI_STANTON, 0x002000, NULL),
+ // APOGEE, duet FireWire.
+ OXFW_DEV_ENTRY(OUI_APOGEE, MODEL_DUET_FW, NULL),
{ }
};
MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index fa2d7f9e2dc3..39ea9a6dde33 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -32,6 +32,33 @@
#include "../amdtp-am824.h"
#include "../cmp.h"
+enum snd_oxfw_quirk {
+ // Postpone transferring packets during handling asynchronous transaction. As a result,
+ // next isochronous packet includes more events than one packet can include.
+ SND_OXFW_QUIRK_JUMBO_PAYLOAD = 0x01,
+ // The dbs field of CIP header in tx packet is wrong.
+ SND_OXFW_QUIRK_WRONG_DBS = 0x02,
+ // Blocking transmission mode is used.
+ SND_OXFW_QUIRK_BLOCKING_TRANSMISSION = 0x04,
+ // Stanton SCS1.d and SCS1.m support unique transaction.
+ SND_OXFW_QUIRK_SCS_TRANSACTION = 0x08,
+ // Apogee Duet FireWire ignores data blocks in packet with NO_INFO for audio data
+ // processing, while output level meter moves. Any value in syt field of packet takes
+ // the device to process audio data even if the value is invalid in a point of
+ // IEC 61883-1/6.
+ SND_OXFW_QUIRK_IGNORE_NO_INFO_PACKET = 0x10,
+ // Loud Technologies Mackie Onyx 1640i seems to configure OXFW971 ASIC so that it decides
+ // event frequency according to events in received isochronous packets. The device looks to
+ // performs media clock recovery voluntarily. In the recovery, the packets with NO_INFO
+ // are ignored, thus driver should transfer packets with timestamp.
+ SND_OXFW_QUIRK_VOLUNTARY_RECOVERY = 0x20,
+ // Miglia Harmony Audio does not support AV/C Stream Format Information command.
+ SND_OXFW_QUIRK_STREAM_FORMAT_INFO_UNSUPPORTED = 0x40,
+ // Miglia Harmony Audio transmits CIP in which the value of dbc field expresses the number
+ // of accumulated payload quadlets including the packet.
+ SND_OXFW_QUIRK_DBC_IS_TOTAL_PAYLOAD_QUADLETS = 0x80,
+};
+
/* This is an arbitrary number for convinience. */
#define SND_OXFW_STREAM_FORMAT_ENTRIES 10
struct snd_oxfw {
@@ -40,10 +67,8 @@ struct snd_oxfw {
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
-
- bool wrong_dbs;
+ // The combination of snd_oxfw_quirk enumeration-constants.
+ unsigned int quirks;
bool has_output;
bool has_input;
u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES];
@@ -62,7 +87,6 @@ struct snd_oxfw {
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
- const struct ieee1394_device_id *entry;
void *spec;
struct amdtp_domain domain;
@@ -117,7 +141,7 @@ struct snd_oxfw_stream_formation {
unsigned int pcm;
unsigned int midi;
};
-int snd_oxfw_stream_parse_format(u8 *format,
+int snd_oxfw_stream_parse_format(const u8 *format,
struct snd_oxfw_stream_formation *formation);
int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw,
enum avc_general_plug_dir dir,
diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile
index a1d21f244d64..43fed14cf172 100644
--- a/sound/firewire/tascam/Makefile
+++ b/sound/firewire/tascam/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \
+snd-firewire-tascam-y := 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
index f823a2ab3544..079afa4bd381 100644
--- a/sound/firewire/tascam/amdtp-tascam.c
+++ b/sound/firewire/tascam/amdtp-tascam.c
@@ -29,7 +29,7 @@ int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate)
if (s->direction == AMDTP_IN_STREAM)
data_channels += 2;
- return amdtp_stream_set_parameters(s, rate, data_channels);
+ return amdtp_stream_set_parameters(s, rate, data_channels, 1);
}
static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
@@ -176,16 +176,13 @@ static void read_status_messages(struct amdtp_stream *s,
}
}
-static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_ir_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -195,21 +192,18 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
}
read_status_messages(s, buf, data_blocks);
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
-static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
- const struct pkt_desc *descs,
- unsigned int packets,
- struct snd_pcm_substream *pcm)
+static void process_it_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
+ unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
- for (i = 0; i < packets; ++i) {
- const struct pkt_desc *desc = descs + i;
+ for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
@@ -219,15 +213,16 @@ static unsigned int process_it_ctx_payloads(struct amdtp_stream *s,
} else {
write_pcm_silence(s, buf, data_blocks);
}
- }
- return pcm_frames;
+ desc = amdtp_stream_next_packet_desc(s, desc);
+ }
}
int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir, unsigned int pcm_channels)
{
amdtp_stream_process_ctx_payloads_t process_ctx_payloads;
+ unsigned int flags = CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK | CIP_UNAWARE_SYT;
struct amdtp_tscm *p;
unsigned int fmt;
int err;
@@ -240,17 +235,14 @@ int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit,
process_ctx_payloads = process_it_ctx_payloads;
}
- err = amdtp_stream_init(s, unit, dir,
- CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt,
+ err = amdtp_stream_init(s, unit, dir, flags, fmt,
process_ctx_payloads, sizeof(struct amdtp_tscm));
if (err < 0)
- return 0;
+ return err;
if (dir == AMDTP_OUT_STREAM) {
// Use fixed value for FDF field.
s->ctx_data.rx.fdf = 0x00;
- // Not used.
- s->ctx_data.rx.syt_override = 0x0000;
}
/* This protocol uses fixed number of data channels for PCM samples. */
diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c
index 6f38335fe10b..8fc30cba29d5 100644
--- a/sound/firewire/tascam/tascam-hwdep.c
+++ b/sound/firewire/tascam/tascam-hwdep.c
@@ -154,7 +154,7 @@ static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg)
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),
+ strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
@@ -265,7 +265,7 @@ int snd_tscm_create_hwdep_device(struct snd_tscm *tscm)
if (err < 0)
return err;
- strcpy(hwdep->name, "Tascam");
+ strscpy(hwdep->name, "Tascam");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM;
hwdep->ops = ops;
hwdep->private_data = tscm;
diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c
index 02eed2dce435..c57fac4f1968 100644
--- a/sound/firewire/tascam/tascam-midi.c
+++ b/sound/firewire/tascam/tascam-midi.c
@@ -108,9 +108,9 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm)
/* 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);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ tscm->card->shortname, subs->number + 1);
}
}
@@ -123,9 +123,9 @@ int snd_tscm_create_midi_devices(struct snd_tscm *tscm)
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);
+ scnprintf(subs->name, sizeof(subs->name),
+ "%s MIDI %d",
+ tscm->card->shortname, subs->number + 1);
}
}
diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c
index 36c1353f2494..a73003ac11e6 100644
--- a/sound/firewire/tascam/tascam-pcm.c
+++ b/sound/firewire/tascam/tascam-pcm.c
@@ -119,7 +119,7 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_tscm *tscm = substream->private_data;
int err = 0;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
@@ -141,7 +141,7 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
mutex_lock(&tscm->mutex);
- if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+ if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--tscm->substreams_counter;
snd_tscm_stream_stop_duplex(tscm);
@@ -279,6 +279,7 @@ int snd_tscm_create_pcm_devices(struct snd_tscm *tscm)
return err;
pcm->private_data = tscm;
+ pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", tscm->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c
index eb07e1decf9b..dfe783d01d7d 100644
--- a/sound/firewire/tascam/tascam-stream.c
+++ b/sound/firewire/tascam/tascam-stream.c
@@ -11,7 +11,7 @@
#define CLOCK_STATUS_MASK 0xffff0000
#define CLOCK_CONFIG_MASK 0x0000ffff
-#define CALLBACK_TIMEOUT 500
+#define READY_TIMEOUT_MS 4000
static int get_clock(struct snd_tscm *tscm, u32 *data)
{
@@ -423,6 +423,8 @@ int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate,
fw_iso_resources_free(&tscm->rx_resources);
return err;
}
+
+ tscm->need_long_tx_init_skip = (rate != curr_rate);
}
return 0;
@@ -454,6 +456,7 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
if (!amdtp_stream_running(&tscm->rx_stream)) {
int spd = fw_parent_device(tscm->unit)->max_speed;
+ unsigned int tx_init_skip_cycles;
err = set_stream_formats(tscm, rate);
if (err < 0)
@@ -473,14 +476,23 @@ int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)
if (err < 0)
goto error;
- err = amdtp_domain_start(&tscm->domain, 0);
+ if (tscm->need_long_tx_init_skip)
+ tx_init_skip_cycles = 16000;
+ else
+ tx_init_skip_cycles = 0;
+
+ // MEMO: Just after starting packet streaming, it transfers packets without any
+ // event. Enough after receiving the sequence of packets, it multiplexes events into
+ // the packet. However, just after changing sampling transfer frequency, it stops
+ // multiplexing during packet transmission. Enough after, it restarts multiplexing
+ // again. The device ignores presentation time expressed by the value of syt field
+ // of CIP header in received packets. The sequence of the number of data blocks per
+ // packet is important for media clock recovery.
+ err = amdtp_domain_start(&tscm->domain, tx_init_skip_cycles, true, true);
if (err < 0)
- return err;
+ goto error;
- if (!amdtp_stream_wait_callback(&tscm->rx_stream,
- CALLBACK_TIMEOUT) ||
- !amdtp_stream_wait_callback(&tscm->tx_stream,
- CALLBACK_TIMEOUT)) {
+ if (!amdtp_domain_wait_ready(&tscm->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
@@ -502,6 +514,8 @@ void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm)
fw_iso_resources_free(&tscm->tx_resources);
fw_iso_resources_free(&tscm->rx_resources);
+
+ tscm->need_long_tx_init_skip = false;
}
}
diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c
index 90288b4b4637..a073cece4a7d 100644
--- a/sound/firewire/tascam/tascam-transaction.c
+++ b/sound/firewire/tascam/tascam-transaction.c
@@ -209,7 +209,7 @@ static void midi_port_work(struct work_struct *work)
/* Set interval to next transaction. */
port->next_ktime = ktime_add_ns(ktime_get(),
- port->consume_bytes * 8 * NSEC_PER_SEC / 31250);
+ port->consume_bytes * 8 * (NSEC_PER_SEC / 31250));
/* Start this transaction. */
port->idling = false;
diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c
index 75f2edd8e78f..4f68bb4c58bc 100644
--- a/sound/firewire/tascam/tascam.c
+++ b/sound/firewire/tascam/tascam.c
@@ -9,7 +9,7 @@
MODULE_DESCRIPTION("TASCAM FireWire series Driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
static const struct snd_tscm_spec model_specs[] = {
{
@@ -73,9 +73,9 @@ static int identify_model(struct snd_tscm *tscm)
if (tscm->spec == NULL)
return -ENODEV;
- strcpy(tscm->card->driver, "FW-TASCAM");
- strcpy(tscm->card->shortname, model);
- strcpy(tscm->card->mixername, model);
+ strscpy(tscm->card->driver, "FW-TASCAM");
+ strscpy(tscm->card->shortname, model);
+ strscpy(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],
@@ -90,19 +90,31 @@ static void tscm_card_free(struct snd_card *card)
snd_tscm_transaction_unregister(tscm);
snd_tscm_stream_destroy_duplex(tscm);
+
+ mutex_destroy(&tscm->mutex);
+ fw_unit_put(tscm->unit);
}
-static void do_registration(struct work_struct *work)
+static int snd_tscm_probe(struct fw_unit *unit,
+ const struct ieee1394_device_id *entry)
{
- struct snd_tscm *tscm = container_of(work, struct snd_tscm, dwork.work);
+ struct snd_card *card;
+ struct snd_tscm *tscm;
int err;
- err = snd_card_new(&tscm->unit->device, -1, NULL, THIS_MODULE, 0,
- &tscm->card);
+ err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*tscm), &card);
if (err < 0)
- return;
- tscm->card->private_free = tscm_card_free;
- tscm->card->private_data = tscm;
+ return err;
+ card->private_free = tscm_card_free;
+
+ tscm = card->private_data;
+ tscm->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, tscm);
+ tscm->card = card;
+
+ mutex_init(&tscm->mutex);
+ spin_lock_init(&tscm->lock);
+ init_waitqueue_head(&tscm->hwdep_wait);
err = identify_model(tscm);
if (err < 0)
@@ -130,81 +142,33 @@ static void do_registration(struct work_struct *work)
if (err < 0)
goto error;
- err = snd_card_register(tscm->card);
+ err = snd_card_register(card);
if (err < 0)
goto error;
- tscm->registered = true;
-
- return;
-error:
- snd_card_free(tscm->card);
- dev_info(&tscm->unit->device,
- "Sound card registration failed: %d\n", err);
-}
-
-static int snd_tscm_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *entry)
-{
- struct snd_tscm *tscm;
-
- /* Allocate this independent of sound card instance. */
- tscm = devm_kzalloc(&unit->device, sizeof(struct snd_tscm), GFP_KERNEL);
- if (!tscm)
- return -ENOMEM;
- tscm->unit = fw_unit_get(unit);
- dev_set_drvdata(&unit->device, tscm);
-
- mutex_init(&tscm->mutex);
- spin_lock_init(&tscm->lock);
- init_waitqueue_head(&tscm->hwdep_wait);
-
- /* Allocate and register this sound card later. */
- INIT_DEFERRABLE_WORK(&tscm->dwork, do_registration);
- snd_fw_schedule_registration(unit, &tscm->dwork);
-
return 0;
+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);
- /* Postpone a workqueue for deferred registration. */
- if (!tscm->registered)
- snd_fw_schedule_registration(unit, &tscm->dwork);
-
snd_tscm_transaction_reregister(tscm);
- /*
- * After registration, userspace can start packet streaming, then this
- * code block works fine.
- */
- if (tscm->registered) {
- mutex_lock(&tscm->mutex);
- snd_tscm_stream_update_duplex(tscm);
- mutex_unlock(&tscm->mutex);
- }
+ 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);
- /*
- * Confirm to stop the work for registration before the sound card is
- * going to be released. The work is not scheduled again because bus
- * reset handler is not called anymore.
- */
- cancel_delayed_work_sync(&tscm->dwork);
-
- if (tscm->registered) {
- // Block till all of ALSA character devices are released.
- snd_card_free(tscm->card);
- }
-
- mutex_destroy(&tscm->mutex);
- fw_unit_put(tscm->unit);
+ // Block till all of ALSA character devices are released.
+ snd_card_free(tscm->card);
}
static const struct ieee1394_device_id snd_tscm_id_table[] = {
diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h
index 78b7a08986a1..d07ffcb27be6 100644
--- a/sound/firewire/tascam/tascam.h
+++ b/sound/firewire/tascam/tascam.h
@@ -70,8 +70,6 @@ struct snd_tscm {
struct mutex mutex;
spinlock_t lock;
- bool registered;
- struct delayed_work dwork;
const struct snd_tscm_spec *spec;
struct fw_iso_resources tx_resources;
@@ -99,6 +97,7 @@ struct snd_tscm {
unsigned int push_pos;
struct amdtp_domain domain;
+ bool need_long_tx_init_skip;
};
#define TSCM_ADDR_BASE 0xffff00000000ull