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