]>
Commit | Line | Data |
---|---|---|
3f47152a TS |
1 | /* |
2 | * oxfw-scs1x.c - a part of driver for OXFW970/971 based devices | |
3 | * | |
4 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> | |
5 | * Copyright (c) 2015 Takashi Sakamoto <o-takashi@sakamocchi.jp> | |
6 | * | |
7 | * Licensed under the terms of the GNU General Public License, version 2. | |
8 | */ | |
9 | ||
10 | #include "oxfw.h" | |
11 | ||
e3315b43 TS |
12 | #define HSS1394_ADDRESS 0xc007dedadadaULL |
13 | #define HSS1394_MAX_PACKET_SIZE 64 | |
13b8b78c | 14 | #define HSS1394_TAG_USER_DATA 0x00 |
e3315b43 TS |
15 | #define HSS1394_TAG_CHANGE_ADDRESS 0xf1 |
16 | ||
17 | struct fw_scs1x { | |
18 | struct fw_address_handler hss_handler; | |
13b8b78c TS |
19 | u8 input_escape_count; |
20 | struct snd_rawmidi_substream *input; | |
d7d20e77 TS |
21 | |
22 | /* For MIDI playback. */ | |
23 | struct snd_rawmidi_substream *output; | |
24 | bool output_idle; | |
25 | u8 output_status; | |
26 | u8 output_bytes; | |
27 | bool output_escaped; | |
28 | bool output_escape_high_nibble; | |
ea790053 | 29 | struct work_struct work; |
d7d20e77 TS |
30 | wait_queue_head_t idle_wait; |
31 | u8 buffer[HSS1394_MAX_PACKET_SIZE]; | |
32 | bool transaction_running; | |
33 | struct fw_transaction transaction; | |
b4c23ab1 | 34 | unsigned int transaction_bytes; |
956dea9e | 35 | bool error; |
d7d20e77 | 36 | struct fw_device *fw_dev; |
e3315b43 TS |
37 | }; |
38 | ||
13b8b78c TS |
39 | static const u8 sysex_escape_prefix[] = { |
40 | 0xf0, /* SysEx begin */ | |
41 | 0x00, 0x01, 0x60, /* Stanton DJ */ | |
42 | 0x48, 0x53, 0x53, /* "HSS" */ | |
43 | }; | |
44 | ||
45 | static void midi_input_escaped_byte(struct snd_rawmidi_substream *stream, | |
46 | u8 byte) | |
47 | { | |
48 | u8 nibbles[2]; | |
49 | ||
50 | nibbles[0] = byte >> 4; | |
51 | nibbles[1] = byte & 0x0f; | |
52 | snd_rawmidi_receive(stream, nibbles, 2); | |
53 | } | |
54 | ||
55 | static void midi_input_byte(struct fw_scs1x *scs, | |
56 | struct snd_rawmidi_substream *stream, u8 byte) | |
57 | { | |
58 | const u8 eox = 0xf7; | |
59 | ||
60 | if (scs->input_escape_count > 0) { | |
61 | midi_input_escaped_byte(stream, byte); | |
62 | scs->input_escape_count--; | |
63 | if (scs->input_escape_count == 0) | |
64 | snd_rawmidi_receive(stream, &eox, sizeof(eox)); | |
65 | } else if (byte == 0xf9) { | |
66 | snd_rawmidi_receive(stream, sysex_escape_prefix, | |
67 | ARRAY_SIZE(sysex_escape_prefix)); | |
68 | midi_input_escaped_byte(stream, 0x00); | |
69 | midi_input_escaped_byte(stream, 0xf9); | |
70 | scs->input_escape_count = 3; | |
71 | } else { | |
72 | snd_rawmidi_receive(stream, &byte, 1); | |
73 | } | |
74 | } | |
75 | ||
76 | static void midi_input_packet(struct fw_scs1x *scs, | |
77 | struct snd_rawmidi_substream *stream, | |
78 | const u8 *data, unsigned int bytes) | |
79 | { | |
80 | unsigned int i; | |
81 | const u8 eox = 0xf7; | |
82 | ||
83 | if (data[0] == HSS1394_TAG_USER_DATA) { | |
84 | for (i = 1; i < bytes; ++i) | |
85 | midi_input_byte(scs, stream, data[i]); | |
86 | } else { | |
87 | snd_rawmidi_receive(stream, sysex_escape_prefix, | |
88 | ARRAY_SIZE(sysex_escape_prefix)); | |
89 | for (i = 0; i < bytes; ++i) | |
90 | midi_input_escaped_byte(stream, data[i]); | |
91 | snd_rawmidi_receive(stream, &eox, sizeof(eox)); | |
92 | } | |
93 | } | |
94 | ||
e3315b43 TS |
95 | static void handle_hss(struct fw_card *card, struct fw_request *request, |
96 | int tcode, int destination, int source, int generation, | |
97 | unsigned long long offset, void *data, size_t length, | |
98 | void *callback_data) | |
99 | { | |
13b8b78c TS |
100 | struct fw_scs1x *scs = callback_data; |
101 | struct snd_rawmidi_substream *stream; | |
102 | int rcode; | |
103 | ||
104 | if (offset != scs->hss_handler.offset) { | |
105 | rcode = RCODE_ADDRESS_ERROR; | |
106 | goto end; | |
107 | } | |
108 | if (tcode != TCODE_WRITE_QUADLET_REQUEST && | |
109 | tcode != TCODE_WRITE_BLOCK_REQUEST) { | |
110 | rcode = RCODE_TYPE_ERROR; | |
111 | goto end; | |
112 | } | |
113 | ||
114 | if (length >= 1) { | |
6aa7de05 | 115 | stream = READ_ONCE(scs->input); |
13b8b78c TS |
116 | if (stream) |
117 | midi_input_packet(scs, stream, data, length); | |
118 | } | |
119 | ||
120 | rcode = RCODE_COMPLETE; | |
121 | end: | |
122 | fw_send_response(card, request, rcode); | |
e3315b43 TS |
123 | } |
124 | ||
d7d20e77 TS |
125 | static void scs_write_callback(struct fw_card *card, int rcode, |
126 | void *data, size_t length, void *callback_data) | |
127 | { | |
128 | struct fw_scs1x *scs = callback_data; | |
129 | ||
956dea9e TS |
130 | if (!rcode_is_permanent_error(rcode)) { |
131 | /* Don't retry for this data. */ | |
132 | if (rcode == RCODE_COMPLETE) | |
133 | scs->transaction_bytes = 0; | |
134 | } else { | |
135 | scs->error = true; | |
136 | } | |
d7d20e77 TS |
137 | |
138 | scs->transaction_running = false; | |
ea790053 | 139 | schedule_work(&scs->work); |
d7d20e77 TS |
140 | } |
141 | ||
142 | static bool is_valid_running_status(u8 status) | |
143 | { | |
144 | return status >= 0x80 && status <= 0xef; | |
145 | } | |
146 | ||
147 | static bool is_one_byte_cmd(u8 status) | |
148 | { | |
149 | return status == 0xf6 || | |
150 | status >= 0xf8; | |
151 | } | |
152 | ||
153 | static bool is_two_bytes_cmd(u8 status) | |
154 | { | |
155 | return (status >= 0xc0 && status <= 0xdf) || | |
156 | status == 0xf1 || | |
157 | status == 0xf3; | |
158 | } | |
159 | ||
160 | static bool is_three_bytes_cmd(u8 status) | |
161 | { | |
162 | return (status >= 0x80 && status <= 0xbf) || | |
163 | (status >= 0xe0 && status <= 0xef) || | |
164 | status == 0xf2; | |
165 | } | |
166 | ||
167 | static bool is_invalid_cmd(u8 status) | |
168 | { | |
169 | return status == 0xf4 || | |
170 | status == 0xf5 || | |
171 | status == 0xf9 || | |
172 | status == 0xfd; | |
173 | } | |
174 | ||
ea790053 | 175 | static void scs_output_work(struct work_struct *work) |
d7d20e77 | 176 | { |
ea790053 | 177 | struct fw_scs1x *scs = container_of(work, struct fw_scs1x, work); |
d7d20e77 TS |
178 | struct snd_rawmidi_substream *stream; |
179 | unsigned int i; | |
180 | u8 byte; | |
181 | int generation; | |
182 | ||
183 | if (scs->transaction_running) | |
184 | return; | |
185 | ||
6aa7de05 | 186 | stream = READ_ONCE(scs->output); |
956dea9e | 187 | if (!stream || scs->error) { |
d7d20e77 TS |
188 | scs->output_idle = true; |
189 | wake_up(&scs->idle_wait); | |
190 | return; | |
191 | } | |
192 | ||
b4c23ab1 TS |
193 | if (scs->transaction_bytes > 0) |
194 | goto retry; | |
195 | ||
d7d20e77 TS |
196 | i = scs->output_bytes; |
197 | for (;;) { | |
198 | if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { | |
199 | scs->output_bytes = i; | |
200 | scs->output_idle = true; | |
201 | wake_up(&scs->idle_wait); | |
202 | return; | |
203 | } | |
204 | /* | |
205 | * Convert from real MIDI to what I think the device expects (no | |
206 | * running status, one command per packet, unescaped SysExs). | |
207 | */ | |
208 | if (scs->output_escaped && byte < 0x80) { | |
209 | if (scs->output_escape_high_nibble) { | |
210 | if (i < HSS1394_MAX_PACKET_SIZE) { | |
211 | scs->buffer[i] = byte << 4; | |
212 | scs->output_escape_high_nibble = false; | |
213 | } | |
214 | } else { | |
215 | scs->buffer[i++] |= byte & 0x0f; | |
216 | scs->output_escape_high_nibble = true; | |
217 | } | |
218 | } else if (byte < 0x80) { | |
219 | if (i == 1) { | |
220 | if (!is_valid_running_status( | |
221 | scs->output_status)) | |
222 | continue; | |
223 | scs->buffer[0] = HSS1394_TAG_USER_DATA; | |
224 | scs->buffer[i++] = scs->output_status; | |
225 | } | |
226 | scs->buffer[i++] = byte; | |
227 | if ((i == 3 && is_two_bytes_cmd(scs->output_status)) || | |
228 | (i == 4 && is_three_bytes_cmd(scs->output_status))) | |
229 | break; | |
230 | if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) && | |
231 | !memcmp(scs->buffer + 1, sysex_escape_prefix, | |
232 | ARRAY_SIZE(sysex_escape_prefix))) { | |
233 | scs->output_escaped = true; | |
234 | scs->output_escape_high_nibble = true; | |
235 | i = 0; | |
236 | } | |
237 | if (i >= HSS1394_MAX_PACKET_SIZE) | |
238 | i = 1; | |
239 | } else if (byte == 0xf7) { | |
240 | if (scs->output_escaped) { | |
241 | if (i >= 1 && scs->output_escape_high_nibble && | |
242 | scs->buffer[0] != | |
243 | HSS1394_TAG_CHANGE_ADDRESS) | |
244 | break; | |
245 | } else { | |
246 | if (i > 1 && scs->output_status == 0xf0) { | |
247 | scs->buffer[i++] = 0xf7; | |
248 | break; | |
249 | } | |
250 | } | |
251 | i = 1; | |
252 | scs->output_escaped = false; | |
253 | } else if (!is_invalid_cmd(byte) && byte < 0xf8) { | |
254 | i = 1; | |
255 | scs->buffer[0] = HSS1394_TAG_USER_DATA; | |
256 | scs->buffer[i++] = byte; | |
257 | scs->output_status = byte; | |
258 | scs->output_escaped = false; | |
259 | if (is_one_byte_cmd(byte)) | |
260 | break; | |
261 | } | |
262 | } | |
263 | scs->output_bytes = 1; | |
264 | scs->output_escaped = false; | |
265 | ||
b4c23ab1 TS |
266 | scs->transaction_bytes = i; |
267 | retry: | |
d7d20e77 TS |
268 | scs->transaction_running = true; |
269 | generation = scs->fw_dev->generation; | |
270 | smp_rmb(); /* node_id vs. generation */ | |
271 | fw_send_request(scs->fw_dev->card, &scs->transaction, | |
272 | TCODE_WRITE_BLOCK_REQUEST, scs->fw_dev->node_id, | |
273 | generation, scs->fw_dev->max_speed, HSS1394_ADDRESS, | |
b4c23ab1 TS |
274 | scs->buffer, scs->transaction_bytes, |
275 | scs_write_callback, scs); | |
d7d20e77 TS |
276 | } |
277 | ||
8250427d TS |
278 | static int midi_capture_open(struct snd_rawmidi_substream *stream) |
279 | { | |
280 | return 0; | |
281 | } | |
282 | ||
283 | static int midi_capture_close(struct snd_rawmidi_substream *stream) | |
284 | { | |
285 | return 0; | |
286 | } | |
287 | ||
288 | static void midi_capture_trigger(struct snd_rawmidi_substream *stream, int up) | |
289 | { | |
290 | struct fw_scs1x *scs = stream->rmidi->private_data; | |
291 | ||
292 | if (up) { | |
293 | scs->input_escape_count = 0; | |
6aa7de05 | 294 | WRITE_ONCE(scs->input, stream); |
8250427d | 295 | } else { |
6aa7de05 | 296 | WRITE_ONCE(scs->input, NULL); |
8250427d TS |
297 | } |
298 | } | |
299 | ||
6f5dcb28 TS |
300 | static int midi_playback_open(struct snd_rawmidi_substream *stream) |
301 | { | |
302 | return 0; | |
303 | } | |
304 | ||
305 | static int midi_playback_close(struct snd_rawmidi_substream *stream) | |
306 | { | |
307 | return 0; | |
308 | } | |
309 | ||
310 | static void midi_playback_trigger(struct snd_rawmidi_substream *stream, int up) | |
311 | { | |
312 | struct fw_scs1x *scs = stream->rmidi->private_data; | |
313 | ||
314 | if (up) { | |
315 | scs->output_status = 0; | |
316 | scs->output_bytes = 1; | |
317 | scs->output_escaped = false; | |
318 | scs->output_idle = false; | |
b4c23ab1 | 319 | scs->transaction_bytes = 0; |
956dea9e | 320 | scs->error = false; |
6f5dcb28 | 321 | |
6aa7de05 | 322 | WRITE_ONCE(scs->output, stream); |
ea790053 | 323 | schedule_work(&scs->work); |
6f5dcb28 | 324 | } else { |
6aa7de05 | 325 | WRITE_ONCE(scs->output, NULL); |
6f5dcb28 TS |
326 | } |
327 | } | |
328 | static void midi_playback_drain(struct snd_rawmidi_substream *stream) | |
329 | { | |
330 | struct fw_scs1x *scs = stream->rmidi->private_data; | |
331 | ||
332 | wait_event(scs->idle_wait, scs->output_idle); | |
333 | } | |
334 | ||
e3315b43 TS |
335 | static int register_address(struct snd_oxfw *oxfw) |
336 | { | |
337 | struct fw_scs1x *scs = oxfw->spec; | |
338 | __be64 data; | |
339 | ||
340 | data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | | |
341 | scs->hss_handler.offset); | |
342 | return snd_fw_transaction(oxfw->unit, TCODE_WRITE_BLOCK_REQUEST, | |
343 | HSS1394_ADDRESS, &data, sizeof(data), 0); | |
344 | } | |
345 | ||
346 | static void remove_scs1x(struct snd_rawmidi *rmidi) | |
347 | { | |
348 | struct fw_scs1x *scs = rmidi->private_data; | |
349 | ||
350 | fw_core_remove_address_handler(&scs->hss_handler); | |
351 | } | |
352 | ||
353 | void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw) | |
354 | { | |
355 | register_address(oxfw); | |
356 | } | |
357 | ||
3f47152a TS |
358 | int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw) |
359 | { | |
782fbec7 TS |
360 | static const struct snd_rawmidi_ops midi_capture_ops = { |
361 | .open = midi_capture_open, | |
362 | .close = midi_capture_close, | |
363 | .trigger = midi_capture_trigger, | |
364 | }; | |
1753187e TS |
365 | static const struct snd_rawmidi_ops midi_playback_ops = { |
366 | .open = midi_playback_open, | |
367 | .close = midi_playback_close, | |
368 | .trigger = midi_playback_trigger, | |
369 | .drain = midi_playback_drain, | |
370 | }; | |
3f47152a | 371 | struct snd_rawmidi *rmidi; |
e3315b43 | 372 | struct fw_scs1x *scs; |
3f47152a TS |
373 | int err; |
374 | ||
e3315b43 TS |
375 | scs = kzalloc(sizeof(struct fw_scs1x), GFP_KERNEL); |
376 | if (scs == NULL) | |
377 | return -ENOMEM; | |
d7d20e77 | 378 | scs->fw_dev = fw_parent_device(oxfw->unit); |
e3315b43 TS |
379 | oxfw->spec = scs; |
380 | ||
381 | /* Allocate own handler for imcoming asynchronous transaction. */ | |
382 | scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; | |
383 | scs->hss_handler.address_callback = handle_hss; | |
384 | scs->hss_handler.callback_data = scs; | |
385 | err = fw_core_add_address_handler(&scs->hss_handler, | |
386 | &fw_high_memory_region); | |
387 | if (err < 0) | |
388 | return err; | |
389 | ||
390 | err = register_address(oxfw); | |
391 | if (err < 0) | |
392 | goto err_allocated; | |
393 | ||
3f47152a | 394 | /* Use unique name for backward compatibility to scs1x module. */ |
6f5dcb28 | 395 | err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 1, 1, &rmidi); |
3f47152a | 396 | if (err < 0) |
e3315b43 TS |
397 | goto err_allocated; |
398 | rmidi->private_data = scs; | |
399 | rmidi->private_free = remove_scs1x; | |
3f47152a TS |
400 | |
401 | snprintf(rmidi->name, sizeof(rmidi->name), | |
402 | "%s MIDI", oxfw->card->shortname); | |
403 | ||
6f5dcb28 TS |
404 | rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | |
405 | SNDRV_RAWMIDI_INFO_OUTPUT | | |
406 | SNDRV_RAWMIDI_INFO_DUPLEX; | |
8250427d TS |
407 | snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, |
408 | &midi_capture_ops); | |
6f5dcb28 TS |
409 | snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, |
410 | &midi_playback_ops); | |
8250427d | 411 | |
ea790053 | 412 | INIT_WORK(&scs->work, scs_output_work); |
d7d20e77 TS |
413 | init_waitqueue_head(&scs->idle_wait); |
414 | scs->output_idle = true; | |
415 | ||
e3315b43 TS |
416 | return 0; |
417 | err_allocated: | |
418 | fw_core_remove_address_handler(&scs->hss_handler); | |
3f47152a TS |
419 | return err; |
420 | } |