/*
 *  Copyright (c) by Jaroslav Kysela <perex@suse.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.
 *
 */

#define SND_MAIN_OBJECT_FILE
#include "driver.h"
#include "sb.h"

extern struct snd_stru_pcm1_hardware snd_sb8_playback;
extern struct snd_stru_pcm1_hardware snd_sb8_record;

int snd_sb8dsp_command(sbdsp_t * codec, unsigned char val)
{
	int i;
	unsigned long limit;

	limit = jiffies + HZ / 10;
	for (i = 500000; i > 0 && (limit-jiffies) > 0; i--)
		if ((inb(SBP(codec, STATUS)) & 0x80) == 0) {
			outb(val, SBP(codec, COMMAND));
			return 1;
		}
	snd_printd("snd_sb8dsp_command: timeout (0x%x)\n", val);
	return 0;
}

int snd_sb8dsp_get_byte(sbdsp_t * codec)
{
	int i;

	for (i = 1000; i; i--)
		if (inb(SBP(codec, DATA_AVAIL)) & 0x80)
			return inb(SBP(codec, READ));
	snd_printd("snd_sb8dsp_get_byte failed: 0x%x = 0x%x!!!\n", SBP(codec, DATA_AVAIL), inb(SBP(codec, DATA_AVAIL)));
	return -ENODEV;
}

int snd_sb8dsp_reset(sbdsp_t * codec)
{
	int i;

	outb(1, SBP(codec, RESET));
	snd_delay(1);
	outb(0, SBP(codec, RESET));
	snd_delay(3);
	for (i = 0; i < 1000 && !(inb(SBP(codec, DATA_AVAIL)) & 0x80); i++);
	if (inb(SBP(codec, READ)) != 0xaa) {
		snd_printd("sb_reset: failed at 0x%x!!!\n", codec->port);
		return -ENODEV;
	}
	return 0;
}

int snd_sb8dsp_version(snd_pcm_t * pcm)
{
	unsigned long flags;
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;
	int i;
	unsigned int result = -1;

	pcm1 = (snd_pcm1_t *) pcm->private_data;
	codec = (sbdsp_t *) pcm1->private_data;
	snd_spin_lock(codec, reg, &flags);
	snd_sb8dsp_command(codec, SB_DSP_GET_VERSION);
	for (i = 100000; i; i--) {
		if (inb(SBP(codec, DATA_AVAIL)) & 0x80) {
			result = (short) inb(SBP(codec, READ)) << 8;
			break;
		}
	}
	for (i = 100000; i; i--) {
		if (inb(SBP(codec, DATA_AVAIL)) & 0x80) {
			result |= (short) inb(SBP(codec, READ));
			break;
		}
	}
	snd_spin_unlock(codec, reg, &flags);
	return result;
}

int snd_sb8dsp_probe(snd_pcm_t * pcm)
{
	unsigned long flags;
	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;

	snd_spin_lock(codec, reg, &flags);
	if (snd_sb8dsp_reset(codec) < 0) {
		snd_printdd("SB: [0x%x] reset failed... 0x%x\n", codec->port, inb(SBP(codec, READ)));
		snd_spin_unlock(codec, reg, &flags);
		return -ENODEV;
	}
	snd_spin_unlock(codec, reg, &flags);

	version = snd_sb8dsp_version(pcm);
	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:
		snd_printk("SB [0x%x]: DSP chip version %i.%i is not supported by the SB8 code\n", codec->port, version >> 8, version & 0xff);
		break;
	default:
		snd_printk("SB [0x%x]: unknown 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;
	default:
		str = "???";
	}
	strcpy(codec->name, str);
	sprintf(pcm->name, "DSP v%i.%i", version >> 8, version & 0xff);

	return 0;
}

void snd_sb8dsp_free(void *private_data)
{
	sbdsp_t *codec;

	codec = (sbdsp_t *) private_data;
	snd_free(codec, sizeof(sbdsp_t));
}

snd_pcm_t *snd_sb8dsp_new_device(snd_card_t * card,
				 unsigned short port,
				 snd_irq_t * irqptr,
				 snd_dma_t * dma8ptr,
				 unsigned short hardware)
{
	snd_pcm_t *pcm;
	snd_pcm1_t *pcm1;
	sbdsp_t *codec;

	pcm = snd_pcm1_new_device(card, "SB8 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_spin_prepare(codec, reg);
	snd_spin_prepare(codec, open8);
	snd_spin_prepare(codec, open16);
	snd_spin_prepare(codec, midi_input);
	snd_spin_prepare(&codec->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->hardware = hardware;
	pcm1->private_data = codec;
	pcm1->private_free = snd_sb8dsp_free;
	strcpy(pcm->name, "Sound Blaster");
	if (snd_sb8dsp_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;
	memcpy(&pcm1->playback.hw, &snd_sb8_playback, sizeof(snd_sb8_playback));
	memcpy(&pcm1->record.hw, &snd_sb8_record, sizeof(snd_sb8_record));
	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;
	}
	return pcm;
}

/*
 *  INIT part
 */

#ifndef LINUX_2_1
extern struct symbol_table snd_symbol_table_sb8_export;
#endif

int init_module(void)
{
#ifndef LINUX_2_1
	if (register_symtab(&snd_symbol_table_sb8_export) < 0)
		return -ENOMEM;
#endif
	return 0;
}

void cleanup_module(void)
{
}
