303 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* 3 * Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>4 * Creative Audio MIDI, for the CA0106 Driver5 * Version: 0.0.16 *7 * Changelog:8 * Implementation is based on mpu401 and emu10k1x and9 * tested with ca0106.10 * mpu401: Copyright (c) by Jaroslav Kysela <perex@perex.cz>11 * emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>12 */13 14#include <linux/spinlock.h>15#include <sound/core.h>16#include <sound/rawmidi.h>17 18#include "ca_midi.h"19 20#define ca_midi_write_data(midi, data) midi->write(midi, data, 0)21#define ca_midi_write_cmd(midi, data) midi->write(midi, data, 1)22#define ca_midi_read_data(midi) midi->read(midi, 0)23#define ca_midi_read_stat(midi) midi->read(midi, 1)24#define ca_midi_input_avail(midi) (!(ca_midi_read_stat(midi) & midi->input_avail))25#define ca_midi_output_ready(midi) (!(ca_midi_read_stat(midi) & midi->output_ready))26 27static void ca_midi_clear_rx(struct snd_ca_midi *midi)28{29 int timeout = 100000;30 for (; timeout > 0 && ca_midi_input_avail(midi); timeout--)31 ca_midi_read_data(midi);32#ifdef CONFIG_SND_DEBUG33 if (timeout <= 0)34 pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n",35 ca_midi_read_stat(midi));36#endif37}38 39static void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status)40{41 unsigned char byte;42 43 if (midi->rmidi == NULL) {44 midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable);45 return;46 }47 48 spin_lock(&midi->input_lock);49 if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) {50 if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {51 ca_midi_clear_rx(midi);52 } else {53 byte = ca_midi_read_data(midi);54 if(midi->substream_input)55 snd_rawmidi_receive(midi->substream_input, &byte, 1);56 57 58 }59 }60 spin_unlock(&midi->input_lock);61 62 spin_lock(&midi->output_lock);63 if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) {64 if (midi->substream_output &&65 snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {66 ca_midi_write_data(midi, byte);67 } else {68 midi->interrupt_disable(midi,midi->tx_enable);69 }70 }71 spin_unlock(&midi->output_lock);72 73}74 75static void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack)76{77 unsigned long flags;78 int timeout, ok;79 80 spin_lock_irqsave(&midi->input_lock, flags);81 ca_midi_write_data(midi, 0x00);82 /* ca_midi_clear_rx(midi); */83 84 ca_midi_write_cmd(midi, cmd);85 if (ack) {86 ok = 0;87 timeout = 10000;88 while (!ok && timeout-- > 0) {89 if (ca_midi_input_avail(midi)) {90 if (ca_midi_read_data(midi) == midi->ack)91 ok = 1;92 }93 }94 if (!ok && ca_midi_read_data(midi) == midi->ack)95 ok = 1;96 } else {97 ok = 1;98 }99 spin_unlock_irqrestore(&midi->input_lock, flags);100 if (!ok)101 pr_err("ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n",102 cmd,103 midi->get_dev_id_port(midi->dev_id),104 ca_midi_read_stat(midi),105 ca_midi_read_data(midi));106}107 108static int ca_midi_input_open(struct snd_rawmidi_substream *substream)109{110 struct snd_ca_midi *midi = substream->rmidi->private_data;111 unsigned long flags;112 113 if (snd_BUG_ON(!midi->dev_id))114 return -ENXIO;115 spin_lock_irqsave(&midi->open_lock, flags);116 midi->midi_mode |= CA_MIDI_MODE_INPUT;117 midi->substream_input = substream;118 if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {119 spin_unlock_irqrestore(&midi->open_lock, flags);120 ca_midi_cmd(midi, midi->reset, 1);121 ca_midi_cmd(midi, midi->enter_uart, 1);122 } else {123 spin_unlock_irqrestore(&midi->open_lock, flags);124 }125 return 0;126}127 128static int ca_midi_output_open(struct snd_rawmidi_substream *substream)129{130 struct snd_ca_midi *midi = substream->rmidi->private_data;131 unsigned long flags;132 133 if (snd_BUG_ON(!midi->dev_id))134 return -ENXIO;135 spin_lock_irqsave(&midi->open_lock, flags);136 midi->midi_mode |= CA_MIDI_MODE_OUTPUT;137 midi->substream_output = substream;138 if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {139 spin_unlock_irqrestore(&midi->open_lock, flags);140 ca_midi_cmd(midi, midi->reset, 1);141 ca_midi_cmd(midi, midi->enter_uart, 1);142 } else {143 spin_unlock_irqrestore(&midi->open_lock, flags);144 }145 return 0;146}147 148static int ca_midi_input_close(struct snd_rawmidi_substream *substream)149{150 struct snd_ca_midi *midi = substream->rmidi->private_data;151 unsigned long flags;152 153 if (snd_BUG_ON(!midi->dev_id))154 return -ENXIO;155 spin_lock_irqsave(&midi->open_lock, flags);156 midi->interrupt_disable(midi,midi->rx_enable);157 midi->midi_mode &= ~CA_MIDI_MODE_INPUT;158 midi->substream_input = NULL;159 if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {160 spin_unlock_irqrestore(&midi->open_lock, flags);161 ca_midi_cmd(midi, midi->reset, 0);162 } else {163 spin_unlock_irqrestore(&midi->open_lock, flags);164 }165 return 0;166}167 168static int ca_midi_output_close(struct snd_rawmidi_substream *substream)169{170 struct snd_ca_midi *midi = substream->rmidi->private_data;171 unsigned long flags;172 173 if (snd_BUG_ON(!midi->dev_id))174 return -ENXIO;175 176 spin_lock_irqsave(&midi->open_lock, flags);177 178 midi->interrupt_disable(midi,midi->tx_enable);179 midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT;180 midi->substream_output = NULL;181 182 if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {183 spin_unlock_irqrestore(&midi->open_lock, flags);184 ca_midi_cmd(midi, midi->reset, 0);185 } else {186 spin_unlock_irqrestore(&midi->open_lock, flags);187 }188 return 0;189}190 191static void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)192{193 struct snd_ca_midi *midi = substream->rmidi->private_data;194 195 if (snd_BUG_ON(!midi->dev_id))196 return;197 198 if (up) {199 midi->interrupt_enable(midi,midi->rx_enable);200 } else {201 midi->interrupt_disable(midi, midi->rx_enable);202 }203}204 205static void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)206{207 struct snd_ca_midi *midi = substream->rmidi->private_data;208 unsigned long flags;209 210 if (snd_BUG_ON(!midi->dev_id))211 return;212 213 if (up) {214 int max = 4;215 unsigned char byte;216 217 spin_lock_irqsave(&midi->output_lock, flags);218 219 /* try to send some amount of bytes here before interrupts */220 while (max > 0) {221 if (ca_midi_output_ready(midi)) {222 if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) ||223 snd_rawmidi_transmit(substream, &byte, 1) != 1) {224 /* no more data */225 spin_unlock_irqrestore(&midi->output_lock, flags);226 return;227 }228 ca_midi_write_data(midi, byte);229 max--;230 } else {231 break;232 }233 }234 235 spin_unlock_irqrestore(&midi->output_lock, flags);236 midi->interrupt_enable(midi,midi->tx_enable);237 238 } else {239 midi->interrupt_disable(midi,midi->tx_enable);240 }241}242 243static const struct snd_rawmidi_ops ca_midi_output =244{245 .open = ca_midi_output_open,246 .close = ca_midi_output_close,247 .trigger = ca_midi_output_trigger,248};249 250static const struct snd_rawmidi_ops ca_midi_input =251{252 .open = ca_midi_input_open,253 .close = ca_midi_input_close,254 .trigger = ca_midi_input_trigger,255};256 257static void ca_midi_free(struct snd_ca_midi *midi)258{259 midi->interrupt = NULL;260 midi->interrupt_enable = NULL;261 midi->interrupt_disable = NULL;262 midi->read = NULL;263 midi->write = NULL;264 midi->get_dev_id_card = NULL;265 midi->get_dev_id_port = NULL;266 midi->rmidi = NULL;267}268 269static void ca_rmidi_free(struct snd_rawmidi *rmidi)270{271 ca_midi_free(rmidi->private_data);272}273 274int ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name)275{276 struct snd_rawmidi *rmidi;277 int err;278 279 err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi);280 if (err < 0)281 return err;282 283 midi->dev_id = dev_id;284 midi->interrupt = ca_midi_interrupt;285 286 spin_lock_init(&midi->open_lock);287 spin_lock_init(&midi->input_lock);288 spin_lock_init(&midi->output_lock);289 290 strcpy(rmidi->name, name);291 snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output);292 snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input);293 rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |294 SNDRV_RAWMIDI_INFO_INPUT |295 SNDRV_RAWMIDI_INFO_DUPLEX;296 rmidi->private_data = midi;297 rmidi->private_free = ca_rmidi_free;298 299 midi->rmidi = rmidi;300 return 0;301}302 303