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
22irqreturn_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(&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(chip->midi_substream_input, &byte, 1);
43			}
44		}
45	}
46	spin_unlock(&chip->midi_input_lock);
47	return IRQ_HANDLED;
48}
49
50static 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(&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(&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(&chip->open_lock, flags);
73	}
74	return 0;
75}
76
77static 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(&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(&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(&chip->open_lock, flags);
100	}
101	return 0;
102}
103
104static 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(&chip->open_lock, flags);
115		snd_sbdsp_reset(chip);		/* reset DSP */
116	} else {
117		spin_unlock_irqrestore(&chip->open_lock, flags);
118	}
119	return 0;
120}
121
122static 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(&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(&chip->open_lock, flags);
134		snd_sbdsp_reset(chip);		/* reset DSP */
135	} else {
136		spin_unlock_irqrestore(&chip->open_lock, flags);
137	}
138	return 0;
139}
140
141static 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(&chip->open_lock, flags);
162}
163
164static 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, &byte, 1) != 1) {
176			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
177			del_timer(&chip->midi_timer);
178			spin_unlock_irqrestore(&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(&chip->open_lock, flags);
188				break;
189			}
190			outb(byte, SBP(chip, WRITE));
191		} else {
192			snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
193			snd_sbdsp_command(chip, byte);
194		}
195		snd_rawmidi_transmit_ack(substream, 1);
196		spin_unlock_irqrestore(&chip->open_lock, flags);
197	}
198}
199
200static 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(&chip->midi_timer, 1 + jiffies);
208	spin_unlock_irqrestore(&chip->open_lock, flags);
209	snd_sb8dsp_midi_output_write(substream);
210}
211
212static 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(&chip->midi_timer, 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(&chip->open_lock, flags);
230
231	if (up)
232		snd_sb8dsp_midi_output_write(substream);
233}
234
235static 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
242static 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
249int snd_sb8dsp_midi(struct snd_sb *chip, int device)
250{
251	struct snd_rawmidi *rmidi;
252	int err;
253
254	err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi);
255	if (err < 0)
256		return err;
257	strcpy(rmidi->name, "SB8 MIDI");
258	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
259	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &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