/*
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.cz>
 *  Routines for control of SoundBlaster cards
 *
 *
 *   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 "sb.h"
#include "initval.h"

#ifdef __QNXNTO__
int snd_index[SND_CARDS] = SND_DEFAULT_IDX;	/* Index 1-MAX */
char *snd_id[SND_CARDS] = SND_DEFAULT_STR;	/* ID for this card */
int snd_port[SND_CARDS] = {0x220, 0x240, 0x260};//SND_DEFAULT_PNP_PORT;	/* 0x220,0x240,0x260 */
int snd_mpu_port[SND_CARDS] = {0x330, 0x300,[2 ... (SND_CARDS - 1)] = -1};
int snd_awe_port[SND_CARDS] = {0x620,[1 ... (SND_CARDS - 1)] = -1};
int snd_irq[SND_CARDS] = {9}; //SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma8[SND_CARDS] = {1}; //SND_DEFAULT_DMA;	/* 0,1,3 */
int snd_dma8_size[SND_CARDS] = {64}; // SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma16[SND_CARDS] = SND_DEFAULT_DMA;	/* 5,6,7 */
int snd_dma16_size[SND_CARDS] = {64}; //SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_mic_agc[SND_CARDS] = {[0 ... (SND_CARDS - 1)] = 1};
#else
int snd_index[SND_CARDS] = {-1,-1,-1,-1,-1,-1,-1,-1};	/* Index 1-MAX */
char *snd_id[SND_CARDS];
int snd_port[SND_CARDS] = {0x220, 0x240, 0x260};//SND_DEFAULT_PNP_PORT;	/* 0x220,0x240,0x260 */
int snd_mpu_port[SND_CARDS] = {0x330, 0x300, -1, -1, -1, -1, -1, -1};
int snd_awe_port[SND_CARDS] = {0x620, -1, -1, -1, -1, -1, -1 ,-1};
int snd_irq[SND_CARDS] = {9}; //SND_DEFAULT_IRQ;	/* 5,7,9,10 */
int snd_dma8[SND_CARDS] = {1}; //SND_DEFAULT_DMA;	/* 0,1,3 */
int snd_dma8_size[SND_CARDS] = {64}; // SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_dma16[SND_CARDS] = {8,8,8,8,8,8,8,8};	/* 5,6,7 */
int snd_dma16_size[SND_CARDS] = {64}; //SND_DEFAULT_DMA_SIZE;	/* 8,16,32,64,128 */
int snd_mic_agc[SND_CARDS] = {1,1,1,1,1,1,1,1};
#endif


extern struct snd_stru_pcm1_hardware snd_sb8_playback;
extern struct snd_stru_pcm1_hardware snd_sb8_record;
extern struct snd_stru_pcm1_hardware snd_sb16_playback;
extern struct snd_stru_pcm1_hardware snd_sb16_record;

int snd_sb_getopt(int argc, char **argv)
{
	int c;
	
	while( (c = getopt(argc, argv, SND_OPTIONS)) != -1 ) {
		switch(c) {
			case SND_IOPORT:
				snd_port[0] = strtol(optarg, NULL, 16);
				break;
			case SND_IRQ:
				snd_irq[0] = atoi(optarg);
				break;
			case SND_DMA:
				snd_dma8[0] = atoi(optarg);
				if ( optarg = strchr(optarg, ','))
					snd_dma16[0] = atoi(optarg+1);
				break;
		}
	}
	return 0;
}

int snd_sbdsp_probe(snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;
	int version, hw;
	char *str;

	/*
	 *  initialization sequence
	 */

	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;

	if (snd_sbdsp_reset(codec) < 0) {
		snd_printdd("SB: [0x%x] reset failed... 0x%x\n", codec->port, in8(SBP(codec, READ)));
		return -ENODEV;
	}
	version = snd_sbdsp_version(codec);
	if (version < 0)
		return -ENODEV;

	snd_printdd("SB [0x%x]: DSP chip found, version = %i.%i\n", codec->port, version >> 8, version & 0xff);

	hw = SB_HW_AUTO;
	switch (version >> 8) {
	case 1:
		hw = SB_HW_10;
		break;
	case 2:
		hw = !(version & 0xff) ? SB_HW_20 : SB_HW_201;
		break;
	case 3:
		hw = SB_HW_PRO;
		break;
	case 4:
		hw = SB_HW_16;
		break;
	default:
		snd_printk("SB [0x%x]: uknown DSP chip version %i.%i\n", codec->port, version >> 8, version & 0xff);
		return -ENODEV;
	}
	switch (codec->hardware = hw) {
	case SB_HW_10:
		str = "Sound Blaster 1.0";
		break;
	case SB_HW_20:
		str = "Sound Blaster 2.0";
		break;
	case SB_HW_201:
		str = "Sound Blaster 2.01+";
		break;
	case SB_HW_PRO:
		str = "Sound Blaster Pro";
		break;
	case SB_HW_16:
		str = "Sound Blaster 16";
		break;
	default:
		str = "???";
	}
	strcpy(codec->name, str);
	sprintf(pcm->name, "DSP v%i.%i", version >> 8, version & 0xff);

	return 0;
}

int snd_sbdsp_sb16_configure(snd_pcm_t * pcm)
{
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;
	unsigned char irqreg = 0, dmareg = 0, mpureg, realirq, realdma,
	 realmpureg;

	/* note: mpu register should be present only on SB16 Vibra soundcards */
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;

	snd_printd("codec -> irq = %i, codec -> dma8 = %i, codec -> dma16 = %i\n", codec->irq, codec->dma8, codec->dma16);

	mpureg = snd_sbmixer_read(&codec->mixer, 0x84) & ~0x06;
	switch (codec->irq) {
	case 2:
	case 9:
		irqreg |= 0x01;
		break;
	case 5:
		irqreg |= 0x02;
		break;
	case 7:
		irqreg |= 0x04;
		break;
	case 10:
		irqreg |= 0x08;
		break;
	default:
		return -EINVAL;
	}
	if (codec->dma8ptr) {
		switch (codec->dma8) {
		case 0:
			dmareg |= 0x01;
			break;
		case 1:
			dmareg |= 0x02;
			break;
		case 3:
			dmareg |= 0x08;
			break;
		default:
			return -EINVAL;
		}
	}
	if (codec->dma16ptr) {
		switch (codec->dma16) {
		case 5:
			dmareg |= 0x20;
			break;
		case 6:
			dmareg |= 0x40;
			break;
		case 7:
			dmareg |= 0x80;
			break;
		default:
			return -EINVAL;
		}
	}
	switch (codec->mpu_port) {
	case 0x300:
		mpureg |= 0x04;
		break;
	case 0x330:
		mpureg |= 0x00;
		break;
	default:
		mpureg |= 0x02;	/* disable MPU */
	}
	snd_sbmixer_write(&codec->mixer, 0x80, irqreg);
	realirq = snd_sbmixer_read(&codec->mixer, 0x80);
	snd_sbmixer_write(&codec->mixer, 0x81, dmareg);
	realdma = snd_sbmixer_read(&codec->mixer, 0x81);
	snd_sbmixer_write(&codec->mixer, 0x84, mpureg);
	realmpureg = snd_sbmixer_read(&codec->mixer, 0x84);
	if ((~realirq) & irqreg || (~realdma) & dmareg) {
		snd_printdd("SB16 [0x%x]: unable to set DMA & IRQ (PnP device?)\n", codec->port);
		snd_printdd("SB16 [0x%x]: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", codec->port, realirq, realdma, realmpureg);
		snd_printdd("SB16 [0x%x]: new: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", codec->port, irqreg, dmareg, mpureg);
#if 0				/* this test doesn't work for all SB16 PnP soundcards */
		return -ENODEV;
#endif
	}
	return 0;
}

void snd_sbdsp_free(void *private_data)
{
	sbdsp_t *codec;
	sbmixer_t *mixer;

	codec = (sbdsp_t *) private_data;
	if (codec->hardware == SB_HW_16) {
    	snd_sb16_proc_done(codec->pcm);
	}
	snd_mutex_destroy(codec, reg);
	snd_mutex_destroy(codec, open8);
	snd_mutex_destroy(codec, open16);
	snd_mutex_destroy(codec, midi_input);
	mixer = &codec->mixer;
	snd_mutex_destroy(mixer, mixer);
	snd_free(codec, sizeof(sbdsp_t));
}

snd_pcm_t *snd_sbdsp_new_device(snd_card_t * card,
				unsigned short port,
				snd_irq_t *irqptr,
				snd_dma_t *dma8ptr,
				snd_dma_t *dma16ptr,
				unsigned short hardware)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;
	sbmixer_t *mixer;
	
	pcm = snd_pcm1_new_device(card, "SB DSP");
	if (!pcm)
		return NULL;
	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) snd_calloc(sizeof(sbdsp_t));
	if (!codec)
		return NULL;
	snd_mutex_prepare(codec, reg);
	snd_mutex_prepare(codec, open16);
	snd_mutex_prepare(codec, midi_input);
	mixer = &codec->mixer;
	snd_mutex_prepare(mixer, mixer);
	codec->pcm = pcm;
	codec->card = pcm->card;
	codec->port = port;
	codec->mixer.port = port;
	codec->irqptr = irqptr;
	codec->irq = irqptr->irq;
	codec->dma8ptr = dma8ptr;
	if (dma8ptr)
		codec->dma8 = dma8ptr->dma;
	codec->dma16ptr = dma16ptr;
	if (dma16ptr)
		codec->dma16 = dma16ptr->dma;
	codec->hardware = hardware;
	pcm1->private_data = codec;
	pcm1->private_free = snd_sbdsp_free;
	strcpy(pcm->name, "Sound Blaster");
	if (snd_sbdsp_probe(pcm) < 0) {
		snd_pcm_free(pcm);
		return NULL;
	}
	pcm->info_flags = SND_PCM_INFO_CODEC | SND_PCM_INFO_MMAP |
	    SND_PCM_INFO_PLAYBACK | SND_PCM_INFO_RECORD;
	codec->force_mode16 = SB_MODE16_AUTO;
	switch (codec->hardware) {
	case SB_HW_10:
	case SB_HW_20:
	case SB_HW_201:
	case SB_HW_PRO:
		memcpy(&pcm1->playback.hw, &snd_sb8_playback, sizeof(snd_sb8_playback));
		memcpy(&pcm1->record.hw, &snd_sb8_record, sizeof(snd_sb8_record));
		break;
	case SB_HW_16:
		memcpy(&pcm1->playback.hw, &snd_sb16_playback, sizeof(snd_sb16_playback));
		memcpy(&pcm1->record.hw, &snd_sb16_record, sizeof(snd_sb16_record));
		snd_sb16_proc_init(pcm);
		break;
	}
	pcm->info_flags = SND_PCM_INFO_DSP | SND_PCM_INFO_PLAYBACK |
	    SND_PCM_INFO_RECORD | SND_PCM_INFO_MMAP;
	switch (codec->hardware) {
	case SB_HW_10:
		pcm1->playback.hw.flags &= ~SND_PCM1_HW_AUTODMA;	/* ugh, antique !!! */
		pcm1->record.hw.flags &= ~SND_PCM1_HW_AUTODMA;	/* ugh, antique !!! */
		pcm1->playback.hw.flags |= SND_PCM1_HW_BLOCKPTR;
		pcm1->record.hw.flags |= SND_PCM1_HW_BLOCKPTR;
		break;
	case SB_HW_PRO:
		pcm1->playback.hw.max_voices =
		    pcm1->record.hw.max_voices = 2;
		/* follow thru */
	case SB_HW_201:
		pcm1->playback.hw.max_rate = 44100;
		pcm1->record.hw.max_rate = 15000;
		break;
	case SB_HW_16:
		pcm->info_flags |= SND_PCM_INFO_DUPLEX | SND_PCM_INFO_DUPLEX_LIMIT;
		break;
	}
	return pcm;
}
