/* KallistiOS ##version##

   main.c
   (c)2000-2002 Dan Potter
   
   Generic sound driver with streaming capabilities

   This slightly more complicated version allows for sound effect channels,
   and full sampling rate, panning, and volume control for each.

*/

#include "aica.h"
#include "aica_cmd_iface.h"

/****************** Timer *******************************************/

#define timer (*((volatile uint32 *)AICA_MEM_CLOCK))

void timer_wait(int jiffies) {
	int fin = timer + jiffies;
	while (timer <= fin)
		;
}

/****************** Tiny Libc ***************************************/

#include <stddef.h>

void * memcpy(void *dest, const void *src, size_t count) {
	unsigned char *tmp = (unsigned char *) dest;
	unsigned char *s = (unsigned char *) src;

	while (count--)
		*tmp++ = *s++;

	return dest;
}                                                        

/****************** Main Program ************************************/

/* Our SH-4 interface (statically placed memory structures) */
volatile aica_queue_t	*q_cmd = (volatile aica_queue_t *)AICA_MEM_CMD_QUEUE;
volatile aica_queue_t	*q_resp = (volatile aica_queue_t *)AICA_MEM_RESP_QUEUE;
volatile aica_channel_t	*chans = (volatile aica_channel_t *)AICA_MEM_CHANNELS;

/* Process a CHAN command */
void process_chn(uint32 chn, aica_channel_t *chndat) {
	switch(chndat->cmd & AICA_CH_CMD_MASK) {
	case AICA_CH_CMD_NONE:
		break;
	case AICA_CH_CMD_START:
		memcpy((void*)(chans+chn), chndat, sizeof(aica_channel_t));
		chans[chn].pos = 0;
		aica_play(chn);
		break;
	case AICA_CH_CMD_STOP:
		aica_stop(chn);
		break;
	case AICA_CH_CMD_UPDATE:
		if (chndat->cmd & AICA_CH_UPDATE_SET_FREQ) {
			chans[chn].freq = chndat->freq;
			aica_freq(chn);
		}
		if (chndat->cmd & AICA_CH_UPDATE_SET_VOL) {
			chans[chn].vol = chndat->vol;
			aica_vol(chn);
		}
		if (chndat->cmd & AICA_CH_UPDATE_SET_PAN) {
			chans[chn].pan = chndat->pan;
			aica_pan(chn);
		}
		break;
	default:
		/* error */
		break;
	}
}

/* Process one packet of queue data */
uint32 process_one(uint32 tail) {
	uint32		pktdata[AICA_CMD_MAX_SIZE], *pdptr, size, i;
	volatile uint32	* src;
	aica_cmd_t	* pkt;

	src = (volatile uint32 *)(q_cmd->data + tail);
	pkt = (aica_cmd_t *)pktdata;
	pdptr = pktdata;

	/* Get the size field */
	size = *src;
	if (size > AICA_CMD_MAX_SIZE)
		size = AICA_CMD_MAX_SIZE;

	/* Copy out the packet data */
	for (i=0; i<size; i++) {
		*pdptr++ = *src++;
		if ((uint32)src >= (q_cmd->data + q_cmd->size))
			src = (volatile uint32 *)q_cmd->data;
	}
	
	/* Figure out what type of packet it is */
	switch (pkt->cmd) {
	case AICA_CMD_NONE:
		break;
	case AICA_CMD_PING:
		/* Not implemented yet */
		break;
	case AICA_CMD_CHAN:
		process_chn(pkt->cmd_id, (aica_channel_t *)pkt->cmd_data);
		break;
	case AICA_CMD_SYNC_CLOCK:
		/* Reset our timer clock to zero */
		timer = 0;
		break;
	default:
		/* error */
		break;
	}

	return size;
}

/* Look for an available request in the command queue; if one is there
   then process it and move the tail pointer. */
void process_cmd_queue() {
	uint32		head, tail, tsloc, ts;

	/* Grab these values up front in case SH-4 changes head */
	head = q_cmd->head;
	tail = q_cmd->tail;

	/* Do we have anything to process? */
	while (head != tail) {
		/* Look at the next packet. If our clock isn't there yet, then
		   we won't process anything yet either. */
		tsloc = tail + offsetof(aica_cmd_t, timestamp);
		if (tsloc >= q_cmd->size)
			tsloc -= q_cmd->size;
		ts = *((volatile uint32*)(q_cmd->data + tsloc));
		if (ts > 0 && ts >= timer)
			return;

		/* Process it */
		ts = process_one(tail);

		/* Ok, skip over the packet */
		tail += ts * 4;
		if (tail >= q_cmd->size)
			tail -= q_cmd->size;
		q_cmd->tail = tail;
	}
}

int arm_main() {
	int i;

	/* Setup our queues */
	q_cmd->head = q_cmd->tail = 0;
	q_cmd->data = AICA_MEM_CMD_QUEUE + sizeof(aica_queue_t);
	q_cmd->size = AICA_MEM_RESP_QUEUE - q_cmd->data;
	q_cmd->process_ok = 1;
	q_cmd->valid = 1;

	q_resp->head = q_resp->tail = 0;
	q_resp->data = AICA_MEM_RESP_QUEUE + sizeof(aica_queue_t);
	q_resp->size = AICA_MEM_CHANNELS - q_resp->data;
	q_resp->process_ok = 1;
	q_resp->valid = 1;

	/* Initialize the AICA part of the SPU */
	aica_init();

	/* Wait for a command */
	for( ; ; ) {
		/* Update channel position counters */
		for (i=0; i<2; i++)
			aica_get_pos(i);

		/* Check for a command */
		if (q_cmd->process_ok)
			process_cmd_queue();

		/* Little delay to prevent memory lock */
		timer_wait(10);
	}
}
