NetBSD-5.0.2/sys/arch/dreamcast/dev/microcode/aica_arm.c

/*	$NetBSD: aica_arm.c,v 1.4 2005/12/24 20:06:59 perry Exp $	*/

/*
 * Copyright (c) 2003 SHIMIZU Ryo <ryo@misakimix.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

typedef	unsigned char	uint8_t;
typedef	unsigned short	uint16_t;
typedef	unsigned long	uint32_t;

#include <arch/dreamcast/dev/g2/aicavar.h>

#define	DC_REG_ADDR	0x00800000

#define	REG_READ_1(off)		\
	(*(volatile uint8_t *)(DC_REG_ADDR + (off)))
#define	REG_READ_2(off)		\
	(*(volatile uint16_t *)(DC_REG_ADDR + (off)))
#define	REG_READ_4(off)		\
	(*(volatile uint32_t *)(DC_REG_ADDR + (off)))
#define	REG_WRITE_1(off,val)	\
	((*(volatile uint8_t *)(DC_REG_ADDR + (off))) = (val))
#define	REG_WRITE_2(off,val)	\
	((*(volatile uint16_t *)(DC_REG_ADDR + (off))) = (val))
#define	REG_WRITE_4(off,val)	\
	((*(volatile uint32_t *)((DC_REG_ADDR)+(off))) = (val))

#define	CH_READ_1(ch,off)	REG_READ_1(((ch) << 7) + (off))
#define	CH_READ_2(ch,off)	REG_READ_2(((ch) << 7) + (off))
#define	CH_READ_4(ch,off)	REG_READ_4(((ch) << 7) + (off))
#define	CH_WRITE_1(ch,off,val)	REG_WRITE_1(((ch) << 7) + (off), val)
#define	CH_WRITE_2(ch,off,val)	REG_WRITE_2(((ch) << 7) + (off), val)
#define	CH_WRITE_4(ch,off,val)	REG_WRITE_4(((ch) << 7) + (off), val)

void aica_init(void);
inline int in_first_half(unsigned int);
inline int in_second_half(unsigned int);
void bzero_4(void *, unsigned int);
void bzero(void *, unsigned int);
uint32_t rate2reg(unsigned int);
void aica_stop(void);
void aica_main(void);

void
aica_init()
{
	int ch, off;

	/* Initialize AICA channels */
	REG_WRITE_4(0x2800, 0x0000);	/* Master volume: Min */

	for (ch = 0; ch < 64; ch++) {
		CH_WRITE_4(ch, 0x00, 0x8000);	/* Key off */
		CH_WRITE_4(ch, 0x04, 0x0000);	/* DataAddress (low) */
		CH_WRITE_4(ch, 0x08, 0x0000);	/* LoopStartPosition */
		CH_WRITE_4(ch, 0x0c, 0x0000);	/* LoopEndPosition */
		CH_WRITE_4(ch, 0x10, 0x001f);	/* AR = 0x1f = 0 msec */
		CH_WRITE_4(ch, 0x14, 0x001f);	/* RR = 0x1f = 0 msec */
		CH_WRITE_4(ch, 0x18, 0x0000);	/* Pitch */
		CH_WRITE_4(ch, 0x1c, 0x0000);	/* LFO Control */
		CH_WRITE_4(ch, 0x20, 0x0000);	/* DSP Channel to send */
		CH_WRITE_4(ch, 0x24, 0x0000);	/* Pan & Volume */
		CH_WRITE_4(ch, 0x28, 0x0024);	/* Volume & LowPassFilter */
		CH_WRITE_4(ch, 0x2c, 0x0000);	/* LowPassFilter for Attack  */
		CH_WRITE_4(ch, 0x30, 0x0000);	/* LowPassFilter for Decay   */
		CH_WRITE_4(ch, 0x34, 0x0000);	/* LowPassFilter for Sustain */
		CH_WRITE_4(ch, 0x38, 0x0000);	/* LowPassFilter for Keyoff  */
		CH_WRITE_4(ch, 0x3c, 0x0000);	/* LowPassFilter for Release */
		CH_WRITE_4(ch, 0x40, 0x0000);	/* LowPassFilter transition
						   for Attack, Decay */
		CH_WRITE_4(ch, 0x44, 0x0000);	/* LowPassFilter transition
						   for Decay, Release */

		for (off = 0x48; off < 0x80; off+=4) {
			CH_WRITE_4(ch, off, 0x0000);	/* other = 0 */
		}
	}

	REG_WRITE_4(0x2800, 0x000f);	/* Master volume: Max */
}


inline int
in_first_half(unsigned int loophalf)
{

	REG_WRITE_1(0x280d, 0);	/* select channel 0 */
	return REG_READ_4(0x2814) < loophalf;
}

inline int
in_second_half(unsigned int loophalf)
{

	REG_WRITE_1(0x280d, 0);	/* select channel 0 */
	return REG_READ_4(0x2814) >= loophalf;
}


void
bzero_4(void *b, unsigned int len)
{
	uint32_t *p;

	p = b;
	len = (len + 3) & ~3;
	for (; len != 0; len -= 4)
		*p++ = 0;
}

void
bzero(void *b,unsigned int len)
{
	uint8_t *p;

	p = b;
	for (; len != 0; len--)
		*p++ = 0;
}


uint32_t
rate2reg(unsigned int rate)
{
	uint32_t base, fns;
	int oct;

	base = 44100 << 7;
	for (oct = 7; oct >= -8 && rate < base; oct--)
		base >>= 1;

	if (rate < base)
		return (oct << 11) & 0xf800;

	rate -= base;

#if 0
	/* (base / 2) : round off */
	fns = (rate * 1024 + (base / 2)) / base;
#else
	/* avoid using udivsi3() */
	{
		uint32_t tmp = (rate * 1024 + (base / 2));
		for (fns = 0; tmp >= base; tmp -= base, fns++)
			;
	}
#endif

	/* adjustment */
	if (fns == 1024) {
		oct++;
		fns = 0;
	} else {
		if ((rate > base * fns / 1024) &&
		    (fns < 1023) &&
		    (rate == base * (fns + 1) / 1024)) {
			fns++;
		} else if ((rate < base * fns / 1024) &&
		           (fns > 0) &&
		           (rate == base * (fns - 1)/ 1024)) {
			fns--;
		}
	}

	return ((oct << 11) & 0xf800) + fns;
}



void
aica_stop()
{

	CH_WRITE_4(0, 0x00, 0x8000);
	CH_WRITE_4(1, 0x00, 0x8000);
	bzero_4((void *)AICA_DMABUF_LEFT, AICA_DMABUF_SIZE);
	bzero_4((void *)AICA_DMABUF_RIGHT, AICA_DMABUF_SIZE);
}

void
aica_main()
{
	volatile aica_cmd_t *aicacmd = (volatile aica_cmd_t *)AICA_ARM_CMD;
	int play_state;
	unsigned int loopend = 0,loophalf = 0;
	unsigned int blksize = 0, ratepitch;
	uint32_t cmd, serial;

	aica_init();

	REG_WRITE_4(0x28b4, 0x0020);	/* INT Enable to SH4 */

	bzero_4((void *)AICA_DMABUF_LEFT, AICA_DMABUF_SIZE);
	bzero_4((void *)AICA_DMABUF_RIGHT, AICA_DMABUF_SIZE);

	play_state = 0;
	serial = aicacmd->serial = 0;

	for (;;) {
		if (serial != aicacmd->serial) {
			serial = aicacmd->serial;
			cmd = aicacmd->command;
			aicacmd->command = AICA_COMMAND_NOP;
		} else {
			cmd = AICA_COMMAND_NOP;
		}

		switch (cmd) {
		case AICA_COMMAND_NOP:
			/*
			 * AICA_COMMAND_NOP - Idle process
			 */
			switch (play_state) {
			case 0: /* not playing */
				break;
			case 1: /* first half */
				if (in_second_half(loophalf)) {
					/* Send INT to SH4 */
					REG_WRITE_4(0x28b8, 0x0020);
					play_state = 2;
				}
				break;
			case 2: /* second half */
				if (in_first_half(loophalf)) {
					/* Send INT to SH4 */
					REG_WRITE_4(0x28b8, 0x0020);
					play_state = 1;
				}
				break;
			case 3:
				if (in_second_half(loophalf)) {
					aica_stop();
					play_state = 0;
				}
				break;
			case 4:
				if (in_first_half(loophalf)) {
					aica_stop();
					play_state = 0;
				}
				break;
			}
			break;

		case AICA_COMMAND_PLAY:
			blksize = aicacmd->blocksize;

			CH_WRITE_4(0, 0x00, 0x8000);
			CH_WRITE_4(1, 0x00, 0x8000);

			switch (aicacmd->precision) {
			case 16:
				loopend = blksize;
				break;
			case 8:
				loopend = blksize * 2;
				break;
			case 4:
				loopend = blksize * 4;
				break;
			}
			loophalf = loopend / 2;

			ratepitch = rate2reg(aicacmd->rate);

			/* setup left */
			CH_WRITE_4(0, 0x08, 0);		/* loop start */
			CH_WRITE_4(0, 0x0c, loopend);	/* loop end */
			CH_WRITE_4(0, 0x18, ratepitch);	/* SamplingRate */
			CH_WRITE_4(0, 0x24, 0x0f1f);	/* volume MAX,
							   right PAN */

			/* setup right */
			CH_WRITE_4(1, 0x08,0);		/* loop start */
			CH_WRITE_4(1, 0x0c, loopend);	/* loop end */
			CH_WRITE_4(1, 0x18, ratepitch);	/* SamplingRate */
			CH_WRITE_4(1, 0x24, 0x0f0f);	/* volume MAX,
							   right PAN */

			{
				uint32_t mode, lparam, rparam;

				if (aicacmd->precision == 4)
					mode = 3 << 7;	/* 4bit ADPCM */
				else if (aicacmd->precision == 8)
					mode = 1 << 7;	/* 8bit */
				else
					mode = 0;	/* 16bit */

				switch (aicacmd->channel) {
				case 2:
					CH_WRITE_4(0, 0x04,
					    AICA_DMABUF_LEFT & 0xffff);
					CH_WRITE_4(1, 0x04,
					    AICA_DMABUF_RIGHT & 0xffff);
					lparam = 0xc000 /*PLAY*/ |
					    0x0200 /*LOOP*/ | mode |
					    (AICA_DMABUF_LEFT >> 16);
					rparam = 0xc000 /*PLAY*/ |
					    0x0200 /*LOOP*/ | mode |
					    (AICA_DMABUF_RIGHT >> 16);
					CH_WRITE_4(0, 0x00, lparam);
					CH_WRITE_4(1, 0x00, rparam);
					break;
				case 1:
					CH_WRITE_1(0, 0x24, 0);	/* middle
								   balance */
					CH_WRITE_4(0, 0x04,
					    AICA_DMABUF_LEFT & 0xffff);
					CH_WRITE_4(0, 0x00, 0xc000 /*PLAY*/ |
					    0x0200 /*LOOP*/ | mode |
					    (AICA_DMABUF_LEFT >> 16));
					break;
				}
			}
			play_state = 1;
			break;

		case AICA_COMMAND_STOP:
			switch (play_state) {
			case 1:
				bzero_4((void *)(AICA_DMABUF_LEFT + blksize),
				    blksize);
				bzero_4((void *)(AICA_DMABUF_RIGHT + blksize),
				    blksize);
				play_state = 3;
				break;
			case 2:
				bzero_4((void *)AICA_DMABUF_LEFT, blksize);
				bzero_4((void *)AICA_DMABUF_RIGHT, blksize);
				play_state = 4;
				break;
			default:
				aica_stop();
				play_state = 0;
				break;
			}
			break;

		case AICA_COMMAND_INIT:
			aica_stop();
			play_state = 0;
			break;

		case AICA_COMMAND_MVOL:
			REG_WRITE_4(0x2800, L256TO16(aicacmd->l_param));
			break;

		case AICA_COMMAND_VOL:
			CH_WRITE_1(0, 0x29, 0xff - (aicacmd->l_param & 0xff));
			CH_WRITE_1(1, 0x29, 0xff - (aicacmd->r_param & 0xff));
			break;

		}
	}
}