/*
 *  Initialization & resource registration routines
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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 "io.h"
#include "driver.h"
#include "info.h"
#include "pcm1.h"
#include "mixer.h"
#include "control.h"
#ifdef __QNXNTO__
#include <sys/procmgr.h>
#endif

int snd_ecards_limit = SND_CARDS;
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];

#ifdef __QNXNTO__
dispatch_t *snd_dpp;
#endif

static int snd_mgr_init(int argc, char **argv)
{
	argc = argc, argv = argv;

#ifdef __QNXNTO__	

	if (procmgr_daemon(EXIT_SUCCESS, PROCMGR_DAEMON_NOCHDIR | PROCMGR_DAEMON_NODEVNULL) == -1) {
		perror ("procmgr_daemon failed\n");
		return (-1);
	}

	if (ThreadCtl(_NTO_TCTL_IO,0) < 0) {
        perror ("ThreadCtl failed\n");
        return (-1);
	}
	if ((snd_dpp = dispatch_create()) == NULL) {
		perror ("dispatch_create() failed\n");
		return (-1);
	}
#else

#ifndef DEBUG

        /* First ignore hangup in case started with ontty because parent
         * (session leader) forks then exits causing child to be HUP'd
         *
         * If interrupt/quit are not ignored (because I'm in the foreground)
         * and I can become a process group leader (because I'm not already)
         * then fork to background myself
         */
        signal(SIGHUP,  SIG_IGN);   /* survive session leader death */
        signal(SIGCHLD, SIG_IGN);   /* ignore child deaths */
        if (((signal(SIGINT, SIG_IGN) != SIG_IGN |
                signal(SIGQUIT, SIG_IGN) != SIG_IGN)) &&
                setpgid(0, 0) == 0 && fork())
                        exit(EXIT_SUCCESS);
#endif
#endif
	snd_timer_init();
   	return (0);	
}	


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

int snd_init( int argc, char **argv)
{
	if ( snd_mgr_init(argc, argv) < 0 )
		return -1;
	snd_sound_init();
	snd_driver_init();
	snd_pcm_init();
	snd_pcm1_init();
	snd_mixer_init();
	return 0;
}

void snd_cleanup(void)
{
	snd_mixer_cleanup();
	snd_pcm1_cleanup();
	snd_pcm_cleanup();
	snd_sound_cleanup();
}

snd_card_t *snd_card_new(int idx, char *xid)
{
	snd_card_t *card;

	card = (snd_card_t *) snd_calloc(sizeof(snd_card_t));
	if (!card)
		return NULL;
	if (xid) {
		if (!snd_info_check_reserved_words(xid)) {
			snd_free(card, sizeof(snd_card_t));
			return NULL;

		}
		strncpy(card->id, xid, sizeof(card->id) - 1);
	}
	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));
		return NULL;
	}
	snd_cards_lock |= 1 << idx;		/* lock it */
	card->number = idx;
	if (!card->id[0])
		sprintf(card->id, "card%i", card->number + 1);

	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 device\n");
		/* Not fatal error */
	}
	if (snd_info_card_register(card) < 0) {
		snd_printd("unable to register card info\n");
		/* Not fatal error */
	}
	return card;
}

int snd_card_free(snd_card_t * card)
{
	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_cards_lock &= ~(1 << card->number);
	snd_unregister_interrupts(card);
	snd_unregister_dma_channels(card);
	snd_unregister_ioports(card);
	snd_mutex_destroy(card, control);
	if (card->private_free)
		card->private_free(card->private_data);
	snd_free(card, sizeof(snd_card_t));
	return 0;
}

int snd_card_register(snd_card_t * card)
{
	if (!card)
		return -EINVAL;
	snd_control_restore(card);
	snd_cards_bitmap |= 1 << card->number;
	snd_cards[card->number] = card;
	snd_cards_count++;
	snd_printk("%d - %s\n",card->number, card->longname);
	return 0;
}

int snd_card_unregister(snd_card_t * card)
{
	if (!card)
		return -EINVAL;
	snd_control_store(card);
	snd_cards_bitmap &= ~(1 << card->number);
	snd_cards[card->number] = NULL;
	snd_cards_count--;
	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)
{
	int idx, count;
	snd_card_t *card;

	for (idx = count = 0; idx < SND_CARDS; idx++) {
		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);
		}
	}
	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 = 0x1000;
	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)
{
	nanospin_ns(loops*10000);	
}

/*
 *  I/O port registration
 */

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);
	if (request_region(port, size)) {
		snd_printdd("requested ports 0x%x-0x%x busy\n",
						port, port + size - 1);
		snd_free_str(xname);
		return -EBUSY;
	}
	pport = (snd_port_t *) snd_calloc(sizeof(snd_port_t));
	if (!pport) {
		snd_free_str(xname);
		return -ENOMEM;
	}
	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))
					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);
	if (type == SND_DMA_TYPE_ISA) {
		if (request_dma(number)) {
			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) {
			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 = 0;
	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, sa_flags))
					continue;
				free_irq(number);
				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;
	}
	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, sa_flags)) {
		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;
	irqptr->handler = handler;
	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;
#ifdef __QNXNTO__
		pthread_cancel(irqptr->tid);
#endif
		free_irq(irqptr->irq);
		snd_free_str(irqptr->name);
		snd_free(irqptr, sizeof(snd_irq_t));
		irqptr = irqnext;
	}
	return 0;
}
