Commit | Line | Data |
---|---|---|
a10e763b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
705ececd | 2 | /* |
c078a4aa | 3 | * Line 6 Linux USB driver |
705ececd | 4 | * |
1027f476 | 5 | * Copyright (C) 2004-2010 Markus Grabner (grabner@icg.tugraz.at) |
705ececd MG |
6 | */ |
7 | ||
5a0e3ad6 | 8 | #include <linux/slab.h> |
1027f476 | 9 | #include <linux/usb.h> |
ccddbe4a | 10 | #include <linux/export.h> |
705ececd MG |
11 | #include <sound/core.h> |
12 | #include <sound/rawmidi.h> | |
13 | ||
1027f476 | 14 | #include "driver.h" |
705ececd | 15 | #include "midi.h" |
705ececd | 16 | |
d7e37336 GKH |
17 | #define line6_rawmidi_substream_midi(substream) \ |
18 | ((struct snd_line6_midi *)((substream)->rmidi->private_data)) | |
705ececd | 19 | |
d7e37336 GKH |
20 | static int send_midi_async(struct usb_line6 *line6, unsigned char *data, |
21 | int length); | |
705ececd | 22 | |
705ececd MG |
23 | /* |
24 | Pass data received via USB to MIDI. | |
25 | */ | |
d7e37336 GKH |
26 | void line6_midi_receive(struct usb_line6 *line6, unsigned char *data, |
27 | int length) | |
705ececd | 28 | { |
d7e37336 GKH |
29 | if (line6->line6midi->substream_receive) |
30 | snd_rawmidi_receive(line6->line6midi->substream_receive, | |
31 | data, length); | |
705ececd MG |
32 | } |
33 | ||
34 | /* | |
35 | Read data from MIDI buffer and transmit them via USB. | |
36 | */ | |
37 | static void line6_midi_transmit(struct snd_rawmidi_substream *substream) | |
38 | { | |
e1a164d7 MG |
39 | struct usb_line6 *line6 = |
40 | line6_rawmidi_substream_midi(substream)->line6; | |
705ececd | 41 | struct snd_line6_midi *line6midi = line6->line6midi; |
269edc8e | 42 | struct midi_buffer *mb = &line6midi->midibuf_out; |
f2459201 | 43 | unsigned char chunk[LINE6_FALLBACK_MAXPACKETSIZE]; |
705ececd MG |
44 | int req, done; |
45 | ||
d7e37336 | 46 | for (;;) { |
b8800d32 AE |
47 | req = min3(line6_midibuf_bytes_free(mb), line6->max_packet_size, |
48 | LINE6_FALLBACK_MAXPACKETSIZE); | |
705ececd MG |
49 | done = snd_rawmidi_transmit_peek(substream, chunk, req); |
50 | ||
d7e37336 | 51 | if (done == 0) |
705ececd MG |
52 | break; |
53 | ||
1027f476 | 54 | line6_midibuf_write(mb, chunk, done); |
705ececd MG |
55 | snd_rawmidi_transmit_ack(substream, done); |
56 | } | |
57 | ||
d7e37336 | 58 | for (;;) { |
64619003 | 59 | done = line6_midibuf_read(mb, chunk, |
8508fa2e AE |
60 | LINE6_FALLBACK_MAXPACKETSIZE, |
61 | LINE6_MIDIBUF_READ_TX); | |
705ececd | 62 | |
d7e37336 | 63 | if (done == 0) |
705ececd MG |
64 | break; |
65 | ||
705ececd MG |
66 | send_midi_async(line6, chunk, done); |
67 | } | |
705ececd MG |
68 | } |
69 | ||
70 | /* | |
71 | Notification of completion of MIDI transmission. | |
72 | */ | |
0c7ab158 | 73 | static void midi_sent(struct urb *urb) |
705ececd MG |
74 | { |
75 | unsigned long flags; | |
76 | int status; | |
77 | int num; | |
78 | struct usb_line6 *line6 = (struct usb_line6 *)urb->context; | |
79 | ||
80 | status = urb->status; | |
81 | kfree(urb->transfer_buffer); | |
82 | usb_free_urb(urb); | |
83 | ||
d7e37336 | 84 | if (status == -ESHUTDOWN) |
705ececd MG |
85 | return; |
86 | ||
bc518ba4 | 87 | spin_lock_irqsave(&line6->line6midi->lock, flags); |
705ececd MG |
88 | num = --line6->line6midi->num_active_send_urbs; |
89 | ||
d7e37336 | 90 | if (num == 0) { |
705ececd MG |
91 | line6_midi_transmit(line6->line6midi->substream_transmit); |
92 | num = line6->line6midi->num_active_send_urbs; | |
93 | } | |
94 | ||
d7e37336 | 95 | if (num == 0) |
1027f476 | 96 | wake_up(&line6->line6midi->send_wait); |
705ececd | 97 | |
bc518ba4 | 98 | spin_unlock_irqrestore(&line6->line6midi->lock, flags); |
705ececd MG |
99 | } |
100 | ||
101 | /* | |
102 | Send an asynchronous MIDI message. | |
bc518ba4 | 103 | Assumes that line6->line6midi->lock is held |
705ececd MG |
104 | (i.e., this function is serialized). |
105 | */ | |
d7e37336 GKH |
106 | static int send_midi_async(struct usb_line6 *line6, unsigned char *data, |
107 | int length) | |
705ececd MG |
108 | { |
109 | struct urb *urb; | |
110 | int retval; | |
111 | unsigned char *transfer_buffer; | |
112 | ||
113 | urb = usb_alloc_urb(0, GFP_ATOMIC); | |
114 | ||
a019f5e8 | 115 | if (urb == NULL) |
705ececd | 116 | return -ENOMEM; |
705ececd | 117 | |
cff86387 | 118 | transfer_buffer = kmemdup(data, length, GFP_ATOMIC); |
705ececd | 119 | |
597a1e7c | 120 | if (transfer_buffer == NULL) { |
705ececd | 121 | usb_free_urb(urb); |
705ececd MG |
122 | return -ENOMEM; |
123 | } | |
124 | ||
d7e37336 | 125 | usb_fill_int_urb(urb, line6->usbdev, |
7ecb46e9 | 126 | usb_sndintpipe(line6->usbdev, |
9e165be7 | 127 | line6->properties->ep_ctrl_w), |
d7e37336 GKH |
128 | transfer_buffer, length, midi_sent, line6, |
129 | line6->interval); | |
705ececd | 130 | urb->actual_length = 0; |
4f95646c TI |
131 | retval = usb_urb_ep_type_check(urb); |
132 | if (retval < 0) | |
133 | goto error; | |
705ececd | 134 | |
4f95646c TI |
135 | retval = usb_submit_urb(urb, GFP_ATOMIC); |
136 | if (retval < 0) | |
137 | goto error; | |
705ececd MG |
138 | |
139 | ++line6->line6midi->num_active_send_urbs; | |
705ececd | 140 | return 0; |
4f95646c TI |
141 | |
142 | error: | |
143 | dev_err(line6->ifcdev, "usb_submit_urb failed\n"); | |
144 | usb_free_urb(urb); | |
145 | return retval; | |
705ececd MG |
146 | } |
147 | ||
148 | static int line6_midi_output_open(struct snd_rawmidi_substream *substream) | |
149 | { | |
150 | return 0; | |
151 | } | |
152 | ||
153 | static int line6_midi_output_close(struct snd_rawmidi_substream *substream) | |
154 | { | |
155 | return 0; | |
156 | } | |
157 | ||
d7e37336 GKH |
158 | static void line6_midi_output_trigger(struct snd_rawmidi_substream *substream, |
159 | int up) | |
705ececd MG |
160 | { |
161 | unsigned long flags; | |
e1a164d7 MG |
162 | struct usb_line6 *line6 = |
163 | line6_rawmidi_substream_midi(substream)->line6; | |
705ececd MG |
164 | |
165 | line6->line6midi->substream_transmit = substream; | |
bc518ba4 | 166 | spin_lock_irqsave(&line6->line6midi->lock, flags); |
705ececd | 167 | |
d7e37336 | 168 | if (line6->line6midi->num_active_send_urbs == 0) |
705ececd MG |
169 | line6_midi_transmit(substream); |
170 | ||
bc518ba4 | 171 | spin_unlock_irqrestore(&line6->line6midi->lock, flags); |
705ececd MG |
172 | } |
173 | ||
174 | static void line6_midi_output_drain(struct snd_rawmidi_substream *substream) | |
175 | { | |
e1a164d7 MG |
176 | struct usb_line6 *line6 = |
177 | line6_rawmidi_substream_midi(substream)->line6; | |
1027f476 | 178 | struct snd_line6_midi *midi = line6->line6midi; |
f3c5261e | 179 | |
e1a164d7 MG |
180 | wait_event_interruptible(midi->send_wait, |
181 | midi->num_active_send_urbs == 0); | |
705ececd MG |
182 | } |
183 | ||
184 | static int line6_midi_input_open(struct snd_rawmidi_substream *substream) | |
185 | { | |
186 | return 0; | |
187 | } | |
188 | ||
189 | static int line6_midi_input_close(struct snd_rawmidi_substream *substream) | |
190 | { | |
191 | return 0; | |
192 | } | |
193 | ||
d7e37336 GKH |
194 | static void line6_midi_input_trigger(struct snd_rawmidi_substream *substream, |
195 | int up) | |
705ececd | 196 | { |
e1a164d7 MG |
197 | struct usb_line6 *line6 = |
198 | line6_rawmidi_substream_midi(substream)->line6; | |
705ececd | 199 | |
d7e37336 | 200 | if (up) |
705ececd MG |
201 | line6->line6midi->substream_receive = substream; |
202 | else | |
a0d6f2b1 | 203 | line6->line6midi->substream_receive = NULL; |
705ececd MG |
204 | } |
205 | ||
f43e5407 | 206 | static const struct snd_rawmidi_ops line6_midi_output_ops = { |
705ececd MG |
207 | .open = line6_midi_output_open, |
208 | .close = line6_midi_output_close, | |
209 | .trigger = line6_midi_output_trigger, | |
210 | .drain = line6_midi_output_drain, | |
211 | }; | |
212 | ||
f43e5407 | 213 | static const struct snd_rawmidi_ops line6_midi_input_ops = { |
705ececd MG |
214 | .open = line6_midi_input_open, |
215 | .close = line6_midi_input_close, | |
216 | .trigger = line6_midi_input_trigger, | |
217 | }; | |
218 | ||
705ececd | 219 | /* Create a MIDI device */ |
aaa68d2f TI |
220 | static int snd_line6_new_midi(struct usb_line6 *line6, |
221 | struct snd_rawmidi **rmidi_ret) | |
705ececd MG |
222 | { |
223 | struct snd_rawmidi *rmidi; | |
224 | int err; | |
225 | ||
c6fffce9 | 226 | err = snd_rawmidi_new(line6->card, "Line 6 MIDI", 0, 1, 1, rmidi_ret); |
d7e37336 | 227 | if (err < 0) |
705ececd MG |
228 | return err; |
229 | ||
aaa68d2f TI |
230 | rmidi = *rmidi_ret; |
231 | strcpy(rmidi->id, line6->properties->id); | |
232 | strcpy(rmidi->name, line6->properties->name); | |
705ececd MG |
233 | |
234 | rmidi->info_flags = | |
e1a164d7 MG |
235 | SNDRV_RAWMIDI_INFO_OUTPUT | |
236 | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; | |
705ececd | 237 | |
d7e37336 GKH |
238 | snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, |
239 | &line6_midi_output_ops); | |
240 | snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, | |
241 | &line6_midi_input_ops); | |
705ececd MG |
242 | return 0; |
243 | } | |
244 | ||
705ececd | 245 | /* MIDI device destructor */ |
aaa68d2f | 246 | static void snd_line6_midi_free(struct snd_rawmidi *rmidi) |
705ececd | 247 | { |
aaa68d2f | 248 | struct snd_line6_midi *line6midi = rmidi->private_data; |
f3c5261e | 249 | |
1027f476 MG |
250 | line6_midibuf_destroy(&line6midi->midibuf_in); |
251 | line6_midibuf_destroy(&line6midi->midibuf_out); | |
aaa68d2f | 252 | kfree(line6midi); |
705ececd MG |
253 | } |
254 | ||
255 | /* | |
c6fffce9 | 256 | Initialize the Line 6 MIDI subsystem. |
705ececd MG |
257 | */ |
258 | int line6_init_midi(struct usb_line6 *line6) | |
259 | { | |
705ececd | 260 | int err; |
aaa68d2f | 261 | struct snd_rawmidi *rmidi; |
705ececd MG |
262 | struct snd_line6_midi *line6midi; |
263 | ||
7811a3ad | 264 | if (!(line6->properties->capabilities & LINE6_CAP_CONTROL_MIDI)) { |
027360c5 GKH |
265 | /* skip MIDI initialization and report success */ |
266 | return 0; | |
267 | } | |
705ececd | 268 | |
aaa68d2f TI |
269 | err = snd_line6_new_midi(line6, &rmidi); |
270 | if (err < 0) | |
271 | return err; | |
705ececd | 272 | |
aaa68d2f TI |
273 | line6midi = kzalloc(sizeof(struct snd_line6_midi), GFP_KERNEL); |
274 | if (!line6midi) | |
705ececd MG |
275 | return -ENOMEM; |
276 | ||
aaa68d2f TI |
277 | rmidi->private_data = line6midi; |
278 | rmidi->private_free = snd_line6_midi_free; | |
705ececd | 279 | |
aaa68d2f | 280 | init_waitqueue_head(&line6midi->send_wait); |
bc518ba4 | 281 | spin_lock_init(&line6midi->lock); |
705ececd | 282 | line6midi->line6 = line6; |
705ececd | 283 | |
aaa68d2f | 284 | err = line6_midibuf_init(&line6midi->midibuf_in, MIDI_BUFFER_SIZE, 0); |
d7e37336 | 285 | if (err < 0) |
705ececd MG |
286 | return err; |
287 | ||
aaa68d2f | 288 | err = line6_midibuf_init(&line6midi->midibuf_out, MIDI_BUFFER_SIZE, 1); |
d7e37336 | 289 | if (err < 0) |
705ececd MG |
290 | return err; |
291 | ||
aaa68d2f | 292 | line6->line6midi = line6midi; |
705ececd MG |
293 | return 0; |
294 | } | |
ccddbe4a | 295 | EXPORT_SYMBOL_GPL(line6_init_midi); |