1/*
2 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
3 *  Routines for control of SoundBlaster cards - MIDI interface
4 *
5 *   This program is free software; you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation; either version 2 of the License, or
8 *   (at your option) any later version.
9 *
10 *   This program is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 *   GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with this program; if not, write to the Free Software
17 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 *
19 * --
20 *
21 * Sun May  9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
22 *   Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from
23 *   working.
24 *
25 * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de>
26 *   Added full duplex UART mode for DSP version 2.0 and later.
27 */
28
29#include <sound/driver.h>
30#include <asm/io.h>
31#include <linux/time.h>
32#include <sound/core.h>
33#include <sound/sb.h>
34
35
36irqreturn_t snd_sb8dsp_midi_interrupt(struct snd_sb *chip)
37{
38	struct snd_rawmidi *rmidi;
39	int max = 64;
40	char byte;
41
42	if (!chip)
43		return IRQ_NONE;
44
45	rmidi = chip->rmidi;
46	if (!rmidi) {
47		inb(SBP(chip, DATA_AVAIL));	/* ack interrupt */
48		return IRQ_NONE;
49	}
50
51	spin_lock(&chip->midi_input_lock);
52	while (max-- > 0) {
53		if (inb(SBP(chip, DATA_AVAIL)) & 0x80) {
54			byte = inb(SBP(chip, READ));
55			if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
56				snd_rawmidi_receive(chip->midi_substream_input, &byte, 1);
57			}
58		}
59	}
60	spin_unlock(&chip->midi_input_lock);
61	return IRQ_HANDLED;
62}
63
64static int snd_sb8dsp_midi_input_open(struct snd_rawmidi_substream *substream)
65{
66	unsigned long flags;
67	struct snd_sb *chip;
68	unsigned int valid_open_flags;
69
70	chip = substream->rmidi->private_data;
71	valid_open_flags = chip->hardware >= SB_HW_20
72		? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0;
73	spin_lock_irqsave(&chip->open_lock, flags);
74	if (chip->open & ~valid_open_flags) {
75		spin_unlock_irqrestore(&chip->open_lock, flags);
76		return -EAGAIN;
77	}
78	chip->open |= SB_OPEN_MIDI_INPUT;
79	chip->midi_substream_input = substream;
80	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
81		spin_unlock_irqrestore(&chip->open_lock, flags);
82		snd_sbdsp_reset(chip);		/* reset DSP */
83		if (chip->hardware >= SB_HW_20)
84			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
85	} else {
86		spin_unlock_irqrestore(&chip->open_lock, flags);
87	}
88	return 0;
89}
90
91static int snd_sb8dsp_midi_output_open(struct snd_rawmidi_substream *substream)
92{
93	unsigned long flags;
94	struct snd_sb *chip;
95	unsigned int valid_open_flags;
96
97	chip = substream->rmidi->private_data;
98	valid_open_flags = chip->hardware >= SB_HW_20
99		? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0;
100	spin_lock_irqsave(&chip->open_lock, flags);
101	if (chip->open & ~valid_open_flags) {
102		spin_unlock_irqrestore(&chip->open_lock, flags);
103		return -EAGAIN;
104	}
105	chip->open |= SB_OPEN_MIDI_OUTPUT;
106	chip->midi_substream_output = substream;
107	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
108		spin_unlock_irqrestore(&chip->open_lock, flags);
109		snd_sbdsp_reset(chip);		/* reset DSP */
110		if (chip->hardware >= SB_HW_20)
111			snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ);
112	} else {
113		spin_unlock_irqrestore(&chip->open_lock, flags);
114	}
115	return 0;
116}
117
118static int snd_sb8dsp_midi_input_close(struct snd_rawmidi_substream *substream)
119{
120	unsigned long flags;
121	struct snd_sb *chip;
122
123	chip = substream->rmidi->private_data;
124	spin_lock_irqsave(&chip->open_lock, flags);
125	chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER);
126	chip->midi_substream_input = NULL;
127	if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) {
128		spin_unlock_irqrestore(&chip->open_lock, flags);
129		snd_sbdsp_reset(chip);		/* reset DSP */
130	} else {
131		spin_unlock_irqrestore(&chip->open_lock, flags);
132	}
133	return 0;
134}
135
136static int snd_sb8dsp_midi_output_close(struct snd_rawmidi_substream *substream)
137{
138	unsigned long flags;
139	struct snd_sb *chip;
140
141	chip = substream->rmidi->private_data;
142	spin_lock_irqsave(&chip->open_lock, flags);
143	chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER);
144	chip->midi_substream_output = NULL;
145	if (!(chip->open & SB_OPEN_MIDI_INPUT)) {
146		spin_unlock_irqrestore(&chip->open_lock, flags);
147		snd_sbdsp_reset(chip);		/* reset DSP */
148	} else {
149		spin_unlock_irqrestore(&chip->open_lock, flags);
150	}
151	return 0;
152}
153
154static void snd_sb8dsp_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
155{
156	unsigned long flags;
157	struct snd_sb *chip;
158
159	chip = substream->rmidi->private_data;
160	spin_lock_irqsave(&chip->open_lock, flags);
161	if (up) {
162		if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) {
163			if (chip->hardware < SB_HW_20)
164				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
165			chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER;
166		}
167	} else {
168		if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) {
169			if (chip->hardware < SB_HW_20)
170				snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ);
171			chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER;
172		}
173	}
174	spin_unlock_irqrestore(&chip->open_lock, flags);
175}
176
177static void snd_sb8dsp_midi_output_write(struct snd_rawmidi_substream *substream)
178{
179	unsigned long flags;
180	struct snd_sb *chip;
181	char byte;
182	int max = 32;
183
184	/* how big is Tx FIFO? */
185	chip = substream->rmidi->private_data;
186	while (max-- > 0) {
187		spin_lock_irqsave(&chip->open_lock, flags);
188		if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) {
189			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
190			del_timer(&chip->midi_timer);
191			spin_unlock_irqrestore(&chip->open_lock, flags);
192			break;
193		}
194		if (chip->hardware >= SB_HW_20) {
195			int timeout = 8;
196			while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0)
197				;
198			if (timeout == 0) {
199				/* Tx FIFO full - try again later */
200				spin_unlock_irqrestore(&chip->open_lock, flags);
201				break;
202			}
203			outb(byte, SBP(chip, WRITE));
204		} else {
205			snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT);
206			snd_sbdsp_command(chip, byte);
207		}
208		snd_rawmidi_transmit_ack(substream, 1);
209		spin_unlock_irqrestore(&chip->open_lock, flags);
210	}
211}
212
213static void snd_sb8dsp_midi_output_timer(unsigned long data)
214{
215	struct snd_rawmidi_substream *substream = (struct snd_rawmidi_substream *) data;
216	struct snd_sb * chip = substream->rmidi->private_data;
217	unsigned long flags;
218
219	spin_lock_irqsave(&chip->open_lock, flags);
220	chip->midi_timer.expires = 1 + jiffies;
221	add_timer(&chip->midi_timer);
222	spin_unlock_irqrestore(&chip->open_lock, flags);
223	snd_sb8dsp_midi_output_write(substream);
224}
225
226static void snd_sb8dsp_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
227{
228	unsigned long flags;
229	struct snd_sb *chip;
230
231	chip = substream->rmidi->private_data;
232	spin_lock_irqsave(&chip->open_lock, flags);
233	if (up) {
234		if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) {
235			init_timer(&chip->midi_timer);
236			chip->midi_timer.function = snd_sb8dsp_midi_output_timer;
237			chip->midi_timer.data = (unsigned long) substream;
238			chip->midi_timer.expires = 1 + jiffies;
239			add_timer(&chip->midi_timer);
240			chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER;
241		}
242	} else {
243		if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) {
244			chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER;
245		}
246	}
247	spin_unlock_irqrestore(&chip->open_lock, flags);
248
249	if (up)
250		snd_sb8dsp_midi_output_write(substream);
251}
252
253static struct snd_rawmidi_ops snd_sb8dsp_midi_output =
254{
255	.open =		snd_sb8dsp_midi_output_open,
256	.close =	snd_sb8dsp_midi_output_close,
257	.trigger =	snd_sb8dsp_midi_output_trigger,
258};
259
260static struct snd_rawmidi_ops snd_sb8dsp_midi_input =
261{
262	.open =		snd_sb8dsp_midi_input_open,
263	.close =	snd_sb8dsp_midi_input_close,
264	.trigger =	snd_sb8dsp_midi_input_trigger,
265};
266
267int snd_sb8dsp_midi(struct snd_sb *chip, int device, struct snd_rawmidi ** rrawmidi)
268{
269	struct snd_rawmidi *rmidi;
270	int err;
271
272	if (rrawmidi)
273		*rrawmidi = NULL;
274	if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0)
275		return err;
276	strcpy(rmidi->name, "SB8 MIDI");
277	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output);
278	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input);
279	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT;
280	if (chip->hardware >= SB_HW_20)
281		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
282	rmidi->private_data = chip;
283	chip->rmidi = rmidi;
284	if (rrawmidi)
285		*rrawmidi = rmidi;
286	return 0;
287}
288