/*
 *   serial.c
 *   Copyright (c)  by Isaku Yamahata <yamahata@private.email.ne.jp>
 *   Fri Jan 8 1999 first version
 *
 * Thr Jan 27 2000 exarias@ps.ksky.ne.jp
 *      Added module supports up to 8 cards.
 *      Added multiport(more than 32 channel in a card) support.
 *      Changed to set device info to module parameter as
 *      snd_port, snd_irq, snd_divisor, snd_polled and snd_ports.
 * 
 *   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 <linux/serial_reg.h>

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

MODULE_DESCRIPTION("\
Driver: MIDI serial\n\
");

int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 0-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
int snd_port[SND_CARDS] = SND_DEFAULT_PORT;     /* 0x3f8,0x2f8,0x3e8,0x2e8 */
int snd_irq[SND_CARDS] = SND_DEFAULT_IRQ;       /* 3,4,5,7,9,10,11,14,15 */
int snd_divisor[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 3};       /* 1,2,3,... */
int snd_ports[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};       /* 1,2,3,4 */
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.");
MODULE_PARM(snd_port, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_port, "Port # for UART16550A chip. [PORT12]");
MODULE_PARM(snd_irq, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_irq, "IRQ # for UART16550A chip. [IRQ]");
MODULE_PARM(snd_divisor, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_divisor, "Divisor of rate. [list=1,2,3]");
MODULE_PARM(snd_ports, "1-" __MODULE_STRING(SND_CARDS) "i");
MODULE_PARM_DESC(snd_ports, "Number of MIDI ports. [list=1,2,3,4]");

struct snd_serial {
	snd_card_t *card;
};


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;
	return serial_card;
}


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

////////////////////////////////////////////////////////////
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;
}

static int __init snd_serial_probe(int dev, struct snd_serial *serial_card)
{
	snd_card_t *card;
	int err;

	if (snd_ports[dev] < 1 || snd_ports[dev] > SND_SERIAL_MAX_PORTS)
		return -ENODEV;

	//snd_printd ("snd_serial_probe snd_card_new'ed\n");
	card  = snd_card_new(snd_index[dev], snd_id[dev],
			     snd_serial_use_inc, snd_serial_use_dec);
	if (card == NULL)
		return -ENOMEM;

	card->type = SND_CARD_TYPE_SERIAL;
	strcpy(card->abbreviation, "serial");
	strcpy(card->shortname, "serial midi (uart16550A)");

	snd_printd("snd_serial_probe detecting\n");
	if (snd_uart16550_detect(snd_port[dev]) <= 0)
		goto __nodev;

	snd_printd("snd_serial_probe new_device'ing\n");
	err = snd_uart16550_new_device(card, dev,
				       snd_irq[dev],
				       snd_port[dev],
				       snd_divisor[dev],
				       snd_ports[dev]);
	snd_printd("snd_serial_probe new_device'ed\n");
	if (err < 0) {
		snd_printd("snd_serial_probe err == %i\n", err);
		goto __nodev;
	}

	sprintf(card->longname, "%s at 0x%x, irq %d div %d ports %d",
		card->shortname,
		snd_port[dev],
		snd_irq[dev],
		snd_divisor[dev],
		snd_ports[dev]);

	// 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;

      __nodev:	//no usable ports found
	//snd_printd("snd_serial_probe no usable port found\n");
	snd_card_free(card);
	return -ENODEV;
}


static int __init alsa_card_serial_init(void)
{
	int dev = 0;
	int cards = 0;
	struct snd_serial *serial_card;


	for (dev = 0; dev < SND_CARDS && snd_port[dev] > 0; dev++) {
		serial_card = snd_serial_new();
		if (serial_card == NULL) {
			continue;
		}
		if (snd_serial_probe(dev, serial_card) < 0) {	//failed

			snd_serial_free(serial_card);
			continue;
		} else {
			snd_serial_cards[dev] = serial_card;
			cards++;
		}
	}

	if (cards == 0) {
#ifdef MODULE
		snd_printk("serial midi soundcard #%i not found or device busy\n",
			   dev + 1);
#endif
		return -ENODEV;
	}
	return 0;
}

static void __exit alsa_card_serial_exit(void)
{
	int dev;

	//snd_printd ("cleanup_module serial.o called.\n");
	for (dev = 0; dev < SND_CARDS; dev++) {
		struct snd_serial *serial_card = snd_serial_cards[dev];
		if (serial_card != NULL) {
			//snd_printd ("serial.o cleanup_module snd_card_free dev=%d\n", dev);
			snd_card_free(serial_card->card);
			snd_serial_free(serial_card);
		}
	}
}

module_init(alsa_card_serial_init)
module_exit(alsa_card_serial_exit)
