
/*********************************************************************
 *	compiler.c - functions for the various dsp registers. 
 *      Copyright (C) 2000 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 <string.h>
#include "compiler.h"

/* error messages */

static void illegal(const char *message)
{
	if (__arg)
		fprintf(stderr, "%s:%d: %s, arg %d\n", __file, __line, message,
			__arg);
	else
		fprintf(stderr, "%s:%d: %s\n", __file, __line, message);

	exit(1);
	return;
}

void warning(const char *message)
{
	if (__arg)
		fprintf(stderr, "%s:%d: warning: %s, arg %d\n", __file, __line,
			message, __arg);
	else
		fprintf(stderr, "%s:%d: %s\n", __file, __line, message);

	return;
}

/* gprs */

void init_gpr_io(struct dsp_gpr_io **gpr)
{

	if ((*gpr = (struct dsp_gpr_io *) malloc(sizeof(struct dsp_gpr_io))) == NULL)
                illegal("not enough memory");

	__gpr_io_count++;

	(*gpr)->in.type = GPR | GPR_TYPE_IO;
	(*gpr)->out.type = GPR | GPR_TYPE_IO;

	(*gpr)->in.value = 0;
	(*gpr)->out.value = 0;

	(*gpr)->in.address = GPR_BASE + __gpr_total_count;
	__gpr_total_count++;

	(*gpr)->out.address = GPR_BASE + __gpr_total_count;
	__gpr_total_count++;
	list_add_tail(&((*gpr)->list), &__gpr_io_list);

	if (__gpr_total_count > GPR_TOTAL)
                illegal("too many GPRs");

}

void init_gpr(struct dsp_reg **gpr, __u32 type)
{
	struct list_head *list = NULL;
	char name[GPR_NAME_SIZE];

	if ((*gpr = (struct dsp_reg *) malloc(sizeof(struct dsp_reg))) == NULL)
		illegal("not enough memory");

	switch (type) {

	case GPR_TYPE_STATIC:
		list = &__gpr_static_list;
		__gpr_static_count++;
		break;

	case GPR_TYPE_DYNAMIC:
		list = &__gpr_dynamic_list;
		__gpr_dynamic_count++;
		break;

	case GPR_TYPE_CONTROL:
		sprintf(name, "ctl %d", __gpr_control_count);
		strncpy((*gpr)->name, name, GPR_NAME_SIZE);
		list = &__gpr_control_list;
		__gpr_control_count++;
		break;

	default:
		illegal("unknown GPR type");
	}

	(*gpr)->type = GPR | type;
	(*gpr)->address = GPR_BASE + __gpr_total_count;
	(*gpr)->value = 0;
	__gpr_total_count++;

	list_add_tail(&((*gpr)->list), list);

	if (__gpr_total_count > GPR_TOTAL)
		illegal("too many GPRs");

	return;
}

void init_tram_block(struct tram_block **block, __u32 type, __u32 size)
{
	struct list_head *list = NULL;

	*block = (struct tram_block *) malloc(sizeof(struct tram_block));
	if (*block == NULL) 
		illegal("not enough memory");

	switch (type) {

	case TRAMB_TYPE_DELAYLINE:
		list = &__tramb_delayline_list;
		__tramb_delayline_count++;
		break;
	case TRAMB_TYPE_LOOKUPTABLE:
		list = &__tramb_tablelookup_list;
		__tramb_tablelookup_count++;
		break;
	default:
		illegal("unknown TRAM BLOCK type");
	}

	INIT_LIST_HEAD(&(*block)->line_read_list);
	INIT_LIST_HEAD(&(*block)->line_write_list);

	(*block)->line_read_count = 0;
	(*block)->line_write_count = 0;

	(*block)->type = type;

	if (size > TRAMB_MAX_SIZE)
		illegal("TRAM BLOCK too big");

	if (size < TRAMB_MIN_SIZE)
		illegal("TRAM BLOCK too small");

	(*block)->size = size;

	list_add_tail(&(*block)->list, list);

	return;
}

void init_tram_line(struct tram_line **line, __u32 type, struct tram_block *block)
{
	struct list_head *list = NULL;

	if ((*line = (struct tram_line *) malloc(sizeof(struct tram_line))) ==
	    NULL) illegal("not enough memory");

	switch (type) {

	case TRAML_TYPE_READ:
		list = &block->line_read_list;
		block->line_read_count++;
		break;
	case TRAML_TYPE_WRITE:
		list = &block->line_write_list;
		block->line_write_count++;
		break;
	default:
		illegal("unknown TRAM LINE type");
	}

	(*line)->type = type;
	(*line)->block = block;
	list_add_tail(&(*line)->list, list);

	INIT_LIST_HEAD(&(*line)->address.list);
	(*line)->address.type = TRAML_ADDRESS | type;
	(*line)->address.value = 0;
	(*line)->address.address = TRAML_ADDR_BASE + __traml_total_count;

	INIT_LIST_HEAD(&(*line)->data.list);
	(*line)->data.type = TRAML_DATA | type;
	(*line)->data.value = 0;
	(*line)->data.address = TRAML_DATA_BASE + __traml_total_count;

	if ((++__traml_total_count) > TRAML_TOTAL)
		illegal("too many TRAM LINES");

	return;
}

struct dsp_reg *tram_line_data(struct tram_line *line)
{
	return &line->data;
}

struct dsp_reg *tram_line_address(struct tram_line *line)
{
	return &line->address;
}

void set_gpr_value(struct dsp_reg *gpr, __s32 value)
{
	if ((gpr->type & GPR_TYPE_STATIC) || (gpr->type & GPR_TYPE_CONTROL)) {
		if (gpr->value)
			warning("GPR value already set");

		gpr->value = value;

		if (gpr->type & GPR_TYPE_CONTROL) {
			if (gpr->value < gpr->min) {
				gpr->value = gpr->min;
				warning("gpr value out of range");
			} else if (gpr->value > gpr->max) {
				gpr->value = gpr->max;
				warning("gpr value out of range");
			}
		}
	} else
		illegal("can not set GPR value");

	return;
}

void set_gpr_range(struct dsp_reg *gpr, __s32 min, __s32 max)
{
        if ((gpr->type & GPR_TYPE_CONTROL)) {
                if (gpr->min || gpr->max)
                        warning("GPR value already set");

		if (min >= max)
			illegal("invalid GPR range");

                gpr->min = min;
		gpr->max = max;
        } else
                illegal("can not set GPR value");

        return;
}


void set_gpr_name(struct dsp_reg *gpr, const char *name)
{
	if (gpr->type & GPR_TYPE_CONTROL) {
		if (strlen(name) >= GPR_NAME_SIZE)
			warning("GPR name too long");

		strncpy(gpr->name, name, GPR_NAME_SIZE);
		gpr->name[GPR_NAME_SIZE - 1] = '\0';
	} else
		illegal("can not set GPR name");

	return;
}

void set_tram_line_address_value(struct tram_line *line, __u32 value)
{
	if (line->address.value)
		warning("TRAM LINE address value already set");

	if (value >= line->block->size)
		illegal("TRAM LINE address value out of range");

	line->address.value = value;

	return;
}

/* constants */

struct dsp_reg *cnt(int x)
{
	if (x < 0x00 || x >= CONSTANTS_TOTAL)
		illegal("invalid CONSTANT");

	if (__constant[x] == NULL) {
		__constant[x] =
		    (struct dsp_reg *) malloc(sizeof(struct dsp_reg));

		if (__constant[x] == NULL)
			illegal("not enough memory");

		INIT_LIST_HEAD(&__constant[x]->list);
		__constant[x]->type = CONSTANT;
		__constant[x]->address = CONSTANTS_BASE + x;
		__constant[x]->value = 0;
	}

	return __constant[x];
}

struct dsp_reg *input(int x)
{
	if (x < 0x00 || x >= INPUTS_TOTAL)
		illegal("invalid INPUT");

	if (__input[x] == NULL) {
		__input[x] = (struct dsp_reg *) malloc(sizeof(struct dsp_reg));

		if (__input[x] == NULL)
			illegal("not enough memory");

		INIT_LIST_HEAD(&__input[x]->list);
		__input[x]->type = INPUT;
		__input[x]->address = INPUTS_BASE + x;
		__input[x]->value = 0;
	}

	return __input[x];
}

struct dsp_reg *output(int x)
{
	if (x < 0x00 || x >= OUTPUTS_TOTAL)
		illegal("invalid OUTPUT");

	if (__output[x] == NULL) {
		__output[x] = (struct dsp_reg *) malloc(sizeof(struct dsp_reg));

		if (__output[x] == NULL)
			illegal("not enough memory");

		INIT_LIST_HEAD(&__output[x]->list);
		__output[x]->type = OUTPUT;
		__output[x]->address = OUTPUTS_BASE + x;
		__output[x]->value = 0;
	}

	return __output[x];
}

static void write_ok(struct dsp_reg *x)
{
	if (x->type == (TRAML_DATA | TRAML_TYPE_READ))
		illegal("can't write to TRAM LINE DATA READ register");

	/* apparently we can write to the accumulator */
	else if ((x->type & CONSTANT) && (x->address != 0x56)
		 && (x->address != 0x57) && (x->address != 0x60))
		illegal("can't write to CONSTANT register");
	else if (x->type & INPUT)
		illegal("can't write to INPUT register");

	return;
}

static void read_ok(struct dsp_reg *x, int op)
{
	if (x->type == (TRAML_DATA | TRAML_TYPE_WRITE))
		illegal("can't read from TRAM LINE DATA WRITE register");
	else if (x->type & OUTPUT)
		illegal("can't read from OUTPUT register");
	else if ((x->address == 0x57) && (op != 0xf))
		warning("use the stoccr instruction to read the CCR");

	return;
}

/* operation */

void operation(int op, struct dsp_reg *z, struct dsp_reg *w, struct dsp_reg *x,
	       struct dsp_reg *y)
{
	__u32 w0, w1;

	if (__ip >= 0x200)
		illegal("too many instructions");

	if (op > 0xf || op < 0x0)
		illegal("invalid OP code");

	__arg = 1;
	write_ok(z);

	__arg = 2;
	read_ok(w, op);

	__arg = 3;
	read_ok(x, op);

	__arg = 4;
	read_ok(y, op);

	w0 = (x->address << 10) | y->address;
	w1 = (op << 20) | (z->address << 10) | w->address;
	__dsp_code[__ip * 2] = w0;
	__dsp_code[__ip * 2 + 1] = w1;
	__ip++;

	__arg = 0;

	return;
}
