/*
 **********************************************************************
 *     dsp.c -- dsp patch manager 
 *     Copyright 2000 Rui Sousa 
 *
 **********************************************************************
 *
 *     Date                 Author          Summary of changes
 *     ----                 ------          ------------------
 *     May 1, 2000          Rui Sousa       initial non working draft 
 *
 **********************************************************************
 *
 *     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 <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <fcntl.h>
#include <unistd.h>
#include "dsp.h"

static struct patch_manager patch_mgr;

/*
int gpr_init(struct dsp_patch *patch, int start)
{
	int i, j;
	u32 operand[2];
	int gpr_table[GPR_SIZE];
	int size;

	if(start < 0x100 || start > 0x200)
		return -1;

	patch->gpr_start = start;
	patch->gpr_end = start;

	size = patch->dsp_end - patch->dsp_start;

	for(i = 0; i < size; i ++) {
          	operand[0] = (patch->code[i] >> 10) & 0x3ff;
          	operand[1] = patch->code[i] & 0x3ff;

		for(j = 0; j < 2; j++)
			if(operand[j] >= 0x100 && operand[j] <0x200) {
				if(gpr_table[operand[j] - 0x100] == 0) {
                                	gpr_table[operand[j] - 0x100] = patch->gpr_end;
					patch->gpr_end++;
                        	} 
				operand[j] = gpr_table[operand[j] - 0x100];
			}

		patch->code[i] &= 0xfff00000;
		patch->code[i] = operand[0] << 10 | operand[1];
	}

	return 0;
}

int gpr_set(struct dsp_patch *patch, int start)
{
        int i, j;
	u32 operand[2];
	int size, offset;

	if(start < 0x100 || start > 0x200)
                return -1;

	offset = start - patch->gpr_start;

	size = patch->gpr_end - patch->gpr_start;

	for(i = 0; i < size; i ++) {
                operand[0] = (patch->code[i] >> 10) & 0x3ff;
                operand[1] = patch->code[i] & 0x3ff;

                for(j = 0; j < 2; j++)
                        if(operand[j] >= 0x100 && operand[j] <0x200)
                                operand[j] += offset;

                patch->code[i] &= 0xfff00000;
                patch->code[i] = operand[0] << 10 | operand[1];
        }

        patch->gpr_start = start;

        return 0;
}
*/

static int verify_patch(struct dsp_patch *patch)
{
	int i, j;
	u32 operand[2];

	switch (patch->type) {
	case INPUT:
		if (patch->line_in[0] < 0 || patch->line_in[0] >= 0x1f)
			return 0;

		if (patch->gpr_in[0] < 0x100 || patch->gpr_in[0] >= 0x200)
			return 0;

		if (patch->gpr_out[0] < 0x100 || patch->gpr_out[0] >= 0x200)
			return 0;

		break;
	case OUTPUT:
		if (patch->line_out[0] < 0x20 || patch->line_out[0] >= 0x2f)
			return 0;

		if (patch->gpr_in[0] < 0x100 || patch->gpr_in[0] >= 0x200)
			return 0;

		if (patch->gpr_out[0] < 0x100 || patch->gpr_out[0] >= 0x200)
			return 0;

		break;
	case ROUTING:
		/* should also check that each line_in[] value occurs only once */

		for (i = 0; i < NUM_INPUTS && patch->line_in[i] >= 0; i++) {
			if (patch->line_in[i] < 0 || patch->line_in[i] >= 0x1f)
				return 0;

			if (patch->line_out[i] < 0x20 || patch->line_out[i] >= 0x2f)
				return 0;

			if (patch->gpr_in[i] < 0x100 || patch->gpr_in[i] >= 0x200)
				return 0;

			if (patch->gpr_out[i] < 0x100 || patch->gpr_out[i] >= 0x200)
				return 0;
		}

		break;
	default:
		return 0;
		break;
	}

	/* no reference to IN/OUT registers */

	for(i = 0; i < patch->size; i ++) {
                operand[0] = (patch->code[i] >> 10) & 0x3ff;
                operand[1] = patch->code[i] & 0x3ff;

                for(j = 0; j < 2; j++)
                        if(operand[j] < 0x30)
				return 0;
        }

	return 1;
}

static int set_gprs()
{
	return 0;
}

static int send_to_card()
{
	copr_buffer buf;
	u32 dsp_code[DSP_CODE_SIZE];
        int audio_fd;
	struct list_head *entry;
	struct dsp_patch *patch_tmp;

	list_for_each(entry, &patch_mgr.patches) {
		patch_tmp = list_entry(entry, struct dsp_patch, patch);
		memcpy(dsp_code + patch_tmp->start, patch_tmp->code, patch_tmp->size * sizeof(u32));
	}

	if ((audio_fd = open("/dev/dsp", O_WRONLY, 0)) == -1) {
                perror("open /dev/dsp");
                exit(EXIT_FAILURE);
        }

	buf.command = 2;
        buf.offs = 0x400;
        buf.len = 0x200;

	memcpy(buf.data, dsp_code, buf.len * sizeof(u32));

	if (ioctl(audio_fd, SNDCTL_COPR_LOAD, &buf) == -1)
                perror("SNDCTL_COPR_LOAD");

        buf.offs = 0x600;
	memcpy(buf.data, dsp_code + buf.len, buf.len * sizeof(u32));

	if (ioctl(audio_fd, SNDCTL_COPR_LOAD, &buf) == -1)
                perror("SNDCTL_COPR_LOAD");

	close(audio_fd);

	return 0;
}

static void set_addresses()
{
	struct dsp_patch *patch_tmp;
	struct list_head *entry;
	int address = 0;
	int i;

	for (i = 0; i < NUM_INPUTS; i++)
		list_for_each (entry, &patch_mgr.line_in[i]) {
			patch_tmp = list_entry(entry, struct dsp_patch, in);

			patch_tmp->start = address;
			address += patch_tmp->size;
		}

	for (i = 0; i < NUM_INPUTS; i++)
		list_for_each (entry, &patch_mgr.routing_in[i]) {
			patch_tmp = list_entry(entry, struct dsp_patch, in);

			/* each routing patch can be attached to more than one input line */
			if (patch_tmp->line_in[0] == i) {
				patch_tmp->start = address;
				address += patch_tmp->size;
			}
		}

	for (i = 0; i < NUM_OUTPUTS; i++)
		list_for_each (entry, &patch_mgr.line_out[i]) {
			patch_tmp = list_entry(entry, struct dsp_patch, out);

			patch_tmp->start = address;
			address += patch_tmp->size;
		}

	patch_mgr.free_space = DSP_CODE_SIZE - address;

	return;
}

struct dsp_patch *load_dsp_patch(struct dsp_patch *patch)
{
	struct dsp_patch *patch_tmp;
	int i;

	if (patch_mgr.free_space < patch->size)
		return (void *) NULL;

	if (!verify_patch(patch))
		return (void *) NULL;

	switch (patch->type) {
	case INPUT:
		list_add(&patch->in[0], &patch_mgr.line_in[patch->line_in[0]]);
		break;

	case OUTPUT:
		list_add(&patch->out[0], &patch_mgr.line_out[patch->line_out[0]]);
		break;

	case ROUTING:
		/* attach the routing patch to all input lines it manages */

		for (i = 0; i < NUM_INPUTS && patch->line_in[i] >= 0; i++)
			if (!list_empty(&patch_mgr.routing_in[patch->line_in[i]]))
				return (void *) NULL;

		for (i = 0; i < NUM_INPUTS && patch->line_in[i] >= 0; i++)
			list_add(&patch->in[i], &patch_mgr.routing_in[patch->line_in[i]]);

		for (i = 0; i < NUM_OUTPUTS && patch->line_out[i] >= 0; i++)
			list_add(&patch->out[i], &patch_mgr.routing_out[patch->line_out[i]]);

		break;
	default:
		return (void *) NULL;
		break;
	}

	set_addresses();
	set_gprs();

	patch_tmp = (struct dsp_patch *) malloc(sizeof(struct dsp_patch));
	memcpy(patch_tmp, patch, sizeof(struct dsp_patch));

	list_add(&patch->patch, &patch_mgr.patches);

	send_to_card();

	return patch_tmp;
}

void unload_dsp_patch(struct dsp_patch *patch)
{
	int i;

	list_del(&patch->patch);

	switch (patch->type) {
	case INPUT:
		list_del(&patch->in[0]);
		break;
	case OUTPUT:
		list_del(&patch->out[0]);
		break;
	case ROUTING:
		for (i = 0; i < NUM_INPUTS && patch->line_in[i] >= 0; i++)
			list_del(&patch->in[i]);

		for (i = 0; i < NUM_OUTPUTS && patch->line_out[i] >= 0; i++)
			list_del(&patch->out[i]);

		break;
	default:
		break;
	}

	set_addresses();
	set_gprs();

	send_to_card();

	return;
}
