/*
 *  Patch 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    various fixes and improvements.
 */
/*
 * All the code for loading in a patch.  There is very little that is
 * chip specific here.  Just the actual writing to the board.
 */

#include "../../include/driver.h"
#include "../../include/emu8000.h"
#include "../../include/emu8000_reg.h"
#include "emu8000_port.h"
#ifndef ALSA_BUILD
#include <linux/asequencer.h>
#else
#include "../../include/asequencer.h"
#endif
#include "../../include/soundfont.h"

MODULE_PARM(emu8000_reset_addr, "i");
MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)");

int emu8000_reset_addr = 0;

#define BLANK_LOOP_START	4
#define BLANK_LOOP_END		8
#define BLANK_LOOP_SIZE		12
#define BLANK_HEAD_SIZE		48

/*
 * Read a word from userland, taking care of conversions from
 * 8bit samples etc.
 */
static unsigned short
read_word(const void *buf, int offset, int mode)
{
	unsigned short c;
	if (mode & SND_SFNT_SAMPLE_8BITS) {
		unsigned char cc;
		get_user(cc, (unsigned char*)buf + offset);
		c = cc << 8; /* convert 8bit -> 16bit */
	} else {
		get_user(c, (unsigned short*)buf + offset);
	}
	if (mode & SND_SFNT_SAMPLE_UNSIGNED)
		c ^= 0x8000; /* unsigned -> signed */
	return c;
}

/*
 * write sample word data
 *
 * You should not have to keep resetting the address each time
 * as the chip is supposed to step on the next address automatically.
 * It mostly does, but during writes of some samples at random it
 * completely loses words (every one in 16 roughly but with no
 * obvious pattern).
 *
 * This is therefore much slower than need be, but is at least
 * working.
 */
inline static void
write_word(emu8000_t *emu, int *offset, unsigned short data)
{
	if (emu8000_reset_addr) {
		if (emu8000_reset_addr > 1)
			snd_emu8000_write_wait(emu);
		EMU8000_SMALW_WRITE(emu, *offset);
	}
	EMU8000_SMLD_WRITE(emu, data);
	*offset += 1;
}

/*
 * Write the sample to EMU800 memory.  This routine is invoked out of
 * the generic soundfont routines as a callback.
 */
int
snd_emu8000_sample_write(void *private, snd_sf_sample_t *sp, const void *data,
			 long count)
{
	int  i;
	int  rc;
	int  offset;
	int  truesize;
	int  dram_offset, dram_start;
	emu8000_t *emu;

	emu = private;
	if (emu == NULL)
		return -EINVAL;

	if (sp->v.size == 0)
		return 0;

	/* be sure loop points start < end */
	if (sp->v.loopstart > sp->v.loopend) {
		int tmp = sp->v.loopstart;
		sp->v.loopstart = sp->v.loopend;
		sp->v.loopend = tmp;
	}

	/* compute true data size to be loaded */
	truesize = sp->v.size;
	if (sp->v.mode_flags & SND_SFNT_SAMPLE_BIDIR_LOOP)
		truesize += sp->v.loopend - sp->v.loopstart;
	if (sp->v.mode_flags & SND_SFNT_SAMPLE_NO_BLANK)
		truesize += BLANK_LOOP_SIZE;
	if (sp->mem_offset + truesize >= emu->mem_size/2) {
		snd_printd("EMU8000: out of memory for sample off %x, truesize=%x size=%x\n", sp->mem_offset, truesize, emu->mem_size);
		/* not ENOMEM (for compatibility) */
		return -ENOSPC;
	}

	if (sp->v.mode_flags & SND_SFNT_SAMPLE_8BITS) {
		if (verify_area(VERIFY_READ, data, sp->v.size))
			return -EFAULT;
	} else {
		if (verify_area(VERIFY_READ, data, sp->v.size * 2))
			return -EFAULT;
	}

	/* recalculate address offset */
	sp->v.end -= sp->v.start;
	sp->v.loopstart -= sp->v.start;
	sp->v.loopend -= sp->v.start;
	sp->v.start = 0;

	/* dram position */
	dram_offset = EMU8000_DRAM_OFFSET + sp->mem_offset;
	dram_start = dram_offset;

	/* set the total size (store onto obsolete checksum value) */
	sp->v.truesize = truesize;

	snd_emu8000_terminate_all(emu);
	if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0)
		return rc;

	/* Set the address to start writing at */
	snd_emu8000_write_wait(emu);
	EMU8000_SMALW_WRITE(emu, dram_offset);

	/*snd_emu8000_init_fm(emu);*/

	/* first block - write 48 samples for silence */
	if (! sp->mem_offset) {
		for (i = 0; i < BLANK_HEAD_SIZE; i++) {
			write_word(emu, &dram_offset, 0);
		}
	}

	offset = 0;
	for (i = 0; i < sp->v.size; i++) {
		unsigned short s;

		s = read_word(data, offset, sp->v.mode_flags);
		offset++;
		write_word(emu, &dram_offset, s);

		if (i == sp->v.loopend &&
		    (sp->v.mode_flags & (SND_SFNT_SAMPLE_BIDIR_LOOP|SND_SFNT_SAMPLE_REVERSE_LOOP)))
		{
			int looplen = sp->v.loopend - sp->v.loopstart;
			int k;

			/* copy reverse loop */
			for (k = 1; k <= looplen; k++) {
				s = read_word(data, offset - k, sp->v.mode_flags);
				write_word(emu, &dram_offset, s);
			}
			if (sp->v.mode_flags & SND_SFNT_SAMPLE_BIDIR_LOOP) {
				sp->v.loopend += looplen;
			} else {
				sp->v.loopstart += looplen;
				sp->v.loopend += looplen;
			}
			sp->v.end += looplen;
		}
	}

	/* if no blank loop is attached in the sample, add it */
	if (sp->v.mode_flags & SND_SFNT_SAMPLE_NO_BLANK) {
		for (i = 0; i < BLANK_LOOP_SIZE; i++) {
			write_word(emu, &dram_offset, 0);
		}
		if (sp->v.mode_flags & SND_SFNT_SAMPLE_SINGLESHOT) {
			sp->v.loopstart = sp->v.end + BLANK_LOOP_START;
			sp->v.loopend = sp->v.end + BLANK_LOOP_END;
		}
	}

	/* add dram offset */
	sp->v.start += dram_start;
	sp->v.end += dram_start;
	sp->v.loopstart += dram_start;
	sp->v.loopend += dram_start;

	sp->mem_offset = dram_offset - EMU8000_DRAM_OFFSET;

	snd_emu8000_close_dma(emu);
	snd_emu8000_init_fm(emu);

	return count;
}

/*
 * Store a patch to the emu8000.
 */
int
snd_emu8000_patch(emu8000_t *emu, const void *data, long count)
{
	return snd_soundfont_load(emu->sflist, data, count, emu->client);
}
