;----------------------------------------------------------;
; BOS kernel                Christoffer Bubach, 2004-2007. ;
;----------------------------------------------------------;
;                                                          ;
;               floppy disk driver.                        ;
;                                                          ;
;----------------------------------------------------------;



;---------------------------;
;  variables and contants   ;
;---------------------------;
    fdd_int_done    dw   0                           ; is the IRQ done?
    fdd_motor_on    db   0                           ; fdd motor on=1, off=0
    fdd_drivehead   db   0                           ; ((head*4)|drive)
    fdd_head        db   0
    fdd_track       db   0
    fdd_sector      db   0
    fdd_error       db   0                           ; should we recalibrate
                                                     ; at next read/write?
    fdd_errorcode   db   0

    result_ST0      db   0
    result_ST1      db   0                           ; or pcn
    result_ST2      db   0
    result_ST3      db   0
    result_C        db   0
    result_H        db   0
    result_R        db   0
    result_N        db   0
    result_extra    db   0



;------------------------------------------------------;
;    initializes the floppy disk driver                ;
;                                                      ;
;------------------------------------------------------;
fdc_init:
         push    eax
         push    ebx
         push    ecx
         push    edi
         push    esi

         xor     eax, eax                            ; get configuration
         mov     al, 0x10                            ; from CMOS.
         out     0x70, al
         in      al, 0x71

         shr     al, 4
         cmp     al, 4                               ; a 1.44mb?
         jnz     .no_floppy

         mov     cl, 0x26                            ; hook IRQ 6
         mov     dx, 0x8                             ; CS = 8
         mov     edi, floppy_irq
         call    set_int

         mov     cl, 6                               ; enable IRQ6
         call    enable_irq

         call    fdd_reset                           ; reset FDC

    .no_floppy:
         pop     esi
         pop     edi
         pop     ecx
         pop     ebx
         pop     eax
         ret



;------------------------------------------------------;
;         floppy IRQ                                   ;
;                                                      ;
;------------------------------------------------------;
floppy_irq:
         push    ds
         push    eax
         push    ebx

         mov     ax, 0x10
         mov     ds, ax

         mov     [fdd_int_done], 1
         mov     al, 0x20
         out     0x20, al

         pop     ebx
         pop     eax
         pop     ds
         iret



;------------------------------------------------------;
;     wait for a floppy int                            ;
;                           out:  cf  = 1 if timeout   ;
;------------------------------------------------------;
wait_int:
         push    eax
         push    ecx

         mov     ecx, 150                            ; 50 = 0.5 seconds.
         call    active_delay                        ; timer.inc
    .l1:
         cmp     dword [ecx], 0                      ; timeup?
         je      .error
         mov     ax, [fdd_int_done]                  ; if not we check for int.
         or      ax, ax
         jz      .l1

         clc
         jmp     .end
    .error:
         stc
    .end:
         pop     ecx
         pop     eax
         ret



;------------------------------------------------------;
;     fdd motor off                                    ;
;                           out:  nothing              ;
;------------------------------------------------------;
fdd_off:
         cmp     [fdd_motor_on], 0
         je      .end
         push    eax
         push    edx

         mov     dx, 0x3F2
         mov     al, 0x0c                            ; motor off
         out     dx, al
         mov     [fdd_motor_on], 0

         pop     edx
         pop     eax
    .end:
         ret



;------------------------------------------------------;
;     fdd motor on                                     ;
;                           out: nothing               ;
;------------------------------------------------------;
fdd_on:
         cmp     [fdd_motor_on], 1
         je      .end
         push    eax
         push    edx

         mov     dx, 0x3F2                           ; motor 0 on..
         mov     al, 0x1C
         out     dx, al

         mov     ecx, 20                             ; 1/5 of a sec. to speed up
         call    delay                               ; in timer.inc
         mov     [fdd_motor_on], 1

         pop     edx
         pop     eax
    .end:
         ret



;------------------------------------------------------;
;   send a data byte to the FDC                        ;
;                                                      ;
;                                in:  al  = data byte  ;
;------------------------------------------------------;
fdc_sendbyte:
         push    edx
         push    ecx
         push    eax

         mov     ecx, 50                             ; 50 = 0.5 seconds.
         call    active_delay                        ; timer.inc
    .l1:
         cmp     dword [ecx], 0                      ; timeup?
         je      .error
         mov     dx, 0x3f4                           ; check status reg
         in      al, dx
         and     al, 0xC0
         cmp     al, 0x80                            ; ok to write?
         jnz     .l1

         pop     eax
         pop     ecx
         mov     dx, 0x3F5                           ; send byte
         out     dx, al
         pop     edx
         clc
         ret
    .error:
         pop     eax
         pop     ecx
         pop     edx
         stc
         ret



;------------------------------------------------------;
;   read a data byte from the FDC                      ;
;                                                      ;
;                             out: al  = data byte     ;
;------------------------------------------------------;
fdc_getbyte:
         push    edx
         push    ecx
         push    eax

         mov     ecx, 50                             ; 50 = 0.5 seconds.
         call    active_delay                        ; timer.inc
    .l1:
         cmp     dword [ecx], 0                      ; timeup?
         je      .error
         mov     dx, 0x3f4                           ; check status reg
         in      al, dx
         and     al, 0xD0
         cmp     al, 0xD0                            ; ok to read?
         jnz     .l1

         pop     eax
         pop     ecx
         mov     dx, 0x3F5                           ; get the byte
         in      al, dx
         pop     edx
         clc
         ret
    .error:
         pop     eax
         pop     ecx
         pop     edx
         stc
         ret



;------------------------------------------------------;
;   sense interrupt status command                     ;
;                                                      ;
;------------------------------------------------------;
sensei:
         push    eax

         mov     al, 0x08                            ; fdc command
         call    fdc_sendbyte
         call    fdc_getbyte
         mov     ah, al                              ; save ST0 in ah
         call    fdc_getbyte                         ; read PCN
         clc
         test    ah, 0x80                            ; test for error:
         jz      .end                                ; "invalid command"
         stc
    .end:
         pop     eax
         ret



;------------------------------------------------------;
;    reset controller                                  ;
;                                                      ;
;------------------------------------------------------;
fdd_reset:
         push    eax
         push    ecx
         push    edx

         mov     byte [fdd_motor_on], 0

         mov     dx, 0x3f2
         mov     al, 8                               ; off with all motors,
         out     dx, al                              ; dma,irq etc..

         mov     ecx, 5
         call    delay                               ; in timer.inc

         mov     dx, 0x3f7
         mov     al, 0
         out     dx, al                              ; work at 500 kbit/s

         mov     dx, 0x3f2
         mov     al, 0x0c
         out     dx, al                              ; reenable interrupts

         mov     [fdd_int_done], 0
         call    wait_int                            ; wait for floppy int.
         jc      .error                              ; timeout?

         mov     cx, 0x04
    .status:                                         ; 4 dummy-reads.
         call    sensei
         loop    .status

         mov     al, 0x03                            ; specify command
         call    fdc_sendbyte
         mov     al, 0xDF                            ; SRT, HUT
         call    fdc_sendbyte
         mov     al, 0x02                            ; HLT, ND
         call    fdc_sendbyte

         mov     al, 1
         call    fdd_recal_seek
         jc      .error
         call    fdd_off
         clc
         jmp     .end
    .error:
         call    fdd_off
         stc
    .end:
         pop     edx
         pop     ecx
         pop     eax
         ret



;------------------------------------------------------;
;  fdd_recal_seek  -   fdd recalibrate/seek            ;
;------------------------------------------------------;
;                                                      ;
;   in:  al  = 0 on seek, 1 on recalibrate             ;
;        bl  = (at seek) track                         ;
;        bh  = (at seek) ((head*4)|drive)              ;
;                                                      ;
;------------------------------------------------------;
fdd_recal_seek:
         push    eax

         call    fdd_on                              ; turn motor on
         cmp     al, 0
         jne     .recalibrate
         clc
         cmp     bl, [result_C]                      ; are we there yet? :D
         je      .ok
         mov     al, 0x0F                            ; seek command
         call    fdc_sendbyte
         mov     al, bh                              ; ((head*4)|drive)
         call    fdc_sendbyte
         mov     al, bl                              ; track
         call    fdc_sendbyte
         mov     [result_C], bl                      ; now on..?
         jmp     .get_int
    .recalibrate:
         mov     al, 0x07                            ; recalibrate command
         call    fdc_sendbyte
         mov     al, 0                               ; drive number
         call    fdc_sendbyte
         mov     [result_C], 0                       ; now on track 0
    .get_int:
         mov     [fdd_int_done], 0
         call    wait_int
         jc      .error

         call    sensei                              ; sense interrupt status
         jc      .error
     .ok:
         clc
         jmp     .end
    .error:
         stc
    .end:
         pop     eax
         ret



;------------------------------------------------------;
;  fdd_read_write   -  fdd read/write                  ;
;------------------------------------------------------;
;   input:  bl  = 0 read, 1 write                      ;
;           ch  = track/cylinder                       ;
;           cl  = sector                               ;
;           dh  = head                                 ;
;           edi = address to store or read the data    ;
;   output: al  = status                               ;
;           cf  = 0 if ok, 1 if error                  ;
;------------------------------------------------------;
fdd_read_write:
         pushad

         and     dh, 1                               ; head 0 or 1?
         mov     [fdd_head], dh                      ; store it.
         shl     dh, 2
         or      dh, 0                               ; drive 0, fd0
         mov     [fdd_drivehead], dh                 ; dh = ((head*4)|drive)
         mov     [fdd_errorcode], 0x04               ; basic error code
         cmp     ch, 0x51                            ; check for allowed
         jae     .error                              ; track number.
         mov     [fdd_track], ch
         cmp     cl, 0x13                            ; check for allowed
         jae     .error                              ; sector number.
         mov     [fdd_sector], cl

         cmp     [fdd_error], 1
         jne     .no_previous_error
         mov     al, 1
         call    fdd_recal_seek
    .no_previous_error:
         call    fdd_on

         mov     dx, 0x3F7
         mov     al, 0                               ; 500Kb/sec mode
         out     dx, al
         mov     [fdd_errorcode], 0x80               ; error code

         xor     ecx, ecx
         mov     cx, 3                               ; try seek 3 times
    .l2:
         mov     al, 0
         push    ebx
         mov     bl, [fdd_track]
         mov     bh, [fdd_drivehead]                 ; ((head*4)|drive)
         call    fdd_recal_seek
         pop     ebx
         jnc     .l3                                 ; ok, continue.
         loop    .l2
         jmp     .error                              ; timeout.
    .l3:
         push    ebx
         cmp     bl, 0
         je      .read_fdd

    .write_fdd:
         push    edi
         mov     esi, edi
         mov     edi, 0x80000                        ; copy the stuff we will
         mov     ecx, 128                            ; write to the DMA buffer
         rep     movsd                               ; 128*4=512
         pop     edi

         mov     bl, 2                               ; channel 2
         mov     esi, 512                            ; bytes to write
         mov     ecx, 0x80000                        ; page & offset
         mov     bh, 1                               ; write floppy, read DMA
         call    dma_transfer

         mov     al, 0xC5                            ; write sector command
         call    fdc_sendbyte
         jmp     .cont

    .read_fdd:
         mov     bl, 2                               ; channel 2
         mov     esi, 512                            ; bytes to read
         mov     ecx, 0x80000                        ; page & offset
         mov     bh, 0                               ; read floppy, write DMA
         call    dma_transfer

         mov     al, 0xE6                            ; read sector command
         call    fdc_sendbyte

    .cont:
         pop     ebx
         mov     al, [fdd_drivehead]                 ; ((head*4)|drive)
         call    fdc_sendbyte
         mov     al, [fdd_track]                     ; track/cylinder
         call    fdc_sendbyte

         mov     al, [fdd_head]                      ; head/side 0 or 1
         call    fdc_sendbyte
         mov     al, [fdd_sector]                    ; sector number
         call    fdc_sendbyte
         mov     al, 0x02                            ; sector size, 512 bytes
         call    fdc_sendbyte

         mov     al, 0x12                            ; sectors to a track
         call    fdc_sendbyte
         mov     al, 0x1B                            ; gap length
         call    fdc_sendbyte
         mov     al, 0xFF                            ; data length
         call    fdc_sendbyte

         mov     [fdd_int_done], 0
         call    wait_int
         jc      .error

         call    fdc_getbyte
         mov     [result_ST0], al                    ; ST0
         call    fdc_getbyte
         mov     [result_ST1], al                    ; ST1
         call    fdc_getbyte
         mov     [result_ST2], al                    ; ST2
         call    fdc_getbyte
         mov     [result_C], al                      ; cylinder
         call    fdc_getbyte
         mov     [result_H], al                      ; head
         call    fdc_getbyte
         mov     [result_R], al                      ; sector number.
         call    fdc_getbyte
         mov     [result_N], al                      ; sector size

         test    [result_ST0], 0xc0                  ; test if sr0 is 0xC0
         jnz     .error
         cmp     bl, 1                               ; did we write the disk?
         je      .ok

         mov     ecx, 512                            ; sector size in bytes
         mov     esi, 0x80000                        ; copy to dest. in edi
         rep     movsb
    .ok:
         mov     [fdd_errorcode], 0                  ; no error.. :D
         mov     [fdd_error], 0                      ; no recal. next time
         clc
         jmp     .end
    .error:
         mov     [fdd_error], 1                      ; recalibrate next time
         stc
    .end:
         call    fdd_off                             ; floppy motor off
         popad
         mov     al, [fdd_errorcode]                 ; error status
         ret