/*********************************************************************
 *      driver_access.c - functions needed to r/w to the driver
 *      Copyright (C) 2000-2001 Rui Sousa 
 ********************************************************************* 
 *     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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <errno.h>
#include <fcntl.h>

#include "../include/dsp.h"
#include "../include/list.h"

#include "../include/internal.h"

/*
Theory of operation:

Patch information is stored in the driver as per the kdsp_patch struct,
the routing patch as per dsp_kernel_rpatch, and gpr usage information as
per dsp_kernel_gpr.

All other information must be re-constructed from the state of the emu10k1
itself.

dsp_init() is called to create user space structures and linked list containing
the state of the emu10k1.


 */


extern __u32 addr_change[0x400];

int get_driver_version( int mixer_fd)
{
	struct mixer_private_ioctl ctl;
	int ret;
	
	ctl.cmd = CMD_GETGPR2OSS;
	ctl.val[0] = 0;
	ctl.val[1] = 0;

	ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
	if (ret < 0) 
		// this is a very old driver (<0.14)
		return ret;

	ctl.cmd = CMD_PRIVATE3_VERSION;
	ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
	if (ret < 0) {
		// this is versions 0.14 or 0.15
		return 0;
	}

	//it's a newer driver with a private3_version number
	return ctl.val[0];
}
int dsp_get_info (struct dsp_patch_manager *mgr)
{

	struct mixer_private_ioctl ctl;

	ctl.cmd = CMD_PRIVATE3_VERSION;

	if (ioctl(mgr->mixer_fd, SOUND_MIXER_PRIVATE3, &ctl) < 0) {
		perror("SOUND_MIXER_PRIVATE3");
		return -1;
	}
	mgr->card_info.private_version=ctl.val[0];
	mgr->card_info.major_ver=ctl.val[1];
	mgr->card_info.minor_ver=ctl.val[2];
	mgr->card_info.card_type=ctl.val[3];
	mgr->card_info.iocfg=ctl.val[4];

	return 0;
}
int dsp_load_reg(int offset, __u32 value, struct  dsp_patch_manager *mgr)
{
	copr_buffer buf;
	int ret;

	if((offset<0x000 || offset>=0x1000 ) && offset != 0x52)
		return -EINVAL;


	buf.command = CMD_WRITE;
	buf.offs =  offset;
	buf.len = 1;

	memcpy(buf.data, &value,  sizeof(__u32));

	ret = ioctl(mgr->dsp_fd, SNDCTL_COPR_LOAD, &buf);
	return ret;
}



//we check each patch and see wether they use io gpr0 or gpr1
static void determine_io_gprs(struct dsp_patch_manager *mgr)
{
	struct dsp_patch *patch;
	struct list_head *entry;



	list_for_each(entry, &mgr->ipatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);
		determine_io(mgr, patch);
	}

	
	list_for_each(entry, &mgr->opatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);
		determine_io(mgr, patch);
	}
}

static void match_io(struct dsp_patch_manager *mgr, struct dsp_patch *patch, int *in, int gpr0, int gpr1, int i)
{

#ifdef DEBUG	
	printf("entering match_io() patch->name=%s \n\tpatch->in_gprs[i]=%x, out_gprs[i]=%x\n",patch->name,patch->in_gprs[i],patch->out_gprs[i]);
	printf("\tgpr0=%x, gpr1=%x, in=%x\n",gpr0,gpr1,*in);
#endif
	if (patch->in_gprs[i] != *in) {

		if (patch->in_gprs[i] == -1 && patch->out_gprs[i] == -1)
			return;

		
		/* match the input */
		addr_change[patch->in_gprs[i]] = *in;

		if (patch->in_gprs[i] >= mgr->gpr_base)
			clear_bit(patch->in_gprs[i] - mgr->gpr_base, patch->gpr_input);

		if (*in >= mgr->gpr_base)
			set_bit(*in - mgr->gpr_base, patch->gpr_input);

		if (patch->out_gprs[i] == *in) {
#ifdef DEBUG
			printf("\tNeed to swap io gprs\n");
#endif
			/* the output and input are now swapped */
			if (patch->in_gprs[i] < mgr->gpr_base) {
				/* the input was not an io gpr */
				if (*in == mgr->gpr_base + gpr1)
					patch->in_gprs[i] = mgr->gpr_base + gpr0;
				else
					patch->in_gprs[i] = mgr->gpr_base + gpr1;

				get_gpr(mgr, patch->in_gprs[i] - mgr->gpr_base, patch->gpr_used);

				addr_change[patch->out_gprs[i]] = patch->in_gprs[i];
			
				*in = patch->in_gprs[i];
			}else{
				
			addr_change[patch->out_gprs[i]] = patch->in_gprs[i];
			*in = patch->in_gprs[i];
			patch->in_gprs[i]=patch->out_gprs[i];
			patch->out_gprs[i]=*in;	
			}
			
		} else {

			if (patch->in_gprs[i] >= mgr->gpr_base)
				free_gpr(mgr, patch->in_gprs[i] - mgr->gpr_base, patch->gpr_used);

			if (*in >= mgr->gpr_base)
				get_gpr(mgr, *in - mgr->gpr_base, patch->gpr_used);

			*in = patch->out_gprs[i];
		}
	} else {

		if (patch->out_gprs[i] != -1){	
			/* already matched */

			*in = patch->out_gprs[i];
		}else{

		}
		
		
	}
#ifdef DEBUG	
	printf("  exiting match_io(),\n\tpatch->in_gprs[i]=%x, out_gprs[i]=%x\n",patch->in_gprs[i],patch->out_gprs[i]);
	printf("\tgpr0=%x, gpr1=%x, in=%x\n",gpr0,gpr1,*in);
#endif
}


//FIXME: this needs to be changed to support cases were the
//input and output lines are unequal.
static void set_io_gprs(struct dsp_patch_manager *mgr)
{
	struct dsp_patch *patch,*last_patch;
	struct list_head *entry;
	int in, i, gpr0, gpr1;
#ifdef DEBUG
	printf("entering set_io_gprs()\n");
#endif
	for (i = 0; i < DSP_NUM_INPUTS; i++) {
		in = dsp_in_line_to_reg(mgr,i);

		gpr0 = mgr->io[i][0];
		gpr1 = mgr->io[i][1];

		list_for_each(entry, &mgr->ipatch_list) {
			patch = list_entry(entry, struct dsp_patch, list);
			if(patch_uses_input(patch,i)){

				init_addr_change_table(addr_change, 0, mgr->gpr_base + GPR_SIZE);

				match_io(mgr, patch, &in, gpr0, gpr1, i);
				
				search_and_replace(mgr,patch->code, patch->code_size, addr_change, 0, mgr->gpr_base + GPR_SIZE);
			}
		}			
		mgr->rpatch.in[i] = in;
	}

	construct_routing(mgr);

	for (i = 0; i < DSP_NUM_OUTPUTS; i++) {
		in =  mgr->rpatch.out[i];

		
		gpr0 = mgr->io[i][0];
		gpr1 = mgr->io[i][1];
		
		last_patch=0;
		list_for_each(entry, &mgr->opatch_list) {
			patch = list_entry(entry, struct dsp_patch, list);
			if(patch_uses_output(patch,i)){

				init_addr_change_table(addr_change, 0, mgr->gpr_base + GPR_SIZE);

				match_io(mgr, patch, &in, gpr0, gpr1,i);

				search_and_replace(mgr,patch->code, patch->code_size, addr_change, 0, mgr->gpr_base + GPR_SIZE);
				last_patch=patch;
				
			}
		}
		

		if (last_patch!=0) {
			/* last patch in the line */
			init_addr_change_table(addr_change, 0, mgr->gpr_base + GPR_SIZE);

			addr_change[last_patch->out_gprs[i]] = dsp_out_line_to_reg(mgr, i);
			if (last_patch->out_gprs[i] >= mgr->gpr_base)
				free_gpr(mgr, last_patch->out_gprs[i] - mgr->gpr_base, last_patch->gpr_used);
			search_and_replace(mgr, last_patch->code, last_patch->code_size, addr_change, 0, mgr->gpr_base + GPR_SIZE);
		}
	}
#ifdef DEBUG
	printf("exiting set_io_gprs()\n");
#endif
}


static void set_code_start(struct dsp_patch_manager *mgr)
{
	struct dsp_patch *patch;
	struct list_head *entry;
	int code_start;


	code_start = 0;

	
	list_for_each(entry, &mgr->ipatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);
		patch->code_start = code_start;
		code_start += patch->code_size;
	}

	mgr->rpatch.code_start = code_start;
	code_start += mgr->rpatch.code_size;

	
	list_for_each(entry, &mgr->opatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);
		patch->code_start = code_start;
		code_start += patch->code_size;
	}
}

// returns instruction # if the code contains a particular address (val), else returns -1
static  int search_code2(struct dsp_patch *patch, int val)
{
	int i;

	for (i = 0; i < patch->code_size / 2; i++)
		if (((patch->code[i * 2 + 1] >> 10) & 0x3ff) == val ||
		    (patch->code[i * 2 + 1] & 0x3ff) == val ||
		    ((patch->code[i * 2] >> 10) & 0x3ff) == val || (patch->code[i * 2] & 0x3ff) == val)
			return i;
	
	return -1;
}

//FIXME: still need verify align bit for reads
static int set_tram_align(struct dsp_patch_manager *mgr, struct dsp_patch *patch)
{
	int instr, tram_buff, j;
	for (j = 0; j < patch->traml_isize; j++) {
		tram_buff=j + patch->traml_istart ;
		if ((instr = search_code2(patch, tram_buff+TRAML_IDATA_BASE)) == -1)
			//we shouln't be in here
			continue;

		instr+=patch->code_start/2;
		if( (patch->traml_iaddr[j]&TANKMEMADDRREG_WRITE ) && ((instr +2 )>(3 * tram_buff)) )
			patch->traml_iaddr[j] |= TANKMEMADDRREG_ALIGN;
		else if( (patch->traml_iaddr[j]&TANKMEMADDRREG_READ ) && ((instr)<(3 * tram_buff)) )
			patch->traml_iaddr[j] |= TANKMEMADDRREG_ALIGN;
		else
			patch->traml_iaddr[j] &= ~TANKMEMADDRREG_ALIGN;
	}

	for (j = 0; j < patch->traml_esize; j++) {
		tram_buff=j + patch->traml_estart ;
		if ((instr = search_code2(patch, tram_buff+TRAML_EDATA_BASE)) == -1)
			continue;
		
		instr+=patch->code_start/2;
		if( (patch->traml_eaddr[j]&TANKMEMADDRREG_WRITE ) && ((instr +2 )>(4 * tram_buff)) )
			patch->traml_eaddr[j] |= TANKMEMADDRREG_ALIGN;
		if( (patch->traml_eaddr[j]&TANKMEMADDRREG_WRITE ) && ((instr - 0x17e )>(4 * tram_buff)) )
			patch->traml_eaddr[j] |= TANKMEMADDRREG_ALIGN;

		else
			patch->traml_eaddr[j] &= ~TANKMEMADDRREG_ALIGN;
	}
	return 0;
}




static void set_tram_addr(struct dsp_patch_manager *mgr, struct dsp_patch *patch)
{
	int j;

	/* keep internal block in internal tram */
	for (j = 0; j < patch->traml_isize; j++) {
		addr_change[patch->traml_istart + j] = TRAML_IDATA_BASE + mgr->traml_ifree_start + j;
		
		addr_change[TRAML_IADDR_BASE - TRAML_IDATA_BASE + patch->traml_istart + j] =
			TRAML_IADDR_BASE + mgr->traml_ifree_start + j;

		patch->traml_iaddr[j] += mgr->tramb_ifree_start - patch->tramb_istart;
	}
	
	patch->tramb_istart = mgr->tramb_ifree_start;
	patch->traml_istart = mgr->traml_ifree_start;

	/* keep external block in external tram */
	for (j = 0; j < patch->traml_esize; j++) {
		addr_change[TRAML_EDATA_BASE - TRAML_IDATA_BASE + patch->traml_estart + j] =
			TRAML_EDATA_BASE + mgr->traml_efree_start + j;

		addr_change[TRAML_EADDR_BASE - TRAML_IDATA_BASE + patch->traml_estart + j] =
			TRAML_EADDR_BASE + mgr->traml_efree_start + j;

		patch->traml_eaddr[j] += mgr->tramb_efree_start - patch->tramb_estart;
	}

	patch->tramb_estart = mgr->tramb_efree_start;
	patch->traml_estart = mgr->traml_efree_start;

	
	search_and_replace(mgr, patch->code, patch->code_size, addr_change, TRAML_IDATA_BASE,
			   TRAML_EADDR_BASE + TRAML_ESIZE);


	mgr->tramb_ifree_start += patch->tramb_isize;
	mgr->traml_ifree_start += patch->traml_isize;
	mgr->tramb_efree_start += patch->tramb_esize;
	mgr->traml_efree_start += patch->traml_esize;


	return;
	
}


static void set_tram(struct dsp_patch_manager *mgr)
{
	struct dsp_patch *patch;
	struct list_head *entry;

	mgr->traml_ifree_start = 0;
	mgr->traml_efree_start = 0;

	mgr->tramb_ifree_start = 0;
	mgr->tramb_efree_start = 0;



	list_for_each(entry, &mgr->ipatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);
		set_tram_addr(mgr, patch);
		set_tram_align(mgr,patch);
	}

	
	list_for_each(entry, &mgr->opatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);
		set_tram_addr(mgr, patch);
		set_tram_align(mgr,patch);
	}
}
static int load_patch_code(struct dsp_patch_manager *mgr, __u32 * code, __u16 start, __u16 size, int fd)
{
	copr_buffer buf;
	int ret;

	
	buf.command = CMD_WRITE;
		buf.offs = mgr->code_base + start;	

	buf.len = size < 0x200 ? size : 0x200;

	memcpy(buf.data, code, buf.len * sizeof(__u32));

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	if (buf.len < size) {
			buf.offs = mgr->code_base + start + buf.len;

		buf.len = size - buf.len;

		memcpy(buf.data, &code[start+buf.len], buf.len * sizeof(__u32));

		ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
		if (ret < 0) {
			perror("SNDCTL_COPR_LOAD");
			return ret;
		}
	}

	return 0;
}
static int load_gprs(struct dsp_patch_manager *mgr, int audio_fd, int mixer_fd)
{
	copr_buffer buf;
	struct mixer_private_ioctl ctl;
	int i, j, ret;
	int vol_gprs[SOUND_MIXER_NRDEVICES][2];

	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
	{
		vol_gprs[i][0] = -1;
		vol_gprs[i][1] = -1;
	}

	ctl.cmd = CMD_SETGPR;

	buf.command = CMD_WRITE;
	buf.len = 1;

	for (i = 0; i < DSP_NUM_GPRS; i++) {
		ctl.val[0] = i;
		memcpy(&ctl.val[1], &mgr->gpr[i], sizeof(struct dsp_kernel_gpr));

		ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
		if (ret < 0) {
			perror("SOUND_MIXER_PRIVATE3");
			return ret;
		}

		buf.offs = mgr->gpr_base + i;

		((__u32 *) buf.data)[0] = mgr->gpr[i].value;

		ret = ioctl(audio_fd, SNDCTL_COPR_LOAD, &buf);
		if (ret < 0) {
			perror("SNDCTL_COPR_LOAD");
			return ret;
		}

		if ((mgr->gpr[i].type == GPR_TYPE_CONTROL) &&
		    (mgr->gpr[i].mixer_id < SOUND_MIXER_NRDEVICES))
		{
			vol_gprs[mgr->gpr[i].mixer_id][mgr->gpr[i].mixer_ch] = i;
		}
	}

	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
	{
		for (j = 0; j < 2; j++)
		{
			ctl.cmd = CMD_SETGPR2OSS;
			ctl.val[0] = i;
			ctl.val[1] = j;
			ctl.val[2] = vol_gprs[i][j];

			ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
			if (ret < 0) {
				perror("SOUND_MIXER_PRIVATE3");
				return ret;
			}
		}
	}

	return 0;
}

static int load_patch_tramls(struct dsp_patch *patch, int fd)
{
	copr_buffer buf;
	int ret;

	buf.command = CMD_WRITE;
	buf.offs = TRAML_IADDR_BASE + patch->traml_istart;
	buf.len = patch->traml_isize;

	memcpy(buf.data, patch->traml_iaddr, buf.len * sizeof(__u32));

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	buf.command = CMD_WRITE;
	buf.offs = TRAML_EADDR_BASE + patch->traml_estart;
	buf.len = patch->traml_esize;

	memcpy(buf.data, patch->traml_eaddr, buf.len * sizeof(__u32));

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	return 0;
}

static int clean_free_tram_buffers(struct dsp_patch_manager *mgr, int fd)
{
	copr_buffer buf;
	int i;
	int ret;

	
	for(i=0; i<TRAML_ISIZE;i++)
		((__u32 *)buf.data)[i]=0;

	//free internal tram buffers
	buf.command = CMD_WRITE;
	buf.offs =  TRAML_IADDR_BASE + mgr->traml_ifree_start;
	buf.len =  TRAML_ISIZE - mgr->traml_ifree_start;


	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	//free external tram buffers
	buf.offs =  TRAML_EADDR_BASE + mgr->traml_efree_start;
	buf.len =  TRAML_ESIZE-mgr->traml_efree_start;

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	return 0;
}

static int clean_free_dsp(struct dsp_patch_manager *mgr, int fd)
{
	copr_buffer buf;
	int i, size;
	int ret;

	size = mgr->icode_free_start - mgr->code_free_start;

	if (size <= 0)
		return 0;

	if(IS_EMU10K1)
		for (i = 0; i < (size < 1000 ? size : 1000) / 2; i++) {
			((__u32 *) buf.data)[2 * i] = 0x40 << 10 | 0x40;
			((__u32 *) buf.data)[2 * i + 1] = 6 << 20 | 0x40 << 10 | 0x40;
		}
	else
		//OP(0xf, 0x0c0, 0x0c0, 0x0cf, 0x0c0);
		for (i = 0; i < (size < 1000 ? size : 1000) / 2; i++) {
			((__u32 *) buf.data)[2 * i] = 0xcf << 12 | 0xc0;
			((__u32 *) buf.data)[2 * i + 1] = 0xf << 24 | 0xc0 << 12 | 0xc0;
		}


	buf.command = CMD_WRITE;
	buf.offs = mgr->code_base + mgr->code_free_start;
	buf.len = size < 0x200 ? size : 0x200;

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	if (buf.len < size) {
		buf.offs = mgr->code_base + mgr->code_free_start + buf.len;
		buf.len = size - buf.len;

		ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
		if (ret < 0) {
			perror("SNDCTL_COPR_LOAD");
			return ret;
		}
	}

	return 0;
}

static int load_patches(struct dsp_patch_manager *mgr, int audio_fd, int mixer_fd)
{
	struct mixer_private_ioctl ctl;
	struct list_head *entry;
	struct dsp_patch *patch;
	struct dsp_rpatch *rpatch;
	int id, ret;

#ifdef DEBUG
	printf("entering load_patches()\n");
#endif	

	set_io_gprs(mgr);
	
	set_code_start(mgr);
	set_tram(mgr);


	id = 0;

	ctl.cmd = CMD_SETPATCH;
	ctl.val[0] = id;

	/* routing patch */
	rpatch = &mgr->rpatch;

	memcpy(ctl.val + 1, rpatch, sizeof(struct dsp_kernel_rpatch));

	ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
	if (ret < 0) {
		perror("SOUND_MIXER_PRIVATE3");
		return ret;
	}

	load_patch_code(mgr, rpatch->code, rpatch->code_start, rpatch->code_size, audio_fd);


	/* FIXME */
	/* don't load a patch if it wasn't modified */

	/* input patches */


	list_for_each(entry, &mgr->ipatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);

		id++;
		ctl.val[0] = id;

		memcpy(ctl.val + 1, patch, sizeof(struct kdsp_patch));

		ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
		if (ret < 0) {
			perror("SOUND_MIXER_PRIVATE3");
			return ret;
		}

		ret = load_patch_code(mgr, patch->code, patch->code_start, patch->code_size, audio_fd);
		if (ret < 0)
			return ret;

		load_patch_tramls(patch, audio_fd);
	}

	/* output patches */


	list_for_each(entry, &mgr->opatch_list) {
		patch = list_entry(entry, struct dsp_patch, list);

		id++;
		ctl.val[0] = id;

		memcpy(ctl.val + 1, patch, sizeof(struct kdsp_patch));

		ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
		if (ret < 0) {
			perror("SOUND_MIXER_PRIVATE3");
			return ret;
		}

		ret = load_patch_code(mgr, patch->code, patch->code_start, patch->code_size, audio_fd);
		if (ret < 0)
			return ret;

		load_patch_tramls(patch, audio_fd);
	}

	/* delimiter patch */

	id++;
	ctl.val[0] = id;

	((struct dsp_patch *) (ctl.val + 1))->code_size = 0;

	ret = ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl);
	if (ret < 0) {
		perror("SOUND_MIXER_PRIVATE3");
		return ret;
	}

	clean_free_dsp(mgr, audio_fd);
	clean_free_tram_buffers(mgr, audio_fd);
#ifdef DEBUG
	printf("exiting load_patches()\n");
#endif	

	return 0;
}

static int set_external_tram(struct dsp_patch_manager *mgr, int fd)
{
	__u32 size;
	int ret;

	/* size here is in bytes */
	size = 2 * mgr->tramb_efree_start;

	if (!size)
		return 0;

	ret = ioctl(fd, SOUND_MIXER_PRIVATE4, &size);
	if (ret < 0) {
		perror("SOUND_MIXER_PRIVATE4");
		return ret;
	}

	return 0;
}


int dsp_load(struct dsp_patch_manager *mgr)
{

	int i;
	int ret;
#ifdef DEBUG
	printf("entering dsp_load()\n");
#endif	

	if (!mgr->init)
		return -1;

	ret = set_external_tram(mgr, mgr->mixer_fd);
	if (ret < 0)
		return ret;


	ret = load_patches(mgr, mgr->dsp_fd, mgr->mixer_fd);
	if (ret < 0)
		return ret;


	ret = load_gprs(mgr, mgr->dsp_fd, mgr->mixer_fd);
	if (ret < 0)
		return ret;


	/* release io and dynamic gprs of the routing patch */
	/* this is done here so that we can call dsp_load() several times */
	for (i = 0; i < DSP_NUM_GPRS; i++)
		if (test_bit(i, mgr->rpatch.gpr_used) &&
		    (mgr->gpr[i].type == GPR_TYPE_IO ||
		     mgr->gpr[i].type == GPR_TYPE_DYNAMIC)) free_gpr(mgr, i, mgr->rpatch.gpr_used);

#ifdef DEBUG
	printf("exiting dsp_load()\n");
#endif	

	return 0;

}
//generic code getting function, caller should have already malloc memory
static int get_code(struct dsp_patch_manager *mgr, __u32 *code, int start, int size, int audio_fd)
{
	copr_buffer buf;
	int ret;

	buf.command = CMD_READ;
	buf.offs = mgr->code_base + start;
	buf.len = size < 0x200 ? size : 0x200;

	ret = ioctl(audio_fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	
	memcpy(code, buf.data, buf.len * sizeof(__u32));

	if (size > buf.len) {
		buf.offs = mgr->gpr_base + start + buf.len;
		buf.len = size - buf.len;

		ret = ioctl(audio_fd, SNDCTL_COPR_LOAD, &buf);
		if (ret < 0) {
			perror("SNDCTL_COPR_LOAD");
			return ret;
		}

		memcpy(&(code[buf.len]), buf.data, buf.len * sizeof(__u32));
	}
	return 0;
}

static int get_patch_code(struct dsp_patch_manager *mgr, struct dsp_patch *patch, int fd)
{
	copr_buffer buf;
	int ret;

	patch->code = (__u32 *) malloc(patch->code_size * sizeof(__u32));
	if (patch->code == NULL) {
		perror("patch code");
		return -1;
	}

	buf.command = CMD_READ;
	buf.offs = mgr->code_base + patch->code_start;
	buf.len = patch->code_size < 0x200 ? patch->code_size : 0x200;

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	memcpy(patch->code, buf.data, buf.len * sizeof(__u32));

	if (patch->code_size > buf.len) {
		buf.offs = mgr->code_base + patch->code_start + buf.len;
		buf.len = patch->code_size - buf.len;

		ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
		if (ret < 0) {
			perror("SNDCTL_COPR_LOAD");
			return ret;
		}

		memcpy(&(patch->code)[buf.len], buf.data, buf.len * sizeof(__u32));
	}

	return 0;
}

static int get_patch_tramls(struct dsp_patch *patch, int fd)
{
	copr_buffer buf;
	int ret;

	patch->traml_iaddr = (__u32 *) malloc(patch->traml_isize * sizeof(__u32));

	if (patch->traml_iaddr == NULL) {
		perror("tram internal lines");
		return -1;
	}

	buf.command = CMD_READ;
	buf.offs = TRAML_IADDR_BASE + patch->traml_istart;
	buf.len = patch->traml_isize;

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	memcpy(patch->traml_iaddr, buf.data, buf.len * sizeof(__u32));

	patch->traml_eaddr = (__u32 *) malloc(patch->traml_esize * sizeof(__u32));

	if (patch->traml_eaddr == NULL) {
		perror("tram external lines");
		return -1;
	}

	buf.command = CMD_READ;
	buf.offs = TRAML_EADDR_BASE + patch->traml_estart;
	buf.len = patch->traml_esize;

	ret = ioctl(fd, SNDCTL_COPR_LOAD, &buf);
	if (ret < 0) {
		perror("SNDCTL_COPR_LOAD");
		return ret;
	}

	memcpy(patch->traml_eaddr, buf.data, buf.len * sizeof(__u32));

	return 0;
}

static int get_gprs(struct dsp_patch_manager *mgr, int audio_fd, int mixer_fd)
{
	copr_buffer buf;
	struct mixer_private_ioctl ctl;
	int i, j;

	for (i = 0; i < DSP_NUM_GPRS; i++) {
		ctl.cmd = CMD_GETGPR;
		ctl.val[0] = i;

		if (ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl) < 0) {
			perror("SOUND_MIXER_PRIVATE3");
			return -1;
		}

		memcpy(&mgr->gpr[i], &ctl, sizeof(struct dsp_kernel_gpr));

		buf.command = CMD_READ;
		buf.len = 1;
		buf.offs = mgr->gpr_base + i;

		if (ioctl(audio_fd, SNDCTL_COPR_LOAD, &buf) < 0) {
			perror("SNDCTL_COPR_LOAD");
			return -1;
		}

		mgr->gpr[i].value = ((__u32 *) buf.data)[0];
		mgr->gpr[i].addr = i;
		mgr->gpr[i].mixer_id = SOUND_MIXER_NONE;
	}

	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
	{
		for (j = 0; j < 2; j++)
		{
			ctl.cmd = CMD_GETGPR2OSS;
			ctl.val[0] = i;
			ctl.val[1] = j;

			if (ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl) < 0) {
				perror("SOUND_MIXER_PRIVATE3");
				return -1;
			}

			mgr->gpr[ctl.val[2]].mixer_id = i;
			mgr->gpr[ctl.val[2]].mixer_ch = j;
		}
	}

	return 0;
}

static int get_patches(struct dsp_patch_manager *mgr, int audio_fd, int mixer_fd)
{
	struct mixer_private_ioctl ctl;
	
	
	struct dsp_patch *patch;
	struct dsp_rpatch *rpatch;
	int i;
	
	/* input/ouput list initialization */
	
	INIT_LIST_HEAD(&mgr->ipatch_list);
	INIT_LIST_HEAD(&mgr->opatch_list);

	mgr->traml_efree_start = 0;
	mgr->traml_ifree_start = 0;

	mgr->tramb_efree_start = 0;
	mgr->tramb_ifree_start = 0;

	mgr->code_free_start = 0;

	ctl.cmd = CMD_GETPATCH;

	/* get routing patch, id is always 0 */
	ctl.val[0] = 0;
	
	if (ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl) < 0) {
			perror("SOUND_MIXER_PRIVATE3");
		return -1;
	}
	rpatch = &mgr->rpatch;
	
	memcpy(rpatch, &ctl, sizeof(struct dsp_kernel_rpatch));
	
	mgr->code_free_start += rpatch->code_size;

	/* always allocate extra space allowing the routing patch to grow later */
	rpatch->code = (__u32 *) malloc(DSP_CODE_SIZE * sizeof(__u32));
	if (rpatch->code == NULL) {
		perror("routing patch code");
		return -1;
	}
	
	get_code(mgr, rpatch->code, rpatch->code_start, rpatch->code_size,  audio_fd);

	/* get all other paches, stop when the code size is 0 */
	for (i = 1;; i++) {
		ctl.cmd = CMD_GETPATCH;
		ctl.val[0] = i;

		if (ioctl(mixer_fd, SOUND_MIXER_PRIVATE3, &ctl) < 0) {
			perror("SOUND_MIXER_PRIVATE3");
			return -1;
		}

		patch = (struct dsp_patch *) malloc(sizeof(struct dsp_patch));

		if (patch == NULL) {
			perror("patch");
			return -1;
		}

		memcpy(patch, &ctl, sizeof(struct kdsp_patch));

		if (patch->code_size == 0) {
			free(patch);
			break;
		}

		if (get_patch_code(mgr, patch, audio_fd) < 0)
			return -1;

		if (get_patch_tramls( patch, audio_fd) < 0)
			return -1;
		
		if (patch->code_start < rpatch->code_start)
			list_add_tail(&patch->list, &mgr->ipatch_list);
		else {
			list_add_tail(&patch->list, &mgr->opatch_list);

		}

		mgr->code_free_start += patch->code_size;

		mgr->traml_ifree_start += patch->traml_isize;
		mgr->traml_efree_start += patch->traml_esize;

		mgr->tramb_ifree_start += patch->tramb_isize;
		mgr->tramb_efree_start += patch->tramb_esize;

	}

	return 0;
}


/* get all the dsp code information from the driver */
int dsp_init(struct dsp_patch_manager *mgr)
{

	int ret;
#ifdef DEBUG
	printf("entering dsp_init()\n");
#endif	
	ret = get_driver_version( mgr->mixer_fd);

	if (ret < 0){
		perror("SOUND_MIXER_PRIVATE3: You're probably using an older incompatible driver");
		return ret;
	}
	else if(ret>PRIVATE3_VERSION)
		printf("WARNING: emu-dspmgr was compiled for an older driver and possibly incompatible driver\n");
	if(ret>1){	
		dsp_get_info(mgr);
	}

	mgr->gpr_base=IS_AUDIGY?A_GPR_BASE:GPR_BASE;
	mgr->code_base=IS_AUDIGY?A_DSP_CODE_BASE:DSP_CODE_BASE;

	//for now we'll limit to emu10k1 sizes
	mgr->gpr_size=GPR_SIZE;
	mgr->code_size=DSP_CODE_SIZE;
	
	ret = get_patches(mgr, mgr->dsp_fd, mgr->mixer_fd);

	if (ret < 0)
		return ret;

	mgr->icode_free_start = mgr->code_free_start;

	ret = get_gprs(mgr, mgr->dsp_fd, mgr->mixer_fd);
	if (ret < 0)
		return ret;

	
	init_io_gprs_table(mgr);
	determine_io_gprs(mgr);

	mgr->init = 1;
#ifdef DEBUG
	printf("exiting dsp_init()\n");
#endif	

	return 0;

}
