| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) by Jaroslav Kysela <perex@perex.cz> |
| * Routines for the GF1 MIDI interface - like UART 6850 |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/time.h> |
| #include <sound/core.h> |
| #include <sound/gus.h> |
| |
| static void snd_gf1_interrupt_midi_in(struct snd_gus_card * gus) |
| { |
| int count; |
| unsigned char stat, byte; |
| __always_unused unsigned char data; |
| unsigned long flags; |
| |
| count = 10; |
| while (count) { |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| stat = snd_gf1_uart_stat(gus); |
| if (!(stat & 0x01)) { /* data in Rx FIFO? */ |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| count--; |
| continue; |
| } |
| count = 100; /* arm counter to new value */ |
| data = snd_gf1_uart_get(gus); |
| if (!(gus->gf1.uart_cmd & 0x80)) { |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| continue; |
| } |
| if (stat & 0x10) { /* framing error */ |
| gus->gf1.uart_framing++; |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| continue; |
| } |
| byte = snd_gf1_uart_get(gus); |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| snd_rawmidi_receive(gus->midi_substream_input, &byte, 1); |
| if (stat & 0x20) { |
| gus->gf1.uart_overrun++; |
| } |
| } |
| } |
| |
| static void snd_gf1_interrupt_midi_out(struct snd_gus_card * gus) |
| { |
| char byte; |
| unsigned long flags; |
| |
| /* try unlock output */ |
| if (snd_gf1_uart_stat(gus) & 0x01) |
| snd_gf1_interrupt_midi_in(gus); |
| |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (snd_gf1_uart_stat(gus) & 0x02) { /* Tx FIFO free? */ |
| if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) { /* no other bytes or error */ |
| snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */ |
| } else { |
| snd_gf1_uart_put(gus, byte); |
| } |
| } |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| } |
| |
| static void snd_gf1_uart_reset(struct snd_gus_card * gus, int close) |
| { |
| snd_gf1_uart_cmd(gus, 0x03); /* reset */ |
| if (!close && gus->uart_enable) { |
| udelay(160); |
| snd_gf1_uart_cmd(gus, 0x00); /* normal operations */ |
| } |
| } |
| |
| static int snd_gf1_uart_output_open(struct snd_rawmidi_substream *substream) |
| { |
| unsigned long flags; |
| struct snd_gus_card *gus; |
| |
| gus = substream->rmidi->private_data; |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (!(gus->gf1.uart_cmd & 0x80)) { /* input active? */ |
| snd_gf1_uart_reset(gus, 0); |
| } |
| gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out; |
| gus->midi_substream_output = substream; |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| #if 0 |
| dev_dbg(gus->card->dev, |
| "write init - cmd = 0x%x, stat = 0x%x\n", |
| gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); |
| #endif |
| return 0; |
| } |
| |
| static int snd_gf1_uart_input_open(struct snd_rawmidi_substream *substream) |
| { |
| unsigned long flags; |
| struct snd_gus_card *gus; |
| int i; |
| |
| gus = substream->rmidi->private_data; |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) { |
| snd_gf1_uart_reset(gus, 0); |
| } |
| gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in; |
| gus->midi_substream_input = substream; |
| if (gus->uart_enable) { |
| for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++) |
| snd_gf1_uart_get(gus); /* clean Rx */ |
| if (i >= 1000) |
| dev_err(gus->card->dev, "gus midi uart init read - cleanup error\n"); |
| } |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| #if 0 |
| dev_dbg(gus->card->dev, |
| "read init - enable = %i, cmd = 0x%x, stat = 0x%x\n", |
| gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); |
| dev_dbg(gus->card->dev, |
| "[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x (page = 0x%x)\n", |
| gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100), |
| inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102)); |
| #endif |
| return 0; |
| } |
| |
| static int snd_gf1_uart_output_close(struct snd_rawmidi_substream *substream) |
| { |
| unsigned long flags; |
| struct snd_gus_card *gus; |
| |
| gus = substream->rmidi->private_data; |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in) |
| snd_gf1_uart_reset(gus, 1); |
| snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT); |
| gus->midi_substream_output = NULL; |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| return 0; |
| } |
| |
| static int snd_gf1_uart_input_close(struct snd_rawmidi_substream *substream) |
| { |
| unsigned long flags; |
| struct snd_gus_card *gus; |
| |
| gus = substream->rmidi->private_data; |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) |
| snd_gf1_uart_reset(gus, 1); |
| snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN); |
| gus->midi_substream_input = NULL; |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| return 0; |
| } |
| |
| static void snd_gf1_uart_input_trigger(struct snd_rawmidi_substream *substream, int up) |
| { |
| struct snd_gus_card *gus; |
| unsigned long flags; |
| |
| gus = substream->rmidi->private_data; |
| |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (up) { |
| if ((gus->gf1.uart_cmd & 0x80) == 0) |
| snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */ |
| } else { |
| if (gus->gf1.uart_cmd & 0x80) |
| snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */ |
| } |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| } |
| |
| static void snd_gf1_uart_output_trigger(struct snd_rawmidi_substream *substream, int up) |
| { |
| unsigned long flags; |
| struct snd_gus_card *gus; |
| char byte; |
| int timeout; |
| |
| gus = substream->rmidi->private_data; |
| |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (up) { |
| if ((gus->gf1.uart_cmd & 0x20) == 0) { |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| /* wait for empty Rx - Tx is probably unlocked */ |
| timeout = 10000; |
| while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01); |
| /* Tx FIFO free? */ |
| spin_lock_irqsave(&gus->uart_cmd_lock, flags); |
| if (gus->gf1.uart_cmd & 0x20) { |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| return; |
| } |
| if (snd_gf1_uart_stat(gus) & 0x02) { |
| if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| return; |
| } |
| snd_gf1_uart_put(gus, byte); |
| } |
| snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20); /* enable Tx interrupt */ |
| } |
| } else { |
| if (gus->gf1.uart_cmd & 0x20) |
| snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); |
| } |
| spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); |
| } |
| |
| static const struct snd_rawmidi_ops snd_gf1_uart_output = |
| { |
| .open = snd_gf1_uart_output_open, |
| .close = snd_gf1_uart_output_close, |
| .trigger = snd_gf1_uart_output_trigger, |
| }; |
| |
| static const struct snd_rawmidi_ops snd_gf1_uart_input = |
| { |
| .open = snd_gf1_uart_input_open, |
| .close = snd_gf1_uart_input_close, |
| .trigger = snd_gf1_uart_input_trigger, |
| }; |
| |
| int snd_gf1_rawmidi_new(struct snd_gus_card *gus, int device) |
| { |
| struct snd_rawmidi *rmidi; |
| int err; |
| |
| err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi); |
| if (err < 0) |
| return err; |
| strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1"); |
| snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output); |
| snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input); |
| rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; |
| rmidi->private_data = gus; |
| gus->midi_uart = rmidi; |
| return err; |
| } |