diff mbox series

[02/11] ALSA: firewire-motu: add message parser for meter information in command DSP model

Message ID 20211015080826.34847-3-o-takashi@sakamocchi.jp (mailing list archive)
State New, archived
Headers show
Series ALSA: firewire-motu: add ioctl commands to retrieve information in messages delivered by isoc packet | expand

Commit Message

Takashi Sakamoto Oct. 15, 2021, 8:08 a.m. UTC
Some of MOTU models allows software to configure their DSP parameters by
command included in asynchronous transaction. The models multiplex messages
for hardware meters into isochronous packet as well as PCM frames. For
convenience, I call them as 'command DSP' model.

This patch adds message parser for them to gather hardware meter
information.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 include/uapi/sound/firewire.h                 |  30 ++++
 sound/firewire/motu/Makefile                  |   3 +-
 sound/firewire/motu/amdtp-motu.c              |   3 +
 .../motu/motu-command-dsp-message-parser.c    | 160 ++++++++++++++++++
 sound/firewire/motu/motu-protocol-v3.c        |  10 +-
 sound/firewire/motu/motu-stream.c             |   4 +
 sound/firewire/motu/motu.c                    |   4 +
 sound/firewire/motu/motu.h                    |   7 +
 8 files changed, 216 insertions(+), 5 deletions(-)
 create mode 100644 sound/firewire/motu/motu-command-dsp-message-parser.c
diff mbox series

Patch

diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index 347fd7a05596..82d4765fbeee 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -143,4 +143,34 @@  struct snd_firewire_motu_register_dsp_meter {
 	__u8 data[SNDRV_FIREWIRE_MOTU_REGISTER_DSP_METER_COUNT];
 };
 
+// In below MOTU models, software is allowed to control their DSP by command in frame of
+// asynchronous transaction to 0x'ffff'0001'0000:
+//
+//  - 828 mk3 (FireWire only and Hybrid)
+//  - 896 mk3 (FireWire only and Hybrid)
+//  - Ultralite mk3 (FireWire only and Hybrid)
+//  - Traveler mk3
+//  - Track 16
+//
+// On the other hand, the states of hardware meter is split into specific messages included in the
+// sequence of isochronous packet. ALSA firewire-motu driver gathers the message and allow userspace
+// application to read it via ioctl.
+
+#define SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT	400
+
+/**
+ * struct snd_firewire_motu_command_dsp_meter - the container for meter information in DSP
+ *						controlled by command
+ * @data: Signal level meters. The mapping between position and signal channel is model-dependent.
+ *
+ * The structure expresses the part of DSP status for hardware meter. The 32 bit storage is
+ * estimated to include IEEE 764 32 bit single precision floating point (binary32) value. It is
+ * expected to be linear value (not logarithm) for audio signal level between 0.0 and +1.0. However,
+ * the last two quadlets (data[398] and data[399]) are filled with 0xffffffff since they are the
+ * marker of one period.
+ */
+struct snd_firewire_motu_command_dsp_meter {
+	__u32 data[SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT];
+};
+
 #endif /* _UAPI_SOUND_FIREWIRE_H_INCLUDED */
diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile
index edbdf40c7162..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-register-dsp-message-parser.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 605b831492ac..3ea91e281147 100644
--- a/sound/firewire/motu/amdtp-motu.c
+++ b/sound/firewire/motu/amdtp-motu.c
@@ -361,6 +361,9 @@  static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s,
 	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.
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..6716074f8bc1
--- /dev/null
+++ b/sound/firewire/motu/motu-command-dsp-message-parser.c
@@ -0,0 +1,160 @@ 
+// 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 {
+	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;
+	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;
+	int i;
+
+	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) {
+						if (parser->value_index <
+						    SNDRV_FIREWIRE_MOTU_COMMAND_DSP_METER_COUNT) {
+							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;
+			}
+			}
+		}
+	}
+}
diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c
index d0dd587460de..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},
 };
diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c
index 654b313ba98d..64aec9c3eefd 100644
--- a/sound/firewire/motu/motu-stream.c
+++ b/sound/firewire/motu/motu-stream.c
@@ -259,6 +259,10 @@  int snd_motu_stream_start_duplex(struct snd_motu *motu)
 			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);
diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c
index 0edf8f594a55..5fc7ae475537 100644
--- a/sound/firewire/motu/motu.c
+++ b/sound/firewire/motu/motu.c
@@ -116,6 +116,10 @@  static int motu_probe(struct fw_unit *unit, const struct ieee1394_device_id *ent
 		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);
diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h
index 8d6850bb925e..d818ce4901c9 100644
--- a/sound/firewire/motu/motu.h
+++ b/sound/firewire/motu/motu.h
@@ -88,6 +88,7 @@  enum snd_motu_spec_flags {
 	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
@@ -278,4 +279,10 @@  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);
 
+
+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);
+
 #endif