/*
 *  Initialisation routines for the emu8000 (AWE32/64)
 *
 *  Copyright (C) 1999 Steve Ratcliffe
 *
 *  Based on awe_wave.c by Takashi Iwai
 *
 *   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.
 *
 * Changes
 * 19990227   Takashi Iwai    tweak_voice() to setup voices to defaults
 *                 all reverb, eq, chorus stuff removed as its in
 *                 its own file now.
 * 19990306   Takashi Iwai    sequencer initialization is done in emu8000.c
 */
/*
 * Initialisation routines for the emu8000.
 *
 * If this chip is used on any card other than the AWE32/64 then it
 * is possible that different initialisations will be needed.
 */

#include "driver.h"
#include "emu8000.h"
#include "emu8000_reg.h"
#include "emu8000_equalizer.h"
#include "soundfont.h"
#include "emu8000_voice.h"
#include "emu8000_port.h"


/* Prototypes for static functions */
static void init_audio(emu8000_t *emu);
static void init_dma(emu8000_t *emu);
static void init_arrays(emu8000_t *emu);
static void send_array(emu8000_t *emu, unsigned short *data, int size);
static void size_dram(emu8000_t *emu);
static void init_channels(emu8000_t *emu);
static void tweak_voices(emu8000_t *emu);

#define DEF_FM_CHORUS_DEPTH 0x10
#define DEF_FM_REVERB_DEPTH 0x10

/*
 * The main initialisation routine.
 */
void
snd_emu8000_init(emu8000_t *emu)
{
	emu->last_reg = 0xffff;

	/* initialize hardware configuration */
	EMU8000_HWCF1_WRITE(emu, 0x0059);
	EMU8000_HWCF2_WRITE(emu, 0x0020);

	/* disable audio; this seems to reduce a clicking noise a bit.. */
	EMU8000_HWCF3_WRITE(emu, 0);

	/* initialize audio channels */
	init_audio(emu);

	/* initialize DMA */
	init_dma(emu);

	/* initialize init arrays */
	init_arrays(emu);

	/* check DRAM memory size */
	size_dram(emu);

	init_channels(emu);

	/*
	 * Initialize the FM section of the AWE32, this is needed
	 * for DRAM refresh as well
	 */
	snd_emu8000_init_fm(emu);

	/* set voice envelopes */
	tweak_voices(emu);

	/* enable audio */
	EMU8000_HWCF3_WRITE(emu, 0x4);

	/* set equzlier, chorus and reverb modes */
	snd_emu8000_update_equalizer(emu);
	snd_emu8000_update_chorus_mode(emu);
	snd_emu8000_update_reverb_mode(emu);

	emu->use_time = 0;
}

/* intiailize audio channels */
static void
init_audio(emu8000_t *emu)
{
	int ch;

	/* turn off envelope engines */
	for (ch = 0; ch < emu->max_channels; ch++)
		EMU8000_DCYSUSV_WRITE(emu, ch, 0x80);
  
	/* reset all other parameters to zero */
	for (ch = 0; ch < emu->max_channels; ch++) {
		EMU8000_ENVVOL_WRITE(emu, ch, 0);
		EMU8000_ENVVAL_WRITE(emu, ch, 0);
		EMU8000_DCYSUS_WRITE(emu, ch, 0);
		EMU8000_ATKHLDV_WRITE(emu, ch, 0);
		EMU8000_LFO1VAL_WRITE(emu, ch, 0);
		EMU8000_ATKHLD_WRITE(emu, ch, 0);
		EMU8000_LFO2VAL_WRITE(emu, ch, 0);
		EMU8000_IP_WRITE(emu, ch, 0);
		EMU8000_IFATN_WRITE(emu, ch, 0);
		EMU8000_PEFE_WRITE(emu, ch, 0);
		EMU8000_FMMOD_WRITE(emu, ch, 0);
		EMU8000_TREMFRQ_WRITE(emu, ch, 0);
		EMU8000_FM2FRQ2_WRITE(emu, ch, 0);
		EMU8000_PTRX_WRITE(emu, ch, 0);
		EMU8000_VTFT_WRITE(emu, ch, 0);
		EMU8000_PSST_WRITE(emu, ch, 0);
		EMU8000_CSL_WRITE(emu, ch, 0);
		EMU8000_CCCA_WRITE(emu, ch, 0);
	}

	for (ch = 0; ch < emu->max_channels; ch++) {
		EMU8000_CPF_WRITE(emu, ch, 0);
		EMU8000_CVCF_WRITE(emu, ch, 0);
	}
}

/* initialize DMA address */
static void
init_dma(emu8000_t *emu)
{
	EMU8000_SMALR_WRITE(emu, 0);
	EMU8000_SMARR_WRITE(emu, 0);
	EMU8000_SMALW_WRITE(emu, 0);
	EMU8000_SMARW_WRITE(emu, 0);
}

/* initialization arrays; from ADIP */
static unsigned short init1[128] = {
	0x03ff, 0x0030,  0x07ff, 0x0130, 0x0bff, 0x0230,  0x0fff, 0x0330,
	0x13ff, 0x0430,  0x17ff, 0x0530, 0x1bff, 0x0630,  0x1fff, 0x0730,
	0x23ff, 0x0830,  0x27ff, 0x0930, 0x2bff, 0x0a30,  0x2fff, 0x0b30,
	0x33ff, 0x0c30,  0x37ff, 0x0d30, 0x3bff, 0x0e30,  0x3fff, 0x0f30,

	0x43ff, 0x0030,  0x47ff, 0x0130, 0x4bff, 0x0230,  0x4fff, 0x0330,
	0x53ff, 0x0430,  0x57ff, 0x0530, 0x5bff, 0x0630,  0x5fff, 0x0730,
	0x63ff, 0x0830,  0x67ff, 0x0930, 0x6bff, 0x0a30,  0x6fff, 0x0b30,
	0x73ff, 0x0c30,  0x77ff, 0x0d30, 0x7bff, 0x0e30,  0x7fff, 0x0f30,

	0x83ff, 0x0030,  0x87ff, 0x0130, 0x8bff, 0x0230,  0x8fff, 0x0330,
	0x93ff, 0x0430,  0x97ff, 0x0530, 0x9bff, 0x0630,  0x9fff, 0x0730,
	0xa3ff, 0x0830,  0xa7ff, 0x0930, 0xabff, 0x0a30,  0xafff, 0x0b30,
	0xb3ff, 0x0c30,  0xb7ff, 0x0d30, 0xbbff, 0x0e30,  0xbfff, 0x0f30,

	0xc3ff, 0x0030,  0xc7ff, 0x0130, 0xcbff, 0x0230,  0xcfff, 0x0330,
	0xd3ff, 0x0430,  0xd7ff, 0x0530, 0xdbff, 0x0630,  0xdfff, 0x0730,
	0xe3ff, 0x0830,  0xe7ff, 0x0930, 0xebff, 0x0a30,  0xefff, 0x0b30,
	0xf3ff, 0x0c30,  0xf7ff, 0x0d30, 0xfbff, 0x0e30,  0xffff, 0x0f30,
};

static unsigned short init2[128] = {
	0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330,
	0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730,
	0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30,
	0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30,

	0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330,
	0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730,
	0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30,
	0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30,

	0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330,
	0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730,
	0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30,
	0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30,

	0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330,
	0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730,
	0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30,
	0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30,
};

static unsigned short init3[128] = {
	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254,
	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234,
	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224,

	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254,
	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264,
	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294,
	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3,

	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287,
	0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7,
	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386,
	0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55,

	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308,
	0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F,
	0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319,
	0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570,
};

static unsigned short init4[128] = {
	0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5,
	0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254,
	0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234,
	0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224,

	0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254,
	0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264,
	0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294,
	0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3,

	0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287,
	0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7,
	0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386,
	0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55,

	0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308,
	0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F,
	0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319,
	0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570,
};


/*
 * Send initialization arrays to start up, this just follows the
 * initialisation sequence in the adip.
 */
static void
init_arrays(emu8000_t *emu)
{
	send_array(emu, init1, NELEM(init1)/4);

	snd_emu8000_wait(emu, 1024);

	send_array(emu, init2, NELEM(init2)/4);
	send_array(emu, init3, NELEM(init3)/4);

	EMU8000_HWCF4_WRITE(emu, 0);
	EMU8000_HWCF5_WRITE(emu, 0x83);
	EMU8000_HWCF6_WRITE(emu, 0x8000);

	send_array(emu, init4, NELEM(init4)/4);
}

/* send an initialization array
 * Taken from the oss driver, not obvious from the doc how this
 * is meant to work
 */
static void
send_array(emu8000_t *emu, unsigned short *data, int size)
{
	int i;
	unsigned short *p;

	p = data;
	for (i = 0; i < size; i++, p++)
		EMU8000_INIT1_WRITE(emu, i, *p);
	for (i = 0; i < size; i++, p++)
		EMU8000_INIT2_WRITE(emu, i, *p);
	for (i = 0; i < size; i++, p++)
		EMU8000_INIT3_WRITE(emu, i, *p);
	for (i = 0; i < size; i++, p++)
		EMU8000_INIT4_WRITE(emu, i, *p);
}

#define UNIQUE_ID1	0xa5b9
#define UNIQUE_ID2	0x9d53

/*
 * Size the onboard memory.
 * This is written so as not to need arbitary delays after the write. It
 * seems that the only way to do this is to use the one channel and keep
 * reallocating between read and write.
 */
static void
size_dram(emu8000_t *emu)
{
	int  size;

	if (emu->mem_size >= 0) /* already initialized */
		return;

	size = 0;

	/* write out a magic number */
	snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);
	snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ);
	EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET);
	EMU8000_SMLD_WRITE(emu, UNIQUE_ID1);
	snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */

	while (size < EMU8000_MAX_DRAM) {

		size += 512 * 1024;  /* increment 512kbytes */

		/* Write a unique data on the test address.
		 * if the address is out of range, the data is written on
		 * 0x200000(=EMU8000_DRAM_OFFSET).  Then the id word is
		 * changed by this data.
		 */
		//snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);
		EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
		EMU8000_SMLD_WRITE(emu, UNIQUE_ID2);
		snd_emu8000_write_wait(emu);

		/*
		 * read the data on the just written DRAM address
		 * if not the same then we have reached the end of ram.
		 */
		//snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);
		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1));
		//snd_emu8000_read_wait(emu);
		EMU8000_SMLD_READ(emu); /* discard stale data  */
		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2)
			break; /* we must have wrapped around */

		snd_emu8000_read_wait(emu);

		/*
		 * If it is the same it could be that the address just
		 * wraps back to the beginning; so check to see if the
		 * initial value has been overwritten.
		 */
		EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET);
		EMU8000_SMLD_READ(emu); /* discard stale data  */
		if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1)
			break; /* we must have wrapped around */
		snd_emu8000_read_wait(emu);
	}
	snd_emu8000_close_dma(emu);

	snd_printd("EMU8000: %d Kbytes memory detected\n", size/1024);

	emu->mem_size = size;
}

static void
init_channels(emu8000_t *emu)
{
	emu8000_voice_t *vp;
	int  ch;

	for (ch = 0; ch < emu->max_channels; ch++) {
		vp = &emu->voices[ch];

		vp->ch = ch;
		vp->state = EMU8000_ST_OFF;
		vp->time = 0;

		/* XXX check if up to date */
	}
}


/*
 * Initiailise the FM section.  You have to do this to use sample RAM
 * and therefore lose 2 voices.
 */
void
snd_emu8000_init_fm(emu8000_t *emu)
{
	unsigned long flags;

	/* Initialize the last two channels for DRAM refresh and producing
	   the reverb and chorus effects for Yamaha OPL-3 synthesizer */

	/* 31: FM left channel, 0xffffe0-0xffffe8 */
	EMU8000_DCYSUSV_WRITE(emu, 30, 0x80);
	EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */
	EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (DEF_FM_CHORUS_DEPTH << 24));
	EMU8000_PTRX_WRITE(emu, 30, (DEF_FM_REVERB_DEPTH << 8));
	EMU8000_CPF_WRITE(emu, 30, 0);
	EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3);

	/* 32: FM right channel, 0xfffff0-0xfffff8 */
	EMU8000_DCYSUSV_WRITE(emu, 31, 0x80);
	EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */
	EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (DEF_FM_CHORUS_DEPTH << 24));
	EMU8000_PTRX_WRITE(emu, 31, (DEF_FM_REVERB_DEPTH << 8));
	EMU8000_CPF_WRITE(emu, 31, 0x8000);
	EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3);

	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0);

	snd_spin_lock(emu, reg, &flags);
	while (!(inw(EMU8000_PTR(emu)) & 0x1000))
		;
	while ((inw(EMU8000_PTR(emu)) & 0x1000))
		;
	snd_spin_unlock(emu, reg, &flags);
	snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828);
	/* this is really odd part.. */
	outb(0x3C, EMU8000_PTR(emu));
	outb(0, EMU8000_DATA1(emu));

	/* skew volume & cutoff */
	EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF);
	EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF);

	emu->voices[30].state = EMU8000_ST_FM;
	emu->voices[31].state = EMU8000_ST_FM;

	/* change maximum channels to 30 */
	emu->max_voices = emu->max_channels - 2;
}

/*
 * set the envelope & LFO parameters to the default values
 */
void
snd_emu8000_tweak_voice(emu8000_t *emu, int i)
{
	/* set all mod/vol envelope shape to minimum */
	EMU8000_ENVVOL_WRITE(emu, i, 0x8000);
	EMU8000_ENVVAL_WRITE(emu, i, 0x8000);
	EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F);
	EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F);
	EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F);
	EMU8000_PEFE_WRITE(emu, i, 0);  /* mod envelope height to zero */
	EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */
	EMU8000_LFO2VAL_WRITE(emu, i, 0x8000);
	EMU8000_IP_WRITE(emu, i, 0xE000);	/* no pitch shift */
	EMU8000_IFATN_WRITE(emu, i, 0xFF00);	/* volume to minimum */
	EMU8000_FMMOD_WRITE(emu, i, 0);
	EMU8000_TREMFRQ_WRITE(emu, i, 0);
	EMU8000_FM2FRQ2_WRITE(emu, i, 0);
}

static void
tweak_voices(emu8000_t *emu)
{
	int i;

	/* reset all channels */
	for (i = 0; i < emu->max_voices; i++)
		snd_emu8000_tweak_voice(emu, i);
}
