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

    wd17xx.c

    Implementations of the Western Digital 17xx and 27xx families of
    floppy disk controllers


    Models:

              DAL   DD   Side   Clock       Remark
      ---------------------------------------------------------
      FD1771                    1 or 2 MHz  First model
      FD1781         x          1 or 2 MHz
      FD1791         x          1 or 2 MHz
      FD1792                    1 or 2 MHz
      FD1793   x     x          1 or 2 MHz
      FD1794   x                1 or 2 MHz
      FD1795         x     x    1 or 2 MHz
      FD1797   x     x     x    1 or 2 MHz
      FD1761         x          1 MHz
      FD1762                    1 MHz       ?
      FD1763   x     x          1 MHz
      FD1764   x                1 MHz       ?
      FD1765         x     x    1 MHz
      FD1767   x     x     x    1 MHz
      WD2791         x          1 or 2 MHz  Internal data separator
      WD2793   x     x          1 or 2 MHz  Internal data separator
      WD2795         x     x    1 or 2 MHz  Internal data separator
      WD2797   x     x     x    1 or 2 MHz  Internal data separator
      WD1770   x     x          8 MHz       Motor On signal
      WD1772   x     x          8 MHz       Motor On signal, Faster stepping rates
      WD1773   x     x          8 MHz       Enable precomp line

      Notes: - In general, later models include all features of earlier models
             - DAL: Data access lines, x = TRUE; otherwise inverted
             - DD: Double density support
             - Side: Has side select support
             - ?: Unknown if it really exists

    Clones:

      - SMC FD179x
      - Fujitsu MB8876 -> FD1791, MB8877 -> FD1793
      - VLSI VL177x


    Changelog:

    Kevin Thacker
        - Removed disk image code and replaced it with floppy drive functions.
          Any disc image is now useable with this code.
        - Fixed write protect

    2005-Apr-16 P.Harvey-Smith:
        - Increased the delay in wd17xx_timed_read_sector_request and
          wd17xx_timed_write_sector_request, to 40us to more closely match
          what happens on the real hardware, this has fixed the NitrOS9 boot
          problems.

    2007-Nov-01 Wilbert Pol:
        Needed these changes to get the MB8877 for Osborne-1 to work:
        - Added support for multiple record read
        - Changed the wd17xx_read_id to not return after DATADONEDELAY, but
          the host should read the id data through the data register. This
          was accomplished by making this change in the wd17xx_read_id
          function:
            -               wd17xx_complete_command(device, DELAY_DATADONE);
            +               wd17xx_set_data_request();

    2009-May-10 Robbbert:
        Further change to get the Kaypro II to work
        - When wd17xx_read_id has generated the 6 data bytes, it should make
          an IRQ and turn off the busy status. The timing for Osborne is
          critical, it must be between 300 and 700 usec, I've settled on 400.
          The Kaypro doesn't care timewise, but the busy flag must turn off
          sometime or it hangs.
            -       w->status |= STA_2_BUSY;
            +       wd17xx_set_busy(device, attotime::from_usec(400));

    2009-June-4 Roberto Lavarone:
        - Initial support for wd1771
        - Added simulation of head loaded feedback from drive
        - Bugfix: busy flag was cleared too early

    2009-June-21 Robbbert:
    - The Bugfix above, while valid, caused the Osborne1 to fail. This
      is because the delay must not exceed 14usec (found by extensive testing).
    - The minimum delay is 1usec, need by z80netf while formatting a disk.
    - http://www.bannister.org/forums/ubbthreads.php?ubb=showflat&Number=50889#Post50889
      explains the problems, testing done, and the test procedure for the z80netf.
    - Thus, the delay is set to 10usec, and all the disks I have (not many)
      seem to work.
    - Note to anyone who wants to change something: Make sure that the
      Osborne1 boots up! It is extremely sensitive to timing!
    - For testing only: The osborne1 rom can be patched to make it much
      more stable, by changing the byte at 0x0da7 from 0x28 to 0x18.

    2009-June-25 Robbbert:
    - Turns out kayproii not working, 10usec is too short.. but 11usec is ok.
      Setting it to 12usec.
      Really, this whole thing needs a complete rewrite.

    2009-July-08 Roberto Lavarone:
    - Fixed a bug in head load flag handling: einstein and samcoupe now working again

    2009-September-30 Robbbert:
    - Modified what status flags are returned after a Forced Interrupt,
      to allow Microbee to boot CP/M.

    2010-Jan-31 Phill Harvey-Smith
    - The above bugfixes for the Kaypro/Osborne1 have borked the booting on the Dragon
      Alpha. The Alpha it seems needs a delay of ay least 17us or the NMI generated by
      INTRQ happens too early and doen't break out of the read/write bytes loops.

      I have made the delay settable by calling wd17xx_set_complete_command_delay, and
      let it default to 12us, as required above, so that the Dragon Alpha works again.
      This hopefully should not break the other machines.

      This should probably be considdered a minor hack but it does seem to work !

    2010-02-04 Phill Harvey-Smith
    - Added multiple sector write as the RM Nimbus needs it.

    2010-March-22 Curt Coder:
    - Implemented immediate and index pulse interrupts.

    2010-Dec-31 Phill Harvey-Smith
    - Copied multi-sector write code from r7263, for some reason this had been
      silently removed, but is required for the rmnimbus driver.

    2011-Mar-08 Phill Harvey-Smith
    - Triggering intrq now clears the DRQ bit in the status as well as the busy bit.
      Execution of the READ_DAM command now correctly sets w->command.

    2011-Apr-01 Curt Coder
    - Set complete command delay to 16 usec (DD) / 32 usec (SD) and removed
      the external delay setting hack.

    2011-Jun-24 Curt Coder
    - Added device types for all known variants, and enforced inverted DAL lines.

    2011-Sep-18 Curt Coder
    - Connected Side Select Output for variants that support it.

    TODO:
        - What happens if a track is read that doesn't have any id's on it?
         (e.g. unformatted disc)
        - Rewrite into a C++ device

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


#include "emu.h"
#include "formats/imageutl.h"
#include "machine/wd17xx.h"
#include "imagedev/flopdrv.h"

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

#define VERBOSE			0	/* General logging */
#define VERBOSE_DATA	0	/* Logging of each byte during read and write */

#define DELAY_ERROR		3
#define DELAY_NOTREADY	1
#define DELAY_DATADONE	3

#define TYPE_I			1
#define TYPE_II 		2
#define TYPE_III		3
#define TYPE_IV 		4

#define FDC_STEP_RATE   0x03    /* Type I additional flags */
#define FDC_STEP_VERIFY 0x04	/* verify track number */
#define FDC_STEP_HDLOAD 0x08	/* load head */
#define FDC_STEP_UPDATE 0x10	/* update track register */

#define FDC_RESTORE 	0x00	/* Type I commands */
#define FDC_SEEK		0x10
#define FDC_STEP		0x20
#define FDC_STEP_IN 	0x40
#define FDC_STEP_OUT	0x60

#define FDC_MASK_TYPE_I 		(FDC_STEP_HDLOAD|FDC_STEP_VERIFY|FDC_STEP_RATE)

/* Type I commands status */
#define STA_1_BUSY		0x01	/* controller is busy */
#define STA_1_IPL		0x02	/* index pulse */
#define STA_1_TRACK0	0x04	/* track 0 detected */
#define STA_1_CRC_ERR	0x08	/* CRC error */
#define STA_1_SEEK_ERR	0x10	/* seek error */
#define STA_1_HD_LOADED 0x20	/* head loaded */
#define STA_1_WRITE_PRO 0x40	/* floppy is write protected */
#define STA_1_NOT_READY 0x80	/* drive not ready */
#define STA_1_MOTOR_ON	0x80	/* status of the Motor On output (WD1770 and WD1772 only) */

/* Type II and III additional flags */
#define FDC_DELETED_AM	0x01	/* read/write deleted address mark */
#define FDC_SIDE_CMP_T	0x02	/* side compare track data */
#define FDC_15MS_DELAY	0x04	/* delay 15ms before command */
#define FDC_SIDE_CMP_S	0x08	/* side compare sector data */
#define FDC_MULTI_REC	0x10	/* only for type II commands */

/* Type II commands */
#define FDC_READ_SEC	0x80	/* read sector */
#define FDC_WRITE_SEC	0xA0	/* write sector */

#define FDC_MASK_TYPE_II		(FDC_MULTI_REC|FDC_SIDE_CMP_S|FDC_15MS_DELAY|FDC_SIDE_CMP_T|FDC_DELETED_AM)

/* Type II commands status */
#define STA_2_BUSY		0x01
#define STA_2_DRQ		0x02
#define STA_2_LOST_DAT	0x04
#define STA_2_CRC_ERR	0x08
#define STA_2_REC_N_FND 0x10
#define STA_2_REC_TYPE	0x20
#define STA_2_WRITE_PRO 0x40
#define STA_2_NOT_READY 0x80

#define FDC_MASK_TYPE_III		(FDC_SIDE_CMP_S|FDC_15MS_DELAY|FDC_SIDE_CMP_T|FDC_DELETED_AM)

/* Type III commands */
#define FDC_READ_DAM	0xc0	/* read data address mark */
#define FDC_READ_TRK	0xe0	/* read track */
#define FDC_WRITE_TRK	0xf0	/* write track (format) */

/* Type IV additional flags */
#define FDC_IM0 		0x01	/* interrupt mode 0 */
#define FDC_IM1 		0x02	/* interrupt mode 1 */
#define FDC_IM2 		0x04	/* interrupt mode 2 */
#define FDC_IM3 		0x08	/* interrupt mode 3 */

#define FDC_MASK_TYPE_IV		(FDC_IM3|FDC_IM2|FDC_IM1|FDC_IM0)

/* Type IV commands */
#define FDC_FORCE_INT	0xd0	/* force interrupt */

/* structure describing a double density track */
#define TRKSIZE_DD		6144
#if 0
static const UINT8 track_DD[][2] = {
	{16, 0x4e}, 	/* 16 * 4E (track lead in)               */
	{ 8, 0x00}, 	/*  8 * 00 (pre DAM)                     */
	{ 3, 0xf5}, 	/*  3 * F5 (clear CRC)                   */

	{ 1, 0xfe}, 	/* *** sector *** FE (DAM)               */
	{ 1, 0x80}, 	/*  4 bytes track,head,sector,seclen     */
	{ 1, 0xf7}, 	/*  1 * F7 (CRC)                         */
	{22, 0x4e}, 	/* 22 * 4E (sector lead in)              */
	{12, 0x00}, 	/* 12 * 00 (pre AM)                      */
	{ 3, 0xf5}, 	/*  3 * F5 (clear CRC)                   */
	{ 1, 0xfb}, 	/*  1 * FB (AM)                          */
	{ 1, 0x81}, 	/*  x bytes sector data                  */
	{ 1, 0xf7}, 	/*  1 * F7 (CRC)                         */
	{16, 0x4e}, 	/* 16 * 4E (sector lead out)             */
	{ 8, 0x00}, 	/*  8 * 00 (post sector)                 */
	{ 0, 0x00}, 	/* end of data                           */
};
#endif

/* structure describing a single density track */
#define TRKSIZE_SD		3172
#if 0
static const UINT8 track_SD[][2] = {
	{16, 0xff}, 	/* 16 * FF (track lead in)               */
	{ 8, 0x00}, 	/*  8 * 00 (pre DAM)                     */
	{ 1, 0xfc}, 	/*  1 * FC (clear CRC)                   */

	{11, 0xff}, 	/* *** sector *** 11 * FF                */
	{ 6, 0x00}, 	/*  6 * 00 (pre DAM)                     */
	{ 1, 0xfe}, 	/*  1 * FE (DAM)                         */
	{ 1, 0x80}, 	/*  4 bytes track,head,sector,seclen     */
	{ 1, 0xf7}, 	/*  1 * F7 (CRC)                         */
	{10, 0xff}, 	/* 10 * FF (sector lead in)              */
	{ 4, 0x00}, 	/*  4 * 00 (pre AM)                      */
	{ 1, 0xfb}, 	/*  1 * FB (AM)                          */
	{ 1, 0x81}, 	/*  x bytes sector data                  */
	{ 1, 0xf7}, 	/*  1 * F7 (CRC)                         */
	{ 0, 0x00}, 	/* end of data                           */
};
#endif


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

typedef struct _wd1770_state wd1770_state;
struct _wd1770_state
{
	/* callbacks */
	devcb_resolved_read_line in_dden_func;
	devcb_resolved_write_line out_intrq_func;
	devcb_resolved_write_line out_drq_func;

	/* input lines */
	int mr;            /* master reset */
	int rdy;           /* ready, enable precomp */
	int tr00;          /* track 00 */
	int idx;           /* index */
	int wprt;          /* write protect */
	int dden;          /* double density */

	/* output lines */
	int mo;            /* motor on */
	int dirc;          /* direction */
	int drq;           /* data request */
	int intrq;         /* interrupt request */

	/* register */
	UINT8 data_shift;
	UINT8 data;
	UINT8 track;
	UINT8 sector;
	UINT8 command;
	UINT8 status;
	UINT8 interrupt;

	int stepping_rate[4];  /* track stepping rate in ms */

        unsigned short  crc;    /* Holds the current CRC value for write_track CRC calculation */
        int     crc_active;	/* Flag indicating that CRC calculation in write_track is active. */

	UINT8	track_reg;				/* value of track register */
	UINT8	command_type;			/* last command type */
	UINT8	head;					/* current head # */

	UINT8	read_cmd;				/* last read command issued */
	UINT8	write_cmd;				/* last write command issued */
	INT8	direction;				/* last step direction */
	UINT8   last_command_data;		/* last command data */

	UINT8	status_drq; 			/* status register data request bit */
	UINT8	busy_count; 			/* how long to keep busy bit set */

	UINT8	buffer[6144];			/* I/O buffer (holds up to a whole track) */
	UINT32	data_offset;			/* offset into I/O buffer */
	INT32	data_count; 			/* transfer count from/into I/O buffer */

	UINT8	*fmt_sector_data[256];	/* pointer to data after formatting a track */

	UINT8	dam_list[256][4];		/* list of data address marks while formatting */
	int 	dam_data[256];			/* offset to data inside buffer while formatting */
	int 	dam_cnt;				/* valid number of entries in the dam_list */
	UINT16	sector_length;			/* sector length (byte) */

	UINT8	ddam;					/* ddam of sector found - used when reading */
	UINT8	sector_data_id;
	int		data_direction;

	int     hld_count;				/* head loaded counter */

	/* timers to delay execution/completion of commands */
	emu_timer *timer_cmd, *timer_data, *timer_rs, *timer_ws;

	/* this is the drive currently selected */
	device_t *drive;

	/* this is the head currently selected */
	UINT8 hd;

	/* pause time when writing/reading sector */
	int pause_time;

	/* Were we busy when we received a FORCE_INT command */
	UINT8	was_busy;

	/* Pointer to interface */
	const wd17xx_interface *intf;
};


/***************************************************************************
    DEFAULT INTERFACES
***************************************************************************/

const wd17xx_interface default_wd17xx_interface =
{
	DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, { FLOPPY_0, FLOPPY_1, FLOPPY_2, FLOPPY_3}
};

const wd17xx_interface default_wd17xx_interface_2_drives =
{
	DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, { FLOPPY_0, FLOPPY_1, NULL, NULL}
};


/***************************************************************************
    PROTOTYPES
***************************************************************************/

static void wd17xx_complete_command(device_t *device, int delay);
static void wd17xx_timed_data_request(device_t *device);
static int wd17xx_locate_sector(device_t *device);
static void wd17xx_timed_read_sector_request(device_t *device);


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

INLINE wd1770_state *get_safe_token(device_t *device)
{
	assert(device != NULL);
	assert(device->type() == FD1771 || device->type() == FD1781 ||
		device->type() == FD1791 || device->type() == FD1792 || device->type() == FD1793 || device->type() == FD1794 || device->type() == FD1795 || device->type() == FD1797 ||
		device->type() == FD1761 || device->type() == FD1762 || device->type() == FD1763 || device->type() == FD1764 || device->type() == FD1765 || device->type() == FD1767 ||
		device->type() == WD2791 || device->type() == WD2793 || device->type() == WD2795 || device->type() == WD2797 ||
		device->type() == WD1770 || device->type() == WD1772 || device->type() == WD1773 ||
		device->type() == MB8866 || device->type() == MB8876 || device->type() == MB8877);

	return (wd1770_state *)downcast<legacy_device_base *>(device)->token();
}


/***************************************************************************
    HELPER FUNCTIONS
***************************************************************************/

static int wd17xx_has_dal(device_t *device)
{
	return (device->type() == FD1793 || device->type() == FD1794 || device->type() == FD1797 ||
			device->type() == FD1763 || device->type() == FD1764 || device->type() == FD1767 ||
			device->type() == WD1770 || device->type() == WD1772 || device->type() == WD1773 ||
			device->type() == WD2793 || device->type() == WD2797 ||
			device->type() == MB8877);
}

static int wd17xx_is_sd_only(device_t *device)
{
	return (device->type() == FD1771 || device->type() == FD1792 || device->type() == FD1794 || device->type() == FD1762 || device->type() == FD1764);
}

static int wd17xx_has_side_select(device_t *device)
{
	return (device->type() == FD1795 || device->type() == FD1797 ||
			device->type() == FD1765 || device->type() == FD1767 ||
			device->type() == WD2795 || device->type() == WD2797);
}

static int wd17xx_dden(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	if (!w->in_dden_func.isnull())
		return w->in_dden_func();
	else
		return w->dden;
}


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

/* clear a data request */
static void wd17xx_clear_drq(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	w->status &= ~STA_2_DRQ;

	w->drq = CLEAR_LINE;
	w->out_drq_func(w->drq);
}

/* set data request */
static void wd17xx_set_drq(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	if (w->status & STA_2_DRQ)
		w->status |= STA_2_LOST_DAT;

	w->status |= STA_2_DRQ;

	w->drq = ASSERT_LINE;
	w->out_drq_func(w->drq);
}

/* clear interrupt request */
static void	wd17xx_clear_intrq(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	w->intrq = CLEAR_LINE;
	w->out_intrq_func(w->intrq);
}

/* set interrupt request */
static void	wd17xx_set_intrq(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	w->status &= ~STA_2_BUSY;
	w->status &= ~STA_2_DRQ;

	w->intrq = ASSERT_LINE;
	w->out_intrq_func(w->intrq);
}

/* set intrq after delay */
static TIMER_CALLBACK( wd17xx_command_callback )
{
	device_t *device = (device_t *)ptr;
	wd1770_state *w = get_safe_token(device);

	if (w->last_command_data != FDC_FORCE_INT)
	{
		wd17xx_set_intrq(device);
	}
}

/* write next byte to data register and set drq */
static TIMER_CALLBACK( wd17xx_data_callback )
{
	device_t *device = (device_t *)ptr;
	wd1770_state *w = get_safe_token(device);

   /* check if this is a write command */
   if( (w->command_type == TYPE_II && w->command == FDC_WRITE_SEC) ||
         (w->command_type == TYPE_III && w->command == FDC_WRITE_TRK) )
   {
      /* we are ready for new data */
      wd17xx_set_drq(device);

      return;
   }

	/* any bytes remaining? */
	if (w->data_count >= 1)
	{
		/* yes */
		w->data = w->buffer[w->data_offset++];

		if (VERBOSE_DATA)
			logerror("wd17xx_data_callback: $%02X (data_count %d)\n", w->data, w->data_count);

		wd17xx_set_drq(device);

		/* any bytes remaining? */
		if (--w->data_count < 1)
		{
			/* no */
			w->data_offset = 0;

			/* clear ddam type */
			w->status &=~STA_2_REC_TYPE;

			/* read a sector with ddam set? */
			if (w->command_type == TYPE_II && w->ddam != 0)
			{
				/* set it */
				w->status |= STA_2_REC_TYPE;
			}

			/* check if we should handle the next sector for a multi record read */
			if (w->command_type == TYPE_II && w->command == FDC_READ_SEC && (w->read_cmd & 0x10))
			{
				if (VERBOSE)
					logerror("wd17xx_data_callback: multi sector read\n");

				if (w->sector == 0xff)
					w->sector = 0x01;
				else
					w->sector++;

				wd17xx_timed_read_sector_request(device);
			}
			else
			{
				/* Delay the INTRQ 3 byte times because we need to read two CRC bytes and
                   compare them with a calculated CRC */
				wd17xx_complete_command(device, DELAY_DATADONE);

				if (VERBOSE)
					logerror("wd17xx_data_callback: data read completed\n");
			}
		}
		else
		{
			/* requeue us for more data */
			w->timer_data->adjust(attotime::from_usec(wd17xx_dden(device) ? 128 : 32));
		}
	}
	else
	{
		logerror("wd17xx_data_callback: (no new data) $%02X (data_count %d)\n", w->data, w->data_count);
	}
}


static void wd17xx_set_busy(device_t *device, attotime duration)
{
	wd1770_state *w = get_safe_token(device);

	w->status |= STA_1_BUSY;

	w->timer_cmd->adjust(duration);
}


/* BUSY COUNT DOESN'T WORK PROPERLY! */

static void wd17xx_command_restore(device_t *device)
{
	wd1770_state *w = get_safe_token(device);
	UINT8 step_counter;

	if (w->drive == NULL)
		return;

	step_counter = 255;

#if 0
	w->status |= STA_1_BUSY;
#endif

	/* setup step direction */
	w->direction = -1;

	w->command_type = TYPE_I;

	/* reset busy count */
	w->busy_count = 0;

	if (1) // image_slotexists(w->drive) : FIXME
	{
		/* keep stepping until track 0 is received or 255 steps have been done */
		while (floppy_tk00_r(w->drive) && (step_counter != 0))
		{
			/* update time to simulate seek time busy signal */
			w->busy_count++;
			floppy_drive_seek(w->drive, w->direction);
			step_counter--;
		}
	}

	/* update track reg */
	w->track = 0;
#if 0
	/* simulate seek time busy signal */
	w->busy_count = 0;	//w->busy_count * ((w->data & FDC_STEP_RATE) + 1);

	/* when command completes set irq */
	wd17xx_set_intrq(device);
#endif
	wd17xx_set_busy(device, attotime::from_usec(100));
}

/*
    Write an entire track. Formats which do not define a write_track
    function pointer will cause a silent return.
    What is written to the image depends on the selected format. Sector
    dumps have to extract the sectors in the track, while track dumps
    may directly write the bytes.
    (The if-part below may thus be removed.)
*/
static void write_track(device_t *device)
{
	wd1770_state *w = get_safe_token(device);
	floppy_image_legacy *floppy;
#if 0
	int i;
	for (i=0;i+4<w->data_offset;)
	{
		if (w->buffer[i]==0xfe)
		{
			/* got address mark */
			int track   = w->buffer[i+1];
			int side    = w->buffer[i+2];
			int sector  = w->buffer[i+3];
			//int len     = w->buffer[i+4];
			int filler  = 0xe5; /* IBM and Thomson */
			int density = w->density;
			floppy_drive_format_sector(w->drive,side,sector,track,
						w->hd,sector,density?1:0,filler);
			i += 128; /* at least... */
		}
		else
			i++;
	}
#endif

	/* Get the size in bytes of the current track. For real hardware this
    may vary per system in small degree, and there even for each track
    and head, so we should not assume a fixed value here.
    As we are using a buffered track writing, we have to find out how long
    the track will become. The only object which can tell us is the
    selected format.
    */
	w->data_count = 0;
	floppy = flopimg_get_image(w->drive);
	if (floppy != NULL)
		w->data_count = floppy_get_track_size(floppy, w->hd, w->track);

        if (w->data_count==0)
        {
			if (wd17xx_is_sd_only(device))
				w->data_count = TRKSIZE_SD;
			else
				w->data_count = wd17xx_dden(device) ? TRKSIZE_SD : TRKSIZE_DD;
        }

	floppy_drive_write_track_data_info_buffer( w->drive, w->hd, (char *)w->buffer, &(w->data_count) );

	w->data_offset = 0;

	wd17xx_set_drq(device);
	w->status |= STA_2_BUSY;
	w->busy_count = 0;
}

/*
    Read an entire track. It is up to the format to deliver the data. Sector
    dumps may be required to fantasize the missing track bytes, while track
    dumps can directly deliver them.
    (The if-part below may thus be removed.)
*/
static void read_track(device_t *device)
{
	wd1770_state *w = get_safe_token(device);
	floppy_image_legacy *floppy;
#if 0
	UINT8 *psrc;		/* pointer to track format structure */
	UINT8 *pdst;		/* pointer to track buffer */
	int cnt;			/* number of bytes to fill in */
	UINT16 crc; 		/* id or data CRC */
	UINT8 d;			/* data */
	UINT8 t = w->track; /* track of DAM */
	UINT8 h = w->head;	/* head of DAM */
	UINT8 s = w->sector_dam;		/* sector of DAM */
	UINT16 l = w->sector_length;	/* sector length of DAM */
	int i;

	for (i = 0; i < w->sec_per_track; i++)
	{
		w->dam_list[i][0] = t;
		w->dam_list[i][1] = h;
		w->dam_list[i][2] = i;
		w->dam_list[i][3] = l >> 8;
	}

	pdst = w->buffer;

	if (w->density)
	{
		psrc = track_DD[0];    /* double density track format */
		cnt = TRKSIZE_DD;
	}
	else
	{
		psrc = track_SD[0];    /* single density track format */
		cnt = TRKSIZE_SD;
	}

	while (cnt > 0)
	{
		if (psrc[0] == 0)	   /* no more track format info ? */
		{
			if (w->dam_cnt < w->sec_per_track) /* but more DAM info ? */
			{
				if (w->density)/* DD track ? */
					psrc = track_DD[3];
				else
					psrc = track_SD[3];
			}
		}

		if (psrc[0] != 0)	   /* more track format info ? */
		{
			cnt -= psrc[0];    /* subtract size */
			d = psrc[1];

			if (d == 0xf5)	   /* clear CRC ? */
			{
				crc = 0xffff;
				d = 0xa1;	   /* store A1 */
			}

			for (i = 0; i < *psrc; i++)
				*pdst++ = d;   /* fill data */

			if (d == 0xf7)	   /* store CRC ? */
			{
				pdst--; 	   /* go back one byte */
				*pdst++ = crc & 255;	/* put CRC low */
				*pdst++ = crc / 256;	/* put CRC high */
				cnt -= 1;	   /* count one more byte */
			}
			else if (d == 0xfe)/* address mark ? */
			{
				crc = 0xffff;	/* reset CRC */
			}
			else if (d == 0x80)/* sector ID ? */
			{
				pdst--; 	   /* go back one byte */
				t = *pdst++ = w->dam_list[w->dam_cnt][0]; /* track number */
				h = *pdst++ = w->dam_list[w->dam_cnt][1]; /* head number */
				s = *pdst++ = w->dam_list[w->dam_cnt][2]; /* sector number */
				l = *pdst++ = w->dam_list[w->dam_cnt][3]; /* sector length code */
				w->dam_cnt++;
				crc = ccitt_crc16_one(crc, t);	/* build CRC */
				crc = ccitt_crc16_one(crc, h);	/* build CRC */
				crc = ccitt_crc16_one(crc, s);	/* build CRC */
				crc = ccitt_crc16_one(crc, l);	/* build CRC */
				l = (l == 0) ? 128 : l << 8;
			}
			else if (d == 0xfb)// data address mark ?
			{
				crc = 0xffff;	// reset CRC
			}
			else if (d == 0x81)// sector DATA ?
			{
				pdst--; 	   /* go back one byte */
				if (seek(w, t, h, s) == 0)
				{
					if (mame_fread(w->image_file, pdst, l) != l)
					{
						w->status = STA_2_CRC_ERR;
						return;
					}
				}
				else
				{
					w->status = STA_2_REC_N_FND;
					return;
				}
				for (i = 0; i < l; i++) // build CRC of all data
					crc = ccitt_crc16_one(crc, *pdst++);
				cnt -= l;
			}
			psrc += 2;
		}
		else
		{
			*pdst++ = 0xff;    /* fill track */
			cnt--;			   /* until end */
		}
	}
#endif
	/* Determine the track size. We cannot allow different sizes in this
    design (see above, write_track). */
	w->data_count = 0;
	floppy = flopimg_get_image(w->drive);
	if (floppy != NULL)
		w->data_count = floppy_get_track_size(floppy, w->hd, w->track);

        if (w->data_count==0)
        {
			if (wd17xx_is_sd_only(device))
				w->data_count = TRKSIZE_SD;
			else
				w->data_count = wd17xx_dden(device) ? TRKSIZE_SD : TRKSIZE_DD;
        }

	floppy_drive_read_track_data_info_buffer( w->drive, w->hd, (char *)w->buffer, &(w->data_count) );

	w->data_offset = 0;

	wd17xx_set_drq(device);
	w->status |= STA_2_BUSY;
	w->busy_count = 0;
}


/* read the next data address mark */
static void wd17xx_read_id(device_t *device)
{
	chrn_id id;
	wd1770_state *w = get_safe_token(device);

	w->status &= ~(STA_2_CRC_ERR | STA_2_REC_N_FND);

	/* get next id from disc */
	if (floppy_drive_get_next_id(w->drive, w->hd, &id))
	{
		UINT16 crc = 0xffff;

		w->data_offset = 0;
		w->data_count = 6;

		/* for MFM */
		/* crc includes 3x0x0a1, and 1x0x0fe (id mark) */
		crc = ccitt_crc16_one(crc,0x0a1);
		crc = ccitt_crc16_one(crc,0x0a1);
		crc = ccitt_crc16_one(crc,0x0a1);
		crc = ccitt_crc16_one(crc,0x0fe);

		w->buffer[0] = id.C;
		w->buffer[1] = id.H;
		w->buffer[2] = id.R;
		w->buffer[3] = id.N;
		crc = ccitt_crc16_one(crc, w->buffer[0]);
		crc = ccitt_crc16_one(crc, w->buffer[1]);
		crc = ccitt_crc16_one(crc, w->buffer[2]);
		crc = ccitt_crc16_one(crc, w->buffer[3]);
		/* crc is stored hi-byte followed by lo-byte */
		w->buffer[4] = crc>>8;
		w->buffer[5] = crc & 255;

		w->sector = id.C;

		if (VERBOSE)
			logerror("wd17xx_read_id: read id succeeded.\n");

		wd17xx_timed_data_request(device);
	}
	else
	{
		/* record not found */
		w->status |= STA_2_REC_N_FND;
		//w->sector = w->track;
		if (VERBOSE)
			logerror("wd17xx_read_id: read id failed\n");

		wd17xx_complete_command(device, DELAY_ERROR);
	}
}



void wd17xx_index_pulse_callback(device_t *controller, device_t *img, int state)
{
	wd1770_state *w = get_safe_token(controller);

	if (img != w->drive)
		return;

	w->idx = state;

	if (!state && w->idx && BIT(w->interrupt, 2))
		wd17xx_set_intrq(controller);

	if (w->hld_count)
		w->hld_count--;
}



static int wd17xx_locate_sector(device_t *device)
{
	UINT8 revolution_count;
	chrn_id id;
	wd1770_state *w = get_safe_token(device);

	revolution_count = 0;

	w->status &= ~STA_2_REC_N_FND;

	while (revolution_count!=4)
	{
		if (floppy_drive_get_next_id(w->drive, w->hd, &id))
		{
			/* compare track */
			if (id.C == w->track)
			{
				/* compare head, if we were asked to */
				if (!wd17xx_has_side_select(device) || (id.H == w->head) || (w->head == (UINT8) ~0))
				{
					/* compare id */
					if (id.R == w->sector)
					{
						w->sector_length = 1<<(id.N+7);
						w->sector_data_id = id.data_id;
						/* get ddam status */
						w->ddam = id.flags & ID_FLAG_DELETED_DATA;
						/* got record type here */
						if (VERBOSE)
							logerror("sector found! C:$%02x H:$%02x R:$%02x N:$%02x%s\n", id.C, id.H, id.R, id.N, w->ddam ? " DDAM" : "");
						return 1;
					}
				}
			}
		}

		 /* index set? */
		if (floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_INDEX))
		{
			/* update revolution count */
			revolution_count++;
		}
	}
	return 0;
}


static int wd17xx_find_sector(device_t *device)
{
	wd1770_state *w = get_safe_token(device);
	if ( wd17xx_locate_sector(device) )
	{
		return 1;
	}

	/* record not found */
	w->status |= STA_2_REC_N_FND;

	if (VERBOSE)
		logerror("track %d sector %d not found!\n", w->track, w->sector);

	wd17xx_complete_command(device, DELAY_ERROR);

	return 0;
}

static void wd17xx_side_compare(device_t *device, UINT8 command)
{
	wd1770_state *w = get_safe_token(device);

	if (wd17xx_has_side_select(device))
		wd17xx_set_side(device, (command & FDC_SIDE_CMP_T) ? 1 : 0);

	if (command & FDC_SIDE_CMP_T)
		w->head = (command & FDC_SIDE_CMP_S) ? 1 : 0;
	else
		w->head = ~0;
}

/* read a sector */
static void wd17xx_read_sector(device_t *device)
{
	wd1770_state *w = get_safe_token(device);
	w->data_offset = 0;

	/* side compare? */
	wd17xx_side_compare(device, w->read_cmd);

	if (wd17xx_find_sector(device))
	{
		w->data_count = w->sector_length;

		/* read data */
		floppy_drive_read_sector_data(w->drive, w->hd, w->sector_data_id, (char *)w->buffer, w->sector_length);

		wd17xx_timed_data_request(device);

		w->status |= STA_2_BUSY;
		w->busy_count = 0;
	}
}


/* called on error, or when command is actually completed */
/* KT - I have used a timer for systems that use interrupt driven transfers.
A interrupt occurs after the last byte has been read. If it occurs at the time
when the last byte has been read it causes problems - same byte read again
or bytes missed */
/* TJL - I have add a parameter to allow the emulation to specify the delay
*/
static void wd17xx_complete_command(device_t *device, int delay)
{
	wd1770_state *w = get_safe_token(device);

	w->data_count = 0;

	w->hld_count = 2;

	/* set new timer */
	int usecs = wd17xx_dden(device) ? 32 : 16;
	w->timer_cmd->adjust(attotime::from_usec(usecs));

	/* Kill onshot read/write sector timers */
	w->timer_rs->adjust(attotime::never);
	w->timer_ws->adjust(attotime::never);
}



static void wd17xx_write_sector(device_t *device)
{
	wd1770_state *w = get_safe_token(device);
	/* at this point, the disc is write enabled, and data
     * has been transfered into our buffer - now write it to
     * the disc image or to the real disc
     */

	/* side compare? */
	wd17xx_side_compare(device, w->write_cmd);

	/* find sector */
	if (wd17xx_find_sector(device))
	{
		w->data_count = w->sector_length;

		/* write data */
		floppy_drive_write_sector_data(w->drive, w->hd, w->sector_data_id, (char *)w->buffer, w->sector_length, w->write_cmd & 0x01);
	}
}



/* verify the seek operation by looking for a id that has a matching track value */
static void wd17xx_verify_seek(device_t *device)
{
	UINT8 revolution_count;
	chrn_id id;
	wd1770_state *w = get_safe_token(device);

	revolution_count = 0;

	if (VERBOSE)
		logerror("doing seek verify\n");

	w->status &= ~STA_1_SEEK_ERR;

	/* must be found within 5 revolutions otherwise error */
	while (revolution_count!=5)
	{
		if (floppy_drive_get_next_id(w->drive, w->hd, &id))
		{
			/* compare track */
			if (id.C == w->track)
			{
				if (VERBOSE)
					logerror("seek verify succeeded!\n");
				return;
			}
		}

		 /* index set? */
		if (floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_INDEX))
		{
			/* update revolution count */
			revolution_count++;
		}
	}

	w->status |= STA_1_SEEK_ERR;

	if (VERBOSE)
		logerror("failed seek verify!\n");
}



/* callback to initiate read sector */
static TIMER_CALLBACK( wd17xx_read_sector_callback )
{
	device_t *device = (device_t *)ptr;
	wd1770_state *w = get_safe_token(device);

	/* ok, start that read! */

	if (VERBOSE)
		logerror("wd179x: Read Sector callback.\n");

	if (!floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_READY))
		wd17xx_complete_command(device, DELAY_NOTREADY);
	else
		wd17xx_read_sector(device);
}



/* callback to initiate write sector */
static TIMER_CALLBACK( wd17xx_write_sector_callback )
{
	device_t *device = (device_t *)ptr;
	wd1770_state *w = get_safe_token(device);

	/* ok, start that write! */

	if (VERBOSE)
		logerror("wd179x: Write Sector callback.\n");

	if (!floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_READY))
		wd17xx_complete_command(device, DELAY_NOTREADY);
	else
	{

		/* drive write protected? */
		if (floppy_wpt_r(w->drive) == CLEAR_LINE)
		{
			w->status |= STA_2_WRITE_PRO;

			wd17xx_complete_command(device, DELAY_ERROR);
		}
		else
		{
			/* side compare? */
			wd17xx_side_compare(device, w->write_cmd);

			/* attempt to find it first before getting data from cpu */
			if (wd17xx_find_sector(device))
			{
				/* request data */
				w->data_offset = 0;
				w->data_count = w->sector_length;

				wd17xx_set_drq(device);

				w->status |= STA_2_BUSY;
				w->busy_count = 0;
			}
		}
	}
}



/* setup a timed data request - data request will be triggered in a few usecs time */
static void wd17xx_timed_data_request(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	/* set new timer */
	w->timer_data->adjust(attotime::from_usec(wd17xx_dden(device) ? 128 : 32));
}



/* setup a timed read sector - read sector will be triggered in a few usecs time */
static void wd17xx_timed_read_sector_request(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	/* set new timer */
	w->timer_rs->adjust(attotime::from_usec(w->pause_time));
}



/* setup a timed write sector - write sector will be triggered in a few usecs time */
static void wd17xx_timed_write_sector_request(device_t *device)
{
	wd1770_state *w = get_safe_token(device);

	/* set new timer */
	w->timer_ws->adjust(attotime::from_usec(w->pause_time));
}


/***************************************************************************
    INTERFACE
***************************************************************************/

/* use this to determine which drive is controlled by WD */
void wd17xx_set_drive(device_t *device, UINT8 drive)
{
	wd1770_state *w = get_safe_token(device);

	if (VERBOSE)
		logerror("wd17xx_set_drive: $%02x\n", drive);

	if (w->intf->floppy_drive_tags[drive] != NULL)
	{
		if (device->owner() != NULL) {
			w->drive = device->owner()->subdevice(w->intf->floppy_drive_tags[drive]);
			if (w->drive == NULL) {
				w->drive = device->machine().device(w->intf->floppy_drive_tags[drive]);
			}
		}
		else
			w->drive = device->machine().device(w->intf->floppy_drive_tags[drive]);
	}
}

void wd17xx_set_side(device_t *device, UINT8 head)
{
	wd1770_state *w = get_safe_token(device);

	if (VERBOSE)
	{
		if (head != w->hd)
			logerror("wd17xx_set_side: $%02x\n", head);
	}

	w->hd = head;
}

void wd17xx_set_pause_time(device_t *device, int usec)
{
	wd1770_state *w = get_safe_token(device);
	w->pause_time = usec;
}


/***************************************************************************
    DEVICE HANDLERS
***************************************************************************/

/* master reset */
WRITE_LINE_DEVICE_HANDLER( wd17xx_mr_w )
{
	wd1770_state *w = get_safe_token(device);

	/* reset device when going from high to low */
	if (w->mr && state == CLEAR_LINE)
	{
		w->command = 0x03;
		w->status &= ~STA_1_NOT_READY; /* ? */
	}

	/* execute restore command when going from low to high */
	if (w->mr == CLEAR_LINE && state)
	{
		wd17xx_command_restore(device);
		w->sector = 0x01;
	}

	w->mr = state;
}

/* ready and enable precomp (1773 only) */
WRITE_LINE_DEVICE_HANDLER( wd17xx_rdy_w )
{
	wd1770_state *w = get_safe_token(device);
	w->rdy = state;
}

/* motor on, 1770 and 1772 only */
READ_LINE_DEVICE_HANDLER( wd17xx_mo_r )
{
	wd1770_state *w = get_safe_token(device);
	return w->mo;
}

/* track zero */
WRITE_LINE_DEVICE_HANDLER( wd17xx_tr00_w )
{
	wd1770_state *w = get_safe_token(device);
	w->tr00 = state;
}

/* index pulse */
WRITE_LINE_DEVICE_HANDLER( wd17xx_idx_w )
{
	wd1770_state *w = get_safe_token(device);
	w->idx = state;

	if (!state && w->idx && BIT(w->interrupt, 2))
		wd17xx_set_intrq(device);
}

/* write protect status */
WRITE_LINE_DEVICE_HANDLER( wd17xx_wprt_w )
{
	wd1770_state *w = get_safe_token(device);
	w->wprt = state;
}

/* double density enable */
WRITE_LINE_DEVICE_HANDLER( wd17xx_dden_w )
{
	wd1770_state *w = get_safe_token(device);

	/* not supported on FD1771, FD1792, FD1794, FD1762 and FD1764 */
	if (wd17xx_is_sd_only(device))
		fatalerror("wd17xx_dden_w: double density input not supported on this model!");
	else if (!w->in_dden_func.isnull())
		logerror("wd17xx_dden_w: write has no effect because a read handler is already defined!\n");
	else
		w->dden = state;
}

/* data request */
READ_LINE_DEVICE_HANDLER( wd17xx_drq_r )
{
	wd1770_state *w = get_safe_token(device);
	return w->drq;
}

/* interrupt request */
READ_LINE_DEVICE_HANDLER( wd17xx_intrq_r )
{
	wd1770_state *w = get_safe_token(device);
	return w->intrq;
}

/* read the FDC status register. This clears IRQ line too */
READ8_DEVICE_HANDLER( wd17xx_status_r )
{
	wd1770_state *w = get_safe_token(device);
	int result;

	if (!BIT(w->interrupt, 3))
	{
		wd17xx_clear_intrq(device);
	}

	/* bit 7, 'not ready' or 'motor on' */
	if (device->type() == WD1770 || device->type() == WD1772)
	{
		w->status &= ~STA_1_MOTOR_ON;
		w->status |= w->mo << 7;
	}
	else
	{
		w->status &= ~STA_1_NOT_READY;
		if (!floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_READY))
			w->status |= STA_1_NOT_READY;
	}

	result = w->status;

	/* type 1 command or force int command? */
	if ((w->command_type==TYPE_I) || (w->command_type==TYPE_IV && ! w->was_busy))
	{
		result &= ~(STA_1_IPL | STA_1_TRACK0);

		/* bit 1, index pulse */
		result |= w->idx << 1;

		/* bit 2, track 0 state, inverted */
		result |= !floppy_tk00_r(w->drive) << 2;

		if (w->command_type==TYPE_I)
		{
			if (w->hld_count)
				w->status |= STA_1_HD_LOADED;
			else
				w->status &= ~ STA_1_HD_LOADED;
		}

		/* bit 6, write protect, inverted */
		result |= !floppy_wpt_r(w->drive) << 6;
	}

	/* eventually set data request bit */
//  w->status |= w->status_drq;

	if (VERBOSE)
	{
		if (w->data_count < 4)
			logerror("%s: wd17xx_status_r: $%02X (data_count %d)\n", device->machine().describe_context(), result, w->data_count);
	}

	return result ^ (wd17xx_has_dal(device) ? 0 : 0xff);
}

/* read the FDC track register */
READ8_DEVICE_HANDLER( wd17xx_track_r )
{
	wd1770_state *w = get_safe_token(device);

	if (VERBOSE)
		logerror("%s: wd17xx_track_r: $%02X\n", device->machine().describe_context(), w->track);

	return w->track ^ (wd17xx_has_dal(device) ? 0 : 0xff);
}

/* read the FDC sector register */
READ8_DEVICE_HANDLER( wd17xx_sector_r )
{
	wd1770_state *w = get_safe_token(device);

	if (VERBOSE)
		logerror("%s: wd17xx_sector_r: $%02X\n", device->machine().describe_context(), w->sector);

	return w->sector ^ (wd17xx_has_dal(device) ? 0 : 0xff);
}

/* read the FDC data register */
READ8_DEVICE_HANDLER( wd17xx_data_r )
{
	wd1770_state *w = get_safe_token(device);

	if (VERBOSE_DATA)
		logerror("%s: wd17xx_data_r: %02x\n", device->machine().describe_context(), w->data);

	/* clear data request */
	wd17xx_clear_drq(device);

	return w->data ^ (wd17xx_has_dal(device) ? 0 : 0xff);
}

/* write the FDC command register */
WRITE8_DEVICE_HANDLER( wd17xx_command_w )
{
	wd1770_state *w = get_safe_token(device);
	if (!wd17xx_has_dal(device)) data ^= 0xff;

	w->last_command_data = data;

	/* only the WD1770 and WD1772 have a 'motor on' line */
	if (device->type() == WD1770 || device->type() == WD1772)
	{
		w->mo = ASSERT_LINE;
		floppy_mon_w(w->drive, CLEAR_LINE);
	}

	floppy_drive_set_ready_state(w->drive, 1,0);

	if (!BIT(w->interrupt, 3))
	{
		wd17xx_clear_intrq(device);
	}

	/* clear write protected. On read sector, read track and read dam, write protected bit is clear */
	w->status &= ~((1<<6) | (1<<5) | (1<<4));

	if ((data & ~FDC_MASK_TYPE_IV) == FDC_FORCE_INT)
	{
		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X FORCE_INT (data_count %d)\n", device->machine().describe_context(), data, w->data_count);

		w->data_count = 0;
		w->data_offset = 0;
		w->was_busy = w->status & STA_2_BUSY;
		w->status &= ~STA_2_BUSY;

		wd17xx_clear_drq(device);

		if (!BIT(w->interrupt, 3) && BIT(data, 3))
		{
			/* set immediate interrupt */
			wd17xx_set_intrq(device);
		}

		if (BIT(w->interrupt, 3))
		{
			if (data == FDC_FORCE_INT)
			{
				/* clear immediate interrupt */
				w->interrupt = data & 0x0f;
			}
			else
			{
				/* keep immediate interrupt */
				w->interrupt = 0x08 | (data & 0x07);
			}
		}
		else
		{
			w->interrupt = data & 0x0f;
		}

		/* terminate command */
		wd17xx_complete_command(device, DELAY_ERROR);

		w->busy_count = 0;
		w->command_type = TYPE_IV;
		return;
	}

	if (data & 0x80)
	{
		/*w->status_ipl = 0;*/

		if ((data & ~FDC_MASK_TYPE_II) == FDC_READ_SEC)
		{
			if (VERBOSE)
            {
				logerror("%s: wd17xx_command_w $%02X READ_SEC (", device->machine().describe_context(), data);
                logerror("cmd=%02X, trk=%02X, sec=%02X, dat=%02X)\n",w->command,w->track,w->sector,w->data);
            }
			w->read_cmd = data;
			w->command = data & ~FDC_MASK_TYPE_II;
			w->command_type = TYPE_II;
			w->status &= ~STA_2_LOST_DAT;
			w->status |= STA_2_BUSY;
			wd17xx_clear_drq(device);

			wd17xx_timed_read_sector_request(device);

			return;
		}

		if ((data & ~FDC_MASK_TYPE_II) == FDC_WRITE_SEC)
		{
			if (VERBOSE)
            {
				logerror("%s: wd17xx_command_w $%02X WRITE_SEC (", device->machine().describe_context(), data);
                logerror("cmd=%02X, trk=%02X, sec=%02X, dat=%02X)\n",w->command,w->track,w->sector,w->data);
            }

			w->write_cmd = data;
			w->command = data & ~FDC_MASK_TYPE_II;
			w->command_type = TYPE_II;
			w->status &= ~STA_2_LOST_DAT;
			w->status |= STA_2_BUSY;
			wd17xx_clear_drq(device);

			wd17xx_timed_write_sector_request(device);

			return;
		}

		if ((data & ~FDC_MASK_TYPE_III) == FDC_READ_TRK)
		{
			if (VERBOSE)
				logerror("%s: wd17xx_command_w $%02X READ_TRK\n", device->machine().describe_context(), data);

			w->command = data & ~FDC_MASK_TYPE_III;
			w->command_type = TYPE_III;
			w->status &= ~STA_2_LOST_DAT;
			wd17xx_clear_drq(device);
#if 1
//          w->status = seek(w, w->track, w->head, w->sector);
			if (w->status == 0)
				read_track(device);
#endif
			return;
		}

		if ((data & ~FDC_MASK_TYPE_III) == FDC_WRITE_TRK)
		{
			if (VERBOSE)
				logerror("%s: wd17xx_command_w $%02X WRITE_TRK\n", device->machine().describe_context(), data);

			w->command_type = TYPE_III;
			w->status &= ~STA_2_LOST_DAT;
			wd17xx_clear_drq(device);

			if (!floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_READY))
			{
				wd17xx_complete_command(device, DELAY_NOTREADY);
			}
			else
			{

				/* drive write protected? */
				if (floppy_wpt_r(w->drive) == CLEAR_LINE)
				{
				/* yes */
					w->status |= STA_2_WRITE_PRO;
				/* quit command */
					wd17xx_complete_command(device, DELAY_ERROR);
				}
				else
				{
				w->command = data & ~FDC_MASK_TYPE_III;
				w->data_offset = 0;
				if (wd17xx_is_sd_only(device))
					w->data_count = TRKSIZE_SD;
				else
					w->data_count = wd17xx_dden(device) ? TRKSIZE_SD : TRKSIZE_DD;

				wd17xx_set_drq(device);
				w->status |= STA_2_BUSY;
				w->busy_count = 0;
				}
			}
			return;
		}

		if ((data & ~FDC_MASK_TYPE_III) == FDC_READ_DAM)
		{
			if (VERBOSE)
				logerror("%s: wd17xx_command_w $%02X READ_DAM\n", device->machine().describe_context(), data);

			w->command_type = TYPE_III;
			w->command = data & ~FDC_MASK_TYPE_III;
			w->status &= ~STA_2_LOST_DAT;
			w->status |= STA_2_BUSY;

			wd17xx_clear_drq(device);

			if (floppy_drive_get_flag_state(w->drive, FLOPPY_DRIVE_READY))
				wd17xx_read_id(device);
			else
				wd17xx_complete_command(device, DELAY_NOTREADY);

			return;
		}

		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X unknown\n", device->machine().describe_context(), data);

		return;
	}

	w->status |= STA_1_BUSY;

	/* clear CRC error */
	w->status &=~STA_1_CRC_ERR;

	if ((data & ~FDC_MASK_TYPE_I) == FDC_RESTORE)
	{
		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X RESTORE\n", device->machine().describe_context(), data);

		wd17xx_command_restore(device);
	}

	if ((data & ~FDC_MASK_TYPE_I) == FDC_SEEK)
	{
		UINT8 newtrack;

		if (VERBOSE)
			logerror("old track: $%02x new track: $%02x\n", w->track, w->data);
		w->command_type = TYPE_I;

		/* setup step direction */
		if (w->track < w->data)
		{
			if (VERBOSE)
				logerror("direction: +1\n");

			w->direction = 1;
		}
		else if (w->track > w->data)
		{
			if (VERBOSE)
				logerror("direction: -1\n");

			w->direction = -1;
		}

		newtrack = w->data;
		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X SEEK (data_reg is $%02X)\n", device->machine().describe_context(), data, newtrack);

		/* reset busy count */
		w->busy_count = 0;

		/* keep stepping until reached track programmed */
		while (w->track != newtrack)
		{
			/* update time to simulate seek time busy signal */
			w->busy_count++;

			/* update track reg */
			w->track += w->direction;

			floppy_drive_seek(w->drive, w->direction);
		}

		/* simulate seek time busy signal */
		w->busy_count = 0;	//w->busy_count * ((data & FDC_STEP_RATE) + 1);
#if 0
		wd17xx_set_intrq(device);
#endif
		wd17xx_set_busy(device, attotime::from_usec(100));

	}

	if ((data & ~(FDC_STEP_UPDATE | FDC_MASK_TYPE_I)) == FDC_STEP)
	{
		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X STEP dir %+d\n", device->machine().describe_context(), data, w->direction);

		w->command_type = TYPE_I;
        /* if it is a real floppy, issue a step command */
		/* simulate seek time busy signal */
		w->busy_count = 0;	//((data & FDC_STEP_RATE) + 1);

		floppy_drive_seek(w->drive, w->direction);

		if (data & FDC_STEP_UPDATE)
			w->track += w->direction;

#if 0
		wd17xx_set_intrq(device);
#endif
		wd17xx_set_busy(device, attotime::from_usec(100));


	}

	if ((data & ~(FDC_STEP_UPDATE | FDC_MASK_TYPE_I)) == FDC_STEP_IN)
	{
		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X STEP_IN\n", device->machine().describe_context(), data);

		w->command_type = TYPE_I;
		w->direction = +1;
		/* simulate seek time busy signal */
		w->busy_count = 0;	//((data & FDC_STEP_RATE) + 1);

		floppy_drive_seek(w->drive, w->direction);

		if (data & FDC_STEP_UPDATE)
			w->track += w->direction;
#if 0
		wd17xx_set_intrq(device);
#endif
		wd17xx_set_busy(device, attotime::from_usec(100));

	}

	if ((data & ~(FDC_STEP_UPDATE | FDC_MASK_TYPE_I)) == FDC_STEP_OUT)
	{
		if (VERBOSE)
			logerror("%s: wd17xx_command_w $%02X STEP_OUT\n", device->machine().describe_context(), data);

		w->command_type = TYPE_I;
		w->direction = -1;
		/* simulate seek time busy signal */
		w->busy_count = 0;	//((data & FDC_STEP_RATE) + 1);

		/* for now only allows a single drive to be selected */
		floppy_drive_seek(w->drive, w->direction);

		if (data & FDC_STEP_UPDATE)
			w->track += w->direction;

#if 0
		wd17xx_set_intrq(device);
#endif
		wd17xx_set_busy(device, attotime::from_usec(100));
	}

	if (w->command_type == TYPE_I)
	{
		/* 0 is enable spin up sequence, 1 is disable spin up sequence */
		if ((data & FDC_STEP_HDLOAD)==0)
		{
			w->status |= STA_1_HD_LOADED;
			w->hld_count = 2;
		}
		else
			w->status &= ~STA_1_HD_LOADED;

		if (data & FDC_STEP_VERIFY)
		{
			/* verify seek */
			wd17xx_verify_seek(device);
		}
	}
}

/* write the FDC track register */
WRITE8_DEVICE_HANDLER( wd17xx_track_w )
{
	wd1770_state *w = get_safe_token(device);
	if (!wd17xx_has_dal(device)) data ^= 0xff;

	w->track = data;

	if (VERBOSE)
		logerror("%s: wd17xx_track_w $%02X\n", device->machine().describe_context(), data);
}

/* write the FDC sector register */
WRITE8_DEVICE_HANDLER( wd17xx_sector_w )
{
	wd1770_state *w = get_safe_token(device);
	if (!wd17xx_has_dal(device)) data ^= 0xff;

	w->sector = data;

	if (VERBOSE)
		logerror("%s: wd17xx_sector_w $%02X\n", device->machine().describe_context(), data);
}

/* write the FDC data register */
WRITE8_DEVICE_HANDLER( wd17xx_data_w )
{
	wd1770_state *w = get_safe_token(device);
	if (!wd17xx_has_dal(device)) data ^= 0xff;

	if (w->data_count > 0)
	{
		wd17xx_clear_drq(device);

		/* put byte into buffer */
		if (VERBOSE_DATA)
			logerror("wd17xx_info buffered data: $%02X at offset %d.\n", data, w->data_offset);

		w->buffer[w->data_offset++] = data;

                if (--w->data_count < 1)
                {
                        if (w->command == FDC_WRITE_TRK)
                                write_track(device);
                        else
                                wd17xx_write_sector(device);

                        w->data_offset = 0;

						/* Check we should handle the next sector for a multi record write */
						if ( w->command_type == TYPE_II && w->command == FDC_WRITE_SEC && ( w->write_cmd & FDC_MULTI_REC ) )
						{
							w->sector++;
							if (wd17xx_locate_sector(device))
							{
								w->data_count = w->sector_length;

								w->status |= STA_2_BUSY;
								w->busy_count = 0;

								wd17xx_timed_data_request(device);
							}
						}
						else
						{
							wd17xx_complete_command(device, DELAY_DATADONE);
							if (VERBOSE)
								logerror("wd17xx_data_w(): multi data write completed\n");
						}
//                       wd17xx_complete_command(device, DELAY_DATADONE);
                }
                else
                {
                        if (w->command == FDC_WRITE_TRK)
                        {
                                /* Process special data values according to WD17xx specification.
                   Note that as CRC values take up two bytes which are written on
                   every 0xf7 byte, this will cause the actual image to
                   grow larger than what was written from the system. So we need
                   to take the value of data_offset when writing the track.
                                */
                                if (wd17xx_dden(device))
                                {
                                        switch (data)
                                        {
                                        case 0xf5:
                                        case 0xf6:
                                                /* not allowed in FM. */
                                                /* Take back the last write. */
                                                w->data_offset--;
                                                break;
                                        case 0xf7:
                                                /* Take back the last write. */
                                                w->data_offset--;
                                                /* write two crc bytes */
                                                w->buffer[w->data_offset++] = (w->crc>>8)&0xff;
                                                w->buffer[w->data_offset++] = (w->crc&0xff);
                                                w->crc_active = FALSE;
                                                break;
                                        case 0xf8:
                                        case 0xf9:
                                        case 0xfa:
                                        case 0xfb:
                                        case 0xfe:
                                                /* Preset crc */
                                                w->crc = 0xffff;
						/* AM is included in the CRC */
						w->crc = ccitt_crc16_one(w->crc, data);
                                                w->crc_active = TRUE;
                                                break;
                                        case 0xfc:
                                                /* Write index mark. No effect here as we do not store clock patterns.
                           Maybe later. */
                                                break;
                                        case 0xfd:
                                                /* Just write, don't use for CRC. */
                                                break;
                                        default:
                                                /* Byte already written. */
                                                if (w->crc_active)
                                                        w->crc = ccitt_crc16_one(w->crc, data);
                                        }
                                }
                                else  /* MFM */
                                {
                                        switch (data)
                                        {
                                        case 0xf5:
                                                /* Take back the last write. */
                                                w->data_offset--;
                                                /* Write a1 */
                                                w->buffer[w->data_offset++] = 0xa1;
                                                /* Preset CRC */
                                                w->crc = 0xffff;
                                                w->crc_active = TRUE;
                                                break;
                                        case 0xf6:
                                                /* Take back the last write. */
                                                w->data_offset--;
                                                /* Write c2 */
                                                w->buffer[w->data_offset++] = 0xc2;
                                                break;
                                        case 0xf7:
                                                /* Take back the last write. */
                                                w->data_offset--;
                                                /* write two crc bytes */
                                                w->buffer[w->data_offset++] = (w->crc>>8)&0xff;
                                                w->buffer[w->data_offset++] = (w->crc&0xff);
                                                w->crc_active = FALSE;
                                                break;
                                        case 0xf8:
                                        case 0xf9:
                                        case 0xfa:
                                        case 0xfb:
                                        case 0xfc:
                                        case 0xfd:
                                                /* Just write, don't use for CRC. */
                                                break;
                                        case 0xfe:
						/* AM is included in the CRC */
						if (w->crc_active)
							w->crc = ccitt_crc16_one(w->crc, data);
                                                break;
                                        default:
                                                /* Byte already written. */
                                                if (w->crc_active)
                                                        w->crc = ccitt_crc16_one(w->crc, data);
                                        }
                                }
                        }
                        /* yes... setup a timed data request */
                        wd17xx_timed_data_request(device);
                }
	}
	else
	{
		if (VERBOSE)
			logerror("%s: wd17xx_data_w $%02X\n", device->machine().describe_context(), data);
	}
	w->data = data;
}

READ8_DEVICE_HANDLER( wd17xx_r )
{
	UINT8 data = 0;

	switch (offset & 0x03)
	{
	case 0: data = wd17xx_status_r(device, 0); break;
	case 1:	data = wd17xx_track_r(device, 0); break;
	case 2:	data = wd17xx_sector_r(device, 0); break;
	case 3:	data = wd17xx_data_r(device, 0); break;
	}

	return data;
}

WRITE8_DEVICE_HANDLER( wd17xx_w )
{
	switch (offset & 0x03)
	{
	case 0: wd17xx_command_w(device, 0, data); break;
	case 1:	wd17xx_track_w(device, 0, data);   break;
	case 2: wd17xx_sector_w(device, 0, data);  break;
	case 3: wd17xx_data_w(device, 0, data);    break;
	}
}


/***************************************************************************
    MAME DEVICE INTERFACE
***************************************************************************/

static DEVICE_START( wd1770 )
{
	wd1770_state *w = get_safe_token(device);

	assert(device->static_config() != NULL);

	w->intf = (const wd17xx_interface*)device->static_config();

	w->status = STA_1_TRACK0;
	w->pause_time = 1000;

	/* allocate timers */
	w->timer_cmd = device->machine().scheduler().timer_alloc(FUNC(wd17xx_command_callback), (void *)device);
	w->timer_data = device->machine().scheduler().timer_alloc(FUNC(wd17xx_data_callback), (void *)device);
	w->timer_rs = device->machine().scheduler().timer_alloc(FUNC(wd17xx_read_sector_callback), (void *)device);
	w->timer_ws = device->machine().scheduler().timer_alloc(FUNC(wd17xx_write_sector_callback), (void *)device);

	/* resolve callbacks */
	w->in_dden_func.resolve(w->intf->in_dden_func, *device);
	w->out_intrq_func.resolve(w->intf->out_intrq_func, *device);
	w->out_drq_func.resolve(w->intf->out_drq_func, *device);

	/* stepping rate depends on the clock */
	w->stepping_rate[0] = 6;
	w->stepping_rate[1] = 12;
	w->stepping_rate[2] = 20;
	w->stepping_rate[3] = 30;
}

static DEVICE_START( wd1772 )
{
	wd1770_state *w = get_safe_token(device);

	DEVICE_START_CALL(wd1770);

	/* the 1772 has faster track stepping rates */
	w->stepping_rate[0] = 6;
	w->stepping_rate[1] = 12;
	w->stepping_rate[2] = 2;
	w->stepping_rate[3] = 3;
}

static DEVICE_RESET( wd1770 )
{
	wd1770_state *w = get_safe_token(device);
	int i;

	/* set the default state of some input lines */
	w->mr = ASSERT_LINE;
	w->wprt = ASSERT_LINE;
	w->dden = ASSERT_LINE;

	for (i = 0; i < 4; i++)
	{
		if(w->intf->floppy_drive_tags[i]!=NULL) {
			device_t *img = NULL;

			if (device->owner() != NULL)
				img = device->owner()->subdevice(w->intf->floppy_drive_tags[i]);
				if (img == NULL) {
					img = device->machine().device(w->intf->floppy_drive_tags[i]);
				}

			else
				img = device->machine().device(w->intf->floppy_drive_tags[i]);

			if (img!=NULL) {
				floppy_drive_set_controller(img,device);
				floppy_drive_set_index_pulse_callback(img, wd17xx_index_pulse_callback);
				floppy_drive_set_rpm( img, 300.);
			}
		}
	}

	wd17xx_set_drive(device, 0);

	w->hd = 0;
	w->hld_count = 0;
	w->sector = 1;
	wd17xx_command_restore(device);
}

void wd17xx_reset(device_t *device)
{
	DEVICE_RESET_CALL( wd1770 );
}


/***************************************************************************
    DEVICE GETINFO
***************************************************************************/

static const char DEVTEMPLATE_SOURCE[] = __FILE__;

#define DEVTEMPLATE_ID(p,s)				p##wd1770##s
#define DEVTEMPLATE_FEATURES			DT_HAS_START | DT_HAS_RESET
#define DEVTEMPLATE_NAME				"WD1770"
#define DEVTEMPLATE_FAMILY				"WD17xx"
#define DEVTEMPLATE_VERSION				"1.0"
#define DEVTEMPLATE_CREDITS				"Copyright MESS Team"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1771##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1771"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1781##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1781"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1791##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1791"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1792##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1792"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1793##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1793"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1794##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1794"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1795##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1795"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1797##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1797"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1761##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1761"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1762##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1762"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1763##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1763"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1764##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1764"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1765##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1765"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##fd1767##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"FD1767"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##wd2791##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"WD2791"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##wd2793##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"WD2793"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##wd2795##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"WD2795"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##wd2797##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"WD2797"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##wd1772##s
#define DEVTEMPLATE_DERIVED_FEATURES	DT_HAS_START
#define DEVTEMPLATE_DERIVED_NAME		"WD1772"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##wd1773##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"WD1773"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##mb8866##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"MB8866"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##mb8876##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"MB8876"
#include "devtempl.h"

#define DEVTEMPLATE_DERIVED_ID(p,s)		p##mb8877##s
#define DEVTEMPLATE_DERIVED_FEATURES	0
#define DEVTEMPLATE_DERIVED_NAME		"MB8877"
#include "devtempl.h"

DEFINE_LEGACY_DEVICE(FD1771, fd1771);
DEFINE_LEGACY_DEVICE(FD1781, fd1781);
DEFINE_LEGACY_DEVICE(FD1791, fd1791);
DEFINE_LEGACY_DEVICE(FD1792, fd1792);
DEFINE_LEGACY_DEVICE(FD1793, fd1793);
DEFINE_LEGACY_DEVICE(FD1794, fd1794);
DEFINE_LEGACY_DEVICE(FD1795, fd1795);
DEFINE_LEGACY_DEVICE(FD1797, fd1797);
DEFINE_LEGACY_DEVICE(FD1761, fd1761);
DEFINE_LEGACY_DEVICE(FD1762, fd1762);
DEFINE_LEGACY_DEVICE(FD1763, fd1763);
DEFINE_LEGACY_DEVICE(FD1764, fd1764);
DEFINE_LEGACY_DEVICE(FD1765, fd1765);
DEFINE_LEGACY_DEVICE(FD1767, fd1767);
DEFINE_LEGACY_DEVICE(WD2791, wd2791);
DEFINE_LEGACY_DEVICE(WD2793, wd2793);
DEFINE_LEGACY_DEVICE(WD2795, wd2795);
DEFINE_LEGACY_DEVICE(WD2797, wd2797);
DEFINE_LEGACY_DEVICE(WD1770, wd1770);
DEFINE_LEGACY_DEVICE(WD1772, wd1772);
DEFINE_LEGACY_DEVICE(WD1773, wd1773);
DEFINE_LEGACY_DEVICE(MB8866, mb8866);
DEFINE_LEGACY_DEVICE(MB8876, mb8876);
DEFINE_LEGACY_DEVICE(MB8877, mb8877);
