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