1e3a8a5b7STakashi Iwai // SPDX-License-Identifier: GPL-2.0-or-later
2e3a8a5b7STakashi Iwai /*
3e3a8a5b7STakashi Iwai * Universal MIDI Packet (UMP) support
4e3a8a5b7STakashi Iwai */
5e3a8a5b7STakashi Iwai
6e3a8a5b7STakashi Iwai #include <linux/list.h>
7e3a8a5b7STakashi Iwai #include <linux/slab.h>
8e3a8a5b7STakashi Iwai #include <linux/module.h>
9e3a8a5b7STakashi Iwai #include <linux/export.h>
10e3a8a5b7STakashi Iwai #include <linux/mm.h>
11e3a8a5b7STakashi Iwai #include <sound/core.h>
12e3a8a5b7STakashi Iwai #include <sound/rawmidi.h>
13e3a8a5b7STakashi Iwai #include <sound/ump.h>
1433cd7630STakashi Iwai #include <sound/ump_convert.h>
15e3a8a5b7STakashi Iwai
16ea29a02fSTakashi Iwai #define ump_err(ump, fmt, args...) dev_err((ump)->core.dev, fmt, ##args)
17ea29a02fSTakashi Iwai #define ump_warn(ump, fmt, args...) dev_warn((ump)->core.dev, fmt, ##args)
18ea29a02fSTakashi Iwai #define ump_info(ump, fmt, args...) dev_info((ump)->core.dev, fmt, ##args)
19ea29a02fSTakashi Iwai #define ump_dbg(ump, fmt, args...) dev_dbg((ump)->core.dev, fmt, ##args)
20e3a8a5b7STakashi Iwai
21e3a8a5b7STakashi Iwai static int snd_ump_dev_register(struct snd_rawmidi *rmidi);
22e3a8a5b7STakashi Iwai static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi);
23e3a8a5b7STakashi Iwai static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
24e3a8a5b7STakashi Iwai void __user *argp);
25fa030f66STakashi Iwai static void snd_ump_proc_read(struct snd_info_entry *entry,
26fa030f66STakashi Iwai struct snd_info_buffer *buffer);
276b41e64aSTakashi Iwai static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream);
286b41e64aSTakashi Iwai static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream);
296b41e64aSTakashi Iwai static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
306b41e64aSTakashi Iwai int up);
316b41e64aSTakashi Iwai static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
32e3a8a5b7STakashi Iwai
3337e0e141STakashi Iwai static void ump_handle_stream_msg(struct snd_ump_endpoint *ump,
3437e0e141STakashi Iwai const u32 *buf, int size);
350b5288f5STakashi Iwai #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
360b5288f5STakashi Iwai static int process_legacy_output(struct snd_ump_endpoint *ump,
370b5288f5STakashi Iwai u32 *buffer, int count);
380b5288f5STakashi Iwai static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
390b5288f5STakashi Iwai int words);
408d891c86STakashi Iwai static void update_legacy_names(struct snd_ump_endpoint *ump);
410b5288f5STakashi Iwai #else
process_legacy_output(struct snd_ump_endpoint * ump,u32 * buffer,int count)420b5288f5STakashi Iwai static inline int process_legacy_output(struct snd_ump_endpoint *ump,
430b5288f5STakashi Iwai u32 *buffer, int count)
440b5288f5STakashi Iwai {
450b5288f5STakashi Iwai return 0;
460b5288f5STakashi Iwai }
process_legacy_input(struct snd_ump_endpoint * ump,const u32 * src,int words)470b5288f5STakashi Iwai static inline void process_legacy_input(struct snd_ump_endpoint *ump,
480b5288f5STakashi Iwai const u32 *src, int words)
490b5288f5STakashi Iwai {
500b5288f5STakashi Iwai }
update_legacy_names(struct snd_ump_endpoint * ump)518d891c86STakashi Iwai static inline void update_legacy_names(struct snd_ump_endpoint *ump)
528d891c86STakashi Iwai {
538d891c86STakashi Iwai }
540b5288f5STakashi Iwai #endif
550b5288f5STakashi Iwai
56e3a8a5b7STakashi Iwai static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
57e3a8a5b7STakashi Iwai .dev_register = snd_ump_dev_register,
58e3a8a5b7STakashi Iwai .dev_unregister = snd_ump_dev_unregister,
59e3a8a5b7STakashi Iwai .ioctl = snd_ump_ioctl,
60fa030f66STakashi Iwai .proc_read = snd_ump_proc_read,
61e3a8a5b7STakashi Iwai };
62e3a8a5b7STakashi Iwai
636b41e64aSTakashi Iwai static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = {
646b41e64aSTakashi Iwai .open = snd_ump_rawmidi_open,
656b41e64aSTakashi Iwai .close = snd_ump_rawmidi_close,
666b41e64aSTakashi Iwai .trigger = snd_ump_rawmidi_trigger,
676b41e64aSTakashi Iwai };
686b41e64aSTakashi Iwai
696b41e64aSTakashi Iwai static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = {
706b41e64aSTakashi Iwai .open = snd_ump_rawmidi_open,
716b41e64aSTakashi Iwai .close = snd_ump_rawmidi_close,
726b41e64aSTakashi Iwai .trigger = snd_ump_rawmidi_trigger,
736b41e64aSTakashi Iwai .drain = snd_ump_rawmidi_drain,
746b41e64aSTakashi Iwai };
756b41e64aSTakashi Iwai
snd_ump_endpoint_free(struct snd_rawmidi * rmidi)76e3a8a5b7STakashi Iwai static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
77e3a8a5b7STakashi Iwai {
78e3a8a5b7STakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
79e3a8a5b7STakashi Iwai struct snd_ump_block *fb;
80e3a8a5b7STakashi Iwai
81e3a8a5b7STakashi Iwai while (!list_empty(&ump->block_list)) {
82e3a8a5b7STakashi Iwai fb = list_first_entry(&ump->block_list, struct snd_ump_block,
83e3a8a5b7STakashi Iwai list);
84e3a8a5b7STakashi Iwai list_del(&fb->list);
85e3a8a5b7STakashi Iwai if (fb->private_free)
86e3a8a5b7STakashi Iwai fb->private_free(fb);
87e3a8a5b7STakashi Iwai kfree(fb);
88e3a8a5b7STakashi Iwai }
89e3a8a5b7STakashi Iwai
90e3a8a5b7STakashi Iwai if (ump->private_free)
91e3a8a5b7STakashi Iwai ump->private_free(ump);
920b5288f5STakashi Iwai
930b5288f5STakashi Iwai #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
9433cd7630STakashi Iwai kfree(ump->out_cvts);
950b5288f5STakashi Iwai #endif
96e3a8a5b7STakashi Iwai }
97e3a8a5b7STakashi Iwai
98e3a8a5b7STakashi Iwai /**
99e3a8a5b7STakashi Iwai * snd_ump_endpoint_new - create a UMP Endpoint object
100e3a8a5b7STakashi Iwai * @card: the card instance
101e3a8a5b7STakashi Iwai * @id: the id string for rawmidi
102e3a8a5b7STakashi Iwai * @device: the device index for rawmidi
103e3a8a5b7STakashi Iwai * @output: 1 for enabling output
104e3a8a5b7STakashi Iwai * @input: 1 for enabling input
105e3a8a5b7STakashi Iwai * @ump_ret: the pointer to store the new UMP instance
106e3a8a5b7STakashi Iwai *
107e3a8a5b7STakashi Iwai * Creates a new UMP Endpoint object. A UMP Endpoint is tied with one rawmidi
108e3a8a5b7STakashi Iwai * instance with one input and/or one output rawmidi stream (either uni-
109e3a8a5b7STakashi Iwai * or bi-directional). A UMP Endpoint may contain one or multiple UMP Blocks
110e3a8a5b7STakashi Iwai * that consist of one or multiple UMP Groups.
111e3a8a5b7STakashi Iwai *
112e3a8a5b7STakashi Iwai * Use snd_rawmidi_set_ops() to set the operators to the new instance.
113e3a8a5b7STakashi Iwai * Unlike snd_rawmidi_new(), this function sets up the info_flags by itself
114e3a8a5b7STakashi Iwai * depending on the given @output and @input.
115e3a8a5b7STakashi Iwai *
116e3a8a5b7STakashi Iwai * The device has SNDRV_RAWMIDI_INFO_UMP flag set and a different device
117e3a8a5b7STakashi Iwai * file ("umpCxDx") than a standard MIDI 1.x device ("midiCxDx") is
118e3a8a5b7STakashi Iwai * created.
119e3a8a5b7STakashi Iwai *
120e3a8a5b7STakashi Iwai * Return: Zero if successful, or a negative error code on failure.
121e3a8a5b7STakashi Iwai */
snd_ump_endpoint_new(struct snd_card * card,char * id,int device,int output,int input,struct snd_ump_endpoint ** ump_ret)122e3a8a5b7STakashi Iwai int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
123e3a8a5b7STakashi Iwai int output, int input,
124e3a8a5b7STakashi Iwai struct snd_ump_endpoint **ump_ret)
125e3a8a5b7STakashi Iwai {
126e3a8a5b7STakashi Iwai unsigned int info_flags = SNDRV_RAWMIDI_INFO_UMP;
127e3a8a5b7STakashi Iwai struct snd_ump_endpoint *ump;
128e3a8a5b7STakashi Iwai int err;
129e3a8a5b7STakashi Iwai
130e3a8a5b7STakashi Iwai if (input)
131e3a8a5b7STakashi Iwai info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
132e3a8a5b7STakashi Iwai if (output)
133e3a8a5b7STakashi Iwai info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
134e3a8a5b7STakashi Iwai if (input && output)
135e3a8a5b7STakashi Iwai info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
136e3a8a5b7STakashi Iwai
137e3a8a5b7STakashi Iwai ump = kzalloc(sizeof(*ump), GFP_KERNEL);
138e3a8a5b7STakashi Iwai if (!ump)
139e3a8a5b7STakashi Iwai return -ENOMEM;
140e3a8a5b7STakashi Iwai INIT_LIST_HEAD(&ump->block_list);
1410b5288f5STakashi Iwai mutex_init(&ump->open_mutex);
14237e0e141STakashi Iwai init_waitqueue_head(&ump->stream_wait);
14381fd444aSTakashi Iwai #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
1440b5288f5STakashi Iwai spin_lock_init(&ump->legacy_locks[0]);
1450b5288f5STakashi Iwai spin_lock_init(&ump->legacy_locks[1]);
1460b5288f5STakashi Iwai #endif
147e3a8a5b7STakashi Iwai err = snd_rawmidi_init(&ump->core, card, id, device,
148e3a8a5b7STakashi Iwai output, input, info_flags);
149e3a8a5b7STakashi Iwai if (err < 0) {
150e3a8a5b7STakashi Iwai snd_rawmidi_free(&ump->core);
151e3a8a5b7STakashi Iwai return err;
152e3a8a5b7STakashi Iwai }
153e3a8a5b7STakashi Iwai
154e3a8a5b7STakashi Iwai ump->info.card = card->number;
155e3a8a5b7STakashi Iwai ump->info.device = device;
156e3a8a5b7STakashi Iwai
157e3a8a5b7STakashi Iwai ump->core.private_free = snd_ump_endpoint_free;
158e3a8a5b7STakashi Iwai ump->core.ops = &snd_ump_rawmidi_ops;
1596b41e64aSTakashi Iwai if (input)
1606b41e64aSTakashi Iwai snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,
1616b41e64aSTakashi Iwai &snd_ump_rawmidi_input_ops);
1626b41e64aSTakashi Iwai if (output)
1636b41e64aSTakashi Iwai snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,
1646b41e64aSTakashi Iwai &snd_ump_rawmidi_output_ops);
165e3a8a5b7STakashi Iwai
166e3a8a5b7STakashi Iwai ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id);
167e3a8a5b7STakashi Iwai *ump_ret = ump;
168e3a8a5b7STakashi Iwai return 0;
169e3a8a5b7STakashi Iwai }
170e3a8a5b7STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_endpoint_new);
171e3a8a5b7STakashi Iwai
172e3a8a5b7STakashi Iwai /*
173e3a8a5b7STakashi Iwai * Device register / unregister hooks;
174e3a8a5b7STakashi Iwai * do nothing, placeholders for avoiding the default rawmidi handling
175e3a8a5b7STakashi Iwai */
17681fd444aSTakashi Iwai
17781fd444aSTakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
snd_ump_dev_seq_free(struct snd_seq_device * device)17881fd444aSTakashi Iwai static void snd_ump_dev_seq_free(struct snd_seq_device *device)
17981fd444aSTakashi Iwai {
18081fd444aSTakashi Iwai struct snd_ump_endpoint *ump = device->private_data;
18181fd444aSTakashi Iwai
18281fd444aSTakashi Iwai ump->seq_dev = NULL;
18381fd444aSTakashi Iwai }
18481fd444aSTakashi Iwai #endif
18581fd444aSTakashi Iwai
snd_ump_dev_register(struct snd_rawmidi * rmidi)186e3a8a5b7STakashi Iwai static int snd_ump_dev_register(struct snd_rawmidi *rmidi)
187e3a8a5b7STakashi Iwai {
18881fd444aSTakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
18981fd444aSTakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
19081fd444aSTakashi Iwai int err;
19181fd444aSTakashi Iwai
19281fd444aSTakashi Iwai err = snd_seq_device_new(ump->core.card, ump->core.device,
19381fd444aSTakashi Iwai SNDRV_SEQ_DEV_ID_UMP, 0, &ump->seq_dev);
19481fd444aSTakashi Iwai if (err < 0)
19581fd444aSTakashi Iwai return err;
19681fd444aSTakashi Iwai ump->seq_dev->private_data = ump;
19781fd444aSTakashi Iwai ump->seq_dev->private_free = snd_ump_dev_seq_free;
19881fd444aSTakashi Iwai snd_device_register(ump->core.card, ump->seq_dev);
19981fd444aSTakashi Iwai #endif
200e3a8a5b7STakashi Iwai return 0;
201e3a8a5b7STakashi Iwai }
202e3a8a5b7STakashi Iwai
snd_ump_dev_unregister(struct snd_rawmidi * rmidi)203e3a8a5b7STakashi Iwai static int snd_ump_dev_unregister(struct snd_rawmidi *rmidi)
204e3a8a5b7STakashi Iwai {
205e3a8a5b7STakashi Iwai return 0;
206e3a8a5b7STakashi Iwai }
207e3a8a5b7STakashi Iwai
208e3a8a5b7STakashi Iwai static struct snd_ump_block *
snd_ump_get_block(struct snd_ump_endpoint * ump,unsigned char id)209e3a8a5b7STakashi Iwai snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id)
210e3a8a5b7STakashi Iwai {
211e3a8a5b7STakashi Iwai struct snd_ump_block *fb;
212e3a8a5b7STakashi Iwai
213e3a8a5b7STakashi Iwai list_for_each_entry(fb, &ump->block_list, list) {
214e3a8a5b7STakashi Iwai if (fb->info.block_id == id)
215e3a8a5b7STakashi Iwai return fb;
216e3a8a5b7STakashi Iwai }
217e3a8a5b7STakashi Iwai return NULL;
218e3a8a5b7STakashi Iwai }
219e3a8a5b7STakashi Iwai
2206b41e64aSTakashi Iwai /*
2216b41e64aSTakashi Iwai * rawmidi ops for UMP endpoint
2226b41e64aSTakashi Iwai */
snd_ump_rawmidi_open(struct snd_rawmidi_substream * substream)2236b41e64aSTakashi Iwai static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream)
2246b41e64aSTakashi Iwai {
2256b41e64aSTakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
2266b41e64aSTakashi Iwai int dir = substream->stream;
2276b41e64aSTakashi Iwai int err;
2286b41e64aSTakashi Iwai
2296b41e64aSTakashi Iwai if (ump->substreams[dir])
2306b41e64aSTakashi Iwai return -EBUSY;
2316b41e64aSTakashi Iwai err = ump->ops->open(ump, dir);
2326b41e64aSTakashi Iwai if (err < 0)
2336b41e64aSTakashi Iwai return err;
2346b41e64aSTakashi Iwai ump->substreams[dir] = substream;
2356b41e64aSTakashi Iwai return 0;
2366b41e64aSTakashi Iwai }
2376b41e64aSTakashi Iwai
snd_ump_rawmidi_close(struct snd_rawmidi_substream * substream)2386b41e64aSTakashi Iwai static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream)
2396b41e64aSTakashi Iwai {
2406b41e64aSTakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
2416b41e64aSTakashi Iwai int dir = substream->stream;
2426b41e64aSTakashi Iwai
2436b41e64aSTakashi Iwai ump->substreams[dir] = NULL;
2446b41e64aSTakashi Iwai ump->ops->close(ump, dir);
2456b41e64aSTakashi Iwai return 0;
2466b41e64aSTakashi Iwai }
2476b41e64aSTakashi Iwai
snd_ump_rawmidi_trigger(struct snd_rawmidi_substream * substream,int up)2486b41e64aSTakashi Iwai static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
2496b41e64aSTakashi Iwai int up)
2506b41e64aSTakashi Iwai {
2516b41e64aSTakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
2526b41e64aSTakashi Iwai int dir = substream->stream;
2536b41e64aSTakashi Iwai
2546b41e64aSTakashi Iwai ump->ops->trigger(ump, dir, up);
2556b41e64aSTakashi Iwai }
2566b41e64aSTakashi Iwai
snd_ump_rawmidi_drain(struct snd_rawmidi_substream * substream)2576b41e64aSTakashi Iwai static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
2586b41e64aSTakashi Iwai {
2596b41e64aSTakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
2606b41e64aSTakashi Iwai
2616b41e64aSTakashi Iwai if (ump->ops->drain)
2626b41e64aSTakashi Iwai ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
2636b41e64aSTakashi Iwai }
2646b41e64aSTakashi Iwai
2650b5288f5STakashi Iwai /* number of 32bit words per message type */
2660b5288f5STakashi Iwai static unsigned char ump_packet_words[0x10] = {
2670b5288f5STakashi Iwai 1, 1, 1, 2, 2, 4, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4
2680b5288f5STakashi Iwai };
2690b5288f5STakashi Iwai
2704dce2f07STakashi Iwai /**
2714dce2f07STakashi Iwai * snd_ump_receive_ump_val - parse the UMP packet data
2724dce2f07STakashi Iwai * @ump: UMP endpoint
2734dce2f07STakashi Iwai * @val: UMP packet data
2744dce2f07STakashi Iwai *
2754dce2f07STakashi Iwai * The data is copied onto ump->input_buf[].
2760b5288f5STakashi Iwai * When a full packet is completed, returns the number of words (from 1 to 4).
2770b5288f5STakashi Iwai * OTOH, if the packet is incomplete, returns 0.
2780b5288f5STakashi Iwai */
snd_ump_receive_ump_val(struct snd_ump_endpoint * ump,u32 val)2794dce2f07STakashi Iwai int snd_ump_receive_ump_val(struct snd_ump_endpoint *ump, u32 val)
2800b5288f5STakashi Iwai {
2810b5288f5STakashi Iwai int words;
2820b5288f5STakashi Iwai
2830b5288f5STakashi Iwai if (!ump->input_pending)
2840b5288f5STakashi Iwai ump->input_pending = ump_packet_words[ump_message_type(val)];
2850b5288f5STakashi Iwai
2860b5288f5STakashi Iwai ump->input_buf[ump->input_buf_head++] = val;
2870b5288f5STakashi Iwai ump->input_pending--;
2880b5288f5STakashi Iwai if (!ump->input_pending) {
2890b5288f5STakashi Iwai words = ump->input_buf_head;
2900b5288f5STakashi Iwai ump->input_buf_head = 0;
2910b5288f5STakashi Iwai return words;
2920b5288f5STakashi Iwai }
2930b5288f5STakashi Iwai return 0;
2940b5288f5STakashi Iwai }
2954dce2f07STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_receive_ump_val);
2960b5288f5STakashi Iwai
2976b41e64aSTakashi Iwai /**
2986b41e64aSTakashi Iwai * snd_ump_receive - transfer UMP packets from the device
2996b41e64aSTakashi Iwai * @ump: the UMP endpoint
3006b41e64aSTakashi Iwai * @buffer: the buffer pointer to transfer
3016b41e64aSTakashi Iwai * @count: byte size to transfer
3026b41e64aSTakashi Iwai *
3036b41e64aSTakashi Iwai * Called from the driver to submit the received UMP packets from the device
3046b41e64aSTakashi Iwai * to user-space. It's essentially a wrapper of rawmidi_receive().
3056b41e64aSTakashi Iwai * The data to receive is in CPU-native endianness.
3066b41e64aSTakashi Iwai */
snd_ump_receive(struct snd_ump_endpoint * ump,const u32 * buffer,int count)3076b41e64aSTakashi Iwai int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
3086b41e64aSTakashi Iwai {
3090b5288f5STakashi Iwai struct snd_rawmidi_substream *substream;
3100b5288f5STakashi Iwai const u32 *p = buffer;
3110b5288f5STakashi Iwai int n, words = count >> 2;
3126b41e64aSTakashi Iwai
3130b5288f5STakashi Iwai while (words--) {
3140b5288f5STakashi Iwai n = snd_ump_receive_ump_val(ump, *p++);
3150b5288f5STakashi Iwai if (!n)
3160b5288f5STakashi Iwai continue;
31737e0e141STakashi Iwai ump_handle_stream_msg(ump, ump->input_buf, n);
31881fd444aSTakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
31981fd444aSTakashi Iwai if (ump->seq_ops)
32081fd444aSTakashi Iwai ump->seq_ops->input_receive(ump, ump->input_buf, n);
32181fd444aSTakashi Iwai #endif
3220b5288f5STakashi Iwai process_legacy_input(ump, ump->input_buf, n);
3230b5288f5STakashi Iwai }
3240b5288f5STakashi Iwai
3250b5288f5STakashi Iwai substream = ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
3266b41e64aSTakashi Iwai if (!substream)
3276b41e64aSTakashi Iwai return 0;
3286b41e64aSTakashi Iwai return snd_rawmidi_receive(substream, (const char *)buffer, count);
3296b41e64aSTakashi Iwai }
3306b41e64aSTakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_receive);
3316b41e64aSTakashi Iwai
3326b41e64aSTakashi Iwai /**
3336b41e64aSTakashi Iwai * snd_ump_transmit - transmit UMP packets
3346b41e64aSTakashi Iwai * @ump: the UMP endpoint
3356b41e64aSTakashi Iwai * @buffer: the buffer pointer to transfer
3366b41e64aSTakashi Iwai * @count: byte size to transfer
3376b41e64aSTakashi Iwai *
3386b41e64aSTakashi Iwai * Called from the driver to obtain the UMP packets from user-space to the
3396b41e64aSTakashi Iwai * device. It's essentially a wrapper of rawmidi_transmit().
3406b41e64aSTakashi Iwai * The data to transmit is in CPU-native endianness.
3416b41e64aSTakashi Iwai */
snd_ump_transmit(struct snd_ump_endpoint * ump,u32 * buffer,int count)3426b41e64aSTakashi Iwai int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count)
3436b41e64aSTakashi Iwai {
3446b41e64aSTakashi Iwai struct snd_rawmidi_substream *substream =
3456b41e64aSTakashi Iwai ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];
3460b5288f5STakashi Iwai int err;
3476b41e64aSTakashi Iwai
3486b41e64aSTakashi Iwai if (!substream)
3496b41e64aSTakashi Iwai return -ENODEV;
3500b5288f5STakashi Iwai err = snd_rawmidi_transmit(substream, (char *)buffer, count);
3510b5288f5STakashi Iwai /* received either data or an error? */
3520b5288f5STakashi Iwai if (err)
3530b5288f5STakashi Iwai return err;
3540b5288f5STakashi Iwai return process_legacy_output(ump, buffer, count);
3556b41e64aSTakashi Iwai }
3566b41e64aSTakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_transmit);
3576b41e64aSTakashi Iwai
358e3a8a5b7STakashi Iwai /**
359e3a8a5b7STakashi Iwai * snd_ump_block_new - Create a UMP block
360e3a8a5b7STakashi Iwai * @ump: UMP object
361e3a8a5b7STakashi Iwai * @blk: block ID number to create
362e3a8a5b7STakashi Iwai * @direction: direction (in/out/bidirection)
363e3a8a5b7STakashi Iwai * @first_group: the first group ID (0-based)
364e3a8a5b7STakashi Iwai * @num_groups: the number of groups in this block
365e3a8a5b7STakashi Iwai * @blk_ret: the pointer to store the resultant block object
366e3a8a5b7STakashi Iwai */
snd_ump_block_new(struct snd_ump_endpoint * ump,unsigned int blk,unsigned int direction,unsigned int first_group,unsigned int num_groups,struct snd_ump_block ** blk_ret)367e3a8a5b7STakashi Iwai int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
368e3a8a5b7STakashi Iwai unsigned int direction, unsigned int first_group,
369e3a8a5b7STakashi Iwai unsigned int num_groups, struct snd_ump_block **blk_ret)
370e3a8a5b7STakashi Iwai {
371e3a8a5b7STakashi Iwai struct snd_ump_block *fb, *p;
372e3a8a5b7STakashi Iwai
373e3a8a5b7STakashi Iwai if (blk < 0 || blk >= SNDRV_UMP_MAX_BLOCKS)
374e3a8a5b7STakashi Iwai return -EINVAL;
375e3a8a5b7STakashi Iwai
376e3a8a5b7STakashi Iwai if (snd_ump_get_block(ump, blk))
377e3a8a5b7STakashi Iwai return -EBUSY;
378e3a8a5b7STakashi Iwai
379e3a8a5b7STakashi Iwai fb = kzalloc(sizeof(*fb), GFP_KERNEL);
380e3a8a5b7STakashi Iwai if (!fb)
381e3a8a5b7STakashi Iwai return -ENOMEM;
382e3a8a5b7STakashi Iwai
383e3a8a5b7STakashi Iwai fb->ump = ump;
384e3a8a5b7STakashi Iwai fb->info.card = ump->info.card;
385e3a8a5b7STakashi Iwai fb->info.device = ump->info.device;
386e3a8a5b7STakashi Iwai fb->info.block_id = blk;
387e3a8a5b7STakashi Iwai if (blk >= ump->info.num_blocks)
388e3a8a5b7STakashi Iwai ump->info.num_blocks = blk + 1;
389e3a8a5b7STakashi Iwai fb->info.direction = direction;
390e3a8a5b7STakashi Iwai fb->info.active = 1;
391e3a8a5b7STakashi Iwai fb->info.first_group = first_group;
392e3a8a5b7STakashi Iwai fb->info.num_groups = num_groups;
393e3a8a5b7STakashi Iwai /* fill the default name, may be overwritten to a better name */
394e3a8a5b7STakashi Iwai snprintf(fb->info.name, sizeof(fb->info.name), "Group %d-%d",
395e3a8a5b7STakashi Iwai first_group + 1, first_group + num_groups);
396e3a8a5b7STakashi Iwai
397e3a8a5b7STakashi Iwai /* put the entry in the ordered list */
398e3a8a5b7STakashi Iwai list_for_each_entry(p, &ump->block_list, list) {
399e3a8a5b7STakashi Iwai if (p->info.block_id > blk) {
400e3a8a5b7STakashi Iwai list_add_tail(&fb->list, &p->list);
401e3a8a5b7STakashi Iwai goto added;
402e3a8a5b7STakashi Iwai }
403e3a8a5b7STakashi Iwai }
404e3a8a5b7STakashi Iwai list_add_tail(&fb->list, &ump->block_list);
405e3a8a5b7STakashi Iwai
406e3a8a5b7STakashi Iwai added:
407e3a8a5b7STakashi Iwai ump_dbg(ump, "Created a UMP Block #%d (%s)\n", blk, fb->info.name);
408e3a8a5b7STakashi Iwai *blk_ret = fb;
409e3a8a5b7STakashi Iwai return 0;
410e3a8a5b7STakashi Iwai }
411e3a8a5b7STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_block_new);
412e3a8a5b7STakashi Iwai
snd_ump_ioctl_block(struct snd_ump_endpoint * ump,struct snd_ump_block_info __user * argp)413e3a8a5b7STakashi Iwai static int snd_ump_ioctl_block(struct snd_ump_endpoint *ump,
414e3a8a5b7STakashi Iwai struct snd_ump_block_info __user *argp)
415e3a8a5b7STakashi Iwai {
416e3a8a5b7STakashi Iwai struct snd_ump_block *fb;
417e3a8a5b7STakashi Iwai unsigned char id;
418e3a8a5b7STakashi Iwai
419e3a8a5b7STakashi Iwai if (get_user(id, &argp->block_id))
420e3a8a5b7STakashi Iwai return -EFAULT;
421e3a8a5b7STakashi Iwai fb = snd_ump_get_block(ump, id);
422e3a8a5b7STakashi Iwai if (!fb)
423e3a8a5b7STakashi Iwai return -ENOENT;
424e3a8a5b7STakashi Iwai if (copy_to_user(argp, &fb->info, sizeof(fb->info)))
425e3a8a5b7STakashi Iwai return -EFAULT;
426e3a8a5b7STakashi Iwai return 0;
427e3a8a5b7STakashi Iwai }
428e3a8a5b7STakashi Iwai
429e3a8a5b7STakashi Iwai /*
430e3a8a5b7STakashi Iwai * Handle UMP-specific ioctls; called from snd_rawmidi_ioctl()
431e3a8a5b7STakashi Iwai */
snd_ump_ioctl(struct snd_rawmidi * rmidi,unsigned int cmd,void __user * argp)432e3a8a5b7STakashi Iwai static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
433e3a8a5b7STakashi Iwai void __user *argp)
434e3a8a5b7STakashi Iwai {
435e3a8a5b7STakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
436e3a8a5b7STakashi Iwai
437e3a8a5b7STakashi Iwai switch (cmd) {
438e3a8a5b7STakashi Iwai case SNDRV_UMP_IOCTL_ENDPOINT_INFO:
439e3a8a5b7STakashi Iwai if (copy_to_user(argp, &ump->info, sizeof(ump->info)))
440e3a8a5b7STakashi Iwai return -EFAULT;
441e3a8a5b7STakashi Iwai return 0;
442e3a8a5b7STakashi Iwai case SNDRV_UMP_IOCTL_BLOCK_INFO:
443e3a8a5b7STakashi Iwai return snd_ump_ioctl_block(ump, argp);
444e3a8a5b7STakashi Iwai default:
445e3a8a5b7STakashi Iwai ump_dbg(ump, "rawmidi: unknown command = 0x%x\n", cmd);
446e3a8a5b7STakashi Iwai return -ENOTTY;
447e3a8a5b7STakashi Iwai }
448e3a8a5b7STakashi Iwai }
449e3a8a5b7STakashi Iwai
ump_direction_string(int dir)450fa030f66STakashi Iwai static const char *ump_direction_string(int dir)
451fa030f66STakashi Iwai {
452fa030f66STakashi Iwai switch (dir) {
453fa030f66STakashi Iwai case SNDRV_UMP_DIR_INPUT:
454fa030f66STakashi Iwai return "input";
455fa030f66STakashi Iwai case SNDRV_UMP_DIR_OUTPUT:
456fa030f66STakashi Iwai return "output";
457fa030f66STakashi Iwai case SNDRV_UMP_DIR_BIDIRECTION:
458fa030f66STakashi Iwai return "bidirection";
459fa030f66STakashi Iwai default:
460fa030f66STakashi Iwai return "unknown";
461fa030f66STakashi Iwai }
462fa030f66STakashi Iwai }
463fa030f66STakashi Iwai
ump_ui_hint_string(int dir)464e375b8a0STakashi Iwai static const char *ump_ui_hint_string(int dir)
465e375b8a0STakashi Iwai {
466e375b8a0STakashi Iwai switch (dir) {
467e375b8a0STakashi Iwai case SNDRV_UMP_BLOCK_UI_HINT_RECEIVER:
468e375b8a0STakashi Iwai return "receiver";
469e375b8a0STakashi Iwai case SNDRV_UMP_BLOCK_UI_HINT_SENDER:
470e375b8a0STakashi Iwai return "sender";
471e375b8a0STakashi Iwai case SNDRV_UMP_BLOCK_UI_HINT_BOTH:
472e375b8a0STakashi Iwai return "both";
473e375b8a0STakashi Iwai default:
474e375b8a0STakashi Iwai return "unknown";
475e375b8a0STakashi Iwai }
476e375b8a0STakashi Iwai }
477e375b8a0STakashi Iwai
478fa030f66STakashi Iwai /* Additional proc file output */
snd_ump_proc_read(struct snd_info_entry * entry,struct snd_info_buffer * buffer)479fa030f66STakashi Iwai static void snd_ump_proc_read(struct snd_info_entry *entry,
480fa030f66STakashi Iwai struct snd_info_buffer *buffer)
481fa030f66STakashi Iwai {
482fa030f66STakashi Iwai struct snd_rawmidi *rmidi = entry->private_data;
483fa030f66STakashi Iwai struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
484fa030f66STakashi Iwai struct snd_ump_block *fb;
485fa030f66STakashi Iwai
486fa030f66STakashi Iwai snd_iprintf(buffer, "EP Name: %s\n", ump->info.name);
487fa030f66STakashi Iwai snd_iprintf(buffer, "EP Product ID: %s\n", ump->info.product_id);
488fa030f66STakashi Iwai snd_iprintf(buffer, "UMP Version: 0x%04x\n", ump->info.version);
489fa030f66STakashi Iwai snd_iprintf(buffer, "Protocol Caps: 0x%08x\n", ump->info.protocol_caps);
490fa030f66STakashi Iwai snd_iprintf(buffer, "Protocol: 0x%08x\n", ump->info.protocol);
491e375b8a0STakashi Iwai if (ump->info.version) {
492e375b8a0STakashi Iwai snd_iprintf(buffer, "Manufacturer ID: 0x%08x\n",
493e375b8a0STakashi Iwai ump->info.manufacturer_id);
494e375b8a0STakashi Iwai snd_iprintf(buffer, "Family ID: 0x%04x\n", ump->info.family_id);
495e375b8a0STakashi Iwai snd_iprintf(buffer, "Model ID: 0x%04x\n", ump->info.model_id);
496e375b8a0STakashi Iwai snd_iprintf(buffer, "SW Revision: 0x%02x%02x%02x%02x\n",
497e375b8a0STakashi Iwai ump->info.sw_revision[0],
498e375b8a0STakashi Iwai ump->info.sw_revision[1],
499e375b8a0STakashi Iwai ump->info.sw_revision[2],
500e375b8a0STakashi Iwai ump->info.sw_revision[3]);
501e375b8a0STakashi Iwai }
50201dfa8e9STakashi Iwai snd_iprintf(buffer, "Static Blocks: %s\n",
50301dfa8e9STakashi Iwai (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) ? "Yes" : "No");
504fa030f66STakashi Iwai snd_iprintf(buffer, "Num Blocks: %d\n\n", ump->info.num_blocks);
505fa030f66STakashi Iwai
506fa030f66STakashi Iwai list_for_each_entry(fb, &ump->block_list, list) {
507fa030f66STakashi Iwai snd_iprintf(buffer, "Block %d (%s)\n", fb->info.block_id,
508fa030f66STakashi Iwai fb->info.name);
509fa030f66STakashi Iwai snd_iprintf(buffer, " Direction: %s\n",
510fa030f66STakashi Iwai ump_direction_string(fb->info.direction));
511fa030f66STakashi Iwai snd_iprintf(buffer, " Active: %s\n",
512fa030f66STakashi Iwai fb->info.active ? "Yes" : "No");
513fa030f66STakashi Iwai snd_iprintf(buffer, " Groups: %d-%d\n",
514fa030f66STakashi Iwai fb->info.first_group + 1,
515fa030f66STakashi Iwai fb->info.first_group + fb->info.num_groups);
516fa030f66STakashi Iwai snd_iprintf(buffer, " Is MIDI1: %s%s\n",
517fa030f66STakashi Iwai (fb->info.flags & SNDRV_UMP_BLOCK_IS_MIDI1) ? "Yes" : "No",
518fa030f66STakashi Iwai (fb->info.flags & SNDRV_UMP_BLOCK_IS_LOWSPEED) ? " (Low Speed)" : "");
519e375b8a0STakashi Iwai if (ump->info.version) {
520e375b8a0STakashi Iwai snd_iprintf(buffer, " MIDI-CI Version: %d\n",
521e375b8a0STakashi Iwai fb->info.midi_ci_version);
522e375b8a0STakashi Iwai snd_iprintf(buffer, " Sysex8 Streams: %d\n",
523e375b8a0STakashi Iwai fb->info.sysex8_streams);
524e375b8a0STakashi Iwai snd_iprintf(buffer, " UI Hint: %s\n",
525e375b8a0STakashi Iwai ump_ui_hint_string(fb->info.ui_hint));
526e375b8a0STakashi Iwai }
527fa030f66STakashi Iwai snd_iprintf(buffer, "\n");
528fa030f66STakashi Iwai }
529fa030f66STakashi Iwai }
530fa030f66STakashi Iwai
5318ddb4126STakashi Iwai /* update dir_bits and active flag for all groups in the client */
snd_ump_update_group_attrs(struct snd_ump_endpoint * ump)5328bb7b689STakashi Iwai void snd_ump_update_group_attrs(struct snd_ump_endpoint *ump)
5338ddb4126STakashi Iwai {
5348ddb4126STakashi Iwai struct snd_ump_block *fb;
5358ddb4126STakashi Iwai struct snd_ump_group *group;
5368ddb4126STakashi Iwai int i;
5378ddb4126STakashi Iwai
5388ddb4126STakashi Iwai for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++) {
5398ddb4126STakashi Iwai group = &ump->groups[i];
5408ddb4126STakashi Iwai *group->name = 0;
5418ddb4126STakashi Iwai group->dir_bits = 0;
5428ddb4126STakashi Iwai group->active = 0;
5438ddb4126STakashi Iwai group->group = i;
5448ddb4126STakashi Iwai group->valid = false;
5458ddb4126STakashi Iwai }
5468ddb4126STakashi Iwai
5478ddb4126STakashi Iwai list_for_each_entry(fb, &ump->block_list, list) {
5488ddb4126STakashi Iwai if (fb->info.first_group + fb->info.num_groups > SNDRV_UMP_MAX_GROUPS)
5498ddb4126STakashi Iwai break;
5508ddb4126STakashi Iwai group = &ump->groups[fb->info.first_group];
5518ddb4126STakashi Iwai for (i = 0; i < fb->info.num_groups; i++, group++) {
5528ddb4126STakashi Iwai group->valid = true;
5538ddb4126STakashi Iwai if (fb->info.active)
5548ddb4126STakashi Iwai group->active = 1;
5558ddb4126STakashi Iwai switch (fb->info.direction) {
5568ddb4126STakashi Iwai case SNDRV_UMP_DIR_INPUT:
5578ddb4126STakashi Iwai group->dir_bits |= (1 << SNDRV_RAWMIDI_STREAM_INPUT);
5588ddb4126STakashi Iwai break;
5598ddb4126STakashi Iwai case SNDRV_UMP_DIR_OUTPUT:
5608ddb4126STakashi Iwai group->dir_bits |= (1 << SNDRV_RAWMIDI_STREAM_OUTPUT);
5618ddb4126STakashi Iwai break;
5628ddb4126STakashi Iwai case SNDRV_UMP_DIR_BIDIRECTION:
5638ddb4126STakashi Iwai group->dir_bits |= (1 << SNDRV_RAWMIDI_STREAM_INPUT) |
5648ddb4126STakashi Iwai (1 << SNDRV_RAWMIDI_STREAM_OUTPUT);
5658ddb4126STakashi Iwai break;
5668ddb4126STakashi Iwai }
5678ddb4126STakashi Iwai if (!*fb->info.name)
5688ddb4126STakashi Iwai continue;
5698ddb4126STakashi Iwai if (!*group->name) {
5708ddb4126STakashi Iwai /* store the first matching name */
5718ddb4126STakashi Iwai strscpy(group->name, fb->info.name,
5728ddb4126STakashi Iwai sizeof(group->name));
5738ddb4126STakashi Iwai } else {
5748ddb4126STakashi Iwai /* when overlapping, concat names */
5758ddb4126STakashi Iwai strlcat(group->name, ", ", sizeof(group->name));
5768ddb4126STakashi Iwai strlcat(group->name, fb->info.name,
5778ddb4126STakashi Iwai sizeof(group->name));
5788ddb4126STakashi Iwai }
5798ddb4126STakashi Iwai }
5808ddb4126STakashi Iwai }
5818ddb4126STakashi Iwai }
5828bb7b689STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_update_group_attrs);
5838ddb4126STakashi Iwai
58437e0e141STakashi Iwai /*
58537e0e141STakashi Iwai * UMP endpoint and function block handling
58637e0e141STakashi Iwai */
58737e0e141STakashi Iwai
58837e0e141STakashi Iwai /* open / close UMP streams for the internal stream msg communication */
ump_request_open(struct snd_ump_endpoint * ump)58937e0e141STakashi Iwai static int ump_request_open(struct snd_ump_endpoint *ump)
59037e0e141STakashi Iwai {
59137e0e141STakashi Iwai return snd_rawmidi_kernel_open(&ump->core, 0,
59237e0e141STakashi Iwai SNDRV_RAWMIDI_LFLG_OUTPUT,
59337e0e141STakashi Iwai &ump->stream_rfile);
59437e0e141STakashi Iwai }
59537e0e141STakashi Iwai
ump_request_close(struct snd_ump_endpoint * ump)59637e0e141STakashi Iwai static void ump_request_close(struct snd_ump_endpoint *ump)
59737e0e141STakashi Iwai {
59837e0e141STakashi Iwai snd_rawmidi_kernel_release(&ump->stream_rfile);
59937e0e141STakashi Iwai }
60037e0e141STakashi Iwai
60137e0e141STakashi Iwai /* request a command and wait for the given response;
60237e0e141STakashi Iwai * @req1 and @req2 are u32 commands
60337e0e141STakashi Iwai * @reply is the expected UMP stream status
60437e0e141STakashi Iwai */
ump_req_msg(struct snd_ump_endpoint * ump,u32 req1,u32 req2,u32 reply)60537e0e141STakashi Iwai static int ump_req_msg(struct snd_ump_endpoint *ump, u32 req1, u32 req2,
60637e0e141STakashi Iwai u32 reply)
60737e0e141STakashi Iwai {
60837e0e141STakashi Iwai u32 buf[4];
60937e0e141STakashi Iwai
61037e0e141STakashi Iwai ump_dbg(ump, "%s: request %08x %08x, wait-for %08x\n",
61137e0e141STakashi Iwai __func__, req1, req2, reply);
61237e0e141STakashi Iwai memset(buf, 0, sizeof(buf));
61337e0e141STakashi Iwai buf[0] = req1;
61437e0e141STakashi Iwai buf[1] = req2;
61537e0e141STakashi Iwai ump->stream_finished = 0;
61637e0e141STakashi Iwai ump->stream_wait_for = reply;
61737e0e141STakashi Iwai snd_rawmidi_kernel_write(ump->stream_rfile.output,
61837e0e141STakashi Iwai (unsigned char *)&buf, 16);
61937e0e141STakashi Iwai wait_event_timeout(ump->stream_wait, ump->stream_finished,
62037e0e141STakashi Iwai msecs_to_jiffies(500));
62137e0e141STakashi Iwai if (!READ_ONCE(ump->stream_finished)) {
62237e0e141STakashi Iwai ump_dbg(ump, "%s: request timed out\n", __func__);
62337e0e141STakashi Iwai return -ETIMEDOUT;
62437e0e141STakashi Iwai }
62537e0e141STakashi Iwai ump->stream_finished = 0;
62637e0e141STakashi Iwai ump_dbg(ump, "%s: reply: %08x %08x %08x %08x\n",
62737e0e141STakashi Iwai __func__, buf[0], buf[1], buf[2], buf[3]);
62837e0e141STakashi Iwai return 0;
62937e0e141STakashi Iwai }
63037e0e141STakashi Iwai
63137e0e141STakashi Iwai /* append the received letters via UMP packet to the given string buffer;
63237e0e141STakashi Iwai * return 1 if the full string is received or 0 to continue
63337e0e141STakashi Iwai */
ump_append_string(struct snd_ump_endpoint * ump,char * dest,int maxsize,const u32 * buf,int offset)63437e0e141STakashi Iwai static int ump_append_string(struct snd_ump_endpoint *ump, char *dest,
63537e0e141STakashi Iwai int maxsize, const u32 *buf, int offset)
63637e0e141STakashi Iwai {
63737e0e141STakashi Iwai unsigned char format;
63837e0e141STakashi Iwai int c;
63937e0e141STakashi Iwai
64037e0e141STakashi Iwai format = ump_stream_message_format(buf[0]);
64137e0e141STakashi Iwai if (format == UMP_STREAM_MSG_FORMAT_SINGLE ||
64237e0e141STakashi Iwai format == UMP_STREAM_MSG_FORMAT_START) {
64337e0e141STakashi Iwai c = 0;
64437e0e141STakashi Iwai } else {
64537e0e141STakashi Iwai c = strlen(dest);
64637e0e141STakashi Iwai if (c >= maxsize - 1)
64737e0e141STakashi Iwai return 1;
64837e0e141STakashi Iwai }
64937e0e141STakashi Iwai
65037e0e141STakashi Iwai for (; offset < 16; offset++) {
65137e0e141STakashi Iwai dest[c] = buf[offset / 4] >> (3 - (offset % 4)) * 8;
65237e0e141STakashi Iwai if (!dest[c])
65337e0e141STakashi Iwai break;
65437e0e141STakashi Iwai if (++c >= maxsize - 1)
65537e0e141STakashi Iwai break;
65637e0e141STakashi Iwai }
65737e0e141STakashi Iwai dest[c] = 0;
65837e0e141STakashi Iwai return (format == UMP_STREAM_MSG_FORMAT_SINGLE ||
65937e0e141STakashi Iwai format == UMP_STREAM_MSG_FORMAT_END);
66037e0e141STakashi Iwai }
66137e0e141STakashi Iwai
66237e0e141STakashi Iwai /* handle EP info stream message; update the UMP attributes */
ump_handle_ep_info_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)66337e0e141STakashi Iwai static int ump_handle_ep_info_msg(struct snd_ump_endpoint *ump,
66437e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
66537e0e141STakashi Iwai {
66637e0e141STakashi Iwai ump->info.version = (buf->ep_info.ump_version_major << 8) |
66737e0e141STakashi Iwai buf->ep_info.ump_version_minor;
66837e0e141STakashi Iwai ump->info.num_blocks = buf->ep_info.num_function_blocks;
66937e0e141STakashi Iwai if (ump->info.num_blocks > SNDRV_UMP_MAX_BLOCKS) {
67037e0e141STakashi Iwai ump_info(ump, "Invalid function blocks %d, fallback to 1\n",
67137e0e141STakashi Iwai ump->info.num_blocks);
67237e0e141STakashi Iwai ump->info.num_blocks = 1;
67337e0e141STakashi Iwai }
67437e0e141STakashi Iwai
67501dfa8e9STakashi Iwai if (buf->ep_info.static_function_block)
67601dfa8e9STakashi Iwai ump->info.flags |= SNDRV_UMP_EP_INFO_STATIC_BLOCKS;
67701dfa8e9STakashi Iwai
67837e0e141STakashi Iwai ump->info.protocol_caps = (buf->ep_info.protocol << 8) |
67937e0e141STakashi Iwai buf->ep_info.jrts;
68037e0e141STakashi Iwai
68137e0e141STakashi Iwai ump_dbg(ump, "EP info: version=%x, num_blocks=%x, proto_caps=%x\n",
68237e0e141STakashi Iwai ump->info.version, ump->info.num_blocks, ump->info.protocol_caps);
68337e0e141STakashi Iwai return 1; /* finished */
68437e0e141STakashi Iwai }
68537e0e141STakashi Iwai
68637e0e141STakashi Iwai /* handle EP device info stream message; update the UMP attributes */
ump_handle_device_info_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)68737e0e141STakashi Iwai static int ump_handle_device_info_msg(struct snd_ump_endpoint *ump,
68837e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
68937e0e141STakashi Iwai {
69037e0e141STakashi Iwai ump->info.manufacturer_id = buf->device_info.manufacture_id & 0x7f7f7f;
69137e0e141STakashi Iwai ump->info.family_id = (buf->device_info.family_msb << 8) |
69237e0e141STakashi Iwai buf->device_info.family_lsb;
69337e0e141STakashi Iwai ump->info.model_id = (buf->device_info.model_msb << 8) |
69437e0e141STakashi Iwai buf->device_info.model_lsb;
69537e0e141STakashi Iwai ump->info.sw_revision[0] = (buf->device_info.sw_revision >> 24) & 0x7f;
69637e0e141STakashi Iwai ump->info.sw_revision[1] = (buf->device_info.sw_revision >> 16) & 0x7f;
69737e0e141STakashi Iwai ump->info.sw_revision[2] = (buf->device_info.sw_revision >> 8) & 0x7f;
69837e0e141STakashi Iwai ump->info.sw_revision[3] = buf->device_info.sw_revision & 0x7f;
69937e0e141STakashi Iwai ump_dbg(ump, "EP devinfo: manid=%08x, family=%04x, model=%04x, sw=%02x%02x%02x%02x\n",
70037e0e141STakashi Iwai ump->info.manufacturer_id,
70137e0e141STakashi Iwai ump->info.family_id,
70237e0e141STakashi Iwai ump->info.model_id,
70337e0e141STakashi Iwai ump->info.sw_revision[0],
70437e0e141STakashi Iwai ump->info.sw_revision[1],
70537e0e141STakashi Iwai ump->info.sw_revision[2],
70637e0e141STakashi Iwai ump->info.sw_revision[3]);
70737e0e141STakashi Iwai return 1; /* finished */
70837e0e141STakashi Iwai }
70937e0e141STakashi Iwai
71037e0e141STakashi Iwai /* handle EP name stream message; update the UMP name string */
ump_handle_ep_name_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)71137e0e141STakashi Iwai static int ump_handle_ep_name_msg(struct snd_ump_endpoint *ump,
71237e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
71337e0e141STakashi Iwai {
71437e0e141STakashi Iwai return ump_append_string(ump, ump->info.name, sizeof(ump->info.name),
71537e0e141STakashi Iwai buf->raw, 2);
71637e0e141STakashi Iwai }
71737e0e141STakashi Iwai
71837e0e141STakashi Iwai /* handle EP product id stream message; update the UMP product_id string */
ump_handle_product_id_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)71937e0e141STakashi Iwai static int ump_handle_product_id_msg(struct snd_ump_endpoint *ump,
72037e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
72137e0e141STakashi Iwai {
72237e0e141STakashi Iwai return ump_append_string(ump, ump->info.product_id,
72337e0e141STakashi Iwai sizeof(ump->info.product_id),
72437e0e141STakashi Iwai buf->raw, 2);
72537e0e141STakashi Iwai }
72637e0e141STakashi Iwai
7276a8b4800STakashi Iwai /* notify the protocol change to sequencer */
seq_notify_protocol(struct snd_ump_endpoint * ump)7286a8b4800STakashi Iwai static void seq_notify_protocol(struct snd_ump_endpoint *ump)
7296a8b4800STakashi Iwai {
7306a8b4800STakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
7316a8b4800STakashi Iwai if (ump->seq_ops && ump->seq_ops->switch_protocol)
7326a8b4800STakashi Iwai ump->seq_ops->switch_protocol(ump);
7336a8b4800STakashi Iwai #endif /* CONFIG_SND_SEQUENCER */
7346a8b4800STakashi Iwai }
7356a8b4800STakashi Iwai
736a7980768STakashi Iwai /**
737a7980768STakashi Iwai * snd_ump_switch_protocol - switch MIDI protocol
738a7980768STakashi Iwai * @ump: UMP endpoint
739a7980768STakashi Iwai * @protocol: protocol to switch to
740a7980768STakashi Iwai *
741a7980768STakashi Iwai * Returns 1 if the protocol is actually switched, 0 if unchanged
742a7980768STakashi Iwai */
snd_ump_switch_protocol(struct snd_ump_endpoint * ump,unsigned int protocol)743a7980768STakashi Iwai int snd_ump_switch_protocol(struct snd_ump_endpoint *ump, unsigned int protocol)
744a7980768STakashi Iwai {
7450a900727STakashi Iwai unsigned int type;
7460a900727STakashi Iwai
747a7980768STakashi Iwai protocol &= ump->info.protocol_caps;
748a7980768STakashi Iwai if (protocol == ump->info.protocol)
749a7980768STakashi Iwai return 0;
750a7980768STakashi Iwai
7510a900727STakashi Iwai type = protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK;
7520a900727STakashi Iwai if (type != SNDRV_UMP_EP_INFO_PROTO_MIDI1 &&
7530a900727STakashi Iwai type != SNDRV_UMP_EP_INFO_PROTO_MIDI2)
7540a900727STakashi Iwai return 0;
7550a900727STakashi Iwai
756a7980768STakashi Iwai ump->info.protocol = protocol;
757a7980768STakashi Iwai ump_dbg(ump, "New protocol = %x (caps = %x)\n",
758a7980768STakashi Iwai protocol, ump->info.protocol_caps);
759a7980768STakashi Iwai seq_notify_protocol(ump);
760a7980768STakashi Iwai return 1;
761a7980768STakashi Iwai }
762a7980768STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_switch_protocol);
763a7980768STakashi Iwai
76437e0e141STakashi Iwai /* handle EP stream config message; update the UMP protocol */
ump_handle_stream_cfg_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)76537e0e141STakashi Iwai static int ump_handle_stream_cfg_msg(struct snd_ump_endpoint *ump,
76637e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
76737e0e141STakashi Iwai {
768a7980768STakashi Iwai unsigned int protocol =
76937e0e141STakashi Iwai (buf->stream_cfg.protocol << 8) | buf->stream_cfg.jrts;
770a7980768STakashi Iwai
771a7980768STakashi Iwai snd_ump_switch_protocol(ump, protocol);
77237e0e141STakashi Iwai return 1; /* finished */
77337e0e141STakashi Iwai }
77437e0e141STakashi Iwai
77537e0e141STakashi Iwai /* Extract Function Block info from UMP packet */
fill_fb_info(struct snd_ump_endpoint * ump,struct snd_ump_block_info * info,const union snd_ump_stream_msg * buf)77637e0e141STakashi Iwai static void fill_fb_info(struct snd_ump_endpoint *ump,
77737e0e141STakashi Iwai struct snd_ump_block_info *info,
77837e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
77937e0e141STakashi Iwai {
78037e0e141STakashi Iwai info->direction = buf->fb_info.direction;
78137e0e141STakashi Iwai info->ui_hint = buf->fb_info.ui_hint;
78237e0e141STakashi Iwai info->first_group = buf->fb_info.first_group;
78337e0e141STakashi Iwai info->num_groups = buf->fb_info.num_groups;
7840357abf9STakashi Iwai if (buf->fb_info.midi_10 < 2)
78537e0e141STakashi Iwai info->flags = buf->fb_info.midi_10;
7860357abf9STakashi Iwai else
7870357abf9STakashi Iwai info->flags = SNDRV_UMP_BLOCK_IS_MIDI1 | SNDRV_UMP_BLOCK_IS_LOWSPEED;
78837e0e141STakashi Iwai info->active = buf->fb_info.active;
78937e0e141STakashi Iwai info->midi_ci_version = buf->fb_info.midi_ci_version;
79037e0e141STakashi Iwai info->sysex8_streams = buf->fb_info.sysex8_streams;
79137e0e141STakashi Iwai
79237e0e141STakashi Iwai ump_dbg(ump, "FB %d: dir=%d, active=%d, first_gp=%d, num_gp=%d, midici=%d, sysex8=%d, flags=0x%x\n",
79337e0e141STakashi Iwai info->block_id, info->direction, info->active,
79437e0e141STakashi Iwai info->first_group, info->num_groups, info->midi_ci_version,
79537e0e141STakashi Iwai info->sysex8_streams, info->flags);
796689e0780STakashi Iwai
797689e0780STakashi Iwai if ((info->flags & SNDRV_UMP_BLOCK_IS_MIDI1) && info->num_groups != 1) {
798689e0780STakashi Iwai info->num_groups = 1;
799689e0780STakashi Iwai ump_dbg(ump, "FB %d: corrected groups to 1 for MIDI1\n",
800689e0780STakashi Iwai info->block_id);
801689e0780STakashi Iwai }
80237e0e141STakashi Iwai }
80337e0e141STakashi Iwai
8044a16a3afSTakashi Iwai /* check whether the FB info gets updated by the current message */
is_fb_info_updated(struct snd_ump_endpoint * ump,struct snd_ump_block * fb,const union snd_ump_stream_msg * buf)8054a16a3afSTakashi Iwai static bool is_fb_info_updated(struct snd_ump_endpoint *ump,
8064a16a3afSTakashi Iwai struct snd_ump_block *fb,
8074a16a3afSTakashi Iwai const union snd_ump_stream_msg *buf)
8084a16a3afSTakashi Iwai {
8094a16a3afSTakashi Iwai char tmpbuf[offsetof(struct snd_ump_block_info, name)];
8104a16a3afSTakashi Iwai
81101dfa8e9STakashi Iwai if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {
81201dfa8e9STakashi Iwai ump_info(ump, "Skipping static FB info update (blk#%d)\n",
81301dfa8e9STakashi Iwai fb->info.block_id);
81401dfa8e9STakashi Iwai return 0;
81501dfa8e9STakashi Iwai }
81601dfa8e9STakashi Iwai
8174a16a3afSTakashi Iwai memcpy(tmpbuf, &fb->info, sizeof(tmpbuf));
8184a16a3afSTakashi Iwai fill_fb_info(ump, (struct snd_ump_block_info *)tmpbuf, buf);
8194a16a3afSTakashi Iwai return memcmp(&fb->info, tmpbuf, sizeof(tmpbuf)) != 0;
8204a16a3afSTakashi Iwai }
8214a16a3afSTakashi Iwai
8224a16a3afSTakashi Iwai /* notify the FB info/name change to sequencer */
seq_notify_fb_change(struct snd_ump_endpoint * ump,struct snd_ump_block * fb)8234a16a3afSTakashi Iwai static void seq_notify_fb_change(struct snd_ump_endpoint *ump,
8244a16a3afSTakashi Iwai struct snd_ump_block *fb)
8254a16a3afSTakashi Iwai {
8264a16a3afSTakashi Iwai #if IS_ENABLED(CONFIG_SND_SEQUENCER)
8274a16a3afSTakashi Iwai if (ump->seq_ops && ump->seq_ops->notify_fb_change)
8284a16a3afSTakashi Iwai ump->seq_ops->notify_fb_change(ump, fb);
8294a16a3afSTakashi Iwai #endif
8304a16a3afSTakashi Iwai }
8314a16a3afSTakashi Iwai
83237e0e141STakashi Iwai /* handle FB info message; update FB info if the block is present */
ump_handle_fb_info_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)83337e0e141STakashi Iwai static int ump_handle_fb_info_msg(struct snd_ump_endpoint *ump,
83437e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
83537e0e141STakashi Iwai {
83637e0e141STakashi Iwai unsigned char blk;
83737e0e141STakashi Iwai struct snd_ump_block *fb;
83837e0e141STakashi Iwai
83937e0e141STakashi Iwai blk = buf->fb_info.function_block_id;
84037e0e141STakashi Iwai fb = snd_ump_get_block(ump, blk);
8414a16a3afSTakashi Iwai
84237e0e141STakashi Iwai /* complain only if updated after parsing */
8434a16a3afSTakashi Iwai if (!fb && ump->parsed) {
84437e0e141STakashi Iwai ump_info(ump, "Function Block Info Update for non-existing block %d\n",
84537e0e141STakashi Iwai blk);
84637e0e141STakashi Iwai return -ENODEV;
84737e0e141STakashi Iwai }
8484a16a3afSTakashi Iwai
8494a16a3afSTakashi Iwai /* When updated after the initial parse, check the FB info update */
8504a16a3afSTakashi Iwai if (ump->parsed && !is_fb_info_updated(ump, fb, buf))
8514a16a3afSTakashi Iwai return 1; /* no content change */
8524a16a3afSTakashi Iwai
8534a16a3afSTakashi Iwai if (fb) {
8544a16a3afSTakashi Iwai fill_fb_info(ump, &fb->info, buf);
8558ddb4126STakashi Iwai if (ump->parsed) {
8568bb7b689STakashi Iwai snd_ump_update_group_attrs(ump);
8578d891c86STakashi Iwai update_legacy_names(ump);
8584a16a3afSTakashi Iwai seq_notify_fb_change(ump, fb);
8594a16a3afSTakashi Iwai }
8608ddb4126STakashi Iwai }
8614a16a3afSTakashi Iwai
86237e0e141STakashi Iwai return 1; /* finished */
86337e0e141STakashi Iwai }
86437e0e141STakashi Iwai
86537e0e141STakashi Iwai /* handle FB name message; update the FB name string */
ump_handle_fb_name_msg(struct snd_ump_endpoint * ump,const union snd_ump_stream_msg * buf)86637e0e141STakashi Iwai static int ump_handle_fb_name_msg(struct snd_ump_endpoint *ump,
86737e0e141STakashi Iwai const union snd_ump_stream_msg *buf)
86837e0e141STakashi Iwai {
86937e0e141STakashi Iwai unsigned char blk;
87037e0e141STakashi Iwai struct snd_ump_block *fb;
8714a16a3afSTakashi Iwai int ret;
87237e0e141STakashi Iwai
87337e0e141STakashi Iwai blk = buf->fb_name.function_block_id;
87437e0e141STakashi Iwai fb = snd_ump_get_block(ump, blk);
87537e0e141STakashi Iwai if (!fb)
87637e0e141STakashi Iwai return -ENODEV;
87737e0e141STakashi Iwai
878ad4ab148STakashi Iwai if (ump->parsed &&
879ad4ab148STakashi Iwai (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS)) {
880ad4ab148STakashi Iwai ump_dbg(ump, "Skipping static FB name update (blk#%d)\n",
881ad4ab148STakashi Iwai fb->info.block_id);
882ad4ab148STakashi Iwai return 0;
883ad4ab148STakashi Iwai }
884ad4ab148STakashi Iwai
8854a16a3afSTakashi Iwai ret = ump_append_string(ump, fb->info.name, sizeof(fb->info.name),
88637e0e141STakashi Iwai buf->raw, 3);
8874a16a3afSTakashi Iwai /* notify the FB name update to sequencer, too */
8888ddb4126STakashi Iwai if (ret > 0 && ump->parsed) {
8898bb7b689STakashi Iwai snd_ump_update_group_attrs(ump);
8908d891c86STakashi Iwai update_legacy_names(ump);
8914a16a3afSTakashi Iwai seq_notify_fb_change(ump, fb);
8928ddb4126STakashi Iwai }
8934a16a3afSTakashi Iwai return ret;
89437e0e141STakashi Iwai }
89537e0e141STakashi Iwai
create_block_from_fb_info(struct snd_ump_endpoint * ump,int blk)89637e0e141STakashi Iwai static int create_block_from_fb_info(struct snd_ump_endpoint *ump, int blk)
89737e0e141STakashi Iwai {
89837e0e141STakashi Iwai struct snd_ump_block *fb;
89937e0e141STakashi Iwai unsigned char direction, first_group, num_groups;
90037e0e141STakashi Iwai const union snd_ump_stream_msg *buf =
90137e0e141STakashi Iwai (const union snd_ump_stream_msg *)ump->input_buf;
90237e0e141STakashi Iwai u32 msg;
90337e0e141STakashi Iwai int err;
90437e0e141STakashi Iwai
90537e0e141STakashi Iwai /* query the FB info once */
90637e0e141STakashi Iwai msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |
90737e0e141STakashi Iwai (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_INFO;
90837e0e141STakashi Iwai err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_INFO);
90937e0e141STakashi Iwai if (err < 0) {
91037e0e141STakashi Iwai ump_dbg(ump, "Unable to get FB info for block %d\n", blk);
91137e0e141STakashi Iwai return err;
91237e0e141STakashi Iwai }
91337e0e141STakashi Iwai
91437e0e141STakashi Iwai /* the last input must be the FB info */
91537e0e141STakashi Iwai if (buf->fb_info.status != UMP_STREAM_MSG_STATUS_FB_INFO) {
91637e0e141STakashi Iwai ump_dbg(ump, "Inconsistent input: 0x%x\n", *buf->raw);
91737e0e141STakashi Iwai return -EINVAL;
91837e0e141STakashi Iwai }
91937e0e141STakashi Iwai
92037e0e141STakashi Iwai direction = buf->fb_info.direction;
92137e0e141STakashi Iwai first_group = buf->fb_info.first_group;
92237e0e141STakashi Iwai num_groups = buf->fb_info.num_groups;
92337e0e141STakashi Iwai
92437e0e141STakashi Iwai err = snd_ump_block_new(ump, blk, direction, first_group, num_groups,
92537e0e141STakashi Iwai &fb);
92637e0e141STakashi Iwai if (err < 0)
92737e0e141STakashi Iwai return err;
92837e0e141STakashi Iwai
92937e0e141STakashi Iwai fill_fb_info(ump, &fb->info, buf);
93037e0e141STakashi Iwai
93137e0e141STakashi Iwai msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_FB_DISCOVERY, 0) |
93237e0e141STakashi Iwai (blk << 8) | UMP_STREAM_MSG_REQUEST_FB_NAME;
93337e0e141STakashi Iwai err = ump_req_msg(ump, msg, 0, UMP_STREAM_MSG_STATUS_FB_NAME);
93437e0e141STakashi Iwai if (err)
93537e0e141STakashi Iwai ump_dbg(ump, "Unable to get UMP FB name string #%d\n", blk);
93637e0e141STakashi Iwai
93737e0e141STakashi Iwai return 0;
93837e0e141STakashi Iwai }
93937e0e141STakashi Iwai
94037e0e141STakashi Iwai /* handle stream messages, called from snd_ump_receive() */
ump_handle_stream_msg(struct snd_ump_endpoint * ump,const u32 * buf,int size)94137e0e141STakashi Iwai static void ump_handle_stream_msg(struct snd_ump_endpoint *ump,
94237e0e141STakashi Iwai const u32 *buf, int size)
94337e0e141STakashi Iwai {
94437e0e141STakashi Iwai const union snd_ump_stream_msg *msg;
94537e0e141STakashi Iwai unsigned int status;
94637e0e141STakashi Iwai int ret;
94737e0e141STakashi Iwai
948eacd9c7fSTakashi Iwai /* UMP stream message suppressed (for gadget UMP)? */
949eacd9c7fSTakashi Iwai if (ump->no_process_stream)
950eacd9c7fSTakashi Iwai return;
951eacd9c7fSTakashi Iwai
95237e0e141STakashi Iwai BUILD_BUG_ON(sizeof(*msg) != 16);
95337e0e141STakashi Iwai ump_dbg(ump, "Stream msg: %08x %08x %08x %08x\n",
95437e0e141STakashi Iwai buf[0], buf[1], buf[2], buf[3]);
95537e0e141STakashi Iwai
95637e0e141STakashi Iwai if (size != 4 || ump_message_type(*buf) != UMP_MSG_TYPE_STREAM)
95737e0e141STakashi Iwai return;
95837e0e141STakashi Iwai
95937e0e141STakashi Iwai msg = (const union snd_ump_stream_msg *)buf;
96037e0e141STakashi Iwai status = ump_stream_message_status(*buf);
96137e0e141STakashi Iwai switch (status) {
96237e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_EP_INFO:
96337e0e141STakashi Iwai ret = ump_handle_ep_info_msg(ump, msg);
96437e0e141STakashi Iwai break;
96537e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_DEVICE_INFO:
96637e0e141STakashi Iwai ret = ump_handle_device_info_msg(ump, msg);
96737e0e141STakashi Iwai break;
96837e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_EP_NAME:
96937e0e141STakashi Iwai ret = ump_handle_ep_name_msg(ump, msg);
97037e0e141STakashi Iwai break;
97137e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_PRODUCT_ID:
97237e0e141STakashi Iwai ret = ump_handle_product_id_msg(ump, msg);
97337e0e141STakashi Iwai break;
97437e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_STREAM_CFG:
97537e0e141STakashi Iwai ret = ump_handle_stream_cfg_msg(ump, msg);
97637e0e141STakashi Iwai break;
97737e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_FB_INFO:
97837e0e141STakashi Iwai ret = ump_handle_fb_info_msg(ump, msg);
97937e0e141STakashi Iwai break;
98037e0e141STakashi Iwai case UMP_STREAM_MSG_STATUS_FB_NAME:
98137e0e141STakashi Iwai ret = ump_handle_fb_name_msg(ump, msg);
98237e0e141STakashi Iwai break;
98337e0e141STakashi Iwai default:
98437e0e141STakashi Iwai return;
98537e0e141STakashi Iwai }
98637e0e141STakashi Iwai
98737e0e141STakashi Iwai /* when the message has been processed fully, wake up */
98837e0e141STakashi Iwai if (ret > 0 && ump->stream_wait_for == status) {
98937e0e141STakashi Iwai WRITE_ONCE(ump->stream_finished, 1);
99037e0e141STakashi Iwai wake_up(&ump->stream_wait);
99137e0e141STakashi Iwai }
99237e0e141STakashi Iwai }
99337e0e141STakashi Iwai
99437e0e141STakashi Iwai /**
99537e0e141STakashi Iwai * snd_ump_parse_endpoint - parse endpoint and create function blocks
99637e0e141STakashi Iwai * @ump: UMP object
99737e0e141STakashi Iwai *
99837e0e141STakashi Iwai * Returns 0 for successful parse, -ENODEV if device doesn't respond
99937e0e141STakashi Iwai * (or the query is unsupported), or other error code for serious errors.
100037e0e141STakashi Iwai */
snd_ump_parse_endpoint(struct snd_ump_endpoint * ump)100137e0e141STakashi Iwai int snd_ump_parse_endpoint(struct snd_ump_endpoint *ump)
100237e0e141STakashi Iwai {
100337e0e141STakashi Iwai int blk, err;
100437e0e141STakashi Iwai u32 msg;
100537e0e141STakashi Iwai
100637e0e141STakashi Iwai if (!(ump->core.info_flags & SNDRV_RAWMIDI_INFO_DUPLEX))
100737e0e141STakashi Iwai return -ENODEV;
100837e0e141STakashi Iwai
100937e0e141STakashi Iwai err = ump_request_open(ump);
101037e0e141STakashi Iwai if (err < 0) {
101137e0e141STakashi Iwai ump_dbg(ump, "Unable to open rawmidi device: %d\n", err);
101237e0e141STakashi Iwai return err;
101337e0e141STakashi Iwai }
101437e0e141STakashi Iwai
101537e0e141STakashi Iwai /* Check Endpoint Information */
101637e0e141STakashi Iwai msg = ump_stream_compose(UMP_STREAM_MSG_STATUS_EP_DISCOVERY, 0) |
101737e0e141STakashi Iwai 0x0101; /* UMP version 1.1 */
101837e0e141STakashi Iwai err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_INFO,
101937e0e141STakashi Iwai UMP_STREAM_MSG_STATUS_EP_INFO);
102037e0e141STakashi Iwai if (err < 0) {
102137e0e141STakashi Iwai ump_dbg(ump, "Unable to get UMP EP info\n");
102237e0e141STakashi Iwai goto error;
102337e0e141STakashi Iwai }
102437e0e141STakashi Iwai
102537e0e141STakashi Iwai /* Request Endpoint Device Info */
102637e0e141STakashi Iwai err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_DEVICE_INFO,
102737e0e141STakashi Iwai UMP_STREAM_MSG_STATUS_DEVICE_INFO);
102837e0e141STakashi Iwai if (err < 0)
102937e0e141STakashi Iwai ump_dbg(ump, "Unable to get UMP EP device info\n");
103037e0e141STakashi Iwai
103137e0e141STakashi Iwai /* Request Endpoint Name */
103237e0e141STakashi Iwai err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_EP_NAME,
103337e0e141STakashi Iwai UMP_STREAM_MSG_STATUS_EP_NAME);
103437e0e141STakashi Iwai if (err < 0)
103537e0e141STakashi Iwai ump_dbg(ump, "Unable to get UMP EP name string\n");
103637e0e141STakashi Iwai
103737e0e141STakashi Iwai /* Request Endpoint Product ID */
103837e0e141STakashi Iwai err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_PRODUCT_ID,
103937e0e141STakashi Iwai UMP_STREAM_MSG_STATUS_PRODUCT_ID);
104037e0e141STakashi Iwai if (err < 0)
104137e0e141STakashi Iwai ump_dbg(ump, "Unable to get UMP EP product ID string\n");
104237e0e141STakashi Iwai
104337e0e141STakashi Iwai /* Get the current stream configuration */
104437e0e141STakashi Iwai err = ump_req_msg(ump, msg, UMP_STREAM_MSG_REQUEST_STREAM_CFG,
104537e0e141STakashi Iwai UMP_STREAM_MSG_STATUS_STREAM_CFG);
104637e0e141STakashi Iwai if (err < 0)
104737e0e141STakashi Iwai ump_dbg(ump, "Unable to get UMP EP stream config\n");
104837e0e141STakashi Iwai
104949458c09STakashi Iwai /* If no protocol is set by some reason, assume the valid one */
105049458c09STakashi Iwai if (!(ump->info.protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI_MASK)) {
105149458c09STakashi Iwai if (ump->info.protocol_caps & SNDRV_UMP_EP_INFO_PROTO_MIDI2)
105249458c09STakashi Iwai ump->info.protocol |= SNDRV_UMP_EP_INFO_PROTO_MIDI2;
105349458c09STakashi Iwai else if (ump->info.protocol_caps & SNDRV_UMP_EP_INFO_PROTO_MIDI1)
105449458c09STakashi Iwai ump->info.protocol |= SNDRV_UMP_EP_INFO_PROTO_MIDI1;
105549458c09STakashi Iwai }
105649458c09STakashi Iwai
105737e0e141STakashi Iwai /* Query and create blocks from Function Blocks */
105837e0e141STakashi Iwai for (blk = 0; blk < ump->info.num_blocks; blk++) {
105937e0e141STakashi Iwai err = create_block_from_fb_info(ump, blk);
106037e0e141STakashi Iwai if (err < 0)
106137e0e141STakashi Iwai continue;
106237e0e141STakashi Iwai }
106337e0e141STakashi Iwai
10648ddb4126STakashi Iwai /* initialize group attributions */
10658bb7b689STakashi Iwai snd_ump_update_group_attrs(ump);
10668ddb4126STakashi Iwai
106737e0e141STakashi Iwai error:
106837e0e141STakashi Iwai ump->parsed = true;
106937e0e141STakashi Iwai ump_request_close(ump);
107037e0e141STakashi Iwai if (err == -ETIMEDOUT)
107137e0e141STakashi Iwai err = -ENODEV;
107237e0e141STakashi Iwai return err;
107337e0e141STakashi Iwai }
107437e0e141STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_parse_endpoint);
107537e0e141STakashi Iwai
10760b5288f5STakashi Iwai #if IS_ENABLED(CONFIG_SND_UMP_LEGACY_RAWMIDI)
10770b5288f5STakashi Iwai /*
10780b5288f5STakashi Iwai * Legacy rawmidi support
10790b5288f5STakashi Iwai */
snd_ump_legacy_open(struct snd_rawmidi_substream * substream)10800b5288f5STakashi Iwai static int snd_ump_legacy_open(struct snd_rawmidi_substream *substream)
10810b5288f5STakashi Iwai {
10820b5288f5STakashi Iwai struct snd_ump_endpoint *ump = substream->rmidi->private_data;
10830b5288f5STakashi Iwai int dir = substream->stream;
1084b2bcbd03STakashi Iwai int group = ump->legacy_mapping[substream->number];
1085b5e175e1STakashi Iwai int err;
10860b5288f5STakashi Iwai
1087b5e175e1STakashi Iwai guard(mutex)(&ump->open_mutex);
1088b5e175e1STakashi Iwai if (ump->legacy_substreams[dir][group])
1089b5e175e1STakashi Iwai return -EBUSY;
1090cf29cbf6STakashi Iwai if (!ump->groups[group].active)
1091cf29cbf6STakashi Iwai return -ENODEV;
10920b5288f5STakashi Iwai if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
10930b5288f5STakashi Iwai if (!ump->legacy_out_opens) {
10940b5288f5STakashi Iwai err = snd_rawmidi_kernel_open(&ump->core, 0,
10950b5288f5STakashi Iwai SNDRV_RAWMIDI_LFLG_OUTPUT |
10960b5288f5STakashi Iwai SNDRV_RAWMIDI_LFLG_APPEND,
10970b5288f5STakashi Iwai &ump->legacy_out_rfile);
10980b5288f5STakashi Iwai if (err < 0)
1099b5e175e1STakashi Iwai return err;
11000b5288f5STakashi Iwai }
11010b5288f5STakashi Iwai ump->legacy_out_opens++;
110233cd7630STakashi Iwai snd_ump_convert_reset(&ump->out_cvts[group]);
11030b5288f5STakashi Iwai }
1104b5e175e1STakashi Iwai guard(spinlock_irq)(&ump->legacy_locks[dir]);
11050b5288f5STakashi Iwai ump->legacy_substreams[dir][group] = substream;
1106b5e175e1STakashi Iwai return 0;
11070b5288f5STakashi Iwai }
11080b5288f5STakashi Iwai
snd_ump_legacy_close(struct snd_rawmidi_substream * substream)11090b5288f5STakashi Iwai static int snd_ump_legacy_close(struct snd_rawmidi_substream *substream)
11100b5288f5STakashi Iwai {
11110b5288f5STakashi Iwai struct snd_ump_endpoint *ump = substream->rmidi->private_data;
11120b5288f5STakashi Iwai int dir = substream->stream;
1113b2bcbd03STakashi Iwai int group = ump->legacy_mapping[substream->number];
11140b5288f5STakashi Iwai
1115b5e175e1STakashi Iwai guard(mutex)(&ump->open_mutex);
1116b5e175e1STakashi Iwai scoped_guard(spinlock_irq, &ump->legacy_locks[dir])
11170b5288f5STakashi Iwai ump->legacy_substreams[dir][group] = NULL;
11180b5288f5STakashi Iwai if (dir == SNDRV_RAWMIDI_STREAM_OUTPUT) {
11190b5288f5STakashi Iwai if (!--ump->legacy_out_opens)
11200b5288f5STakashi Iwai snd_rawmidi_kernel_release(&ump->legacy_out_rfile);
11210b5288f5STakashi Iwai }
11220b5288f5STakashi Iwai return 0;
11230b5288f5STakashi Iwai }
11240b5288f5STakashi Iwai
snd_ump_legacy_trigger(struct snd_rawmidi_substream * substream,int up)11250b5288f5STakashi Iwai static void snd_ump_legacy_trigger(struct snd_rawmidi_substream *substream,
11260b5288f5STakashi Iwai int up)
11270b5288f5STakashi Iwai {
11280b5288f5STakashi Iwai struct snd_ump_endpoint *ump = substream->rmidi->private_data;
11290b5288f5STakashi Iwai int dir = substream->stream;
11300b5288f5STakashi Iwai
11310b5288f5STakashi Iwai ump->ops->trigger(ump, dir, up);
11320b5288f5STakashi Iwai }
11330b5288f5STakashi Iwai
snd_ump_legacy_drain(struct snd_rawmidi_substream * substream)11340b5288f5STakashi Iwai static void snd_ump_legacy_drain(struct snd_rawmidi_substream *substream)
11350b5288f5STakashi Iwai {
11360b5288f5STakashi Iwai struct snd_ump_endpoint *ump = substream->rmidi->private_data;
11370b5288f5STakashi Iwai
11380b5288f5STakashi Iwai if (ump->ops->drain)
11390b5288f5STakashi Iwai ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
11400b5288f5STakashi Iwai }
11410b5288f5STakashi Iwai
snd_ump_legacy_dev_register(struct snd_rawmidi * rmidi)11420b5288f5STakashi Iwai static int snd_ump_legacy_dev_register(struct snd_rawmidi *rmidi)
11430b5288f5STakashi Iwai {
11440b5288f5STakashi Iwai /* dummy, just for avoiding create superfluous seq clients */
11450b5288f5STakashi Iwai return 0;
11460b5288f5STakashi Iwai }
11470b5288f5STakashi Iwai
11480b5288f5STakashi Iwai static const struct snd_rawmidi_ops snd_ump_legacy_input_ops = {
11490b5288f5STakashi Iwai .open = snd_ump_legacy_open,
11500b5288f5STakashi Iwai .close = snd_ump_legacy_close,
11510b5288f5STakashi Iwai .trigger = snd_ump_legacy_trigger,
11520b5288f5STakashi Iwai };
11530b5288f5STakashi Iwai
11540b5288f5STakashi Iwai static const struct snd_rawmidi_ops snd_ump_legacy_output_ops = {
11550b5288f5STakashi Iwai .open = snd_ump_legacy_open,
11560b5288f5STakashi Iwai .close = snd_ump_legacy_close,
11570b5288f5STakashi Iwai .trigger = snd_ump_legacy_trigger,
11580b5288f5STakashi Iwai .drain = snd_ump_legacy_drain,
11590b5288f5STakashi Iwai };
11600b5288f5STakashi Iwai
11610b5288f5STakashi Iwai static const struct snd_rawmidi_global_ops snd_ump_legacy_ops = {
11620b5288f5STakashi Iwai .dev_register = snd_ump_legacy_dev_register,
11630b5288f5STakashi Iwai };
11640b5288f5STakashi Iwai
process_legacy_output(struct snd_ump_endpoint * ump,u32 * buffer,int count)11650b5288f5STakashi Iwai static int process_legacy_output(struct snd_ump_endpoint *ump,
11660b5288f5STakashi Iwai u32 *buffer, int count)
11670b5288f5STakashi Iwai {
11680b5288f5STakashi Iwai struct snd_rawmidi_substream *substream;
11690b5288f5STakashi Iwai struct ump_cvt_to_ump *ctx;
11700b5288f5STakashi Iwai const int dir = SNDRV_RAWMIDI_STREAM_OUTPUT;
11710b5288f5STakashi Iwai unsigned char c;
11720b5288f5STakashi Iwai int group, size = 0;
11730b5288f5STakashi Iwai
11740b5288f5STakashi Iwai if (!ump->out_cvts || !ump->legacy_out_opens)
11750b5288f5STakashi Iwai return 0;
11760b5288f5STakashi Iwai
1177b5e175e1STakashi Iwai guard(spinlock_irqsave)(&ump->legacy_locks[dir]);
11780b5288f5STakashi Iwai for (group = 0; group < SNDRV_UMP_MAX_GROUPS; group++) {
11790b5288f5STakashi Iwai substream = ump->legacy_substreams[dir][group];
11800b5288f5STakashi Iwai if (!substream)
11810b5288f5STakashi Iwai continue;
11820b5288f5STakashi Iwai ctx = &ump->out_cvts[group];
11830b5288f5STakashi Iwai while (!ctx->ump_bytes &&
11840b5288f5STakashi Iwai snd_rawmidi_transmit(substream, &c, 1) > 0)
118533cd7630STakashi Iwai snd_ump_convert_to_ump(ctx, group, ump->info.protocol, c);
11860b5288f5STakashi Iwai if (ctx->ump_bytes && ctx->ump_bytes <= count) {
11870b5288f5STakashi Iwai size = ctx->ump_bytes;
11880b5288f5STakashi Iwai memcpy(buffer, ctx->ump, size);
11890b5288f5STakashi Iwai ctx->ump_bytes = 0;
11900b5288f5STakashi Iwai break;
11910b5288f5STakashi Iwai }
11920b5288f5STakashi Iwai }
11930b5288f5STakashi Iwai return size;
11940b5288f5STakashi Iwai }
11950b5288f5STakashi Iwai
process_legacy_input(struct snd_ump_endpoint * ump,const u32 * src,int words)11960b5288f5STakashi Iwai static void process_legacy_input(struct snd_ump_endpoint *ump, const u32 *src,
11970b5288f5STakashi Iwai int words)
11980b5288f5STakashi Iwai {
11990b5288f5STakashi Iwai struct snd_rawmidi_substream *substream;
12000b5288f5STakashi Iwai unsigned char buf[16];
12010b5288f5STakashi Iwai unsigned char group;
12020b5288f5STakashi Iwai const int dir = SNDRV_RAWMIDI_STREAM_INPUT;
12030b5288f5STakashi Iwai int size;
12040b5288f5STakashi Iwai
120533cd7630STakashi Iwai size = snd_ump_convert_from_ump(src, buf, &group);
12060b5288f5STakashi Iwai if (size <= 0)
12070b5288f5STakashi Iwai return;
1208b5e175e1STakashi Iwai guard(spinlock_irqsave)(&ump->legacy_locks[dir]);
12090b5288f5STakashi Iwai substream = ump->legacy_substreams[dir][group];
12100b5288f5STakashi Iwai if (substream)
12110b5288f5STakashi Iwai snd_rawmidi_receive(substream, buf, size);
12120b5288f5STakashi Iwai }
12130b5288f5STakashi Iwai
1214b2bcbd03STakashi Iwai /* Fill ump->legacy_mapping[] for groups to be used for legacy rawmidi */
fill_legacy_mapping(struct snd_ump_endpoint * ump)1215b2bcbd03STakashi Iwai static int fill_legacy_mapping(struct snd_ump_endpoint *ump)
1216b2bcbd03STakashi Iwai {
1217b2bcbd03STakashi Iwai struct snd_ump_block *fb;
1218b2bcbd03STakashi Iwai unsigned int group_maps = 0;
1219b2bcbd03STakashi Iwai int i, num;
1220b2bcbd03STakashi Iwai
1221b2bcbd03STakashi Iwai if (ump->info.flags & SNDRV_UMP_EP_INFO_STATIC_BLOCKS) {
1222b2bcbd03STakashi Iwai list_for_each_entry(fb, &ump->block_list, list) {
1223b2bcbd03STakashi Iwai for (i = 0; i < fb->info.num_groups; i++)
1224b2bcbd03STakashi Iwai group_maps |= 1U << (fb->info.first_group + i);
1225b2bcbd03STakashi Iwai }
1226b2bcbd03STakashi Iwai if (!group_maps)
1227b2bcbd03STakashi Iwai ump_info(ump, "No UMP Group is found in FB\n");
1228b2bcbd03STakashi Iwai }
1229b2bcbd03STakashi Iwai
1230b2bcbd03STakashi Iwai /* use all groups for non-static case */
1231b2bcbd03STakashi Iwai if (!group_maps)
1232b2bcbd03STakashi Iwai group_maps = (1U << SNDRV_UMP_MAX_GROUPS) - 1;
1233b2bcbd03STakashi Iwai
1234b2bcbd03STakashi Iwai num = 0;
1235b2bcbd03STakashi Iwai for (i = 0; i < SNDRV_UMP_MAX_GROUPS; i++)
1236b2bcbd03STakashi Iwai if (group_maps & (1U << i))
1237b2bcbd03STakashi Iwai ump->legacy_mapping[num++] = i;
1238b2bcbd03STakashi Iwai
1239b2bcbd03STakashi Iwai return num;
1240b2bcbd03STakashi Iwai }
1241b2bcbd03STakashi Iwai
fill_substream_names(struct snd_ump_endpoint * ump,struct snd_rawmidi * rmidi,int dir)12421761f4ccSTakashi Iwai static void fill_substream_names(struct snd_ump_endpoint *ump,
12431761f4ccSTakashi Iwai struct snd_rawmidi *rmidi, int dir)
12441761f4ccSTakashi Iwai {
12451761f4ccSTakashi Iwai struct snd_rawmidi_substream *s;
12468ddb4126STakashi Iwai const char *name;
12478ddb4126STakashi Iwai int idx;
12481761f4ccSTakashi Iwai
12498ddb4126STakashi Iwai list_for_each_entry(s, &rmidi->streams[dir].substreams, list) {
12508ddb4126STakashi Iwai idx = ump->legacy_mapping[s->number];
12518ddb4126STakashi Iwai name = ump->groups[idx].name;
12528ddb4126STakashi Iwai if (!*name)
12538ddb4126STakashi Iwai name = ump->info.name;
1254*d4eb5b3cSTakashi Iwai scnprintf(s->name, sizeof(s->name), "Group %d (%.16s)%s",
12559617001aSTakashi Iwai idx + 1, name,
12569617001aSTakashi Iwai ump->groups[idx].active ? "" : " [Inactive]");
12578ddb4126STakashi Iwai }
12581761f4ccSTakashi Iwai }
12591761f4ccSTakashi Iwai
update_legacy_names(struct snd_ump_endpoint * ump)12608d891c86STakashi Iwai static void update_legacy_names(struct snd_ump_endpoint *ump)
12618d891c86STakashi Iwai {
12628d891c86STakashi Iwai struct snd_rawmidi *rmidi = ump->legacy_rmidi;
12638d891c86STakashi Iwai
12648d891c86STakashi Iwai fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_INPUT);
12658d891c86STakashi Iwai fill_substream_names(ump, rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT);
12668d891c86STakashi Iwai }
12678d891c86STakashi Iwai
snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint * ump,char * id,int device)12680b5288f5STakashi Iwai int snd_ump_attach_legacy_rawmidi(struct snd_ump_endpoint *ump,
12690b5288f5STakashi Iwai char *id, int device)
12700b5288f5STakashi Iwai {
12710b5288f5STakashi Iwai struct snd_rawmidi *rmidi;
12720b5288f5STakashi Iwai bool input, output;
1273b2bcbd03STakashi Iwai int err, num;
12740b5288f5STakashi Iwai
1275b2bcbd03STakashi Iwai ump->out_cvts = kcalloc(SNDRV_UMP_MAX_GROUPS,
1276b2bcbd03STakashi Iwai sizeof(*ump->out_cvts), GFP_KERNEL);
127733cd7630STakashi Iwai if (!ump->out_cvts)
127833cd7630STakashi Iwai return -ENOMEM;
12790b5288f5STakashi Iwai
1280b2bcbd03STakashi Iwai num = fill_legacy_mapping(ump);
1281b2bcbd03STakashi Iwai
12820b5288f5STakashi Iwai input = ump->core.info_flags & SNDRV_RAWMIDI_INFO_INPUT;
12830b5288f5STakashi Iwai output = ump->core.info_flags & SNDRV_RAWMIDI_INFO_OUTPUT;
12840b5288f5STakashi Iwai err = snd_rawmidi_new(ump->core.card, id, device,
1285b2bcbd03STakashi Iwai output ? num : 0, input ? num : 0,
12860b5288f5STakashi Iwai &rmidi);
12870b5288f5STakashi Iwai if (err < 0) {
128833cd7630STakashi Iwai kfree(ump->out_cvts);
12890b5288f5STakashi Iwai return err;
12900b5288f5STakashi Iwai }
12910b5288f5STakashi Iwai
12920b5288f5STakashi Iwai if (input)
12930b5288f5STakashi Iwai snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
12940b5288f5STakashi Iwai &snd_ump_legacy_input_ops);
12950b5288f5STakashi Iwai if (output)
12960b5288f5STakashi Iwai snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
12970b5288f5STakashi Iwai &snd_ump_legacy_output_ops);
12984aa69d64STakashi Iwai snprintf(rmidi->name, sizeof(rmidi->name), "%.68s (MIDI 1.0)",
12995f11dd93STakashi Iwai ump->info.name);
13000b5288f5STakashi Iwai rmidi->info_flags = ump->core.info_flags & ~SNDRV_RAWMIDI_INFO_UMP;
13010b5288f5STakashi Iwai rmidi->ops = &snd_ump_legacy_ops;
13020b5288f5STakashi Iwai rmidi->private_data = ump;
13030b5288f5STakashi Iwai ump->legacy_rmidi = rmidi;
13048d891c86STakashi Iwai update_legacy_names(ump);
13051761f4ccSTakashi Iwai
13060b5288f5STakashi Iwai ump_dbg(ump, "Created a legacy rawmidi #%d (%s)\n", device, id);
13070b5288f5STakashi Iwai return 0;
13080b5288f5STakashi Iwai }
13090b5288f5STakashi Iwai EXPORT_SYMBOL_GPL(snd_ump_attach_legacy_rawmidi);
13100b5288f5STakashi Iwai #endif /* CONFIG_SND_UMP_LEGACY_RAWMIDI */
13110b5288f5STakashi Iwai
1312e3a8a5b7STakashi Iwai MODULE_DESCRIPTION("Universal MIDI Packet (UMP) Core Driver");
1313e3a8a5b7STakashi Iwai MODULE_LICENSE("GPL");
1314