// license:BSD-3-Clause
// copyright-holders:Nigel Barnes
/**********************************************************************

    Opus Floppy Disc Controllers

    8272: https://www.youtube.com/watch?v=09alLIz16ck
    EDOS: http://chrisacorns.computinghistory.org.uk/8bit_Upgrades/Opus_DiscController.html
    2791: http://chrisacorns.computinghistory.org.uk/8bit_Upgrades/Opus_DDoss.html
    2793:
    1770: http://chrisacorns.computinghistory.org.uk/8bit_Upgrades/Opus_D-DOS.html

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

#include "emu.h"
#include "opus.h"

#include "formats/acorn_dsk.h"
#include "formats/fsd_dsk.h"


//**************************************************************************
//  DEVICE DEFINITIONS
//**************************************************************************

DEFINE_DEVICE_TYPE(BBC_OPUS8272, bbc_opus8272_device, "bbc_opus8272", "Opus 8272 FDC")
DEFINE_DEVICE_TYPE(BBC_OPUS2791, bbc_opus2791_device, "bbc_opus2791", "Opus 2791 FDC")
DEFINE_DEVICE_TYPE(BBC_OPUS2793, bbc_opus2793_device, "bbc_opus2793", "Opus 2793 FDC")
DEFINE_DEVICE_TYPE(BBC_OPUS1770, bbc_opus1770_device, "bbc_opus1770", "Opus D-DOS(B) 1770 FDC")


//-------------------------------------------------
//  FLOPPY_FORMATS( floppy_formats )
//-------------------------------------------------

void bbc_opusfdc_device::floppy_formats(format_registration &fr)
{
	fr.add_mfm_containers();
	fr.add(FLOPPY_ACORN_SSD_FORMAT);
	fr.add(FLOPPY_ACORN_DSD_FORMAT);
	fr.add(FLOPPY_FSD_FORMAT);
	fr.add(FLOPPY_OPUS_DDOS_FORMAT);
	fr.add(FLOPPY_OPUS_DDCPM_FORMAT);
}

static void bbc_floppies_525(device_slot_interface &device)
{
	device.option_add("525sssd", FLOPPY_525_SSSD);
	device.option_add("525sd",   FLOPPY_525_SD);
	device.option_add("525ssdd", FLOPPY_525_SSDD);
	device.option_add("525dd",   FLOPPY_525_DD);
	device.option_add("525qd",   FLOPPY_525_QD);
}

//-------------------------------------------------
//  ROM( opus )
//-------------------------------------------------

ROM_START( opus8272 )
	ROM_REGION(0x4000, "dfs_rom", 0)
	ROM_DEFAULT_BIOS("ddos305")
	ROM_SYSTEM_BIOS(0, "ddos300", "Opus DDOS 3.00")
	ROMX_LOAD("opus-ddos300.rom", 0x0000, 0x4000, CRC(1b5fa131) SHA1(6b4e0363a9d39807973a2ef0871a78b287cea27e), ROM_BIOS(0))
	ROM_SYSTEM_BIOS(1, "ddos305", "Opus DDOS 3.05")
	ROMX_LOAD("opus-ddos305.rom", 0x0000, 0x4000, CRC(43c75fa4) SHA1(0b7194a234c2316ba825e878e3f69928bf3bb595), ROM_BIOS(1))
ROM_END

ROM_START( opus2791 )
	ROM_REGION(0x4000, "dfs_rom", 0)
	ROM_DEFAULT_BIOS("ddos316")
	ROM_SYSTEM_BIOS(0, "ddos312", "Opus DDOS 3.12")
	ROMX_LOAD("opus-ddos312.rom", 0x0000, 0x4000, CRC(b2c393be) SHA1(6accaa0b13b0b939c86674cddd7bee1aea2f66cb), ROM_BIOS(0))
	ROM_SYSTEM_BIOS(1, "ddos315", "Opus DDOS 3.15")
	ROMX_LOAD("opus-ddos315.rom", 0x0000, 0x4000, CRC(5f06701c) SHA1(9e250dc7ddcde35b19e8f29f2cfe95a79f46d473), ROM_BIOS(1))
	ROM_SYSTEM_BIOS(2, "ddos316", "Opus DDOS 3.16")
	ROMX_LOAD("opus-ddos316.rom", 0x0000, 0x4000, CRC(268ebc0d) SHA1(e608f6e40a5579147cc631f351aae275fdabec5b), ROM_BIOS(2))
	ROM_SYSTEM_BIOS(3, "edos04", "Opus EDOS 0.4")
	ROMX_LOAD("opus-edos04.rom", 0x0000, 0x4000, CRC(1d8a3860) SHA1(05f461464707b4ca24636c9e726af561f227ccdb), ROM_BIOS(3))
ROM_END

ROM_START( opus2793 )
	ROM_REGION(0x4000, "dfs_rom", 0)
	ROM_DEFAULT_BIOS("ddos336")
	ROM_SYSTEM_BIOS(0, "ddos335", "Opus DDOS 3.35")
	ROMX_LOAD("opus-ddos335.rom", 0x0000, 0x4000, CRC(e33167fb) SHA1(42fbc9932db2087708da41cb1ffa94358683cf7a), ROM_BIOS(0))
	ROM_SYSTEM_BIOS(1, "ddos336", "Opus DDOS 3.36")
	ROMX_LOAD("opus-ddos336.rom", 0x0000, 0x4000, CRC(2f400f69) SHA1(a6a57bb907d6b9bd351029fb0471a3a9c343da24), ROM_BIOS(1))
ROM_END

ROM_START( opus1770 )
	ROM_REGION(0x4000, "dfs_rom", 0)
	ROM_DEFAULT_BIOS("ddos346")
	ROM_SYSTEM_BIOS(0, "ddos345", "Opus DDOS 3.45")
	ROMX_LOAD("opus-ddos345.rom", 0x0000, 0x4000, CRC(c0163b95) SHA1(1c5a68e08abbb7ffe663151c59088f750d2287a9), ROM_BIOS(0))
	ROM_SYSTEM_BIOS(1, "ddos346", "Opus DDOS 3.46")
	ROMX_LOAD("opus-ddos346.rom", 0x0000, 0x4000, CRC(bf9c35cf) SHA1(a1ad3e9acbd15400e7da1e50bc6673835cde1fe7), ROM_BIOS(1))
ROM_END


//-------------------------------------------------
//  device_add_mconfig - add device configuration
//-------------------------------------------------

void bbc_opus8272_device::device_add_mconfig(machine_config &config)
{
	I8272A(config, m_fdc, 20_MHz_XTAL / 5, true);
	m_fdc->intrq_wr_callback().set(DEVICE_SELF_OWNER, FUNC(bbc_fdc_slot_device::intrq_w));

	FLOPPY_CONNECTOR(config, m_floppy[0], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
	FLOPPY_CONNECTOR(config, m_floppy[1], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
}

void bbc_opus2791_device::device_add_mconfig(machine_config &config)
{
	WD2791(config, m_fdc, DERIVED_CLOCK(1, 8));
	m_fdc->drq_wr_callback().set(DEVICE_SELF_OWNER, FUNC(bbc_fdc_slot_device::drq_w));
	m_fdc->hld_wr_callback().set(FUNC(bbc_opusfdc_device::motor_w));

	FLOPPY_CONNECTOR(config, m_floppy[0], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
	FLOPPY_CONNECTOR(config, m_floppy[1], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
}

void bbc_opus2793_device::device_add_mconfig(machine_config &config)
{
	WD2793(config, m_fdc, DERIVED_CLOCK(1, 8));
	m_fdc->drq_wr_callback().set(DEVICE_SELF_OWNER, FUNC(bbc_fdc_slot_device::drq_w));
	m_fdc->hld_wr_callback().set(FUNC(bbc_opusfdc_device::motor_w));

	FLOPPY_CONNECTOR(config, m_floppy[0], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
	FLOPPY_CONNECTOR(config, m_floppy[1], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
}

void bbc_opus1770_device::device_add_mconfig(machine_config &config)
{
	WD1770(config, m_fdc, DERIVED_CLOCK(1, 1));
	m_fdc->drq_wr_callback().set(DEVICE_SELF_OWNER, FUNC(bbc_fdc_slot_device::drq_w));

	FLOPPY_CONNECTOR(config, m_floppy[0], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
	FLOPPY_CONNECTOR(config, m_floppy[1], bbc_floppies_525, "525qd", bbc_opusfdc_device::floppy_formats).enable_sound(true);
}

const tiny_rom_entry *bbc_opus8272_device::device_rom_region() const
{
	return ROM_NAME( opus8272 );
}

const tiny_rom_entry *bbc_opus2791_device::device_rom_region() const
{
	return ROM_NAME( opus2791 );
}

const tiny_rom_entry *bbc_opus2793_device::device_rom_region() const
{
	return ROM_NAME( opus2793 );
}

const tiny_rom_entry *bbc_opus1770_device::device_rom_region() const
{
	return ROM_NAME( opus1770 );
}


//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  bbc_opusfdc_device - constructor
//-------------------------------------------------

bbc_opus8272_device::bbc_opus8272_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, BBC_OPUS8272, tag, owner, clock),
	device_bbc_fdc_interface(mconfig, *this),
	m_fdc(*this, "i8272"),
	m_floppy(*this, "i8272:%u", 0)
{
}

bbc_opusfdc_device::bbc_opusfdc_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
	device_t(mconfig, type, tag, owner, clock),
	device_bbc_fdc_interface(mconfig, *this),
	m_fdc(*this, "fdc"),
	m_floppy(*this, "fdc:%u", 0),
	m_drive_control(0)
{
}

bbc_opus2791_device::bbc_opus2791_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: bbc_opusfdc_device(mconfig, BBC_OPUS2791, tag, owner, clock)
{
}

bbc_opus2793_device::bbc_opus2793_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: bbc_opusfdc_device(mconfig, BBC_OPUS2793, tag, owner, clock)
{
}

bbc_opus1770_device::bbc_opus1770_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: bbc_opusfdc_device(mconfig, BBC_OPUS1770, tag, owner, clock)
{
}

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

void bbc_opus8272_device::device_start()
{
}

void bbc_opusfdc_device::device_start()
{
	save_item(NAME(m_drive_control));
}


//**************************************************************************
//  IMPLEMENTATION
//**************************************************************************

uint8_t bbc_opus8272_device::read(offs_t offset)
{
	uint8_t data = 0xff;

	switch (offset & 0x07)
	{
	case 0x02:
		m_fdc->tc_w(true);
		m_fdc->tc_w(false);
		break;

	case 0x06:
		if (m_floppy[0]->get_device()) m_floppy[0]->get_device()->mon_w(1);
		if (m_floppy[1]->get_device()) m_floppy[1]->get_device()->mon_w(1);
		[[fallthrough]];
	case 0x04:
		data = m_fdc->msr_r();
		break;

	case 0x05:
		if (m_floppy[0]->get_device()) m_floppy[0]->get_device()->mon_w(0);
		if (m_floppy[1]->get_device()) m_floppy[1]->get_device()->mon_w(0);
		[[fallthrough]];
	case 0x07:
		data = m_fdc->fifo_r();
		break;
	}
	return data;
}

void bbc_opus8272_device::write(offs_t offset, uint8_t data)
{
	floppy_image_device *floppy = nullptr;

	switch (offset & 0x07)
	{
	case 0x01:
		switch (data & 0x01)
		{
		case 0: floppy = m_floppy[1]->get_device(); break;
		case 1: floppy = m_floppy[0]->get_device(); break;
		}
		m_fdc->set_floppy(floppy);
		break;

	case 0x05:
		if (m_floppy[0]->get_device()) m_floppy[0]->get_device()->mon_w(0);
		if (m_floppy[1]->get_device()) m_floppy[1]->get_device()->mon_w(0);
		[[fallthrough]];
	case 0x07:
		m_fdc->fifo_w(data);
		break;
	}
}


uint8_t bbc_opusfdc_device::read(offs_t offset)
{
	uint8_t data;

	if (offset & 0x04)
	{
		data = m_drive_control;
	}
	else
	{
		data = m_fdc->read(offset & 0x03);
	}
	return data;
}

void bbc_opusfdc_device::write(offs_t offset, uint8_t data)
{
	if (offset & 0x04)
	{
		floppy_image_device *floppy = nullptr;

		m_drive_control = data;

		// bit 0: drive select
		switch (BIT(data, 0))
		{
		case 0: floppy = m_floppy[0]->get_device(); break;
		case 1: floppy = m_floppy[1]->get_device(); break;
		}
		m_fdc->set_floppy(floppy);

		// bit 1: side select
		if (floppy)
			floppy->ss_w(BIT(data, 1));

		// bit 6: density
		m_fdc->dden_w(!BIT(data, 6));
	}
	else
	{
		m_fdc->write(offset & 0x03, data);
	}
}

void bbc_opusfdc_device::motor_w(int state)
{
	if (m_floppy[0]->get_device()) m_floppy[0]->get_device()->mon_w(!state);
	if (m_floppy[1]->get_device()) m_floppy[1]->get_device()->mon_w(!state);
}
