/***************************************************************************

    segasnd.c

    Sound boards for early Sega G-80 based games.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#include "emu.h"
#include "streams.h"
#include "includes/segag80v.h"
#include "cpu/mcs48/mcs48.h"
#include "sound/sp0250.h"
#include "segasnd.h"

#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)


/***************************************************************************
    CONSTANTS
***************************************************************************/

#define SPEECH_MASTER_CLOCK	3120000

#define USB_MASTER_CLOCK	6000000
#define USB_2MHZ_CLOCK		(USB_MASTER_CLOCK/3)
#define USB_PCS_CLOCK		(USB_2MHZ_CLOCK/2)
#define USB_GOS_CLOCK		(USB_2MHZ_CLOCK/16/4)
#define MM5837_CLOCK		100000

#define SAMPLE_RATE			(USB_2MHZ_CLOCK/8)



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

typedef struct _filter_state filter_state;
struct _filter_state
{
	double				capval;				/* current capacitor value */
	double				exponent;			/* constant exponent */
};


typedef struct _timer8253_channel timer8253_channel;
struct _timer8253_channel
{
	UINT8				holding;			/* holding until counts written? */
	UINT8				latchmode;			/* latching mode */
	UINT8				latchtoggle;		/* latching state */
	UINT8				clockmode;			/* clocking mode */
	UINT8				bcdmode;			/* BCD mode? */
	UINT8				output;				/* current output value */
	UINT8				lastgate;			/* previous gate value */
	UINT8				gate;				/* current gate value */
	UINT8				subcount;			/* subcount (2MHz clocks per input clock) */
	UINT16				count;				/* initial count */
	UINT16				remain;				/* current down counter value */
};


typedef struct _timer8253 timer8253;
struct _timer8253
{
	timer8253_channel	chan[3];			/* three channels' worth of information */
	double				env[3];				/* envelope value for each channel */
	filter_state		chan_filter[2];		/* filter states for the first two channels */
	filter_state		gate1;				/* first RC filter state */
	filter_state		gate2;				/* second RC filter state */
	UINT8				config;				/* configuration for this timer */
};


typedef struct _usb_state usb_state;
struct _usb_state
{
	sound_stream *		stream;				/* output stream */
	running_device *cpu;				/* CPU index of the 8035 */
	UINT8				in_latch;			/* input latch */
	UINT8				out_latch;			/* output latch */
	UINT8				last_p2_value;		/* current P2 output value */
	UINT8 *				program_ram;		/* pointer to program RAM */
	UINT8 *				work_ram;			/* pointer to work RAM */
	UINT8				work_ram_bank;		/* currently selected work RAM bank */
	UINT8				t1_clock;			/* T1 clock value */
	UINT8				t1_clock_mask;		/* T1 clock mask (configured via jumpers) */
	timer8253			timer_group[3];		/* 3 groups of timers */
	UINT8				timer_mode[3];		/* mode control for each group */
	UINT32				noise_shift;
	UINT8				noise_state;
	UINT8				noise_subcount;
	double				gate_rc1_exp[2];
	double				gate_rc2_exp[2];
	filter_state		final_filter;
	filter_state		noise_filters[5];
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

/* SP0250-based speech board */
static UINT8 speech_latch, speech_t0, speech_p2, speech_drq;

/* Universal sound board */
static usb_state usb;



/***************************************************************************
    INLINE FUNCTIONS
***************************************************************************/

INLINE void configure_filter(filter_state *state, double r, double c)
{
	state->capval = 0;
	state->exponent = 1.0 - exp(-1.0 / (r * c * SAMPLE_RATE));
}


INLINE double step_rc_filter(filter_state *state, double input)
{
	state->capval += (input - state->capval) * state->exponent;
	return state->capval;
}


INLINE double step_cr_filter(filter_state *state, double input)
{
	double result = (input - state->capval);
	state->capval += (input - state->capval) * state->exponent;
	return result;
}



/***************************************************************************
    SPEECH BOARD
***************************************************************************/


/*************************************
 *
 *  i8035 port accessors
 *
 *************************************/

static READ8_HANDLER( speech_t0_r )
{
	return speech_t0;
}

static READ8_HANDLER( speech_t1_r )
{
	return speech_drq;
}

static READ8_HANDLER( speech_p1_r )
{
	return speech_latch & 0x7f;
}

static READ8_HANDLER( speech_rom_r )
{
	return memory_region(space->machine, "speech")[0x100 * (speech_p2 & 0x3f) + offset];
}

static WRITE8_HANDLER( speech_p1_w )
{
	if (!(data & 0x80))
		speech_t0 = 0;
}

static WRITE8_HANDLER( speech_p2_w )
{
	speech_p2 = data;
}



/*************************************
 *
 *  i8035 port accessors
 *
 *************************************/

static void speech_drq_w(running_device *device, int level)
{
	speech_drq = (level == ASSERT_LINE);
}



/*************************************
 *
 *  External access
 *
 *************************************/

static TIMER_CALLBACK( delayed_speech_w )
{
	int data = param;
	UINT8 old = speech_latch;

	/* all 8 bits are latched */
	speech_latch = data;

	/* the high bit goes directly to the INT line */
	cputag_set_input_line(machine, "audiocpu", 0, (data & 0x80) ? CLEAR_LINE : ASSERT_LINE);

	/* a clock on the high bit clocks a 1 into T0 */
	if (!(old & 0x80) && (data & 0x80))
		speech_t0 = 1;
}


WRITE8_HANDLER( sega_speech_data_w )
{
	timer_call_after_resynch(space->machine, NULL, data, delayed_speech_w);
}


WRITE8_HANDLER( sega_speech_control_w )
{
	LOG(("Speech control = %X\n", data));
}



/*************************************
 *
 *  Speech board address maps
 *
 *************************************/

static ADDRESS_MAP_START( speech_map, ADDRESS_SPACE_PROGRAM, 8 )
	AM_RANGE(0x0000, 0x07ff) AM_MIRROR(0x0800) AM_ROM
ADDRESS_MAP_END


static ADDRESS_MAP_START( speech_portmap, ADDRESS_SPACE_IO, 8 )
	AM_RANGE(0x00, 0xff) AM_READ(speech_rom_r)
	AM_RANGE(0x00, 0xff) AM_DEVWRITE("speech", sp0250_w)
	AM_RANGE(MCS48_PORT_P1, MCS48_PORT_P1) AM_READWRITE(speech_p1_r, speech_p1_w)
	AM_RANGE(MCS48_PORT_P2, MCS48_PORT_P2) AM_WRITE(speech_p2_w)
	AM_RANGE(MCS48_PORT_T0, MCS48_PORT_T0) AM_READ(speech_t0_r)
	AM_RANGE(MCS48_PORT_T1, MCS48_PORT_T1) AM_READ(speech_t1_r)
ADDRESS_MAP_END



/*************************************
 *
 *  Speech board sound interfaces
 *
 *************************************/

static const struct sp0250_interface sp0250_interface =
{
	speech_drq_w
};



/*************************************
 *
 *  Speech board machine drivers
 *
 *************************************/

MACHINE_DRIVER_START( sega_speech_board )

	/* CPU for the speech board */
	MDRV_CPU_ADD("audiocpu", I8035, SPEECH_MASTER_CLOCK)		/* divide by 15 in CPU */
	MDRV_CPU_PROGRAM_MAP(speech_map)
	MDRV_CPU_IO_MAP(speech_portmap)

	/* sound hardware */
	MDRV_SOUND_ADD("speech", SP0250, SPEECH_MASTER_CLOCK)
	MDRV_SOUND_CONFIG(sp0250_interface)
	MDRV_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0)
MACHINE_DRIVER_END



/***************************************************************************
    UNIVERSAL SOUND BOARD
***************************************************************************/


/*************************************
 *
 *  Initialization/reset
 *
 *************************************/

static TIMER_DEVICE_CALLBACK( increment_t1_clock )
{
	/* only increment if it is not being forced clear */
	if (!(usb.last_p2_value & 0x80))
		usb.t1_clock++;
}


void sega_usb_reset(running_machine *machine, UINT8 t1_clock_mask)
{
	/* halt the USB CPU at reset time */
	cpu_set_input_line(usb.cpu, INPUT_LINE_RESET, ASSERT_LINE);

	/* start the clock timer */
	usb.t1_clock_mask = t1_clock_mask;
}



/*************************************
 *
 *  External access
 *
 *************************************/

READ8_HANDLER( sega_usb_status_r )
{
	LOG(("%04X:usb_data_r = %02X\n", cpu_get_pc(space->cpu), (usb.out_latch & 0x81) | (usb.in_latch & 0x7e)));

	cpu_adjust_icount(space->cpu, -200);

	/* only bits 0 and 7 are controlled by the I8035; the remaining */
	/* bits 1-6 reflect the current input latch values */
	return (usb.out_latch & 0x81) | (usb.in_latch & 0x7e);
}


static TIMER_CALLBACK( delayed_usb_data_w )
{
	int data = param;

	/* look for rising/falling edges of bit 7 to control the RESET line */
	cpu_set_input_line(usb.cpu, INPUT_LINE_RESET, (data & 0x80) ? ASSERT_LINE : CLEAR_LINE);

	/* if the CLEAR line is set, the low 7 bits of the input are ignored */
	if ((usb.last_p2_value & 0x40) == 0)
		data &= ~0x7f;

	/* update the effective input latch */
	usb.in_latch = data;
}


WRITE8_HANDLER( sega_usb_data_w )
{
	LOG(("%04X:usb_data_w = %02X\n", cpu_get_pc(space->cpu), data));
	timer_call_after_resynch(space->machine, NULL, data, delayed_usb_data_w);

	/* boost the interleave so that sequences can be sent */
	cpuexec_boost_interleave(space->machine, attotime_zero, ATTOTIME_IN_USEC(250));
}


READ8_HANDLER( sega_usb_ram_r )
{
	return usb.program_ram[offset];
}


WRITE8_HANDLER( sega_usb_ram_w )
{
	if (usb.in_latch & 0x80)
		usb.program_ram[offset] = data;
	else
		LOG(("%04X:sega_usb_ram_w(%03X) = %02X while /LOAD disabled\n", cpu_get_pc(space->cpu), offset, data));
}



/*************************************
 *
 *  I8035 port accesses
 *
 *************************************/

static READ8_HANDLER( usb_p1_r )
{
	/* bits 0-6 are inputs and map to bits 0-6 of the input latch */
	if ((usb.in_latch & 0x7f) != 0)
		LOG(("%03X: P1 read = %02X\n", cpu_get_pc(space->cpu), usb.in_latch & 0x7f));
	return usb.in_latch & 0x7f;
}


static WRITE8_HANDLER( usb_p1_w )
{
	/* bit 7 maps to bit 0 on the output latch */
	usb.out_latch = (usb.out_latch & 0xfe) | (data >> 7);
	LOG(("%03X: P1 write = %02X\n", cpu_get_pc(space->cpu), data));
}


static WRITE8_HANDLER( usb_p2_w )
{
	UINT8 old = usb.last_p2_value;
	usb.last_p2_value = data;

	/* low 2 bits control the bank of work RAM we are addressing */
	usb.work_ram_bank = data & 3;

	/* bit 6 controls the "ready" bit output to the host */
	/* it also clears the input latch from the host (active low) */
	usb.out_latch = ((data & 0x40) << 1) | (usb.out_latch & 0x7f);
	if ((data & 0x40) == 0)
		usb.in_latch = 0;

	/* bit 7 controls the reset on the upper counter at U33 */
	if ((old & 0x80) && !(data & 0x80))
		usb.t1_clock = 0;

	LOG(("%03X: P2 write -> bank=%d ready=%d clock=%d\n", cpu_get_pc(space->cpu), data & 3, (data >> 6) & 1, (data >> 7) & 1));
}


static READ8_HANDLER( usb_t1_r )
{
	/* T1 returns 1 based on the value of the T1 clock; the exact */
	/* pattern is determined by one or more jumpers on the board. */
	return (usb.t1_clock & usb.t1_clock_mask) != 0;
}



/*************************************
 *
 *  Sound generation
 *
 *************************************/

INLINE void clock_channel(timer8253_channel *ch)
{
	UINT8 lastgate = ch->lastgate;

	/* update the gate */
	ch->lastgate = ch->gate;

	/* if we're holding, skip */
	if (ch->holding)
		return;

	/* switch off the clock mode */
	switch (ch->clockmode)
	{
		/* oneshot; waits for trigger to restart */
		case 1:
			if (!lastgate && ch->gate)
			{
				ch->output = 0;
				ch->remain = ch->count;
			}
			else
			{
				if (--ch->remain == 0)
					ch->output = 1;
			}
			break;

		/* square wave: counts down by 2 and toggles output */
		case 3:
			ch->remain = (ch->remain - 1) & ~1;
			if (ch->remain == 0)
			{
				ch->output ^= 1;
				ch->remain = ch->count;
			}
			break;
	}
}


static STREAM_UPDATE( usb_stream_update )
{
	stream_sample_t *dest = outputs[0];

	/* iterate over samples */
	while (samples--)
	{
		double noiseval;
		double sample = 0;
		int group;
		int step;


		/*----------------
            Noise Source
          ----------------

                         RC
           MM5837 ---> FILTER ---> CR FILTER ---> 3.2x AMP ---> NOISE
                       LADDER
       */

		/* update the noise source */
		for (step = USB_2MHZ_CLOCK / SAMPLE_RATE; step >= usb.noise_subcount; step -= usb.noise_subcount)
		{
			usb.noise_shift = (usb.noise_shift << 1) | (((usb.noise_shift >> 13) ^ (usb.noise_shift >> 16)) & 1);
			usb.noise_state = (usb.noise_shift >> 16) & 1;
			usb.noise_subcount = USB_2MHZ_CLOCK / MM5837_CLOCK;
		}
		usb.noise_subcount -= step;

		/* update the filtered noise value -- this is just an approximation to the pink noise filter */
		/* being applied on the PCB, but it sounds pretty close */
		usb.noise_filters[0].capval = 0.99765 * usb.noise_filters[0].capval + usb.noise_state * 0.0990460;
		usb.noise_filters[1].capval = 0.96300 * usb.noise_filters[1].capval + usb.noise_state * 0.2965164;
		usb.noise_filters[2].capval = 0.57000 * usb.noise_filters[2].capval + usb.noise_state * 1.0526913;
		noiseval = usb.noise_filters[0].capval + usb.noise_filters[1].capval + usb.noise_filters[2].capval + usb.noise_state * 0.1848;

		/* final output goes through a CR filter; the scaling factor is arbitrary to get the noise to the */
		/* correct relative volume */
		noiseval = step_cr_filter(&usb.noise_filters[4], noiseval);
		noiseval *= 0.075;

		/* there are 3 identical groups of circuits, each with its own 8253 */
		for (group = 0; group < 3; group++)
		{
			timer8253 *g = &usb.timer_group[group];
			double chan0, chan1, chan2, mix;


			/*-------------
                Channel 0
              -------------

                8253        CR                   AD7524
                OUT0 ---> FILTER ---> BUFFER--->  VRef  ---> 100k ---> mix
            */

			/* channel 0 clocks with the PCS clock */
			for (step = USB_2MHZ_CLOCK / SAMPLE_RATE; step >= g->chan[0].subcount; step -= g->chan[0].subcount)
			{
				g->chan[0].subcount = USB_2MHZ_CLOCK / USB_PCS_CLOCK;
				g->chan[0].gate = 1;
				clock_channel(&g->chan[0]);
			}
			g->chan[0].subcount -= step;

			/* channel 0 is mixed in with a resistance of 100k */
			chan0 = step_cr_filter(&g->chan_filter[0], g->chan[0].output) * g->env[0] * (1.0/100.0);


			/*-------------
                Channel 1
              -------------

                8253        CR                   AD7524
                OUT1 ---> FILTER ---> BUFFER--->  VRef  ---> 100k ---> mix
            */

			/* channel 1 clocks with the PCS clock */
			for (step = USB_2MHZ_CLOCK / SAMPLE_RATE; step >= g->chan[1].subcount; step -= g->chan[1].subcount)
			{
				g->chan[1].subcount = USB_2MHZ_CLOCK / USB_PCS_CLOCK;
				g->chan[1].gate = 1;
				clock_channel(&g->chan[1]);
			}
			g->chan[1].subcount -= step;

			/* channel 1 is mixed in with a resistance of 100k */
			chan1 = step_cr_filter(&g->chan_filter[1], g->chan[1].output) * g->env[1] * (1.0/100.0);


			/*-------------
                Channel 2
              -------------

              If timer_mode == 0:

                           SWITCHED                                  AD7524
                NOISE --->    RC   ---> 1.56x AMP ---> INVERTER --->  VRef ---> 33k ---> mix
                            FILTERS

              If timer mode == 1:

                                         AD7524                                    SWITCHED
                NOISE ---> INVERTER --->  VRef ---> 33k ---> mix ---> INVERTER --->   RC   ---> 1.56x AMP ---> finalmix
                                                                                    FILTERS
            */

			/* channel 2 clocks with the 2MHZ clock and triggers with the GOS clock */
			for (step = 0; step < USB_2MHZ_CLOCK / SAMPLE_RATE; step++)
			{
				if (g->chan[2].subcount-- == 0)
				{
					g->chan[2].subcount = USB_2MHZ_CLOCK / USB_GOS_CLOCK / 2 - 1;
					g->chan[2].gate = !g->chan[2].gate;
				}
				clock_channel(&g->chan[2]);
			}

			/* the exponents for the gate filters are determined by channel 2's output */
			g->gate1.exponent = usb.gate_rc1_exp[g->chan[2].output];
			g->gate2.exponent = usb.gate_rc2_exp[g->chan[2].output];

			/* based on the envelope mode, we do one of two things with source 2 */
			if (g->config == 0)
			{
				chan2 = step_rc_filter(&g->gate2, step_rc_filter(&g->gate1, noiseval)) * -1.56 * g->env[2] * (1.0/33.0);
				mix = chan0 + chan1 + chan2;
			}
			else
			{
				chan2 = -noiseval * g->env[2] * (1.0/33.0);
				mix = chan0 + chan1 + chan2;
				mix = step_rc_filter(&g->gate2, step_rc_filter(&g->gate1, -mix)) * 1.56;
			}

			/* accumulate the sample */
			sample += mix;
		}


		/*-------------
            Final mix
          -------------

          INPUTS
          EQUAL ---> 1.2x INVERTER ---> CR FILTER ---> out
          WEIGHT

       */
       *dest++ = 4000 * step_cr_filter(&usb.final_filter, sample);
	}
}


static DEVICE_START( usb_sound )
{
	running_machine *machine = device->machine;
	filter_state temp;
	int tchan, tgroup;

	/* find the CPU we are associated with */
	usb.cpu = machine->device("usbcpu");
	assert(usb.cpu != NULL);

	/* allocate work RAM */
	usb.work_ram = auto_alloc_array(machine, UINT8, 0x400);

	/* create a sound stream */
	usb.stream = stream_create(device, 0, 1, SAMPLE_RATE, NULL, usb_stream_update);

	/* initialize state */
	usb.noise_shift = 0x15555;

	for (tgroup = 0; tgroup < 3; tgroup++)
	{
		timer8253 *g = &usb.timer_group[tgroup];
		configure_filter(&g->chan_filter[0], 10e3, 1e-6);
		configure_filter(&g->chan_filter[1], 10e3, 1e-6);
		configure_filter(&g->gate1, 100e3, 0.01e-6);
		configure_filter(&g->gate2, 2 * 100e3, 0.01e-6);
	}

	configure_filter(&temp, 100e3, 0.01e-6);
	usb.gate_rc1_exp[0] = temp.exponent;
	configure_filter(&temp, 1e3, 0.01e-6);
	usb.gate_rc1_exp[1] = temp.exponent;
	configure_filter(&temp, 2 * 100e3, 0.01e-6);
	usb.gate_rc2_exp[0] = temp.exponent;
	configure_filter(&temp, 2 * 1e3, 0.01e-6);
	usb.gate_rc2_exp[1] = temp.exponent;

	configure_filter(&usb.noise_filters[0], 2.7e3 + 2.7e3, 1.0e-6);
	configure_filter(&usb.noise_filters[1], 2.7e3 + 1e3, 0.30e-6);
	configure_filter(&usb.noise_filters[2], 2.7e3 + 270, 0.15e-6);
	configure_filter(&usb.noise_filters[3], 2.7e3 + 0, 0.082e-6);
	configure_filter(&usb.noise_filters[4], 33e3, 0.1e-6);

	configure_filter(&usb.final_filter, 100e3, 4.7e-6);

	/* register for save states */
	state_save_register_item(machine, "usb", NULL, 0, usb.in_latch);
	state_save_register_item(machine, "usb", NULL, 0, usb.out_latch);
	state_save_register_item(machine, "usb", NULL, 0, usb.last_p2_value);
	state_save_register_item(machine, "usb", NULL, 0, usb.work_ram_bank);
	state_save_register_item(machine, "usb", NULL, 0, usb.t1_clock);

	for (tgroup = 0; tgroup < 3; tgroup++)
	{
		timer8253 *group = &usb.timer_group[tgroup];
		for (tchan = 0; tchan < 3; tchan++)
		{
			timer8253_channel *channel = &group->chan[tchan];
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->holding);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->latchmode);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->latchtoggle);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->clockmode);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->bcdmode);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->output);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->lastgate);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->gate);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->subcount);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->count);
			state_save_register_item(machine, "usb", NULL, tgroup * 3 + tchan, channel->remain);
		}
		state_save_register_item_array(machine, "usb", NULL, tgroup, group->env);
		state_save_register_item(machine, "usb", NULL, tgroup, group->chan_filter[0].capval);
		state_save_register_item(machine, "usb", NULL, tgroup, group->chan_filter[1].capval);
		state_save_register_item(machine, "usb", NULL, tgroup, group->gate1.capval);
		state_save_register_item(machine, "usb", NULL, tgroup, group->gate2.capval);
		state_save_register_item(machine, "usb", NULL, tgroup, group->config);
	}

	state_save_register_item_array(machine, "usb", NULL, 0, usb.timer_mode);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_shift);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_state);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_subcount);
	state_save_register_item(machine, "usb", NULL, 0, usb.final_filter.capval);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_filters[0].capval);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_filters[1].capval);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_filters[2].capval);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_filters[3].capval);
	state_save_register_item(machine, "usb", NULL, 0, usb.noise_filters[4].capval);
}


DEVICE_GET_INFO( usb_sound )
{
	switch (state)
	{
		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:							info->start = DEVICE_START_NAME(usb_sound);		break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:							strcpy(info->s, "Sega Universal Sound Board");	break;
		case DEVINFO_STR_SOURCE_FILE:						strcpy(info->s, __FILE__);						break;
	}
}

DECLARE_LEGACY_SOUND_DEVICE(USB, usb_sound);
DEFINE_LEGACY_SOUND_DEVICE(USB, usb_sound);



/*************************************
 *
 *  USB timer and envelope controls
 *
 *************************************/

static void timer_w(int which, UINT8 offset, UINT8 data)
{
	timer8253 *g = &usb.timer_group[which];
	timer8253_channel *ch;
	int was_holding;

	stream_update(usb.stream);

	/* switch off the offset */
	switch (offset)
	{
		case 0:
		case 1:
		case 2:
			ch = &g->chan[offset];
			was_holding = ch->holding;

			/* based on the latching mode */
			switch (ch->latchmode)
			{
				case 1:	/* low word only */
					ch->count = data;
					ch->holding = FALSE;
					break;

				case 2:	/* high word only */
					ch->count = data << 8;
					ch->holding = FALSE;
					break;

				case 3:	/* low word followed by high word */
					if (ch->latchtoggle == 0)
					{
						ch->count = (ch->count & 0xff00) | (data & 0x00ff);
						ch->latchtoggle = 1;
					}
					else
					{
						ch->count = (ch->count & 0x00ff) | (data << 8);
						ch->holding = FALSE;
						ch->latchtoggle = 0;
					}
					break;
			}

			/* if we're not holding, load the initial count for some modes */
			if (was_holding && !ch->holding)
				ch->remain = 1;
			break;

		case 3:
			/* break out the components */
			if (((data & 0xc0) >> 6) < 3)
			{
				ch = &g->chan[(data & 0xc0) >> 6];

				/* extract the bits */
				ch->holding = TRUE;
				ch->latchmode = (data >> 4) & 3;
				ch->clockmode = (data >> 1) & 7;
				ch->bcdmode = (data >> 0) & 1;
				ch->latchtoggle = 0;
				ch->output = (ch->clockmode == 1);
			}
			break;
	}
}


static void env_w(int which, UINT8 offset, UINT8 data)
{
	timer8253 *g = &usb.timer_group[which];

	stream_update(usb.stream);

	if (offset < 3)
		g->env[offset] = (double)data;
	else
		g->config = data & 1;
}



/*************************************
 *
 *  USB work RAM access
 *
 *************************************/

static READ8_HANDLER( usb_workram_r )
{
	offset += 256 * usb.work_ram_bank;
	return usb.work_ram[offset];
}


static WRITE8_HANDLER( usb_workram_w )
{
	offset += 256 * usb.work_ram_bank;
	usb.work_ram[offset] = data;

	/* writes to the low 32 bytes go to various controls */
	switch (offset & ~3)
	{
		case 0x00:	/* CTC0 */
			timer_w(0, offset & 3, data);
			break;

		case 0x04:	/* ENV0 */
			env_w(0, offset & 3, data);
			break;

		case 0x08:	/* CTC1 */
			timer_w(1, offset & 3, data);
			break;

		case 0x0c:	/* ENV1 */
			env_w(1, offset & 3, data);
			break;

		case 0x10:	/* CTC2 */
			timer_w(2, offset & 3, data);
			break;

		case 0x14:	/* ENV2 */
			env_w(2, offset & 3, data);
			break;
	}
}



/*************************************
 *
 *  USB address maps
 *
 *************************************/

static ADDRESS_MAP_START( usb_map, ADDRESS_SPACE_PROGRAM, 8 )
	AM_RANGE(0x0000, 0x0fff) AM_RAM AM_BASE(&usb.program_ram)
ADDRESS_MAP_END


static ADDRESS_MAP_START( usb_map_rom, ADDRESS_SPACE_PROGRAM, 8 )
	AM_RANGE(0x0000, 0x0fff) AM_ROM
ADDRESS_MAP_END


static ADDRESS_MAP_START( usb_portmap, ADDRESS_SPACE_IO, 8 )
	AM_RANGE(0x00, 0xff) AM_READWRITE(usb_workram_r, usb_workram_w)
	AM_RANGE(MCS48_PORT_P1, MCS48_PORT_P1) AM_READWRITE(usb_p1_r, usb_p1_w)
	AM_RANGE(MCS48_PORT_P2, MCS48_PORT_P2) AM_WRITE(usb_p2_w)
	AM_RANGE(MCS48_PORT_T1, MCS48_PORT_T1) AM_READ(usb_t1_r)
ADDRESS_MAP_END



/*************************************
 *
 *  USB machine drivers
 *
 *************************************/

MACHINE_DRIVER_START( sega_universal_sound_board )

	/* CPU for the usb board */
	MDRV_CPU_ADD("usbcpu", I8035, USB_MASTER_CLOCK)		/* divide by 15 in CPU */
	MDRV_CPU_PROGRAM_MAP(usb_map)
	MDRV_CPU_IO_MAP(usb_portmap)

	MDRV_TIMER_ADD_PERIODIC("usb_timer", increment_t1_clock, HZ(USB_2MHZ_CLOCK / 256))

	/* sound hardware */
	MDRV_SOUND_ADD("usbsnd", USB, 0)
	MDRV_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0)
MACHINE_DRIVER_END


MACHINE_DRIVER_START( sega_universal_sound_board_rom )
	MDRV_IMPORT_FROM( sega_universal_sound_board )

	/* CPU for the usb board */
	MDRV_CPU_MODIFY("usbcpu")
	MDRV_CPU_PROGRAM_MAP(usb_map_rom)
MACHINE_DRIVER_END
