/*
 *  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.
 *
 * --
 *
 * Wed Aug 18 15:39:50 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk>
 *   In snd_sb8dsp_new_device, both playback_pchn1 and capture_pchn1 were set
 *   to point to pcm->playback.private_data causing playback to fail with DMA
 *   errors. Fixed.
 */

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

extern struct snd_stru_pcm1_hardware snd_sb8_playback;
extern struct snd_stru_pcm1_hardware snd_sb8_capture;

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;
	sbdsp_t *codec;
	int i;
	unsigned int result = -1;

	codec = (sbdsp_t *) pcm->private_data;
	spin_lock_irqsave(&codec->reg_lock, 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;
		}
	}
	spin_unlock_irqrestore(&codec->reg_lock, flags);
	return result;
}

int snd_sb8dsp_probe(snd_pcm_t * pcm)
{
	unsigned long flags;
	sbdsp_t *codec;
	int version, hw;
	char *str;

	/*
	 *  initialization sequence
	 */

	codec = (sbdsp_t *) pcm->private_data;

	spin_lock_irqsave(&codec->reg_lock, flags);
	if (snd_sb8dsp_reset(codec) < 0) {
		snd_printdd("SB: [0x%x] reset failed... 0x%x\n", codec->port, inb(SBP(codec, READ)));
		spin_unlock_irqrestore(&codec->reg_lock, flags);
		return -ENODEV;
	}
	spin_unlock_irqrestore(&codec->reg_lock, 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)
{
	if (private_data)
		snd_kfree(private_data);
}

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_channel_t *playback_pchn1, *capture_pchn1;
	sbdsp_t *codec;

	pcm = snd_pcm1_new_device(card, "SB8 DSP", 1, 1);
	if (!pcm)
		return NULL;
	codec = (sbdsp_t *) snd_kcalloc(sizeof(sbdsp_t), GFP_KERNEL);
	if (!codec)
		return NULL;
	codec->reg_lock = SPIN_LOCK_UNLOCKED;
	codec->open8_lock = SPIN_LOCK_UNLOCKED;
	codec->open16_lock = SPIN_LOCK_UNLOCKED;
	codec->midi_input_lock = SPIN_LOCK_UNLOCKED;
	codec->mixer.lock = SPIN_LOCK_UNLOCKED;
	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;
	pcm->private_data = codec;
	pcm->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_CAPTURE;
	playback_pchn1 = (snd_pcm1_channel_t *) pcm->playback.private_data;
	capture_pchn1 = (snd_pcm1_channel_t *) pcm->capture.private_data;
	memcpy(&playback_pchn1->hw, &snd_sb8_playback, sizeof(snd_sb8_playback));
	memcpy(&capture_pchn1->hw, &snd_sb8_capture, sizeof(snd_sb8_capture));
	playback_pchn1->private_data = capture_pchn1->private_data = codec;
	switch (codec->hardware) {
	case SB_HW_10:
		playback_pchn1->hw.flags &= ~SND_PCM1_HW_AUTODMA;	/* ugh, antique !!! */
		capture_pchn1->hw.flags &= ~SND_PCM1_HW_AUTODMA;		/* ugh, antique !!! */
		playback_pchn1->hw.flags |= SND_PCM1_HW_BLOCKPTR;
		capture_pchn1->hw.flags |= SND_PCM1_HW_BLOCKPTR;
		break;
	case SB_HW_PRO:
		playback_pchn1->hw.max_voices =
		  capture_pchn1->hw.max_voices = 2;
		/* follow thru */
	case SB_HW_201:
		playback_pchn1->hw.max_rate = 44100;
		capture_pchn1->hw.max_rate = 15000;
		break;
	}
	return pcm;
}

EXPORT_SYMBOL(snd_sb8dsp_new_device);
  /* sb8.c */
EXPORT_SYMBOL(snd_sb8dsp_interrupt);
  /* mixer8.c */
EXPORT_SYMBOL(snd_sb8mixer_read);
EXPORT_SYMBOL(snd_sb8mixer_write);
EXPORT_SYMBOL(snd_sb8dsp_new_mixer);
  /* midi8.c */
EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt);
EXPORT_SYMBOL(snd_sb8dsp_midi_new_device);

/*
 *  INIT part
 */

int init_module(void)
{
	return 0;
}

void cleanup_module(void)
{
}
