// license:BSD-3-Clause
// copyright-holders:smf
#include "emu.h"
#include "idehd.h"

/***************************************************************************
    DEBUGGING
***************************************************************************/

#define VERBOSE                     0
#define PRINTF_IDE_COMMANDS         0
#define PRINTF_IDE_PASSWORD         0

#define LOGPRINT(x) do { if (VERBOSE) logerror x; if (PRINTF_IDE_COMMANDS) osd_printf_debug x; } while (0)

#define TIME_PER_SECTOR_WRITE               (attotime::from_usec(100))
#define TIME_PER_ROTATION                   (attotime::from_hz(5400/60))
#define TIME_BETWEEN_SECTORS                (attotime::from_nsec(400))

#define TIME_FULL_STROKE_SEEK               (attotime::from_usec(13000))
#define TIME_AVERAGE_ROTATIONAL_LATENCY     (attotime::from_usec(1300))

ata_mass_storage_device::ata_mass_storage_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: ata_hle_device(mconfig, type, tag, owner, clock),
	m_can_identify_device(0),
	m_num_cylinders(0),
	m_num_sectors(0),
	m_num_heads(0), m_cur_lba(0), m_block_count(0), m_sectors_until_int(0), m_master_password_enable(0), m_user_password_enable(0),
	m_master_password(nullptr),
	m_user_password(nullptr),
	m_dma_transfer_time(attotime::zero)
{
}

/*************************************
 *
 *  Compute the LBA address
 *
 *************************************/

uint32_t ata_mass_storage_device::lba_address()
{
	/* LBA direct? */
	if (m_device_head & IDE_DEVICE_HEAD_L)
		return ((m_device_head & IDE_DEVICE_HEAD_HS) << 24) | (m_cylinder_high << 16) | (m_cylinder_low << 8) | m_sector_number;

	/* standard CHS */
	else
		return (((((m_cylinder_high << 8) | m_cylinder_low) * m_num_heads) + (m_device_head & IDE_DEVICE_HEAD_HS)) * m_num_sectors) + m_sector_number - 1;
}


/*************************************
 *
 *  Build a features page
 *
 *************************************/

static void swap_strncpy(uint16_t *dst, const char *src, int field_size_in_words)
{
	for (int i = 0; i < field_size_in_words; i++)
	{
		uint16_t d;

		if (*src)
		{
			d = *(src++) << 8;
		}
		else
		{
			d = ' ' << 8;
		}

		if (*src)
		{
			d |= *(src++);
		}
		else
		{
			d |= ' ';
		}

		dst[i] = d;
	}

	assert(*(src) == 0);
}


void ata_mass_storage_device::ide_build_identify_device()
{
	memset(m_identify_buffer, 0, sizeof(m_identify_buffer));
	int total_sectors = m_num_cylinders * m_num_heads * m_num_sectors;

	/* basic geometry */
	m_identify_buffer[0] = 0x045a;                    /*  0: configuration bits */
	m_identify_buffer[1] = m_num_cylinders;           /*  1: logical cylinders */
	m_identify_buffer[2] = 0;                         /*  2: reserved */
	m_identify_buffer[3] = m_num_heads;               /*  3: logical heads */
	m_identify_buffer[4] = 0;                         /*  4: vendor specific (obsolete) */
	m_identify_buffer[5] = 0;                         /*  5: vendor specific (obsolete) */
	m_identify_buffer[6] = m_num_sectors;             /*  6: logical sectors per logical track */
	m_identify_buffer[7] = 0;                         /*  7: vendor-specific */
	m_identify_buffer[8] = 0;                         /*  8: vendor-specific */
	m_identify_buffer[9] = 0;                         /*  9: vendor-specific */
	swap_strncpy(&m_identify_buffer[10],               /* 10-19: serial number */
			"00000000000000000000", 10);
	m_identify_buffer[20] = 0;                         /* 20: vendor-specific */
	m_identify_buffer[21] = 0;                         /* 21: vendor-specific */
	m_identify_buffer[22] = 4;                         /* 22: # of vendor-specific bytes on read/write long commands */
	swap_strncpy(&m_identify_buffer[23],               /* 23-26: firmware revision */
			"1.0", 4);
	swap_strncpy(&m_identify_buffer[27],               /* 27-46: model number */
			"MAME Compressed Hard Disk", 20);
	m_identify_buffer[47] = 0x8010;                    /* 47: read/write multiple support, value from Seagate Quantum Fireball */
	m_identify_buffer[48] = 0;                         /* 48: reserved */
	m_identify_buffer[49] = 0x0f03;                    /* 49: capabilities */
	m_identify_buffer[50] = 0;                         /* 50: reserved */
	m_identify_buffer[51] = 2;                         /* 51: PIO data transfer cycle timing mode */
	m_identify_buffer[52] = 2;                         /* 52: single word DMA transfer cycle timing mode */
	m_identify_buffer[53] = 3;                         /* 53: field validity */
	m_identify_buffer[54] = m_num_cylinders;           /* 54: number of current logical cylinders */
	m_identify_buffer[55] = m_num_heads;               /* 55: number of current logical heads */
	m_identify_buffer[56] = m_num_sectors;             /* 56: number of current logical sectors per track */
	m_identify_buffer[57] = total_sectors & 0xffff;    /* 57-58: current capacity in sectors (ATA-1 through ATA-5; obsoleted in ATA-6) */
	m_identify_buffer[58] = total_sectors >> 16;
	m_identify_buffer[59] = 0;                         /* 59: multiple sector timing */
	m_identify_buffer[60] = total_sectors & 0xffff;    /* 60-61: total user addressable sectors for LBA mode (ATA-1 through ATA-7) */
	m_identify_buffer[61] = total_sectors >> 16;
	m_identify_buffer[62] = 0x0007;                    /* 62: single word dma transfer */
	m_identify_buffer[63] = 0x0407;                    /* 63: multiword DMA transfer */
	m_identify_buffer[64] = 0x0003;                    /* 64: flow control PIO transfer modes supported */
	m_identify_buffer[65] = 0x78;                      /* 65: minimum multiword DMA transfer cycle time per word */
	m_identify_buffer[66] = 0x78;                      /* 66: mfr's recommended multiword DMA transfer cycle time */
	m_identify_buffer[67] = 0x014d;                    /* 67: minimum PIO transfer cycle time without flow control */
	m_identify_buffer[68] = 0x78;                      /* 68: minimum PIO transfer cycle time with IORDY */
	m_identify_buffer[69] = 0x00;                      /* 69-70: reserved */
	m_identify_buffer[71] = 0x00;                      /* 71: reserved for IDENTIFY PACKET command */
	m_identify_buffer[72] = 0x00;                      /* 72: reserved for IDENTIFY PACKET command */
	m_identify_buffer[73] = 0x00;                      /* 73: reserved for IDENTIFY PACKET command */
	m_identify_buffer[74] = 0x00;                      /* 74: reserved for IDENTIFY PACKET command */
	m_identify_buffer[75] = 0x00;                      /* 75: queue depth */
	m_identify_buffer[76] = 0x00;                      /* 76-79: reserved */
	m_identify_buffer[80] = 0x00;                      /* 80: major version number */
	m_identify_buffer[81] = 0x00;                      /* 81: minor version number */
	m_identify_buffer[82] = 0x00;                      /* 82: command set supported */
	m_identify_buffer[83] = 0x00;                      /* 83: command sets supported */
	m_identify_buffer[84] = 0x00;                      /* 84: command set/feature supported extension */
	m_identify_buffer[85] = 0x00;                      /* 85: command set/feature enabled */
	m_identify_buffer[86] = 0x00;                      /* 86: command set/feature enabled */
	m_identify_buffer[87] = 0x00;                      /* 87: command set/feature default */
	m_identify_buffer[88] = 0x00;                      /* 88: additional DMA modes (ultra dma) */
	m_identify_buffer[89] = 0x00;                      /* 89: time required for security erase unit completion */
	m_identify_buffer[90] = 0x00;                      /* 90: time required for enhanced security erase unit completion */
	m_identify_buffer[91] = 0x00;                      /* 91: current advanced power management value */
	m_identify_buffer[92] = 0x00;                      /* 92: master password revision code */
	m_identify_buffer[93] = 0x00;                      /* 93: hardware reset result */
	m_identify_buffer[94] = 0x00;                      /* 94: acoustic management values */
	m_identify_buffer[95] = 0x00;                      /* 95-99: reserved */
	m_identify_buffer[100] = total_sectors & 0xffff;   /* 100-103: maximum 48-bit LBA */
	m_identify_buffer[101] = total_sectors >> 16;
	m_identify_buffer[102] = 0x00;
	m_identify_buffer[103] = 0x00;
	m_identify_buffer[104] = 0x00;                     /* 104-126: reserved */
	m_identify_buffer[127] = 0x00;                     /* 127: removable media status notification */
	m_identify_buffer[128] = 0x00;                     /* 128: security status */
	m_identify_buffer[129] = 0x00;                     /* 129-159: vendor specific */
	m_identify_buffer[160] = 0x00;                     /* 160: CFA power mode 1 */
	m_identify_buffer[161] = 0x00;                     /* 161-175: reserved for CompactFlash */
	m_identify_buffer[176] = 0x00;                     /* 176-205: current media serial number */
	m_identify_buffer[206] = 0x00;                     /* 206-254: reserved */
	m_identify_buffer[255] = 0x00;                     /* 255: integrity word */

	if (total_sectors >= 16514064)
	{
		/// CHS limit
		m_identify_buffer[1] = 16383;           /*  1: logical cylinders */
		m_identify_buffer[3] = 16;               /*  3: logical heads */
		m_identify_buffer[6] = 63;             /*  6: logical sectors per logical track */
		m_identify_buffer[54] = 16383;           /* 54: number of current logical cylinders */
		m_identify_buffer[55] = 16;               /* 55: number of current logical heads */
		m_identify_buffer[56] = 63;             /* 56: number of current logical sectors per track */
		m_identify_buffer[57] = 16514064 & 0xffff;    /* 57-58: current capacity in sectors (ATA-1 through ATA-5; obsoleted in ATA-6) */
		m_identify_buffer[58] = 16514064 >> 16;
	}

	if (total_sectors > 268435455)
	{
		/// LBA limit
		m_identify_buffer[60] = 268435455 & 0xffff;    /* 60-61: total user addressable sectors for LBA mode (ATA-1 through ATA-7) */
		m_identify_buffer[61] = 268435455 >> 16;
	}
}

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

void ata_mass_storage_device::device_start()
{
	ata_hle_device::device_start();

	save_item(NAME(m_can_identify_device));
	save_item(NAME(m_num_cylinders));
	save_item(NAME(m_num_sectors));
	save_item(NAME(m_num_heads));

	save_item(NAME(m_cur_lba));
	save_item(NAME(m_sectors_until_int));
	save_item(NAME(m_master_password_enable));
	save_item(NAME(m_user_password_enable));
	save_item(NAME(m_block_count));
}

void ata_mass_storage_device::soft_reset()
{
	ata_hle_device::soft_reset();

	m_cur_lba = 0;
	m_status |= IDE_STATUS_DSC;

	m_master_password_enable = (m_master_password != nullptr);
	m_user_password_enable = (m_user_password != nullptr);
}

void ata_mass_storage_device::perform_diagnostic()
{
	if (m_can_identify_device)
		m_error = IDE_ERROR_DIAGNOSTIC_PASSED;
}

void ata_mass_storage_device::signature()
{
	m_sector_count = 1;
	m_sector_number = 1;
	m_cylinder_low = 0;
	m_cylinder_high = 0;
	m_device_head = 0;
}

void ata_mass_storage_device::finished_command()
{
	int total_sectors = m_num_cylinders * m_num_heads * m_num_sectors;

	switch (m_command)
	{
	case IDE_COMMAND_IDENTIFY_DEVICE:
		if (m_can_identify_device)
		{
			for( int w = 0; w < 256; w++ )
			{
				m_buffer[w * 2] = m_identify_buffer[ w ] & 0xff;
				m_buffer[(w * 2) + 1] = m_identify_buffer[ w ] >> 8;
			}

			m_status |= IDE_STATUS_DRQ;
		}
		else
		{
			m_status |= IDE_STATUS_ERR;
			m_error = IDE_ERROR_NONE;
		}

		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_SET_CONFIG:
		set_geometry(m_sector_count,(m_device_head & IDE_DEVICE_HEAD_HS) + 1);
		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_READ_SECTORS:
	case IDE_COMMAND_READ_SECTORS_NORETRY:
	case IDE_COMMAND_READ_MULTIPLE:
	case IDE_COMMAND_VERIFY_SECTORS:
	case IDE_COMMAND_VERIFY_SECTORS_NORETRY:
	case IDE_COMMAND_READ_DMA:
		finished_read();
		break;

	case IDE_COMMAND_WRITE_SECTORS:
	case IDE_COMMAND_WRITE_SECTORS_NORETRY:
	case IDE_COMMAND_WRITE_MULTIPLE:
	case IDE_COMMAND_WRITE_DMA:
		finished_write();
		break;

	case IDE_COMMAND_RECALIBRATE:
		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_READ_NATIVE_MAX_ADDRESS:
		m_buffer[0] = (total_sectors & 0xff000000) >> 24;
		m_buffer[1] = (total_sectors & 0x00ff0000) >> 16;
		m_buffer[2] = (total_sectors & 0x0000ff00) >> 8;
		m_buffer[3] = (total_sectors & 0x000000ff);
		set_irq(ASSERT_LINE);
		break;

	default:
		ata_hle_device::finished_command();
		break;
	}
}

/*************************************
 *
 *  Advance to the next sector
 *
 *************************************/

void ata_mass_storage_device::next_sector()
{
	uint8_t cur_head = m_device_head & IDE_DEVICE_HEAD_HS;

	/* LBA direct? */
	if (m_device_head & IDE_DEVICE_HEAD_L)
	{
		m_sector_number++;
		if (m_sector_number == 0)
		{
			m_cylinder_low++;
			if (m_cylinder_low == 0)
			{
				m_cylinder_high++;
				if (m_cylinder_high == 0)
					cur_head++;
			}
		}
	}

	/* standard CHS */
	else
	{
		/* sectors are 1-based */
		m_sector_number++;
		if (m_sector_number > m_num_sectors)
		{
			/* heads are 0 based */
			m_sector_number = 1;
			cur_head++;
			if (cur_head >= m_num_heads)
			{
				cur_head = 0;
				m_cylinder_low++;
				if (m_cylinder_low == 0)
					m_cylinder_high++;
			}
		}
	}

	m_device_head = (m_device_head & ~IDE_DEVICE_HEAD_HS) | cur_head;

	m_cur_lba = lba_address();
}



/*************************************
 *
 *  security error handling
 *
 *************************************/

void ata_mass_storage_device::security_error()
{
	/* set error state */
	m_status |= IDE_STATUS_ERR;
	m_error = IDE_ERROR_NONE;
	m_status &= ~IDE_STATUS_DRDY;
}



/*************************************
 *
 *  Sector reading
 *
 *************************************/

attotime ata_mass_storage_device::seek_time()
{
	int sectors_per_cylinder =  m_num_heads * m_num_sectors;

	if (sectors_per_cylinder == 0 || m_num_cylinders == 0)
		return attotime::zero;

	int new_lba = lba_address();
	int old_cylinder = m_cur_lba / sectors_per_cylinder;
	int new_cylinder = new_lba / sectors_per_cylinder;
	int diff = abs(old_cylinder - new_cylinder);

	m_cur_lba = new_lba;

	if (diff == 0)
		return TIME_BETWEEN_SECTORS;

	attotime seek_time = (TIME_FULL_STROKE_SEEK * diff) / m_num_cylinders;

	return seek_time + TIME_AVERAGE_ROTATIONAL_LATENCY;
}

void ata_mass_storage_device::fill_buffer()
{
	switch (m_command)
	{
	case IDE_COMMAND_IDENTIFY_DEVICE:
		break;

	case IDE_COMMAND_READ_MULTIPLE:
		/* if there is more data to read, keep going */
		if (m_sector_count > 0)
			m_sector_count--;

		if (m_sector_count > 0)
		{
			// Read the next sector with no delay
			finished_read();
		}
		break;

	default:
		/* if there is more data to read, keep going */
		if (m_sector_count > 0)
			m_sector_count--;

		if (m_sector_count > 0)
		{
			set_dasp(ASSERT_LINE);
			if (m_command == IDE_COMMAND_READ_DMA)
				start_busy(TIME_BETWEEN_SECTORS + m_dma_transfer_time, PARAM_COMMAND);
			else
				start_busy(TIME_BETWEEN_SECTORS, PARAM_COMMAND);
		}
		break;
	}
}


void ata_mass_storage_device::finished_read()
{
	int lba = lba_address(), read_status;

	set_dasp(CLEAR_LINE);

	/* now do the read */
	read_status = read_sector(lba, &m_buffer[0]);

	/* if we succeeded, advance to the next sector and set the nice bits */
	if (read_status)
	{
		/* advance the pointers, unless this is the last sector */
		/* Gauntlet: Dark Legacy checks to make sure we stop on the last sector */
		if (m_sector_count != 1)
			next_sector();

		/* signal an interrupt, IDE_COMMAND_READ_MULTIPLE sets the interrupt at the start the block */
		if (--m_sectors_until_int == 0 || (m_sector_count == 1 && m_command != IDE_COMMAND_READ_MULTIPLE))
		{
			m_sectors_until_int = ((m_command == IDE_COMMAND_READ_MULTIPLE) ? m_block_count : 1);
			set_irq(ASSERT_LINE);
		}

		/* if we're just verifying we can read the next sector */
		if (m_command == IDE_COMMAND_VERIFY_SECTORS ||
			m_command == IDE_COMMAND_VERIFY_SECTORS_NORETRY )
		{
			read_buffer_empty();
		}
		else
		{
			m_status |= IDE_STATUS_DRQ;

			if (m_command == IDE_COMMAND_READ_DMA)
				set_dmarq(ASSERT_LINE);
		}
	}

	/* if we got an error, we need to report it */
	else
	{
		/* set the error flag and the error */
		m_status |= IDE_STATUS_ERR;
		m_error = IDE_ERROR_BAD_SECTOR;

		/* signal an interrupt */
		set_irq(ASSERT_LINE);
	}
}


void ata_mass_storage_device::read_first_sector()
{
	if (m_master_password_enable || m_user_password_enable)
	{
		security_error();
	}
	else
	{
		set_dasp(ASSERT_LINE);

		start_busy(seek_time(), PARAM_COMMAND);
	}
}

/*************************************
 *
 *  Sector writing
 *
 *************************************/

void ata_mass_storage_device::process_buffer()
{
	if (m_command == IDE_COMMAND_SECURITY_UNLOCK)
	{
		if (m_user_password_enable && memcmp(&m_buffer[0], m_user_password, 2 + 32) == 0)
		{
			LOGPRINT(("IDE Unlocked user password\n"));
			m_user_password_enable = 0;
		}
		if (m_master_password_enable && memcmp(&m_buffer[0], m_master_password, 2 + 32) == 0)
		{
			LOGPRINT(("IDE Unlocked master password\n"));
			m_master_password_enable = 0;
		}
		if (PRINTF_IDE_PASSWORD)
		{
			int i;

			for (i = 0; i < 34; i += 2)
			{
				if (i % 8 == 2)
					osd_printf_debug("\n");

				osd_printf_debug("0x%02x, 0x%02x, ", m_buffer[i], m_buffer[i + 1]);
				//osd_printf_debug("0x%02x%02x, ", m_buffer[i], m_buffer[i + 1]);
			}
			osd_printf_debug("\n");
		}

		if (m_master_password_enable || m_user_password_enable)
			security_error();
	}
	else if (m_command == IDE_COMMAND_SECURITY_DISABLE_PASSWORD)
	{
		LOGPRINT(("IDE Done unimplemented SECURITY_DISABLE_PASSWORD command\n"));
	}
	else
	{
		set_dasp(ASSERT_LINE);

		if (m_command == IDE_COMMAND_WRITE_MULTIPLE)
		{
			if (m_sectors_until_int != 1)
			{
				/* ready to write now */
				finished_write();
			}
			else
			{
				/* set a timer to do the write */
				start_busy(TIME_PER_SECTOR_WRITE, PARAM_COMMAND);
			}
		}
		else
		{
			/* set a timer to do the write */
			start_busy(TIME_PER_SECTOR_WRITE, PARAM_COMMAND);
		}
	}
}


void ata_mass_storage_device::finished_write()
{
	int lba = lba_address(), count;

	set_dasp(CLEAR_LINE);

	/* now do the write */
	count = write_sector(lba, &m_buffer[0]);

	/* if we succeeded, advance to the next sector and set the nice bits */
	if (count == 1)
	{
		/* advance the pointers, unless this is the last sector */
		/* Gauntlet: Dark Legacy checks to make sure we stop on the last sector */
		if (m_sector_count != 1)
			next_sector();

		/* signal an interrupt */
		if (--m_sectors_until_int == 0 || m_sector_count == 1)
		{
			m_sectors_until_int = ((m_command == IDE_COMMAND_WRITE_MULTIPLE) ? m_block_count : 1);
			set_irq(ASSERT_LINE);
		}

		/* signal an interrupt if there's more data needed */
		if (m_sector_count > 0)
			m_sector_count--;

		if (m_sector_count > 0)
		{
			m_status |= IDE_STATUS_DRQ;

			if (m_command == IDE_COMMAND_WRITE_DMA)
				set_dmarq(ASSERT_LINE);
		}
	}

	/* if we got an error, we need to report it */
	else
	{
		/* set the error flag and the error */
		m_status |= IDE_STATUS_ERR;
		m_error = IDE_ERROR_BAD_SECTOR;

		/* signal an interrupt */
		set_irq(ASSERT_LINE);
	}
}


/*************************************
 *
 *  Handle IDE commands
 *
 *************************************/

void ata_mass_storage_device::process_command()
{
	m_sectors_until_int = 0;
	m_buffer_size = IDE_DISK_SECTOR_SIZE;

	switch (m_command)
	{
	case IDE_COMMAND_READ_SECTORS:
	case IDE_COMMAND_READ_SECTORS_NORETRY:
		LOGPRINT(("IDE Read multiple: C=%u H=%d S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		m_sectors_until_int = 1;

		/* start the read going */
		read_first_sector();
		break;

	case IDE_COMMAND_READ_MULTIPLE:
		LOGPRINT(("IDE Read multiple block: C=%u H=%u S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		m_sectors_until_int = 1;

		/* start the read going */
		read_first_sector();
		break;

	case IDE_COMMAND_VERIFY_SECTORS:
	case IDE_COMMAND_VERIFY_SECTORS_NORETRY:
		LOGPRINT(("IDE Read verify multiple with/without retries: C=%u H=%u S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		/* reset the buffer */
		m_sectors_until_int = m_sector_count;

		/* start the read going */
		read_first_sector();
		break;

	case IDE_COMMAND_READ_DMA:
		LOGPRINT(("IDE Read multiple DMA: C=%u H=%u S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		/* reset the buffer */
		m_sectors_until_int = m_sector_count;

		/* start the read going */
		read_first_sector();
		break;

	case IDE_COMMAND_WRITE_SECTORS:
	case IDE_COMMAND_WRITE_SECTORS_NORETRY:
		LOGPRINT(("IDE Write multiple: C=%u H=%u S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		/* reset the buffer */
		m_sectors_until_int = 1;

		/* mark the buffer ready */
		m_status |= IDE_STATUS_DRQ;
		break;

	case IDE_COMMAND_WRITE_MULTIPLE:
		LOGPRINT(("IDE Write multiple block: C=%u H=%u S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		/* reset the buffer */
		m_sectors_until_int = m_block_count;

		/* mark the buffer ready */
		m_status |= IDE_STATUS_DRQ;
		break;

	case IDE_COMMAND_WRITE_DMA:
		LOGPRINT(("IDE Write multiple DMA: C=%u H=%u S=%u LBA=%u count=%u\n",
			(m_cylinder_high << 8) | m_cylinder_low, m_device_head & IDE_DEVICE_HEAD_HS, m_sector_number, lba_address(), m_sector_count));

		/* reset the buffer */
		m_sectors_until_int = m_sector_count;

		/* mark the buffer ready */
		m_status |= IDE_STATUS_DRQ;

		/* start the read going */
		set_dmarq(ASSERT_LINE);
		break;

	case IDE_COMMAND_SECURITY_UNLOCK:
		LOGPRINT(("IDE Security Unlock\n"));

		/* mark the buffer ready */
		m_status |= IDE_STATUS_DRQ;

		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_SECURITY_DISABLE_PASSWORD:
		LOGPRINT(("IDE Unimplemented SECURITY DISABLE PASSWORD command\n"));

		/* mark the buffer ready */
		m_status |= IDE_STATUS_DRQ;

		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_IDENTIFY_DEVICE:
		LOGPRINT(("IDE Identify device\n"));

		start_busy(MINIMUM_COMMAND_TIME, PARAM_COMMAND);
		break;

	case IDE_COMMAND_RECALIBRATE:
		start_busy(MINIMUM_COMMAND_TIME, PARAM_COMMAND);
		break;

	case IDE_COMMAND_IDLE:
		/* signal an interrupt */
		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_SET_CONFIG:
		LOGPRINT(("IDE Set configuration (%u heads, %u sectors)\n", (m_device_head & IDE_DEVICE_HEAD_HS) + 1, m_sector_count));

		start_busy(MINIMUM_COMMAND_TIME, PARAM_COMMAND);
		break;

	case IDE_COMMAND_SET_MAX:
		LOGPRINT(("IDE Set max (%02X %02X %02X %02X %02X)\n", m_feature, m_sector_count & 0xff, m_sector_number, m_cylinder_low, m_cylinder_high));

		/* signal an interrupt */
		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_SET_BLOCK_COUNT:
		LOGPRINT(("IDE Set block count (%u)\n", m_sector_count));

		m_block_count = m_sector_count;

		/* signal an interrupt */
		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_SEEK:
		/* signal an interrupt */
		set_irq(ASSERT_LINE);
		break;

	case IDE_COMMAND_READ_NATIVE_MAX_ADDRESS:
		start_busy(MINIMUM_COMMAND_TIME, PARAM_COMMAND);
		break;

	default:
		ata_hle_device::process_command();
		break;
	}
}

//**************************************************************************
//  IDE HARD DISK DEVICE
//**************************************************************************

// device type definition
DEFINE_DEVICE_TYPE(IDE_HARDDISK, ide_hdd_device, "idehd", "IDE Hard Disk")

//-------------------------------------------------
//  ide_hdd_device - constructor
//-------------------------------------------------

ide_hdd_device::ide_hdd_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: ide_hdd_device(mconfig, IDE_HARDDISK, tag, owner, clock)
{
}

ide_hdd_device::ide_hdd_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
	: ata_mass_storage_device(mconfig, type, tag, owner, clock),
	m_image(*this, "image")
{
}

void ide_hdd_device::device_start()
{
	ata_mass_storage_device::device_start();

	/* create a timer for timing status */
	m_last_status_timer = timer_alloc(TID_NULL);
}

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

void ide_hdd_device::device_reset()
{
	m_handle = m_image->get_chd_file();
	m_disk = m_image->get_hard_disk_file();

	if (m_disk != nullptr && !m_can_identify_device)
	{
		const hard_disk_info *hdinfo = hard_disk_get_info(m_disk);
		if (hdinfo->sectorbytes == IDE_DISK_SECTOR_SIZE)
		{
			m_num_cylinders = hdinfo->cylinders;
			m_num_sectors = hdinfo->sectors;
			m_num_heads = hdinfo->heads;
			if (PRINTF_IDE_COMMANDS) osd_printf_debug("CHS: %u %u %u\n", m_num_cylinders, m_num_heads, m_num_sectors);
			osd_printf_debug("CHS: %u %u %u\n", m_num_cylinders, m_num_heads, m_num_sectors);
		}

		// build the features page
		uint32_t metalength;
		if (m_handle && !m_handle->read_metadata(HARD_DISK_IDENT_METADATA_TAG, 0, &m_buffer[0], 512, metalength))
		{
			for( int w = 0; w < 256; w++ )
			{
				m_identify_buffer[w] = (m_buffer[(w * 2) + 1] << 8) | m_buffer[w * 2];
			}
		}
		else
		{
			ide_build_identify_device();
		}

		m_can_identify_device = 1;
	}

	ata_mass_storage_device::device_reset();
}

uint8_t ide_hdd_device::calculate_status()
{
	uint8_t result = ata_hle_device::calculate_status();

	if (m_last_status_timer->elapsed() > TIME_PER_ROTATION)
	{
		result |= IDE_STATUS_IDX;
		m_last_status_timer->adjust(attotime::never);
	}

	return result;
}

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

void ide_hdd_device::device_add_mconfig(machine_config &config)
{
	HARDDISK(config, "image", "ide_hdd");
}
