struct snd_ump_endpoint;
 struct snd_ump_block;
 struct snd_ump_ops;
+struct ump_cvt_to_ump;
 
 struct snd_ump_endpoint {
        struct snd_rawmidi core;        /* raw UMP access */
        void (*private_free)(struct snd_ump_endpoint *ump);
 
        struct list_head block_list;    /* list of snd_ump_block objects */
+
+       /* intermediate buffer for UMP input */
+       u32 input_buf[4];
+       int input_buf_head;
+       int input_pending;
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+       struct mutex open_mutex;
+
+       spinlock_t legacy_locks[2];
+       struct snd_rawmidi *legacy_rmidi;
+       struct snd_rawmidi_substream *legacy_substreams[2][SNDRV_UMP_MAX_GROUPS];
+
+       /* for legacy output; need to open the actual substream unlike input */
+       int legacy_out_opens;
+       struct snd_rawmidi_file legacy_out_rfile;
+       struct ump_cvt_to_ump *out_cvts;
+#endif
 };
 
 /* ops filled by UMP drivers */
 int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count);
 int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count);
 
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
+                                 char *id, int device);
+#else
+static inline int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
+                                               char *id, int device)
+{
+       return 0;
+}
+#endif
+
 /*
  * Some definitions for UMP
  */
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Universal MIDI Packet (UMP): Message Definitions
+ */
+#ifndef __SOUND_UMP_MSG_H
+#define __SOUND_UMP_MSG_H
+
+/* MIDI 1.0 / 2.0 Status Code (4bit) */
+enum {
+       UMP_MSG_STATUS_PER_NOTE_RCC = 0x0,
+       UMP_MSG_STATUS_PER_NOTE_ACC = 0x1,
+       UMP_MSG_STATUS_RPN = 0x2,
+       UMP_MSG_STATUS_NRPN = 0x3,
+       UMP_MSG_STATUS_RELATIVE_RPN = 0x4,
+       UMP_MSG_STATUS_RELATIVE_NRPN = 0x5,
+       UMP_MSG_STATUS_PER_NOTE_PITCH_BEND = 0x6,
+       UMP_MSG_STATUS_NOTE_OFF = 0x8,
+       UMP_MSG_STATUS_NOTE_ON = 0x9,
+       UMP_MSG_STATUS_POLY_PRESSURE = 0xa,
+       UMP_MSG_STATUS_CC = 0xb,
+       UMP_MSG_STATUS_PROGRAM = 0xc,
+       UMP_MSG_STATUS_CHANNEL_PRESSURE = 0xd,
+       UMP_MSG_STATUS_PITCH_BEND = 0xe,
+       UMP_MSG_STATUS_PER_NOTE_MGMT = 0xf,
+};
+
+/* MIDI 1.0 Channel Control (7bit) */
+enum {
+       UMP_CC_BANK_SELECT = 0,
+       UMP_CC_MODULATION = 1,
+       UMP_CC_BREATH = 2,
+       UMP_CC_FOOT = 4,
+       UMP_CC_PORTAMENTO_TIME = 5,
+       UMP_CC_DATA = 6,
+       UMP_CC_VOLUME = 7,
+       UMP_CC_BALANCE = 8,
+       UMP_CC_PAN = 10,
+       UMP_CC_EXPRESSION = 11,
+       UMP_CC_EFFECT_CONTROL_1 = 12,
+       UMP_CC_EFFECT_CONTROL_2 = 13,
+       UMP_CC_GP_1 = 16,
+       UMP_CC_GP_2 = 17,
+       UMP_CC_GP_3 = 18,
+       UMP_CC_GP_4 = 19,
+       UMP_CC_BANK_SELECT_LSB = 32,
+       UMP_CC_MODULATION_LSB = 33,
+       UMP_CC_BREATH_LSB = 34,
+       UMP_CC_FOOT_LSB = 36,
+       UMP_CC_PORTAMENTO_TIME_LSB = 37,
+       UMP_CC_DATA_LSB = 38,
+       UMP_CC_VOLUME_LSB = 39,
+       UMP_CC_BALANCE_LSB = 40,
+       UMP_CC_PAN_LSB = 42,
+       UMP_CC_EXPRESSION_LSB = 43,
+       UMP_CC_EFFECT1_LSB = 44,
+       UMP_CC_EFFECT2_LSB = 45,
+       UMP_CC_GP_1_LSB = 48,
+       UMP_CC_GP_2_LSB = 49,
+       UMP_CC_GP_3_LSB = 50,
+       UMP_CC_GP_4_LSB = 51,
+       UMP_CC_SUSTAIN = 64,
+       UMP_CC_PORTAMENTO_SWITCH = 65,
+       UMP_CC_SOSTENUTO = 66,
+       UMP_CC_SOFT_PEDAL = 67,
+       UMP_CC_LEGATO = 68,
+       UMP_CC_HOLD_2 = 69,
+       UMP_CC_SOUND_CONTROLLER_1 = 70,
+       UMP_CC_SOUND_CONTROLLER_2 = 71,
+       UMP_CC_SOUND_CONTROLLER_3 = 72,
+       UMP_CC_SOUND_CONTROLLER_4 = 73,
+       UMP_CC_SOUND_CONTROLLER_5 = 74,
+       UMP_CC_SOUND_CONTROLLER_6 = 75,
+       UMP_CC_SOUND_CONTROLLER_7 = 76,
+       UMP_CC_SOUND_CONTROLLER_8 = 77,
+       UMP_CC_SOUND_CONTROLLER_9 = 78,
+       UMP_CC_SOUND_CONTROLLER_10 = 79,
+       UMP_CC_GP_5 = 80,
+       UMP_CC_GP_6 = 81,
+       UMP_CC_GP_7 = 82,
+       UMP_CC_GP_8 = 83,
+       UMP_CC_PORTAMENTO_CONTROL = 84,
+       UMP_CC_EFFECT_1 = 91,
+       UMP_CC_EFFECT_2 = 92,
+       UMP_CC_EFFECT_3 = 93,
+       UMP_CC_EFFECT_4 = 94,
+       UMP_CC_EFFECT_5 = 95,
+       UMP_CC_DATA_INC = 96,
+       UMP_CC_DATA_DEC = 97,
+       UMP_CC_NRPN_LSB = 98,
+       UMP_CC_NRPN_MSB = 99,
+       UMP_CC_RPN_LSB = 100,
+       UMP_CC_RPN_MSB = 101,
+       UMP_CC_ALL_SOUND_OFF = 120,
+       UMP_CC_RESET_ALL = 121,
+       UMP_CC_LOCAL_CONTROL = 122,
+       UMP_CC_ALL_NOTES_OFF = 123,
+       UMP_CC_OMNI_OFF = 124,
+       UMP_CC_OMNI_ON = 125,
+       UMP_CC_POLY_OFF = 126,
+       UMP_CC_POLY_ON = 127,
+};
+
+/* MIDI 1.0 / 2.0 System Messages (0xfx) */
+enum {
+       UMP_SYSTEM_STATUS_MIDI_TIME_CODE = 0xf1,
+       UMP_SYSTEM_STATUS_SONG_POSITION = 0xf2,
+       UMP_SYSTEM_STATUS_SONG_SELECT = 0xf3,
+       UMP_SYSTEM_STATUS_TUNE_REQUEST = 0xf6,
+       UMP_SYSTEM_STATUS_TIMING_CLOCK = 0xf8,
+       UMP_SYSTEM_STATUS_START = 0xfa,
+       UMP_SYSTEM_STATUS_CONTINUE = 0xfb,
+       UMP_SYSTEM_STATUS_STOP = 0xfc,
+       UMP_SYSTEM_STATUS_ACTIVE_SENSING = 0xfe,
+       UMP_SYSTEM_STATUS_RESET = 0xff,
+};
+
+/* MIDI 1.0 Realtime and SysEx status messages (0xfx) */
+enum {
+       UMP_MIDI1_MSG_REALTIME          = 0xf0, /* mask */
+       UMP_MIDI1_MSG_SYSEX_START       = 0xf0,
+       UMP_MIDI1_MSG_SYSEX_END         = 0xf7,
+};
+
+/*
+ * UMP Message Definitions
+ */
+
+/* MIDI 1.0 Note Off / Note On (32bit) */
+struct snd_ump_midi1_msg_note {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 velocity:8;
+#else
+       u32 velocity:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+#endif
+} __packed;
+
+/* MIDI 1.0 Poly Pressure (32bit) */
+struct snd_ump_midi1_msg_paf {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 data:8;
+#else
+       u32 data:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+#endif
+} __packed;
+
+/* MIDI 1.0 Control Change (32bit) */
+struct snd_ump_midi1_msg_cc {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 index:8;
+       u32 data:8;
+#else
+       u32 data:8;
+       u32 index:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+#endif
+} __packed;
+
+/* MIDI 1.0 Program Change (32bit) */
+struct snd_ump_midi1_msg_program {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 program:8;
+       u32 reserved:8;
+#else
+#endif
+       u32 reserved:8;
+       u32 program:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+} __packed;
+
+/* MIDI 1.0 Channel Pressure (32bit) */
+struct snd_ump_midi1_msg_caf {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 data:8;
+       u32 reserved:8;
+#else
+       u32 reserved:8;
+       u32 data:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+#endif
+} __packed;
+
+/* MIDI 1.0 Pitch Bend (32bit) */
+struct snd_ump_midi1_msg_pitchbend {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 data_lsb:8;
+       u32 data_msb:8;
+#else
+       u32 data_msb:8;
+       u32 data_lsb:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+#endif
+} __packed;
+
+/* System Common and Real Time messages (32bit); no channel field */
+struct snd_ump_system_msg {
+#ifdef __BIG_ENDIAN_BITFIELD
+       u32 type:4;
+       u32 group:4;
+       u32 status:8;
+       u32 parm1:8;
+       u32 parm2:8;
+#else
+       u32 parm2:8;
+       u32 parm1:8;
+       u32 status:8;
+       u32 group:4;
+       u32 type:4;
+#endif
+} __packed;
+
+/* MIDI 1.0 UMP CVM (32bit) */
+union snd_ump_midi1_msg {
+       struct snd_ump_midi1_msg_note note;
+       struct snd_ump_midi1_msg_paf paf;
+       struct snd_ump_midi1_msg_cc cc;
+       struct snd_ump_midi1_msg_program pg;
+       struct snd_ump_midi1_msg_caf caf;
+       struct snd_ump_midi1_msg_pitchbend pb;
+       struct snd_ump_system_msg system;
+       u32 raw;
+};
+
+/* MIDI 2.0 Note Off / Note On (64bit) */
+struct snd_ump_midi2_msg_note {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 attribute_type:8;
+       /* 1 */
+       u32 velocity:16;
+       u32 attribute_data:16;
+#else
+       /* 0 */
+       u32 attribute_type:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 attribute_data:16;
+       u32 velocity:16;
+#endif
+} __packed;
+
+/* MIDI 2.0 Poly Pressure (64bit) */
+struct snd_ump_midi2_msg_paf {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 reserved:8;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 reserved:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 Per-Note Controller (64bit) */
+struct snd_ump_midi2_msg_pernote_cc {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 index:8;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 index:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 Per-Note Management (64bit) */
+struct snd_ump_midi2_msg_pernote_mgmt {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 flags:8;
+       /* 1 */
+       u32 reserved;
+#else
+       /* 0 */
+       u32 flags:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 reserved;
+#endif
+} __packed;
+
+/* MIDI 2.0 Control Change (64bit) */
+struct snd_ump_midi2_msg_cc {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 index:8;
+       u32 reserved:8;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 reserved:8;
+       u32 index:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 Registered Controller (RPN) / Assignable Controller (NRPN) (64bit) */
+struct snd_ump_midi2_msg_rpn {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 bank:8;
+       u32 index:8;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 index:8;
+       u32 bank:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 Program Change (64bit) */
+struct snd_ump_midi2_msg_program {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 reserved:15;
+       u32 bank_valid:1;
+       /* 1 */
+       u32 program:8;
+       u32 reserved2:8;
+       u32 bank_msb:8;
+       u32 bank_lsb:8;
+#else
+       /* 0 */
+       u32 bank_valid:1;
+       u32 reserved:15;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 bank_lsb:8;
+       u32 bank_msb:8;
+       u32 reserved2:8;
+       u32 program:8;
+#endif
+} __packed;
+
+/* MIDI 2.0 Channel Pressure (64bit) */
+struct snd_ump_midi2_msg_caf {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 reserved:16;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 reserved:16;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 Pitch Bend (64bit) */
+struct snd_ump_midi2_msg_pitchbend {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 reserved:16;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 reserved:16;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 Per-Note Pitch Bend (64bit) */
+struct snd_ump_midi2_msg_pernote_pitchbend {
+#ifdef __BIG_ENDIAN_BITFIELD
+       /* 0 */
+       u32 type:4;
+       u32 group:4;
+       u32 status:4;
+       u32 channel:4;
+       u32 note:8;
+       u32 reserved:8;
+       /* 1 */
+       u32 data;
+#else
+       /* 0 */
+       u32 reserved:8;
+       u32 note:8;
+       u32 channel:4;
+       u32 status:4;
+       u32 group:4;
+       u32 type:4;
+       /* 1 */
+       u32 data;
+#endif
+} __packed;
+
+/* MIDI 2.0 UMP CVM (64bit) */
+union snd_ump_midi2_msg {
+       struct snd_ump_midi2_msg_note note;
+       struct snd_ump_midi2_msg_paf paf;
+       struct snd_ump_midi2_msg_pernote_cc pernote_cc;
+       struct snd_ump_midi2_msg_pernote_mgmt pernote_mgmt;
+       struct snd_ump_midi2_msg_cc cc;
+       struct snd_ump_midi2_msg_rpn rpn;
+       struct snd_ump_midi2_msg_program pg;
+       struct snd_ump_midi2_msg_caf caf;
+       struct snd_ump_midi2_msg_pitchbend pb;
+       struct snd_ump_midi2_msg_pernote_pitchbend pernote_pb;
+       u32 raw[2];
+};
+
+#endif /* __SOUND_UMP_MSG_H */
 
        tristate
        select SND_RAWMIDI
 
+config SND_UMP_LEGACY_RAWMIDI
+       bool "Legacy raw MIDI support for UMP streams"
+       depends on SND_UMP
+       help
+         This option enables the legacy raw MIDI support for UMP streams.
+         When this option is set, an additional rawmidi device for the
+         legacy MIDI 1.0 byte streams is created for each UMP Endpoint.
+         The device contains 16 substreams corresponding to UMP groups.
+
 config SND_COMPRESS_OFFLOAD
        tristate
 
 
 snd-ctl-led-objs  := control_led.o
 snd-rawmidi-objs  := rawmidi.o
 snd-ump-objs      := ump.o
+snd-ump-$(CONFIG_SND_UMP_LEGACY_RAWMIDI) += ump_convert.o
 snd-timer-objs    := timer.o
 snd-hrtimer-objs  := hrtimer.o
 snd-rtctimer-objs := rtctimer.o
 
 #include <sound/core.h>
 #include <sound/rawmidi.h>
 #include <sound/ump.h>
+#include "ump_convert.h"
 
 #define ump_err(ump, fmt, args...)     dev_err(&(ump)->core.dev, fmt, ##args)
 #define ump_warn(ump, fmt, args...)    dev_warn(&(ump)->core.dev, fmt, ##args)
                                    int up);
 static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
 
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+static int process_legacy_output(struct snd_ump_endpoint *ump,
+                                u32 *buffer, int count);
+static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
+                                int words);
+#else
+static inline int process_legacy_output(struct snd_ump_endpoint *ump,
+                                       u32 *buffer, int count)
+{
+       return 0;
+}
+static inline void process_legacy_input(struct snd_ump_endpoint *ump,
+                                       const u32 *src, int words)
+{
+}
+#endif
+
 static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
        .dev_register = snd_ump_dev_register,
        .dev_unregister = snd_ump_dev_unregister,
 
        if (ump->private_free)
                ump->private_free(ump);
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+       snd_ump_convert_free(ump);
+#endif
 }
 
 /**
        if (!ump)
                return -ENOMEM;
        INIT_LIST_HEAD(&ump->block_list);
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+       mutex_init(&ump->open_mutex);
+       spin_lock_init(&ump->legacy_locks[0]);
+       spin_lock_init(&ump->legacy_locks[1]);
+#endif
        err = snd_rawmidi_init(&ump->core, card, id, device,
                               output, input, info_flags);
        if (err < 0) {
                ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
 }
 
+/* number of 32bit words per message type */
+static unsigned char ump_packet_words[0x10] = {
+       1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
+};
+
+/* parse the UMP packet data;
+ * the data is copied onto ump->input_buf[].
+ * When a full packet is completed, returns the number of words (from 1 to 4).
+ * OTOH, if the packet is incomplete, returns 0.
+ */
+static int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)
+{
+       int words;
+
+       if (!ump->input_pending)
+               ump->input_pending = ump_packet_words[ump_message_type(val)];
+
+       ump->input_buf[ump->input_buf_head++] = val;
+       ump->input_pending--;
+       if (!ump->input_pending) {
+               words = ump->input_buf_head;
+               ump->input_buf_head = 0;
+               return words;
+       }
+       return 0;
+}
+
 /**
  * snd_ump_receive - transfer UMP packets from the device
  * @ump: the UMP endpoint
  */
 int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
 {
-       struct snd_rawmidi_substream *substream =
-               ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
+       struct snd_rawmidi_substream *substream;
+       const u32 *p = buffer;
+       int n, words = count >> 2;
+
+       while (words--) {
+               n = snd_ump_receive_ump_val(ump, *p++);
+               if (!n)
+                       continue;
+               process_legacy_input(ump, ump->input_buf, n);
+       }
 
+       substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
        if (!substream)
                return 0;
        return snd_rawmidi_receive(substream, (const char *)buffer, count);
 {
        struct snd_rawmidi_substream *substream =
                ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+       int err;
 
        if (!substream)
                return -ENODEV;
-       return snd_rawmidi_transmit(substream, (char *)buffer, count);
+       err = snd_rawmidi_transmit(substream, (char *)buffer, count);
+       /* received either data or an error? */
+       if (err)
+               return err;
+       return process_legacy_output(ump, buffer, count);
 }
 EXPORT_SYMBOL_GPL(snd_ump_transmit);
 
        }
 }
 
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+/*
+ * Legacy rawmidi support
+ */
+static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)
+{
+       struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+       int dir = substream->stream;
+       int group = substream->number;
+       int err;
+
+       mutex_lock(&ump->open_mutex);
+       if (ump->legacy_substreams[dir][group]) {
+               err = -EBUSY;
+               goto unlock;
+       }
+       if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+               if (!ump->legacy_out_opens) {
+                       err = snd_rawmidi_kernel_open(&ump->core, 0,
+                                                     SNDRV_RAWMIDI_LFLG_OUTPUT |
+                                                     SNDRV_RAWMIDI_LFLG_APPEND,
+                                                     &ump->legacy_out_rfile);
+                       if (err < 0)
+                               goto unlock;
+               }
+               ump->legacy_out_opens++;
+               snd_ump_reset_convert_to_ump(ump, group);
+       }
+       spin_lock_irq(&ump->legacy_locks[dir]);
+       ump->legacy_substreams[dir][group] = substream;
+       spin_unlock_irq(&ump->legacy_locks[dir]);
+ unlock:
+       mutex_unlock(&ump->open_mutex);
+       return 0;
+}
+
+static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)
+{
+       struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+       int dir = substream->stream;
+       int group = substream->number;
+
+       mutex_lock(&ump->open_mutex);
+       spin_lock_irq(&ump->legacy_locks[dir]);
+       ump->legacy_substreams[dir][group] = NULL;
+       spin_unlock_irq(&ump->legacy_locks[dir]);
+       if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
+               if (!--ump->legacy_out_opens)
+                       snd_rawmidi_kernel_release(&ump->legacy_out_rfile);
+       }
+       mutex_unlock(&ump->open_mutex);
+       return 0;
+}
+
+static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,
+                                  int up)
+{
+       struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+       int dir = substream->stream;
+
+       ump->ops->trigger(ump, dir, up);
+}
+
+static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)
+{
+       struct snd_ump_endpoint *ump = substream->rmidi->private_data;
+
+       if (ump->ops->drain)
+               ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
+}
+
+static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)
+{
+       /* dummy, just for avoiding create superfluous seq clients */
+       return 0;
+}
+
+static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {
+       .open = snd_ump_legacy_open,
+       .close = snd_ump_legacy_close,
+       .trigger = snd_ump_legacy_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {
+       .open = snd_ump_legacy_open,
+       .close = snd_ump_legacy_close,
+       .trigger = snd_ump_legacy_trigger,
+       .drain = snd_ump_legacy_drain,
+};
+
+static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {
+       .dev_register = snd_ump_legacy_dev_register,
+};
+
+static int process_legacy_output(struct snd_ump_endpoint *ump,
+                                u32 *buffer, int count)
+{
+       struct snd_rawmidi_substream *substream;
+       struct ump_cvt_to_ump *ctx;
+       const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;
+       unsigned char c;
+       int group, size = 0;
+       unsigned long flags;
+
+       if (!ump->out_cvts || !ump->legacy_out_opens)
+               return 0;
+
+       spin_lock_irqsave(&ump->legacy_locks[dir], flags);
+       for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {
+               substream = ump->legacy_substreams[dir][group];
+               if (!substream)
+                       continue;
+               ctx = &ump->out_cvts[group];
+               while (!ctx->ump_bytes &&
+                      snd_rawmidi_transmit(substream, &c, 1) > 0)
+                       snd_ump_convert_to_ump(ump, group, c);
+               if (ctx->ump_bytes && ctx->ump_bytes <= count) {
+                       size = ctx->ump_bytes;
+                       memcpy(buffer, ctx->ump, size);
+                       ctx->ump_bytes = 0;
+                       break;
+               }
+       }
+       spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
+       return size;
+}
+
+static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
+                                int words)
+{
+       struct snd_rawmidi_substream *substream;
+       unsigned char buf[16];
+       unsigned char group;
+       unsigned long flags;
+       const int dir = SNDRV_RAWMIDI_STREAM_INPUT;
+       int size;
+
+       size = snd_ump_convert_from_ump(ump, src, buf, &group);
+       if (size <= 0)
+               return;
+       spin_lock_irqsave(&ump->legacy_locks[dir], flags);
+       substream = ump->legacy_substreams[dir][group];
+       if (substream)
+               snd_rawmidi_receive(substream, buf, size);
+       spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
+}
+
+int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
+                                 char *id, int device)
+{
+       struct snd_rawmidi *rmidi;
+       bool input, output;
+       int err;
+
+       err = snd_ump_convert_init(ump);
+       if (err < 0)
+               return err;
+
+       input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;
+       output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;
+       err = snd_rawmidi_new(ump->core.card, id, device,
+                             output ? 16 : 0, input ? 16 : 0,
+                             &rmidi);
+       if (err < 0) {
+               snd_ump_convert_free(ump);
+               return err;
+       }
+
+       if (input)
+               snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+                                   &snd_ump_legacy_input_ops);
+       if (output)
+               snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+                                   &snd_ump_legacy_output_ops);
+       rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
+       rmidi->ops = &snd_ump_legacy_ops;
+       rmidi->private_data = ump;
+       ump->legacy_rmidi = rmidi;
+       ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);
+#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */
+
 MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");
 MODULE_LICENSE("GPL");
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Helpers for UMP <-> MIDI 1.0 byte stream conversion
+ */
+
+#include <linux/module.h>
+#include <linux/export.h>
+#include <sound/core.h>
+#include <sound/asound.h>
+#include <sound/ump.h>
+#include "ump_convert.h"
+
+/*
+ * Upgrade / downgrade value bits
+ */
+static u8 downscale_32_to_7bit(u32 src)
+{
+       return src >> 25;
+}
+
+static u16 downscale_32_to_14bit(u32 src)
+{
+       return src >> 18;
+}
+
+static u8 downscale_16_to_7bit(u16 src)
+{
+       return src >> 9;
+}
+
+static u16 upscale_7_to_16bit(u8 src)
+{
+       u16 val, repeat;
+
+       val = (u16)src << 9;
+       if (src <= 0x40)
+               return val;
+       repeat = src & 0x3f;
+       return val | (repeat << 3) | (repeat >> 3);
+}
+
+static u32 upscale_7_to_32bit(u8 src)
+{
+       u32 val, repeat;
+
+       val = src << 25;
+       if (src <= 0x40)
+               return val;
+       repeat = src & 0x3f;
+       return val | (repeat << 19) | (repeat << 13) |
+               (repeat << 7) | (repeat << 1) | (repeat >> 5);
+}
+
+static u32 upscale_14_to_32bit(u16 src)
+{
+       u32 val, repeat;
+
+       val = src << 18;
+       if (src <= 0x2000)
+               return val;
+       repeat = src & 0x1fff;
+       return val | (repeat << 5) | (repeat >> 8);
+}
+
+/*
+ * UMP -> MIDI 1 byte stream conversion
+ */
+/* convert a UMP System message to MIDI 1.0 byte stream */
+static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf)
+{
+       buf[0] = ump_message_status_channel(data);
+       switch (ump_message_status_code(data)) {
+       case UMP_SYSTEM_STATUS_MIDI_TIME_CODE:
+       case UMP_SYSTEM_STATUS_SONG_SELECT:
+               buf[1] = (data >> 8) & 0x7f;
+               return 1;
+       case UMP_SYSTEM_STATUS_SONG_POSITION:
+               buf[1] = (data >> 8) & 0x7f;
+               buf[2] = data & 0x7f;
+               return 3;
+       default:
+               return 1;
+       }
+}
+
+/* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */
+static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf)
+{
+       buf[0] = ump_message_status_channel(data);
+       buf[1] = (data >> 8) & 0xff;
+       switch (ump_message_status_code(data)) {
+       case UMP_MSG_STATUS_PROGRAM:
+       case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+               return 2;
+       default:
+               buf[2] = data & 0xff;
+               return 3;
+       }
+}
+
+/* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */
+static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2,
+                                  unsigned char *buf)
+{
+       unsigned char status = midi2->note.status;
+       unsigned char channel = midi2->note.channel;
+       u16 v;
+
+       buf[0] = (status << 4) | channel;
+       switch (status) {
+       case UMP_MSG_STATUS_NOTE_OFF:
+       case UMP_MSG_STATUS_NOTE_ON:
+               buf[1] = midi2->note.note;
+               buf[2] = downscale_16_to_7bit(midi2->note.velocity);
+               if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
+                       buf[2] = 1;
+               return 3;
+       case UMP_MSG_STATUS_POLY_PRESSURE:
+               buf[1] = midi2->paf.note;
+               buf[2] = downscale_32_to_7bit(midi2->paf.data);
+               return 3;
+       case UMP_MSG_STATUS_CC:
+               buf[1] = midi2->cc.index;
+               buf[2] = downscale_32_to_7bit(midi2->cc.data);
+               return 3;
+       case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+               buf[1] = downscale_32_to_7bit(midi2->caf.data);
+               return 2;
+       case UMP_MSG_STATUS_PROGRAM:
+               if (midi2->pg.bank_valid) {
+                       buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
+                       buf[1] = UMP_CC_BANK_SELECT;
+                       buf[2] = midi2->pg.bank_msb;
+                       buf[3] = channel | (UMP_MSG_STATUS_CC << 4);
+                       buf[4] = UMP_CC_BANK_SELECT_LSB;
+                       buf[5] = midi2->pg.bank_lsb;
+                       buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4);
+                       buf[7] = midi2->pg.program;
+                       return 8;
+               }
+               buf[1] = midi2->pg.program;
+               return 2;
+       case UMP_MSG_STATUS_PITCH_BEND:
+               v = downscale_32_to_14bit(midi2->pb.data);
+               buf[1] = v & 0x7f;
+               buf[2] = v >> 7;
+               return 3;
+       case UMP_MSG_STATUS_RPN:
+       case UMP_MSG_STATUS_NRPN:
+               buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
+               buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
+               buf[2] = midi2->rpn.bank;
+               buf[3] = buf[0];
+               buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
+               buf[5] = midi2->rpn.index;
+               buf[6] = buf[0];
+               buf[7] = UMP_CC_DATA;
+               v = downscale_32_to_14bit(midi2->rpn.data);
+               buf[8] = v >> 7;
+               buf[9] = buf[0];
+               buf[10] = UMP_CC_DATA_LSB;
+               buf[11] = v & 0x7f;
+               return 12;
+       default:
+               return 0;
+       }
+}
+
+/* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */
+static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf)
+{
+       unsigned char status;
+       unsigned char bytes;
+       int size, offset;
+
+       status = ump_sysex_message_status(*data);
+       if (status > UMP_SYSEX_STATUS_END)
+               return 0; // unsupported, skip
+       bytes = ump_sysex_message_length(*data);
+       if (bytes > 6)
+               return 0; // skip
+
+       size = 0;
+       if (status == UMP_SYSEX_STATUS_SINGLE ||
+           status == UMP_SYSEX_STATUS_START) {
+               buf[0] = UMP_MIDI1_MSG_SYSEX_START;
+               size = 1;
+       }
+
+       offset = 8;
+       for (; bytes; bytes--, size++) {
+               buf[size] = (*data >> offset) & 0x7f;
+               if (!offset) {
+                       offset = 24;
+                       data++;
+               } else {
+                       offset -= 8;
+               }
+       }
+
+       if (status == UMP_SYSEX_STATUS_SINGLE ||
+           status == UMP_SYSEX_STATUS_END)
+               buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
+
+       return size;
+}
+
+/* convert from a UMP packet @data to MIDI 1.0 bytes at @buf;
+ * the target group is stored at @group_ret,
+ * returns the number of bytes of MIDI 1.0 stream
+ */
+int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump,
+                            const u32 *data,
+                            unsigned char *buf,
+                            unsigned char *group_ret)
+{
+       *group_ret = ump_message_group(*data);
+
+       switch (ump_message_type(*data)) {
+       case UMP_MSG_TYPE_SYSTEM:
+               return cvt_ump_system_to_legacy(*data, buf);
+       case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
+               return cvt_ump_midi1_to_legacy(*data, buf);
+       case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
+               return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data,
+                                              buf);
+       case UMP_MSG_TYPE_DATA:
+               return cvt_ump_sysex7_to_legacy(data, buf);
+       }
+
+       return 0;
+}
+
+/*
+ * MIDI 1 byte stream -> UMP conversion
+ */
+/* convert MIDI 1.0 SysEx to a UMP packet */
+static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt,
+                                  unsigned char group, u32 *data, bool finish)
+{
+       unsigned char status;
+       bool start = cvt->in_sysex == 1;
+       int i, offset;
+
+       if (start && finish)
+               status = UMP_SYSEX_STATUS_SINGLE;
+       else if (start)
+               status = UMP_SYSEX_STATUS_START;
+       else if (finish)
+               status = UMP_SYSEX_STATUS_END;
+       else
+               status = UMP_SYSEX_STATUS_CONTINUE;
+       *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len);
+       offset = 8;
+       for (i = 0; i < cvt->len; i++) {
+               *data |= cvt->buf[i] << offset;
+               if (!offset) {
+                       offset = 24;
+                       data++;
+               } else
+                       offset -= 8;
+       }
+       cvt->len = 0;
+       if (finish)
+               cvt->in_sysex = 0;
+       else
+               cvt->in_sysex++;
+       return 8;
+}
+
+/* convert to a UMP System message */
+static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt,
+                                   unsigned char group, u32 *data)
+{
+       data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]);
+       if (cvt->cmd_bytes > 1)
+               data[0] |= cvt->buf[1] << 8;
+       if (cvt->cmd_bytes > 2)
+               data[0] |= cvt->buf[2];
+       return 4;
+}
+
+static void fill_rpn(struct ump_cvt_to_ump_bank *cc,
+                    union snd_ump_midi2_msg *midi2)
+{
+       if (cc->rpn_set) {
+               midi2->rpn.status = UMP_MSG_STATUS_RPN;
+               midi2->rpn.bank = cc->cc_rpn_msb;
+               midi2->rpn.index = cc->cc_rpn_lsb;
+               cc->rpn_set = 0;
+               cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
+       } else {
+               midi2->rpn.status = UMP_MSG_STATUS_NRPN;
+               midi2->rpn.bank = cc->cc_nrpn_msb;
+               midi2->rpn.index = cc->cc_nrpn_lsb;
+               cc->nrpn_set = 0;
+               cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
+       }
+       midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
+                                             cc->cc_data_lsb);
+       cc->cc_data_msb = cc->cc_data_lsb = 0;
+}
+
+/* convert to a MIDI 1.0 Channel Voice message */
+static int cvt_legacy_cmd_to_ump(struct snd_ump_endpoint *ump,
+                                struct ump_cvt_to_ump *cvt,
+                                unsigned char group, u32 *data,
+                                unsigned char bytes)
+{
+       const unsigned char *buf = cvt->buf;
+       struct ump_cvt_to_ump_bank *cc;
+       union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data;
+       unsigned char status, channel;
+
+       BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4);
+       BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8);
+
+       /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */
+       if (ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) {
+               data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE,
+                                     group, 0, buf[0]);
+               data[0] |= buf[1] << 8;
+               if (bytes > 2)
+                       data[0] |= buf[2];
+               return 4;
+       }
+
+       status = *buf >> 4;
+       channel = *buf & 0x0f;
+       cc = &cvt->bank[channel];
+
+       /* special handling: treat note-on with 0 velocity as note-off */
+       if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
+               status = UMP_MSG_STATUS_NOTE_OFF;
+
+       /* initialize the packet */
+       data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE,
+                             group, status, channel);
+       data[1] = 0;
+
+       switch (status) {
+       case UMP_MSG_STATUS_NOTE_ON:
+               if (!buf[2])
+                       status = UMP_MSG_STATUS_NOTE_OFF;
+               fallthrough;
+       case UMP_MSG_STATUS_NOTE_OFF:
+               midi2->note.note = buf[1];
+               midi2->note.velocity = upscale_7_to_16bit(buf[2]);
+               break;
+       case UMP_MSG_STATUS_POLY_PRESSURE:
+               midi2->paf.note = buf[1];
+               midi2->paf.data = upscale_7_to_32bit(buf[2]);
+               break;
+       case UMP_MSG_STATUS_CC:
+               switch (buf[1]) {
+               case UMP_CC_RPN_MSB:
+                       cc->rpn_set = 1;
+                       cc->cc_rpn_msb = buf[2];
+                       return 0; // skip
+               case UMP_CC_RPN_LSB:
+                       cc->rpn_set = 1;
+                       cc->cc_rpn_lsb = buf[2];
+                       return 0; // skip
+               case UMP_CC_NRPN_MSB:
+                       cc->nrpn_set = 1;
+                       cc->cc_nrpn_msb = buf[2];
+                       return 0; // skip
+               case UMP_CC_NRPN_LSB:
+                       cc->nrpn_set = 1;
+                       cc->cc_nrpn_lsb = buf[2];
+                       return 0; // skip
+               case UMP_CC_DATA:
+                       cc->cc_data_msb = buf[2];
+                       return 0; // skip
+               case UMP_CC_BANK_SELECT:
+                       cc->bank_set = 1;
+                       cc->cc_bank_msb = buf[2];
+                       return 0; // skip
+               case UMP_CC_BANK_SELECT_LSB:
+                       cc->bank_set = 1;
+                       cc->cc_bank_lsb = buf[2];
+                       return 0; // skip
+               case UMP_CC_DATA_LSB:
+                       cc->cc_data_lsb = buf[2];
+                       if (cc->rpn_set || cc->nrpn_set)
+                               fill_rpn(cc, midi2);
+                       else
+                               return 0; // skip
+                       break;
+               default:
+                       midi2->cc.index = buf[1];
+                       midi2->cc.data = upscale_7_to_32bit(buf[2]);
+                       break;
+               }
+               break;
+       case UMP_MSG_STATUS_PROGRAM:
+               midi2->pg.program = buf[1];
+               if (cc->bank_set) {
+                       midi2->pg.bank_valid = 1;
+                       midi2->pg.bank_msb = cc->cc_bank_msb;
+                       midi2->pg.bank_lsb = cc->cc_bank_lsb;
+                       cc->bank_set = 0;
+                       cc->cc_bank_msb = cc->cc_bank_lsb = 0;
+               }
+               break;
+       case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+               midi2->caf.data = upscale_7_to_32bit(buf[1]);
+               break;
+       case UMP_MSG_STATUS_PITCH_BEND:
+               midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7));
+               break;
+       default:
+               return 0;
+       }
+
+       return 8;
+}
+
+static int do_convert_to_ump(struct snd_ump_endpoint *ump,
+                            unsigned char group, unsigned char c, u32 *data)
+{
+       /* bytes for 0x80-0xf0 */
+       static unsigned char cmd_bytes[8] = {
+               3, 3, 3, 3, 2, 2, 3, 0
+       };
+       /* bytes for 0xf0-0xff */
+       static unsigned char system_bytes[16] = {
+               0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1
+       };
+       struct ump_cvt_to_ump *cvt = &ump->out_cvts[group];
+       unsigned char bytes;
+
+       if (c == UMP_MIDI1_MSG_SYSEX_START) {
+               cvt->in_sysex = 1;
+               cvt->len = 0;
+               return 0;
+       }
+       if (c == UMP_MIDI1_MSG_SYSEX_END) {
+               if (!cvt->in_sysex)
+                       return 0; /* skip */
+               return cvt_legacy_sysex_to_ump(cvt, group, data, true);
+       }
+
+       if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) {
+               bytes = system_bytes[c & 0x0f];
+               if (!bytes)
+                       return 0; /* skip */
+               if (bytes == 1) {
+                       data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c);
+                       return 4;
+               }
+               cvt->buf[0] = c;
+               cvt->len = 1;
+               cvt->cmd_bytes = bytes;
+               cvt->in_sysex = 0; /* abort SysEx */
+               return 0;
+       }
+
+       if (c & 0x80) {
+               bytes = cmd_bytes[(c >> 8) & 7];
+               cvt->buf[0] = c;
+               cvt->len = 1;
+               cvt->cmd_bytes = bytes;
+               cvt->in_sysex = 0; /* abort SysEx */
+               return 0;
+       }
+
+       if (cvt->in_sysex) {
+               cvt->buf[cvt->len++] = c;
+               if (cvt->len == 6)
+                       return cvt_legacy_sysex_to_ump(cvt, group, data, false);
+               return 0;
+       }
+
+       if (!cvt->len)
+               return 0;
+
+       cvt->buf[cvt->len++] = c;
+       if (cvt->len < cvt->cmd_bytes)
+               return 0;
+       cvt->len = 1;
+       if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME)
+               return cvt_legacy_system_to_ump(cvt, group, data);
+       return cvt_legacy_cmd_to_ump(ump, cvt, group, data, cvt->cmd_bytes);
+}
+
+/* feed a MIDI 1.0 byte @c and convert to a UMP packet;
+ * the target group is @group,
+ * the result is stored in out_cvts[group].ump[] and out_cvts[group].ump_bytes
+ */
+void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump,
+                           unsigned char group, unsigned char c)
+{
+       struct ump_cvt_to_ump *cvt = &ump->out_cvts[group];
+
+       cvt->ump_bytes = do_convert_to_ump(ump, group, c, cvt->ump);
+}
+
+/* reset the converter context, called at each open */
+void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump,
+                                 unsigned char group)
+{
+       memset(&ump->out_cvts[group], 0, sizeof(*ump->out_cvts));
+}
+
+/* initialize converters */
+int snd_ump_convert_init(struct snd_ump_endpoint *ump)
+{
+       ump->out_cvts = kcalloc(16, sizeof(*ump->out_cvts), GFP_KERNEL);
+       if (!ump->out_cvts)
+               return -ENOMEM;
+       return 0;
+}
+
+/* release resources */
+void snd_ump_convert_free(struct snd_ump_endpoint *ump)
+{
+       kfree(ump->out_cvts);
+       ump->out_cvts = NULL;
+}
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __UMP_CONVERT_H
+#define __UMP_CONVERT_H
+
+#include <sound/ump_msg.h>
+
+/* context for converting from legacy control messages to UMP packet */
+struct ump_cvt_to_ump_bank {
+       bool rpn_set;
+       bool nrpn_set;
+       bool bank_set;
+       unsigned char cc_rpn_msb, cc_rpn_lsb;
+       unsigned char cc_nrpn_msb, cc_nrpn_lsb;
+       unsigned char cc_data_msb, cc_data_lsb;
+       unsigned char cc_bank_msb, cc_bank_lsb;
+};
+
+/* context for converting from MIDI1 byte stream to UMP packet */
+struct ump_cvt_to_ump {
+       /* MIDI1 intermediate buffer */
+       unsigned char buf[4];
+       int len;
+       int cmd_bytes;
+
+       /* UMP output packet */
+       u32 ump[4];
+       int ump_bytes;
+
+       /* various status */
+       unsigned int in_sysex;
+       struct ump_cvt_to_ump_bank bank[16];    /* per channel */
+};
+
+int snd_ump_convert_init(struct snd_ump_endpoint *ump);
+void snd_ump_convert_free(struct snd_ump_endpoint *ump);
+int snd_ump_convert_from_ump(struct snd_ump_endpoint *ump,
+                            const u32 *data, unsigned char *dst,
+                            unsigned char *group_ret);
+void snd_ump_convert_to_ump(struct snd_ump_endpoint *ump,
+                           unsigned char group, unsigned char c);
+void snd_ump_reset_convert_to_ump(struct snd_ump_endpoint *ump,
+                                 unsigned char group);
+#endif /* __UMP_CONVERT_H */