162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
462306a36Sopenharmony_ci *  Creative Audio MIDI, for the CA0106 Driver
562306a36Sopenharmony_ci *  Version: 0.0.1
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  Changelog:
862306a36Sopenharmony_ci *    Implementation is based on mpu401 and emu10k1x and
962306a36Sopenharmony_ci *    tested with ca0106.
1062306a36Sopenharmony_ci *    mpu401: Copyright (c) by Jaroslav Kysela <perex@perex.cz>
1162306a36Sopenharmony_ci *    emu10k1x: Copyright (c) by Francisco Moraes <fmoraes@nc.rr.com>
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/spinlock.h>
1562306a36Sopenharmony_ci#include <sound/core.h>
1662306a36Sopenharmony_ci#include <sound/rawmidi.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "ca_midi.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define ca_midi_write_data(midi, data)	midi->write(midi, data, 0)
2162306a36Sopenharmony_ci#define ca_midi_write_cmd(midi, data)	midi->write(midi, data, 1)
2262306a36Sopenharmony_ci#define ca_midi_read_data(midi)		midi->read(midi, 0)
2362306a36Sopenharmony_ci#define ca_midi_read_stat(midi)		midi->read(midi, 1)
2462306a36Sopenharmony_ci#define ca_midi_input_avail(midi)	(!(ca_midi_read_stat(midi) & midi->input_avail))
2562306a36Sopenharmony_ci#define ca_midi_output_ready(midi)	(!(ca_midi_read_stat(midi) & midi->output_ready))
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic void ca_midi_clear_rx(struct snd_ca_midi *midi)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	int timeout = 100000;
3062306a36Sopenharmony_ci	for (; timeout > 0 && ca_midi_input_avail(midi); timeout--)
3162306a36Sopenharmony_ci		ca_midi_read_data(midi);
3262306a36Sopenharmony_ci#ifdef CONFIG_SND_DEBUG
3362306a36Sopenharmony_ci	if (timeout <= 0)
3462306a36Sopenharmony_ci		pr_err("ca_midi_clear_rx: timeout (status = 0x%x)\n",
3562306a36Sopenharmony_ci			   ca_midi_read_stat(midi));
3662306a36Sopenharmony_ci#endif
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic void ca_midi_interrupt(struct snd_ca_midi *midi, unsigned int status)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	unsigned char byte;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (midi->rmidi == NULL) {
4462306a36Sopenharmony_ci		midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable);
4562306a36Sopenharmony_ci		return;
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	spin_lock(&midi->input_lock);
4962306a36Sopenharmony_ci	if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) {
5062306a36Sopenharmony_ci		if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
5162306a36Sopenharmony_ci			ca_midi_clear_rx(midi);
5262306a36Sopenharmony_ci		} else {
5362306a36Sopenharmony_ci			byte = ca_midi_read_data(midi);
5462306a36Sopenharmony_ci			if(midi->substream_input)
5562306a36Sopenharmony_ci				snd_rawmidi_receive(midi->substream_input, &byte, 1);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci		}
5962306a36Sopenharmony_ci	}
6062306a36Sopenharmony_ci	spin_unlock(&midi->input_lock);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	spin_lock(&midi->output_lock);
6362306a36Sopenharmony_ci	if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) {
6462306a36Sopenharmony_ci		if (midi->substream_output &&
6562306a36Sopenharmony_ci		    snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
6662306a36Sopenharmony_ci			ca_midi_write_data(midi, byte);
6762306a36Sopenharmony_ci		} else {
6862306a36Sopenharmony_ci			midi->interrupt_disable(midi,midi->tx_enable);
6962306a36Sopenharmony_ci		}
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci	spin_unlock(&midi->output_lock);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic void ca_midi_cmd(struct snd_ca_midi *midi, unsigned char cmd, int ack)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	unsigned long flags;
7862306a36Sopenharmony_ci	int timeout, ok;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	spin_lock_irqsave(&midi->input_lock, flags);
8162306a36Sopenharmony_ci	ca_midi_write_data(midi, 0x00);
8262306a36Sopenharmony_ci	/* ca_midi_clear_rx(midi); */
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	ca_midi_write_cmd(midi, cmd);
8562306a36Sopenharmony_ci	if (ack) {
8662306a36Sopenharmony_ci		ok = 0;
8762306a36Sopenharmony_ci		timeout = 10000;
8862306a36Sopenharmony_ci		while (!ok && timeout-- > 0) {
8962306a36Sopenharmony_ci			if (ca_midi_input_avail(midi)) {
9062306a36Sopenharmony_ci				if (ca_midi_read_data(midi) == midi->ack)
9162306a36Sopenharmony_ci					ok = 1;
9262306a36Sopenharmony_ci			}
9362306a36Sopenharmony_ci		}
9462306a36Sopenharmony_ci		if (!ok && ca_midi_read_data(midi) == midi->ack)
9562306a36Sopenharmony_ci			ok = 1;
9662306a36Sopenharmony_ci	} else {
9762306a36Sopenharmony_ci		ok = 1;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci	spin_unlock_irqrestore(&midi->input_lock, flags);
10062306a36Sopenharmony_ci	if (!ok)
10162306a36Sopenharmony_ci		pr_err("ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n",
10262306a36Sopenharmony_ci			   cmd,
10362306a36Sopenharmony_ci			   midi->get_dev_id_port(midi->dev_id),
10462306a36Sopenharmony_ci			   ca_midi_read_stat(midi),
10562306a36Sopenharmony_ci			   ca_midi_read_data(midi));
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int ca_midi_input_open(struct snd_rawmidi_substream *substream)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct snd_ca_midi *midi = substream->rmidi->private_data;
11162306a36Sopenharmony_ci	unsigned long flags;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	if (snd_BUG_ON(!midi->dev_id))
11462306a36Sopenharmony_ci		return -ENXIO;
11562306a36Sopenharmony_ci	spin_lock_irqsave(&midi->open_lock, flags);
11662306a36Sopenharmony_ci	midi->midi_mode |= CA_MIDI_MODE_INPUT;
11762306a36Sopenharmony_ci	midi->substream_input = substream;
11862306a36Sopenharmony_ci	if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
11962306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
12062306a36Sopenharmony_ci		ca_midi_cmd(midi, midi->reset, 1);
12162306a36Sopenharmony_ci		ca_midi_cmd(midi, midi->enter_uart, 1);
12262306a36Sopenharmony_ci	} else {
12362306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
12462306a36Sopenharmony_ci	}
12562306a36Sopenharmony_ci	return 0;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic int ca_midi_output_open(struct snd_rawmidi_substream *substream)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct snd_ca_midi *midi = substream->rmidi->private_data;
13162306a36Sopenharmony_ci	unsigned long flags;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	if (snd_BUG_ON(!midi->dev_id))
13462306a36Sopenharmony_ci		return -ENXIO;
13562306a36Sopenharmony_ci	spin_lock_irqsave(&midi->open_lock, flags);
13662306a36Sopenharmony_ci	midi->midi_mode |= CA_MIDI_MODE_OUTPUT;
13762306a36Sopenharmony_ci	midi->substream_output = substream;
13862306a36Sopenharmony_ci	if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
13962306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
14062306a36Sopenharmony_ci		ca_midi_cmd(midi, midi->reset, 1);
14162306a36Sopenharmony_ci		ca_midi_cmd(midi, midi->enter_uart, 1);
14262306a36Sopenharmony_ci	} else {
14362306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic int ca_midi_input_close(struct snd_rawmidi_substream *substream)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct snd_ca_midi *midi = substream->rmidi->private_data;
15162306a36Sopenharmony_ci	unsigned long flags;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	if (snd_BUG_ON(!midi->dev_id))
15462306a36Sopenharmony_ci		return -ENXIO;
15562306a36Sopenharmony_ci	spin_lock_irqsave(&midi->open_lock, flags);
15662306a36Sopenharmony_ci	midi->interrupt_disable(midi,midi->rx_enable);
15762306a36Sopenharmony_ci	midi->midi_mode &= ~CA_MIDI_MODE_INPUT;
15862306a36Sopenharmony_ci	midi->substream_input = NULL;
15962306a36Sopenharmony_ci	if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
16062306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
16162306a36Sopenharmony_ci		ca_midi_cmd(midi, midi->reset, 0);
16262306a36Sopenharmony_ci	} else {
16362306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci	return 0;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int ca_midi_output_close(struct snd_rawmidi_substream *substream)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct snd_ca_midi *midi = substream->rmidi->private_data;
17162306a36Sopenharmony_ci	unsigned long flags;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	if (snd_BUG_ON(!midi->dev_id))
17462306a36Sopenharmony_ci		return -ENXIO;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	spin_lock_irqsave(&midi->open_lock, flags);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	midi->interrupt_disable(midi,midi->tx_enable);
17962306a36Sopenharmony_ci	midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT;
18062306a36Sopenharmony_ci	midi->substream_output = NULL;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
18362306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
18462306a36Sopenharmony_ci		ca_midi_cmd(midi, midi->reset, 0);
18562306a36Sopenharmony_ci	} else {
18662306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->open_lock, flags);
18762306a36Sopenharmony_ci	}
18862306a36Sopenharmony_ci	return 0;
18962306a36Sopenharmony_ci}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic void ca_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	struct snd_ca_midi *midi = substream->rmidi->private_data;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (snd_BUG_ON(!midi->dev_id))
19662306a36Sopenharmony_ci		return;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (up) {
19962306a36Sopenharmony_ci		midi->interrupt_enable(midi,midi->rx_enable);
20062306a36Sopenharmony_ci	} else {
20162306a36Sopenharmony_ci		midi->interrupt_disable(midi, midi->rx_enable);
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic void ca_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	struct snd_ca_midi *midi = substream->rmidi->private_data;
20862306a36Sopenharmony_ci	unsigned long flags;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	if (snd_BUG_ON(!midi->dev_id))
21162306a36Sopenharmony_ci		return;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (up) {
21462306a36Sopenharmony_ci		int max = 4;
21562306a36Sopenharmony_ci		unsigned char byte;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci		spin_lock_irqsave(&midi->output_lock, flags);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci		/* try to send some amount of bytes here before interrupts */
22062306a36Sopenharmony_ci		while (max > 0) {
22162306a36Sopenharmony_ci			if (ca_midi_output_ready(midi)) {
22262306a36Sopenharmony_ci				if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) ||
22362306a36Sopenharmony_ci				    snd_rawmidi_transmit(substream, &byte, 1) != 1) {
22462306a36Sopenharmony_ci					/* no more data */
22562306a36Sopenharmony_ci					spin_unlock_irqrestore(&midi->output_lock, flags);
22662306a36Sopenharmony_ci					return;
22762306a36Sopenharmony_ci				}
22862306a36Sopenharmony_ci				ca_midi_write_data(midi, byte);
22962306a36Sopenharmony_ci				max--;
23062306a36Sopenharmony_ci			} else {
23162306a36Sopenharmony_ci				break;
23262306a36Sopenharmony_ci			}
23362306a36Sopenharmony_ci		}
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci		spin_unlock_irqrestore(&midi->output_lock, flags);
23662306a36Sopenharmony_ci		midi->interrupt_enable(midi,midi->tx_enable);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	} else {
23962306a36Sopenharmony_ci		midi->interrupt_disable(midi,midi->tx_enable);
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic const struct snd_rawmidi_ops ca_midi_output =
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	.open =		ca_midi_output_open,
24662306a36Sopenharmony_ci	.close =	ca_midi_output_close,
24762306a36Sopenharmony_ci	.trigger =	ca_midi_output_trigger,
24862306a36Sopenharmony_ci};
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic const struct snd_rawmidi_ops ca_midi_input =
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	.open =		ca_midi_input_open,
25362306a36Sopenharmony_ci	.close =	ca_midi_input_close,
25462306a36Sopenharmony_ci	.trigger =	ca_midi_input_trigger,
25562306a36Sopenharmony_ci};
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic void ca_midi_free(struct snd_ca_midi *midi)
25862306a36Sopenharmony_ci{
25962306a36Sopenharmony_ci	midi->interrupt = NULL;
26062306a36Sopenharmony_ci	midi->interrupt_enable = NULL;
26162306a36Sopenharmony_ci	midi->interrupt_disable = NULL;
26262306a36Sopenharmony_ci	midi->read = NULL;
26362306a36Sopenharmony_ci	midi->write = NULL;
26462306a36Sopenharmony_ci	midi->get_dev_id_card = NULL;
26562306a36Sopenharmony_ci	midi->get_dev_id_port = NULL;
26662306a36Sopenharmony_ci	midi->rmidi = NULL;
26762306a36Sopenharmony_ci}
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_cistatic void ca_rmidi_free(struct snd_rawmidi *rmidi)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	ca_midi_free(rmidi->private_data);
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ciint ca_midi_init(void *dev_id, struct snd_ca_midi *midi, int device, char *name)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct snd_rawmidi *rmidi;
27762306a36Sopenharmony_ci	int err;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi);
28062306a36Sopenharmony_ci	if (err < 0)
28162306a36Sopenharmony_ci		return err;
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	midi->dev_id = dev_id;
28462306a36Sopenharmony_ci	midi->interrupt = ca_midi_interrupt;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	spin_lock_init(&midi->open_lock);
28762306a36Sopenharmony_ci	spin_lock_init(&midi->input_lock);
28862306a36Sopenharmony_ci	spin_lock_init(&midi->output_lock);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	strcpy(rmidi->name, name);
29162306a36Sopenharmony_ci	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output);
29262306a36Sopenharmony_ci	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input);
29362306a36Sopenharmony_ci	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
29462306a36Sopenharmony_ci	                     SNDRV_RAWMIDI_INFO_INPUT |
29562306a36Sopenharmony_ci	                     SNDRV_RAWMIDI_INFO_DUPLEX;
29662306a36Sopenharmony_ci	rmidi->private_data = midi;
29762306a36Sopenharmony_ci	rmidi->private_free = ca_rmidi_free;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	midi->rmidi = rmidi;
30062306a36Sopenharmony_ci	return 0;
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
303