]>
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; | |
e3315b43 TS |
21 | }; |
22 | ||
13b8b78c TS |
23 | static const u8 sysex_escape_prefix[] = { |
24 | 0xf0, /* SysEx begin */ | |
25 | 0x00, 0x01, 0x60, /* Stanton DJ */ | |
26 | 0x48, 0x53, 0x53, /* "HSS" */ | |
27 | }; | |
28 | ||
29 | static void midi_input_escaped_byte(struct snd_rawmidi_substream *stream, | |
30 | u8 byte) | |
31 | { | |
32 | u8 nibbles[2]; | |
33 | ||
34 | nibbles[0] = byte >> 4; | |
35 | nibbles[1] = byte & 0x0f; | |
36 | snd_rawmidi_receive(stream, nibbles, 2); | |
37 | } | |
38 | ||
39 | static void midi_input_byte(struct fw_scs1x *scs, | |
40 | struct snd_rawmidi_substream *stream, u8 byte) | |
41 | { | |
42 | const u8 eox = 0xf7; | |
43 | ||
44 | if (scs->input_escape_count > 0) { | |
45 | midi_input_escaped_byte(stream, byte); | |
46 | scs->input_escape_count--; | |
47 | if (scs->input_escape_count == 0) | |
48 | snd_rawmidi_receive(stream, &eox, sizeof(eox)); | |
49 | } else if (byte == 0xf9) { | |
50 | snd_rawmidi_receive(stream, sysex_escape_prefix, | |
51 | ARRAY_SIZE(sysex_escape_prefix)); | |
52 | midi_input_escaped_byte(stream, 0x00); | |
53 | midi_input_escaped_byte(stream, 0xf9); | |
54 | scs->input_escape_count = 3; | |
55 | } else { | |
56 | snd_rawmidi_receive(stream, &byte, 1); | |
57 | } | |
58 | } | |
59 | ||
60 | static void midi_input_packet(struct fw_scs1x *scs, | |
61 | struct snd_rawmidi_substream *stream, | |
62 | const u8 *data, unsigned int bytes) | |
63 | { | |
64 | unsigned int i; | |
65 | const u8 eox = 0xf7; | |
66 | ||
67 | if (data[0] == HSS1394_TAG_USER_DATA) { | |
68 | for (i = 1; i < bytes; ++i) | |
69 | midi_input_byte(scs, stream, data[i]); | |
70 | } else { | |
71 | snd_rawmidi_receive(stream, sysex_escape_prefix, | |
72 | ARRAY_SIZE(sysex_escape_prefix)); | |
73 | for (i = 0; i < bytes; ++i) | |
74 | midi_input_escaped_byte(stream, data[i]); | |
75 | snd_rawmidi_receive(stream, &eox, sizeof(eox)); | |
76 | } | |
77 | } | |
78 | ||
e3315b43 TS |
79 | static void handle_hss(struct fw_card *card, struct fw_request *request, |
80 | int tcode, int destination, int source, int generation, | |
81 | unsigned long long offset, void *data, size_t length, | |
82 | void *callback_data) | |
83 | { | |
13b8b78c TS |
84 | struct fw_scs1x *scs = callback_data; |
85 | struct snd_rawmidi_substream *stream; | |
86 | int rcode; | |
87 | ||
88 | if (offset != scs->hss_handler.offset) { | |
89 | rcode = RCODE_ADDRESS_ERROR; | |
90 | goto end; | |
91 | } | |
92 | if (tcode != TCODE_WRITE_QUADLET_REQUEST && | |
93 | tcode != TCODE_WRITE_BLOCK_REQUEST) { | |
94 | rcode = RCODE_TYPE_ERROR; | |
95 | goto end; | |
96 | } | |
97 | ||
98 | if (length >= 1) { | |
99 | stream = ACCESS_ONCE(scs->input); | |
100 | if (stream) | |
101 | midi_input_packet(scs, stream, data, length); | |
102 | } | |
103 | ||
104 | rcode = RCODE_COMPLETE; | |
105 | end: | |
106 | fw_send_response(card, request, rcode); | |
e3315b43 TS |
107 | } |
108 | ||
8250427d TS |
109 | static int midi_capture_open(struct snd_rawmidi_substream *stream) |
110 | { | |
111 | return 0; | |
112 | } | |
113 | ||
114 | static int midi_capture_close(struct snd_rawmidi_substream *stream) | |
115 | { | |
116 | return 0; | |
117 | } | |
118 | ||
119 | static void midi_capture_trigger(struct snd_rawmidi_substream *stream, int up) | |
120 | { | |
121 | struct fw_scs1x *scs = stream->rmidi->private_data; | |
122 | ||
123 | if (up) { | |
124 | scs->input_escape_count = 0; | |
125 | ACCESS_ONCE(scs->input) = stream; | |
126 | } else { | |
127 | ACCESS_ONCE(scs->input) = NULL; | |
128 | } | |
129 | } | |
130 | ||
131 | static struct snd_rawmidi_ops midi_capture_ops = { | |
132 | .open = midi_capture_open, | |
133 | .close = midi_capture_close, | |
134 | .trigger = midi_capture_trigger, | |
135 | }; | |
136 | ||
e3315b43 TS |
137 | static int register_address(struct snd_oxfw *oxfw) |
138 | { | |
139 | struct fw_scs1x *scs = oxfw->spec; | |
140 | __be64 data; | |
141 | ||
142 | data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | | |
143 | scs->hss_handler.offset); | |
144 | return snd_fw_transaction(oxfw->unit, TCODE_WRITE_BLOCK_REQUEST, | |
145 | HSS1394_ADDRESS, &data, sizeof(data), 0); | |
146 | } | |
147 | ||
148 | static void remove_scs1x(struct snd_rawmidi *rmidi) | |
149 | { | |
150 | struct fw_scs1x *scs = rmidi->private_data; | |
151 | ||
152 | fw_core_remove_address_handler(&scs->hss_handler); | |
153 | } | |
154 | ||
155 | void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw) | |
156 | { | |
157 | register_address(oxfw); | |
158 | } | |
159 | ||
3f47152a TS |
160 | int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw) |
161 | { | |
162 | struct snd_rawmidi *rmidi; | |
e3315b43 | 163 | struct fw_scs1x *scs; |
3f47152a TS |
164 | int err; |
165 | ||
e3315b43 TS |
166 | scs = kzalloc(sizeof(struct fw_scs1x), GFP_KERNEL); |
167 | if (scs == NULL) | |
168 | return -ENOMEM; | |
169 | oxfw->spec = scs; | |
170 | ||
171 | /* Allocate own handler for imcoming asynchronous transaction. */ | |
172 | scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; | |
173 | scs->hss_handler.address_callback = handle_hss; | |
174 | scs->hss_handler.callback_data = scs; | |
175 | err = fw_core_add_address_handler(&scs->hss_handler, | |
176 | &fw_high_memory_region); | |
177 | if (err < 0) | |
178 | return err; | |
179 | ||
180 | err = register_address(oxfw); | |
181 | if (err < 0) | |
182 | goto err_allocated; | |
183 | ||
3f47152a | 184 | /* Use unique name for backward compatibility to scs1x module. */ |
8250427d | 185 | err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 0, 1, &rmidi); |
3f47152a | 186 | if (err < 0) |
e3315b43 TS |
187 | goto err_allocated; |
188 | rmidi->private_data = scs; | |
189 | rmidi->private_free = remove_scs1x; | |
3f47152a TS |
190 | |
191 | snprintf(rmidi->name, sizeof(rmidi->name), | |
192 | "%s MIDI", oxfw->card->shortname); | |
193 | ||
8250427d TS |
194 | rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT; |
195 | snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, | |
196 | &midi_capture_ops); | |
197 | ||
e3315b43 TS |
198 | return 0; |
199 | err_allocated: | |
200 | fw_core_remove_address_handler(&scs->hss_handler); | |
3f47152a TS |
201 | return err; |
202 | } |