1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
4 | * Routines for control of SoundBlaster cards - MIDI interface |
5 | * |
6 | * -- |
7 | * |
8 | * Sun May 9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk> |
9 | * Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from |
10 | * working. |
11 | * |
12 | * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de> |
13 | * Added full duplex UART mode for DSP version 2.0 and later. |
14 | */ |
15 | |
16 | #include <linux/io.h> |
17 | #include <linux/time.h> |
18 | #include <sound/core.h> |
19 | #include <sound/sb.h> |
20 | |
21 | |
22 | irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip) |
23 | { |
24 | struct snd_rawmidi *rmidi; |
25 | int max = 64; |
26 | char byte; |
27 | |
28 | if (!chip) |
29 | return IRQ_NONE; |
30 | |
31 | rmidi = chip->rmidi; |
32 | if (!rmidi) { |
33 | inb(SBP(chip, DATA_AVAIL)); /* ack interrupt */ |
34 | return IRQ_NONE; |
35 | } |
36 | |
37 | spin_lock(lock: &chip->midi_input_lock); |
38 | while (max-- > 0) { |
39 | if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { |
40 | byte = inb(SBP(chip, READ)); |
41 | if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) { |
42 | snd_rawmidi_receive(substream: chip->midi_substream_input, buffer: &byte, count: 1); |
43 | } |
44 | } |
45 | } |
46 | spin_unlock(lock: &chip->midi_input_lock); |
47 | return IRQ_HANDLED; |
48 | } |
49 | |
50 | static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream) |
51 | { |
52 | unsigned long flags; |
53 | struct snd_sb *chip; |
54 | unsigned int valid_open_flags; |
55 | |
56 | chip = substream->rmidi->private_data; |
57 | valid_open_flags = chip->hardware >= SB_HW_20 |
58 | ? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0; |
59 | spin_lock_irqsave(&chip->open_lock, flags); |
60 | if (chip->open & ~valid_open_flags) { |
61 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
62 | return -EAGAIN; |
63 | } |
64 | chip->open |= SB_OPEN_MIDI_INPUT; |
65 | chip->midi_substream_input = substream; |
66 | if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) { |
67 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
68 | snd_sbdsp_reset(chip); /* reset DSP */ |
69 | if (chip->hardware >= SB_HW_20) |
70 | snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ); |
71 | } else { |
72 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
73 | } |
74 | return 0; |
75 | } |
76 | |
77 | static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream) |
78 | { |
79 | unsigned long flags; |
80 | struct snd_sb *chip; |
81 | unsigned int valid_open_flags; |
82 | |
83 | chip = substream->rmidi->private_data; |
84 | valid_open_flags = chip->hardware >= SB_HW_20 |
85 | ? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0; |
86 | spin_lock_irqsave(&chip->open_lock, flags); |
87 | if (chip->open & ~valid_open_flags) { |
88 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
89 | return -EAGAIN; |
90 | } |
91 | chip->open |= SB_OPEN_MIDI_OUTPUT; |
92 | chip->midi_substream_output = substream; |
93 | if (!(chip->open & SB_OPEN_MIDI_INPUT)) { |
94 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
95 | snd_sbdsp_reset(chip); /* reset DSP */ |
96 | if (chip->hardware >= SB_HW_20) |
97 | snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ); |
98 | } else { |
99 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
100 | } |
101 | return 0; |
102 | } |
103 | |
104 | static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream) |
105 | { |
106 | unsigned long flags; |
107 | struct snd_sb *chip; |
108 | |
109 | chip = substream->rmidi->private_data; |
110 | spin_lock_irqsave(&chip->open_lock, flags); |
111 | chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER); |
112 | chip->midi_substream_input = NULL; |
113 | if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) { |
114 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
115 | snd_sbdsp_reset(chip); /* reset DSP */ |
116 | } else { |
117 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
118 | } |
119 | return 0; |
120 | } |
121 | |
122 | static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream) |
123 | { |
124 | unsigned long flags; |
125 | struct snd_sb *chip; |
126 | |
127 | chip = substream->rmidi->private_data; |
128 | del_timer_sync(timer: &chip->midi_timer); |
129 | spin_lock_irqsave(&chip->open_lock, flags); |
130 | chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER); |
131 | chip->midi_substream_output = NULL; |
132 | if (!(chip->open & SB_OPEN_MIDI_INPUT)) { |
133 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
134 | snd_sbdsp_reset(chip); /* reset DSP */ |
135 | } else { |
136 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
137 | } |
138 | return 0; |
139 | } |
140 | |
141 | static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) |
142 | { |
143 | unsigned long flags; |
144 | struct snd_sb *chip; |
145 | |
146 | chip = substream->rmidi->private_data; |
147 | spin_lock_irqsave(&chip->open_lock, flags); |
148 | if (up) { |
149 | if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) { |
150 | if (chip->hardware < SB_HW_20) |
151 | snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ); |
152 | chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER; |
153 | } |
154 | } else { |
155 | if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) { |
156 | if (chip->hardware < SB_HW_20) |
157 | snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ); |
158 | chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER; |
159 | } |
160 | } |
161 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
162 | } |
163 | |
164 | static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream) |
165 | { |
166 | unsigned long flags; |
167 | struct snd_sb *chip; |
168 | char byte; |
169 | int max = 32; |
170 | |
171 | /* how big is Tx FIFO? */ |
172 | chip = substream->rmidi->private_data; |
173 | while (max-- > 0) { |
174 | spin_lock_irqsave(&chip->open_lock, flags); |
175 | if (snd_rawmidi_transmit_peek(substream, buffer: &byte, count: 1) != 1) { |
176 | chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER; |
177 | del_timer(timer: &chip->midi_timer); |
178 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
179 | break; |
180 | } |
181 | if (chip->hardware >= SB_HW_20) { |
182 | int timeout = 8; |
183 | while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0) |
184 | ; |
185 | if (timeout == 0) { |
186 | /* Tx FIFO full - try again later */ |
187 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
188 | break; |
189 | } |
190 | outb(value: byte, SBP(chip, WRITE)); |
191 | } else { |
192 | snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT); |
193 | snd_sbdsp_command(chip, val: byte); |
194 | } |
195 | snd_rawmidi_transmit_ack(substream, count: 1); |
196 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
197 | } |
198 | } |
199 | |
200 | static void snd_sb8dsp_midi_output_timer(struct timer_list *t) |
201 | { |
202 | struct snd_sb *chip = from_timer(chip, t, midi_timer); |
203 | struct snd_rawmidi_substream *substream = chip->midi_substream_output; |
204 | unsigned long flags; |
205 | |
206 | spin_lock_irqsave(&chip->open_lock, flags); |
207 | mod_timer(timer: &chip->midi_timer, expires: 1 + jiffies); |
208 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
209 | snd_sb8dsp_midi_output_write(substream); |
210 | } |
211 | |
212 | static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) |
213 | { |
214 | unsigned long flags; |
215 | struct snd_sb *chip; |
216 | |
217 | chip = substream->rmidi->private_data; |
218 | spin_lock_irqsave(&chip->open_lock, flags); |
219 | if (up) { |
220 | if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) { |
221 | mod_timer(timer: &chip->midi_timer, expires: 1 + jiffies); |
222 | chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER; |
223 | } |
224 | } else { |
225 | if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) { |
226 | chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER; |
227 | } |
228 | } |
229 | spin_unlock_irqrestore(lock: &chip->open_lock, flags); |
230 | |
231 | if (up) |
232 | snd_sb8dsp_midi_output_write(substream); |
233 | } |
234 | |
235 | static const struct snd_rawmidi_ops snd_sb8dsp_midi_output = |
236 | { |
237 | .open = snd_sb8dsp_midi_output_open, |
238 | .close = snd_sb8dsp_midi_output_close, |
239 | .trigger = snd_sb8dsp_midi_output_trigger, |
240 | }; |
241 | |
242 | static const struct snd_rawmidi_ops snd_sb8dsp_midi_input = |
243 | { |
244 | .open = snd_sb8dsp_midi_input_open, |
245 | .close = snd_sb8dsp_midi_input_close, |
246 | .trigger = snd_sb8dsp_midi_input_trigger, |
247 | }; |
248 | |
249 | int snd_sb8dsp_midi(struct snd_sb *chip, int device) |
250 | { |
251 | struct snd_rawmidi *rmidi; |
252 | int err; |
253 | |
254 | err = snd_rawmidi_new(card: chip->card, id: "SB8 MIDI" , device, output_count: 1, input_count: 1, rmidi: &rmidi); |
255 | if (err < 0) |
256 | return err; |
257 | strcpy(p: rmidi->name, q: "SB8 MIDI" ); |
258 | snd_rawmidi_set_ops(rmidi, stream: SNDRV_RAWMIDI_STREAM_OUTPUT, ops: &snd_sb8dsp_midi_output); |
259 | snd_rawmidi_set_ops(rmidi, stream: SNDRV_RAWMIDI_STREAM_INPUT, ops: &snd_sb8dsp_midi_input); |
260 | rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT; |
261 | if (chip->hardware >= SB_HW_20) |
262 | rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; |
263 | rmidi->private_data = chip; |
264 | timer_setup(&chip->midi_timer, snd_sb8dsp_midi_output_timer, 0); |
265 | chip->rmidi = rmidi; |
266 | return 0; |
267 | } |
268 | |