1/*
2 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
3 *  Routines for the GF1 MIDI interface - like UART 6850
4 *
5 *
6 *   This program is free software; you can redistribute it and/or modify
7 *   it under the terms of the GNU General Public License as published by
8 *   the Free Software Foundation; either version 2 of the License, or
9 *   (at your option) any later version.
10 *
11 *   This program is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *   GNU General Public License for more details.
15 *
16 *   You should have received a copy of the GNU General Public License
17 *   along with this program; if not, write to the Free Software
18 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 *
20 */
21
22#include <sound/driver.h>
23#include <linux/delay.h>
24#include <linux/interrupt.h>
25#include <linux/time.h>
26#include <sound/core.h>
27#include <sound/gus.h>
28
29static void snd_gf1_interrupt_midi_in(struct snd_gus_card * gus)
30{
31	int count;
32	unsigned char stat, data, byte;
33	unsigned long flags;
34
35	count = 10;
36	while (count) {
37		spin_lock_irqsave(&gus->uart_cmd_lock, flags);
38		stat = snd_gf1_uart_stat(gus);
39		if (!(stat & 0x01)) {	/* data in Rx FIFO? */
40			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
41			count--;
42			continue;
43		}
44		count = 100;	/* arm counter to new value */
45		data = snd_gf1_uart_get(gus);
46		if (!(gus->gf1.uart_cmd & 0x80)) {
47			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
48			continue;
49		}
50		if (stat & 0x10) {	/* framing error */
51			gus->gf1.uart_framing++;
52			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
53			continue;
54		}
55		byte = snd_gf1_uart_get(gus);
56		spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
57		snd_rawmidi_receive(gus->midi_substream_input, &byte, 1);
58		if (stat & 0x20) {
59			gus->gf1.uart_overrun++;
60		}
61	}
62}
63
64static void snd_gf1_interrupt_midi_out(struct snd_gus_card * gus)
65{
66	char byte;
67	unsigned long flags;
68
69	/* try unlock output */
70	if (snd_gf1_uart_stat(gus) & 0x01)
71		snd_gf1_interrupt_midi_in(gus);
72
73	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
74	if (snd_gf1_uart_stat(gus) & 0x02) {	/* Tx FIFO free? */
75		if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) {	/* no other bytes or error */
76			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */
77		} else {
78			snd_gf1_uart_put(gus, byte);
79		}
80	}
81	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
82}
83
84static void snd_gf1_uart_reset(struct snd_gus_card * gus, int close)
85{
86	snd_gf1_uart_cmd(gus, 0x03);	/* reset */
87	if (!close && gus->uart_enable) {
88		udelay(160);
89		snd_gf1_uart_cmd(gus, 0x00);	/* normal operations */
90	}
91}
92
93static int snd_gf1_uart_output_open(struct snd_rawmidi_substream *substream)
94{
95	unsigned long flags;
96	struct snd_gus_card *gus;
97
98	gus = substream->rmidi->private_data;
99	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
100	if (!(gus->gf1.uart_cmd & 0x80)) {	/* input active? */
101		snd_gf1_uart_reset(gus, 0);
102	}
103	gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out;
104	gus->midi_substream_output = substream;
105	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
106	return 0;
107}
108
109static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream)
110{
111	unsigned long flags;
112	struct snd_gus_card *gus;
113	int i;
114
115	gus = substream->rmidi->private_data;
116	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
117	if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) {
118		snd_gf1_uart_reset(gus, 0);
119	}
120	gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;
121	gus->midi_substream_input = substream;
122	if (gus->uart_enable) {
123		for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)
124			snd_gf1_uart_get(gus);	/* clean Rx */
125		if (i >= 1000)
126			snd_printk(KERN_ERR "gus midi uart init read - cleanup error\n");
127	}
128	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
129	return 0;
130}
131
132static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream)
133{
134	unsigned long flags;
135	struct snd_gus_card *gus;
136
137	gus = substream->rmidi->private_data;
138	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
139	if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in)
140		snd_gf1_uart_reset(gus, 1);
141	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT);
142	gus->midi_substream_output = NULL;
143	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
144	return 0;
145}
146
147static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream)
148{
149	unsigned long flags;
150	struct snd_gus_card *gus;
151
152	gus = substream->rmidi->private_data;
153	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
154	if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out)
155		snd_gf1_uart_reset(gus, 1);
156	snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN);
157	gus->midi_substream_input = NULL;
158	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
159	return 0;
160}
161
162static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)
163{
164	struct snd_gus_card *gus;
165	unsigned long flags;
166
167	gus = substream->rmidi->private_data;
168
169	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
170	if (up) {
171		if ((gus->gf1.uart_cmd & 0x80) == 0)
172			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */
173	} else {
174		if (gus->gf1.uart_cmd & 0x80)
175			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */
176	}
177	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
178}
179
180static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)
181{
182	unsigned long flags;
183	struct snd_gus_card *gus;
184	char byte;
185	int timeout;
186
187	gus = substream->rmidi->private_data;
188
189	spin_lock_irqsave(&gus->uart_cmd_lock, flags);
190	if (up) {
191		if ((gus->gf1.uart_cmd & 0x20) == 0) {
192			spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
193			/* wait for empty Rx - Tx is probably unlocked */
194			timeout = 10000;
195			while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01);
196			/* Tx FIFO free? */
197			spin_lock_irqsave(&gus->uart_cmd_lock, flags);
198			if (gus->gf1.uart_cmd & 0x20) {
199				spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
200				return;
201			}
202			if (snd_gf1_uart_stat(gus) & 0x02) {
203				if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {
204					spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
205					return;
206				}
207				snd_gf1_uart_put(gus, byte);
208			}
209			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20);	/* enable Tx interrupt */
210		}
211	} else {
212		if (gus->gf1.uart_cmd & 0x20)
213			snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20);
214	}
215	spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);
216}
217
218static struct snd_rawmidi_ops snd_gf1_uart_output =
219{
220	.open =		snd_gf1_uart_output_open,
221	.close =	snd_gf1_uart_output_close,
222	.trigger =	snd_gf1_uart_output_trigger,
223};
224
225static struct snd_rawmidi_ops snd_gf1_uart_input =
226{
227	.open =		snd_gf1_uart_input_open,
228	.close =	snd_gf1_uart_input_close,
229	.trigger =	snd_gf1_uart_input_trigger,
230};
231
232int snd_gf1_rawmidi_new(struct snd_gus_card * gus, int device, struct snd_rawmidi ** rrawmidi)
233{
234	struct snd_rawmidi *rmidi;
235	int err;
236
237	if (rrawmidi)
238		*rrawmidi = NULL;
239	if ((err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi)) < 0)
240		return err;
241	strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1");
242	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output);
243	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input);
244	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
245	rmidi->private_data = gus;
246	gus->midi_uart = rmidi;
247	if (rrawmidi)
248		*rrawmidi = rmidi;
249	return err;
250}
251