; fdc.asm
;
; Robert Östling, 2002-03-10
;
; Floppy disk driver.

%define _fdc_asm 0


[bits 32]
[section .text]


%include "fdc.inc"
%include "mem.inc"
%include "idt.inc"
%include "timer.inc"
%include "irq.inc"
%include "dma.inc"

global	fdc_init
global	fdc_timer
global	fdc_send_byte
global	fdc_read_byte
global	fdc_motor_on
global	fdc_motor_off
global	fdc_reset
global	fdc_wait
global	fdc_seek
global	fdc_recalibrate
global	fdc_read_write



; fdc_init
;
; Initializes the floppy disk driver.
;
; Returns:
; cl = 0 if NEC765 detected, otherwise 1 (enhanced FDC)
; eax = 0 if OK, else 1

fdc_init:
		push	ebx
		push	esi
		push	edi

		call	mem_alloc_dma_page
		or	eax,eax
		jnz	.quit

		xchg	eax,esi
		mov	byte[fdc_dma_page],al
		shl	eax,16
		mov	dword[fdc_dma_buffer],eax

		mov	eax,0x26			; hook interrupt 0x26 (IRQ6)
		mov	ebx,fdc_int
		call	idt_set_interrupt_vector

		mov	edi,fdc_timer
		call	timer_add_callback

		mov	cl,6				; enable IRQ6
		call	irq_enable

		call	fdc_reset			; reset FDC

		mov	al,cmd_version
		call	fdc_send_byte
		call	fdc_read_byte			; version in cl

		xor	eax,eax

		cmp	cl,0x80
		jnz	.enhanced

		mov	cl,0				; return 0 in CL (NEC765 FDC)
		jmp	short .quit
.enhanced:
		mov	cl,1				; return 1 in CL (enhanced FDC)
.quit:
		pop	edi
		pop	esi
		pop	ebx
		ret



; fdc_int
;
; Interrupt function for floppy IRQ
;
; Returns:
; All register unchanged (of course ;)

fdc_int:
		mov	byte[fdc_done],1
		push	eax
		mov	al,0x20
		out	0x20,al
		pop	eax
		iret



; fdc_timer
;
; Called by timer
;
; Returns:
; All registers unchanged

fdc_timer:
		cmp	dword[fdc_timeout],0
		jz	.timeout
		dec	dword[fdc_timeout]
.timeout:
		cmp	dword[fdc_motor_count],0
		jz	.motor
		dec	dword[fdc_motor_count]
		jmp	short .quit
.motor:
		cmp	byte[fdc_motor_status],0
		jz	.quit

		push	eax
		push	edx
		mov	al,0x0c
		mov	dx,fdc_dor
		out	dx,al				; turn off motor
		mov	byte[fdc_motor_status],0
		pop	edx
		pop	eax
.quit:
		ret



; fdc_send_byte
;
; Send a data byte to the FDC
;
; al = data byte
;
; returns:
; eax = 0 if OK, otherwise 1

fdc_send_byte:
		push	ecx
		push	edx

		mov	ah,al
		mov	ecx,0x100
.try_again:
		mov	dx,fdc_msr
		in	al,dx				; read from Main status register
		and	al,0xc0
		cmp	al,0x80				; is bit 7 set, and bit 6 clear?
		jnz	.nope

		mov	dx,fdc_data			; yes, send the data to the Data port
		mov	al,ah
		out	dx,al				; send the byte
		xor	eax,eax				; return 0
		jmp	.quit
.nope:
		loop	.try_again
		mov	eax,1
.quit:

		pop	edx
		pop	ecx
		ret



; fdc_read_byte
;
; Read a data byte from the FDC
;
; returns:
; eax = 0 if OK, otherwise 1
; cl = data byte

fdc_read_byte:
		push	edx

		mov	ah,al
		mov	ecx,0x100
.try_again:
		mov	dx,fdc_msr
		in	al,dx				; read from Main status register
		and	al,0xd0
		cmp	al,0xd0				; are bit 7, 6 and 0 set?
		jnz	.nope

		mov	dx,fdc_data			; yes, read the data from the Data port
		in	al,dx				; read the byte
		mov	cl,al				; and copy it to cl
		xor	eax,eax				; return 0
		jmp	.quit
.nope:
		loop	.try_again
		mov	eax,1
.quit:
		pop	ecx
		ret



; fdc_motor_on
;
; Turns the floppy drive motor on
;
; Returns:
; All register unchanged.

fdc_motor_on:
		push	eax
		push	ecx
		push 	edx

		cmp	byte[fdc_motor_status],0
		jnz	.already_on

		mov	dword[fdc_motor_count],0
		mov	dx,fdc_dor
		mov	al,0x1c
		out	dx,al

		xor	ecx,ecx
		mov	eax,50
		call	timer_delay			; 0.5s delay

		mov	byte[fdc_motor_status],1
.already_on:
		pop	edx
		pop	ecx
		pop	eax
		ret



; fdc_motor_off
;
; Starts the countdown for turning the motor off.
;
; Returns:
; All registers unchanged.

fdc_motor_off:
		mov	dword[fdc_motor_count],200	; 2s timeout
		ret



fdc_reset:
		push	eax
		push	ecx
		push	edx
		push	ebx

		mov	dx,fdc_dor
		mov	al,0
		out	dx,al				; stop motor and disable IRQ/DMA

		mov	dword[fdc_motor_count],0
		mov	byte[fdc_motor_status],0	; off

		;mov	dx,fdc_drs
		mov	dx,0x3f7
		mov	al,0
		out	dx,al				; data rate = 500KB/s

		mov	dx,fdc_dor
		mov	al,0x0c
		out	dx,al				; reenable interrupts

		mov	byte[fdc_done],1
		mov	ebx,1
		call	fdc_wait


		mov	al,cmd_specify
		call	fdc_send_byte
		mov	al,0xdf
		call	fdc_send_byte
		mov	al,0x02
		call	fdc_send_byte

		mov	dl,1
		call	fdc_seek
		call	fdc_recalibrate

		mov	byte[fdc_diskchange],0

		pop	ebx
		pop	edx
		pop	ecx
		pop	eax
		ret



; fdc_wait
;
; Waits for FDC command to complete
;
; ebx = sense interrupt status (0 = no, 1 = yes)
;
; Returns:
; eax = 0 if OK, else 1

fdc_wait:
		push	ecx
		push	edx
		push	ebx

		mov	dword[fdc_timeout],100
.wait_done:
		cmp	byte[fdc_done],0
		jnz	.wait_done_ok
		cmp	dword[fdc_timeout],0
		jz	.wait_done_ok
		jmp	short .wait_done
.wait_done_ok:

		push	esi
		xor	esi,esi
.read_again:
		cmp	esi,7
		jnb	.done_read
		mov	dx,fdc_msr
		in	al,dx
		and	al,0x10
		jz	.done_read

		call	fdc_read_byte

		mov	byte[esi+fdc_status],cl
		inc	esi

		jmp	short .read_again
.done_read:
		pop	esi

		or	ebx,ebx
		jz	.dont_sensei

		mov	al,cmd_sensei
		call	fdc_send_byte
		call	fdc_read_byte
		mov	byte[fdc_sr0],cl
		call	fdc_read_byte
		mov	byte[fdc_track],cl
.dont_sensei:

		mov	byte[fdc_done],0

		cmp	dword[fdc_timeout],0
		jnz	.timeout_ok

		mov	dx,fdc_dir
		in	al,dx
		and	al,0x80
		jz	.no_diskchange

		mov	byte[fdc_diskchange],1
.no_diskchange:
		mov	eax,1
		jmp	.quit
.timeout_ok:
		xor	eax,eax
.quit:
		pop	ebx
		pop	edx
		pop	ecx
		ret



; fdc_seek
;
; dl = track
;
; Returns:
; eax = 0 if OK, else 1

fdc_seek:
		push	ecx
		push	ebx

		xor	eax,eax
		cmp	dl,byte[fdc_track]
		jz	.quit

		mov	al,cmd_seek
		call	fdc_send_byte
		mov	al,0
		call	fdc_send_byte
		mov	al,dl
		call	fdc_send_byte

		mov	ebx,1
		call	fdc_wait
		or	eax,eax
		jnz	.quit

		mov	byte[0xb8402],'a' ; ************

		xor	ecx,ecx
		mov	eax,2
		call	timer_delay

		and	byte[fdc_sr0],0x20
		jz	.error
		mov	byte[0xb8404],dl ; ************
		add	byte[0xb8404],'0' ; ************
		mov	al,byte[fdc_track]
		mov	byte[0xb8406],al ; ************
		add	byte[0xb8406],'0' ; ************
		cmp	dl,byte[fdc_track]
;		jnz	.error
.ok:
		xor	eax,eax
		jmp	short .quit
.error:
		mov	byte[0xb8400],'!' ; ************
		mov	eax,1
.quit:
		pop	ebx
		pop	ecx
		ret



; fdc_recalibrate
;
; Returns:
; All registers unchanged.

fdc_recalibrate:
		push	eax
		push	ebx

		call	fdc_motor_on

		mov	al,cmd_recal
		call	fdc_send_byte
		mov	al,0
		call	fdc_send_byte

		mov	ebx,1
		call	fdc_wait

		call	fdc_motor_off

		pop	ebx
		pop	eax
		ret




; fdc_read_write
;
; edi = read (0 = false, 1 = true)
; esi = pointer to data
; ecx = sectors to read/write
; bl = track
; bh = head
; bits 16-23 of ebx = sector

fdc_read_write:
		push	ecx
		push	edx
		push	ebx
		push	esi
		push	edi

		call	fdc_motor_on

		or	edi,edi
		jnz	.dont_copy

		push	ecx
		push	esi
		push	edi
		mov	edi,dword[fdc_dma_buffer]
		shl	ecx,7
		rep	movsd
		pop	edi
		pop	esi
		pop	ecx
.dont_copy:

.try_again:
		mov	dx,fdc_dir
		in	al,dx
		and	al,0x80
		jz	.no_diskchange

		mov	byte[fdc_diskchange],1
		mov	dl,1
		call	fdc_seek
		call	fdc_recalibrate
		call	fdc_motor_off
		mov	eax,1
		jmp	.quit
.no_diskchange:
		mov	dl,bl
		call	fdc_seek
		or	eax,eax
		jz	.seek_ok

		call	fdc_motor_off
		jmp	.quit
.seek_ok:

		mov	dx,fdc_ccr
		mov	al,0
		out	dx,al

		or	edi,edi
		jz	.write_dma

		mov	byte[0xb8400],'1' ; ***********************

		push	ecx
		push	ebx
		push	esi

		mov	bl,2				; channel 2
		mov	esi,ecx
		shl	esi,9				; bytes to read
		mov	ecx,dword[fdc_dma_buffer]	; page & offset
		mov	bh,1				; read

		call	dma_transfer

		pop	esi
		pop	ebx
		pop	ecx

		mov	al,cmd_read
		call	fdc_send_byte

		mov	byte[0xb8402],'2' ; ***********************


		jmp	short .read_dma
.write_dma:

		push	ecx
		push	ebx
		push	esi

		mov	bl,2				; channel 2
		mov	esi,ecx
		shl	esi,9				; bytes to write
		mov	ecx,dword[fdc_dma_buffer]	; page & offset
		mov	bh,0				; write
		call	dma_transfer

		pop	esi
		pop	ebx
		pop	ecx

		mov	al,cmd_write
		call	fdc_send_byte
.read_dma:

		mov	al,bh
		shl	al,2
		call	fdc_send_byte			; head

		mov	al,bl
		call	fdc_send_byte			; track

		mov	al,bh
		call	fdc_send_byte			; head

		mov	eax,ebx
		shr	eax,16
		call	fdc_send_byte			; sector

		mov	al,2
		call	fdc_send_byte

		mov	al,18
		call	fdc_send_byte			; sectors per track

		mov	al,0x1b
		call	fdc_send_byte

		mov	al,0xff
		call	fdc_send_byte

		mov	byte[0xb8404],'3' ; ***********************

		push	ebx
		mov	ebx,1
		call	fdc_wait
		pop	ebx
		or	eax,eax
		jnz	.quit

		mov	byte[0xb8406],'4' ; ***********************

.operation_ok:
		mov	al,byte[fdc_status]
		and	al,0xc0
		jz	.ok

		mov	byte[0xb8408],'5' ; ***********************

		call	fdc_recalibrate
		mov	eax,1
		jmp	.try_again
.ok:

		mov	byte[0xb840a],'6' ; ***********************
		call	fdc_motor_off

		or	ebx,ebx
		jz	.dont_read
		or	esi,esi
		jz	.dont_read

		push	ecx
		push	esi
		push	edi
		mov	edi,esi
		mov	esi,dword[fdc_dma_buffer]
		shl	ecx,7
		rep	movsd
		pop	edi
		pop	esi
		pop	ecx
.dont_read:
		xor	eax,eax

.quit:
		pop	edi
		pop	esi
		pop	ebx
		pop	edx
		pop	ecx
		ret


[section .data]

fdc_diskchange		db	0
fdc_status_i		dw	0
fdc_status		db	0,0,0,0,0,0,0
fdc_timeout		dd	0
fdc_sr0			db	0
fdc_track		db	0xff

[section .bss]

fdc_motor_status resb	1
fdc_motor_count	resd	1
fdc_dma_page	resb	1
fdc_dma_buffer	resd	1
fdc_done	resb	1
