162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Universal MIDI Packet (UMP) support
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/list.h>
762306a36Sopenharmony_ci#include <linux/slab.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/export.h>
1062306a36Sopenharmony_ci#include <linux/mm.h>
1162306a36Sopenharmony_ci#include <sound/core.h>
1262306a36Sopenharmony_ci#include <sound/rawmidi.h>
1362306a36Sopenharmony_ci#include <sound/ump.h>
1462306a36Sopenharmony_ci#include <sound/ump_convert.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define ump_err(ump, fmt, args...)	dev_err((ump)->core.dev, fmt, ##args)
1762306a36Sopenharmony_ci#define ump_warn(ump, fmt, args...)	dev_warn((ump)->core.dev, fmt, ##args)
1862306a36Sopenharmony_ci#define ump_info(ump, fmt, args...)	dev_info((ump)->core.dev, fmt, ##args)
1962306a36Sopenharmony_ci#define ump_dbg(ump, fmt, args...)	dev_dbg((ump)->core.dev, fmt, ##args)
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic int snd_ump_dev_register(struct snd_rawmidi *rmidi);
2262306a36Sopenharmony_cistatic int snd_ump_dev_unregister(struct snd_rawmidi *rmidi);
2362306a36Sopenharmony_cistatic long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
2462306a36Sopenharmony_ci			  void __user *argp);
2562306a36Sopenharmony_cistatic void snd_ump_proc_read(struct snd_info_entry *entry,
2662306a36Sopenharmony_ci			      struct snd_info_buffer *buffer);
2762306a36Sopenharmony_cistatic int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream);
2862306a36Sopenharmony_cistatic int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream);
2962306a36Sopenharmony_cistatic void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
3062306a36Sopenharmony_ci				    int up);
3162306a36Sopenharmony_cistatic void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic void ump_handle_stream_msg(struct snd_ump_endpoint *ump,
3462306a36Sopenharmony_ci				  const u32 *buf, int size);
3562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
3662306a36Sopenharmony_cistatic int process_legacy_output(struct snd_ump_endpoint *ump,
3762306a36Sopenharmony_ci				 u32 *buffer, int count);
3862306a36Sopenharmony_cistatic void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
3962306a36Sopenharmony_ci				 int words);
4062306a36Sopenharmony_ci#else
4162306a36Sopenharmony_cistatic inline int process_legacy_output(struct snd_ump_endpoint *ump,
4262306a36Sopenharmony_ci					u32 *buffer, int count)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	return 0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_cistatic inline void process_legacy_input(struct snd_ump_endpoint *ump,
4762306a36Sopenharmony_ci					const u32 *src, int words)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci#endif
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
5362306a36Sopenharmony_ci	.dev_register = snd_ump_dev_register,
5462306a36Sopenharmony_ci	.dev_unregister = snd_ump_dev_unregister,
5562306a36Sopenharmony_ci	.ioctl = snd_ump_ioctl,
5662306a36Sopenharmony_ci	.proc_read = snd_ump_proc_read,
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = {
6062306a36Sopenharmony_ci	.open = snd_ump_rawmidi_open,
6162306a36Sopenharmony_ci	.close = snd_ump_rawmidi_close,
6262306a36Sopenharmony_ci	.trigger = snd_ump_rawmidi_trigger,
6362306a36Sopenharmony_ci};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = {
6662306a36Sopenharmony_ci	.open = snd_ump_rawmidi_open,
6762306a36Sopenharmony_ci	.close = snd_ump_rawmidi_close,
6862306a36Sopenharmony_ci	.trigger = snd_ump_rawmidi_trigger,
6962306a36Sopenharmony_ci	.drain = snd_ump_rawmidi_drain,
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
7562306a36Sopenharmony_ci	struct snd_ump_block *fb;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	while (!list_empty(&ump->block_list)) {
7862306a36Sopenharmony_ci		fb = list_first_entry(&ump->block_list, struct snd_ump_block,
7962306a36Sopenharmony_ci				      list);
8062306a36Sopenharmony_ci		list_del(&fb->list);
8162306a36Sopenharmony_ci		if (fb->private_free)
8262306a36Sopenharmony_ci			fb->private_free(fb);
8362306a36Sopenharmony_ci		kfree(fb);
8462306a36Sopenharmony_ci	}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (ump->private_free)
8762306a36Sopenharmony_ci		ump->private_free(ump);
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
9062306a36Sopenharmony_ci	kfree(ump->out_cvts);
9162306a36Sopenharmony_ci#endif
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci/**
9562306a36Sopenharmony_ci * snd_ump_endpoint_new - create a UMP Endpoint object
9662306a36Sopenharmony_ci * @card: the card instance
9762306a36Sopenharmony_ci * @id: the id string for rawmidi
9862306a36Sopenharmony_ci * @device: the device index for rawmidi
9962306a36Sopenharmony_ci * @output: 1 for enabling output
10062306a36Sopenharmony_ci * @input: 1 for enabling input
10162306a36Sopenharmony_ci * @ump_ret: the pointer to store the new UMP instance
10262306a36Sopenharmony_ci *
10362306a36Sopenharmony_ci * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi
10462306a36Sopenharmony_ci * instance with one input and/or one output rawmidi stream (either uni-
10562306a36Sopenharmony_ci * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks
10662306a36Sopenharmony_ci * that consist of one or multiple UMP Groups.
10762306a36Sopenharmony_ci *
10862306a36Sopenharmony_ci * Use snd_rawmidi_set_ops() to set the operators to the new instance.
10962306a36Sopenharmony_ci * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself
11062306a36Sopenharmony_ci * depending on the given @output and @input.
11162306a36Sopenharmony_ci *
11262306a36Sopenharmony_ci * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device
11362306a36Sopenharmony_ci * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is
11462306a36Sopenharmony_ci * created.
11562306a36Sopenharmony_ci *
11662306a36Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure.
11762306a36Sopenharmony_ci */
11862306a36Sopenharmony_ciint snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
11962306a36Sopenharmony_ci			 int output, int input,
12062306a36Sopenharmony_ci			 struct snd_ump_endpoint **ump_ret)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP;
12362306a36Sopenharmony_ci	struct snd_ump_endpoint *ump;
12462306a36Sopenharmony_ci	int err;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	if (input)
12762306a36Sopenharmony_ci		info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
12862306a36Sopenharmony_ci	if (output)
12962306a36Sopenharmony_ci		info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
13062306a36Sopenharmony_ci	if (input && output)
13162306a36Sopenharmony_ci		info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	ump = kzalloc(sizeof(*ump), GFP_KERNEL);
13462306a36Sopenharmony_ci	if (!ump)
13562306a36Sopenharmony_ci		return -ENOMEM;
13662306a36Sopenharmony_ci	INIT_LIST_HEAD(&ump->block_list);
13762306a36Sopenharmony_ci	mutex_init(&ump->open_mutex);
13862306a36Sopenharmony_ci	init_waitqueue_head(&ump->stream_wait);
13962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
14062306a36Sopenharmony_ci	spin_lock_init(&ump->legacy_locks[0]);
14162306a36Sopenharmony_ci	spin_lock_init(&ump->legacy_locks[1]);
14262306a36Sopenharmony_ci#endif
14362306a36Sopenharmony_ci	err = snd_rawmidi_init(&ump->core, card, id, device,
14462306a36Sopenharmony_ci			       output, input, info_flags);
14562306a36Sopenharmony_ci	if (err < 0) {
14662306a36Sopenharmony_ci		snd_rawmidi_free(&ump->core);
14762306a36Sopenharmony_ci		return err;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	ump->info.card = card->number;
15162306a36Sopenharmony_ci	ump->info.device = device;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	ump->core.private_free = snd_ump_endpoint_free;
15462306a36Sopenharmony_ci	ump->core.ops = &snd_ump_rawmidi_ops;
15562306a36Sopenharmony_ci	if (input)
15662306a36Sopenharmony_ci		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,
15762306a36Sopenharmony_ci				    &snd_ump_rawmidi_input_ops);
15862306a36Sopenharmony_ci	if (output)
15962306a36Sopenharmony_ci		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,
16062306a36Sopenharmony_ci				    &snd_ump_rawmidi_output_ops);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id);
16362306a36Sopenharmony_ci	*ump_ret = ump;
16462306a36Sopenharmony_ci	return 0;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_endpoint_new);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci/*
16962306a36Sopenharmony_ci * Device register / unregister hooks;
17062306a36Sopenharmony_ci *  do nothing, placeholders for avoiding the default rawmidi handling
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
17462306a36Sopenharmony_cistatic void snd_ump_dev_seq_free(struct snd_seq_device *device)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = device->private_data;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	ump->seq_dev = NULL;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci#endif
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic int snd_ump_dev_register(struct snd_rawmidi *rmidi)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
18562306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
18662306a36Sopenharmony_ci	int err;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	err = snd_seq_device_new(ump->core.card, ump->core.device,
18962306a36Sopenharmony_ci				 SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev);
19062306a36Sopenharmony_ci	if (err < 0)
19162306a36Sopenharmony_ci		return err;
19262306a36Sopenharmony_ci	ump->seq_dev->private_data = ump;
19362306a36Sopenharmony_ci	ump->seq_dev->private_free = snd_ump_dev_seq_free;
19462306a36Sopenharmony_ci	snd_device_register(ump->core.card, ump->seq_dev);
19562306a36Sopenharmony_ci#endif
19662306a36Sopenharmony_ci	return 0;
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic int snd_ump_dev_unregister(struct snd_rawmidi *rmidi)
20062306a36Sopenharmony_ci{
20162306a36Sopenharmony_ci	return 0;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic struct snd_ump_block *
20562306a36Sopenharmony_cisnd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	struct snd_ump_block *fb;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	list_for_each_entry(fb, &ump->block_list, list) {
21062306a36Sopenharmony_ci		if (fb->info.block_id == id)
21162306a36Sopenharmony_ci			return fb;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci	return NULL;
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci/*
21762306a36Sopenharmony_ci * rawmidi ops for UMP endpoint
21862306a36Sopenharmony_ci */
21962306a36Sopenharmony_cistatic int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
22262306a36Sopenharmony_ci	int dir = substream->stream;
22362306a36Sopenharmony_ci	int err;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (ump->substreams[dir])
22662306a36Sopenharmony_ci		return -EBUSY;
22762306a36Sopenharmony_ci	err = ump->ops->open(ump, dir);
22862306a36Sopenharmony_ci	if (err < 0)
22962306a36Sopenharmony_ci		return err;
23062306a36Sopenharmony_ci	ump->substreams[dir] = substream;
23162306a36Sopenharmony_ci	return 0;
23262306a36Sopenharmony_ci}
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cistatic int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
23762306a36Sopenharmony_ci	int dir = substream->stream;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	ump->substreams[dir] = NULL;
24062306a36Sopenharmony_ci	ump->ops->close(ump, dir);
24162306a36Sopenharmony_ci	return 0;
24262306a36Sopenharmony_ci}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
24562306a36Sopenharmony_ci				    int up)
24662306a36Sopenharmony_ci{
24762306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
24862306a36Sopenharmony_ci	int dir = substream->stream;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	ump->ops->trigger(ump, dir, up);
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
25462306a36Sopenharmony_ci{
25562306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (ump->ops->drain)
25862306a36Sopenharmony_ci		ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
25962306a36Sopenharmony_ci}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci/* number of 32bit words per message type */
26262306a36Sopenharmony_cistatic unsigned char ump_packet_words[0x10] = {
26362306a36Sopenharmony_ci	1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
26462306a36Sopenharmony_ci};
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci/**
26762306a36Sopenharmony_ci * snd_ump_receive_ump_val - parse the UMP packet data
26862306a36Sopenharmony_ci * @ump: UMP endpoint
26962306a36Sopenharmony_ci * @val: UMP packet data
27062306a36Sopenharmony_ci *
27162306a36Sopenharmony_ci * The data is copied onto ump->input_buf[].
27262306a36Sopenharmony_ci * When a full packet is completed, returns the number of words (from 1 to 4).
27362306a36Sopenharmony_ci * OTOH, if the packet is incomplete, returns 0.
27462306a36Sopenharmony_ci */
27562306a36Sopenharmony_ciint snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	int words;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	if (!ump->input_pending)
28062306a36Sopenharmony_ci		ump->input_pending = ump_packet_words[ump_message_type(val)];
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	ump->input_buf[ump->input_buf_head++] = val;
28362306a36Sopenharmony_ci	ump->input_pending--;
28462306a36Sopenharmony_ci	if (!ump->input_pending) {
28562306a36Sopenharmony_ci		words = ump->input_buf_head;
28662306a36Sopenharmony_ci		ump->input_buf_head = 0;
28762306a36Sopenharmony_ci		return words;
28862306a36Sopenharmony_ci	}
28962306a36Sopenharmony_ci	return 0;
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_receive_ump_val);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci/**
29462306a36Sopenharmony_ci * snd_ump_receive - transfer UMP packets from the device
29562306a36Sopenharmony_ci * @ump: the UMP endpoint
29662306a36Sopenharmony_ci * @buffer: the buffer pointer to transfer
29762306a36Sopenharmony_ci * @count: byte size to transfer
29862306a36Sopenharmony_ci *
29962306a36Sopenharmony_ci * Called from the driver to submit the received UMP packets from the device
30062306a36Sopenharmony_ci * to user-space.  It's essentially a wrapper of rawmidi_receive().
30162306a36Sopenharmony_ci * The data to receive is in CPU-native endianness.
30262306a36Sopenharmony_ci */
30362306a36Sopenharmony_ciint snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
30462306a36Sopenharmony_ci{
30562306a36Sopenharmony_ci	struct snd_rawmidi_substream *substream;
30662306a36Sopenharmony_ci	const u32 *p = buffer;
30762306a36Sopenharmony_ci	int n, words = count >> 2;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	while (words--) {
31062306a36Sopenharmony_ci		n = snd_ump_receive_ump_val(ump, *p++);
31162306a36Sopenharmony_ci		if (!n)
31262306a36Sopenharmony_ci			continue;
31362306a36Sopenharmony_ci		ump_handle_stream_msg(ump, ump->input_buf, n);
31462306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
31562306a36Sopenharmony_ci		if (ump->seq_ops)
31662306a36Sopenharmony_ci			ump->seq_ops->input_receive(ump, ump->input_buf, n);
31762306a36Sopenharmony_ci#endif
31862306a36Sopenharmony_ci		process_legacy_input(ump, ump->input_buf, n);
31962306a36Sopenharmony_ci	}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
32262306a36Sopenharmony_ci	if (!substream)
32362306a36Sopenharmony_ci		return 0;
32462306a36Sopenharmony_ci	return snd_rawmidi_receive(substream, (const char *)buffer, count);
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_receive);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci/**
32962306a36Sopenharmony_ci * snd_ump_transmit - transmit UMP packets
33062306a36Sopenharmony_ci * @ump: the UMP endpoint
33162306a36Sopenharmony_ci * @buffer: the buffer pointer to transfer
33262306a36Sopenharmony_ci * @count: byte size to transfer
33362306a36Sopenharmony_ci *
33462306a36Sopenharmony_ci * Called from the driver to obtain the UMP packets from user-space to the
33562306a36Sopenharmony_ci * device.  It's essentially a wrapper of rawmidi_transmit().
33662306a36Sopenharmony_ci * The data to transmit is in CPU-native endianness.
33762306a36Sopenharmony_ci */
33862306a36Sopenharmony_ciint snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	struct snd_rawmidi_substream *substream =
34162306a36Sopenharmony_ci		ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];
34262306a36Sopenharmony_ci	int err;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	if (!substream)
34562306a36Sopenharmony_ci		return -ENODEV;
34662306a36Sopenharmony_ci	err = snd_rawmidi_transmit(substream, (char *)buffer, count);
34762306a36Sopenharmony_ci	/* received either data or an error? */
34862306a36Sopenharmony_ci	if (err)
34962306a36Sopenharmony_ci		return err;
35062306a36Sopenharmony_ci	return process_legacy_output(ump, buffer, count);
35162306a36Sopenharmony_ci}
35262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_transmit);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci/**
35562306a36Sopenharmony_ci * snd_ump_block_new - Create a UMP block
35662306a36Sopenharmony_ci * @ump: UMP object
35762306a36Sopenharmony_ci * @blk: block ID number to create
35862306a36Sopenharmony_ci * @direction: direction (in/out/bidirection)
35962306a36Sopenharmony_ci * @first_group: the first group ID (0-based)
36062306a36Sopenharmony_ci * @num_groups: the number of groups in this block
36162306a36Sopenharmony_ci * @blk_ret: the pointer to store the resultant block object
36262306a36Sopenharmony_ci */
36362306a36Sopenharmony_ciint snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
36462306a36Sopenharmony_ci		      unsigned int direction, unsigned int first_group,
36562306a36Sopenharmony_ci		      unsigned int num_groups, struct snd_ump_block **blk_ret)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	struct snd_ump_block *fb, *p;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS)
37062306a36Sopenharmony_ci		return -EINVAL;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	if (snd_ump_get_block(ump, blk))
37362306a36Sopenharmony_ci		return -EBUSY;
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	fb = kzalloc(sizeof(*fb), GFP_KERNEL);
37662306a36Sopenharmony_ci	if (!fb)
37762306a36Sopenharmony_ci		return -ENOMEM;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_ci	fb->ump = ump;
38062306a36Sopenharmony_ci	fb->info.card = ump->info.card;
38162306a36Sopenharmony_ci	fb->info.device = ump->info.device;
38262306a36Sopenharmony_ci	fb->info.block_id = blk;
38362306a36Sopenharmony_ci	if (blk >= ump->info.num_blocks)
38462306a36Sopenharmony_ci		ump->info.num_blocks = blk + 1;
38562306a36Sopenharmony_ci	fb->info.direction = direction;
38662306a36Sopenharmony_ci	fb->info.active = 1;
38762306a36Sopenharmony_ci	fb->info.first_group = first_group;
38862306a36Sopenharmony_ci	fb->info.num_groups = num_groups;
38962306a36Sopenharmony_ci	/* fill the default name, may be overwritten to a better name */
39062306a36Sopenharmony_ci	snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d",
39162306a36Sopenharmony_ci		 first_group + 1, first_group + num_groups);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	/* put the entry in the ordered list */
39462306a36Sopenharmony_ci	list_for_each_entry(p, &ump->block_list, list) {
39562306a36Sopenharmony_ci		if (p->info.block_id > blk) {
39662306a36Sopenharmony_ci			list_add_tail(&fb->list, &p->list);
39762306a36Sopenharmony_ci			goto added;
39862306a36Sopenharmony_ci		}
39962306a36Sopenharmony_ci	}
40062306a36Sopenharmony_ci	list_add_tail(&fb->list, &ump->block_list);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci added:
40362306a36Sopenharmony_ci	ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name);
40462306a36Sopenharmony_ci	*blk_ret = fb;
40562306a36Sopenharmony_ci	return 0;
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_block_new);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic int snd_ump_ioctl_block(struct snd_ump_endpoint *ump,
41062306a36Sopenharmony_ci			       struct snd_ump_block_info __user *argp)
41162306a36Sopenharmony_ci{
41262306a36Sopenharmony_ci	struct snd_ump_block *fb;
41362306a36Sopenharmony_ci	unsigned char id;
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	if (get_user(id, &argp->block_id))
41662306a36Sopenharmony_ci		return -EFAULT;
41762306a36Sopenharmony_ci	fb = snd_ump_get_block(ump, id);
41862306a36Sopenharmony_ci	if (!fb)
41962306a36Sopenharmony_ci		return -ENOENT;
42062306a36Sopenharmony_ci	if (copy_to_user(argp, &fb->info, sizeof(fb->info)))
42162306a36Sopenharmony_ci		return -EFAULT;
42262306a36Sopenharmony_ci	return 0;
42362306a36Sopenharmony_ci}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci/*
42662306a36Sopenharmony_ci * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl()
42762306a36Sopenharmony_ci */
42862306a36Sopenharmony_cistatic long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
42962306a36Sopenharmony_ci			  void __user *argp)
43062306a36Sopenharmony_ci{
43162306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	switch (cmd) {
43462306a36Sopenharmony_ci	case SNDRV_UMP_IOCTL_ENDPOINT_INFO:
43562306a36Sopenharmony_ci		if (copy_to_user(argp, &ump->info, sizeof(ump->info)))
43662306a36Sopenharmony_ci			return -EFAULT;
43762306a36Sopenharmony_ci		return 0;
43862306a36Sopenharmony_ci	case SNDRV_UMP_IOCTL_BLOCK_INFO:
43962306a36Sopenharmony_ci		return snd_ump_ioctl_block(ump, argp);
44062306a36Sopenharmony_ci	default:
44162306a36Sopenharmony_ci		ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd);
44262306a36Sopenharmony_ci		return -ENOTTY;
44362306a36Sopenharmony_ci	}
44462306a36Sopenharmony_ci}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_cistatic const char *ump_direction_string(int dir)
44762306a36Sopenharmony_ci{
44862306a36Sopenharmony_ci	switch (dir) {
44962306a36Sopenharmony_ci	case SNDRV_UMP_DIR_INPUT:
45062306a36Sopenharmony_ci		return "input";
45162306a36Sopenharmony_ci	case SNDRV_UMP_DIR_OUTPUT:
45262306a36Sopenharmony_ci		return "output";
45362306a36Sopenharmony_ci	case SNDRV_UMP_DIR_BIDIRECTION:
45462306a36Sopenharmony_ci		return "bidirection";
45562306a36Sopenharmony_ci	default:
45662306a36Sopenharmony_ci		return "unknown";
45762306a36Sopenharmony_ci	}
45862306a36Sopenharmony_ci}
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_cistatic const char *ump_ui_hint_string(int dir)
46162306a36Sopenharmony_ci{
46262306a36Sopenharmony_ci	switch (dir) {
46362306a36Sopenharmony_ci	case  SNDRV_UMP_BLOCK_UI_HINT_RECEIVER:
46462306a36Sopenharmony_ci		return "receiver";
46562306a36Sopenharmony_ci	case SNDRV_UMP_BLOCK_UI_HINT_SENDER:
46662306a36Sopenharmony_ci		return "sender";
46762306a36Sopenharmony_ci	case SNDRV_UMP_BLOCK_UI_HINT_BOTH:
46862306a36Sopenharmony_ci		return "both";
46962306a36Sopenharmony_ci	default:
47062306a36Sopenharmony_ci		return "unknown";
47162306a36Sopenharmony_ci	}
47262306a36Sopenharmony_ci}
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci/* Additional proc file output */
47562306a36Sopenharmony_cistatic void snd_ump_proc_read(struct snd_info_entry *entry,
47662306a36Sopenharmony_ci			      struct snd_info_buffer *buffer)
47762306a36Sopenharmony_ci{
47862306a36Sopenharmony_ci	struct snd_rawmidi *rmidi = entry->private_data;
47962306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
48062306a36Sopenharmony_ci	struct snd_ump_block *fb;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	snd_iprintf(buffer, "EP Name: %s\n", ump->info.name);
48362306a36Sopenharmony_ci	snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id);
48462306a36Sopenharmony_ci	snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version);
48562306a36Sopenharmony_ci	snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps);
48662306a36Sopenharmony_ci	snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol);
48762306a36Sopenharmony_ci	if (ump->info.version) {
48862306a36Sopenharmony_ci		snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n",
48962306a36Sopenharmony_ci			    ump->info.manufacturer_id);
49062306a36Sopenharmony_ci		snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id);
49162306a36Sopenharmony_ci		snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id);
49262306a36Sopenharmony_ci		snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n",
49362306a36Sopenharmony_ci			    ump->info.sw_revision[0],
49462306a36Sopenharmony_ci			    ump->info.sw_revision[1],
49562306a36Sopenharmony_ci			    ump->info.sw_revision[2],
49662306a36Sopenharmony_ci			    ump->info.sw_revision[3]);
49762306a36Sopenharmony_ci	}
49862306a36Sopenharmony_ci	snd_iprintf(buffer, "Static Blocks: %s\n",
49962306a36Sopenharmony_ci		    (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No");
50062306a36Sopenharmony_ci	snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks);
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	list_for_each_entry(fb, &ump->block_list, list) {
50362306a36Sopenharmony_ci		snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id,
50462306a36Sopenharmony_ci			    fb->info.name);
50562306a36Sopenharmony_ci		snd_iprintf(buffer, "  Direction: %s\n",
50662306a36Sopenharmony_ci			    ump_direction_string(fb->info.direction));
50762306a36Sopenharmony_ci		snd_iprintf(buffer, "  Active: %s\n",
50862306a36Sopenharmony_ci			    fb->info.active ? "Yes" : "No");
50962306a36Sopenharmony_ci		snd_iprintf(buffer, "  Groups: %d-%d\n",
51062306a36Sopenharmony_ci			    fb->info.first_group + 1,
51162306a36Sopenharmony_ci			    fb->info.first_group + fb->info.num_groups);
51262306a36Sopenharmony_ci		snd_iprintf(buffer, "  Is MIDI1: %s%s\n",
51362306a36Sopenharmony_ci			    (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No",
51462306a36Sopenharmony_ci			    (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : "");
51562306a36Sopenharmony_ci		if (ump->info.version) {
51662306a36Sopenharmony_ci			snd_iprintf(buffer, "  MIDI-CI Version: %d\n",
51762306a36Sopenharmony_ci				    fb->info.midi_ci_version);
51862306a36Sopenharmony_ci			snd_iprintf(buffer, "  Sysex8 Streams: %d\n",
51962306a36Sopenharmony_ci				    fb->info.sysex8_streams);
52062306a36Sopenharmony_ci			snd_iprintf(buffer, "  UI Hint: %s\n",
52162306a36Sopenharmony_ci				    ump_ui_hint_string(fb->info.ui_hint));
52262306a36Sopenharmony_ci		}
52362306a36Sopenharmony_ci		snd_iprintf(buffer, "\n");
52462306a36Sopenharmony_ci	}
52562306a36Sopenharmony_ci}
52662306a36Sopenharmony_ci
52762306a36Sopenharmony_ci/*
52862306a36Sopenharmony_ci * UMP endpoint and function block handling
52962306a36Sopenharmony_ci */
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci/* open / close UMP streams for the internal stream msg communication */
53262306a36Sopenharmony_cistatic int ump_request_open(struct snd_ump_endpoint *ump)
53362306a36Sopenharmony_ci{
53462306a36Sopenharmony_ci	return snd_rawmidi_kernel_open(&ump->core, 0,
53562306a36Sopenharmony_ci				       SNDRV_RAWMIDI_LFLG_OUTPUT,
53662306a36Sopenharmony_ci				       &ump->stream_rfile);
53762306a36Sopenharmony_ci}
53862306a36Sopenharmony_ci
53962306a36Sopenharmony_cistatic void ump_request_close(struct snd_ump_endpoint *ump)
54062306a36Sopenharmony_ci{
54162306a36Sopenharmony_ci	snd_rawmidi_kernel_release(&ump->stream_rfile);
54262306a36Sopenharmony_ci}
54362306a36Sopenharmony_ci
54462306a36Sopenharmony_ci/* request a command and wait for the given response;
54562306a36Sopenharmony_ci * @req1 and @req2 are u32 commands
54662306a36Sopenharmony_ci * @reply is the expected UMP stream status
54762306a36Sopenharmony_ci */
54862306a36Sopenharmony_cistatic int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2,
54962306a36Sopenharmony_ci		       u32 reply)
55062306a36Sopenharmony_ci{
55162306a36Sopenharmony_ci	u32 buf[4];
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n",
55462306a36Sopenharmony_ci		__func__, req1, req2, reply);
55562306a36Sopenharmony_ci	memset(buf, 0, sizeof(buf));
55662306a36Sopenharmony_ci	buf[0] = req1;
55762306a36Sopenharmony_ci	buf[1] = req2;
55862306a36Sopenharmony_ci	ump->stream_finished = 0;
55962306a36Sopenharmony_ci	ump->stream_wait_for = reply;
56062306a36Sopenharmony_ci	snd_rawmidi_kernel_write(ump->stream_rfile.output,
56162306a36Sopenharmony_ci				 (unsigned char *)&buf, 16);
56262306a36Sopenharmony_ci	wait_event_timeout(ump->stream_wait, ump->stream_finished,
56362306a36Sopenharmony_ci			   msecs_to_jiffies(500));
56462306a36Sopenharmony_ci	if (!READ_ONCE(ump->stream_finished)) {
56562306a36Sopenharmony_ci		ump_dbg(ump, "%s: request timed out\n", __func__);
56662306a36Sopenharmony_ci		return -ETIMEDOUT;
56762306a36Sopenharmony_ci	}
56862306a36Sopenharmony_ci	ump->stream_finished = 0;
56962306a36Sopenharmony_ci	ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n",
57062306a36Sopenharmony_ci		__func__, buf[0], buf[1], buf[2], buf[3]);
57162306a36Sopenharmony_ci	return 0;
57262306a36Sopenharmony_ci}
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci/* append the received letters via UMP packet to the given string buffer;
57562306a36Sopenharmony_ci * return 1 if the full string is received or 0 to continue
57662306a36Sopenharmony_ci */
57762306a36Sopenharmony_cistatic int ump_append_string(struct snd_ump_endpoint *ump, char *dest,
57862306a36Sopenharmony_ci			     int maxsize, const u32 *buf, int offset)
57962306a36Sopenharmony_ci{
58062306a36Sopenharmony_ci	unsigned char format;
58162306a36Sopenharmony_ci	int c;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	format = ump_stream_message_format(buf[0]);
58462306a36Sopenharmony_ci	if (format == UMP_STREAM_MSG_FORMAT_SINGLE ||
58562306a36Sopenharmony_ci	    format == UMP_STREAM_MSG_FORMAT_START) {
58662306a36Sopenharmony_ci		c = 0;
58762306a36Sopenharmony_ci	} else {
58862306a36Sopenharmony_ci		c = strlen(dest);
58962306a36Sopenharmony_ci		if (c >= maxsize - 1)
59062306a36Sopenharmony_ci			return 1;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	for (; offset < 16; offset++) {
59462306a36Sopenharmony_ci		dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8;
59562306a36Sopenharmony_ci		if (!dest[c])
59662306a36Sopenharmony_ci			break;
59762306a36Sopenharmony_ci		if (++c >= maxsize - 1)
59862306a36Sopenharmony_ci			break;
59962306a36Sopenharmony_ci	}
60062306a36Sopenharmony_ci	dest[c] = 0;
60162306a36Sopenharmony_ci	return (format == UMP_STREAM_MSG_FORMAT_SINGLE ||
60262306a36Sopenharmony_ci		format == UMP_STREAM_MSG_FORMAT_END);
60362306a36Sopenharmony_ci}
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci/* handle EP info stream message; update the UMP attributes */
60662306a36Sopenharmony_cistatic int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump,
60762306a36Sopenharmony_ci				  const union snd_ump_stream_msg *buf)
60862306a36Sopenharmony_ci{
60962306a36Sopenharmony_ci	ump->info.version = (buf->ep_info.ump_version_major << 8) |
61062306a36Sopenharmony_ci		buf->ep_info.ump_version_minor;
61162306a36Sopenharmony_ci	ump->info.num_blocks = buf->ep_info.num_function_blocks;
61262306a36Sopenharmony_ci	if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) {
61362306a36Sopenharmony_ci		ump_info(ump, "Invalid function blocks %d, fallback to 1\n",
61462306a36Sopenharmony_ci			 ump->info.num_blocks);
61562306a36Sopenharmony_ci		ump->info.num_blocks = 1;
61662306a36Sopenharmony_ci	}
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	if (buf->ep_info.static_function_block)
61962306a36Sopenharmony_ci		ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	ump->info.protocol_caps = (buf->ep_info.protocol << 8) |
62262306a36Sopenharmony_ci		buf->ep_info.jrts;
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n",
62562306a36Sopenharmony_ci		ump->info.version, ump->info.num_blocks, ump->info.protocol_caps);
62662306a36Sopenharmony_ci	return 1; /* finished */
62762306a36Sopenharmony_ci}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci/* handle EP device info stream message; update the UMP attributes */
63062306a36Sopenharmony_cistatic int ump_handle_device_info_msg(struct snd_ump_endpoint *ump,
63162306a36Sopenharmony_ci				      const union snd_ump_stream_msg *buf)
63262306a36Sopenharmony_ci{
63362306a36Sopenharmony_ci	ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f;
63462306a36Sopenharmony_ci	ump->info.family_id = (buf->device_info.family_msb << 8) |
63562306a36Sopenharmony_ci		buf->device_info.family_lsb;
63662306a36Sopenharmony_ci	ump->info.model_id = (buf->device_info.model_msb << 8) |
63762306a36Sopenharmony_ci		buf->device_info.model_lsb;
63862306a36Sopenharmony_ci	ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f;
63962306a36Sopenharmony_ci	ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f;
64062306a36Sopenharmony_ci	ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f;
64162306a36Sopenharmony_ci	ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f;
64262306a36Sopenharmony_ci	ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n",
64362306a36Sopenharmony_ci		ump->info.manufacturer_id,
64462306a36Sopenharmony_ci		ump->info.family_id,
64562306a36Sopenharmony_ci		ump->info.model_id,
64662306a36Sopenharmony_ci		ump->info.sw_revision[0],
64762306a36Sopenharmony_ci		ump->info.sw_revision[1],
64862306a36Sopenharmony_ci		ump->info.sw_revision[2],
64962306a36Sopenharmony_ci		ump->info.sw_revision[3]);
65062306a36Sopenharmony_ci	return 1; /* finished */
65162306a36Sopenharmony_ci}
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci/* handle EP name stream message; update the UMP name string */
65462306a36Sopenharmony_cistatic int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump,
65562306a36Sopenharmony_ci				  const union snd_ump_stream_msg *buf)
65662306a36Sopenharmony_ci{
65762306a36Sopenharmony_ci	return ump_append_string(ump, ump->info.name, sizeof(ump->info.name),
65862306a36Sopenharmony_ci				 buf->raw, 2);
65962306a36Sopenharmony_ci}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci/* handle EP product id stream message; update the UMP product_id string */
66262306a36Sopenharmony_cistatic int ump_handle_product_id_msg(struct snd_ump_endpoint *ump,
66362306a36Sopenharmony_ci				     const union snd_ump_stream_msg *buf)
66462306a36Sopenharmony_ci{
66562306a36Sopenharmony_ci	return ump_append_string(ump, ump->info.product_id,
66662306a36Sopenharmony_ci				 sizeof(ump->info.product_id),
66762306a36Sopenharmony_ci				 buf->raw, 2);
66862306a36Sopenharmony_ci}
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ci/* notify the protocol change to sequencer */
67162306a36Sopenharmony_cistatic void seq_notify_protocol(struct snd_ump_endpoint *ump)
67262306a36Sopenharmony_ci{
67362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
67462306a36Sopenharmony_ci	if (ump->seq_ops && ump->seq_ops->switch_protocol)
67562306a36Sopenharmony_ci		ump->seq_ops->switch_protocol(ump);
67662306a36Sopenharmony_ci#endif /* CONFIG_SND_SEQUENCER */
67762306a36Sopenharmony_ci}
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci/**
68062306a36Sopenharmony_ci * snd_ump_switch_protocol - switch MIDI protocol
68162306a36Sopenharmony_ci * @ump: UMP endpoint
68262306a36Sopenharmony_ci * @protocol: protocol to switch to
68362306a36Sopenharmony_ci *
68462306a36Sopenharmony_ci * Returns 1 if the protocol is actually switched, 0 if unchanged
68562306a36Sopenharmony_ci */
68662306a36Sopenharmony_ciint snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol)
68762306a36Sopenharmony_ci{
68862306a36Sopenharmony_ci	protocol &= ump->info.protocol_caps;
68962306a36Sopenharmony_ci	if (protocol == ump->info.protocol)
69062306a36Sopenharmony_ci		return 0;
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci	ump->info.protocol = protocol;
69362306a36Sopenharmony_ci	ump_dbg(ump, "New protocol = %x (caps = %x)\n",
69462306a36Sopenharmony_ci		protocol, ump->info.protocol_caps);
69562306a36Sopenharmony_ci	seq_notify_protocol(ump);
69662306a36Sopenharmony_ci	return 1;
69762306a36Sopenharmony_ci}
69862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_switch_protocol);
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_ci/* handle EP stream config message; update the UMP protocol */
70162306a36Sopenharmony_cistatic int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump,
70262306a36Sopenharmony_ci				     const union snd_ump_stream_msg *buf)
70362306a36Sopenharmony_ci{
70462306a36Sopenharmony_ci	unsigned int protocol =
70562306a36Sopenharmony_ci		(buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	snd_ump_switch_protocol(ump, protocol);
70862306a36Sopenharmony_ci	return 1; /* finished */
70962306a36Sopenharmony_ci}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci/* Extract Function Block info from UMP packet */
71262306a36Sopenharmony_cistatic void fill_fb_info(struct snd_ump_endpoint *ump,
71362306a36Sopenharmony_ci			 struct snd_ump_block_info *info,
71462306a36Sopenharmony_ci			 const union snd_ump_stream_msg *buf)
71562306a36Sopenharmony_ci{
71662306a36Sopenharmony_ci	info->direction = buf->fb_info.direction;
71762306a36Sopenharmony_ci	info->ui_hint = buf->fb_info.ui_hint;
71862306a36Sopenharmony_ci	info->first_group = buf->fb_info.first_group;
71962306a36Sopenharmony_ci	info->num_groups = buf->fb_info.num_groups;
72062306a36Sopenharmony_ci	info->flags = buf->fb_info.midi_10;
72162306a36Sopenharmony_ci	info->active = buf->fb_info.active;
72262306a36Sopenharmony_ci	info->midi_ci_version = buf->fb_info.midi_ci_version;
72362306a36Sopenharmony_ci	info->sysex8_streams = buf->fb_info.sysex8_streams;
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci	ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n",
72662306a36Sopenharmony_ci		info->block_id, info->direction, info->active,
72762306a36Sopenharmony_ci		info->first_group, info->num_groups, info->midi_ci_version,
72862306a36Sopenharmony_ci		info->sysex8_streams, info->flags);
72962306a36Sopenharmony_ci}
73062306a36Sopenharmony_ci
73162306a36Sopenharmony_ci/* check whether the FB info gets updated by the current message */
73262306a36Sopenharmony_cistatic bool is_fb_info_updated(struct snd_ump_endpoint *ump,
73362306a36Sopenharmony_ci			       struct snd_ump_block *fb,
73462306a36Sopenharmony_ci			       const union snd_ump_stream_msg *buf)
73562306a36Sopenharmony_ci{
73662306a36Sopenharmony_ci	char tmpbuf[offsetof(struct snd_ump_block_info, name)];
73762306a36Sopenharmony_ci
73862306a36Sopenharmony_ci	if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {
73962306a36Sopenharmony_ci		ump_info(ump, "Skipping static FB info update (blk#%d)\n",
74062306a36Sopenharmony_ci			 fb->info.block_id);
74162306a36Sopenharmony_ci		return 0;
74262306a36Sopenharmony_ci	}
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	memcpy(tmpbuf, &fb->info, sizeof(tmpbuf));
74562306a36Sopenharmony_ci	fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf);
74662306a36Sopenharmony_ci	return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0;
74762306a36Sopenharmony_ci}
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci/* notify the FB info/name change to sequencer */
75062306a36Sopenharmony_cistatic void seq_notify_fb_change(struct snd_ump_endpoint *ump,
75162306a36Sopenharmony_ci				 struct snd_ump_block *fb)
75262306a36Sopenharmony_ci{
75362306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_SEQUENCER)
75462306a36Sopenharmony_ci	if (ump->seq_ops && ump->seq_ops->notify_fb_change)
75562306a36Sopenharmony_ci		ump->seq_ops->notify_fb_change(ump, fb);
75662306a36Sopenharmony_ci#endif
75762306a36Sopenharmony_ci}
75862306a36Sopenharmony_ci
75962306a36Sopenharmony_ci/* handle FB info message; update FB info if the block is present */
76062306a36Sopenharmony_cistatic int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump,
76162306a36Sopenharmony_ci				  const union snd_ump_stream_msg *buf)
76262306a36Sopenharmony_ci{
76362306a36Sopenharmony_ci	unsigned char blk;
76462306a36Sopenharmony_ci	struct snd_ump_block *fb;
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_ci	blk = buf->fb_info.function_block_id;
76762306a36Sopenharmony_ci	fb = snd_ump_get_block(ump, blk);
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	/* complain only if updated after parsing */
77062306a36Sopenharmony_ci	if (!fb && ump->parsed) {
77162306a36Sopenharmony_ci		ump_info(ump, "Function Block Info Update for non-existing block %d\n",
77262306a36Sopenharmony_ci			 blk);
77362306a36Sopenharmony_ci		return -ENODEV;
77462306a36Sopenharmony_ci	}
77562306a36Sopenharmony_ci
77662306a36Sopenharmony_ci	/* When updated after the initial parse, check the FB info update */
77762306a36Sopenharmony_ci	if (ump->parsed && !is_fb_info_updated(ump, fb, buf))
77862306a36Sopenharmony_ci		return 1; /* no content change */
77962306a36Sopenharmony_ci
78062306a36Sopenharmony_ci	if (fb) {
78162306a36Sopenharmony_ci		fill_fb_info(ump, &fb->info, buf);
78262306a36Sopenharmony_ci		if (ump->parsed)
78362306a36Sopenharmony_ci			seq_notify_fb_change(ump, fb);
78462306a36Sopenharmony_ci	}
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci	return 1; /* finished */
78762306a36Sopenharmony_ci}
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci/* handle FB name message; update the FB name string */
79062306a36Sopenharmony_cistatic int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump,
79162306a36Sopenharmony_ci				  const union snd_ump_stream_msg *buf)
79262306a36Sopenharmony_ci{
79362306a36Sopenharmony_ci	unsigned char blk;
79462306a36Sopenharmony_ci	struct snd_ump_block *fb;
79562306a36Sopenharmony_ci	int ret;
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	blk = buf->fb_name.function_block_id;
79862306a36Sopenharmony_ci	fb = snd_ump_get_block(ump, blk);
79962306a36Sopenharmony_ci	if (!fb)
80062306a36Sopenharmony_ci		return -ENODEV;
80162306a36Sopenharmony_ci
80262306a36Sopenharmony_ci	ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name),
80362306a36Sopenharmony_ci				buf->raw, 3);
80462306a36Sopenharmony_ci	/* notify the FB name update to sequencer, too */
80562306a36Sopenharmony_ci	if (ret > 0 && ump->parsed)
80662306a36Sopenharmony_ci		seq_notify_fb_change(ump, fb);
80762306a36Sopenharmony_ci	return ret;
80862306a36Sopenharmony_ci}
80962306a36Sopenharmony_ci
81062306a36Sopenharmony_cistatic int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk)
81162306a36Sopenharmony_ci{
81262306a36Sopenharmony_ci	struct snd_ump_block *fb;
81362306a36Sopenharmony_ci	unsigned char direction, first_group, num_groups;
81462306a36Sopenharmony_ci	const union snd_ump_stream_msg *buf =
81562306a36Sopenharmony_ci		(const union snd_ump_stream_msg *)ump->input_buf;
81662306a36Sopenharmony_ci	u32 msg;
81762306a36Sopenharmony_ci	int err;
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	/* query the FB info once */
82062306a36Sopenharmony_ci	msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |
82162306a36Sopenharmony_ci		(blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO;
82262306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO);
82362306a36Sopenharmony_ci	if (err < 0) {
82462306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get FB info for block %d\n", blk);
82562306a36Sopenharmony_ci		return err;
82662306a36Sopenharmony_ci	}
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	/* the last input must be the FB info */
82962306a36Sopenharmony_ci	if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) {
83062306a36Sopenharmony_ci		ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw);
83162306a36Sopenharmony_ci		return -EINVAL;
83262306a36Sopenharmony_ci	}
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	direction = buf->fb_info.direction;
83562306a36Sopenharmony_ci	first_group = buf->fb_info.first_group;
83662306a36Sopenharmony_ci	num_groups = buf->fb_info.num_groups;
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	err = snd_ump_block_new(ump, blk, direction, first_group, num_groups,
83962306a36Sopenharmony_ci				&fb);
84062306a36Sopenharmony_ci	if (err < 0)
84162306a36Sopenharmony_ci		return err;
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	fill_fb_info(ump, &fb->info, buf);
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_ci	msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |
84662306a36Sopenharmony_ci		(blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME;
84762306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME);
84862306a36Sopenharmony_ci	if (err)
84962306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk);
85062306a36Sopenharmony_ci
85162306a36Sopenharmony_ci	return 0;
85262306a36Sopenharmony_ci}
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci/* handle stream messages, called from snd_ump_receive() */
85562306a36Sopenharmony_cistatic void ump_handle_stream_msg(struct snd_ump_endpoint *ump,
85662306a36Sopenharmony_ci				  const u32 *buf, int size)
85762306a36Sopenharmony_ci{
85862306a36Sopenharmony_ci	const union snd_ump_stream_msg *msg;
85962306a36Sopenharmony_ci	unsigned int status;
86062306a36Sopenharmony_ci	int ret;
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_ci	/* UMP stream message suppressed (for gadget UMP)? */
86362306a36Sopenharmony_ci	if (ump->no_process_stream)
86462306a36Sopenharmony_ci		return;
86562306a36Sopenharmony_ci
86662306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(*msg) != 16);
86762306a36Sopenharmony_ci	ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n",
86862306a36Sopenharmony_ci		buf[0], buf[1], buf[2], buf[3]);
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci	if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM)
87162306a36Sopenharmony_ci		return;
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	msg = (const union snd_ump_stream_msg *)buf;
87462306a36Sopenharmony_ci	status = ump_stream_message_status(*buf);
87562306a36Sopenharmony_ci	switch (status) {
87662306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_EP_INFO:
87762306a36Sopenharmony_ci		ret = ump_handle_ep_info_msg(ump, msg);
87862306a36Sopenharmony_ci		break;
87962306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_DEVICE_INFO:
88062306a36Sopenharmony_ci		ret = ump_handle_device_info_msg(ump, msg);
88162306a36Sopenharmony_ci		break;
88262306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_EP_NAME:
88362306a36Sopenharmony_ci		ret = ump_handle_ep_name_msg(ump, msg);
88462306a36Sopenharmony_ci		break;
88562306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_PRODUCT_ID:
88662306a36Sopenharmony_ci		ret = ump_handle_product_id_msg(ump, msg);
88762306a36Sopenharmony_ci		break;
88862306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_STREAM_CFG:
88962306a36Sopenharmony_ci		ret = ump_handle_stream_cfg_msg(ump, msg);
89062306a36Sopenharmony_ci		break;
89162306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_FB_INFO:
89262306a36Sopenharmony_ci		ret = ump_handle_fb_info_msg(ump, msg);
89362306a36Sopenharmony_ci		break;
89462306a36Sopenharmony_ci	case UMP_STREAM_MSG_STATUS_FB_NAME:
89562306a36Sopenharmony_ci		ret = ump_handle_fb_name_msg(ump, msg);
89662306a36Sopenharmony_ci		break;
89762306a36Sopenharmony_ci	default:
89862306a36Sopenharmony_ci		return;
89962306a36Sopenharmony_ci	}
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	/* when the message has been processed fully, wake up */
90262306a36Sopenharmony_ci	if (ret > 0 && ump->stream_wait_for == status) {
90362306a36Sopenharmony_ci		WRITE_ONCE(ump->stream_finished, 1);
90462306a36Sopenharmony_ci		wake_up(&ump->stream_wait);
90562306a36Sopenharmony_ci	}
90662306a36Sopenharmony_ci}
90762306a36Sopenharmony_ci
90862306a36Sopenharmony_ci/**
90962306a36Sopenharmony_ci * snd_ump_parse_endpoint - parse endpoint and create function blocks
91062306a36Sopenharmony_ci * @ump: UMP object
91162306a36Sopenharmony_ci *
91262306a36Sopenharmony_ci * Returns 0 for successful parse, -ENODEV if device doesn't respond
91362306a36Sopenharmony_ci * (or the query is unsupported), or other error code for serious errors.
91462306a36Sopenharmony_ci */
91562306a36Sopenharmony_ciint snd_ump_parse_endpoint(struct snd_ump_endpoint *ump)
91662306a36Sopenharmony_ci{
91762306a36Sopenharmony_ci	int blk, err;
91862306a36Sopenharmony_ci	u32 msg;
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX))
92162306a36Sopenharmony_ci		return -ENODEV;
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	err = ump_request_open(ump);
92462306a36Sopenharmony_ci	if (err < 0) {
92562306a36Sopenharmony_ci		ump_dbg(ump, "Unable to open rawmidi device: %d\n", err);
92662306a36Sopenharmony_ci		return err;
92762306a36Sopenharmony_ci	}
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	/* Check Endpoint Information */
93062306a36Sopenharmony_ci	msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) |
93162306a36Sopenharmony_ci		0x0101; /* UMP version 1.1 */
93262306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO,
93362306a36Sopenharmony_ci			  UMP_STREAM_MSG_STATUS_EP_INFO);
93462306a36Sopenharmony_ci	if (err < 0) {
93562306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get UMP EP info\n");
93662306a36Sopenharmony_ci		goto error;
93762306a36Sopenharmony_ci	}
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_ci	/* Request Endpoint Device Info */
94062306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO,
94162306a36Sopenharmony_ci			  UMP_STREAM_MSG_STATUS_DEVICE_INFO);
94262306a36Sopenharmony_ci	if (err < 0)
94362306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get UMP EP device info\n");
94462306a36Sopenharmony_ci
94562306a36Sopenharmony_ci	/* Request Endpoint Name */
94662306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME,
94762306a36Sopenharmony_ci			  UMP_STREAM_MSG_STATUS_EP_NAME);
94862306a36Sopenharmony_ci	if (err < 0)
94962306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get UMP EP name string\n");
95062306a36Sopenharmony_ci
95162306a36Sopenharmony_ci	/* Request Endpoint Product ID */
95262306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID,
95362306a36Sopenharmony_ci			  UMP_STREAM_MSG_STATUS_PRODUCT_ID);
95462306a36Sopenharmony_ci	if (err < 0)
95562306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get UMP EP product ID string\n");
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_ci	/* Get the current stream configuration */
95862306a36Sopenharmony_ci	err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG,
95962306a36Sopenharmony_ci			  UMP_STREAM_MSG_STATUS_STREAM_CFG);
96062306a36Sopenharmony_ci	if (err < 0)
96162306a36Sopenharmony_ci		ump_dbg(ump, "Unable to get UMP EP stream config\n");
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci	/* Query and create blocks from Function Blocks */
96462306a36Sopenharmony_ci	for (blk = 0; blk < ump->info.num_blocks; blk++) {
96562306a36Sopenharmony_ci		err = create_block_from_fb_info(ump, blk);
96662306a36Sopenharmony_ci		if (err < 0)
96762306a36Sopenharmony_ci			continue;
96862306a36Sopenharmony_ci	}
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_ci error:
97162306a36Sopenharmony_ci	ump->parsed = true;
97262306a36Sopenharmony_ci	ump_request_close(ump);
97362306a36Sopenharmony_ci	if (err == -ETIMEDOUT)
97462306a36Sopenharmony_ci		err = -ENODEV;
97562306a36Sopenharmony_ci	return err;
97662306a36Sopenharmony_ci}
97762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_parse_endpoint);
97862306a36Sopenharmony_ci
97962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
98062306a36Sopenharmony_ci/*
98162306a36Sopenharmony_ci * Legacy rawmidi support
98262306a36Sopenharmony_ci */
98362306a36Sopenharmony_cistatic int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)
98462306a36Sopenharmony_ci{
98562306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = substream->rmidi->private_data;
98662306a36Sopenharmony_ci	int dir = substream->stream;
98762306a36Sopenharmony_ci	int group = ump->legacy_mapping[substream->number];
98862306a36Sopenharmony_ci	int err = 0;
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_ci	mutex_lock(&ump->open_mutex);
99162306a36Sopenharmony_ci	if (ump->legacy_substreams[dir][group]) {
99262306a36Sopenharmony_ci		err = -EBUSY;
99362306a36Sopenharmony_ci		goto unlock;
99462306a36Sopenharmony_ci	}
99562306a36Sopenharmony_ci	if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
99662306a36Sopenharmony_ci		if (!ump->legacy_out_opens) {
99762306a36Sopenharmony_ci			err = snd_rawmidi_kernel_open(&ump->core, 0,
99862306a36Sopenharmony_ci						      SNDRV_RAWMIDI_LFLG_OUTPUT |
99962306a36Sopenharmony_ci						      SNDRV_RAWMIDI_LFLG_APPEND,
100062306a36Sopenharmony_ci						      &ump->legacy_out_rfile);
100162306a36Sopenharmony_ci			if (err < 0)
100262306a36Sopenharmony_ci				goto unlock;
100362306a36Sopenharmony_ci		}
100462306a36Sopenharmony_ci		ump->legacy_out_opens++;
100562306a36Sopenharmony_ci		snd_ump_convert_reset(&ump->out_cvts[group]);
100662306a36Sopenharmony_ci	}
100762306a36Sopenharmony_ci	spin_lock_irq(&ump->legacy_locks[dir]);
100862306a36Sopenharmony_ci	ump->legacy_substreams[dir][group] = substream;
100962306a36Sopenharmony_ci	spin_unlock_irq(&ump->legacy_locks[dir]);
101062306a36Sopenharmony_ci unlock:
101162306a36Sopenharmony_ci	mutex_unlock(&ump->open_mutex);
101262306a36Sopenharmony_ci	return err;
101362306a36Sopenharmony_ci}
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_cistatic int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)
101662306a36Sopenharmony_ci{
101762306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = substream->rmidi->private_data;
101862306a36Sopenharmony_ci	int dir = substream->stream;
101962306a36Sopenharmony_ci	int group = ump->legacy_mapping[substream->number];
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci	mutex_lock(&ump->open_mutex);
102262306a36Sopenharmony_ci	spin_lock_irq(&ump->legacy_locks[dir]);
102362306a36Sopenharmony_ci	ump->legacy_substreams[dir][group] = NULL;
102462306a36Sopenharmony_ci	spin_unlock_irq(&ump->legacy_locks[dir]);
102562306a36Sopenharmony_ci	if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
102662306a36Sopenharmony_ci		if (!--ump->legacy_out_opens)
102762306a36Sopenharmony_ci			snd_rawmidi_kernel_release(&ump->legacy_out_rfile);
102862306a36Sopenharmony_ci	}
102962306a36Sopenharmony_ci	mutex_unlock(&ump->open_mutex);
103062306a36Sopenharmony_ci	return 0;
103162306a36Sopenharmony_ci}
103262306a36Sopenharmony_ci
103362306a36Sopenharmony_cistatic void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,
103462306a36Sopenharmony_ci				   int up)
103562306a36Sopenharmony_ci{
103662306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = substream->rmidi->private_data;
103762306a36Sopenharmony_ci	int dir = substream->stream;
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	ump->ops->trigger(ump, dir, up);
104062306a36Sopenharmony_ci}
104162306a36Sopenharmony_ci
104262306a36Sopenharmony_cistatic void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)
104362306a36Sopenharmony_ci{
104462306a36Sopenharmony_ci	struct snd_ump_endpoint *ump = substream->rmidi->private_data;
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_ci	if (ump->ops->drain)
104762306a36Sopenharmony_ci		ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
104862306a36Sopenharmony_ci}
104962306a36Sopenharmony_ci
105062306a36Sopenharmony_cistatic int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)
105162306a36Sopenharmony_ci{
105262306a36Sopenharmony_ci	/* dummy, just for avoiding create superfluous seq clients */
105362306a36Sopenharmony_ci	return 0;
105462306a36Sopenharmony_ci}
105562306a36Sopenharmony_ci
105662306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {
105762306a36Sopenharmony_ci	.open = snd_ump_legacy_open,
105862306a36Sopenharmony_ci	.close = snd_ump_legacy_close,
105962306a36Sopenharmony_ci	.trigger = snd_ump_legacy_trigger,
106062306a36Sopenharmony_ci};
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_cistatic const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {
106362306a36Sopenharmony_ci	.open = snd_ump_legacy_open,
106462306a36Sopenharmony_ci	.close = snd_ump_legacy_close,
106562306a36Sopenharmony_ci	.trigger = snd_ump_legacy_trigger,
106662306a36Sopenharmony_ci	.drain = snd_ump_legacy_drain,
106762306a36Sopenharmony_ci};
106862306a36Sopenharmony_ci
106962306a36Sopenharmony_cistatic const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {
107062306a36Sopenharmony_ci	.dev_register = snd_ump_legacy_dev_register,
107162306a36Sopenharmony_ci};
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_cistatic int process_legacy_output(struct snd_ump_endpoint *ump,
107462306a36Sopenharmony_ci				 u32 *buffer, int count)
107562306a36Sopenharmony_ci{
107662306a36Sopenharmony_ci	struct snd_rawmidi_substream *substream;
107762306a36Sopenharmony_ci	struct ump_cvt_to_ump *ctx;
107862306a36Sopenharmony_ci	const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;
107962306a36Sopenharmony_ci	unsigned char c;
108062306a36Sopenharmony_ci	int group, size = 0;
108162306a36Sopenharmony_ci	unsigned long flags;
108262306a36Sopenharmony_ci
108362306a36Sopenharmony_ci	if (!ump->out_cvts || !ump->legacy_out_opens)
108462306a36Sopenharmony_ci		return 0;
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci	spin_lock_irqsave(&ump->legacy_locks[dir], flags);
108762306a36Sopenharmony_ci	for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {
108862306a36Sopenharmony_ci		substream = ump->legacy_substreams[dir][group];
108962306a36Sopenharmony_ci		if (!substream)
109062306a36Sopenharmony_ci			continue;
109162306a36Sopenharmony_ci		ctx = &ump->out_cvts[group];
109262306a36Sopenharmony_ci		while (!ctx->ump_bytes &&
109362306a36Sopenharmony_ci		       snd_rawmidi_transmit(substream, &c, 1) > 0)
109462306a36Sopenharmony_ci			snd_ump_convert_to_ump(ctx, group, ump->info.protocol, c);
109562306a36Sopenharmony_ci		if (ctx->ump_bytes && ctx->ump_bytes <= count) {
109662306a36Sopenharmony_ci			size = ctx->ump_bytes;
109762306a36Sopenharmony_ci			memcpy(buffer, ctx->ump, size);
109862306a36Sopenharmony_ci			ctx->ump_bytes = 0;
109962306a36Sopenharmony_ci			break;
110062306a36Sopenharmony_ci		}
110162306a36Sopenharmony_ci	}
110262306a36Sopenharmony_ci	spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
110362306a36Sopenharmony_ci	return size;
110462306a36Sopenharmony_ci}
110562306a36Sopenharmony_ci
110662306a36Sopenharmony_cistatic void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
110762306a36Sopenharmony_ci				 int words)
110862306a36Sopenharmony_ci{
110962306a36Sopenharmony_ci	struct snd_rawmidi_substream *substream;
111062306a36Sopenharmony_ci	unsigned char buf[16];
111162306a36Sopenharmony_ci	unsigned char group;
111262306a36Sopenharmony_ci	unsigned long flags;
111362306a36Sopenharmony_ci	const int dir = SNDRV_RAWMIDI_STREAM_INPUT;
111462306a36Sopenharmony_ci	int size;
111562306a36Sopenharmony_ci
111662306a36Sopenharmony_ci	size = snd_ump_convert_from_ump(src, buf, &group);
111762306a36Sopenharmony_ci	if (size <= 0)
111862306a36Sopenharmony_ci		return;
111962306a36Sopenharmony_ci	spin_lock_irqsave(&ump->legacy_locks[dir], flags);
112062306a36Sopenharmony_ci	substream = ump->legacy_substreams[dir][group];
112162306a36Sopenharmony_ci	if (substream)
112262306a36Sopenharmony_ci		snd_rawmidi_receive(substream, buf, size);
112362306a36Sopenharmony_ci	spin_unlock_irqrestore(&ump->legacy_locks[dir], flags);
112462306a36Sopenharmony_ci}
112562306a36Sopenharmony_ci
112662306a36Sopenharmony_ci/* Fill ump->legacy_mapping[] for groups to be used for legacy rawmidi */
112762306a36Sopenharmony_cistatic int fill_legacy_mapping(struct snd_ump_endpoint *ump)
112862306a36Sopenharmony_ci{
112962306a36Sopenharmony_ci	struct snd_ump_block *fb;
113062306a36Sopenharmony_ci	unsigned int group_maps = 0;
113162306a36Sopenharmony_ci	int i, num;
113262306a36Sopenharmony_ci
113362306a36Sopenharmony_ci	if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {
113462306a36Sopenharmony_ci		list_for_each_entry(fb, &ump->block_list, list) {
113562306a36Sopenharmony_ci			for (i = 0; i < fb->info.num_groups; i++)
113662306a36Sopenharmony_ci				group_maps |= 1U << (fb->info.first_group + i);
113762306a36Sopenharmony_ci		}
113862306a36Sopenharmony_ci		if (!group_maps)
113962306a36Sopenharmony_ci			ump_info(ump, "No UMP Group is found in FB\n");
114062306a36Sopenharmony_ci	}
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	/* use all groups for non-static case */
114362306a36Sopenharmony_ci	if (!group_maps)
114462306a36Sopenharmony_ci		group_maps = (1U << SNDRV_UMP_MAX_GROUPS) - 1;
114562306a36Sopenharmony_ci
114662306a36Sopenharmony_ci	num = 0;
114762306a36Sopenharmony_ci	for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++)
114862306a36Sopenharmony_ci		if (group_maps & (1U << i))
114962306a36Sopenharmony_ci			ump->legacy_mapping[num++] = i;
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci	return num;
115262306a36Sopenharmony_ci}
115362306a36Sopenharmony_ci
115462306a36Sopenharmony_cistatic void fill_substream_names(struct snd_ump_endpoint *ump,
115562306a36Sopenharmony_ci				 struct snd_rawmidi *rmidi, int dir)
115662306a36Sopenharmony_ci{
115762306a36Sopenharmony_ci	struct snd_rawmidi_substream *s;
115862306a36Sopenharmony_ci
115962306a36Sopenharmony_ci	list_for_each_entry(s, &rmidi->streams[dir].substreams, list)
116062306a36Sopenharmony_ci		snprintf(s->name, sizeof(s->name), "Group %d (%.16s)",
116162306a36Sopenharmony_ci			 ump->legacy_mapping[s->number] + 1, ump->info.name);
116262306a36Sopenharmony_ci}
116362306a36Sopenharmony_ci
116462306a36Sopenharmony_ciint snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
116562306a36Sopenharmony_ci				  char *id, int device)
116662306a36Sopenharmony_ci{
116762306a36Sopenharmony_ci	struct snd_rawmidi *rmidi;
116862306a36Sopenharmony_ci	bool input, output;
116962306a36Sopenharmony_ci	int err, num;
117062306a36Sopenharmony_ci
117162306a36Sopenharmony_ci	ump->out_cvts = kcalloc(SNDRV_UMP_MAX_GROUPS,
117262306a36Sopenharmony_ci				sizeof(*ump->out_cvts), GFP_KERNEL);
117362306a36Sopenharmony_ci	if (!ump->out_cvts)
117462306a36Sopenharmony_ci		return -ENOMEM;
117562306a36Sopenharmony_ci
117662306a36Sopenharmony_ci	num = fill_legacy_mapping(ump);
117762306a36Sopenharmony_ci
117862306a36Sopenharmony_ci	input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;
117962306a36Sopenharmony_ci	output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;
118062306a36Sopenharmony_ci	err = snd_rawmidi_new(ump->core.card, id, device,
118162306a36Sopenharmony_ci			      output ? num : 0, input ? num : 0,
118262306a36Sopenharmony_ci			      &rmidi);
118362306a36Sopenharmony_ci	if (err < 0) {
118462306a36Sopenharmony_ci		kfree(ump->out_cvts);
118562306a36Sopenharmony_ci		return err;
118662306a36Sopenharmony_ci	}
118762306a36Sopenharmony_ci
118862306a36Sopenharmony_ci	if (input)
118962306a36Sopenharmony_ci		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
119062306a36Sopenharmony_ci				    &snd_ump_legacy_input_ops);
119162306a36Sopenharmony_ci	if (output)
119262306a36Sopenharmony_ci		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
119362306a36Sopenharmony_ci				    &snd_ump_legacy_output_ops);
119462306a36Sopenharmony_ci	snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)",
119562306a36Sopenharmony_ci		 ump->info.name);
119662306a36Sopenharmony_ci	rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
119762306a36Sopenharmony_ci	rmidi->ops = &snd_ump_legacy_ops;
119862306a36Sopenharmony_ci	rmidi->private_data = ump;
119962306a36Sopenharmony_ci	ump->legacy_rmidi = rmidi;
120062306a36Sopenharmony_ci	if (input)
120162306a36Sopenharmony_ci		fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT);
120262306a36Sopenharmony_ci	if (output)
120362306a36Sopenharmony_ci		fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT);
120462306a36Sopenharmony_ci
120562306a36Sopenharmony_ci	ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
120662306a36Sopenharmony_ci	return 0;
120762306a36Sopenharmony_ci}
120862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);
120962306a36Sopenharmony_ci#endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */
121062306a36Sopenharmony_ci
121162306a36Sopenharmony_ciMODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");
121262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1213