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

    audio/williams.h

    Functions to emulate general the various Williams/Midway sound cards.

****************************************************************************

    Copyright Aaron Giles
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.
        * 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.
        * Neither the name 'MAME' nor the names of its contributors may be
          used to endorse or promote products derived from this software
          without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY AARON GILES ''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 AARON GILES 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.

****************************************************************************

    Midway/Williams Audio Boards
    ----------------------------

    6809 MEMORY MAP

    Function                                  Address     R/W  Data
    ---------------------------------------------------------------
    Program RAM                               0000-07FF   R/W  D0-D7

    Music (YM-2151)                           2000-2001   R/W  D0-D7

    6821 PIA                                  4000-4003   R/W  D0-D7

    HC55516 clock low, digit latch            6000        W    D0
    HC55516 clock high                        6800        W    xx

    Bank select                               7800        W    D0-D2

    Banked Program ROM                        8000-FFFF   R    D0-D7

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

#include "emu.h"
#include "machine/6821pia.h"
#include "cpu/m6809/m6809.h"
#include "williams.h"
#include "sound/2151intf.h"
#include "sound/okim6295.h"
#include "sound/hc55516.h"
#include "sound/dac.h"


#define NARC_MASTER_CLOCK		XTAL_8MHz
#define NARC_FM_CLOCK			XTAL_3_579545MHz

#define CVSD_MASTER_CLOCK		XTAL_8MHz
#define CVSD_FM_CLOCK			XTAL_3_579545MHz

#define ADPCM_MASTER_CLOCK		XTAL_8MHz
#define ADPCM_FM_CLOCK			XTAL_3_579545MHz



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

extern const device_type WILLIAMS_NARC_SOUND = &device_creator<williams_narc_sound_device>;
extern const device_type WILLIAMS_CVSD_SOUND = &device_creator<williams_cvsd_sound_device>;
extern const device_type WILLIAMS_ADPCM_SOUND = &device_creator<williams_adpcm_sound_device>;



//**************************************************************************
//  CVSD SOUND BOARD
//**************************************************************************

//-------------------------------------------------
//  williams_cvsd_sound_device - constructor
//-------------------------------------------------

williams_cvsd_sound_device::williams_cvsd_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, WILLIAMS_CVSD_SOUND, "Williams CVSD Sound Board", "wmscvsd", tag, owner, clock),
	  device_mixer_interface(mconfig, *this),
	  m_cpu(*this, "cpu"),
	  m_pia(*this, "pia"),
	  m_hc55516(*this, "cvsd"),
	  m_talkback(0)
{
}


//-------------------------------------------------
//  write - handle an external write to the input
//  latch
//-------------------------------------------------

WRITE16_MEMBER(williams_cvsd_sound_device::write)
{
	synchronize(0, data);
}


//-------------------------------------------------
//  reset_write - write to the reset line
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_cvsd_sound_device::reset_write)
{
	// going high halts the CPU
	if (state)
	{
		bank_select_w(*m_cpu->space(), 0, 0);
		device_reset();
		m_cpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
	}

	// going low resets and reactivates the CPU
	else
		m_cpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
}


//-------------------------------------------------
//  bank_select_w - change memory banks
//-------------------------------------------------

WRITE8_MEMBER(williams_cvsd_sound_device::bank_select_w)
{
	membank("rombank")->set_entry(data & 0x0f);
}


//-------------------------------------------------
//  talkback_w - write to the talkback latch
//-------------------------------------------------

WRITE8_MEMBER(williams_cvsd_sound_device::talkback_w)
{
	m_talkback = data;
	logerror("CVSD Talkback = %02X\n", data);
}


//-------------------------------------------------
//  cvsd_digit_clock_clear_w - clear the clock on
//  the HC55516 and clock the data
//-------------------------------------------------

WRITE8_MEMBER(williams_cvsd_sound_device::cvsd_digit_clock_clear_w)
{
	hc55516_digit_w(m_hc55516, data);
	hc55516_clock_w(m_hc55516, 0);
}


//-------------------------------------------------
//  cvsd_clock_set_w - set the clock on the HC55516
//-------------------------------------------------

WRITE8_MEMBER(williams_cvsd_sound_device::cvsd_clock_set_w)
{
	hc55516_clock_w(m_hc55516, 1);
}


//-------------------------------------------------
//  ym2151_irq_w - process IRQ signal changes from
//  the YM2151
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_cvsd_sound_device::ym2151_irq_w)
{
	m_pia->ca1_w(!state);
}


//-------------------------------------------------
//  pia_irqa - process IRQ A signal changes from
//  the 6821
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_cvsd_sound_device::pia_irqa)
{
	m_cpu->set_input_line(M6809_FIRQ_LINE, state ? ASSERT_LINE : CLEAR_LINE);
}


//-------------------------------------------------
//  pia_irqb - process IRQ B signal changes from
//  the 6821
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_cvsd_sound_device::pia_irqb)
{
	m_cpu->set_input_line(INPUT_LINE_NMI, state ? ASSERT_LINE : CLEAR_LINE);
}


//-------------------------------------------------
//  audio CPU map
//-------------------------------------------------

static ADDRESS_MAP_START( williams_cvsd_map, AS_PROGRAM, 8, williams_cvsd_sound_device )
	AM_RANGE(0x0000, 0x07ff) AM_MIRROR(0x1800) AM_RAM
	AM_RANGE(0x2000, 0x2001) AM_MIRROR(0x1ffe) AM_DEVREADWRITE_LEGACY("ym2151", ym2151_r, ym2151_w)
	AM_RANGE(0x4000, 0x4003) AM_MIRROR(0x1ffc) AM_DEVREADWRITE("pia", pia6821_device, read, write)
	AM_RANGE(0x6000, 0x6000) AM_MIRROR(0x07ff) AM_WRITE(cvsd_digit_clock_clear_w)
	AM_RANGE(0x6800, 0x6800) AM_MIRROR(0x07ff) AM_WRITE(cvsd_clock_set_w)
	AM_RANGE(0x7800, 0x7800) AM_MIRROR(0x07ff) AM_WRITE(bank_select_w)
	AM_RANGE(0x8000, 0xffff) AM_ROMBANK("rombank")
ADDRESS_MAP_END


//-------------------------------------------------
//  6821 PIA interface
//-------------------------------------------------

static const pia6821_interface cvsd_pia_intf =
{
	DEVCB_NULL,		// port A in
	DEVCB_NULL,		// port B in
	DEVCB_NULL,		// line CA1 in
	DEVCB_NULL,		// line CB1 in
	DEVCB_NULL,		// line CA2 in
	DEVCB_NULL,		// line CB2 in
	DEVCB_DEVICE_HANDLER("dac", dac_w),		// port A out
	DEVCB_DEVICE_MEMBER(DEVICE_SELF_OWNER, williams_cvsd_sound_device, talkback_w),		// port B out
	DEVCB_NULL,		// line CA2 out
	DEVCB_NULL,		// port CB2 out
	DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, williams_cvsd_sound_device, pia_irqa),	// IRQA
	DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, williams_cvsd_sound_device, pia_irqb)	// IRQB
};


//-------------------------------------------------
//  YM2151 configuration
//-------------------------------------------------

static const ym2151_interface cvsd_ym2151_interface =
{
	DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, williams_cvsd_sound_device, ym2151_irq_w)
};


//-------------------------------------------------
//  machine configuration
//-------------------------------------------------

static MACHINE_CONFIG_FRAGMENT( williams_cvsd_sound )
	MCFG_CPU_ADD("cpu", M6809E, CVSD_MASTER_CLOCK)
	MCFG_CPU_PROGRAM_MAP(williams_cvsd_map)

	MCFG_PIA6821_ADD("pia", cvsd_pia_intf)

	MCFG_SOUND_ADD("ym2151", YM2151, CVSD_FM_CLOCK)
	MCFG_SOUND_CONFIG(cvsd_ym2151_interface)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.10)

	MCFG_SOUND_ADD("dac", DAC, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.50)

	MCFG_SOUND_ADD("cvsd", HC55516, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.60)
MACHINE_CONFIG_END


//-------------------------------------------------
//  device_mconfig_additions - return a pointer to
//  the device's machine fragment
//-------------------------------------------------

machine_config_constructor williams_cvsd_sound_device::device_mconfig_additions() const
{
	return MACHINE_CONFIG_NAME( williams_cvsd_sound );
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void williams_cvsd_sound_device::device_start()
{
	// configure master CPU banks
	UINT8 *rom = memregion("cpu")->base();
	for (int bank = 0; bank < 16; bank++)
	{
		//
        //  D0/D1 -> selects: 0=U4 1=U19 2=U20 3=n/c
        //  D2 -> A15
        //  D3 -> A16
        //
		offs_t offset = 0x8000 * ((bank >> 2) & 3) + 0x20000 * (bank & 3);
		membank("rombank")->configure_entry(bank, &rom[0x10000 + offset]);
	}
	membank("rombank")->set_entry(0);

	// reset the IRQ state
	m_pia->ca1_w(1);

	// register for save states
	save_item(NAME(m_talkback));
}


//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void williams_cvsd_sound_device::device_reset()
{
	// reset interrupt states
	m_cpu->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE);
	m_cpu->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);
	m_cpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
}


//-------------------------------------------------
//  device_timer - timer callbacks
//-------------------------------------------------

void williams_cvsd_sound_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	// process incoming data write
	m_pia->portb_w(param & 0xff);
	m_pia->cb1_w((param >> 8) & 1);
	m_pia->cb2_w((param >> 9) & 1);
}



//**************************************************************************
//  NARC SOUND BOARD
//**************************************************************************

//-------------------------------------------------
//  williams_narc_sound_device - constructor
//-------------------------------------------------

williams_narc_sound_device::williams_narc_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, WILLIAMS_NARC_SOUND, "Williams NARC Sound Board", "wmsnarc", tag, owner, clock),
	  device_mixer_interface(mconfig, *this),
	  m_cpu0(*this, "cpu0"),
	  m_cpu1(*this, "cpu1"),
	  m_hc55516(*this, "cvsd"),
	  m_latch(0),
	  m_latch2(0),
	  m_talkback(0),
	  m_audio_sync(0),
	  m_sound_int_state(0)
{
}


//-------------------------------------------------
//  read - return the talkback register with the
//  SYNC bits in bits 8 and 9
//-------------------------------------------------

READ16_MEMBER(williams_narc_sound_device::read)
{
	return m_talkback | (m_audio_sync << 8);
}


//-------------------------------------------------
//  write - handle an external write to the input
//  latch
//-------------------------------------------------

WRITE16_MEMBER(williams_narc_sound_device::write)
{
	synchronize(TID_MASTER_COMMAND, data);
}


//-------------------------------------------------
//  reset_write - write to the reset line
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_narc_sound_device::reset_write)
{
	// going high halts the CPU
	if (state)
	{
		master_bank_select_w(*m_cpu0->space(), 0, 0);
		slave_bank_select_w(*m_cpu1->space(), 0, 0);
		device_reset();
		m_cpu0->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
		m_cpu1->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
	}

	// going low resets and reactivates the CPU
	else
	{
		m_cpu0->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
		m_cpu1->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
	}
}


//-------------------------------------------------
//  master_bank_select_w - select the bank for the
//  master CPU
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::master_bank_select_w)
{
	membank("masterbank")->set_entry(data & 0x0f);
}


//-------------------------------------------------
//  slave_bank_select_w - select the bank for the
//  slave CPU
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::slave_bank_select_w)
{
	membank("slavebank")->set_entry(data & 0x0f);
}


//-------------------------------------------------
//  command_r - read command written by external
//  agent
//-------------------------------------------------

READ8_MEMBER(williams_narc_sound_device::command_r)
{
	m_cpu0->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);
	m_sound_int_state = 0;
	return m_latch;
}


//-------------------------------------------------
//  command2_w - write command from master CPU to
//  slave CPU
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::command2_w)
{
	synchronize(TID_SLAVE_COMMAND, data);
}


//-------------------------------------------------
//  command2_r - read command written by master
//  CPU
//-------------------------------------------------

READ8_MEMBER(williams_narc_sound_device::command2_r)
{
	m_cpu1->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE);
	return m_latch2;
}


//-------------------------------------------------
//  master_talkback_w - handle writes to the
//  talkback latch from the master CPU
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::master_talkback_w)
{
	m_talkback = data;
	logerror("Master Talkback = %02X\n", data);
}


//-------------------------------------------------
//  master_sync_w - handle writes to the master
//  SYNC register
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::master_sync_w)
{
	timer_set(attotime::from_double(TIME_OF_74LS123(180000, 0.000001)), TID_SYNC_CLEAR, 0x01);
	m_audio_sync |= 0x01;
	logerror("Master sync = %02X\n", data);
}


//-------------------------------------------------
//  slave_talkback_w - handle writes to the
//  talkback latch from the slave CPU
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::slave_talkback_w)
{
	logerror("Slave Talkback = %02X\n", data);
}


//-------------------------------------------------
//  slave_sync_w - handle writes to the slave
//  SYNC register
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::slave_sync_w)
{
	timer_set(attotime::from_double(TIME_OF_74LS123(180000, 0.000001)), TID_SYNC_CLEAR, 0x02);
	m_audio_sync |= 0x02;
	logerror("Slave sync = %02X\n", data);
}


//-------------------------------------------------
//  cvsd_digit_clock_clear_w - clear the clock on
//  the HC55516 and clock the data
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::cvsd_digit_clock_clear_w)
{
	hc55516_digit_w(m_hc55516, data);
	hc55516_clock_w(m_hc55516, 0);
}


//-------------------------------------------------
//  cvsd_clock_set_w - set the clock on the HC55516
//-------------------------------------------------

WRITE8_MEMBER(williams_narc_sound_device::cvsd_clock_set_w)
{
	hc55516_clock_w(m_hc55516, 1);
}


//-------------------------------------------------
//  ym2151_irq_w - handle line changes on the
//  YM2151 IRQ line
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_narc_sound_device::ym2151_irq_w)
{
	m_cpu0->set_input_line(M6809_FIRQ_LINE, state ? ASSERT_LINE : CLEAR_LINE);
}


//-------------------------------------------------
//  master CPU map
//-------------------------------------------------

static ADDRESS_MAP_START( williams_narc_master_map, AS_PROGRAM, 8, williams_narc_sound_device )
	AM_RANGE(0x0000, 0x1fff) AM_RAM
	AM_RANGE(0x2000, 0x2001) AM_MIRROR(0x03fe) AM_DEVREADWRITE_LEGACY("ym2151", ym2151_r, ym2151_w)
	AM_RANGE(0x2800, 0x2800) AM_MIRROR(0x03ff) AM_WRITE(master_talkback_w)
	AM_RANGE(0x2c00, 0x2c00) AM_MIRROR(0x03ff) AM_WRITE(command2_w)
	AM_RANGE(0x3000, 0x3000) AM_MIRROR(0x03ff) AM_DEVWRITE_LEGACY("dac1", dac_w)
	AM_RANGE(0x3400, 0x3400) AM_MIRROR(0x03ff) AM_READ(command_r)
	AM_RANGE(0x3800, 0x3800) AM_MIRROR(0x03ff) AM_WRITE(master_bank_select_w)
	AM_RANGE(0x3c00, 0x3c00) AM_MIRROR(0x03ff) AM_WRITE(master_sync_w)
	AM_RANGE(0x4000, 0xbfff) AM_ROMBANK("masterbank")
	AM_RANGE(0xc000, 0xffff) AM_ROMBANK("masterupper")
ADDRESS_MAP_END


//-------------------------------------------------
//  slave CPU map
//-------------------------------------------------

static ADDRESS_MAP_START( williams_narc_slave_map, AS_PROGRAM, 8, williams_narc_sound_device )
	AM_RANGE(0x0000, 0x1fff) AM_RAM
	AM_RANGE(0x2000, 0x2000) AM_MIRROR(0x03ff) AM_WRITE(cvsd_clock_set_w)
	AM_RANGE(0x2400, 0x2400) AM_MIRROR(0x03ff) AM_WRITE(cvsd_digit_clock_clear_w)
	AM_RANGE(0x2800, 0x2800) AM_MIRROR(0x03ff) AM_WRITE(slave_talkback_w)
	AM_RANGE(0x3000, 0x3000) AM_MIRROR(0x03ff) AM_DEVWRITE_LEGACY("dac2", dac_w)
	AM_RANGE(0x3400, 0x3400) AM_MIRROR(0x03ff) AM_READ(command2_r)
	AM_RANGE(0x3800, 0x3800) AM_MIRROR(0x03ff) AM_WRITE(slave_bank_select_w)
	AM_RANGE(0x3c00, 0x3c00) AM_MIRROR(0x03ff) AM_WRITE(slave_sync_w)
	AM_RANGE(0x4000, 0xbfff) AM_ROMBANK("slavebank")
	AM_RANGE(0xc000, 0xffff) AM_ROMBANK("slaveupper")
ADDRESS_MAP_END


//-------------------------------------------------
//  YM2151 configuration
//-------------------------------------------------

static const ym2151_interface narc_ym2151_interface =
{
	DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, williams_narc_sound_device, ym2151_irq_w)
};


//-------------------------------------------------
//  machine configuration
//-------------------------------------------------

static MACHINE_CONFIG_FRAGMENT( williams_narc_sound )
	MCFG_CPU_ADD("cpu0", M6809E, NARC_MASTER_CLOCK)
	MCFG_CPU_PROGRAM_MAP(williams_narc_master_map)

	MCFG_CPU_ADD("cpu1", M6809E, NARC_MASTER_CLOCK)
	MCFG_CPU_PROGRAM_MAP(williams_narc_slave_map)

	MCFG_SOUND_ADD("ym2151", YM2151, NARC_FM_CLOCK)
	MCFG_SOUND_CONFIG(narc_ym2151_interface)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.10)

	MCFG_SOUND_ADD("dac1", DAC, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.50)

	MCFG_SOUND_ADD("dac2", DAC, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.50)

	MCFG_SOUND_ADD("cvsd", HC55516, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.60)
MACHINE_CONFIG_END


//-------------------------------------------------
//  device_mconfig_additions - return a pointer to
//  the device's machine fragment
//-------------------------------------------------

machine_config_constructor williams_narc_sound_device::device_mconfig_additions() const
{
	return MACHINE_CONFIG_NAME( williams_narc_sound );
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void williams_narc_sound_device::device_start()
{
	// configure master CPU banks
	UINT8 *rom = memregion("cpu0")->base();
	for (int bank = 0; bank < 16; bank++)
	{
		//
        //  D0 -> A15
        //  D1/D2 -> selects: 0=n/c 1=U3 2=U4 3=U5
        //  D3 -> A16
        //
		offs_t offset = 0x8000 * (bank & 1) + 0x10000 * ((bank >> 3) & 1) + 0x20000 * ((bank >> 1) & 3);
		membank("masterbank")->configure_entry(bank, &rom[0x10000 + offset]);
	}
	membank("masterupper")->set_base(&rom[0x10000 + 0x4000 + 0x8000 + 0x10000 + 0x20000 * 3]);

	// configure slave CPU banks
	rom = memregion("cpu1")->base();
	for (int bank = 0; bank < 16; bank++)
	{
		//
        //  D0 -> A15
        //  D1/D2 -> selects: 0=U35 1=U36 2=U37 3=U38
        //  D3 -> A16
        //
		offs_t offset = 0x8000 * (bank & 1) + 0x10000 * ((bank >> 3) & 1) + 0x20000 * ((bank >> 1) & 3);
		membank("slavebank")->configure_entry(bank, &rom[0x10000 + offset]);
	}
	membank("slaveupper")->set_base(&rom[0x10000 + 0x4000 + 0x8000 + 0x10000 + 0x20000 * 3]);

	// register for save states
	save_item(NAME(m_latch));
	save_item(NAME(m_latch2));
	save_item(NAME(m_talkback));
	save_item(NAME(m_audio_sync));
	save_item(NAME(m_sound_int_state));
}


//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void williams_narc_sound_device::device_reset()
{
	// reset interrupt states
	m_sound_int_state = 0;
	m_cpu0->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE);
	m_cpu0->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);
	m_cpu0->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
	m_cpu1->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE);
	m_cpu1->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);
	m_cpu1->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
}


//-------------------------------------------------
//  device_timer - timer callbacks
//-------------------------------------------------

void williams_narc_sound_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
		case TID_MASTER_COMMAND:
			m_latch = param & 0xff;
			m_cpu0->set_input_line(INPUT_LINE_NMI, (param & 0x100) ? CLEAR_LINE : ASSERT_LINE);
			if ((param & 0x200) == 0)
			{
				m_cpu0->set_input_line(M6809_IRQ_LINE, ASSERT_LINE);
				m_sound_int_state = 1;
			}
			break;

		case TID_SLAVE_COMMAND:
			m_latch2 = param & 0xff;
			m_cpu1->set_input_line(M6809_FIRQ_LINE, ASSERT_LINE);
			break;

		case TID_SYNC_CLEAR:
			m_audio_sync &= ~param;
			break;
	}
}


//**************************************************************************
//  ADPCM SOUND BOARD
//**************************************************************************

//-------------------------------------------------
//  williams_adpcm_sound_device - constructor
//-------------------------------------------------

williams_adpcm_sound_device::williams_adpcm_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
	: device_t(mconfig, WILLIAMS_ADPCM_SOUND, "Williams ADPCM Sound Board", "wmsadpcm", tag, owner, clock),
	  device_mixer_interface(mconfig, *this),
	  m_cpu(*this, "cpu"),
	  m_latch(0),
	  m_talkback(0),
	  m_sound_int_state(0)
{
}


//-------------------------------------------------
//  write - handle an external write to the input
//  latch
//-------------------------------------------------

WRITE16_MEMBER(williams_adpcm_sound_device::write)
{
	synchronize(TID_COMMAND, data);
}


//-------------------------------------------------
//  reset_write - write to the reset line
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_adpcm_sound_device::reset_write)
{
	// going high halts the CPU
	if (state)
	{
		bank_select_w(*m_cpu->space(), 0, 0);
		device_reset();
		m_cpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
	}

	// going low resets and reactivates the CPU
	else
		m_cpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
}


//-------------------------------------------------
//  irq_read - read the sound IRQ state
//-------------------------------------------------

READ_LINE_MEMBER(williams_adpcm_sound_device::irq_read)
{
	return m_sound_int_state;
}


//-------------------------------------------------
//  bank_select_w - select the sound CPU memory
//  bank
//-------------------------------------------------

WRITE8_MEMBER(williams_adpcm_sound_device::bank_select_w)
{
	membank("rombank")->set_entry(data & 0x07);
}


//-------------------------------------------------
//  bank_select_w - select the OKI6295 memory
//  bank
//-------------------------------------------------

WRITE8_MEMBER(williams_adpcm_sound_device::oki6295_bank_select_w)
{
	membank("okibank")->set_entry(data & 7);
}


//-------------------------------------------------
//  command_r - read the command from the external
//  latch
//-------------------------------------------------

READ8_MEMBER(williams_adpcm_sound_device::command_r)
{
	m_cpu->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);

	// don't clear the external IRQ state for a short while; this allows the
    // self-tests to pass
	timer_set(attotime::from_usec(10), TID_IRQ_CLEAR);
	return m_latch;
}


//-------------------------------------------------
//  talkback_w - write to the talkback latch
//-------------------------------------------------

WRITE8_MEMBER(williams_adpcm_sound_device::talkback_w)
{
	m_talkback = data;
	logerror("ADPCM Talkback = %02X\n", data);
}


//-------------------------------------------------
//  talkback_w - write to the talkback latch
//-------------------------------------------------

WRITE_LINE_MEMBER(williams_adpcm_sound_device::ym2151_irq_w)
{
	m_cpu->set_input_line(M6809_FIRQ_LINE, state ? ASSERT_LINE : CLEAR_LINE);
}


//-------------------------------------------------
//  audio CPU map
//-------------------------------------------------

static ADDRESS_MAP_START( williams_adpcm_map, AS_PROGRAM, 8, williams_adpcm_sound_device )
	AM_RANGE(0x0000, 0x1fff) AM_RAM
	AM_RANGE(0x2000, 0x2000) AM_MIRROR(0x03ff) AM_WRITE(bank_select_w)
	AM_RANGE(0x2400, 0x2401) AM_MIRROR(0x03fe) AM_DEVREADWRITE_LEGACY("ym2151", ym2151_r, ym2151_w)
	AM_RANGE(0x2800, 0x2800) AM_MIRROR(0x03ff) AM_DEVWRITE_LEGACY("dac", dac_w)
	AM_RANGE(0x2c00, 0x2c00) AM_MIRROR(0x03ff) AM_DEVREADWRITE("oki", okim6295_device, read, write)
	AM_RANGE(0x3000, 0x3000) AM_MIRROR(0x03ff) AM_READ(command_r)
	AM_RANGE(0x3400, 0x3400) AM_MIRROR(0x03ff) AM_WRITE(oki6295_bank_select_w)
	AM_RANGE(0x3c00, 0x3c00) AM_MIRROR(0x03ff) AM_WRITE(talkback_w)
	AM_RANGE(0x4000, 0xbfff) AM_ROMBANK("rombank")
	AM_RANGE(0xc000, 0xffff) AM_ROMBANK("romupper")
ADDRESS_MAP_END


//-------------------------------------------------
//  OKI6295 map
//-------------------------------------------------

static ADDRESS_MAP_START( williams_adpcm_oki_map, AS_0, 8, williams_adpcm_sound_device )
	AM_RANGE(0x00000, 0x1ffff) AM_ROMBANK("okibank")
	AM_RANGE(0x20000, 0x3ffff) AM_ROM AM_REGION("oki", 0x60000)
ADDRESS_MAP_END


//-------------------------------------------------
//  YM2151 configuration
//-------------------------------------------------

static const ym2151_interface adpcm_ym2151_interface =
{
	DEVCB_DEVICE_LINE_MEMBER(DEVICE_SELF_OWNER, williams_adpcm_sound_device, ym2151_irq_w)
};


//-------------------------------------------------
//  machine configuration
//-------------------------------------------------

static MACHINE_CONFIG_FRAGMENT( williams_adpcm_sound )
	MCFG_CPU_ADD("cpu", M6809E, ADPCM_MASTER_CLOCK)
	MCFG_CPU_PROGRAM_MAP(williams_adpcm_map)

	MCFG_SOUND_ADD("ym2151", YM2151, ADPCM_FM_CLOCK)
	MCFG_SOUND_CONFIG(adpcm_ym2151_interface)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.10)

	MCFG_SOUND_ADD("dac", DAC, 0)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.50)

	MCFG_OKIM6295_ADD("oki", ADPCM_MASTER_CLOCK/8, OKIM6295_PIN7_HIGH) // clock frequency & pin 7 not verified
	MCFG_DEVICE_ADDRESS_MAP(AS_0, williams_adpcm_oki_map)
	MCFG_SOUND_ROUTE(ALL_OUTPUTS, DEVICE_SELF_OWNER, 0.50)
MACHINE_CONFIG_END


//-------------------------------------------------
//  device_mconfig_additions - return a pointer to
//  the device's machine fragment
//-------------------------------------------------

machine_config_constructor williams_adpcm_sound_device::device_mconfig_additions() const
{
	return MACHINE_CONFIG_NAME( williams_adpcm_sound );
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void williams_adpcm_sound_device::device_start()
{
	// configure banks
	UINT8 *rom = memregion("cpu")->base();
	membank("rombank")->configure_entries(0, 8, &rom[0x10000], 0x8000);
	membank("romupper")->set_base(&rom[0x10000 + 0x4000 + 7 * 0x8000]);

	// expand ADPCM data
	rom = memregion("oki")->base();
	// it is assumed that U12 is loaded @ 0x00000 and U13 is loaded @ 0x40000
	membank("okibank")->configure_entry(0, &rom[0x40000]);
	membank("okibank")->configure_entry(1, &rom[0x40000]);
	membank("okibank")->configure_entry(2, &rom[0x20000]);
	membank("okibank")->configure_entry(3, &rom[0x00000]);
	membank("okibank")->configure_entry(4, &rom[0xe0000]);
	membank("okibank")->configure_entry(5, &rom[0xc0000]);
	membank("okibank")->configure_entry(6, &rom[0xa0000]);
	membank("okibank")->configure_entry(7, &rom[0x80000]);

	// register for save states
	save_item(NAME(m_latch));
	save_item(NAME(m_talkback));
	save_item(NAME(m_sound_int_state));
}


//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void williams_adpcm_sound_device::device_reset()
{
	// reset interrupt states
	m_sound_int_state = 0;
	m_cpu->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE);
	m_cpu->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);
	m_cpu->set_input_line(INPUT_LINE_NMI, CLEAR_LINE);
}


//-------------------------------------------------
//  device_timer - timer callbacks
//-------------------------------------------------

void williams_adpcm_sound_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch (id)
	{
		case TID_COMMAND:
			m_latch = param & 0xff;
			if (!(param & 0x200))
			{
				m_cpu->set_input_line(M6809_IRQ_LINE, ASSERT_LINE);
				m_sound_int_state = 1;
				machine().scheduler().boost_interleave(attotime::zero, attotime::from_usec(100));
			}
			break;

		case TID_IRQ_CLEAR:
			m_sound_int_state = 0;
			break;
	}
}
