/*
 *  Initialization & resource registration routines
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   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 "driver.h"
#include "control.h"
#include "info.h"

int snd_cards_count;
unsigned int snd_cards_bitmap;		/* valid */
static unsigned int snd_cards_lock;	/* locked for registering/using */
snd_card_t *snd_cards[SND_CARDS];
snd_spin_define_static(card_lock);

void snd_driver_init(void)
{
	snd_cards_count = 0;
	snd_cards_bitmap = 0;
	memset(&snd_cards, 0, sizeof(snd_cards));
}

snd_card_t *snd_card_new(int idx, char *xid,
			 void (*use_inc) (snd_card_t * card),
			 void (*use_dec) (snd_card_t * card))
{
	unsigned long flags;
	snd_card_t *card;

	MOD_INC_USE_COUNT;
	if (!use_inc || !use_dec) {
		MOD_DEC_USE_COUNT;
		return NULL;
	}
	card = (snd_card_t *) snd_calloc(sizeof(snd_card_t));
	if (!card) {
		MOD_DEC_USE_COUNT;
		return NULL;
	}
	if (xid) {
		if (!snd_info_check_reserved_words(xid)) {
			snd_free(card, sizeof(snd_card_t));
			MOD_DEC_USE_COUNT;
			return NULL;
		}
		strncpy(card->id, xid, sizeof(card->id) - 1);
	}
	snd_spin_lock_static(card_lock, &flags);
	if (idx < 0) {
		for (idx = 0; idx < snd_ecards_limit; idx++)
			if (!(snd_cards_lock & (1 << idx)))
				break;
	} else {
		idx--;
		if (idx >= 0 && idx < snd_ecards_limit)
			if (snd_cards_lock & (1 << idx))
				idx = -1;	/* invalid */
	}
	if (idx < 0 || idx >= snd_ecards_limit) {
		snd_free(card, sizeof(snd_card_t));
		snd_spin_unlock_static(card_lock, &flags);
		MOD_DEC_USE_COUNT;
		return NULL;
	}
	snd_cards_lock |= 1 << idx;		/* lock it */
	snd_spin_unlock_static(card_lock, &flags);
	card->number = idx;
	if (!card->id[0])
		sprintf(card->id, "card%i", card->number + 1);
	card->use_inc = use_inc;
	card->use_dec = use_dec;
	snd_mutex_prepare(card, control);
	snd_switch_prepare(&card->switches);
	/* control interface cannot be accessed from user space until */
	/* snd_cards_bitmask and snd_cards are set by snd_card_register */
	if (snd_control_register(card) < 0) {
		snd_printd("unable to register control minors\n");
		/* Not fatal error */
	}
	if (snd_info_card_register(card) < 0) {
		snd_printd("unable to register card info\n");
		/* Not fatal error */
	}
#if 0
	snd_printk("%i: card = 0x%lx\n", card->number, (long) card);
#endif
	return card;
}

int snd_card_free(snd_card_t * card)
{
	unsigned long flags;

	if (!card)
		return -EINVAL;
	if (snd_info_card_unregister(card) < 0) {
		snd_printd("unable to unregister card info\n");
		/* Not fatal error */
	}
	if (snd_control_unregister(card) < 0) {
		snd_printd("unable to unregister control minors\n");
		/* Not fatal error */
	}
	snd_switch_free(&card->switches);
	snd_spin_lock_static(card_lock, &flags);
	snd_cards_lock &= ~(1 << card->number);
	snd_spin_unlock_static(card_lock, &flags);
	snd_unregister_interrupts(card);
	snd_unregister_dma_channels(card);
	snd_unregister_ioports(card);
	if (card->private_free)
		card->private_free(card->private_data);
	snd_free(card, sizeof(snd_card_t));
	MOD_DEC_USE_COUNT;
	return 0;
}

int snd_card_register(snd_card_t * card)
{
	unsigned long flags;

	if (!card)
		return -EINVAL;
	snd_control_restore(card);
	snd_spin_lock_static(card_lock, &flags);
	snd_cards_bitmap |= 1 << card->number;
	snd_cards[card->number] = card;
	snd_cards_count++;
	snd_spin_unlock_static(card_lock, &flags);
	return 0;
}

int snd_card_unregister(snd_card_t * card)
{
	unsigned long flags;

	if (!card)
		return -EINVAL;
	snd_control_store(card);
	snd_spin_lock_static(card_lock, &flags);
	snd_cards_bitmap &= ~(1 << card->number);
	snd_cards[card->number] = NULL;
	snd_cards_count--;
	snd_spin_unlock_static(card_lock, &flags);
	return 0;
}

static snd_info_entry_t *snd_card_info_entry = NULL;

static void snd_card_info_read(snd_info_buffer_t * buffer, void *private_data)
{
	unsigned long flags;
	int idx, count;
	snd_card_t *card;

	for (idx = count = 0; idx < SND_CARDS; idx++) {
		snd_spin_lock_static(card_lock, &flags);
		if (snd_cards_bitmap & (1 << idx)) {
			count++;
			card = snd_cards[idx];
			snd_iprintf(buffer, "%i [%-15s]: %s - %s\n",
					idx,
					card->id,
					card->abbreviation,
					card->shortname);
			snd_iprintf(buffer, "                     %s\n",
					card->longname);
		}
		snd_spin_unlock_static(card_lock, &flags);
	}
	if (!count) {
		snd_iprintf(buffer, "--- no soundcards ---\n");
	}
}

void snd_card_info_read_oss(snd_info_buffer_t * buffer)
{
	unsigned long flags;
	int idx, count;
	snd_card_t *card;

	for (idx = count = 0; idx < SND_CARDS; idx++) {
		snd_spin_lock_static(card_lock, &flags);
		if (snd_cards_bitmap & (1 << idx)) {
			count++;
			card = snd_cards[idx];
			snd_iprintf(buffer, "%s\n", card->longname);
		}
		snd_spin_unlock_static(card_lock, &flags);
	}
	if (!count) {
		snd_iprintf(buffer, "--- no soundcards ---\n");
	}
}

int snd_card_info_init(void)
{
	snd_info_entry_t *entry;

	entry = snd_info_create_entry(NULL, "cards");
	if (!entry)
		return -ENOMEM;
	entry->t.text.read_size = PAGE_SIZE;
	entry->t.text.read = snd_card_info_read;
	if (snd_info_register(entry) < 0) {
		snd_info_free_entry(entry);
		return -ENOMEM;
	}
	snd_card_info_entry = entry;
	return 0;
}

int snd_card_info_done(void)
{
	if (snd_card_info_entry)
		snd_info_unregister(snd_card_info_entry);
	return 0;
}

/*
 *  Delay ten microseconds * loops...
 */

void snd_delay(int loops)
{
	int i;

	while (loops-- > 0)
		for (i = 0; i < 16; i++)
			inb(0x80);
}

/*
 *  I/O port registration
 */

int snd_check_ioport(snd_card_t * card, int port, int size)
{
	if (!card)
		return -EINVAL;
	if (check_region(port, size)) {
		snd_printdd("check - ports 0x%x-0x%x busy\n", port, port + size - 1);
		return -EBUSY;
	}
	return 0;
}

int snd_register_ioport(snd_card_t * card, int port, int size, char *name,
                        snd_port_t ** rport)
{
	char *xname;
	snd_port_t *pport, *pport1;

	if (rport)
		*rport = NULL;
	if (!card)
		return -EINVAL;
	xname = snd_malloc_strdup(name);
	pport = (snd_port_t *) snd_calloc(sizeof(snd_port_t));
	if (!pport) {
		snd_free_str(xname);
		return -ENOMEM;
	}
	if (check_region(port, size)) {
		snd_printdd("requested ports 0x%x-0x%x busy\n",
						port, port + size - 1);
		snd_free(pport, sizeof(snd_port_t));
		snd_free_str(xname);
		return -EBUSY;
	}
	request_region(port, size, xname);
	pport->port = port;
	pport->size = size;
	pport->name = xname;
	if (card->ports == NULL) {
		card->ports = pport;
	} else {
		for (pport1 = card->ports; pport1->next != NULL; pport1 = pport1->next);
		pport1->next = pport;
	}
	if (rport)
		*rport = pport;
	return 0;
}

int snd_unregister_ioport(snd_card_t * card, snd_port_t * port)
{
	snd_port_t *prev;

	if (port == card->ports) {
		card->ports = port->next;
		release_region(port->port, port->size);
		snd_free(port, sizeof(snd_port_t));
		return 0;
	}
	for (prev = card->ports; prev && prev->next != port; prev = prev->next);
	if (prev == NULL)
		return -EINVAL;
	prev->next = port->next;
	release_region(port->port, port->size);
	snd_free(port, sizeof(snd_port_t));
	return 0;
}

int snd_unregister_ioports(snd_card_t * card)
{
	snd_port_t *pport, *pportnext;

	if (!card)
		return -EINVAL;
	pport = card->ports;
	card->ports = NULL;
	while (pport != NULL) {
		pportnext = pport->next;
		release_region(pport->port, pport->size);
		snd_free_str(pport->name);
		snd_free(pport, sizeof(snd_port_t));
		pport = pportnext;
	}
	return 0;
}

/*
 *  DMA channel registration
 */

int snd_register_dma_channel(snd_card_t * card, char *name, int number,
			     int type, int rsize, int *possible_numbers,
			     snd_dma_t ** rdma)
{
	snd_dma_t *dmaptr, *dmaptr1;

	if (!card)
		return -EINVAL;
	if (type < 0 || type > SND_DMA_TYPE_HARDWARE)
		return -EINVAL;
	if (rsize == SND_AUTO_DMA_SIZE) {
		switch (type) {
		case SND_DMA_TYPE_ISA:
			rsize = 128;
			break;
		case SND_DMA_TYPE_PCI:
			rsize = 512;
			break;
		case SND_DMA_TYPE_HARDWARE:
			rsize = 1024;
			break;
		default:
			return -EINVAL;
		}
	}
	if (rsize < 4 || rsize > 1024)
		return -EINVAL;
	if (type == SND_DMA_TYPE_ISA) {
		if (rsize > 128)
			return -EINVAL;
	}
	if (type == SND_DMA_TYPE_ISA) {
		if (!possible_numbers)
			return -EINVAL;
		if (number == SND_AUTO_DMA) {
			for (; (number = *possible_numbers) >= 0;
			    				possible_numbers++) {
				if (request_dma(number, "snd test"))
					continue;
				free_dma(number);
				break;
			}
			if (number < 0) {
				snd_printdd("dma register: AUTO DMA failed\n");
				return -ENOMEM;
			}
		} else {
			if (*possible_numbers >= 0) {
				for (; *possible_numbers >= 0; possible_numbers++)
					if (*possible_numbers == number)
						break;
				if (*possible_numbers < 0) {
					snd_printdd("dma register: DMA invalid\n");
					return -EINVAL;
				}				
			}
		}
		if (number < 4 && rsize > 64)
			rsize = 64;
		if (number < 0 || number > 7)
			return -EINVAL;
	} else {
		if (possible_numbers)
			return -EINVAL;
		if (number < 0)
			return -EINVAL;
	}
	dmaptr = (snd_dma_t *) snd_calloc(sizeof(snd_dma_t));
	if (!dmaptr)
		return -ENOMEM;
	dmaptr->type = type;
	dmaptr->dma = number;
	dmaptr->rsize = rsize * 1024;
	dmaptr->name = snd_malloc_strdup(name);
	snd_mutex_prepare(dmaptr, lock);
	snd_mutex_prepare(dmaptr, mutex);
	if (type == SND_DMA_TYPE_ISA) {
		if (request_dma(number, dmaptr->name)) {
			snd_printk("unable to grab DMA %i for %s\n",
						number, dmaptr->name);
			snd_free_str(dmaptr->name);
			snd_free(dmaptr, sizeof(struct snd_stru_dma));
			return -EBUSY;
		}
	}
	if (card->dmas == NULL) {
		card->dmas = dmaptr;
	} else {
		for (dmaptr1 = card->dmas; dmaptr1->next != NULL; dmaptr1 = dmaptr1->next);
		dmaptr1->next = dmaptr;
	}
	*rdma = dmaptr;
	return 0;
}

int snd_unregister_dma_channels(snd_card_t * card)
{
	snd_dma_t *dmaptr, *dmaptrnext;

	if (!card)
		return -EINVAL;
	dmaptr = card->dmas;
	card->dmas = NULL;
	while (dmaptr) {
		dmaptrnext = dmaptr->next;
		if (dmaptr->type == SND_DMA_TYPE_ISA) {
			disable_dma(dmaptr->dma);
			free_dma(dmaptr->dma);
		}
		snd_free_str(dmaptr->name);
		snd_free(dmaptr, sizeof(snd_dma_t));
		dmaptr = dmaptrnext;
	}
	return 0;
}

/*
 *  IRQ channel registration
 */

int snd_register_interrupt(snd_card_t * card, char *name, int number,
			   int type, snd_irq_handler_t * handler,
			   void *dev_id, int *possible_numbers,
			   snd_irq_t ** rirq)
{
	int sa_flags;
	char *xname;
	snd_irq_t *irqptr, *irqtmp;

	if (!rirq)
		return -EINVAL;
	*rirq = NULL;
	if (!card)
		return -EINVAL;		
	if (type < 0 || type > SND_IRQ_TYPE_PCI)
		return -EINVAL;
	sa_flags = SA_INTERRUPT;
	if (type != SND_IRQ_TYPE_ISA)
		sa_flags = SA_SHIRQ;
	if (type == SND_IRQ_TYPE_ISA) {
		if (!possible_numbers)
			return -EINVAL;
		if (number == SND_AUTO_IRQ) {
			for (; (number = *possible_numbers) >= 0;
							possible_numbers++) {
				if (request_irq(number, handler, sa_flags,
				                "snd test", dev_id))
					continue;
				free_irq(number, dev_id);
				break;
			}
			if (number < 0) {
				snd_printdd("irq register: AUTO IRQ failed\n");
				return -ENOMEM;
			}
		} else {
			if (*possible_numbers >= 0) {
				for (; *possible_numbers >= 0;
							possible_numbers++)
					if (*possible_numbers == number)
						break;
				if (*possible_numbers < 0) {
					snd_printdd("irq register: IRQ invalid\n");
					return -EINVAL;
				}
			}
		}
	} else {
		if (possible_numbers)
			return -EINVAL;
	}
#if (defined( LINUX_2_1 ) && __SMP__) || !defined( __i386__ )
	if (number < 0 || number > 63)
		return -EINVAL;
#else
	if (number < 0 || number > 15)
		return -EINVAL;
#endif
	xname = snd_malloc_strdup(name);
	if (!xname)
		return -ENOMEM;
	irqptr = (snd_irq_t *) snd_calloc(sizeof(snd_irq_t));
	if (!irqptr) {
		snd_free_str(xname);
		return -ENOMEM;
	}
	if (request_irq(number, handler, sa_flags, xname, dev_id)) {
		snd_printk("unable to grab IRQ %i for %s\n", number, xname);
		snd_free_str(xname);
		snd_free(irqptr, sizeof(snd_irq_t));
		return -EBUSY;
	}
	irqptr->type = type;
	irqptr->irq = number;
	irqptr->name = xname;
	irqptr->dev_id = dev_id;
	if (card->irqs == NULL) {
		card->irqs = irqptr;
	} else {
		for (irqtmp = card->irqs; irqtmp->next != NULL; irqtmp = irqtmp->next);
		irqtmp->next = irqptr;
	}
	*rirq = irqptr;
	return 0;
}

int snd_unregister_interrupts(snd_card_t * card)
{
	snd_irq_t *irqptr, *irqnext;

	if (!card)
		return -EINVAL;
	irqptr = card->irqs;
	card->irqs = NULL;
	while (irqptr != NULL) {
		irqnext = irqptr->next;
		free_irq(irqptr->irq, irqptr->dev_id);
		snd_free_str(irqptr->name);
		snd_free(irqptr, sizeof(snd_irq_t));
		irqptr = irqnext;
	}
	return 0;
}
