--- /dev/null
+/*
+ * Sinclair Interface One + Microdrives, mapped as if they were a
+ * floppy disk.
+ *
+ * First draft: motor control not yet made smart
+ */
+
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devmdv.h>
+
+#define MAX_MDV 2 /* for now */
+
+/* Should probably have a max and a max open to keep the maps managable */
+static unsigned char mdvmap[MAX_MDV][256];
+static uint8_t mdv_valid;
+
+/* Used by the asm helpers */
+uint8_t mdv_sector;
+uint8_t *mdv_buf;
+uint8_t mdv_hdr_buf[15];
+uint16_t mdv_len;
+
+static int mdv_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
+{
+ int err;
+ irqflags_t irq;
+
+ if (rawflag)
+ goto bad;
+
+ mdv_motor_on(minor);
+ /* FIXME: support swap ? */
+ mdv_sector = mdvmap[minor][udata.u_buf->bf_blk];
+ mdv_buf = udata.u_buf->bf_data;
+
+ irq = di();
+ if (is_read)
+ err = mdv_bread();
+ else
+ err = mdv_bwrite();
+ irqrestore(irq);
+ mdv_motor_off();
+ return 0;
+bad:
+ udata.u_error = EIO;
+ return -1;
+}
+
+int mdv_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return mdv_transfer(minor, true, rawflag);
+}
+
+int mdv_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return mdv_transfer(minor, false, rawflag);
+}
+
+int mdv_open(uint8_t minor, uint16_t flag)
+{
+ uint8_t *t;
+ int err;
+
+ flag;
+
+ if (minor >= MAX_MDV) {
+ udata.u_error = ENODEV;
+ return -1;
+ }
+ t = tmpbuf();
+ mdv_buf = t;
+ mdv_sector = 1;
+ err = mdv_bread();
+ if (err) {
+ mdv_sector = 128;
+ err = mdv_bread();
+ if (err) {
+ kprintf("mdv_open: maps bad: %d\n", err);
+ udata.u_error = ENXIO;
+ return -1;
+ }
+ kprintf("mdv_open: had to use secondary map\n");
+ }
+ brelse(t);
+ mdv_valid |= 1 << minor;
+ return 0;
+}
+
+int mdv_close(uint8_t minor)
+{
+ /* Simple approach for now */
+ mdv_valid &= ~(1 << minor);
+ return 0;
+}
--- /dev/null
+;
+; Sinclair Microdrive Controller
+;
+; Doing in software what floppy disk controllers do in hardware!
+;
+; A microdrive has up to 254 sectors on it, in reality nearer 200. The
+; blocks are sequential on the tape but in *reverse* order. This is
+; because the formatter writes 254,253,... down to 1, with some
+; overwriting those blocks written first, then verifies the blocks
+; to see how many fitted.
+;
+; Some of the blocks will be bad. The formatter in the ROM marks the
+; failed block bad and also the one following.
+;
+; To use it like a floppy disc we use sector numbers instead of names
+; as the speccy does. It's not quite the same however. We handle bad
+; blocks by keeping a logical/physical mapping. Each physical block
+; has a physical and logical identifier. In spectrum firmware land
+; there is an "erase" command. When running as a pseudo-floppy we
+; don't have that. Instead we keep a blockmap table in physical 1 and
+; 128.
+;
+ .module microdrive
+
+ .globl _mdv_motor_on
+ .globl _mdv_motor_off
+ .globl _mdv_bread
+ .globl _mdv_bwrite
+ .globl mdv_boot
+
+ .globl HBP
+
+ ; imports
+ .globl _mdv_sector
+ .globl _mdv_buf
+ .globl _mdv_hdr_buf
+ .globl _mdv_len
+
+ .globl mdv_sync
+ .globl mdv_get_blk
+
+SECTORID .equ 0x08 ; FIXME - set real format up!
+CSUM .equ 0x0E ; FIXME ditto
+
+ .area _CODE
+
+nap_1ms: push de
+ ld de, #87
+ jr napl
+nap: push de
+napl: dec de
+ ld a, d
+ or e
+ jr nz,napl
+ pop de
+ ret
+;
+; Must preserve E
+;
+mdv_csum_hdr:
+ xor a
+ ld b, #14
+ ld hl, #_mdv_hdr_buf
+csum_hdr: ; check the header is valid
+ add (hl)
+ adc #1
+ inc hl
+ jr z, csum_h0
+ dec a
+csum_h0:
+ djnz csum_hdr
+ cp (hl)
+ ret
+
+;
+; Load a microdrive sector into the buffer selected by _mdv_buf
+; for _mdv_len (in partial/full counts format). The lead partial goes
+; into _mdv_hdr_buf always
+;
+; Note this loads a buffer, any buffer, whatever arrives. It's your
+; problem to decide if it's the buffer you wanted.
+;
+
+mdv_seek: ld b, #8 ; we need to see gap for 8 cycles
+ dec hl
+ ld a, h
+ or l
+ ret z ; expired
+mdv_seek2:
+ in a, (0xEF)
+ and #4
+ jr z, mdv_seek
+
+ ; We found a gap bit, celebrate
+ djnz mdv_seek2
+ ; Happy gappy
+
+ ld a, #3
+ out (0xfe), a
+ ; Now do the same the other way up
+mdv_seeku: ld b, #6 ; we need to see ungap for 6 cycles
+ dec hl
+ ld a, h
+ or l
+ ret z ; expired
+mdv_seeku2: in a, (0xEF)
+ and #4
+ jr nz, mdv_seeku
+ djnz mdv_seeku2
+
+ ; Gappity gap
+ ld a, #5
+ out (0xfe), a
+ ld a, #0xEE
+ out (0xEF), a
+
+ ld b, #0x3C ; Must see a sync within 60 cycles
+
+mdv_sync: in a, (0xEF)
+ and #0x02
+ jr z, mdv_sync_go
+ djnz mdv_sync
+ xor a
+ out (0xfe), a
+ jr mdv_seek ; back to square one
+
+mdv_sync_go:
+ ; We are in sync
+ ld hl, #_mdv_hdr_buf
+ ld de, (_mdv_buf)
+ ld bc, (_mdv_len) ; in partial/full pair format
+ ld a, #7
+ out (0xfe), a
+ ld a, c
+ ld c, #0xE7
+
+;
+; Q: do we have enough clocks to pull the partial, flip buffer ptr and
+; continue. Seems we probably do
+;
+ inir ; copy the partial
+ sub #1
+ jr c, mdv_hdr_only
+ ex de, hl ; just about fast enough
+mdv_blockread:
+ inir
+ sub #1
+ jr nc, mdv_blockread
+ in a, (c) ; grab the checksum
+ ld e, a
+ ; the eagle has landed
+mdv_hdr_only:
+ xor a
+ out (0xfe), a
+ ld a, #0xee
+ out (0xEF), a
+ or a
+ ret
+
+
+;
+; Load the next header, well probably header - you might get the
+; start of a data chunk, in which case try again
+;
+
+mdv_get_hdr: ld hl, #0x0F00 ; 15 + no loops
+ ld (_mdv_len), hl
+ ld hl, #0 ; allow a long time to find a header
+ call mdv_seek ; they seek him here, they seek him there
+ jr z, hdr_fail ; if he ret's z he's off elsewhere
+
+
+ ; fixme: check its a header block !
+ call mdv_csum_hdr
+ ret z ; Z = good header
+ ld a, #2
+ ret ; 2 = bad csum
+hdr_fail:
+ ld a, #3
+ or a
+ ret ; 3 = no response, give up for good
+
+;
+; Find a microdrive block by matching header
+;
+; This uses the physical sector number which is *not* the same as
+; our logical one. We'll deal with that later.
+;
+; FIXME: we should spot repetitions of the first block# seen so we can
+; give up after 3 loops of the tape exactly.
+;
+mdv_find_hdr: ld bc, #2048 ; worst case is 4 times round the tape
+ push bc
+ call mdv_get_hdr
+ ld hl, #_mdv_hdr_buf
+ ld a, (hl)
+ cp #1
+ ret nz ; NZ, 1 = not a header
+ jr nz, mdv_find_hdr_bad
+ ld hl, #_mdv_hdr_buf + SECTORID
+ ld a, (_mdv_sector)
+ cp (hl)
+ pop bc
+ ret z ; found it
+mdv_find_hdr_2:
+ dec bc
+ ld a, b
+ or c
+ jr nz, mdv_find_hdr ; keep looking
+ inc a ; NZ
+ ret
+mdv_find_hdr_bad:
+ cp #3 ; 3 = give up now
+ jr nz, mdv_find_hdr_2
+ or a ; will be > 0
+ ret ; NZ
+
+;
+; Load the data for a microdrive block. It's assumed you just found
+; the right header then called this
+;
+mdv_get_blk: ld hl, #0x0F02 ; 15 + 2 loops (data) + csum in e
+ ld (_mdv_len), hl
+ ld hl, #0x01F4 ; that's the count the IF1 allows
+ call mdv_seek
+ jr z, hdr_fail ; bad fail
+ ld hl, #_mdv_hdr_buf
+ ld a, (hl)
+ out (0xfe), a
+ and #0x01
+ jr nz, failblk ; we got another header???
+ ; Sum the header block
+ call mdv_csum_hdr
+ jr nz, failblk
+
+
+ ld hl, (_mdv_buf) ; now the data
+ ld bc, #2 ; 2 x 256 byte runs
+ xor a
+csum_data2:
+ add (hl)
+ adc #1
+ inc hl
+ jr z, csum_d2
+ dec a
+csum_d2:
+ djnz csum_data2
+ dec c
+ jr nz, csum_data2
+ cp e ; expected csum
+ ret z ; good block
+ ld a, #2
+ out (0xfe), a
+ ; try again
+failblk:
+ ret
+
+;
+; Load a sector into memory.
+;
+mdv_fetch: call mdv_find_hdr ; nz = not found
+ call z, mdv_get_blk ; data if worked
+ ret ; done
+
+
+;
+; Microdrive motor control. This is basically a two wire clock/data
+; pair, shifted through the drives. We have a maximum of eight drives
+; so whenever we select we clock out 8 bits one of which turns on
+; a motor.
+;
+
+;
+; Turn all motors off
+;
+mdv_motors_off: ld a, #0xff
+ jr mdv_motor_a
+;
+; Turn on motor for microdrive unit A
+;
+mdv_motor: ld bc, #0x08EF ; port EF, 8 cycles
+ neg ; Clever way to get the
+ add #0x09 ; right bit number as used
+mdv_motor_a:
+ ld e, a ; by the if1 firmware
+;
+; Now we will do 8 cycles of bit banging clock and data
+;
+mdv_motor_lp: ld a, #0xEF ; clock it
+ out (0xef), a ; select the microdrive sel
+ ; line
+ dec e ; are we there yet
+ jr nz, mdv_motor_0 ; send zero
+;
+; Clock out an "on" bit
+;
+ ld a,#1
+ out (0xF7), a
+ ld a, #0xee
+ out (c), a
+ call nap_1ms
+ ld a, #0xec
+ jr mdv_motor_1
+;
+; Clock out an "off" bit
+;
+mdv_motor_0:
+ xor a
+ out (0xEF), a
+ call nap_1ms ; 1ms pulse 0
+ ld a, #0xED
+
+mdv_motor_1: out (c), a
+ call nap_1ms
+ djnz mdv_motor_lp
+
+;
+; "Spin" up the drive - in our case get the tape to drive speed
+;
+mdv_spin_up:
+ ld bc, #13000
+ jp nap
+
+
+;
+; C language interfaces
+;
+; int mdv_motors_off(void)
+;
+_mdv_motor_off: call mdv_motors_off
+ret0:
+ ld hl, #0
+ ret
+
+;
+; int mdv_motor_on(uint8_t drive)
+;
+_mdv_motor_on: pop hl
+ pop af
+ push af
+ push hl
+ call mdv_motor
+ jr ret0
+;
+; int mdv_read(void)
+; mdv_sector and mdv_buf have been set up ready
+;
+_mdv_bread:
+ call mdv_fetch
+ jr z, ret0
+ ld l, a
+ xor a
+ ld h, a
+ ret
+
+_mdv_bwrite:
+ ld hl, #0xffff ; not done yet
+ ret
+
+
+;
+; Bootstrap logic. This is used when the cartridge powers up
+; in order to load the rest of the kernel from the boot microdrive
+; Interrupts are off, stack is valid. We don't check if the tape
+; causes a stack overwrite, that's operator error!
+
+mdv_boot:
+
+ ld (0xfffe), sp
+ ld sp, #0xfffe
+;
+; Spin up the boot volume
+;
+ ld hl, #0x4000
+ ld (_mdv_buf), hl
+ ld a, #1
+ out (0xfe), a ; blue
+ call mdv_motor
+ ld hl, #1024 ; 4 trips round the tape
+mdv_boot_loop:
+ push hl
+;
+; Each loop we fetch a block and if its an 'FK' block then we
+; load it into RAM at the given offset for 512 bytes. We assume that
+; the mdv is created with sufficient interleave we can keep pulling
+; the next block ok
+;
+ call mdv_get_hdr
+HBP:
+ jr nz, mdv_bad
+ call mdv_get_blk
+ jr nz, mdv_bad
+ ld ix, #_mdv_hdr_buf
+ ld a, #'F' ; magic for kernel blocks
+ cp 4(ix)
+ jr nz, not_fk
+ ld a, #'K'
+ cp 5(ix)
+ jr nz, not_fk
+ ld hl, #0x5800 ; attribute memory
+ ld d, #0
+ ld e, 1(ix)
+ add hl, de
+ ld (hl), #0x1f
+ inc hl
+ ld (hl), #0x1f
+;
+; We may ldir over _mdv_hdr_buf so do the attributes then
+; follow up with the block copy
+;
+ ld a, 1(ix)
+ out (0xfe), a ; loading stripes
+ ld d, a ; high byte of address
+ ld e, #0
+ ld hl, (_mdv_buf)
+ ld bc, #512
+ ldir
+ call done_all ; check if we are complete
+ jr z, mdv_boot_done
+not_fk: pop hl
+ dec hl
+ ld a, h
+ or l
+ jr nz, mdv_boot_loop
+mdv_fail: ld a, #2
+ out (0xfe), a ; red border
+failed: jr failed
+
+mdv_bad: cp #3
+ jr z, mdv_fail ; give up return
+ jr not_fk
+
+mdv_boot_done:
+ ld a, #0x7
+ out (0xFE), a
+ ld sp, (0xfffe)
+ ret
+
+done_all: ld hl, #0x585B ; check data is loaded
+ ld b, #0x44
+ ld a, #0x1f
+done_1: cp (hl)
+ ret nz
+ inc hl
+ djnz done_1
+ ld hl, #0x58C0 ; and code
+ ld b, #0x3E ; but not FE-FFFF
+done_2: cp (hl)
+ ret nz
+ inc hl
+ djnz done_2
+ ret ; Z
+