/*
 *   Sequencer routine for uart16550 (mainly port management)
 *   Copyright (c) by Isaku Yamahata (yamahata@private.email.ne.jp)
 *   16 Jan 1999 first version.
 *
 *   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.
 *
 */

#include "../../include/driver.h"

#ifdef CONFIG_SND_SEQUENCER

#include "../../include/seq_kernel.h"
#include "../../include/midi.h"

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

#define UART16550_PORT_A 0
#define UART16550_PORT_B 1
#define UART16550_PORT_UNKNOWN 255	///???

#define UART16550_PORT_NOT_OPENED (0)
#define UART16550_PORT_A_OPEN_MASK (1 << 0)
#define UART16550_PORT_A_OPENED (1 << 0)
#define UART16550_PORT_B_OPEN_MASK (1 << 1)
#define UART16550_PORT_B_OPENED (1 << 1)

struct snd_seq_uart16550_port;
typedef struct snd_seq_uart16550_port snd_seq_uart16550_port_t;

struct snd_seq_uart16550_client {
	snd_card_t *card;	//uart16550

	snd_rawmidi_t *rmidi;	//uart16550

	int device;		//device number

	int client_index;	//client number

	int current_port;	//current port number UART16550_PORT_A,UART16550_PORT_B

	int port_status;	//remenber status of open/close of PORT_A, PORT_B

	snd_seq_uart16550_port_t *port_a;	//ALSA port associate to port a

	snd_seq_uart16550_port_t *port_b;	//ALSA port associate to port b

};

struct snd_seq_uart16550_port {
	struct snd_seq_uart16550_client *client;
        int seq_port;		//ALSA port number

	int port;		//UART16550_PORT_A or UART16550_PORT_B

	unsigned char running_status;	/* for keeping the last status byte */
};


//////////////////////////////////////////////////////////////////////memory

static snd_seq_uart16550_client_t *snd_seq_uart16550_client_new
 (snd_card_t * card, snd_rawmidi_t * rmidi, int device) {
	snd_seq_uart16550_client_t *uart16550_client;

	if (card == NULL || rmidi == NULL)
		return NULL;

	uart16550_client = snd_kmalloc(sizeof(snd_seq_uart16550_client_t), GFP_KERNEL);
	if (uart16550_client == NULL)
		return NULL;

	uart16550_client->card = card;
	uart16550_client->rmidi = rmidi;
	uart16550_client->device = device;
	uart16550_client->client_index = -1;	//automatic allocation

	uart16550_client->current_port = UART16550_PORT_UNKNOWN;
	uart16550_client->port_status = UART16550_PORT_NOT_OPENED;
	uart16550_client->port_a = NULL;
	uart16550_client->port_b = NULL;

	return uart16550_client;
}

static void snd_seq_uart16550_client_delete
 (snd_seq_uart16550_client_t * uart16550_client) {
	snd_kfree(uart16550_client);
}

static snd_seq_uart16550_port_t *snd_seq_uart16550_port_new
 (snd_seq_uart16550_client_t * uart16550_client, int uart_port) {
	snd_seq_uart16550_port_t *uart16550_port;

	if (uart16550_client == NULL ||
	!(uart_port == UART16550_PORT_A || uart_port == UART16550_PORT_B))
		return NULL;

	uart16550_port = snd_kmalloc(sizeof(snd_seq_uart16550_port_t), GFP_KERNEL);
	if (uart16550_port == NULL)
		return NULL;

	uart16550_port->client = uart16550_client;
	uart16550_port->port = uart_port;
	uart16550_port->running_status = 0;

	return uart16550_port;
}

static void snd_seq_uart16550_port_delete(snd_seq_uart16550_port_t * uart16550_port)
{
	snd_kfree(uart16550_port);
}

///////////////////////////////////////////////////////////////////port input

/* fill standard header data, source port & channel are filled in */
static void snd_seq_midi_setheader(snd_seq_event_t * ev, int port, int channel)
{
	memset(ev, 0, sizeof(snd_seq_event_t));

	ev->flags = SND_SEQ_EVENT_LENGTH_FIXED;

	ev->source.queue = SND_SEQ_ADDRESS_UNKNOWN;
	ev->source.port = port;
	ev->source.channel = channel;

	ev->dest.queue = SND_SEQ_ADDRESS_SUBSCRIBERS;
	ev->dest.channel = channel;

	ev->flags = SND_SEQ_TIME_STAMP_TICK | SND_SEQ_TIME_MODE_REL;
}


//snd_midi_command is stolen from alsa-driver/kernel/generic/seq/seq_midi.c
/*
 * the ALSA low-level midi routines appear to return 'whole' midi events and 
 * have already handled midi running state. I don't know if will remain in 
 * the future. If not, some more elaborate MIDI parser is needed.
 */
static void snd_midi_command(snd_rawmidi_t * rmidi, void *cmd_private_data,
			     unsigned char *command, int count)
{
	//seq_midisynth_t *msynth = (seq_midisynth_t *) cmd_private_data;
	snd_seq_uart16550_client_t *uart_client =
	(snd_seq_uart16550_client_t *) cmd_private_data;
	int channel;
	int port;
	int client;
	snd_seq_event_t ev;

	if (uart_client == NULL) {
		snd_printd("msynth == NULL\n");
		return;
	}
	//port = msynth->seq_port;
	//client = msynth->seq_client;
	port = uart_client->port_a->seq_port;
	client = uart_client->client_index;
	channel = command[0] & 0x0f;

	switch (command[0] & 0xf0) {

	case 0x80:		// note off

		if (count == 3) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_NOTEOFF;
			ev.data.note.note = command[1];
			ev.data.note.velocity = command[2];

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0x90:		// note on

		if (count == 3) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_NOTEON;
			ev.data.note.note = command[1];
			ev.data.note.velocity = command[2];

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0xa0:		// poly key pressure

		if (count == 3) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_KEYPRESS;
			ev.data.control.param = command[1];
			ev.data.control.value = command[2];

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0xb0:		// control change

		if (count == 3) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_CONTROLLER;
			ev.data.control.param = command[1];
			ev.data.control.value = command[2];

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0xc0:		// program change

		if (count == 2) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_PGMCHANGE;
			ev.data.control.value = command[1];

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0xd0:		// channel pressure

		if (count == 2) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_CHANPRESS;
			ev.data.control.value = command[1];

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0xe0:		// pitch bender

		if (count == 3) {
			snd_seq_midi_setheader(&ev, port, channel);

			ev.type = SND_SEQ_EVENT_PITCHBEND;
			ev.data.control.value = (command[1] & 0x7f) + ((command[2] & 0x7f) << 7) - 8192;

			snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			return;
		}
		break;

	case 0xf0:
		switch (command[0]) {
		case 0xf0:	/* sysex */
			snd_seq_midi_setheader(&ev, port, 0);
			ev.flags = (ev.flags & ~SND_SEQ_EVENT_LENGTH_MASK) | SND_SEQ_EVENT_LENGTH_VARIABLE;
			ev.type = SND_SEQ_EVENT_SYSEX;
			ev.data.ext.ptr = snd_seq_ext_malloc(count, 1);
			ev.data.ext.len = count;
			if (ev.data.ext.ptr) {
				memcpy(ev.data.ext.ptr, command, count);
				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
			} else {
				snd_printd("failed to get %d bytes for sysex\n", count);
			}
			return;

		case 0xf1:	/* MTC quarter frame */
			if (count == 2) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_QFRAME;
				ev.data.control.value = command[1];

				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
			break;

		case 0xf2:	/* song position */
			if (count == 3) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_SONGPOS;
				ev.data.control.value = (command[1] & 0x7f) + ((command[2] & 0x7f) << 7);

				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
			break;

		case 0xf3:	/* song select */
			if (count == 2) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_SONGSEL;
				ev.data.control.value = command[1];

				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
			break;

		case 0xf4:	/* undefined */
			return;

		case 0xf5:	/* undefined */
			return;

		case 0xf6:	/* tune request */
			snd_printd("Rx: tune request\n");
			return;

		case 0xf7:	/* end of sysex */
			return;

			// system real-time messages

		case 0xf8:	/* timing clock */
			if (count == 1) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_CLOCK;
				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
		case 0xfa:	/* start */
			if (count == 1) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_START;
				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
		case 0xfb:	// continue

			if (count == 1) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_CONTINUE;
				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
		case 0xfc:	// stop

			if (count == 1) {
				snd_seq_midi_setheader(&ev, port, 0);

				ev.type = SND_SEQ_EVENT_STOP;
				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
		case 0xfd:	/* undefined */
			return;

		case 0xfe:	// active sensing

			if (count == 1) {
				snd_seq_midi_setheader(&ev, port, channel);

				ev.type = SND_SEQ_EVENT_HEARTBEAT;
				snd_seq_kernel_client_enqueue(client, &ev, 1, 0);
				return;
			}
		case 0xff:	// system reset

			snd_printd("Rx: system reset\n");
			return;
		}
	}

#ifdef CONFIG_SND_DEBUG
	/* not a legal MIDI sequence.... */
	snd_printk("rx command '%s': ", rmidi->name);
	while (count-- > 0)
		printk("%02x:", *command++);
	printk("\n");
#endif
}



static int snd_seq_uart16550_subscribe(void *private_data /* = uart16550_port_t */,
				       snd_seq_port_subscribe_t *info)
{
	snd_seq_uart16550_port_t *uart_port =
	(snd_seq_uart16550_port_t *) private_data;
	snd_seq_uart16550_client_t *uart_client = uart_port->client;
	int ret;

	snd_printd("snd_seq_uart16550_subscribe called\n");
	MOD_INC_USE_COUNT;
	if ((info->midi_channels && info->midi_channels != 16) ||
	    info->midi_voices || info->synth_voices) {
	    	MOD_DEC_USE_COUNT;
	    	return -EINVAL;
	}
	/* open midi port */
	ret = snd_midi_open(uart_client->card->number, uart_client->device,
			    SND_RAWMIDI_LFLG_INPUT, &uart_client->rmidi);

	if (ret < 0) {
		snd_printd("midi input open failed!!!\n");
		MOD_DEC_USE_COUNT;
		return ret;
	}
	uart_client->rmidi->input.u.p.command = snd_midi_command;
	uart_client->rmidi->input.u.p.cmd_private_data = uart_client;
	snd_midi_start_input(uart_client->rmidi);

	return 0;
}

static int snd_seq_uart16550_unsubscribe(void *private_data /* = uart16550_port_t */,
					 snd_seq_port_subscribe_t *info)
{
	snd_seq_uart16550_port_t *uart_port =
	(snd_seq_uart16550_port_t *) private_data;
	snd_seq_uart16550_client_t *uart_client = uart_port->client;
	int ret;

	snd_printd("snd_seq_uart16550_unsubscribe called\n");
	if (uart_client->rmidi == NULL)
		return -EINVAL;
	ret = snd_midi_close(uart_client->card->number, uart_client->device,
			     SND_RAWMIDI_LFLG_INPUT);
	MOD_DEC_USE_COUNT;
	return ret;
}

//////////////////////////////////////////////////////////////port output

//this function is stolen from alsa-driver/kernel/seq/seq_midi.c
/* send data to specified midi device */
inline static void dump_midi(snd_rawmidi_t * rmidi, unsigned char *buf, int count)
{
	int done = snd_midi_transmit(rmidi, buf, count);
#if 0
	{
		int i;

		printk("dump: ");
		for (i = 0; i < count; i++)
			printk("%02x:", *buf++);
		printk("\n");
	}
#endif
	if (done != count) {
		snd_printd("dump_midi only wrote %d instead of %d bytes to midi device\n",
			   done, count);
	}
}


inline static void snd_seq_uart16550_port_change
 (snd_seq_uart16550_client_t * uart_client, int uart_port) {
	if (uart_port != UART16550_PORT_A && uart_port != UART16550_PORT_B) {
		snd_printd("snd_seq_uart16550_port_change invalid uart_port\n");
		return;
	}
	if (uart_client->current_port != uart_port) {
		//send 0xF5 pp to change uart port to pp
		unsigned char buffer[2];
		buffer[0] = 0xF5;
		buffer[1] = uart_port + 1;
		dump_midi(uart_client->rmidi, buffer, 2);
		uart_client->current_port = uart_port;

		//reset running status
		uart_client->port_a->running_status = 0;
		uart_client->port_b->running_status = 0;
	}
}


static int snd_seq_uart16550_input_event(snd_seq_event_t * ev,
					 int direct,
					 void *private_data /* = snd_seq_uart16550_port_t */,
					 int atomic, int hop)
{
        snd_seq_uart16550_client_t *uart_client =
	        (((snd_seq_uart16550_port_t *) private_data)->client);

	int channel = ev->dest.channel;
	int client;
	unsigned char msg[10];	/* buffer for constructing midi messages */
	snd_rawmidi_t *rmidi;
	snd_seq_uart16550_port_t *uart_port;

	if (uart_client == NULL) {
		snd_printd("uart_client == NULL\n");
		return -EINVAL;
	}
	client = uart_client->client_index;
	rmidi = uart_client->rmidi;
	if (rmidi == NULL) 
		return -EINVAL;	//no need?

	//////////////////////////////check uart port
	if (ev->dest.port == uart_client->port_a->seq_port) {
		uart_port = uart_client->port_a;
		snd_seq_uart16550_port_change(uart_client, UART16550_PORT_A);

	} else if (ev->dest.port == uart_client->port_b->seq_port) {
		uart_port = uart_client->port_b;
		snd_seq_uart16550_port_change(uart_client, UART16550_PORT_B);
	} else {
		snd_printd("snd_seq_uart16550_input_event invalid dest port number\n");
		snd_printd("ev source: %i.%i.%i.%i, dest = %i.%i.%i.%i\n",
			   ev->source.queue,
			   ev->source.client,
			   ev->source.port,
			   ev->source.channel,
			   ev->dest.queue,
			   ev->dest.client,
			   ev->dest.port,
			   ev->dest.channel);
		return -EINVAL;
	}


#if 0
	if ((signed) (jiffies - msynth->jiffies) > HZ / 50)
		msynth->running_state = 0;
	msynth->jiffies = jiffies;
#endif

#if 0
	printk("ev source: %i.%i.%i.%i, dest = %i.%i.%i.%i\n",
	       ev->source.queue,
	       ev->source.client,
	       ev->source.port,
	       ev->source.channel,
	       ev->dest.queue,
	       ev->dest.client,
	       ev->dest.port,
	       ev->dest.channel);
#endif


	/* decode actual event data... */
	switch (ev->type) {
	case SND_SEQ_EVENT_NOTE:
		break;


	case SND_SEQ_EVENT_NOTEOFF:
		msg[0] = (channel & 0x0f) | 0x80;	/* note off */
		msg[1] = ev->data.note.note & 0x7f;
		msg[2] = ev->data.note.velocity & 0x7f;
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 2);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 3);
		}
		break;

	case SND_SEQ_EVENT_NOTEON:
		msg[0] = (channel & 0x0f) | 0x90;	/* note on */
		msg[1] = ev->data.note.note & 0x7f;
		msg[2] = ev->data.note.velocity & 0x7f;
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 2);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 3);
		}
		break;

	case SND_SEQ_EVENT_KEYPRESS:
		msg[0] = (channel & 0x0f) | 0xa0;	/* polyphonic key pressure */
		msg[1] = ev->data.control.param & 0x7f;
		msg[2] = ev->data.control.value & 0x7f;
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 2);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 3);
		}
		break;

	case SND_SEQ_EVENT_CONTROLLER:
		msg[0] = (channel & 0x0f) | 0xb0;	/* control change */
		msg[1] = ev->data.control.param & 0x7f;
		msg[2] = ev->data.control.value & 0x7f;
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 2);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 3);
		}
		break;

	case SND_SEQ_EVENT_PGMCHANGE:
		msg[0] = (channel & 0x0f) | 0xc0;	/* program change */
		msg[1] = ev->data.control.value & 0x7f;
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 1);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 2);
		}
		break;

	case SND_SEQ_EVENT_CHANPRESS:
		msg[0] = (channel & 0x0f) | 0xd0;	/* channel pressure */
		msg[1] = ev->data.control.value & 0x7f;
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 1);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 2);
		}
		break;

	case SND_SEQ_EVENT_PITCHBEND:
		msg[0] = (channel & 0x0f) | 0xe0;	/* pitch bender */
		msg[1] = (ev->data.control.value + 8192) & 0x7f;	/* lsb */
		msg[2] = ((ev->data.control.value + 8192) >> 7) & 0x7f;		/* msb */
		if (uart_port->running_status == msg[0]) {
			dump_midi(rmidi, msg + 1, 2);
		} else {
			uart_port->running_status = msg[0];
			dump_midi(rmidi, msg, 3);
		}
		break;



	case SND_SEQ_EVENT_SYSEX:{
			unsigned char *sysex = (unsigned char *) ev->data.ext.ptr;

			//printf("Event  = System Exclusive len=%d\n", ev->data.ext.len);
			dump_midi(rmidi, sysex, ev->data.ext.len);
			uart_port->running_status = 0;
		}
		break;


	case SND_SEQ_EVENT_QFRAME:
		msg[0] = 0xf1;	/* MTC quarter frame */
		msg[1] = ev->data.control.value & 0x7f;
		dump_midi(rmidi, msg, 2);
		uart_port->running_status = 0;
		break;

	case SND_SEQ_EVENT_CLOCK:
		msg[0] = 0xf8;
		dump_midi(rmidi, msg, 1);
		uart_port->running_status = 0;
		break;

	case SND_SEQ_EVENT_START:
		msg[0] = 0xfa;
		dump_midi(rmidi, msg, 1);
		uart_port->running_status = 0;
		break;

	case SND_SEQ_EVENT_CONTINUE:
		msg[0] = 0xfb;
		dump_midi(rmidi, msg, 1);
		uart_port->running_status = 0;
		break;

	case SND_SEQ_EVENT_STOP:
		msg[0] = 0xfc;
		dump_midi(rmidi, msg, 1);
		uart_port->running_status = 0;
		break;

	case SND_SEQ_EVENT_HEARTBEAT:
		msg[0] = 0xfe;	/* active sensing */
		dump_midi(rmidi, msg, 1);
		uart_port->running_status = 0;
		break;


		/* messages which we are sending over the MIDI buss */
	case SND_SEQ_EVENT_TEMPO:
	case SND_SEQ_EVENT_TIMESIGN:
	case SND_SEQ_EVENT_KEYSIGN:
		break;


	default:
		snd_printd("Event  = Decoding for type %d is not implemented\n", ev->type);
	}

	return 0;
}



static int snd_seq_uart16550_do_use(snd_seq_uart16550_client_t * uart_client,
				    int port)
{
	const int old_port_status = uart_client->port_status;
	snd_printd("snd_seq_uart16550_do_use port = %d\n", port);
	switch (port) {
	case UART16550_PORT_A:
		if ((uart_client->port_status & UART16550_PORT_A_OPENED) != 0)
			return -EBUSY;
		uart_client->port_status |= UART16550_PORT_A_OPENED;
		break;
	case UART16550_PORT_B:
		if ((uart_client->port_status & UART16550_PORT_B_OPENED) != 0)
			return -EBUSY;
		uart_client->port_status |= UART16550_PORT_B_OPENED;
		break;
	default:
		return -ENODEV;
		break;
	}

	snd_printd("snd_seq_uart16550_do_use processing\n");
	if (old_port_status == UART16550_PORT_NOT_OPENED) {
		MOD_INC_USE_COUNT;
		{
			int err = snd_midi_open(uart_client->card->number,
						uart_client->device,
						SND_RAWMIDI_LFLG_OUTPUT,
						&uart_client->rmidi);
			snd_printd("snd_seq_uart16550_do_use really opened\n");
			if (err < 0) {
				MOD_DEC_USE_COUNT;
				return err;
			}
		}
	}
	return 0;
}

static int snd_seq_uart16550_do_unuse(snd_seq_uart16550_client_t * uart_client,
				      int port)
{
	snd_printd("snd_seq_uart16550_do_unuse port = %d port_status=%d\n",
		   port, uart_client->port_status);
	switch (port) {
	case UART16550_PORT_A:
		if ((uart_client->port_status & UART16550_PORT_A_OPENED) == 0)
			return -EINVAL;
		uart_client->port_status &= ~UART16550_PORT_A_OPENED;
		if (uart_client->port_a != NULL) {
		        uart_client->port_a->running_status = 0;
		}
		break;
	case UART16550_PORT_B:
		if ((uart_client->port_status & UART16550_PORT_B_OPENED) == 0)
			return -EINVAL;
		uart_client->port_status &= ~UART16550_PORT_B_OPENED;
		if (uart_client->port_b != NULL) {
		        uart_client->port_b->running_status = 0;
		}
		break;
	default:
		return -ENODEV;
		break;
	}

	if (uart_client->port_status == UART16550_PORT_NOT_OPENED) {
	        unsigned char buf = 0xfe;
		//sending single active sensing message to shut up
		int err = snd_midi_transmit (uart_client->rmidi, &buf, 1);
		if (err != 1)
		        snd_printd ("snd_seq_uart16550_do_unuse sending active sensing is failed.\n");
	        uart_client->current_port = UART16550_PORT_UNKNOWN;
		err = snd_midi_flush_output(uart_client->rmidi);
		if (err != 0)
		        snd_printd ("snd_seq_uart16550_do_unuse flushing raw midi device failed.\n");
		err = snd_midi_close(uart_client->card->number,
				     uart_client->device,
				     SND_RAWMIDI_LFLG_OUTPUT);
		snd_printd("snd_seq_uart16550_do_unuse really closed\n");
		MOD_DEC_USE_COUNT;
		return err;	//if err < 0, there is nothing to be done.

	}
	return 0;
}


static int snd_seq_uart16550_use(void *private_data /* = uart16550_port_t */,
				 snd_seq_port_subscribe_t *info)
{
	snd_seq_uart16550_port_t *uart16550_port =
	(snd_seq_uart16550_port_t *) private_data;

	if ((info->midi_channels && info->midi_channels != 16) ||
	    info->midi_voices || info->synth_voices)
	    	return -EINVAL;
	return snd_seq_uart16550_do_use(uart16550_port->client,
					uart16550_port->port);
}

static int snd_seq_uart16550_unuse(void *private_data /* = uart16550_port_t */,
				   snd_seq_port_subscribe_t *info)
{
	snd_seq_uart16550_port_t *uart16550_port =
	(snd_seq_uart16550_port_t *) private_data;
	return snd_seq_uart16550_do_unuse(uart16550_port->client,
					  uart16550_port->port);
}

//////////////////////////////////////////////////////////////////////port
static snd_seq_uart16550_port_t *snd_seq_uart16550_create_port
 (snd_seq_uart16550_client_t * client,
  const char *port_name, int serial_port, int capability) {
	snd_seq_uart16550_port_t *uart_port =
	snd_seq_uart16550_port_new(client, serial_port);
	snd_seq_port_callback_t port_callbacks;
	snd_seq_port_info_t port_info;
	int tmp;

	if (uart_port == NULL)
		return NULL;

	memset(&port_info, 0, sizeof(port_info));
	port_info.client = client->client_index;
	sprintf(port_info.name, "%s", port_name);
	port_info.capability = capability;
	port_info.type = SND_SEQ_PORT_TYPE_MIDI_GENERIC;
	port_info.midi_channels = 16;
	memset(&port_callbacks, 0, sizeof(port_callbacks));
	port_callbacks.private_data = uart_port;
	port_callbacks.subscribe = snd_seq_uart16550_subscribe;
	port_callbacks.unsubscribe = snd_seq_uart16550_unsubscribe;
	port_callbacks.use = snd_seq_uart16550_use;
	port_callbacks.unuse = snd_seq_uart16550_unuse;
	port_callbacks.event_input = snd_seq_uart16550_input_event;
	port_info.kernel = &port_callbacks;

	tmp = snd_seq_kernel_client_ctl(client->client_index,
				  SND_SEQ_IOCTL_CREATE_PORT, &port_info);
	if (tmp < 0) {
		snd_seq_uart16550_port_delete(uart_port);
		return NULL;
	}
	uart_port->seq_port = port_info.port;

	return uart_port;
}


static void snd_seq_uart16550_delete_port(snd_seq_uart16550_port_t * uart_port)
{
	snd_seq_port_info_t port_info;

	if (uart_port == NULL)
		return;

	port_info.client = uart_port->client->client_index;
	port_info.port = uart_port->seq_port;
	snd_seq_kernel_client_ctl(port_info.client, SND_SEQ_IOCTL_DELETE_PORT,
				  &port_info);
	snd_seq_uart16550_port_delete(uart_port);
}


int snd_seq_uart16550_register_port(snd_card_t * card, snd_rawmidi_t * rmidi,
				    int device)
{
	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;

	snd_seq_uart16550_client_t *client;
	snd_seq_client_callback_t callbacks;
	snd_seq_client_info_t client_info;

	int capability;
	char port_name[64];

	if (uart == NULL)	//no need?
		return -EINVAL;

	//////////////////////////////////////////////////create kernel client
	client = snd_seq_uart16550_client_new(card, rmidi, device);
	if (client == NULL)
		return -ENOMEM;

	callbacks.private_data = client;
	callbacks.allow_input = 1;
	callbacks.allow_output = 1;
	//0でいいの?
	client->client_index = snd_seq_create_kernel_client(card, 0, &callbacks);
	//client->client_index = snd_seq_create_kernel_client (card, -1, &callbacks);
	if (client->client_index < 0) {
		snd_seq_uart16550_client_delete(client);
		return client->client_index;
	}
	memset(&client_info, 0, sizeof(snd_seq_client_info_t));
	sprintf(client_info.name, "%i: MIDI Synth", card->number);
	snd_seq_kernel_client_ctl(client->client_index,
			    SND_SEQ_IOCTL_SET_CLIENT_INFO, &client_info);

	////////////////////////////////////////////////////////////create port A
	capability =
	      SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SYNC_READ | SND_SEQ_PORT_CAP_SUBS_READ
	    | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SYNC_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE
	    | SND_SEQ_PORT_CAP_DUPLEX;
	sprintf(port_name, "%i: %s %s", card->number, rmidi->name, "port A");
	client->port_a = snd_seq_uart16550_create_port(client, port_name,
						       UART16550_PORT_A,
						       capability);
	if (client->port_a == NULL) {
		snd_seq_delete_kernel_client(client->client_index);
		return -ENOMEM;
	}
	////////////////////////////////////////////////////////////create port B
	capability = SND_SEQ_PORT_CAP_SUBS_WRITE
	    | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SYNC_WRITE;
	sprintf(port_name, "%i: %s %s", card->number, rmidi->name, "port B");
	client->port_b = snd_seq_uart16550_create_port(client, port_name,
						       UART16550_PORT_B,
						       capability);

	//though create port_b failed, port_a is already created, so we go on.
	if (client->port_b == NULL)
		snd_printk("snd_seq_uart16550_register_port creating port B failed.\n");

	uart->seq_client = client;
	return 0;
}

int snd_seq_uart16550_unregister_port(snd_card_t * card, snd_rawmidi_t * rmidi)
{

	snd_uart16550_t *uart = (snd_uart16550_t *) rmidi->private_data;
	snd_seq_uart16550_client_t *client = uart->seq_client;

	////////////////////////////////////////////////////////////delete port_b
	snd_seq_uart16550_delete_port(client->port_b);
	client->port_b = NULL;	//no need

	////////////////////////////////////////////////////////////delete port_a
	snd_seq_uart16550_delete_port(client->port_a);
	client->port_a = NULL;	//no need

	////////////////////////////////////////////////////////////delete client
	snd_seq_delete_kernel_client(client->client_index);
	snd_seq_uart16550_client_delete(uart->seq_client);
	uart->seq_client = NULL;	//no need?

	return 0;
}

#endif /* CONFIG_SND_SEQUENCER */
