247 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (c) by Jaroslav Kysela <perex@perex.cz>4 * Routines for the GF1 MIDI interface - like UART 68505 */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 13static 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 49static 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 69static 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 78static 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 092 dev_dbg(gus->card->dev,93 "write init - cmd = 0x%x, stat = 0x%x\n",94 gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));95#endif96 return 0;97}98 99static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream)100{101 unsigned long flags;102 struct snd_gus_card *gus;103 int i;104 105 gus = substream->rmidi->private_data;106 spin_lock_irqsave(&gus->uart_cmd_lock, flags);107 if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) {108 snd_gf1_uart_reset(gus, 0);109 }110 gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in;111 gus->midi_substream_input = substream;112 if (gus->uart_enable) {113 for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++)114 snd_gf1_uart_get(gus); /* clean Rx */115 if (i >= 1000)116 dev_err(gus->card->dev, "gus midi uart init read - cleanup error\n");117 }118 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);119#if 0120 dev_dbg(gus->card->dev,121 "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n",122 gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus));123 dev_dbg(gus->card->dev,124 "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x (page = 0x%x)\n",125 gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100),126 inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102));127#endif128 return 0;129}130 131static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream)132{133 unsigned long flags;134 struct snd_gus_card *gus;135 136 gus = substream->rmidi->private_data;137 spin_lock_irqsave(&gus->uart_cmd_lock, flags);138 if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in)139 snd_gf1_uart_reset(gus, 1);140 snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT);141 gus->midi_substream_output = NULL;142 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);143 return 0;144}145 146static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream)147{148 unsigned long flags;149 struct snd_gus_card *gus;150 151 gus = substream->rmidi->private_data;152 spin_lock_irqsave(&gus->uart_cmd_lock, flags);153 if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out)154 snd_gf1_uart_reset(gus, 1);155 snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN);156 gus->midi_substream_input = NULL;157 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);158 return 0;159}160 161static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up)162{163 struct snd_gus_card *gus;164 unsigned long flags;165 166 gus = substream->rmidi->private_data;167 168 spin_lock_irqsave(&gus->uart_cmd_lock, flags);169 if (up) {170 if ((gus->gf1.uart_cmd & 0x80) == 0)171 snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */172 } else {173 if (gus->gf1.uart_cmd & 0x80)174 snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */175 }176 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);177}178 179static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up)180{181 unsigned long flags;182 struct snd_gus_card *gus;183 char byte;184 int timeout;185 186 gus = substream->rmidi->private_data;187 188 spin_lock_irqsave(&gus->uart_cmd_lock, flags);189 if (up) {190 if ((gus->gf1.uart_cmd & 0x20) == 0) {191 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);192 /* wait for empty Rx - Tx is probably unlocked */193 timeout = 10000;194 while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01);195 /* Tx FIFO free? */196 spin_lock_irqsave(&gus->uart_cmd_lock, flags);197 if (gus->gf1.uart_cmd & 0x20) {198 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);199 return;200 }201 if (snd_gf1_uart_stat(gus) & 0x02) {202 if (snd_rawmidi_transmit(substream, &byte, 1) != 1) {203 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);204 return;205 }206 snd_gf1_uart_put(gus, byte);207 }208 snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20); /* enable Tx interrupt */209 }210 } else {211 if (gus->gf1.uart_cmd & 0x20)212 snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20);213 }214 spin_unlock_irqrestore(&gus->uart_cmd_lock, flags);215}216 217static const struct snd_rawmidi_ops snd_gf1_uart_output =218{219 .open = snd_gf1_uart_output_open,220 .close = snd_gf1_uart_output_close,221 .trigger = snd_gf1_uart_output_trigger,222};223 224static const struct snd_rawmidi_ops snd_gf1_uart_input =225{226 .open = snd_gf1_uart_input_open,227 .close = snd_gf1_uart_input_close,228 .trigger = snd_gf1_uart_input_trigger,229};230 231int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device)232{233 struct snd_rawmidi *rmidi;234 int err;235 236 err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi);237 if (err < 0)238 return err;239 strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1");240 snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output);241 snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input);242 rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;243 rmidi->private_data = gus;244 gus->midi_uart = rmidi;245 return err;246}247