diff --git a/include/sound/ump.h b/include/sound/ump.h
index 6f786b462f16..45f4c9b673b5 100644
--- a/include/sound/ump.h
+++ b/include/sound/ump.h
@@ -10,6 +10,7 @@
 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 */
@@ -23,6 +24,24 @@ struct snd_ump_endpoint {
 	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 */
@@ -54,6 +73,17 @@ int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
 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
  */
diff --git a/include/sound/ump_msg.h b/include/sound/ump_msg.h
new file mode 100644
index 000000000000..c76c39944a5f
--- /dev/null
+++ b/include/sound/ump_msg.h
@@ -0,0 +1,540 @@
+// 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 */
diff --git a/sound/core/Kconfig b/sound/core/Kconfig
index eb1c6c930de9..e41818e59a15 100644
--- a/sound/core/Kconfig
+++ b/sound/core/Kconfig
@@ -30,6 +30,15 @@ config SND_UMP
 	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
 
diff --git a/sound/core/Makefile b/sound/core/Makefile
index 562a05edbc50..a6b444ee2832 100644
--- a/sound/core/Makefile
+++ b/sound/core/Makefile
@@ -29,6 +29,7 @@ snd-pcm-dmaengine-objs := pcm_dmaengine.o
 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
diff --git a/sound/core/ump.c b/sound/core/ump.c
index 0d5ca95cd007..176789090896 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -11,6 +11,7 @@
 #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)
@@ -29,6 +30,23 @@ static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
 				    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,
@@ -65,6 +83,10 @@ static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
 
 	if (ump->private_free)
 		ump->private_free(ump);
+
+#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
+	snd_ump_convert_free(ump);
+#endif
 }
 
 /**
@@ -110,6 +132,11 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
 	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) {
@@ -206,6 +233,33 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
 		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
@@ -218,9 +272,18 @@ static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
  */
 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);
@@ -241,10 +304,15 @@ int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int 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);
 
@@ -387,5 +455,197 @@ static void snd_ump_proc_read(struct snd_info_entry *entry,
 	}
 }
 
+#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)
+{
+	unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP;
+	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;
+	if (input)
+		info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	if (output)
+		info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+	if (input && output)
+		info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	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");
diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c
new file mode 100644
index 000000000000..cb7c2f959a27
--- /dev/null
+++ b/sound/core/ump_convert.c
@@ -0,0 +1,520 @@
+// 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;
+}
diff --git a/sound/core/ump_convert.h b/sound/core/ump_convert.h
new file mode 100644
index 000000000000..bbfe96084779
--- /dev/null
+++ b/sound/core/ump_convert.h
@@ -0,0 +1,43 @@
+// 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 */
