; Low level pieces for floppy driver
;
.globl _fd_reset
+ .globl _fd_seek
.globl _fd_operation
.globl _fd_map
.globl _fd_cmd
+;
+; On entry c holds the bits from 0x34, bde must be preserved
+; a may be trashed
+;
+fd_setup:
+ bit 2,c ; motor off
+ jr nz, motor_stopped
+ ld hl,#400 ; delay
+ ; If the side has changed we need to allow for a head load delay
+ ; FIXME
+ ; We don't switch config mid flow (we close/reopen) so we probably
+ ; don't need to consider a config change delay here
+ bit 5,c ; head load
+ ret nz
+ jp delayhl1 ; wait 40ms for head load
+motor_stopped:
+ ld hl,#0x4E20 ; two seconds for spin up
+ jp delayhl1
+
+;
+; Passed a buffer with the controller states in
+; Set up the state, do a restore and then clean up
+;
+; Does not work on all 8" types attachable to the FDC16 ?
+;
_fd_reset:
- ret
+ in a,(0x34)
+ ld c,a
+ ld a,(_fd_cmd + 1)
+ out (0x34),a ; set control bits
+ call fd_setup ; get the drive spinning
+ ld a,(_fd_cmd + 5) ; restore with delay bits
+ out (0x30),a ; issue the correct command
+fd_restore_wait:
+ in a,(0x34) ; wait for error or done
+ bit 2,a ; motor stopped
+ jr nz, nodisk ; that's an error
+ rra ; end of job ?
+ jr nc, fd_restore_wait ; nope - try again
+ call delayhl ; short sleep for the controller
+ in a,(0x34) ; read the status
+ and #0x98 ; bits that mean an error
+reta:
+ ld l,a ; report them back in HL
+ ret
+nodisk:
+ ld l,#0xFF
+ ret
+
+
+;
+; Passed a control buffer
+; 0: cmd
+; 1: bit 0 set if writing
+; 2: 0x34 bits
+; 3/4: data
+; 5: delay information (used by seek not read/write)
+;
+;
_fd_operation:
- ret
+ ;
+ ; We run in common. Map the kernel or user according to the
+ ; destination of the transfer
+ ;
+ ld a, (_fd_map)
+ or a
+ call nz, map_process_always
+ ;
+ ; Set up the registers in order. The aux register is already
+ ; done by our caller
+ ;
+ ld hl,#_fd_cmd
+ ld b,(hl) ; command
+ inc hl
+ bit 0,(hl) ; read or write ?
+ ; patch patch1/2 according to the transfer direction
+ ld a,#0xA2 ; ini
+ jr z, patchit
+ ld a,#0xA3 ; outi
+patchit:
+ ld (patch1+1),a
+ ld (patch2+1),a
+ ;
+ ; We have to disable interrupts before we write the
+ ; command register when doing a data transfer otherwise an
+ ; interrupt can lose us bytes and break the transfer
+ ;
+ di
+ in a,(0x34) ; check head status
+ bit 5,a
+ jr nz, nomod ; loaded
+ set 2,b ; set head-load bit in command
+nomod:
+ ;
+ ; Now figure out what set up time is needed. There are
+ ; three cases
+ ;
+ ; 1. Motor is stopped
+ ; 2. Motor is running head is unloaded
+ ; 3. Motor is running head is loaded
+ ld c,a ; Save motor bits
+ ;
+ ; Get the set up values
+ ;
+ ld a,(hl) ; 0x34 bits
+ ld d,a ; save bits for later
+ ;
+ ; Set up for the transfer using autowait.
+ ;
+ out (0x34),a ; output 0x34 bits
+ ;
+ ; Get the drive spinning
+ ;
+ call fd_setup
+ ;
+ ; When we hit this point the drive is supposed to be selected
+ ; and running. The head has had time to settle if needed and
+ ; we can try and do an I/O at last.
+ ;
+issue_command:
+ ld hl,(_fd_cmd+3) ; buffer
+ ld c,#0x33 ; data port
+ ld a,b
+ out (0x30),a ; issue the command
+
+ ;
+ ; For now we only do double density (256 words per sector)
+ ;
+ ld b,#0
+ ;
+ ; Check for EOJ (DRQ is handled by autowait)
+ ;
+fd_waitop:
+ in a,(0x34)
+ rra
+ jr c,fd_done
+ ;
+ ; ASAP transfer a byte
+ ;
+patch1: outi
+ inc b ; count in words
+ in a,(0x34) ; check EOJ again
+ rra
+ jr c,fd_done
+patch2: outi ; second byte out
+ jp nz,fd_waitop ; faster than jr
+ ;
+ ; The transfer is done - wait for EOJ
+ ;
+fd_waiteoj:
+ ei
+ in a,(0x34)
+ rra
+ jr nc, fd_waiteoj
+ ;
+ ; Job complete. Turn off autowait and recover
+ ; the status byte
+ ;
+fd_done:
+ ei
+ call map_kernel
+ ld a,d ; 0x34 bits
+ and #0x7F
+ out (0x34),a ; autowait off
+ ;
+ ; Let the controller catch up for 10ms
+ ;
+ ld hl,#100
+ call delayhl1
+ ;
+ ; Read the status bits
+ ;
+ in a,(0x30)
+ and #0xFC ; Mask off bad bits
+jrreta: jr reta ; HL return
+
+;
+; Seek from track to track. The calling C code has set up the
+; control and configuration bits for us including managing the
+; seek bit. This probably doesn't work for some of the slow 8" drives
+;
+; HL points at our config...
+;
+_fd_seek:
+ in a,(0x34)
+ ld c,a
+ ld a,(_fd_cmd + 1) ; control bits
+ out (0x34),a ; control
+ ld d,a
+ call fd_setup ; get the drive spinning
+ ld a,(_fd_cmd + 5)
+ out (0x30),a ; seek (0x10)+delay
+seek_wait:
+ in a,(0x34)
+ bit 2,a ; motor stopped
+ jp nz, nodisk
+ rra
+ jr nc, seek_wait
+ ld a,d
+ and #0x7f
+ out (0x34),a ; auto off
+ ld hl,#100 ; 10ms
+ call delayhl1
+ in a,(0x30) ; recover status
+ and #0x98
+ jr jrreta
+
_fd_map:
- .byte 0
+ .byte 0
_fd_cmd:
- .byte 0, 0, 0, 0, 0, 0, 0
+ .byte 0, 0, 0, 0, 0, 0
+
+
+;
+; 0.9ms for 8" drive 1.2ms for 5.25"
+;
+delayhl:
+ ld hl,#0x09
+ ld a,(_fd_cmd+1)
+ bit 2,a ; 8 " ?
+ jr nz, delayhl1
+ ld hl,#0x0C
+delayhl1:
+ push bc ; 11
+delayhl2:
+ dec hl ; 6
+
+ ;
+ ; This inner loop costs us 13 cycles per iteration plus 2
+ ; (setup costs us 7 more, exit costs us 5 less)
+ ;
+ ; Giving us 366 cycles per loop
+ ;
+
+ ld b,#0x1c ; 7
+delayhl3:
+ djnz delayhl3 ; 13 / 8
+ ;
+ ; Each cycle of the outer loop costs us another 6 to dec hl
+ ; and 28 to do the end part of the loop
+ ;
+ ; Giving us a total of 400 cycles per loop, at 4MHz that
+ ; means each loop is 0.1ms
+ ;
+ nop ; 4
+ nop ; 4
+ ld a,l ; 4
+ or h ; 4
+ jr nz,delayhl2 ; 12 / 7
+ pop bc ; 10
+ ret ; 10
#define MAX_DRIVE 4
-#define OPDIR_NONE 0
-#define OPDIR_READ 1
-#define OPDIR_WRITE 2
+#define OPDIR_READ 0
+#define OPDIR_WRITE 1
#define FD_READ 0x80 /* 2797 needs 0x88, 1797 needs 0x80 */
#define FD_WRITE 0xA0 /* Likewise A8 v A0 */
/* 4 large disks (0 5" 1 8" 255 none */
uint8_t drivetype[4] = { 1, 1, 1, 1 };
+/* Step rate:
+ 5" 0E slow 5" 0C 8" 0F Slow 8" (special not supported) */
+static uint8_t delay[MAX_DRIVE] = { 0x0F, 0x0F, 0x0F, 0x0F };
+static uint8_t track[MAX_DRIVE] = { 0xFF, 0xFF, 0xFF, 0xFF };
/*
* We only support normal block I/O for the moment.
*/
-static uint8_t selmap[4] = { 0x01, 0x02, 0x04, 0x08 };
+static uint8_t selmap[MAX_DRIVE] = { 0x01, 0x02, 0x04, 0x08 };
static const uint8_t skewtab[2][18] = {
/* Matches CDOS 5" skew for DSDD */
*
* Just to get us going - only do DSDD.
*/
-
+
static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
{
- int tries;
+ int8_t tries;
uint8_t err = 0;
uint8_t drive = minor & 3;
- uint8_t *driveptr = fd_tab + drive;
+ uint8_t trackno, sector;
uint8_t large = !(minor & 0x10);
const uint8_t *skew = skewtab[large]; /* skew table */
if (rawflag && d_blkoff(BLKSHIFT))
return -1;
+
/* Command to go to the controller after any seek is done */
fd_cmd[0] = is_read ? FD_READ : FD_WRITE;
- if (large) {
- /* Track */
- fd_cmd[1] = udata.u_block / 16;
- /* Sector */
- fd_cmd[2] = skew[udata.u_block % 16]; /* 1..n skewed */
- /* control */
- fd_cmd[6] = 0xF0;
- } else {
- fd_cmd[1] = udata.u_block / 10;
- fd_cmd[2] = skew[udata.u_block % 10];
- fd_cmd[6] = 0xA0;
- }
- fd_cmd[6] |= (1 << drive);
+ /* Control byte: autowait, DD, motor, 5", drive bit */
+ fd_cmd[1] = 0xE0 | selmap[drive];
+ if (large)
+ fd_cmd[1] = 0x10; /* turn on 8" bit */
/* Directon of xfer */
- fd_cmd[3] = is_read ? OPDIR_READ: OPDIR_WRITE;
- /* Buffer */
- fd_cmd[4] = ((uint16_t)udata.u_dptr) & 0xFF;
- fd_cmd[5] = ((uint16_t)udata.u_dptr) >> 8;
-
- /* FIXME: Sides. Really 32 sectors/track 16 each side */
-
+ fd_cmd[2] = is_read ? OPDIR_READ: OPDIR_WRITE;
+ fd_cmd[5] = 0x10 | delay[drive];
+
+ /*
+ * Restore the track register to match this drive
+ */
+ if (track[drive] != 0xFF)
+ fd_track = track[drive];
+ else
+ fd_reset();
+
+ /*
+ * Begin transfers
+ */
while (udata.u_done < udata.u_nblock) {
+ /* Need to consider SS v DS here */
if (large) {
- fd_cmd[1] = udata.u_block / 16;
- fd_cmd[2] = (udata.u_block % 16) + 1;
+ fd_aux = 0x4C | (udata.u_block & 16) ? 2 : 0;
+ trackno = udata.u_block / 32;
+ sector = udata.u_block % 16;
} else {
- fd_cmd[1] = udata.u_block / 10;
- fd_cmd[2] = (udata.u_block % 10) + 1;
+ trackno = udata.u_block / 20;
+ sector = udata.u_block % 20;
+ if (sector > 9) {
+ sector -= 10;
+ fd_aux = 0x5E; /* side 1 */
+ } else
+ fd_aux = 0x5C;
}
+ /* Buffer */
+ fd_cmd[3] = ((uint16_t)udata.u_dptr) & 0xFF;
+ fd_cmd[4] = ((uint16_t)udata.u_dptr) >> 8;
+
for (tries = 0; tries < 4 ; tries++) {
- err = fd_operation(driveptr);
+ (void)fd_data;
+ fd_sector = skew[sector]; /* Also makes 1 based */
+ if (fd_track != trackno) {
+ fd_data = trackno;
+ if (fd_seek()) {
+ fd_reset();
+ continue;
+ }
+ }
+ /* Do the read or write */
+ err = fd_operation();
if (err == 0)
break;
+ /* Try and recover */
if (tries > 1)
- fd_reset(driveptr);
+ fd_reset();
}
if (tries == 4)
goto bad;
udata.u_block++;
udata.u_done++;
}
+ /* Save the track */
+ track[drive] = fd_track;
return udata.u_done << 9;
bad:
+ track[drive] = fd_track;
kprintf("fd%d: error %x\n", minor, err);
bad2:
udata.u_error = EIO;