/*
 *   serial.c
 *   Copyright (c)  by Isaku Yamahata <yamahata@private.email.ne.jp>
 *   Fri Jan 8 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.
 *
 */


#define SND_MAIN_OBJECT_FILE

#include "../include/driver.h"
#include "../include/initval.h"
#include "../include/midi.h"

#include <linux/serial_reg.h>

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


int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 1-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for serial MIDI.");
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SND_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for serial MIDI.");

//card manages snd_rawmidi_t more than one.
struct snd_uart_list {
	snd_rawmidi_t *rmidi;
	struct snd_uart_list *next;
};

struct snd_serial {
	snd_card_t *card;
	struct snd_uart_list *uart_list;
};


static struct snd_serial *snd_serial_cards[SND_CARDS] = SND_DEFAULT_PTR;


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

struct snd_serial *snd_serial_new(void)
{
	struct snd_serial *serial_card =
	(struct snd_serial *) snd_kcalloc(sizeof(struct snd_serial), GFP_KERNEL);
	if (serial_card == NULL)
		return NULL;

	serial_card->card = NULL;
	serial_card->uart_list = NULL;
	return serial_card;
}


void snd_serial_free(struct snd_serial *serial_card)
{
	snd_kfree(serial_card);
}

struct snd_uart_list *snd_uart_list_new(snd_rawmidi_t * rmidi)
{
	struct snd_uart_list *uart_list;

	//snd_printd ("snd_uart_list_new called\n");
	if (rmidi == NULL)
		return NULL;

	uart_list = snd_kcalloc(sizeof(struct snd_uart_list), GFP_KERNEL);
	if (uart_list == NULL)
		return NULL;

	uart_list->rmidi = rmidi;
	uart_list->next = NULL;
	return uart_list;
}

void snd_uart_list_free(struct snd_uart_list *uart_list)
{
	snd_kfree(uart_list);
}



////////////////////////////////////////////////////////////
static void snd_serial_use_inc(snd_card_t * card)
{
	MOD_INC_USE_COUNT;
}

static void snd_serial_use_dec(snd_card_t * card)
{
	MOD_DEC_USE_COUNT;
}

struct resource_value {
	unsigned short irq_number;
	unsigned short iobase;
	unsigned char divisor;
	int polled;
};

//#define SERIAL_PORTS 4
#define SERIAL_PORTS 2
static const struct resource_value default_value[SERIAL_PORTS] =
{
	{
		4, 0x3f8, 3, 0
	},
	{
		3, 0x2f8, 3, 0
      //3, 0x2f8, 3, 1
	},
#if (SERIAL_PORTS == 4)
	{
		4, 0x3e8, 3, 0
	},
	{
		3, 0x2e8, 3, 0
	},
#endif
};

static int snd_serial_probe(int dev, struct snd_serial *serial_card)
{
	char longname_buffer[80];
	int port;
	int detected_ports = 0;
	snd_card_t *card = snd_card_new(snd_index[dev], snd_id[dev],
				 snd_serial_use_inc, snd_serial_use_dec);

	//snd_printd ("snd_serial_probe snd_card_new'ed\n");
	if (card == NULL)
		return -ENOMEM;
	card->type = SND_CARD_TYPE_SERIAL;
	strcpy(card->abbreviation, "serial");
	strcpy(card->shortname, "serial midi (uart16550A)");
	memset(longname_buffer, 0, sizeof(longname_buffer));
	sprintf(longname_buffer, "serial midi (uart16550A) at");

	//snd_printd ("snd_serial_probe port probing\n");
	for (port = 0; port < SERIAL_PORTS; port++) {
		snd_rawmidi_t *rmidi;
		struct snd_uart_list *uart_list;
		snd_printd("snd_serial_probe detecting\n");
		if (snd_uart16550_detect(default_value[port].iobase) < 0)
			continue;

		snd_printd("snd_serial_probe new_device'ing\n");
		rmidi = snd_uart16550_new_device(card,
					  default_value[port].irq_number,
					      default_value[port].iobase,
					     default_value[port].divisor,
					     default_value[port].polled);
		snd_printd("snd_serial_probe new_device'ed\n");
		if (rmidi == NULL) {
			snd_printd("snd_serial_probe rmidi == NULL\n");
			continue;
		}
#if 1
		if (__snd_rawmidi_register(rmidi, detected_ports) < 0) {
			snd_printk("__snd_rawmidi_register failed\n");
			snd_rawmidi_free(rmidi);
			continue;
		}
#ifdef CONFIG_SND_SEQUENCER
		if (snd_seq_uart16550_register_port(card, rmidi, detected_ports) < 0) {
			snd_printk("snd_seq_uart16550_register_port failed\n");
		}
#endif
#else
		if (snd_rawmidi_register(rmidi, detected_ports) < 0) {
			snd_printk("__snd_rawmidi_register failed\n");
			snd_rawmidi_free(rmidi);
			continue;
		}
#endif

		uart_list = snd_uart_list_new(rmidi);
		if (uart_list == NULL) {
			snd_rawmidi_free(rmidi);
			continue;
		}
		//find last uart_list
		//snd_printd ("fine last uart_list");
		if (serial_card->uart_list == NULL) {
			serial_card->uart_list = uart_list;
		} else {
			struct snd_uart_list *tmp;
			for (tmp = serial_card->uart_list;
			     tmp->next != NULL;
			     tmp = tmp->next)
				/*do nothing */ ;
			//append this uart_list
			tmp->next = uart_list;
		}

		sprintf(card->longname, "%s irq %d, iobase 0x%x, divisor %d, polled %d",
			longname_buffer,
			default_value[port].irq_number,
			default_value[port].iobase,
			default_value[port].divisor,
			default_value[port].polled);
		detected_ports++;
		sprintf(longname_buffer, "%s,", card->longname);
	}

	if (detected_ports == 0) {	//no usable ports found

		snd_printd("snd_serial_probe no usable port found\n");
		snd_card_free(card);
		return -ENODEV;
	}
	//snd_printd ("snd_serial_probe snd_card_register'ing\n");
	snd_card_register(card);
	serial_card->card = card;
	//snd_printd ("snd_serial_probe snd_card_register'ed\n");
	return 0;
}



int init_module(void)
{
	int dev = 0;
	int cards = 0;
	struct snd_serial *serial_card;


#if 0
	for (dev = 0; dev < SND_CARDS; dev++) {
#endif
		//snd_printd ("init_module serial.o\n");
		serial_card = snd_serial_new();
		if (serial_card == NULL) {
#if 0
			continue;
#else
			return -ENOMEM;
#endif
		}
		//snd_printd ("init_module serial.o probing\n");
		if (snd_serial_probe(dev, serial_card) < 0) {	//failed

			snd_serial_free(serial_card);
#if 0
			continue;
#else
			return -ENODEV;
#endif
		} else {
			//snd_printd ("init_module registering card=%d\n", dev);
			snd_serial_cards[dev] = serial_card;
			cards++;
		}
#if 0
	}
#endif

	if (cards == 0) {
		//???
		snd_printk("serial midi soundcard #%i not found or device busy\n",
			   dev + 1);
		return -ENODEV;
	}
	//snd_printd ("init_module serial.o done.\n");
	return 0;
}


void cleanup_module(void)
{
	int idx = 0;

	//snd_printd ("cleanup_module serial.o called.\n");
	for (idx = 0; idx < SND_CARDS; idx++) {
		struct snd_serial *serial_card = snd_serial_cards[idx];
		if (serial_card != NULL) {
			struct snd_uart_list *uart_list;
			//snd_printd ("cleanup_module serial.o card unregistering\n");
			snd_card_unregister(serial_card->card);

			//free uart16550
			uart_list = serial_card->uart_list;
			while (uart_list != NULL) {
				struct snd_uart_list *tmp = uart_list;
#if 1
				__snd_rawmidi_unregister(uart_list->rmidi);
#ifdef CONFIG_SND_SEQUENCER
				snd_seq_uart16550_unregister_port(serial_card->card,
						       uart_list->rmidi);
#endif
#else
				//snd_printd ("cleanup_module serial.o rmidi unregistering\n");
				//snd_rawmidi_unregister (uart_list->rmidi);
#endif
				uart_list = uart_list->next;
				snd_uart_list_free(tmp);
			}

			//snd_printd ("serial.o cleanup_module snd_card_free dev=%d\n", idx);
			snd_card_free(serial_card->card);
			snd_serial_free(serial_card);
		}
	}
}
