diff options
Diffstat (limited to 'sound/firewire/motu')
-rw-r--r-- | sound/firewire/motu/Makefile | 3 | ||||
-rw-r--r-- | sound/firewire/motu/amdtp-motu.c | 11 | ||||
-rw-r--r-- | sound/firewire/motu/motu-command-dsp-message-parser.c | 181 | ||||
-rw-r--r-- | sound/firewire/motu/motu-hwdep.c | 119 | ||||
-rw-r--r-- | sound/firewire/motu/motu-protocol-v2.c | 14 | ||||
-rw-r--r-- | sound/firewire/motu/motu-protocol-v3.c | 14 | ||||
-rw-r--r-- | sound/firewire/motu/motu-register-dsp-message-parser.c | 420 | ||||
-rw-r--r-- | sound/firewire/motu/motu-stream.c | 10 | ||||
-rw-r--r-- | sound/firewire/motu/motu.c | 10 | ||||
-rw-r--r-- | sound/firewire/motu/motu.h | 23 |
10 files changed, 783 insertions, 22 deletions
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile index acdf66564fb0..3bef2a0b1e2e 100644 --- a/sound/firewire/motu/Makefile +++ b/sound/firewire/motu/Makefile @@ -4,5 +4,6 @@ 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 \ - motu-protocol-v1.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 a18c2c033e83..2fb52f481d12 100644 --- a/sound/firewire/motu/amdtp-motu.c +++ b/sound/firewire/motu/amdtp-motu.c @@ -333,6 +333,7 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, unsigned int packets, struct snd_pcm_substream *pcm) { + struct snd_motu *motu = container_of(s, struct snd_motu, tx_stream); struct amdtp_motu *p = s->protocol; unsigned int pcm_frames = 0; int i; @@ -357,6 +358,14 @@ static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, read_midi_messages(s, buf, data_blocks); } + if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) { + snd_motu_register_dsp_message_parser_parse(motu, descs, packets, + s->data_block_quadlets); + } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) { + snd_motu_command_dsp_message_parser_parse(motu, descs, packets, + s->data_block_quadlets); + } + // For tracepoints. if (trace_data_block_sph_enabled() || trace_data_block_message_enabled()) @@ -415,8 +424,6 @@ 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); } 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..9efe4d364baf --- /dev/null +++ b/sound/firewire/motu/motu-command-dsp-message-parser.c @@ -0,0 +1,181 @@ +// 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(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int 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 < desc_count; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buffer = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + int j; + + 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 b5ced5d27758..a900fc0e7644 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,46 @@ 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; + } return count; } @@ -67,7 +101,7 @@ 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; @@ -155,6 +189,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; } @@ -193,5 +292,7 @@ int snd_motu_create_hwdep_device(struct snd_motu *motu) hwdep->private_data = motu; hwdep->exclusive = true; + motu->hwdep = hwdep; + return 0; } diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c index 2bd4485e4bc7..a5f70efa2e88 100644 --- a/sound/firewire/motu/motu-protocol-v2.c +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -275,7 +275,8 @@ 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}, }; @@ -283,7 +284,7 @@ const struct snd_motu_spec snd_motu_spec_828mk2 = { const struct snd_motu_spec snd_motu_spec_896hd = { .name = "896HD", .protocol_version = SND_MOTU_PROTOCOL_V2, - // No support for MIDI. + .flags = SND_MOTU_SPEC_REGISTER_DSP, .tx_fixed_pcm_chunks = {14, 14, 8}, .rx_fixed_pcm_chunks = {14, 14, 8}, }; @@ -292,7 +293,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}, }; @@ -301,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}, }; @@ -310,7 +313,8 @@ 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, + 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 56e4504e7ec9..05608e8ca0bc 100644 --- a/sound/firewire/motu/motu-protocol-v3.c +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -261,12 +261,12 @@ int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu) 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_TX_MIDI_3RD_Q | + SND_MOTU_SPEC_COMMAND_DSP, .tx_fixed_pcm_chunks = {18, 18, 14}, .rx_fixed_pcm_chunks = {14, 14, 10}, }; @@ -275,7 +275,8 @@ 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. }; @@ -284,7 +285,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}, }; @@ -293,7 +295,8 @@ 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}, }; @@ -301,6 +304,7 @@ const struct snd_motu_spec snd_motu_spec_audio_express = { 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..0c587567540f --- /dev/null +++ b/sound/firewire/motu/motu-register-dsp-message-parser.c @@ -0,0 +1,420 @@ +// 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(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int 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 < desc_count; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buffer = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + int j; + + 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 9e6ca39ebd7f..64aec9c3eefd 100644 --- a/sound/firewire/motu/motu-stream.c +++ b/sound/firewire/motu/motu-stream.c @@ -255,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, diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c index f65426238d4c..5fc7ae475537 100644 --- a/sound/firewire/motu/motu.c +++ b/sound/firewire/motu/motu.c @@ -112,6 +112,16 @@ static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *ent if (err < 0) goto error; + 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; diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h index f1a830b358d4..79704ae6a73e 100644 --- a/sound/firewire/motu/motu.h +++ b/sound/firewire/motu/motu.h @@ -74,10 +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 { @@ -85,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 @@ -270,4 +275,22 @@ static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu) 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(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int data_block_quadlets); +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(struct snd_motu *motu, const struct pkt_desc *descs, + unsigned int desc_count, unsigned int data_block_quadlets); +void snd_motu_command_dsp_message_parser_copy_meter(struct snd_motu *motu, + struct snd_firewire_motu_command_dsp_meter *meter); + #endif |