765 FDC - 0.3.3                                   John Elliott, 26 January 2005
===============================================================================

  "765" is an emulation of the uPD765a (AKA Intel 8272) Floppy Disc Controller
[FDC] as used in Amstrad computers such as the PCW, CPC and Spectrum +3. At 
present it is not a "full" 765; features not used in the PCW BIOS (such as: 
DMA; multisector reads/writes; multitrack mode) are either left unimplemented 
or incomplete.

  "765" is released under the GNU Library GPL. 

What's new
==========
  Version 0.3.3 incorporates a bugfix for writing sectors to disc formats 
that distinguish between MFM and FM recording mode. LibDsk 1.1.2 and later
make this distinction for .DSK files.

  For earlier versions, see ChangeLog.

How to use 765
==============

  765 can either be used standalone or on top of LibDsk 
<http://www.seasip.demon.co.uk/Unix/LibDsk/> version 0.9.0 or later. I 
recommend you use it in conjunction with LibDsk as it thus gains the ability
to access floppy drives and disc image files in formats other than CPCEMU
.DSK format.

  765 provides the disc controller emulation for my PCW emulator JOYCE
<http://www.seasip.demon.co.uk/Unix/Joyce/>. If you have questions not 
answered in this document, they may be answered in the JOYCE source code.

Construction and destruction
============================

  To create a floppy controller object, use fdc_new():

FDC_PTR fdc_new(void);

  and to destroy it, use fdc_destroy():

void    fdc_destroy(FDC_PTR *p);

  There are four kinds of drive, though they are all represented by the same
pointer type (FDRV_PTR). The functions used to create the drives are:

* fd_new()     - The base class. This represents a drive that isn't there 
                 (for example, the second drive on a single-drive computer).
* fd_newdsk()  - A drive which is implemented using CPCEMU-format disc
	  	 image files (.DSK).
* fd_newldsk() - A drive which is implemented using the LIBDSK disc-
	 	 access library. This class is only present if you configured
		 with the --with-libdsk option. 
* fd_newnc9(FDRV_PTR)  - LibDsk contains some special code for the strange 
		 behaviour of the floppy controller on a PcW9256. On this 
		 controller, drives 2 and 3 always return the status of 
		 drive 1, but with "ready" set to true even if the drive 
		 isn't ready. This drive type is provided to emulate
		 that behaviour; the parameter (which can be NULL) is the
		 drive whose status this drive will return.

Destroying drive objects
========================

  To destroy an object, use

void     fd_destroy(FDRV_PTR *fd);

  Note that this takes the address of the pointer; on return, the value of the
pointer will be set to NULL.

Drive emulations
================

All drive classes have a type. This can be read/written using the fd_gettype() 
and fd_settype() functions. It can have one of the following values:

#define FD_30     ( 1)
		This drive behaves like a 3.0" drive.
#define FD_35     ( 2)
		This drive behaves like a 3.5" drive.
#define FD_525    ( 3)
		This drive behaves like a 5.25" drive.

The not-connected drives have a type which should not be changed; it is one of 

#define FD_NONE   ( 0)
		This is emulating a drive that isn't there.
#define FD_NC9256 (-1)
		The PcW9256 special case.

Changing the type of a drive changes a few aspects of the emulation. For 
example, a 3.5" drive does not give the "track 0" signal when its motor is off,
while a 3.0" drive does.

Drive errors
============

You will only need to handle these if you are implementing a drive class,
but they are:
#define FD_E_OK	       (0) 	/* OK */
#define FD_E_SEEKFAIL (-1)	/* Seek fail */
#define FD_E_NOADDR   (-2)	/* Missing address mark */
#define FD_E_NODATA   (-3) 	/* No data */
#define FD_E_DATAERR  (-4)	/* CRC Data error */
#define FD_E_NOSECTOR (-5)	/* Sector not found */
#define FD_E_NOTRDY   (-6)	/* Drive not ready */
#define FD_E_READONLY (-7)	/* Read only */

Drive Properties
================

  The properties listed below are present for all drive types. 

  The first three properties should be set as soon as it's been created 
(by fd_newdsk(), fd_newldsk(), etc.)

void fd_settype    (FDRV_PTR fd, int type);
int  fd_gettype    (FDRV_PTR fd);
			See "Drive Emulations" above for valid values.

void fd_setheads   (FDRV_PTR fd, int heads);
int  fd_getheads   (FDRV_PTR fd);
			Number of heads the drive has, 1 or 2.

void fd_setcyls    (FDRV_PTR fd, int cyls);
int  fd_getcyls    (FDRV_PTR fd);
			Number of cylinders (tracks) the drive has.
			Nominally 40 or 80, but usually a drive can
			go a little further, eg to 42 or 82.


void fd_setreadonly(FDRV_PTR fd, int ro);
int  fd_getreadonly(FDRV_PTR fd);
			Have the drive or its disc been set to read-only?

The following properties cannot be directly changed, only read:

int fd_getmotor    (FDRV_PTR fd);
			Zero if the drive's motor is off, otherwise nonzero. 
			To change this, use fdc_set_motor().
int fd_getcurcyl   (FDRV_PTR fd);
			The current cylinder that the drive head is 
			over. Note that if the drive is double-
			stepping, this is the "real" cylinder - 
		   	so it could = 24 and be reading cylinder 12 
                       	of a 40-track DSK file. To change this 
			send a SEEK command to the floppy controller.

The derived drive classes have additional properties:

DSK (created by fd_newdsk())
----------------------------

char *   fdd_getfilename(FDRV_PTR fd);
void     fdd_setfilename(FDRV_PTR fd, const char *s);

		Get or set the filename of the .DSK file representing the
		disc in this drive. Setting the filename does an implicit 
		fd_eject() to remove any previous disc. The .DSK file must
		exist.

LIBDSK (created by fd_newldsk())
--------------------------------

char *   fdl_getfilename(FDRV_PTR fd);
void     fdl_setfilename(FDRV_PTR fd, const char *s);

		Get or set the filename of the disc image file representing the
		disc in this drive. Setting the filename does an implicit 
		fd_eject() to remove any previous disc. The file must exist.

const char *   fdl_gettype(FDRV_PTR fd);
void           fdl_settype(FDRV_PTR fd, const char *s);

		Get or set the LibDsk drive type of this drive (see LibDsk 
		documentation). Can be NULL to auto-detect file format.

9256 (created by fd_newnc9())
-----------------------------

FDRV_PTR fd9_getproxy(FDRV_PTR self);
void     fd9_setproxy(FDRV_PTR self, FDRV_PTR PROXY);

	Get or set the drive that this drive will mimic 
	(ie, the PcW9256 B: drive).

Controller properties
---------------------

void fdc_setisr(FDC_PTR self, FDC_ISR isr);
FDC_ISR fdc_getisr(FDC_PTR self);

	Set or get the interrupt handler. This is called when the FDC wants
	to raise or lower its interrupt line. If you don't want FDC interrupts
	set this to NULL.
 
FDRV_PTR fdc_getdrive(FDC_PTR self, int drive);
void fdc_setdrive(FDC_PTR self, int drive, FDRV_PTR ptr);
	Set or get the four drives this controller addresses. You must set 
	all four. If the FDC's select lines are incompletely decoded (as is
	the case on the PCW8256) then set two pointers to the same drive.

Debugging support 
=================

typedef void (*lib765_error_function_t)(int debuglevel, char *fmt, va_list ap);

void lib765_register_error_function(lib765_error_function_t ef);

  You should define a function conforming to the prototype above in your
own program. Once you have registered the error function the FDC will call
it to print debugging messages. By default error messages will be printed
to stderr.
  "debuglevel" is the priority of the message, with 0 being normal (only 
reporting errors such as failing to open a file) and 7 being maximum (every
command sent or received is dumped as hex).

FDC actions
===========

void fdc_write_data(FDC_PTR self, fdc_byte value);

	Write a byte to the FDC's data register. If this is the last byte of a
	command, that command will be executed.

fdc_byte fdc_read_data (FDC_PTR self);

	Read the FDC's data register.

fdc_byte fdc_read_ctrl (FDC_PTR self);

	Read the FDC's main control register.

void fdc_set_terminal_count(FDC_PTR self, fdc_byte value);

	Raise / lower the FDC's terminal count line. "value" is 0 to lower
	it, other values to raise it. 

void fdc_set_motor(FDC_PTR self, fdc_byte running);

	Set the status of drive motors. "running" is a bitwise flag; 
	bit 0 corresponds to drive 0, bit 1 to drive 1 and so on.

void fdc_tick(FDC_PTR self);

	If you are going to be listening for FDC interrupts, you must call
	this function once every emulated instruction or at some similarly 
	high rate. It checks for pending interrupts and can call 
	(self->fdc_isr)().

void fdc_write_dor(FDC_PTR self, int value);

	The IBM PC application of the uPD765A uses a Digital Output Register
	to override drive selection, control the motors, select DMA mode and
	perform controller resets. Lib765 can emulate this functionality; 
	calls to fdc_write_dor() with 0 <= value <= 255 will make the DOR 
	override the selected drive and control the motors. Pass value = -1
	to disable the DOR and use the normal drive selection and motor 
	control systems. 

fdc_byte fdc_read_dir(FDC_PTR self);

	Read from the Digital Input Register. This is used on the IBM PC to
	support the "disc changed" signal. It will return either 00h or 80h.

void fdc_write_drr(FDC_PTR self, fdc_byte value);

	Write to the Data Rate Register. This is used to switch between 720k,
	1.4M and 2.8M formats.

Disc drive actions
==================

  Most of these functions are for the FDC to talk to disc drives, and you 
shouldn't need to call them. However they can be used if (for example) you 
want to bypass the emulated FDC and read sectors into memory under program 
control.

fd_err_t fdd_new_dsk(FDRV_PTR fd);

	This is not called by the FDC. It is used to create a new .DSK file -
	functionality which does not exist in the rest of the library. To
	do the same thing with a LibDsk drive, use the LibDsk functions
	dsk_creat() and dsk_close().

fd_err_t fd_seek_cylinder(FDRV_PTR fd, int cylinder);
	
	Seek to a given cylinder.

fd_err_t fd_read_id(FDRV_PTR fd, int head, int sector, fdc_byte *buf);

	Read a sector header. The "sector" parameter should be incremented
	each time you call this; this simulates the rotation of the disc 
	and ensures you get a different sector number each time.
	On return, if successful, "buf" will contain the 4-byte sector
	header:
		DB cylinder
		DB head
		DB sector
		DB sectorsize

fd_err_t fd_read_sector(FDRV_PTR fd, int xcylinder,
                int xhead, int head, int sector, fdc_byte *buf, int len,
		int *deleted, int skip_deleted, int mfm, int multitrack);

	Read a sector. xcylinder and xhead are values expected from the sector 
	header on disc, while "head" and "sector" are the actual place to look. 
 	Data will be returned to "buf", maximum "len" bytes. 
	On entry, *deleted is 1 to read deleted data, 0 for normal.
	On exit,  *deleted is 1 if the read command (deleted/nondeleted) did
	not match the type of data on the disc.
	skip_deleted is 1 to skip deleted sectors, 0 to read them. 
	mfm is the recording mode - 0 for FM, 1 for MFM.
	multitrack is 1 for multi-track operation, 0 for single-track. 
	No real attempt has been made to implement multi-track operation
	since no Amstrad BIOS uses it.

fd_err_t fd_write_sector(FDRV_PTR fd, int xcylinder,
                int xhead, int head, int sector, fdc_byte *buf, int len
		int deleted, int skip_deleted, int fm, int multitrack);

	Parameters are the same as for fd_read_sector, except that deleted
 	is an input parameter saying whether to write deleted or non-
	deleted data.

fd_err_t  fd_read_track   (struct floppy_drive *fd, int xcylinder,
                int xhead, int head, fdc_byte *buf, int *len);

	Read a track. Parameters are the same as for fd_read_sector(), 
	execept that "sector" is omitted.

fd_err_t  fd_format_track (struct floppy_drive *fd, int head, 
		int sectors, fdc_byte *track, fdc_byte filler);
	
	Format a track. "sectors" is the count of sectors. "track" holds
	4 * "sectors" bytes; these are the sector headers to write. See 
	fd_read_id for the layout of these.

fdc_byte fd_drive_status(FDRV_PTR fd);
	
	Get the drive status. The following bits should be set in the 
	returned byte:
		bit 7: Drive fault
		bit 6: Drive is read-only
		bit 5: Drive is ready
		bit 4: Head is over track 0
		bit 3: Drive is double-sided
		bits 2-0: Zeroes

fdc_byte fd_isready(FDRV_PTR fd);

	 Return 1 if the drive is ready, 0 if it is not.

void fd_eject(FDRV_PTR fd);

	Eject the disc from the drive. You must eject all discs when
	shutting down as 

Example of use
==============

  To use 765, you will need to:

1. Allocate one or more controllers. For example:

	FDC_PTR the_fdc = fdc_new();

2. Allocate floppy drives:

	FDRV_PTR drive_a = fd_newldsk();	/* Full drive */
	FDRV_PTR drive_b = fd_new();		/* Dummy drive */

3. Initialise the drives you set up:

	fd_settype (drive_a, FD_35);
	fd_setheads(drive_a, 2);
	fd_setcyls (drive_a, 80);
	fdl_setfilename(drive_a, "boot.dsk");

4. Initialise the FDC:

	fdc_reset(the_fdc);
	fdc_setisr(the_fdc, NULL);	/* Not interrupt-driven */

5. Give the FDC its four drives:

	fdc_setdrive(the_fdc, 0, drive_a);
	fdc_setdrive(the_fdc, 1, drive_b);
	fdc_setdrive(the_fdc, 2, drive_b);
	fdc_setdrive(the_fdc, 3, drive_b);

  (these must be done after any call to fdc_reset()).

6. And then let it take commands from your emulator:

	void out(word port, byte value)
	{
		switch(port)
		{
			case 0x1000:	fdc_write_data(the_fdc, value);
					break;
			case 0x1002:	fdc_set_motor(the_fdc, value & 0x0F);
					break;
		}
	}

	byte in(word port)
	{
		switch(port)
		{
			case 0x1000:	return fdc_read_data(the_fdc);
					break;
			case 0x1001:	return fdc_read_ctrl(the_fdc);
					break;
		}

	}

7. When you've finished, tidy up:

	fdc_destroy(&the_fdc);
	fd_destroy(&drive_a);
	fd_destroy(&drive_b);


