xref: /linux/sound/isa/sb/sb8_midi.c (revision 05a54fa773284d1a7923cdfdd8f0c8dabb98bd26)
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/string.h>
18 #include <linux/time.h>
19 #include <sound/core.h>
20 #include <sound/sb.h>
21 
22 
23 irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
24 {
25 	struct snd_rawmidi *rmidi;
26 	int max = 64;
27 	char byte;
28 
29 	if (!chip)
30 		return IRQ_NONE;
31 
32 	rmidi = chip->rmidi;
33 	if (!rmidi) {
34 		inb(SBP(chip, DATA_AVAIL));	/* ack interrupt */
35 		return IRQ_NONE;
36 	}
37 
38 	guard(spinlock)(&chip->midi_input_lock);
39 	while (max-- > 0) {
40 		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
41 			byte = inb(SBP(chip, READ));
42 			if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
43 				snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
44 			}
45 		}
46 	}
47 	return IRQ_HANDLED;
48 }
49 
50 static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream)
51 {
52 	struct snd_sb *chip;
53 	unsigned int valid_open_flags;
54 
55 	chip = substream->rmidi->private_data;
56 	valid_open_flags = chip->hardware >= SB_HW_20
57 		? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
58 	scoped_guard(spinlock_irqsave, &chip->open_lock) {
59 		if (chip->open & ~valid_open_flags)
60 			return -EAGAIN;
61 		chip->open |= SB_OPEN_MIDI_INPUT;
62 		chip->midi_substream_input = substream;
63 		if (chip->open & SB_OPEN_MIDI_OUTPUT)
64 			return 0;
65 	}
66 	snd_sbdsp_reset(chip);		/* reset DSP */
67 	if (chip->hardware >= SB_HW_20)
68 		snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
69 	return 0;
70 }
71 
72 static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream)
73 {
74 	struct snd_sb *chip;
75 	unsigned int valid_open_flags;
76 
77 	chip = substream->rmidi->private_data;
78 	valid_open_flags = chip->hardware >= SB_HW_20
79 		? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
80 	scoped_guard(spinlock_irqsave, &chip->open_lock) {
81 		if (chip->open & ~valid_open_flags)
82 			return -EAGAIN;
83 		chip->open |= SB_OPEN_MIDI_OUTPUT;
84 		chip->midi_substream_output = substream;
85 		if (chip->open & SB_OPEN_MIDI_INPUT)
86 			return 0;
87 	}
88 	snd_sbdsp_reset(chip);		/* reset DSP */
89 	if (chip->hardware >= SB_HW_20)
90 		snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
91 	return 0;
92 }
93 
94 static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream)
95 {
96 	struct snd_sb *chip;
97 
98 	chip = substream->rmidi->private_data;
99 	scoped_guard(spinlock_irqsave, &chip->open_lock) {
100 		chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
101 		chip->midi_substream_input = NULL;
102 		if (chip->open & SB_OPEN_MIDI_OUTPUT)
103 			return 0;
104 	}
105 	snd_sbdsp_reset(chip);		/* reset DSP */
106 	return 0;
107 }
108 
109 static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream)
110 {
111 	struct snd_sb *chip;
112 
113 	chip = substream->rmidi->private_data;
114 	timer_delete_sync(&chip->midi_timer);
115 	scoped_guard(spinlock_irqsave, &chip->open_lock) {
116 		chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
117 		chip->midi_substream_output = NULL;
118 		if (chip->open & SB_OPEN_MIDI_INPUT)
119 			return 0;
120 	}
121 	snd_sbdsp_reset(chip);		/* reset DSP */
122 	return 0;
123 }
124 
125 static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
126 {
127 	struct snd_sb *chip;
128 
129 	chip = substream->rmidi->private_data;
130 	guard(spinlock_irqsave)(&chip->open_lock);
131 	if (up) {
132 		if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
133 			if (chip->hardware < SB_HW_20)
134 				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
135 			chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
136 		}
137 	} else {
138 		if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
139 			if (chip->hardware < SB_HW_20)
140 				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
141 			chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
142 		}
143 	}
144 }
145 
146 static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream)
147 {
148 	struct snd_sb *chip;
149 	char byte;
150 	int max = 32;
151 
152 	/* how big is Tx FIFO? */
153 	chip = substream->rmidi->private_data;
154 	while (max-- > 0) {
155 		guard(spinlock_irqsave)(&chip->open_lock);
156 		if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
157 			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
158 			timer_delete(&chip->midi_timer);
159 			break;
160 		}
161 		if (chip->hardware >= SB_HW_20) {
162 			int timeout = 8;
163 			while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
164 				;
165 			if (timeout == 0) {
166 				/* Tx FIFO full - try again later */
167 				break;
168 			}
169 			outb(byte, SBP(chip, WRITE));
170 		} else {
171 			snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
172 			snd_sbdsp_command(chip, byte);
173 		}
174 		snd_rawmidi_transmit_ack(substream, 1);
175 	}
176 }
177 
178 static void snd_sb8dsp_midi_output_timer(struct timer_list *t)
179 {
180 	struct snd_sb *chip = timer_container_of(chip, t, midi_timer);
181 	struct snd_rawmidi_substream *substream = chip->midi_substream_output;
182 
183 	scoped_guard(spinlock_irqsave, &chip->open_lock) {
184 		mod_timer(&chip->midi_timer, 1 + jiffies);
185 	}
186 	snd_sb8dsp_midi_output_write(substream);
187 }
188 
189 static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
190 {
191 	struct snd_sb *chip;
192 
193 	chip = substream->rmidi->private_data;
194 	scoped_guard(spinlock_irqsave, &chip->open_lock) {
195 		if (up) {
196 			if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
197 				mod_timer(&chip->midi_timer, 1 + jiffies);
198 				chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
199 			}
200 		} else {
201 			if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
202 				chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
203 			}
204 		}
205 	}
206 
207 	if (up)
208 		snd_sb8dsp_midi_output_write(substream);
209 }
210 
211 static const struct snd_rawmidi_ops snd_sb8dsp_midi_output =
212 {
213 	.open =		snd_sb8dsp_midi_output_open,
214 	.close =	snd_sb8dsp_midi_output_close,
215 	.trigger =	snd_sb8dsp_midi_output_trigger,
216 };
217 
218 static const struct snd_rawmidi_ops snd_sb8dsp_midi_input =
219 {
220 	.open =		snd_sb8dsp_midi_input_open,
221 	.close =	snd_sb8dsp_midi_input_close,
222 	.trigger =	snd_sb8dsp_midi_input_trigger,
223 };
224 
225 int snd_sb8dsp_midi(struct snd_sb *chip, int device)
226 {
227 	struct snd_rawmidi *rmidi;
228 	int err;
229 
230 	err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi);
231 	if (err < 0)
232 		return err;
233 	strscpy(rmidi->name, "SB8 MIDI");
234 	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
235 	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
236 	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
237 	if (chip->hardware >= SB_HW_20)
238 		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
239 	rmidi->private_data = chip;
240 	timer_setup(&chip->midi_timer, snd_sb8dsp_midi_output_timer, 0);
241 	chip->rmidi = rmidi;
242 	return 0;
243 }
244