diff options
Diffstat (limited to '')
-rw-r--r-- | sound/firewire/motu/Makefile | 7 | ||||
-rw-r--r-- | sound/firewire/motu/amdtp-motu-trace.h | 85 | ||||
-rw-r--r-- | sound/firewire/motu/amdtp-motu.c | 486 | ||||
-rw-r--r-- | sound/firewire/motu/motu-hwdep.c | 197 | ||||
-rw-r--r-- | sound/firewire/motu/motu-midi.c | 136 | ||||
-rw-r--r-- | sound/firewire/motu/motu-pcm.c | 370 | ||||
-rw-r--r-- | sound/firewire/motu/motu-proc.c | 109 | ||||
-rw-r--r-- | sound/firewire/motu/motu-protocol-v2.c | 358 | ||||
-rw-r--r-- | sound/firewire/motu/motu-protocol-v3.c | 326 | ||||
-rw-r--r-- | sound/firewire/motu/motu-stream.c | 408 | ||||
-rw-r--r-- | sound/firewire/motu/motu-transaction.c | 136 | ||||
-rw-r--r-- | sound/firewire/motu/motu.c | 223 | ||||
-rw-r--r-- | sound/firewire/motu/motu.h | 237 |
13 files changed, 3078 insertions, 0 deletions
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 000000000..7c502d351 --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,7 @@ +# 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 \ + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o motu-protocol-v3.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h new file mode 100644 index 000000000..3d36f125c --- /dev/null +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2017 Takashi Sakamoto + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_motu + +#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SND_FIREWIRE_MOTU_TRACE_H + +#include <linux/tracepoint.h> + +static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); + +TRACE_EVENT(data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + if (s->direction == AMDTP_IN_STREAM) { + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + } else { + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + } + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + if (s->direction == AMDTP_IN_STREAM) { + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + } else { + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + } + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-motu-trace +#include <trace/define_trace.h> diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 000000000..edb31ac26 --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include "motu.h" + +#define CREATE_TRACE_POINTS +#include "amdtp-motu-trace.h" + +#define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 +#define MOTU_FDF_AM824 0x22 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; +}; + +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; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + // Each data block includes SPH in its head. Data chunks follow with + // 3 byte alignment. Padding follows with zero to conform to quadlet + // alignment. + pcm_chunks = formats->pcm_chunks[mode]; + 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); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + 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; +} + +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int data_blocks, + unsigned int pcm_frames) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels = p->pcm_chunks; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; + u8 *byte; + u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; + + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | + (byte[1] << 16) | + (byte[2] << 8); + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int data_blocks, + unsigned int pcm_frames) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels = p->pcm_chunks; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; + u8 *byte; + const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; + + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + for (i = 0; i < data_blocks; ++i) { + *frames = be32_to_cpu(*buffer); + buffer += data_block_quadlets; + frames++; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + /* 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); + buffer += data_block_quadlets; + frames++; + } +} + +static void probe_tracepoints_events(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets) +{ + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + 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); + } +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + struct amdtp_motu *p = s->protocol; + unsigned int pcm_frames = 0; + int i; + + // For data block processing. + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + 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; + } + + if (p->midi_ports) + read_midi_messages(s, buf, data_blocks); + } + + // 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; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + struct amdtp_motu *p = s->protocol; + unsigned int pcm_frames = 0; + int i; + + // For data block processing. + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + 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; + } else { + write_pcm_silence(s, buf, data_blocks); + } + + if (p->midi_ports) + write_midi_messages(s, buf, data_blocks); + + // TODO: how to interact control messages between userspace? + + write_sph(s, buf, data_blocks); + } + + // For tracepoints. + if (trace_data_block_sph_enabled() || + trace_data_block_message_enabled()) + probe_tracepoints_events(s, descs, packets); + + return pcm_frames; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_spec *spec) +{ + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_ctx_payloads = process_ir_ctx_payloads; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (spec->protocol_version == SND_MOTU_PROTOCOL_V3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } + + if (spec == &snd_motu_spec_8pre || + spec == &snd_motu_spec_ultralite) { + // 8pre has some quirks. + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK; + } + } else { + process_ctx_payloads = process_it_ctx_payloads; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_ctx_payloads, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + + 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; + } + + return 0; +} diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 000000000..0764a4770 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed && motu->msg == 0) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + __poll_t events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed || motu->msg) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | EPOLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_motu_create_hwdep_device(struct snd_motu *motu) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 000000000..2365f7dfd --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ +#include "motu.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = snd_motu_stream_reserve_duplex(motu, 0, 0, 0); + if (err >= 0) { + ++motu->substreams_counter; + err = snd_motu_stream_start_duplex(motu); + if (err < 0) + --motu->substreams_counter; + } + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + --motu->substreams_counter; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 000000000..8e1437371 --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <sound/pcm_params.h> +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + struct amdtp_domain *d = &motu->domain; + enum snd_motu_clock_source src; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = snd_motu_stream_cache_packet_formats(motu); + if (err < 0) + goto err_locked; + + err = init_hw_info(motu, substream); + if (err < 0) + goto err_locked; + + err = snd_motu_protocol_get_clock_source(motu, &src); + if (err < 0) + goto err_locked; + + // When source of clock is not internal or any stream is reserved for + // transmission of PCM frames, the available sampling rate is limited + // at current one. + if ((src != SND_MOTU_CLOCK_SOURCE_INTERNAL && + src != SND_MOTU_CLOCK_SOURCE_SPH) || + (motu->substreams_counter > 0 && d->events_per_period > 0)) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int rate; + + err = snd_motu_protocol_get_clock_rate(motu, &rate); + if (err < 0) + goto err_locked; + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + + if (frames_per_period > 0) { + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) + goto err_locked; + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) + goto err_locked; + } + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return 0; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err = 0; + + if (substream->runtime->status->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); + + mutex_lock(&motu->mutex); + err = snd_motu_stream_reserve_duplex(motu, rate, + frames_per_period, frames_per_buffer); + if (err >= 0) + ++motu->substreams_counter; + mutex_unlock(&motu->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --motu->substreams_counter; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_pointer(&motu->domain, &motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_pointer(&motu->domain, &motu->rx_stream); +} + +static int capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&motu->domain, &motu->tx_stream); +} + +static int playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&motu->domain, &motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .ack = capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .ack = playback_ack, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return 0; +} diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 000000000..f009cf7aa --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PDIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", + [SND_MOTU_CLOCK_SOURCE_SPH] = "Source packet header", + [SND_MOTU_CLOCK_SOURCE_UNKNOWN] = "Unknown", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + unsigned int rate; + enum snd_motu_clock_source source; + + if (snd_motu_protocol_get_clock_rate(motu, &rate) < 0) + return; + if (snd_motu_protocol_get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (snd_motu_protocol_cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\ttotal\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + motu->spec->tx_fixed_pcm_chunks[mode], + formats->pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\ttotal\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + motu->spec->rx_fixed_pcm_chunks[mode], + formats->pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(motu->card, name, root); + if (entry) + snd_info_set_text_ops(entry, motu, op); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 000000000..f0a0ecad4 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 +#define V2_CLOCK_FETCH_ENABLE 0x02000000 +#define V2_CLOCK_MODEL_SPECIFIC 0x04000000 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int get_clock_rate(u32 data, unsigned int *rate) +{ + unsigned int index = (data & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +int snd_motu_protocol_v2_get_clock_rate(struct snd_motu *motu, + unsigned int *rate) +{ + __be32 reg; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + return get_clock_rate(be32_to_cpu(reg), rate); +} + +int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu, + unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int detect_clock_source_optical_model(struct snd_motu *motu, u32 data, + enum snd_motu_clock_source *src) +{ + switch (data) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + { + __be32 reg; + + // To check the configuration of optical interface. + int err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, 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 3: + *src = SND_MOTU_CLOCK_SOURCE_SPH; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *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; + break; + default: + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + break; + } + + 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) +{ + __be32 reg; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + return get_clock_source(motu, be32_to_cpu(reg), src); +} + +// Expected for Traveler and 896HD, which implements Altera Cyclone EP1C3. +static int switch_fetching_mode_cyclone(struct snd_motu *motu, u32 *data, + bool enable) +{ + *data |= V2_CLOCK_MODEL_SPECIFIC; + + return 0; +} + +// For UltraLite and 8pre, which implements Xilinx Spartan XC3S200. +static int switch_fetching_mode_spartan(struct snd_motu *motu, u32 *data, + bool enable) +{ + unsigned int rate; + enum snd_motu_clock_source src; + int err; + + err = get_clock_source(motu, *data, &src); + if (err < 0) + return err; + + err = get_clock_rate(*data, &rate); + if (err < 0) + return err; + + if (src == SND_MOTU_CLOCK_SOURCE_SPH && rate > 48000) + *data |= V2_CLOCK_MODEL_SPECIFIC; + + return 0; +} + +int snd_motu_protocol_v2_switch_fetching_mode(struct snd_motu *motu, + bool enable) +{ + if (motu->spec == &snd_motu_spec_828mk2) { + // 828mkII implements Altera ACEX 1K EP1K30. Nothing to do. + return 0; + } else { + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V2_CLOCK_FETCH_ENABLE | V2_CLOCK_MODEL_SPECIFIC); + if (enable) + data |= V2_CLOCK_FETCH_ENABLE; + + if (motu->spec == &snd_motu_spec_traveler) + err = switch_fetching_mode_cyclone(motu, &data, enable); + else + err = switch_fetching_mode_spartan(motu, &data, enable); + if (err < 0) + return err; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, + ®, sizeof(reg)); + } +} + +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) +{ + __be32 reg; + u32 data; + int err; + + motu->tx_packet_formats.pcm_byte_offset = 10; + motu->rx_packet_formats.pcm_byte_offset = 10; + + motu->tx_packet_formats.msg_chunks = 2; + motu->rx_packet_formats.msg_chunks = 2; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + 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_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; +} + +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, + .tx_fixed_pcm_chunks = {14, 14, 0}, + .rx_fixed_pcm_chunks = {14, 14, 0}, +}; + +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, + .tx_fixed_pcm_chunks = {14, 14, 8}, + .rx_fixed_pcm_chunks = {14, 14, 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, + .tx_fixed_pcm_chunks = {14, 14, 0}, + .rx_fixed_pcm_chunks = {14, 14, 0}, +}; + +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, + // 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 new file mode 100644 index 000000000..4e6b0e449 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <linux/delay.h> +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +#define V3_MSG_FLAG_CLK_CHANGED 0x00000002 +#define V3_CLK_WAIT_MSEC 4000 + +int snd_motu_protocol_v3_get_clock_rate(struct snd_motu *motu, + unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +int snd_motu_protocol_v3_set_clock_rate(struct snd_motu *motu, + unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + int result; + + motu->msg = 0; + result = wait_event_interruptible_timeout(motu->hwdep_wait, + motu->msg & V3_MSG_FLAG_CLK_CHANGED, + msecs_to_jiffies(V3_CLK_WAIT_MSEC)); + if (result < 0) + return result; + if (result == 0) + return -ETIMEDOUT; + } + + return 0; +} + +static int detect_clock_source_828mk3(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; + case 0x18: + case 0x19: + { + __be32 reg; + u32 options; + int err; + + err = snd_motu_transaction_read(motu, + V3_OPT_IFACE_MODE_OFFSET, ®, sizeof(reg)); + if (err < 0) + return err; + options = be32_to_cpu(reg); + + if (data == 0x18) { + if (options & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (options & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + 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; + } + + 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, ®, + 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) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int detect_packet_formats_828mk3(struct snd_motu *motu, u32 data) +{ + if (data & V3_ENABLE_OPT_IN_IFACE_A) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) { + motu->tx_packet_formats.pcm_chunks[0] += 4; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } + } + + if (data & V3_ENABLE_OPT_IN_IFACE_B) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) { + motu->tx_packet_formats.pcm_chunks[0] += 4; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } + } + + if (data & V3_ENABLE_OPT_OUT_IFACE_A) { + if (data & V3_NO_ADAT_OPT_OUT_IFACE_A) { + motu->rx_packet_formats.pcm_chunks[0] += 4; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } + } + + if (data & V3_ENABLE_OPT_OUT_IFACE_B) { + if (data & V3_NO_ADAT_OPT_OUT_IFACE_B) { + motu->rx_packet_formats.pcm_chunks[0] += 4; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } + } + + return 0; +} + +int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + motu->tx_packet_formats.pcm_byte_offset = 10; + motu->rx_packet_formats.pcm_byte_offset = 10; + + motu->tx_packet_formats.msg_chunks = 2; + motu->rx_packet_formats.msg_chunks = 2; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + 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_828mk3) + return detect_packet_formats_828mk3(motu, data); + else + return 0; +} + + +const struct snd_motu_spec snd_motu_spec_828mk3 = { + .name = "828mk3", + .protocol_version = SND_MOTU_PROTOCOL_V3, + .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q | + SND_MOTU_SPEC_TX_MIDI_3RD_Q, + .tx_fixed_pcm_chunks = {18, 18, 14}, + .rx_fixed_pcm_chunks = {14, 14, 10}, +}; + +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, + .tx_fixed_pcm_chunks = {18, 14, 10}, + .rx_fixed_pcm_chunks = {14, 14, 14}, +}; + +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, + .tx_fixed_pcm_chunks = {10, 10, 0}, + .rx_fixed_pcm_chunks = {10, 10, 0}, +}; + +const struct snd_motu_spec snd_motu_spec_4pre = { + .name = "4pre", + .protocol_version = SND_MOTU_PROTOCOL_V3, + .tx_fixed_pcm_chunks = {10, 10, 0}, + .rx_fixed_pcm_chunks = {10, 10, 0}, +}; diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 000000000..2028c5419 --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int keep_resources(struct snd_motu *motu, unsigned int rate, + struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + struct snd_motu_packet_format *packet_format; + unsigned int midi_ports = 0; + int err; + + if (stream == &motu->rx_stream) { + resources = &motu->rx_resources; + packet_format = &motu->rx_packet_formats; + + if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q)) + midi_ports = 1; + } else { + resources = &motu->tx_resources; + packet_format = &motu->tx_packet_formats; + + if ((motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q)) + midi_ports = 1; + } + + err = amdtp_motu_set_parameters(stream, rate, midi_ports, + packet_format); + if (err < 0) + return err; + + return fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(motu->unit)->max_speed); +} + +static int begin_session(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + // Configure the unit to start isochronous communication. + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void finish_session(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_protocol_switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_cache_packet_formats(struct snd_motu *motu) +{ + int err; + + err = snd_motu_protocol_cache_packet_formats(motu); + if (err < 0) + return err; + + if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) { + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + } else if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q) { + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + } + + if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) { + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + } else if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) { + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + } + + return 0; +} + +int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + int err; + + err = snd_motu_protocol_get_clock_rate(motu, &curr_rate); + if (err < 0) + return err; + if (rate == 0) + rate = curr_rate; + + if (motu->substreams_counter == 0 || curr_rate != rate) { + amdtp_domain_stop(&motu->domain); + finish_session(motu); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); + + err = snd_motu_protocol_set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = snd_motu_stream_cache_packet_formats(motu); + if (err < 0) + return err; + + err = keep_resources(motu, rate, &motu->tx_stream); + if (err < 0) + return err; + + err = keep_resources(motu, rate, &motu->rx_stream); + if (err < 0) { + fw_iso_resources_free(&motu->tx_resources); + return err; + } + + err = amdtp_domain_set_events_per_period(&motu->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); + return err; + } + } + + return 0; +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->spec->tx_fixed_pcm_chunks[0] == motu->tx_packet_formats.pcm_chunks[0]) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->spec->rx_fixed_pcm_chunks[0] == motu->rx_packet_formats.pcm_chunks[0]) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu) +{ + unsigned int generation = motu->rx_resources.generation; + int err = 0; + + if (motu->substreams_counter == 0) + return 0; + + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_domain_stop(&motu->domain); + finish_session(motu); + } + + if (generation != fw_parent_device(motu->unit)->card->generation) { + err = fw_iso_resources_update(&motu->rx_resources); + if (err < 0) + return err; + + err = fw_iso_resources_update(&motu->tx_resources); + if (err < 0) + return err; + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + int spd = fw_parent_device(motu->unit)->max_speed; + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = begin_session(motu); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + goto stop_streams; + } + + err = amdtp_domain_add_stream(&motu->domain, &motu->tx_stream, + motu->tx_resources.channel, spd); + if (err < 0) + goto stop_streams; + + err = amdtp_domain_add_stream(&motu->domain, &motu->rx_stream, + motu->rx_resources.channel, spd); + if (err < 0) + goto stop_streams; + + err = amdtp_domain_start(&motu->domain, 0); + 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)) { + err = -ETIMEDOUT; + goto stop_streams; + } + + err = snd_motu_protocol_switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + goto stop_streams; + } + } + + return 0; + +stop_streams: + amdtp_domain_stop(&motu->domain); + finish_session(motu); + return err; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->substreams_counter == 0) { + amdtp_domain_stop(&motu->domain); + finish_session(motu); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); + } +} + +static int init_stream(struct snd_motu *motu, struct amdtp_stream *s) +{ + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + int err; + + if (s == &motu->tx_stream) { + resources = &motu->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &motu->rx_resources; + dir = AMDTP_OUT_STREAM; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(s, motu->unit, dir, motu->spec); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_motu *motu, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &motu->tx_stream) + fw_iso_resources_destroy(&motu->tx_resources); + else + fw_iso_resources_destroy(&motu->rx_resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, &motu->tx_stream); + if (err < 0) + return err; + + err = init_stream(motu, &motu->rx_stream); + if (err < 0) { + destroy_stream(motu, &motu->tx_stream); + return err; + } + + err = amdtp_domain_init(&motu->domain); + if (err < 0) { + destroy_stream(motu, &motu->tx_stream); + destroy_stream(motu, &motu->rx_stream); + } + + return err; +} + +// This function should be called before starting streams or after stopping +// streams. +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + amdtp_domain_destroy(&motu->domain); + + destroy_stream(motu, &motu->rx_stream); + destroy_stream(motu, &motu->tx_stream); + + motu->substreams_counter = 0; +} + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 000000000..2dc1d6e59 --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 000000000..a4929c130 --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_MODEL: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(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, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_card_free(struct snd_card *card) +{ + struct snd_motu *motu = card->private_data; + + snd_motu_transaction_unregister(motu); + snd_motu_stream_destroy_duplex(motu); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err; + + if (motu->registered) + return; + + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return; + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + + name_card(motu); + + err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + + err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + + snd_motu_proc_init(motu); + + err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + + if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q)) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + + err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + + err = snd_card_register(motu->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; +} + +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); +} + +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); +} + +#define SND_MOTU_DEV_ENTRY(model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_SPECIFIER_ID | \ + IEEE1394_MATCH_VERSION, \ + .vendor_id = OUI_MOTU, \ + .specifier_id = OUI_MOTU, \ + .version = model, \ + .driver_data = (kernel_ulong_t)data, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x000003, &snd_motu_spec_828mk2), + 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(0x000019, &snd_motu_spec_ultralite_mk3), // FireWire only. + SND_MOTU_DEV_ENTRY(0x000035, &snd_motu_spec_828mk3), // Hybrid. + SND_MOTU_DEV_ENTRY(0x000033, &snd_motu_spec_audio_express), + SND_MOTU_DEV_ENTRY(0x000045, &snd_motu_spec_4pre), + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 000000000..3d0236ee6 --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char pcm_chunks[3]; +}; + +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; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int substreams_counter; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + struct amdtp_domain domain; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_RX_MIDI_2ND_Q = 0x0001, + SND_MOTU_SPEC_RX_MIDI_3RD_Q = 0x0002, + SND_MOTU_SPEC_TX_MIDI_2ND_Q = 0x0004, + SND_MOTU_SPEC_TX_MIDI_3RD_Q = 0x0008, +}; + +#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_SPH, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +enum snd_motu_protocol_version { + SND_MOTU_PROTOCOL_V2, + SND_MOTU_PROTOCOL_V3, +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_protocol_version protocol_version; + enum snd_motu_spec_flags flags; + + unsigned char tx_fixed_pcm_chunks[3]; + unsigned char rx_fixed_pcm_chunks[3]; +}; + +extern const struct snd_motu_spec snd_motu_spec_828mk2; +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_ultralite_mk3; +extern const struct snd_motu_spec snd_motu_spec_audio_express; +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); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_cache_packet_formats(struct snd_motu *motu); +int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_motu_stream_start_duplex(struct snd_motu *motu); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(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, + unsigned int rate); +int snd_motu_protocol_v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src); +int snd_motu_protocol_v2_switch_fetching_mode(struct snd_motu *motu, + bool enable); +int snd_motu_protocol_v2_cache_packet_formats(struct snd_motu *motu); + +int snd_motu_protocol_v3_get_clock_rate(struct snd_motu *motu, + unsigned int *rate); +int snd_motu_protocol_v3_set_clock_rate(struct snd_motu *motu, + unsigned int rate); +int snd_motu_protocol_v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src); +int snd_motu_protocol_v3_switch_fetching_mode(struct snd_motu *motu, + bool enable); +int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu); + +static inline int snd_motu_protocol_get_clock_rate(struct snd_motu *motu, + unsigned int *rate) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + 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 + return -ENXIO; +} + +static inline int snd_motu_protocol_set_clock_rate(struct snd_motu *motu, + unsigned int rate) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + 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 + return -ENXIO; +} + +static inline int snd_motu_protocol_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *source) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + 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 + return -ENXIO; +} + +static inline int snd_motu_protocol_switch_fetching_mode(struct snd_motu *motu, + bool enable) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + 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 + return -ENXIO; +} + +static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + 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 + return -ENXIO; +} + +#endif |