view ym2612.c @ 288:a8ee7934a1f8

Add a YM2612 stub implementation with just timers and status registers so that games that depend on it can run.
author Mike Pavone <pavone@retrodev.com>
date Sun, 05 May 2013 22:56:42 -0700
parents
children cc39629e8d06
line wrap: on
line source

#include <string.h>
#include "ym2612.h"

#define BUSY_CYCLES 17
#define TIMERA_UPDATE_PERIOD 144

#define REG_TIMERA_HIGH 0x3 // 0x24
#define REG_TIMERA_LOW 0x4  // 0x25
#define REG_TIMERB 0x5      // 0x26
#define REG_TIME_CTRL 0x6   // 0x27

#define BIT_TIMERA_ENABLE 0x1
#define BIT_TIMERB_ENABLE 0x2
#define BIT_TIMERA_OVEREN 0x4
#define BIT_TIMERB_OVEREN 0x8
#define BIT_TIMERA_RESET  0x10
#define BIT_TIMERB_RESET  0x20

#define BIT_STATUS_TIMERA 0x1
#define BIT_STATUS_TIMERB 0x2

void ym_init(ym2612_context * context)
{
	memset(context, 0, sizeof(*context));
}

void ym_run(ym2612_context * context, uint32_t to_cycle)
{
	uint32_t delta = to_cycle - context->current_cycle;
	//Timers won't be perfect with this, but it's good enough for now
	//once actual FM emulation is in place the timers should just be 
	//decremented/reloaded on the appropriate ticks
	uint32_t timer_delta = to_cycle / TIMERA_UPDATE_PERIOD;
	if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERA_ENABLE) {
		if (timer_delta > context->timer_a) {
			if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERA_OVEREN) {
				context->status |= BIT_STATUS_TIMERA;
			}
			uint32_t rem_delta = timer_delta - (context->timer_a+1);
			uint16_t timer_val = (context->part1_regs[REG_TIMERA_HIGH] << 2) | (context->part1_regs[REG_TIMERA_LOW] & 0x3);
			context->timer_a = timer_val - (rem_delta % (timer_val + 1));
		} else {
			context->timer_a -= timer_delta;
		}
	}
	timer_delta /= 16; //Timer B runs at 1/16th the speed of Timer A
	if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERB_ENABLE) {
		if (timer_delta > context->timer_b) {
			if (context->part1_regs[REG_TIME_CTRL] & BIT_TIMERB_OVEREN) {
				context->status |= BIT_STATUS_TIMERB;
			}
			uint32_t rem_delta = timer_delta - (context->timer_b+1);
			uint8_t timer_val = context->part1_regs[REG_TIMERB];
			context->timer_b = timer_val - (rem_delta % (timer_val + 1));
		} else {
			context->timer_a -= timer_delta;
		}
	}
	context->current_cycle = to_cycle;
	if (to_cycle >= context->write_cycle + BUSY_CYCLES) {
		context->status &= 0x7F;
	}
}

void ym_address_write_part1(ym2612_context * context, uint8_t address)
{
	if (address >= 0x21 && address < 0xB7) {
		context->selected_reg = context->part1_regs + address - 0x21;
	} else {
		context->selected_reg = NULL;
	}
}

void ym_address_write_part2(ym2612_context * context, uint8_t address)
{
	if (address >= 0x30 && address < 0xB7) {
		context->selected_reg = context->part1_regs + address - 0x30;
	} else {
		context->selected_reg = NULL;
	}
}

void ym_data_write(ym2612_context * context, uint8_t value)
{
	if (context->selected_reg && !(context->status & 0x80)) {
		*context->selected_reg = value;
		context->write_cycle = context->current_cycle;
		context->selected_reg = NULL;//TODO: Verify this
	}
}

uint8_t ym_read_status(ym2612_context * context)
{
	return context->status;
}