Commit 8a5afd29 authored by James Courtier-Dutton's avatar James Courtier-Dutton Committed by Jaroslav Kysela
Browse files

[ALSA] snd-ca0106: Add midi support.



Modules: PCI drivers,CA0106 driver

Author: Tilman Kranz <tilde@tk-sls.de>

Signed-off-by: default avatarJames Courtier-Dutton <James@superbug.co.uk>
parent b0b98119
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -184,6 +184,7 @@ config SND_CA0106
	tristate "SB Audigy LS / Live 24bit"
	depends on SND
	select SND_AC97_CODEC
	select SND_RAWMIDI
	help
	  Say Y here to include support for the Sound Blaster Audigy LS
	  and Live 24bit.
+1 −1
Original line number Diff line number Diff line
snd-ca0106-objs := ca0106_main.o ca0106_proc.o ca0106_mixer.o
snd-ca0106-objs := ca0106_main.o ca0106_proc.o ca0106_mixer.o ca_midi.o

obj-$(CONFIG_SND_CA0106) += snd-ca0106.o
+23 −4
Original line number Diff line number Diff line
@@ -399,10 +399,24 @@
#define PLAYBACK_VOLUME2        0x6a            /* Playback Analog volume per channel. Does not effect AC3 output */
						/* Similar to register 0x66, except that the destination is the I2S mixer instead of the SPDIF mixer. I.E. Outputs to the Analog outputs instead of SPDIF. */
#define UNKNOWN6b               0x6b            /* Unknown. Readonly. Default 00400000 00400000 00400000 00400000 */
#define UART_A_DATA		0x6c            /* Uart, used in setting sample rates, bits per sample etc. */
#define UART_A_CMD		0x6d            /* Uart, used in setting sample rates, bits per sample etc. */
#define UART_B_DATA		0x6e            /* Uart, Unknown. */
#define UART_B_CMD		0x6f            /* Uart, Unknown. */
#define MIDI_UART_A_DATA		0x6c            /* Midi Uart A Data */
#define MIDI_UART_A_CMD		0x6d            /* Midi Uart A Command/Status */
#define MIDI_UART_B_DATA		0x6e            /* Midi Uart B Data (currently unused) */
#define MIDI_UART_B_CMD		0x6f            /* Midi Uart B Command/Status (currently unused) */

/* unique channel identifier for midi->channel */

#define CA0106_MIDI_CHAN_A		0x1
#define CA0106_MIDI_CHAN_B		0x2

/* from mpu401 */

#define CA0106_MIDI_INPUT_AVAIL 	0x80
#define CA0106_MIDI_OUTPUT_READY	0x40
#define CA0106_MPU401_RESET		0xff
#define CA0106_MPU401_ENTER_UART	0x3f
#define CA0106_MPU401_ACK		0xfe

#define SAMPLE_RATE_TRACKER_STATUS 0x70         /* Readonly. Default 00108000 00108000 00500000 00500000 */
						/* Estimated sample rate [19:0] Relative to 48kHz. 0x8000 =  1.0
						 * Rate Locked [20]
@@ -538,6 +552,8 @@
#define CONTROL_CENTER_LFE_CHANNEL 1
#define CONTROL_UNKNOWN_CHANNEL 2

#include "ca_midi.h"

typedef struct snd_ca0106_channel ca0106_channel_t;
typedef struct snd_ca0106 ca0106_t;
typedef struct snd_ca0106_pcm ca0106_pcm_t;
@@ -592,6 +608,9 @@ struct snd_ca0106 {
	int capture_mic_line_in;

	struct snd_dma_buffer buffer;

	ca_midi_t midi;
	ca_midi_t midi2;
};

int __devinit snd_ca0106_mixer(ca0106_t *emu);
+111 −0
Original line number Diff line number Diff line
@@ -338,6 +338,18 @@ static void snd_ca0106_intr_enable(ca0106_t *emu, unsigned int intrenb)
	spin_unlock_irqrestore(&emu->emu_lock, flags);
}

static void snd_ca0106_intr_disable(ca0106_t *emu, unsigned int intrenb)
{
	unsigned long flags;
	unsigned int enable;
  
	spin_lock_irqsave(&emu->emu_lock, flags);
	enable = inl(emu->port + INTE) & ~intrenb;
	outl(enable, emu->port + INTE);
	spin_unlock_irqrestore(&emu->emu_lock, flags);
}


static void snd_ca0106_pcm_free_substream(snd_pcm_runtime_t *runtime)
{
	kfree(runtime->private_data);
@@ -1040,6 +1052,15 @@ static irqreturn_t snd_ca0106_interrupt(int irq, void *dev_id,

        snd_ca0106_ptr_write(chip, EXTENDED_INT, 0, stat76);
	spin_lock(&chip->emu_lock);

	if (chip->midi.dev_id &&
	  (status & (chip->midi.ipr_tx|chip->midi.ipr_rx))) {
		if (chip->midi.interrupt)
			chip->midi.interrupt(&chip->midi, status);
		else
			chip->midi.interrupt_disable(&chip->midi, chip->midi.tx_enable | chip->midi.rx_enable);
	}

	// acknowledge the interrupt if necessary
	outl(status, chip->port+IPR);

@@ -1309,6 +1330,82 @@ static int __devinit snd_ca0106_create(snd_card_t *card,
	return 0;
}


void ca0106_midi_interrupt_enable(ca_midi_t *midi, int intr){
	snd_ca0106_intr_enable((ca0106_t *)(midi->dev_id), intr);
}

void ca0106_midi_interrupt_disable(ca_midi_t *midi, int intr){
	snd_ca0106_intr_disable((ca0106_t *)(midi->dev_id), intr);
}

unsigned char ca0106_midi_read(ca_midi_t *midi, int idx){
	return (unsigned char)snd_ca0106_ptr_read((ca0106_t *)(midi->dev_id), midi->port + idx, 0);
}

void ca0106_midi_write(ca_midi_t *midi, int data, int idx){
	snd_ca0106_ptr_write((ca0106_t *)(midi->dev_id), midi->port + idx, 0, data);
}

snd_card_t *ca0106_dev_id_card(void *dev_id){
	return ((ca0106_t *)dev_id)->card;
}

int ca0106_dev_id_port(void *dev_id){
	return ((ca0106_t *)dev_id)->port;
}

static int __devinit snd_ca0106_midi(ca0106_t *chip, unsigned int channel)
{
	ca_midi_t *midi;
	char *name;
	int err;

        if(channel==CA0106_MIDI_CHAN_B) {
		name = "CA0106 MPU-401 (UART) B";
		midi =  &chip->midi2;
		midi->tx_enable = INTE_MIDI_TX_B;
		midi->rx_enable = INTE_MIDI_RX_B;
		midi->ipr_tx = IPR_MIDI_TX_B;
		midi->ipr_rx = IPR_MIDI_RX_B;
		midi->port = MIDI_UART_B_DATA;
	} else {
		name = "CA0106 MPU-401 (UART)";
		midi =  &chip->midi;
		midi->tx_enable = INTE_MIDI_TX_A;
		midi->rx_enable = INTE_MIDI_TX_B;
		midi->ipr_tx = IPR_MIDI_TX_A;
		midi->ipr_rx = IPR_MIDI_RX_A;
		midi->port = MIDI_UART_A_DATA;
	}

	midi->reset = CA0106_MPU401_RESET;
	midi->enter_uart = CA0106_MPU401_ENTER_UART;
	midi->ack = CA0106_MPU401_ACK;

	midi->input_avail = CA0106_MIDI_INPUT_AVAIL;
	midi->output_ready = CA0106_MIDI_OUTPUT_READY;

	midi->channel = channel;

	midi->interrupt_enable = ca0106_midi_interrupt_enable;
	midi->interrupt_disable = ca0106_midi_interrupt_disable;

	midi->read = ca0106_midi_read;
	midi->write = ca0106_midi_write;

	midi->get_dev_id_card = ca0106_dev_id_card;
	midi->get_dev_id_port = ca0106_dev_id_port;

	midi->dev_id = chip;
	
	if ((err = ca_midi_init(chip, midi, 0, name)) < 0)
		return err;

	return 0;
}


static int __devinit snd_ca0106_probe(struct pci_dev *pci,
					const struct pci_device_id *pci_id)
{
@@ -1360,6 +1457,20 @@ static int __devinit snd_ca0106_probe(struct pci_dev *pci,
		return err;
	}

#ifdef CONFIG_SND_DEBUG_DETECT
	printk("ca0106: probe for MIDI channel A ...");
#endif
	if ((err = snd_ca0106_midi(chip,CA0106_MIDI_CHAN_A)) < 0) {
		snd_card_free(card);
#ifdef CONFIG_SND_DEBUG_DETECT
		printk(" failed, err=0x%x\n",err);
#endif
		return err;
	}
#ifdef CONFIG_SND_DEBUG_DETECT
	printk(" done.\n");
#endif

	snd_ca0106_proc_init(chip);

	if ((err = snd_card_register(card)) < 0) {
+304 −0
Original line number Diff line number Diff line
/* 
 *  Copyright 10/16/2005 Tilman Kranz <tilde@tk-sls.de>
 *  Creative Audio MIDI, for the CA0106 Driver
 *  Version: 0.0.1
 *
 *  Changelog:
 *    Implementation is based on mpu401 and emu10k1x and
 *    tested with ca0106.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 *
 *
 */

#include <linux/spinlock.h>
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/rawmidi.h>

#include "ca_midi.h"

#define ca_midi_write_data(midi, data)	midi->write(midi, data, 0)
#define ca_midi_write_cmd(midi, data)	midi->write(midi, data, 1)
#define ca_midi_read_data(midi)		midi->read(midi, 0)
#define ca_midi_read_stat(midi)		midi->read(midi, 1)
#define ca_midi_input_avail(midi)	(!(ca_midi_read_stat(midi) & midi->input_avail))
#define ca_midi_output_ready(midi)	(!(ca_midi_read_stat(midi) & midi->output_ready))

static void ca_midi_clear_rx(ca_midi_t *midi)
{
	int timeout = 100000;
	for (; timeout > 0 && ca_midi_input_avail(midi); timeout--)
		ca_midi_read_data(midi);
#ifdef CONFIG_SND_DEBUG
	if (timeout <= 0)
		snd_printk(KERN_ERR "ca_midi_clear_rx: timeout (status = 0x%x)\n", ca_midi_read_stat(midi));
#endif
}

void ca_midi_interrupt(ca_midi_t *midi, unsigned int status) {
	unsigned char byte;

	if (midi->rmidi == NULL) {
		midi->interrupt_disable(midi,midi->tx_enable | midi->rx_enable);
		return;
	}

	spin_lock(&midi->input_lock);
	if ((status & midi->ipr_rx) && ca_midi_input_avail(midi)) {
		if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
			ca_midi_clear_rx(midi);
		} else {
			byte = ca_midi_read_data(midi);
			if(midi->substream_input)
				snd_rawmidi_receive(midi->substream_input, &byte, 1);


		}
	}
	spin_unlock(&midi->input_lock);

	spin_lock(&midi->output_lock);
	if ((status & midi->ipr_tx) && ca_midi_output_ready(midi)) {
		if (midi->substream_output &&
		    snd_rawmidi_transmit(midi->substream_output, &byte, 1) == 1) {
			ca_midi_write_data(midi, byte);
		} else {
			midi->interrupt_disable(midi,midi->tx_enable);
		}
	}
	spin_unlock(&midi->output_lock);

}

static void ca_midi_cmd(ca_midi_t *midi, unsigned char cmd, int ack)
{
	unsigned long flags;
	int timeout, ok;

	spin_lock_irqsave(&midi->input_lock, flags);
	ca_midi_write_data(midi, 0x00);
	/* ca_midi_clear_rx(midi); */

	ca_midi_write_cmd(midi, cmd);
	if (ack) {
		ok = 0;
		timeout = 10000;
		while (!ok && timeout-- > 0) {
			if (ca_midi_input_avail(midi)) {
				if (ca_midi_read_data(midi) == midi->ack)
					ok = 1;
			}
		}
		if (!ok && ca_midi_read_data(midi) == midi->ack)
			ok = 1;
	} else {
		ok = 1;
	}
	spin_unlock_irqrestore(&midi->input_lock, flags);
	if (!ok)
		snd_printk(KERN_ERR "ca_midi_cmd: 0x%x failed at 0x%x (status = 0x%x, data = 0x%x)!!!\n",
			   cmd,
			   midi->get_dev_id_port(midi->dev_id),
			   ca_midi_read_stat(midi),
			   ca_midi_read_data(midi));
}

static int ca_midi_input_open(snd_rawmidi_substream_t * substream)
{
	ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
	unsigned long flags;
	
	snd_assert(midi->dev_id, return -ENXIO);
	spin_lock_irqsave(&midi->open_lock, flags);
	midi->midi_mode |= CA_MIDI_MODE_INPUT;
	midi->substream_input = substream;
	if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
		spin_unlock_irqrestore(&midi->open_lock, flags);
		ca_midi_cmd(midi, midi->reset, 1);
		ca_midi_cmd(midi, midi->enter_uart, 1);
	} else {
		spin_unlock_irqrestore(&midi->open_lock, flags);
	}
	return 0;
}

static int ca_midi_output_open(snd_rawmidi_substream_t * substream)
{
	ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
	unsigned long flags;

	snd_assert(midi->dev_id, return -ENXIO);
	spin_lock_irqsave(&midi->open_lock, flags);
	midi->midi_mode |= CA_MIDI_MODE_OUTPUT;
	midi->substream_output = substream;
	if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
		spin_unlock_irqrestore(&midi->open_lock, flags);
		ca_midi_cmd(midi, midi->reset, 1);
		ca_midi_cmd(midi, midi->enter_uart, 1);
	} else {
		spin_unlock_irqrestore(&midi->open_lock, flags);
	}
	return 0;
}

static int ca_midi_input_close(snd_rawmidi_substream_t * substream)
{
	ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
	unsigned long flags;

	snd_assert(midi->dev_id, return -ENXIO);
	spin_lock_irqsave(&midi->open_lock, flags);
	midi->interrupt_disable(midi,midi->rx_enable);
	midi->midi_mode &= ~CA_MIDI_MODE_INPUT;
	midi->substream_input = NULL;
	if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT)) {
		spin_unlock_irqrestore(&midi->open_lock, flags);
		ca_midi_cmd(midi, midi->reset, 0);
	} else {
		spin_unlock_irqrestore(&midi->open_lock, flags);
	}
	return 0;
}

static int ca_midi_output_close(snd_rawmidi_substream_t * substream)
{
	ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
	unsigned long flags;
	snd_assert(midi->dev_id, return -ENXIO);
	
	spin_lock_irqsave(&midi->open_lock, flags);

	midi->interrupt_disable(midi,midi->tx_enable);
	midi->midi_mode &= ~CA_MIDI_MODE_OUTPUT;
	midi->substream_output = NULL;
	
	if (!(midi->midi_mode & CA_MIDI_MODE_INPUT)) {
		spin_unlock_irqrestore(&midi->open_lock, flags);
		ca_midi_cmd(midi, midi->reset, 0);
	} else {
		spin_unlock_irqrestore(&midi->open_lock, flags);
	}
	return 0;
}

static void ca_midi_input_trigger(snd_rawmidi_substream_t * substream, int up)
{
	ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
	snd_assert(midi->dev_id, return);

	if (up) {
		midi->interrupt_enable(midi,midi->rx_enable);
	} else {
		midi->interrupt_disable(midi, midi->rx_enable);
	}
}

static void ca_midi_output_trigger(snd_rawmidi_substream_t * substream, int up)
{
	ca_midi_t *midi = (ca_midi_t *)substream->rmidi->private_data;
	unsigned long flags;

	snd_assert(midi->dev_id, return);

	if (up) {
		int max = 4;
		unsigned char byte;

		spin_lock_irqsave(&midi->output_lock, flags);
	
		/* try to send some amount of bytes here before interrupts */
		while (max > 0) {
			if (ca_midi_output_ready(midi)) {
				if (!(midi->midi_mode & CA_MIDI_MODE_OUTPUT) ||
				    snd_rawmidi_transmit(substream, &byte, 1) != 1) {
					/* no more data */
					spin_unlock_irqrestore(&midi->output_lock, flags);
					return;
				}
				ca_midi_write_data(midi, byte);
				max--;
			} else {
				break;
			}
		}

		spin_unlock_irqrestore(&midi->output_lock, flags);
		midi->interrupt_enable(midi,midi->tx_enable);

	} else {
		midi->interrupt_disable(midi,midi->tx_enable);
	}
}

static snd_rawmidi_ops_t ca_midi_output =
{
	.open =		ca_midi_output_open,
	.close =	ca_midi_output_close,
	.trigger =	ca_midi_output_trigger,
};

static snd_rawmidi_ops_t ca_midi_input =
{
	.open =		ca_midi_input_open,
	.close =	ca_midi_input_close,
	.trigger =	ca_midi_input_trigger,
};

void ca_midi_free(ca_midi_t *midi) {
	midi->interrupt = NULL;
	midi->interrupt_enable = NULL;
	midi->interrupt_disable = NULL;
	midi->read = NULL;
	midi->write = NULL;
	midi->get_dev_id_card = NULL;
	midi->get_dev_id_port = NULL;
	midi->rmidi = NULL;
}

static void ca_rmidi_free(snd_rawmidi_t *rmidi)
{
	ca_midi_free((ca_midi_t *)rmidi->private_data);
}

int __devinit ca_midi_init(void *dev_id, ca_midi_t *midi, int device, char *name)
{
	snd_rawmidi_t *rmidi;
	int err;

	if ((err = snd_rawmidi_new(midi->get_dev_id_card(midi->dev_id), name, device, 1, 1, &rmidi)) < 0)
		return err;

	midi->dev_id = dev_id;
	midi->interrupt = ca_midi_interrupt;

	spin_lock_init(&midi->open_lock);
	spin_lock_init(&midi->input_lock);
	spin_lock_init(&midi->output_lock);

	strcpy(rmidi->name, name);
	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &ca_midi_output);
	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &ca_midi_input);
	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT |
	                     SNDRV_RAWMIDI_INFO_INPUT |
	                     SNDRV_RAWMIDI_INFO_DUPLEX;
	rmidi->private_data = midi;
	rmidi->private_free = ca_rmidi_free;
	
	midi->rmidi = rmidi;
	return 0;
}
Loading