/*
 * uart16550.c
 * 
 * sound/uart16550.c version 2c-s354 for Linux sound driver 3.5.4
 *
 * Copyright by Hannu Savolainen 1993
 *
 * Sun Feb  4 11:31:52 EST 1996 ghansper@daemon.apana.org.au
 *      added 16450/16550 support for standard serial-port UARTs
 *
 * Mon Apr  8 01:52:17 EST 1996 ghansper@daemon.apana.org.au
 *      ported to sound driver version 3.5.1
 *
 * Sun Sep 15 00:29:26 EST 1996 George_Hansper@apana.org.au
 *      ported to sound driver version 3.5.4
 *      added support for setting divisor from configuration
 *
 * Fri Jan 8 1999 yamahata@private.email.ne.jp
 *     ported to Advanced Linux Sound Archtecture
 *     first version 
 *     - added SERIAL* definitions.
 *     - changed name fit to alsa convention. `snd_' prefix.
 *     - modified structure uart16550
 *     - modified following functions for alsa and
 *       eliminated other funcionts.
 *       snd_uart16550_io_loop
 *       snd_uart16550_interrupt
 *       snd_uart16550_detect
 *       snd_uart16550_do_open
 *       snd_uart16550_do_close
 *       snd_uart16550_out
 *       snd_uart16550_output_write
 *     - added following new funcions and structures.
 *       snd_uart16550_t_new
 *       snd_uart16550_t_free
 *       snd_uart16550_do_set_param
 *       snd_uart16550_set_param
 *       snd_uart16550_input_open
 *       snd_uart16550_input_close
 *       snd_uart16550_input_trigger
 *       snd_uart16550_output_open
 *       snd_uart16550_output_close
 *       snd_uart16550_register_resources
 *       snd_uart16550_output
 *       snd_uart16550_input
 *       snd_uart16550_free
 *       snd_uart16550_new_device
 *
 *   Sun Jun 7 1999 yamahata@private.email.ne.jp
 *   fixed a bug in snd_uart16550_output_write.
 *   (When the buffer is full, midi data will be lost)
 *   deleted snd_uart16550_out.
 *   
 *
 *   the serial-midi patch for Open Sound System is got from
 *   http://crystal.apana.org.au/~ghansper/midi/uart16550.3a-s354.tar.gz
 *   Now Hansper released the patch under artistic license.
 *   Yamahata appled clase 7 of artistic license to the patch for OSS and
 *   released serial-midi driver for Advanced Linux Sound Archtecture under
 *   GNU GPL.
 *   Jan 12 1999 Isaku Yamahata.
 *
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *   
 */


#define SND_MAIN_OBJECT_FILE
#include "../../include/driver.h"
#include "../../include/midi.h"

#include <linux/serial_reg.h>

#include "../../include/uart16550.h"
#include "uart16550_type.h"

#define SERIAL_MODE_NOT_OPENED (0)
#define SERIAL_MODE_INPUT_OPEN (1 << 0)
#define SERIAL_MODE_OUTPUT_OPEN (1 << 1)

#define SERIAL_MODE_INPUT_TRIGGERED (1 << 2)
#define SERIAL_MODE_OUTPUT_TRIGGERD (1 << 3)

#define SERIAL_TRUE (1)
#define SERIAL_FALSE (0)

////////////////////////////////////////////////////////////snd_uart16550_t
static snd_uart16550_t *snd_uart16550_t_new(snd_card_t * card,
					    snd_rawmidi_t * rmidi)
{
	snd_uart16550_t *uart;

	//snd_printd ("snd_uart16550_t_new called\n");
	if (card == NULL || rmidi == NULL)
		return NULL;

	uart = snd_kcalloc(sizeof(snd_uart16550_t), GFP_KERNEL);
	if (uart == NULL)
		return NULL;

	uart->card = card;
	uart->rmidi = rmidi;
	uart->seq_client = NULL;
	uart->filemode = 0;
	uart->irq = NULL;
	uart->open_lock = SPIN_LOCK_UNLOCKED;

	return uart;
}

static void snd_uart16550_t_free(snd_uart16550_t * uart)
{
	if (uart == NULL)
		return;

	snd_kfree(uart);
}

///////////////////////////////////////////////////////////interrupt/polling
//snd_uart16550_io_loop is from uart16550_io_loop
//snd_uart16550_interrupt is from uart16550_interrupt


/* This loop should be called with interrupts disabled
 * We don't want to interrupt this, 
 * as we're already handling an interupt 
 */
static void snd_uart16550_io_loop(snd_uart16550_t * uart)
{
	unsigned char c, status;
	int buff_out, fifo_count;

	/* Read Loop */
	while ((status = inb(uart->base + UART_LSR)) & UART_LSR_DR) {
		/* while receive data ready */
		c = inb(uart->base + UART_RX);
		if (uart->filemode & SERIAL_MODE_INPUT_OPEN) {
			uart->rmidi->input.data(uart->rmidi, &c, 1);
		}
		if (status & UART_LSR_OE)
			printk("%s: Overrun on device at %x\n",
			       uart->rmidi->name, uart->base);
	}

	//no need of check SERIAL_MODE_OUTPUT_OPEN because if not,
	//buffer is never filled.
	fifo_count = uart->fifo_count;
	/* Check write status */
	if (status & UART_LSR_THRE) {
		fifo_count = 0;
	}
	buff_out = uart->buff_out;
	/* Write loop */
	while (fifo_count < uart->fifo_limit	/* Can we write ? */
	       && uart->buff_in != buff_out) {	/* and do we want to? */
		fifo_count++;
		outb(uart->tx_buff[buff_out], uart->base + UART_TX);
		buff_out++;
		buff_out &= TX_BUFF_MASK;
	}
	uart->buff_out = buff_out;
	uart->fifo_count = fifo_count;

}

/* NOTES ON SERVICING INTERUPTS
 * ---------------------------
 * After receiving a interupt, it is important to indicate to the UART that this 
 * has been done. 
 * For a Rx interupt, this is done by reading the received byte.
 * For a Tx interupt this is done by either:
 * a) Writing a byte
 * b) Reading the IIR
 * It is particularly important to read the IIR if a Tx interupt is received
 * when there is no data in tx_buff[], as in this case there no other indication that the
 * interupt has been serviced, and it remains outstanding indefinitely.
 * This has the curious side effect that and no further interupts will be generated from this
 * device AT ALL!!.
 * It is also desirable to clear outstanding interupts when the device is opened/closed.
 *
 *
 * Note that some devices need OUT2 to be set before they will generate
 * interrupts at all. (Possibly tied to an internal pull-up on CTS?)
 */
static void snd_uart16550_interrupt(int irq, void *dev_id,
				    struct pt_regs *regs)
{
	snd_uart16550_t *uart;
	unsigned char c;
	unsigned long flags;

	uart = (snd_uart16550_t *) dev_id;
	spin_lock_irqsave(&uart->open_lock, flags);
	if (uart->filemode == SERIAL_MODE_NOT_OPENED) {
		spin_unlock_irqrestore(&uart->open_lock, flags);
		return;
	}
	c = inb(uart->base + UART_IIR);		/* indicate to the UART that the interupt has been serviced */
	snd_uart16550_io_loop(uart);
	spin_unlock_irqrestore(&uart->open_lock, flags);
}


////////////////////////////////////////////////////////////////
static int snd_uart16550_do_set_param(snd_uart16550_t * uart,
				      unsigned short irq_number,
				      unsigned short iobase,
				      unsigned char divisor, int polled)
{
	unsigned long flags;

	//snd_printd ("snd_uart16550_set_param called\n");
	if (uart == NULL)
		return -EINVAL;

	spin_lock_irqsave(&uart->open_lock, flags);
	if (uart->filemode != SERIAL_MODE_NOT_OPENED) {
		spin_unlock_irqrestore(&uart->open_lock, flags);
		return -EBUSY;
	}
	uart->irq_number = irq_number;
	uart->base = iobase;
	uart->divisor = divisor;
	uart->polled = polled;

	spin_unlock_irqrestore(&uart->open_lock, flags);

	return 0;
}


int snd_uart16550_set_param(snd_rawmidi_t * rmidi,
			unsigned short irq_number, unsigned short iobase,
			    unsigned char divisor, int polled)
{
	snd_uart16550_t *uart;

	uart = (snd_uart16550_t *) rmidi->private_data;
	return snd_uart16550_do_set_param(uart,
				    irq_number, iobase, divisor, polled);
}



//this method is from probe_uart16550
//return 1 if found
//return 0 if not found
int snd_uart16550_detect(unsigned short io_base)
{
	int ok;
	unsigned char c;

	/* Do some vague tests for the presence of the uart */

	if (io_base == 0)
		return 0;	/* Not configured */

	ok = 1;			/* uart detected unless one of the following tests should fail */
	outb(UART_LCR_WLEN8	/* 8 data-bits, 1 stop-bit, parity off, DLAB = 0 */
	     ,io_base + UART_LCR);	/* Line Control Register */
	c = inb(io_base + UART_IER);
	/* The top four bits of the IER should always == 0 */
	if ((c & 0xf0) != 0)
		ok = 0;		/* failed */

	outb(0xaa, io_base + UART_SCR);
	/* Write arbitrary data into the scratch reg */
	c = inb(io_base + UART_SCR);
	/* If it comes back, it's OK */
	if (c != 0xaa)
		ok = 0;		/* failed */

	outb(0x55, io_base + UART_SCR);
	/* Write arbitrary data into the scratch reg */
	c = inb(io_base + UART_SCR);
	/* If it comes back, it's OK */
	if (c != 0x55)
		ok = 0;		/* failed */

#ifdef DEBUG_UART16550
	if (ok)
		printk("found.\n");
	else
		printk("not found.\n");
#endif
	return ok;
}




/* This loop should be called with interrupts disabled
 * We don't want to interrupt this, 
 * as we're already handling an interupt 
 */
static int snd_uart16550_do_open(snd_uart16550_t * uart)
{
	char byte;

	//snd_printd ("snd_uart16550_do_open called\n");
	if (uart == NULL)
		return -EINVAL;

	if (uart->filemode != SERIAL_MODE_NOT_OPENED)	//no need?

		return -EBUSY;

	//snd_printd ("snd_uart16550_register_resources now setting up serial port\n");

	/* Initialize basic variables */
	uart->buff_in = 0;
	uart->buff_out = 0;
	uart->fifo_limit = 1;
	uart->fifo_count = 0;


	outb(UART_FCR_ENABLE_FIFO	/* Enable FIFO's (if available) */
	     | UART_FCR_CLEAR_RCVR	/* Clear receiver FIFO */
	     | UART_FCR_CLEAR_XMIT	/* Clear transmitter FIFO */
	     | UART_FCR_TRIGGER_4	/* Set FIFO trigger at 4-bytes */
	/* NOTE: interupt generated after T=(time)4-bytes
	 * if less than UART_FCR_TRIGGER bytes received
	 */
	     ,uart->base + UART_FCR);	/* FIFO Control Register */

	if ((inb(uart->base + UART_IIR) & 0xf0) == 0xc0) {
		uart->fifo_limit = 16;
	}
	if (uart->divisor != 0) {
		uart->old_line_ctrl_reg = inb(uart->base + UART_LCR);
		outb(UART_LCR_DLAB	/* Divisor latch access bit */
		     ,uart->base + UART_LCR);	/* Line Control Register */
		uart->old_divisor_lsb = inb(uart->base + UART_DLL);
		uart->old_divisor_msb = inb(uart->base + UART_DLM);

		outb(uart->divisor
		     ,uart->base + UART_DLL);	/* Divisor Latch Low */
		outb(0
		     ,uart->base + UART_DLM);	/* Divisor Latch High */
		/* DLAB is reset to 0 in next outb() */
	}
	/* Set serial parameters (parity off, etc) */
	outb(UART_LCR_WLEN8	/* 8 data-bits */
	     | 0		/* 1 stop-bit */
	     | 0		/* parity off */
	     | 0		/* DLAB = 0 */
	     ,uart->base + UART_LCR);	/* Line Control Register */

	outb(UART_MCR_RTS	/* Set Request-To-Send line active */
	     | UART_MCR_DTR	/* Set Data-Terminal-Ready line active */
	     | UART_MCR_OUT2	/* Set OUT2 - not always required, but when
				 * it is, it is ESSENTIAL for enabling interrupts
				 */
	     ,uart->base + UART_MCR);	/* Modem Control Register */

	if (uart->polled != SERIAL_FALSE) {
		byte = (0 & UART_IER_RDI)	/* Disable Receiver data interupt */
		    |(0 & UART_IER_THRI)	/* Disable Transmitter holding register empty interupt */
		    ;
	} else {
		byte = UART_IER_RDI	/* Enable Receiver data interupt */
		    | UART_IER_THRI	/* Enable Transmitter holding register empty interupt */
		    ;
	}
	outb(byte, uart->base + UART_IER);	/* Interupt enable Register */

	inb(uart->base + UART_LSR);	/* Clear any pre-existing overrun indication */
	inb(uart->base + UART_IIR);	/* Clear any pre-existing transmit interrupt */
	inb(uart->base + UART_RX);	/* Clear any pre-existing receive interrupt */

	//snd_printd ("snd_uart16550_register_resources done\n");
	return 0;
}


/* This loop should be called with interrupts disabled
 * We don't want to interrupt this, 
 * as we're already handling an interupt 
 */
static int snd_uart16550_do_close(snd_uart16550_t * uart)
{
#ifdef DEBUG_UART16550
	//printk("%s: closing device %d\n",devp->my_name,dev);
#endif

	/* NOTE: may need to disable interrupts before de-registering out handler.
	 * For now, the consequences are harmless.
	 */
	/* save_flags (flags); cli (); */

	outb((0 & UART_IER_RDI)	/* Disable Receiver data interupt */
	     |(0 & UART_IER_THRI)	/* Disable Transmitter holding register empty interupt */
	     ,uart->base + UART_IER);	/* Interupt enable Register */

	outb((0 & UART_MCR_RTS)	/* Deactivate Request-To-Send line  */
	     |(0 & UART_MCR_DTR)	/* Deactivate Data-Terminal-Ready line */
	     ,uart->base + UART_MCR);	/* Modem Control Register */

	inb(uart->base + UART_IIR);	/* Clear any outstanding interupts */

	/* Restore old divisor */
	if (uart->divisor != 0) {
		outb(UART_LCR_DLAB	/* Divisor latch access bit */
		     ,uart->base + UART_LCR);	/* Line Control Register */
		outb(uart->old_divisor_lsb
		     ,uart->base + UART_DLL);	/* Divisor Latch Low */
		outb(uart->old_divisor_msb
		     ,uart->base + UART_DLM);	/* Divisor Latch High */
		/* Restore old LCR (data bits, stop bits, parity, DLAB) */
		outb(uart->old_line_ctrl_reg
		     ,uart->base + UART_LCR);	/* Line Control Register */
	}
	if (uart->filemode != 0) {
		snd_printd("snd_uart16550_unregister_resources bug!!!!");
	}
	return 0;
}

/////////////////////////////////////////////////////////////////////input
static int snd_uart16550_input_open(snd_rawmidi_t * rmidi)
{
	unsigned long flags;
	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;
	spin_lock_irqsave(&uart->open_lock, flags);
	if (uart->filemode == SERIAL_MODE_NOT_OPENED) {
		int ret;
		ret = snd_uart16550_do_open(uart);
		if (ret < 0) {
			spin_unlock_irqrestore(&uart->open_lock, flags);
			return ret;
		}
	}
	uart->filemode |= SERIAL_MODE_INPUT_OPEN;
	//snd_printd ("input_open uart->filemode=%d\n", uart->filemode);
	spin_unlock_irqrestore(&uart->open_lock, flags);

	return 0;
}

static int snd_uart16550_input_close(snd_rawmidi_t * rmidi)
{
	unsigned long flags;
	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;
	spin_lock_irqsave(&uart->open_lock, flags);
	uart->filemode &= ~SERIAL_MODE_INPUT_OPEN;
	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
		snd_uart16550_do_close(uart);
	spin_unlock_irqrestore(&uart->open_lock, flags);
	return 0;
}

static void snd_uart16550_input_trigger(snd_rawmidi_t * rmidi, int up)
{
	unsigned long flags;
	snd_uart16550_t *uart;

	snd_printd("snd_uart16550_input_trigger called\n");
	uart = (snd_uart16550_t *) rmidi->private_data;

	spin_lock_irqsave(&uart->open_lock, flags);
	if (up) {
		uart->filemode |= SERIAL_MODE_INPUT_TRIGGERED;
	} else {
		uart->filemode &= ~SERIAL_MODE_INPUT_TRIGGERED;
	}
	spin_unlock_irqrestore(&uart->open_lock, flags);
}


///////////////////////////////////////////////////////////////////output
static int snd_uart16550_output_open(snd_rawmidi_t * rmidi)
{
	unsigned long flags;
	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;

	//snd_printd ("snd_uart16550_output_open called\n");

	spin_lock_irqsave(&uart->open_lock, flags);
	if (uart->filemode == SERIAL_MODE_NOT_OPENED) {
		int ret;
		ret = snd_uart16550_do_open(uart);
		if (ret < 0) {
			spin_unlock_irqrestore(&uart->open_lock, flags);
			return ret;
		}
	}
	uart->filemode |= SERIAL_MODE_OUTPUT_OPEN;
	//snd_printd ("write_open uart->filemode=%d\n", uart->filemode);
	spin_unlock_irqrestore(&uart->open_lock, flags);
	return 0;
};

static int snd_uart16550_output_close(snd_rawmidi_t * rmidi)
{
	unsigned long flags;
	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;

	//snd_printd ("snd_uart16550_output_close called\n");
	spin_lock_irqsave(&uart->open_lock, flags);
	uart->filemode &= ~SERIAL_MODE_OUTPUT_OPEN;
	if (uart->filemode == SERIAL_MODE_NOT_OPENED)
		snd_uart16550_do_close(uart);
	spin_unlock_irqrestore(&uart->open_lock, flags);
	return 0;
};


//uart = rmidi->private;
//handling is differ by uart->polled.
static void snd_uart16550_output_write(snd_rawmidi_t * rmidi)
{
	unsigned long flags;
	unsigned char midi_byte;
	unsigned char status;
	short int buff_in;
	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;
	
	//snd_printd ("snd_uart16550_output_write called\n");

	/* Interupts are disabled during the updating of the tx_buff,
	 * since it is 'bad' to have two processes updating the same
	 * variables (ie buff_in & buff_out)
	 */

	spin_lock_irqsave(&uart->open_lock, flags);
	if (uart->polled) {	//polling
		snd_uart16550_io_loop(uart);
	}

	while (1) {
	         /* buffer is full is indicated by (buff_in+1 == buff_out) */
	        if (((uart->buff_in+1)&TX_BUFF_MASK) == uart->buff_out)
		  break;
		if (rmidi->output.data(rmidi, &midi_byte, 1) != 1)
		  break;

		if (uart->buff_in == uart->buff_out) {	/* Buffer empty? */
		        /* Tx Buffer Empty - try to write immediately */
		        status = inb(uart->base + UART_LSR);
			if ((status & UART_LSR_THRE) != 0) {
			        /* Transmitter holding register (and Tx FIFO) empty */
			        uart->fifo_count = 1;
				outb(midi_byte, uart->base + UART_TX);
			} else {
			        if (uart->fifo_count < uart->fifo_limit) {
				        uart->fifo_count++;
					outb(midi_byte, uart->base + UART_TX);
				} else {
				        /* Cannot write (buffer empty) - put char in buffer */
				        buff_in = uart->buff_in;
					uart->tx_buff[buff_in] = midi_byte;
					buff_in++;
					buff_in &= TX_BUFF_MASK;
					uart->buff_in = buff_in;
				}
			}
		} else {
		        buff_in = uart->buff_in;
			uart->tx_buff[buff_in] = midi_byte;
			buff_in++;
			buff_in &= TX_BUFF_MASK;
			uart->buff_in = buff_in;
			if (uart->polled)
			        snd_uart16550_io_loop(uart);	/* Feeble attempt to improve performance - but it helps */
		}
	}
	spin_unlock_irqrestore(&uart->open_lock, flags);
	//snd_printd ("snd_uart16550_output_write done\n");
}


////////////////////////////////////////////////////////////////
/* This loop should be called with interrupts disabled
 * We don't want to interrupt this, 
 * as we're already handling an interupt 
 */
static int snd_uart16550_register_resources(snd_uart16550_t * uart)
{
	unsigned long flags;
	snd_port_t *rioport;

	snd_printd("snd_uart16550_register_resources called\n");
	if (uart == NULL) {
		//snd_printd ("snd_uart16550_register_resources uart == NULL bug?\n");
		return -EINVAL;
	}
	spin_lock_irqsave(&uart->open_lock, flags);
	if (snd_register_ioport(uart->card, uart->base, 8,
			      "serial MIDI(uart16550A)", &rioport) < 0) {
		snd_printd("snd_uart16550_register_resources snd_register_ioport failed, port\n");
		spin_unlock_irqrestore(&uart->open_lock, flags);
		return -EBUSY;
	}
	if (!uart->polled) {
		int possible_numbers[2];
		possible_numbers[0] = uart->irq_number;
		possible_numbers[1] = -1;
		snd_printd("snd_uart16550_register_resources requesting irq=%d\n",
			   uart->irq_number);
		if (snd_register_interrupt(uart->card, "serial MIDI(uart16550A)",
					   uart->irq_number,
		//SND_IRQ_TYPE_PCI,
					   SND_IRQ_TYPE_ISA,
					   snd_uart16550_interrupt,
					   (void *) uart,
					   possible_numbers,
					   &uart->irq) < 0) {
			uart->polled = SERIAL_TRUE;
			printk("%s: irq %d busy. Using Polling.\n",
			       uart->rmidi->name, uart->irq_number);
		}
	}
	spin_unlock_irqrestore(&uart->open_lock, flags);
	return 0;
}


static struct snd_stru_rawmidi_direction_hw snd_uart16550_output =
{
	SND_RAWMIDI_HW_POLL,	/* flags */
	NULL,			/* private_data */
	NULL,			/* private_free */
	snd_uart16550_output_open,	/* open */
	snd_uart16550_output_close,	/* close */
	NULL,			/* trigger */
	{snd_uart16550_output_write},	/* io.write */
	NULL,			/* abort */

};

static struct snd_stru_rawmidi_direction_hw snd_uart16550_input =
{
	0,			/* flags */
	NULL,			/* private_data */
	NULL,			/* private_free */
	snd_uart16550_input_open,	/* open */
	snd_uart16550_input_close,	/* close */
	snd_uart16550_input_trigger,	/* trigger */
	{NULL},			/* io.read */
	NULL,			/* abort */

};


static void snd_uart16550_free(void *private_data)
{
	snd_uart16550_t_free((snd_uart16550_t *) private_data);
};

snd_rawmidi_t *snd_uart16550_new_device(snd_card_t * card,
					unsigned short irq_number,
					unsigned short iobase,
					unsigned char divisor,
					int polled)
{
	snd_rawmidi_t *rmidi;
	snd_uart16550_t *uart;

	//snd_printd ("snd_uart16550_new_device called\n");

	//XXX should check device type
	rmidi = snd_rawmidi_new_device(card, "UART Serial MIDI");
	if (rmidi == NULL)
		return NULL;

	uart = snd_uart16550_t_new(card, rmidi);
	if (uart == NULL) {
		snd_rawmidi_free(rmidi);
		return NULL;
	}
	if (snd_uart16550_do_set_param(uart,
			      irq_number, iobase, divisor, polled) < 0) {	//error!!!

		snd_uart16550_t_free(uart);
		snd_rawmidi_free(rmidi);
		return NULL;
	}
	if (snd_uart16550_register_resources(uart) < 0) {
		snd_uart16550_t_free(uart);
		snd_rawmidi_free(rmidi);
		return NULL;
	}
	memcpy(&rmidi->input.hw, &snd_uart16550_input,
	       sizeof(snd_uart16550_input));
	memcpy(&rmidi->output.hw, &snd_uart16550_output,
	       sizeof(snd_uart16550_output));
	strcpy(rmidi->name, "uart16550 MIDI");
	rmidi->info_flags |= SND_RAWMIDI_INFO_OUTPUT |
	    SND_RAWMIDI_INFO_INPUT |
	    SND_RAWMIDI_INFO_DUPLEX;
	rmidi->private_data = uart;
	rmidi->private_free = snd_uart16550_free;
	return rmidi;
}



////////////////////////////////////////////////////////////////////module

EXPORT_SYMBOL(snd_uart16550_new_device);
EXPORT_SYMBOL(snd_uart16550_set_param);
EXPORT_SYMBOL(snd_uart16550_detect);
#ifdef CONFIG_SND_SEQUENCER
EXPORT_SYMBOL(snd_seq_uart16550_register_port);
EXPORT_SYMBOL(snd_seq_uart16550_unregister_port);
#endif

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
