18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de> 48c2ecf20Sopenharmony_ci * Creative Audio MIDI, for the CA0106 Driver 58c2ecf20Sopenharmony_ci * Version: 0.0.1 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Changelog: 88c2ecf20Sopenharmony_ci * Implementation is based on mpu401 and emu10k1x and 98c2ecf20Sopenharmony_ci * tested with ca0106. 108c2ecf20Sopenharmony_ci * mpu401: Copyright (c) by Jaroslav Kysela <perex@perex.cz> 118c2ecf20Sopenharmony_ci * emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com> 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 158c2ecf20Sopenharmony_ci#include <sound/core.h> 168c2ecf20Sopenharmony_ci#include <sound/rawmidi.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include "ca_midi.h" 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#define ca_midi_write_data(midi, data) midi->write(midi, data, 0) 218c2ecf20Sopenharmony_ci#define ca_midi_write_cmd(midi, data) midi->write(midi, data, 1) 228c2ecf20Sopenharmony_ci#define ca_midi_read_data(midi) midi->read(midi, 0) 238c2ecf20Sopenharmony_ci#define ca_midi_read_stat(midi) midi->read(midi, 1) 248c2ecf20Sopenharmony_ci#define ca_midi_input_avail(midi) (!(ca_midi_read_stat(midi) & midi->input_avail)) 258c2ecf20Sopenharmony_ci#define ca_midi_output_ready(midi) (!(ca_midi_read_stat(midi) & midi->output_ready)) 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic void ca_midi_clear_rx(struct snd_ca_midi *midi) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci int timeout = 100000; 308c2ecf20Sopenharmony_ci for (; timeout > 0 && ca_midi_input_avail(midi); timeout--) 318c2ecf20Sopenharmony_ci ca_midi_read_data(midi); 328c2ecf20Sopenharmony_ci#ifdef CONFIG_SND_DEBUG 338c2ecf20Sopenharmony_ci if (timeout <= 0) 348c2ecf20Sopenharmony_ci pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n", 358c2ecf20Sopenharmony_ci ca_midi_read_stat(midi)); 368c2ecf20Sopenharmony_ci#endif 378c2ecf20Sopenharmony_ci} 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistatic void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status) 408c2ecf20Sopenharmony_ci{ 418c2ecf20Sopenharmony_ci unsigned char byte; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci if (midi->rmidi == NULL) { 448c2ecf20Sopenharmony_ci midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable); 458c2ecf20Sopenharmony_ci return; 468c2ecf20Sopenharmony_ci } 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci spin_lock(&midi->input_lock); 498c2ecf20Sopenharmony_ci if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) { 508c2ecf20Sopenharmony_ci if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { 518c2ecf20Sopenharmony_ci ca_midi_clear_rx(midi); 528c2ecf20Sopenharmony_ci } else { 538c2ecf20Sopenharmony_ci byte = ca_midi_read_data(midi); 548c2ecf20Sopenharmony_ci if(midi->substream_input) 558c2ecf20Sopenharmony_ci snd_rawmidi_receive(midi->substream_input, &byte, 1); 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci spin_unlock(&midi->input_lock); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci spin_lock(&midi->output_lock); 638c2ecf20Sopenharmony_ci if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) { 648c2ecf20Sopenharmony_ci if (midi->substream_output && 658c2ecf20Sopenharmony_ci snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) { 668c2ecf20Sopenharmony_ci ca_midi_write_data(midi, byte); 678c2ecf20Sopenharmony_ci } else { 688c2ecf20Sopenharmony_ci midi->interrupt_disable(midi,midi->tx_enable); 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci spin_unlock(&midi->output_lock); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci unsigned long flags; 788c2ecf20Sopenharmony_ci int timeout, ok; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci spin_lock_irqsave(&midi->input_lock, flags); 818c2ecf20Sopenharmony_ci ca_midi_write_data(midi, 0x00); 828c2ecf20Sopenharmony_ci /* ca_midi_clear_rx(midi); */ 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci ca_midi_write_cmd(midi, cmd); 858c2ecf20Sopenharmony_ci if (ack) { 868c2ecf20Sopenharmony_ci ok = 0; 878c2ecf20Sopenharmony_ci timeout = 10000; 888c2ecf20Sopenharmony_ci while (!ok && timeout-- > 0) { 898c2ecf20Sopenharmony_ci if (ca_midi_input_avail(midi)) { 908c2ecf20Sopenharmony_ci if (ca_midi_read_data(midi) == midi->ack) 918c2ecf20Sopenharmony_ci ok = 1; 928c2ecf20Sopenharmony_ci } 938c2ecf20Sopenharmony_ci } 948c2ecf20Sopenharmony_ci if (!ok && ca_midi_read_data(midi) == midi->ack) 958c2ecf20Sopenharmony_ci ok = 1; 968c2ecf20Sopenharmony_ci } else { 978c2ecf20Sopenharmony_ci ok = 1; 988c2ecf20Sopenharmony_ci } 998c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->input_lock, flags); 1008c2ecf20Sopenharmony_ci if (!ok) 1018c2ecf20Sopenharmony_ci pr_err("ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n", 1028c2ecf20Sopenharmony_ci cmd, 1038c2ecf20Sopenharmony_ci midi->get_dev_id_port(midi->dev_id), 1048c2ecf20Sopenharmony_ci ca_midi_read_stat(midi), 1058c2ecf20Sopenharmony_ci ca_midi_read_data(midi)); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic int ca_midi_input_open(struct snd_rawmidi_substream *substream) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct snd_ca_midi *midi = substream->rmidi->private_data; 1118c2ecf20Sopenharmony_ci unsigned long flags; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci if (snd_BUG_ON(!midi->dev_id)) 1148c2ecf20Sopenharmony_ci return -ENXIO; 1158c2ecf20Sopenharmony_ci spin_lock_irqsave(&midi->open_lock, flags); 1168c2ecf20Sopenharmony_ci midi->midi_mode |= CA_MIDI_MODE_INPUT; 1178c2ecf20Sopenharmony_ci midi->substream_input = substream; 1188c2ecf20Sopenharmony_ci if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) { 1198c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1208c2ecf20Sopenharmony_ci ca_midi_cmd(midi, midi->reset, 1); 1218c2ecf20Sopenharmony_ci ca_midi_cmd(midi, midi->enter_uart, 1); 1228c2ecf20Sopenharmony_ci } else { 1238c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci return 0; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int ca_midi_output_open(struct snd_rawmidi_substream *substream) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct snd_ca_midi *midi = substream->rmidi->private_data; 1318c2ecf20Sopenharmony_ci unsigned long flags; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (snd_BUG_ON(!midi->dev_id)) 1348c2ecf20Sopenharmony_ci return -ENXIO; 1358c2ecf20Sopenharmony_ci spin_lock_irqsave(&midi->open_lock, flags); 1368c2ecf20Sopenharmony_ci midi->midi_mode |= CA_MIDI_MODE_OUTPUT; 1378c2ecf20Sopenharmony_ci midi->substream_output = substream; 1388c2ecf20Sopenharmony_ci if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { 1398c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1408c2ecf20Sopenharmony_ci ca_midi_cmd(midi, midi->reset, 1); 1418c2ecf20Sopenharmony_ci ca_midi_cmd(midi, midi->enter_uart, 1); 1428c2ecf20Sopenharmony_ci } else { 1438c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci return 0; 1468c2ecf20Sopenharmony_ci} 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_cistatic int ca_midi_input_close(struct snd_rawmidi_substream *substream) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci struct snd_ca_midi *midi = substream->rmidi->private_data; 1518c2ecf20Sopenharmony_ci unsigned long flags; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci if (snd_BUG_ON(!midi->dev_id)) 1548c2ecf20Sopenharmony_ci return -ENXIO; 1558c2ecf20Sopenharmony_ci spin_lock_irqsave(&midi->open_lock, flags); 1568c2ecf20Sopenharmony_ci midi->interrupt_disable(midi,midi->rx_enable); 1578c2ecf20Sopenharmony_ci midi->midi_mode &= ~CA_MIDI_MODE_INPUT; 1588c2ecf20Sopenharmony_ci midi->substream_input = NULL; 1598c2ecf20Sopenharmony_ci if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) { 1608c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1618c2ecf20Sopenharmony_ci ca_midi_cmd(midi, midi->reset, 0); 1628c2ecf20Sopenharmony_ci } else { 1638c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci return 0; 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic int ca_midi_output_close(struct snd_rawmidi_substream *substream) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci struct snd_ca_midi *midi = substream->rmidi->private_data; 1718c2ecf20Sopenharmony_ci unsigned long flags; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (snd_BUG_ON(!midi->dev_id)) 1748c2ecf20Sopenharmony_ci return -ENXIO; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci spin_lock_irqsave(&midi->open_lock, flags); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci midi->interrupt_disable(midi,midi->tx_enable); 1798c2ecf20Sopenharmony_ci midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT; 1808c2ecf20Sopenharmony_ci midi->substream_output = NULL; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) { 1838c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1848c2ecf20Sopenharmony_ci ca_midi_cmd(midi, midi->reset, 0); 1858c2ecf20Sopenharmony_ci } else { 1868c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->open_lock, flags); 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci return 0; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up) 1928c2ecf20Sopenharmony_ci{ 1938c2ecf20Sopenharmony_ci struct snd_ca_midi *midi = substream->rmidi->private_data; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci if (snd_BUG_ON(!midi->dev_id)) 1968c2ecf20Sopenharmony_ci return; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci if (up) { 1998c2ecf20Sopenharmony_ci midi->interrupt_enable(midi,midi->rx_enable); 2008c2ecf20Sopenharmony_ci } else { 2018c2ecf20Sopenharmony_ci midi->interrupt_disable(midi, midi->rx_enable); 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci struct snd_ca_midi *midi = substream->rmidi->private_data; 2088c2ecf20Sopenharmony_ci unsigned long flags; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci if (snd_BUG_ON(!midi->dev_id)) 2118c2ecf20Sopenharmony_ci return; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (up) { 2148c2ecf20Sopenharmony_ci int max = 4; 2158c2ecf20Sopenharmony_ci unsigned char byte; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci spin_lock_irqsave(&midi->output_lock, flags); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci /* try to send some amount of bytes here before interrupts */ 2208c2ecf20Sopenharmony_ci while (max > 0) { 2218c2ecf20Sopenharmony_ci if (ca_midi_output_ready(midi)) { 2228c2ecf20Sopenharmony_ci if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) || 2238c2ecf20Sopenharmony_ci snd_rawmidi_transmit(substream, &byte, 1) != 1) { 2248c2ecf20Sopenharmony_ci /* no more data */ 2258c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->output_lock, flags); 2268c2ecf20Sopenharmony_ci return; 2278c2ecf20Sopenharmony_ci } 2288c2ecf20Sopenharmony_ci ca_midi_write_data(midi, byte); 2298c2ecf20Sopenharmony_ci max--; 2308c2ecf20Sopenharmony_ci } else { 2318c2ecf20Sopenharmony_ci break; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci } 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&midi->output_lock, flags); 2368c2ecf20Sopenharmony_ci midi->interrupt_enable(midi,midi->tx_enable); 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci } else { 2398c2ecf20Sopenharmony_ci midi->interrupt_disable(midi,midi->tx_enable); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic const struct snd_rawmidi_ops ca_midi_output = 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci .open = ca_midi_output_open, 2468c2ecf20Sopenharmony_ci .close = ca_midi_output_close, 2478c2ecf20Sopenharmony_ci .trigger = ca_midi_output_trigger, 2488c2ecf20Sopenharmony_ci}; 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic const struct snd_rawmidi_ops ca_midi_input = 2518c2ecf20Sopenharmony_ci{ 2528c2ecf20Sopenharmony_ci .open = ca_midi_input_open, 2538c2ecf20Sopenharmony_ci .close = ca_midi_input_close, 2548c2ecf20Sopenharmony_ci .trigger = ca_midi_input_trigger, 2558c2ecf20Sopenharmony_ci}; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_cistatic void ca_midi_free(struct snd_ca_midi *midi) 2588c2ecf20Sopenharmony_ci{ 2598c2ecf20Sopenharmony_ci midi->interrupt = NULL; 2608c2ecf20Sopenharmony_ci midi->interrupt_enable = NULL; 2618c2ecf20Sopenharmony_ci midi->interrupt_disable = NULL; 2628c2ecf20Sopenharmony_ci midi->read = NULL; 2638c2ecf20Sopenharmony_ci midi->write = NULL; 2648c2ecf20Sopenharmony_ci midi->get_dev_id_card = NULL; 2658c2ecf20Sopenharmony_ci midi->get_dev_id_port = NULL; 2668c2ecf20Sopenharmony_ci midi->rmidi = NULL; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic void ca_rmidi_free(struct snd_rawmidi *rmidi) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci ca_midi_free(rmidi->private_data); 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ciint ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci struct snd_rawmidi *rmidi; 2778c2ecf20Sopenharmony_ci int err; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci if ((err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi)) < 0) 2808c2ecf20Sopenharmony_ci return err; 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci midi->dev_id = dev_id; 2838c2ecf20Sopenharmony_ci midi->interrupt = ca_midi_interrupt; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci spin_lock_init(&midi->open_lock); 2868c2ecf20Sopenharmony_ci spin_lock_init(&midi->input_lock); 2878c2ecf20Sopenharmony_ci spin_lock_init(&midi->output_lock); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci strcpy(rmidi->name, name); 2908c2ecf20Sopenharmony_ci snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output); 2918c2ecf20Sopenharmony_ci snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input); 2928c2ecf20Sopenharmony_ci rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | 2938c2ecf20Sopenharmony_ci SNDRV_RAWMIDI_INFO_INPUT | 2948c2ecf20Sopenharmony_ci SNDRV_RAWMIDI_INFO_DUPLEX; 2958c2ecf20Sopenharmony_ci rmidi->private_data = midi; 2968c2ecf20Sopenharmony_ci rmidi->private_free = ca_rmidi_free; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci midi->rmidi = rmidi; 2998c2ecf20Sopenharmony_ci return 0; 3008c2ecf20Sopenharmony_ci} 3018c2ecf20Sopenharmony_ci 302