__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 */
 
 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
 
        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.
 
--- /dev/null
+// 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;
+                       }
+                       }
+               }
+       }
+}
 
                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},
 };
        .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.
 };
        .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},
 };
 
                        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);
 
                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);
 
        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
 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