1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de> |
4 | * Creative Audio MIDI, for the CA0106 Driver |
5 | * Version: 0.0.1 |
6 | * |
7 | * Changelog: |
8 | * Implementation is based on mpu401 and emu10k1x and |
9 | * tested with ca0106. |
10 | * mpu401: Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
11 | * emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com> |
12 | */ |
13 | |
14 | #include <linux/spinlock.h> |
15 | #include <sound/core.h> |
16 | #include <sound/rawmidi.h> |
17 | |
18 | #include "ca_midi.h" |
19 | |
20 | #define ca_midi_write_data(midi, data) midi->write(midi, data, 0) |
21 | #define ca_midi_write_cmd(midi, data) midi->write(midi, data, 1) |
22 | #define ca_midi_read_data(midi) midi->read(midi, 0) |
23 | #define ca_midi_read_stat(midi) midi->read(midi, 1) |
24 | #define ca_midi_input_avail(midi) (!(ca_midi_read_stat(midi) & midi->input_avail)) |
25 | #define ca_midi_output_ready(midi) (!(ca_midi_read_stat(midi) & midi->output_ready)) |
26 | |
27 | static void ca_midi_clear_rx(struct snd_ca_midi *midi) |
28 | { |
29 | int timeout = 100000; |
30 | for (; timeout > 0 && ca_midi_input_avail(midi); timeout--) |
31 | ca_midi_read_data(midi); |
32 | #ifdef CONFIG_SND_DEBUG |
33 | if (timeout <= 0) |
34 | pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n" , |
35 | ca_midi_read_stat(midi)); |
36 | #endif |
37 | } |
38 | |
39 | static void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status) |
40 | { |
41 | unsigned char byte; |
42 | |
43 | if (midi->rmidi == NULL) { |
44 | midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable); |
45 | return; |
46 | } |
47 | |
48 | spin_lock(lock: &midi->input_lock); |
49 | if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) { |
50 | if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { |
51 | ca_midi_clear_rx(midi); |
52 | } else { |
53 | byte = ca_midi_read_data(midi); |
54 | if(midi->substream_input) |
55 | snd_rawmidi_receive(substream: midi->substream_input, buffer: &byte, count: 1); |
56 | |
57 | |
58 | } |
59 | } |
60 | spin_unlock(lock: &midi->input_lock); |
61 | |
62 | spin_lock(lock: &midi->output_lock); |
63 | if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) { |
64 | if (midi->substream_output && |
65 | snd_rawmidi_transmit(substream: midi->substream_output, buffer: &byte, count: 1) == 1) { |
66 | ca_midi_write_data(midi, byte); |
67 | } else { |
68 | midi->interrupt_disable(midi,midi->tx_enable); |
69 | } |
70 | } |
71 | spin_unlock(lock: &midi->output_lock); |
72 | |
73 | } |
74 | |
75 | static void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack) |
76 | { |
77 | unsigned long flags; |
78 | int timeout, ok; |
79 | |
80 | spin_lock_irqsave(&midi->input_lock, flags); |
81 | ca_midi_write_data(midi, 0x00); |
82 | /* ca_midi_clear_rx(midi); */ |
83 | |
84 | ca_midi_write_cmd(midi, cmd); |
85 | if (ack) { |
86 | ok = 0; |
87 | timeout = 10000; |
88 | while (!ok && timeout-- > 0) { |
89 | if (ca_midi_input_avail(midi)) { |
90 | if (ca_midi_read_data(midi) == midi->ack) |
91 | ok = 1; |
92 | } |
93 | } |
94 | if (!ok && ca_midi_read_data(midi) == midi->ack) |
95 | ok = 1; |
96 | } else { |
97 | ok = 1; |
98 | } |
99 | spin_unlock_irqrestore(lock: &midi->input_lock, flags); |
100 | if (!ok) |
101 | pr_err("ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n" , |
102 | cmd, |
103 | midi->get_dev_id_port(midi->dev_id), |
104 | ca_midi_read_stat(midi), |
105 | ca_midi_read_data(midi)); |
106 | } |
107 | |
108 | static int ca_midi_input_open(struct snd_rawmidi_substream *substream) |
109 | { |
110 | struct snd_ca_midi *midi = substream->rmidi->private_data; |
111 | unsigned long flags; |
112 | |
113 | if (snd_BUG_ON(!midi->dev_id)) |
114 | return -ENXIO; |
115 | spin_lock_irqsave(&midi->open_lock, flags); |
116 | midi->midi_mode |= CA_MIDI_MODE_INPUT; |
117 | midi->substream_input = substream; |
118 | if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) { |
119 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
120 | ca_midi_cmd(midi, cmd: midi->reset, ack: 1); |
121 | ca_midi_cmd(midi, cmd: midi->enter_uart, ack: 1); |
122 | } else { |
123 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
124 | } |
125 | return 0; |
126 | } |
127 | |
128 | static int ca_midi_output_open(struct snd_rawmidi_substream *substream) |
129 | { |
130 | struct snd_ca_midi *midi = substream->rmidi->private_data; |
131 | unsigned long flags; |
132 | |
133 | if (snd_BUG_ON(!midi->dev_id)) |
134 | return -ENXIO; |
135 | spin_lock_irqsave(&midi->open_lock, flags); |
136 | midi->midi_mode |= CA_MIDI_MODE_OUTPUT; |
137 | midi->substream_output = substream; |
138 | if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { |
139 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
140 | ca_midi_cmd(midi, cmd: midi->reset, ack: 1); |
141 | ca_midi_cmd(midi, cmd: midi->enter_uart, ack: 1); |
142 | } else { |
143 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
144 | } |
145 | return 0; |
146 | } |
147 | |
148 | static int ca_midi_input_close(struct snd_rawmidi_substream *substream) |
149 | { |
150 | struct snd_ca_midi *midi = substream->rmidi->private_data; |
151 | unsigned long flags; |
152 | |
153 | if (snd_BUG_ON(!midi->dev_id)) |
154 | return -ENXIO; |
155 | spin_lock_irqsave(&midi->open_lock, flags); |
156 | midi->interrupt_disable(midi,midi->rx_enable); |
157 | midi->midi_mode &= ~CA_MIDI_MODE_INPUT; |
158 | midi->substream_input = NULL; |
159 | if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) { |
160 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
161 | ca_midi_cmd(midi, cmd: midi->reset, ack: 0); |
162 | } else { |
163 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
164 | } |
165 | return 0; |
166 | } |
167 | |
168 | static int ca_midi_output_close(struct snd_rawmidi_substream *substream) |
169 | { |
170 | struct snd_ca_midi *midi = substream->rmidi->private_data; |
171 | unsigned long flags; |
172 | |
173 | if (snd_BUG_ON(!midi->dev_id)) |
174 | return -ENXIO; |
175 | |
176 | spin_lock_irqsave(&midi->open_lock, flags); |
177 | |
178 | midi->interrupt_disable(midi,midi->tx_enable); |
179 | midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT; |
180 | midi->substream_output = NULL; |
181 | |
182 | if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { |
183 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
184 | ca_midi_cmd(midi, cmd: midi->reset, ack: 0); |
185 | } else { |
186 | spin_unlock_irqrestore(lock: &midi->open_lock, flags); |
187 | } |
188 | return 0; |
189 | } |
190 | |
191 | static void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) |
192 | { |
193 | struct snd_ca_midi *midi = substream->rmidi->private_data; |
194 | |
195 | if (snd_BUG_ON(!midi->dev_id)) |
196 | return; |
197 | |
198 | if (up) { |
199 | midi->interrupt_enable(midi,midi->rx_enable); |
200 | } else { |
201 | midi->interrupt_disable(midi, midi->rx_enable); |
202 | } |
203 | } |
204 | |
205 | static void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) |
206 | { |
207 | struct snd_ca_midi *midi = substream->rmidi->private_data; |
208 | unsigned long flags; |
209 | |
210 | if (snd_BUG_ON(!midi->dev_id)) |
211 | return; |
212 | |
213 | if (up) { |
214 | int max = 4; |
215 | unsigned char byte; |
216 | |
217 | spin_lock_irqsave(&midi->output_lock, flags); |
218 | |
219 | /* try to send some amount of bytes here before interrupts */ |
220 | while (max > 0) { |
221 | if (ca_midi_output_ready(midi)) { |
222 | if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) || |
223 | snd_rawmidi_transmit(substream, buffer: &byte, count: 1) != 1) { |
224 | /* no more data */ |
225 | spin_unlock_irqrestore(lock: &midi->output_lock, flags); |
226 | return; |
227 | } |
228 | ca_midi_write_data(midi, byte); |
229 | max--; |
230 | } else { |
231 | break; |
232 | } |
233 | } |
234 | |
235 | spin_unlock_irqrestore(lock: &midi->output_lock, flags); |
236 | midi->interrupt_enable(midi,midi->tx_enable); |
237 | |
238 | } else { |
239 | midi->interrupt_disable(midi,midi->tx_enable); |
240 | } |
241 | } |
242 | |
243 | static const struct snd_rawmidi_ops ca_midi_output = |
244 | { |
245 | .open = ca_midi_output_open, |
246 | .close = ca_midi_output_close, |
247 | .trigger = ca_midi_output_trigger, |
248 | }; |
249 | |
250 | static const struct snd_rawmidi_ops ca_midi_input = |
251 | { |
252 | .open = ca_midi_input_open, |
253 | .close = ca_midi_input_close, |
254 | .trigger = ca_midi_input_trigger, |
255 | }; |
256 | |
257 | static void ca_midi_free(struct snd_ca_midi *midi) |
258 | { |
259 | midi->interrupt = NULL; |
260 | midi->interrupt_enable = NULL; |
261 | midi->interrupt_disable = NULL; |
262 | midi->read = NULL; |
263 | midi->write = NULL; |
264 | midi->get_dev_id_card = NULL; |
265 | midi->get_dev_id_port = NULL; |
266 | midi->rmidi = NULL; |
267 | } |
268 | |
269 | static void ca_rmidi_free(struct snd_rawmidi *rmidi) |
270 | { |
271 | ca_midi_free(midi: rmidi->private_data); |
272 | } |
273 | |
274 | int ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name) |
275 | { |
276 | struct snd_rawmidi *rmidi; |
277 | int err; |
278 | |
279 | err = snd_rawmidi_new(card: midi->get_dev_id_card(midi->dev_id), id: name, device, output_count: 1, input_count: 1, rmidi: &rmidi); |
280 | if (err < 0) |
281 | return err; |
282 | |
283 | midi->dev_id = dev_id; |
284 | midi->interrupt = ca_midi_interrupt; |
285 | |
286 | spin_lock_init(&midi->open_lock); |
287 | spin_lock_init(&midi->input_lock); |
288 | spin_lock_init(&midi->output_lock); |
289 | |
290 | strcpy(p: rmidi->name, q: name); |
291 | snd_rawmidi_set_ops(rmidi, stream: SNDRV_RAWMIDI_STREAM_OUTPUT, ops: &ca_midi_output); |
292 | snd_rawmidi_set_ops(rmidi, stream: SNDRV_RAWMIDI_STREAM_INPUT, ops: &ca_midi_input); |
293 | rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | |
294 | SNDRV_RAWMIDI_INFO_INPUT | |
295 | SNDRV_RAWMIDI_INFO_DUPLEX; |
296 | rmidi->private_data = midi; |
297 | rmidi->private_free = ca_rmidi_free; |
298 | |
299 | midi->rmidi = rmidi; |
300 | return 0; |
301 | } |
302 | |
303 | |