;------------------------------------------------------
; MFM trackloader based on Photon/Scoopex old snippets
; Changes by Michael "Axis" Hillebrandt.
; Completely reworked by Chris 'platon42' Hodges.
;------------------------------------------------------

MFMsync     = $4489
MFMBUFSIZE  = 12800

        IF      FW_MULTITASKING_SUPPORT
        IFEQ    FW_YIELD_FROM_MAIN_TOO
        fail    "Trackloader will be called from main task, too"
        ENDC
TRKLDRYIELD MACRO
        bsr     fw_Yield
        ENDM
        ELSE
TRKLDRYIELD MACRO
        ENDM
        ENDC

; Initializes the loader, turns motor on and steps to track 0
fw_InitTrackLoader:
        move.l  #MFMBUFSIZE,d0
        bsr     fw_AllocChip
        move.l  a0,fw_MfmTrackBuffer(a6)
        move.l  #11*512,d0
        bsr     fw_AllocFast
        move.l  a0,fw_TrackBuffer(a6)

        PUSHM   a4
        PUTMSG  10,<"MfmTrack buffer at %p, decoded Track buffer at %p">,fw_MfmTrackBuffer(a6),fw_TrackBuffer(a6)
        lea     $bfd100,a4
        clr.w   fw_CurrentHead(a6)
        ;clr.w  fw_CurrentDrive(a6)
        IF      FW_MULTITASKING_SUPPORT
        move.w  #1,fw_TrackloaderIdle(a6)
        ENDC
        moveq.l #-1,d0
        move.w  d0,fw_LastMfmTrack(a6)
        move.w  d0,fw_LastTrack(a6)
        clr.l   fw_TrackChecksum(a6)
        bsr     fw_FindRightFloppyDriveUnit
        POPM
        rts

; Turns on motor if not already on
fw_TrackloaderDiskMotorOn:
        tst.w   fw_DriveMotorOn(a6)
        bne.s   .skip
        PUSHM   a4
        lea     $bfd100,a4
        bsr     fw_MotorOn
        POPM
.skip
        rts

; Turns the motor off (after reading has completed)
fw_TrackloaderDiskMotorOff:
        PUSHM   a4
        lea     $bfd100,a4
        bsr     fw_MotorOff
        POPM
        rts

;--------------------------------------------------------------------
; Load data from disk
;
; In : a0: buffer to load the data into
;      d0: disk starting offset
;      d1: length in bytes
;
fw_TrackloaderLoad:
        PUSHM   d4-d7/a4
        lea     $bfd100,a4
        lea     .plaincopy(pc),a2
        bsr     fw_LoadMFM
        POPM
        rts

.plaincopy
        PUTMSG  10,<"Copying %d bytes (%ld left) from %p to %p">,d3,d7,a1,a3
        subq.w  #1,d3
        lsr.w   #1,d3
.copyloop
        move.w  (a1)+,(a3)+
        dbra    d3,.copyloop
        rts

;--------------------------------------------------------------------
; Load LZ4 compressed data from disk and decompress it while loading
;
; In : a0: buffer to load the data into
;      d0: disk starting offset
;      d1: compressed length in bytes
;
        IF      FW_TRACKMO_LZ4_SUPPORT
fw_TrackloaderLoadAndDecrunchLZ4:
        PUSHM   d4-d7/a4
        lea     $bfd100,a4
        lea     .lz4decode(pc),a2
        clr.w   fw_TrackLz4State(a6)
        bsr     fw_LoadMFM
        POPM
        rts

.lz4decode
        moveq.l #0,d5
        move.w  fw_TrackLz4State(a6),d5
        PUTMSG  50,<"LZ4 State %d %ld bytes, %ld left from %p to %p">,d5,d3,d7,a1,a3
        lea     .lz4states(pc),a0
        adda.w  (a0,d5.w),a0
        jmp     (a0)
.lz4states
        dc.w    .lzstart-.lz4states         ; 0
        dc.w    .lzlitsizeentry-.lz4states  ; 2
        dc.w    .lzlitcopy-.lz4states       ; 4
        dc.w    .lzreadoffsetlo-.lz4states  ; 6
        dc.w    .lzreadoffsethi-.lz4states  ; 8
        dc.w    .lzmatchlengthentry-.lz4states ; 10

.lzstart
        moveq.l #0,d0
        move.b  (a1)+,d0
        moveq.l #15,d1
        and.w   d0,d1
        lsr.b   #4,d0
        move.w  d0,fw_TrackLz4LiteralLength(a6)
        move.w  d1,fw_TrackLz4MatchLength(a6)

        moveq.l #2,d5   ; next state (lit size)

        cmp.w   #15,d0
        blt.s   .lzlitsizefinished

.lzlitsizeloop
        subq.w  #1,d3
        beq.s   .lzstateterm    ; buffer ends before literal size finalized
.lzlitsizeentry
        moveq.l #0,d0
        move.b  (a1)+,d0
        add.w   d0,fw_TrackLz4LiteralLength(a6)
        not.b   d0
        beq.s   .lzlitsizeloop

.lzlitsizefinished
        moveq.l #4,d5   ; next state (lit copy)

        subq.w  #1,d3
        beq.s   .lzstateterm    ; buffer ends right after literal size finished

.lzlitcopy
        move.w  fw_TrackLz4LiteralLength(a6),d0
        beq.s   .lznoliterals   ; literal size is 0, skip copying literals
        cmp.w   d3,d0
        ble.s   .lzfulllitcopy
        ; at least one byte is leftover
.lzlitcopyuntileob
        move.w  d3,d0
        sub.w   d3,fw_TrackLz4LiteralLength(a6) ; deduct from literal size
        subq.w  #1,d3
.lzlitcopyloopeob
        move.b  (a1)+,(a3)+
        dbra    d3,.lzlitcopyloopeob
        bra     .lzstateterm

.lzstateterm
        PUTMSG  40,<"Terminated at %p (output %p)">,a1,a3
        move.w  d5,fw_TrackLz4State(a6)
        rts

.lzfulllitcopy
        sub.w   d0,d3
        subq.w  #1,d0
.lzlitcopyloop
        move.b  (a1)+,(a3)+
        dbra    d0,.lzlitcopyloop

.lznoliterals
        moveq.l #6,d5   ; next state (match offset hi)
        tst.w   d3
        beq.s   .lzstateterm    ; buffer ended right after lit copy
.lzreadoffsetlo
        move.b  (a1)+,fw_TrackLz4Offset+3(a6)
        moveq.l #8,d5   ; next state (match offset lo)
        subq.w  #1,d3
        beq.s   .lzstateterm    ; buffer ended right after offset lo
.lzreadoffsethi
        move.b  (a1)+,fw_TrackLz4Offset+2(a6)
        moveq.l #10,d5   ; next state (additional match length)
        moveq.l #0,d0
        move.w  fw_TrackLz4MatchLength(a6),d1
        cmp.w   #15,d1
        blt.s   .lzskipmatchlength

.lzmatchlengthloop
        subq.w  #1,d3
        bne.s   .lzmatchlengthenterloop
        move.w  d1,fw_TrackLz4MatchLength(a6)
        bra.s   .lzstateterm    ; buffer ends before literal size finalized
.lzmatchlengthentry
        move.w  fw_TrackLz4MatchLength(a6),d1
        moveq.l #0,d0
.lzmatchlengthenterloop
        move.b  (a1)+,d0
        add.w   d0,d1
        not.b   d0
        beq.s   .lzmatchlengthloop

.lzskipmatchlength
        ; copy match
        addq.w  #3,d1
        move.l  a3,a0
        suba.l  fw_TrackLz4Offset(a6),a0
.lzmatchcopy
        move.b  (a0)+,(a3)+
        dbra    d1,.lzmatchcopy
.lzskipmatch
        moveq.l #0,d5           ; reset state machine
        subq.w  #1,d3
        beq     .lzstateterm    ; buffer ends before next sequence
        bra     .lzstart        ; start over
        ENDC

;--------------------------------------------------------------------
; Load LZ4 and delta compressed data from disk and decompress it while loading
;
; In : a0: buffer to load the data into
;      d0: disk starting offset
;      d1: compressed length in bytes
;
        IF      FW_TRACKMO_LZ4_DLT8_SUPPORT
fw_TrackloaderLoadAndDecrunchLZ4Delta8:
        PUSHM   d4-d7/a4
        lea     $bfd100,a4
        lea     .lz4decode(pc),a2
        clr.w   fw_TrackLz4State(a6)
        clr.b   fw_TrackLz4Delta8Value(a6)
        bsr     fw_LoadMFM
        POPM
        rts

.lz4decode
        moveq.l #0,d5
        move.w  fw_TrackLz4State(a6),d5
        PUTMSG  50,<"LZ4 State %d %ld bytes, %ld left from %p to %p">,d5,d3,d7,a1,a3
        lea     .lz4states(pc),a0
        adda.w  (a0,d5.w),a0
        jmp     (a0)
.lz4states
        dc.w    .lzstart-.lz4states         ; 0
        dc.w    .lzlitsizeentry-.lz4states  ; 2
        dc.w    .lzlitcopy-.lz4states       ; 4
        dc.w    .lzreadoffsetlo-.lz4states  ; 6
        dc.w    .lzreadoffsethi-.lz4states  ; 8
        dc.w    .lzmatchlengthentry-.lz4states ; 10

.lzstart
        moveq.l #0,d0
        move.b  (a1)+,d0
        moveq.l #15,d1
        and.w   d0,d1
        lsr.b   #4,d0
        move.w  d0,fw_TrackLz4LiteralLength(a6)
        move.w  d1,fw_TrackLz4MatchLength(a6)

        moveq.l #2,d5   ; next state (lit size)

        cmp.w   #15,d0
        blt.s   .lzlitsizefinished

.lzlitsizeloop
        subq.w  #1,d3
        beq.s   .lzstateterm    ; buffer ends before literal size finalized
.lzlitsizeentry
        moveq.l #0,d0
        move.b  (a1)+,d0
        add.w   d0,fw_TrackLz4LiteralLength(a6)
        not.b   d0
        beq.s   .lzlitsizeloop

.lzlitsizefinished
        moveq.l #4,d5   ; next state (lit copy)

        subq.w  #1,d3
        beq.s   .lzstateterm    ; buffer ends right after literal size finished

.lzlitcopy
        move.w  fw_TrackLz4LiteralLength(a6),d0
        beq.s   .lznoliterals   ; literal size is 0, skip copying literals
        cmp.w   d3,d0
        ble.s   .lzfulllitcopy
        ; at least one byte is leftover
.lzlitcopyuntileob
        move.w  d3,d0
        sub.w   d3,fw_TrackLz4LiteralLength(a6) ; deduct from literal size
        subq.w  #1,d3
        move.b  fw_TrackLz4Delta8Value(a6),d4
.lzlitcopyloopeob
        add.b   (a1)+,d4
        move.b  d4,(a3)+
        dbra    d3,.lzlitcopyloopeob
        move.b  d4,fw_TrackLz4Delta8Value(a6)
        bra     .lzstateterm

.lzstateterm
        PUTMSG  40,<"Terminated at %p (output %p)">,a1,a3
        move.w  d5,fw_TrackLz4State(a6)
        rts

.lzfulllitcopy
        sub.w   d0,d3
        subq.w  #1,d0
        move.b  fw_TrackLz4Delta8Value(a6),d4
.lzlitcopyloop
        add.b   (a1)+,d4
        move.b  d4,(a3)+
        dbra    d0,.lzlitcopyloop
        move.b  d4,fw_TrackLz4Delta8Value(a6)

.lznoliterals
        moveq.l #6,d5   ; next state (match offset hi)
        tst.w   d3
        beq.s   .lzstateterm    ; buffer ended right after lit copy
.lzreadoffsetlo
        move.b  (a1)+,fw_TrackLz4Offset+3(a6)
        moveq.l #8,d5   ; next state (match offset lo)
        subq.w  #1,d3
        beq.s   .lzstateterm    ; buffer ended right after offset lo
.lzreadoffsethi
        move.b  (a1)+,fw_TrackLz4Offset+2(a6)
        moveq.l #10,d5   ; next state (additional match length)
        moveq.l #0,d0
        move.w  fw_TrackLz4MatchLength(a6),d1
        cmp.w   #15,d1
        blt.s   .lzskipmatchlength

.lzmatchlengthloop
        subq.w  #1,d3
        bne.s   .lzmatchlengthenterloop
        move.w  d1,fw_TrackLz4MatchLength(a6)
        bra.s   .lzstateterm    ; buffer ends before literal size finalized
.lzmatchlengthentry
        move.w  fw_TrackLz4MatchLength(a6),d1
        moveq.l #0,d0
.lzmatchlengthenterloop
        move.b  (a1)+,d0
        add.w   d0,d1
        not.b   d0
        beq.s   .lzmatchlengthloop

.lzskipmatchlength
        ; copy match
        addq.w  #3,d1
        move.l  a3,a0
        suba.l  fw_TrackLz4Offset(a6),a0
        move.b  fw_TrackLz4Delta8Value(a6),d4
.lzmatchcopy
        move.b  (a0)+,d0
        sub.b   -2(a0),d0
        add.b   d0,d4
        move.b  d4,(a3)+
        dbra    d1,.lzmatchcopy
        move.b  d4,fw_TrackLz4Delta8Value(a6)
.lzskipmatch
        moveq.l #0,d5           ; reset state machine
        subq.w  #1,d3
        beq     .lzstateterm    ; buffer ends before next sequence
        bra     .lzstart        ; start over
        ENDC

;--------------------------------------------------------------------
; Waits for a diskchange (disk is removed and inserted again)
; 
fw_TrackloaderWaitForDiskChange:
        PUSHM   a4
        lea     $bfd100,a4
.ready  TRKLDRYIELD
        btst    #CIAB_DSKRDY,$bfe001-$bfd100(a4)
        bne.s   .ready
.notready
        TRKLDRYIELD
        btst    #CIAB_DSKRDY,$bfe001-$bfd100(a4)
        beq.s   .notready

        bsr     fw_MotorOff
        POPM
        rts

; Load sectors from disk
; a0 - buffer to load the data into (must be even)
; a2 - routine to be called for post processing buffer with a3 current buffer pos, a1, trackdisk buffer, d3 length
; d0.l - disk offset (must be even)
; d1.l - bytes to read (may be odd, but one extra byte will be written then)
; returns a1 end of buffer written
fw_LoadMFM:
        PUTMSG  10,<"%d: LoadMFM of %ld bytes at offset %ld to %p">,fw_FrameCounterLong(a6),d1,d0,a0
        IF      FW_MULTITASKING_SUPPORT
.retry
        subq.w  #1,fw_TrackloaderIdle(a6)
        beq.s   .notbusy
        addq.w  #1,fw_TrackloaderIdle(a6)
        PUTMSG  10,<"Trackloader busy! %p">,sp
.wait
        TRKLDRYIELD
        tst.w   fw_TrackloaderIdle(a6)
        ble.s   .wait
        bra.s   .retry
.notbusy
        ENDC

        PUSHM   d6-d7/a3
        divu    #11*512,d0
        move.l  d0,d6           ; starting track / offset
        move.l  d1,d7           ; length
        move.l  a0,a3

        cmp.w   fw_LastTrack(a6),d6
        beq     .righttrack
.wrongtrack
        cmp.w   fw_LastMfmTrack(a6),d6
        beq.s   .rightmfmtrackbutnotyetdecoded
        tst.w   fw_MfmReadingTriggered(a6)
        beq.s   .noreadinginprogress
        PUTMSG  10,<"Wrong track reading in progress... please wait">
        bsr     fw_WaitForTrackDmaDone
        PUTMSG  10,<"DMA done">
.noreadinginprogress
        tst.w   fw_DriveMotorOn(a6)
        bne.s   .noturnon
        bsr     fw_MotorOn
.noturnon
        bsr     fw_StepToRightCylinderAndSelectRightHead
        bsr     fw_TriggerReadTrack
.nexttrack

.rightmfmtrackbutnotyetdecoded
        bsr     fw_WaitForTrackDmaDone
        tst.w   fw_DriveMotorOn(a6)
        bne.s   .noturnon2
        bsr     fw_MotorOn
.noturnon2
        bsr     fw_PreparePrefetchOfNextTrack
        bsr     fw_DecodeMfmTrack

        tst.w   fw_MfmDoPrefetch(a6)
        beq.s   .noprefetch
        PUTMSG  30,<"Prefetching next track">
        bsr     fw_TriggerReadTrack
.noprefetch
.righttrack
        move.l  d6,d1
        swap    d1              ; start offset inside track
        move.l  fw_TrackBuffer(a6),a1 ; start in track buffer
        adda.w  d1,a1
        move.w  #11*512,d3
        sub.w   d1,d3
        ext.l   d3
        cmp.l   d7,d3
        ble.s   .notcompleteinbuffer
        move.w  d7,d3
.notcompleteinbuffer
        sub.l   d3,d7

        jsr     (a2)

        tst.l   d7
        beq.s   .finished

        swap    d6
        clr.w   d6              ; next track always starts at offset 0
        swap    d6
        bra     .nexttrack
.finished
        move.l  a3,a1
        POPM
        PUTMSG  10,<"%d: Data load finished">,fw_FrameCounterLong(a6)
        IF      FW_MULTITASKING_SUPPORT
        addq.w  #1,fw_TrackloaderIdle(a6)
        ENDC
        ;bsr.s  MotorOff
        rts

; Prefetch the next track to be loaded (but not decoded) into MFM buffer
fw_PreparePrefetchOfNextTrack:
        PUTMSG  20,<"%d: PreparePrefetchOfNextTrack %d+1 (%d:%d)">,fw_FrameCounterLong(a6),fw_LastMfmTrack-2(a6),fw_CurrentCylinder-2(a6),fw_CurrentHead-2(a6)
        PUSHM   d6
        clr.w   fw_MfmDoPrefetch(a6)
        tst.w   fw_MfmReadingTriggered(a6)
        bne.s   .noprefetch

        move.w  fw_CurrentCylinder(a6),d6
        add.w   d6,d6
        add.w   fw_CurrentHead(a6),d6
        cmp.w   fw_LastMfmTrack(a6),d6
        bne.s   .noprefetch
        addq.w  #1,d6
        cmp.w   #80*2,d6
        bge.s   .noprefetch

        lsr.w   #1,d6
        bcs.s   .head1pre
        bsr     fw_DriveStepHeadIn  ; 1 cyl forward
        bsr     fw_SelectUpperHead
        bra.s   .prefetch
.head1pre
        bsr     fw_SelectLowerHead
.prefetch
        st      fw_MfmDoPrefetch(a6)
        move.w  #235,d0             ; 15 ms=235 scan lines!
        bsr     fw_SetDriveSettleTime
.noprefetch
        POPM
        rts

; d6=track
fw_StepToRightCylinderAndSelectRightHead:
        PUTMSG  50,<"Select track %d">,d6
        PUSHM   a0/d0/d1/d6/d7
        move.w  d6,d7
        lsr.w   #1,d7

        sub.w   fw_CurrentCylinder(a6),d7   ;delta-step
        beq.s   .steppingdone
        bmi.s   .stepout
        bsr.s    fw_DriveStepHeadIn
        subq.w  #2,d7
        bmi.s   .steppingdone
.stepinloop
        bsr     fw_DriveStepHeadInFast
        dbra    d7,.stepinloop
        bra.s   .steppingdone
.stepout
        neg.w   d7              ; = neg+sub#1
        bsr.s    fw_DriveStepHeadOut
        subq.w  #2,d7
        bmi.b   .steppingdone
.stepoutloop
        bsr.s    fw_DriveStepHeadOutFast
        dbra    d7,.stepoutloop
.steppingdone
        lsr.w   #1,d6
        bcs.s   .head1
        bsr.s   fw_SelectUpperHead
        bra.s   .done
.head1
        bsr     fw_SelectLowerHead
.done
        move.w  #235,d0         ; 15 ms=235 scan lines!
        bsr     fw_SetDriveSettleTime
        POPM
        rts

; step head 1 track in and wait for timeout
fw_DriveStepHeadIn:
        bsr     fw_LoaderCiaWait
        bclr    #CIAB_DSKDIREC,(a4)
        addq.w  #1,fw_CurrentCylinder(a6)
        PUTMSG  30,<"Step in %d">,fw_CurrentCylinder-2(a6)
fw_DriveStepHead:
        bclr    #CIAB_DSKSTEP,(a4)
        bset    #CIAB_DSKSTEP,(a4)
        move.w  #282,d0         ;18 ms=282 scan lines!
        bra.s   fw_SetDriveSettleTime

; step head 1 track in fast and wait for timeout (this can be used if the direction of the head didnt change)
fw_DriveStepHeadInFast:
        addq.w  #1,fw_CurrentCylinder(a6)
        PUTMSG  30,<"Step in fast %d">,fw_CurrentCylinder-2(a6)
fw_DriveStepHeadFast:
        bsr.s   fw_LoaderCiaWait
        bclr    #CIAB_DSKSTEP,(a4)
        bset    #CIAB_DSKSTEP,(a4)
        moveq.l #47,d0          ;3 ms=47 scan lines!
        bra.s   fw_SetDriveSettleTime

; step head 1 track out and wait for timeout
fw_DriveStepHeadOut:
        bsr.s   fw_LoaderCiaWait
        bset    #CIAB_DSKDIREC,(a4)
        subq.w  #1,fw_CurrentCylinder(a6)
        PUTMSG  30,<"Step out %d">,fw_CurrentCylinder-2(a6)
        bra.s   fw_DriveStepHead

; step head 1 track out fast and wait for timeout (this can be used if the direction of the head didnt change)
fw_DriveStepHeadOutFast:
        subq.w  #1,fw_CurrentCylinder(a6)
        PUTMSG  30,<"Step out fast %d">,fw_CurrentCylinder-2(a6)
        bra.s   fw_DriveStepHeadFast

;switch to upper head and wait for timeout
fw_SelectUpperHead:
        bsr.s   fw_LoaderCiaWait
        PUTMSG  30,<"Head0">
        bset    #CIAB_DSKSIDE,(a4)  ; Head 0
        clr.w   fw_CurrentHead(a6)
        moveq.l #2,d0           ;0,1 ms=2 scan lines!
        bra.s   fw_SetDriveSettleTime

;switch to lower head and wait for timeout
fw_SelectLowerHead:
        bsr.s   fw_LoaderCiaWait
        PUTMSG  30,<"Head1">
        bclr    #CIAB_DSKSIDE,(a4)  ; Head 1
        move.w  #1,fw_CurrentHead(a6)
        moveq.l #2,d0           ;0,1 ms=2 scan lines!
        bra.s   fw_SetDriveSettleTime

; move the head to track 0 (step out until track 0 is reached)
fw_DriveStepToCylinder0:
        bsr.s   fw_LoaderCiaWait
        btst    #CIAB_DSKTRACK0,$bfe001-$bfd100(a4)     ;Cyl 0 when low.
        beq.s   .zeroreached
        bsr     fw_DriveStepHeadOut
.stepto0loop
        btst    #CIAB_DSKTRACK0,$bfe001-$bfd100(a4)     ;Cyl 0 when low.
        beq.s   .zeroreached
        bsr     fw_DriveStepHeadOutFast
        bra.s   .stepto0loop
.zeroreached
        PUTMSG  30,<"Cylinder0">
        clr.w   fw_CurrentCylinder(a6)
        rts

fw_SetDriveSettleTime:
        moveq.l #0,d1
        move.b  $bfda00-$bfd100(a4),d1
        swap    d1
        move.b  $bfd900-$bfd100(a4),-(sp)
        move.w  (sp)+,d1
        move.b  $bfd800-$bfd100(a4),d1
        ext.l   d0
        add.l   d1,d0
        move.l  d0,fw_DriveSettleTime(a6)
        rts

;wait the specified amount of rasterlines
;d0 - amount of scanlines
fw_LoaderCiaWait:
        IF      FW_MULTITASKING_SUPPORT
        bra.s   .skipyield
.yieldloop
        cmp.w   #-50,d1
        bgt.s   .skipyield
        TRKLDRYIELD
.skipyield
        ELSE
.yieldloop
        ENDC
        moveq.l #0,d1
        move.b  $bfda00-$bfd100(a4),d1
        swap    d1
        move.b  $bfd900-$bfd100(a4),-(sp)
        move.w  (sp)+,d1
        move.b  $bfd800-$bfd100(a4),d1
        sub.l   fw_DriveSettleTime(a6),d1
        blt.s   .yieldloop
        PUTMSG  40,<"%ld: YD">,d1
        rts

fw_FindRightFloppyDriveUnit:
        move.l  4.w,a0
        cmp.w   #37,LIB_VERSION(a0)
        bhs.s   .checkdrives
        PUTMSG  10,<"LameOS, no drives check">
        ; kick 1.3 can only boot from DF0
        bsr     fw_MotorOn
        bsr     fw_DriveStepToCylinder0
        rts

.checkdrives
.retry
        move.w  fw_CurrentDrive(a6),d0
        PUTMSG  30,<"Checking disk in drive %d">,d0
        addq.w  #CIAB_DSKSEL0,d0
        or.b    #CIAF_DSKSEL0|CIAF_DSKSEL1|CIAF_DSKSEL2|CIAF_DSKSEL3,(a4)
        bclr    d0,(a4)
        bsr     fw_DriveStepToCylinder0
        bsr     fw_DriveStepHeadIn
        bsr     fw_DriveStepHeadOut
        btst    #CIAB_DSKCHANGE,$bfe001-$bfd100(a4)
        bne.s   .found
        move.w  fw_CurrentDrive(a6),d0
        addq.w  #1,d0
        cmp.w   #4,d0
        beq.s   .error
        move.w  d0,fw_CurrentDrive(a6)
        bra.s   .retry
.found  PUTMSG  10,<"Found valid floppy">
        bsr     fw_MotorOn
        rts
.error
        PUTMSG  10,<"No valid floppy found">
        move.w  #ERROR_DISK,d0
        bra     fw_Error

; turn the floppy motor on and wait until the motor is running
fw_MotorOn:
        PUTMSG  10,<"%d: Motor on">,fw_FrameCounterLong(a6)
        move.w  fw_CurrentDrive(a6),d0
        addq.w  #CIAB_DSKSEL0,d0
        or.b    #CIAF_DSKSEL0|CIAF_DSKSEL1|CIAF_DSKSEL2|CIAF_DSKSEL3,(a4)
        ;bset   d0,(a4)
        bclr    #CIAB_DSKMOTOR,(a4)         ; turns motor on
        bclr    d0,(a4)
        move.w  fw_FrameCounter(a6),d0
        add.w   #25,d0                      ; 500 ms delay max
.diskreadyloop
        TRKLDRYIELD
        cmp.w   fw_FrameCounter(a6),d0
        beq.s   .diskreadybroken
        btst    #CIAB_DSKRDY,$bfe001-$bfd100(a4)        ; wait until motor running
        bne.s   .diskreadyloop
.diskreadybroken
        st      fw_DriveMotorOn(a6)
        rts

; turn the floppy motor off
fw_MotorOff:
.retry
        tst.w   fw_MfmReadingTriggered(a6)
        beq.s   .noreadinginprogress
        PUTMSG  10,<"%d: Waiting for read to finish before turning off motor">,fw_FrameCounterLong(a6)
        bsr     fw_WaitForTrackDmaDone
.noreadinginprogress
        IF      FW_MULTITASKING_SUPPORT
        tst.w   fw_TrackloaderIdle(a6)
        bgt.s   .nowait
        TRKLDRYIELD
        bra.s   .retry
        ENDC
.nowait
        PUTMSG  10,<"%d: Motor Off">,fw_FrameCounterLong(a6)
        move.w  fw_CurrentDrive(a6),d0
        addq.w  #CIAB_DSKSEL0,d0
        bset    d0,(a4)
        bset    #CIAB_DSKMOTOR,(a4)
        bclr    d0,(a4)
        clr.w   fw_DriveMotorOn(a6)
        rts

; trigger reading of one track
; trashes a1, d0
fw_TriggerReadTrack:
        tst.w   fw_MfmReadingTriggered(a6)
        bne     fw_Error
        move.w  fw_CurrentCylinder(a6),d0
        add.w   d0,d0
        add.w   fw_CurrentHead(a6),d0
        cmp.w   fw_LastMfmTrack(a6),d0
        bne.s   .cont
        PUTMSG  30,<"MfmTrack already read %d">,d0
        rts
.cont
        PUTMSG  20,<"%d: Triggered reading of track %d">,fw_FrameCounterLong(a6),d0
        clr.w   fw_MfmReadingDone(a6)
        move.w  d0,fw_LastMfmTrack(a6)

        bsr     fw_LoaderCiaWait                ; wait settle time

        move.l  fw_MfmTrackBuffer(a6),a1

        move.w  #INTF_DSKBLK,intreq(a5)
        move.w  #MFMsync,MFMBUFSIZE-2(a1)       ; make sure we get another sync match at the end of buffer
        clr.w   (a1)+
        move.l  a1,dskpt(a5)
        move.w  #DMAF_SETCLR|DMAF_MASTER|DMAF_DISK,dmacon(a5)
        move.w  #MFMsync,dsksync(a5)
        move.w  #ADKF_SETCLR|ADKF_MFMPREC|ADKF_WORDSYNC|ADKF_FAST,adkcon(a5)
        move.w  #$4000,dsklen(a5)
        move.w  #$8000+(MFMBUFSIZE/2)-2,dsklen(a5)  ; DskLen(12800)+DmaEn
        move.w  #$8000+(MFMBUFSIZE/2)-2,dsklen(a5)  ; start reading MFMdata
        st      fw_MfmReadingTriggered(a6)
        rts

fw_WaitForTrackDmaDone:
        tst.w   fw_MfmReadingTriggered(a6)
        bne.s   .waitdma
        rts
.waitdma
.rereadwaitdma
        PUTMSG  40,<"%d: MFM Wait">,fw_FrameCounterLong(a6)
        IF      FW_MULTITASKING_SUPPORT
        bra.s   .firstskipyield
        ENDC
.waitdmadone
        TRKLDRYIELD
.firstskipyield
        btst    #INTB_DSKBLK,intreqr+1(a5)  ; wait until data read
        beq.s   .waitdmadone
        PUTMSG  20,<"%d: MFM Done">,fw_FrameCounterLong(a6)
        st      fw_MfmReadingDone(a6)
        clr.w   fw_MfmReadingTriggered(a6)
        rts

; Decode the loaded MFM track
fw_DecodeMfmTrack:
        PUSHM   a2-a3/d7

.rereadwaitdma
        bsr     fw_WaitForTrackDmaDone
        PUTMSG  20,<"%d: Decoding Track %d">,fw_FrameCounterLong(a6),fw_LastMfmTrack-2(a6)
        move.w  #-1,fw_LastTrack(a6)        ; mark last track buffer as invalid in case of error
        move.l  #$55555555,d3   ; and-const

        move.l  fw_MfmTrackBuffer(a6),a1
        ; This routine is trickier than it appears. The trick is that we must NOT
        ; assume a $4489 at the beginning of our buffer. This phenomenon occurs when
        ; the DMA starts in the middle of the first sync word. The second sync word
        ; is thrown away by the hardware. It sounds exotic, but it actually happens
        ; quite often!
        cmp.w   #MFMsync,2(a1)
        beq.s   .nofixsyncbug
        PUTMSG  10,<"Fixing missing sync">
        move.w  #MFMsync,(a1)
.nofixsyncbug

        moveq.l #0,d7
        clr.l   fw_TrackChecksum(a6)
.decode
.findsyncword
        lea     MFMBUFSIZE(a1),a2
.syncloop
        PUTMSG  70,<"LW %lx">,(a1)
        cmp.w   #MFMsync,(a1)+  ; search for a sync word
        bne.s   .syncloop
        PUTMSG  60,<"Sync %lx at %p">,(a1),a1
        cmp.l   a1,a2           ; check for end of buffer
        beq     .diskerror      ; no more sync found
        cmp.b   (a1),d3         ; search for 0-nibble
        bne.s   .syncloop

        bsr     .decodemfmword

        PUTMSG  60,<"SectorInfo %lx">,d0
        move.b  d0,d1
        lsr.w   #8,d0           ; sector number
        cmp.w   #11,d0
        bge     .diskerror
        btst    d0,d7
        bne     .diskerror
        bset    d0,d7           ; mark as decoded
        add.w   d0,d0           ; x512
        lsl.w   #8,d0
        move.l  fw_TrackBuffer(a6),a0
        adda.w  d0,a0
        PUTMSG  60,<"Decoding %d to %p">,d0,a0

        move.w  d1,d4
        lea     40(a1),a1       ; found a sec, skip unnecessary data
        bsr     .decodemfmword
        move.l  d0,d2           ; checksum

        lea     512(a1),a2      ; first half of sector in a1 and second half in a2
        moveq.l #(512/4)-1,d5
.decodeloop
        move.l  (a1)+,d0        ; decode fmtbyte/trk#,sec#,eow#
        move.l  (a2)+,d1
        and.l   d3,d0
        and.l   d3,d1
        eor.l   d0,d2           ; EOR with checksum
        eor.l   d1,d2           ; EOR with checksum
        add.l   d0,d0
        or.l    d1,d0           ; MFM decoded first longword
        move.l  d0,(a0)+
        dbra    d5,.decodeloop  ; chksum should now be 0 if correct

        or.l    d2,fw_TrackChecksum(a6) ; or with track total chksum
        cmp.b   #1,d4
        bne.s   .nogapskip
        PUTMSG  60,<"Skipping much of gap after decoding">
        lea     300*2(a2),a2    ; gap of 300 words should be safe (340 is about normal)
.nogapskip
        lea     6(a2),a1
        cmp.w   #(1<<11)-1,d7
        bne     .findsyncword   ; decode until the bitmap is complete
        PUTMSG  50,<"Track Checksum %lx">,fw_TrackChecksum(a6)
        tst.l   fw_TrackChecksum(a6)    ; track total chksum OK?
        bne     .diskerror      ; no, then retry

        move.w  fw_LastMfmTrack(a6),fw_LastTrack(a6)
        move.w  fw_LastMfmTrack(a6),d0
        ext.l   d0
        PUTMSG  10,<"%d: Decoded Track %d">,fw_FrameCounterLong(a6),d0
        POPM
        rts

.decodemfmword
        move.l  (a1)+,d0        ; decode fmtbyte/trk#,sec#,eow#
        move.l  (a1)+,d1
        and.l   d3,d0
        and.l   d3,d1
        add.l   d0,d0
        or.l    d1,d0           ; MFM decoded first longword
        rts

.diskerror
        PUTMSG  10,<"Disk Error!">
        move.w  #$800,color(a5)
        move.w  #-1,fw_LastMfmTrack(a6)
        bsr     fw_TriggerReadTrack
        bra     .rereadwaitdma