;==============================================================================
;	Solar OS - RealTime Operating System
;	Copyright (c) 2001-2004 Ontanu Bogdan Valentin
;	All rights reserved.
;	Bucuresti, Romania
;	Bd. Eroilor Sanitari nr 73B, suite 2
;	www.oby.ro
;	email : bogdanontanu@yahoo.com
;
;	This program is free software; you can redistribute it and/or
;	modify it under the terms of the GNU General Public License
;	as published by the Free Software Foundation; either version 2
;	of the License, or (at your option) any later version.
;
;	This program is distributed in the hope that it will be useful,
;	but WITHOUT ANY WARRANTY; without even the implied warranty of
;	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;	GNU General Public License for more details.
;
;	see file LICENSE.TXT for details
;
;	You should have received a copy of the GNU General Public License
;	along with this program; if not, write to the Free Software
;	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
;
;===============================================================================

;*************************************************
;
; 8272 FDC driver
;
;	FDC is the 8272 or newer IC controller on board
;	FDD is the actual floppy hadware 
;*************************************************


FDC_STATUS_PORT		EQU	03f4h
;----------------------------------
; bit 7 - Request for Master
; bit 6 - Data IO Direction 0=Write 1=Read
; bit 5 - Non DMA
; bit 4 - BUSY 
;----------------------------------

FDC_DATA_PORT		EQU	03f5h

FDD_DOR_PORT		EQU	03f2h
FDD_CCR_PORT		EQU	03f7h


FDD_REQUEST STRUC
	req_pid			dd	?
	req_operation		dd	?
	req_state		dd	?	; internal crt operation status
	req_var1		dd	?	; var to use in state machine
	req_timeout		dd	?
	req_fdd_nr		dd	?
	req_fdd_track		dd	?
	req_fdd_head		dd	?
	req_fdd_sector		dd	?
	req_lp_buff		dd	?	
ENDS

ALIGN 4
fdd_motor_on_flag	dd	0	;global to avoid restarting motor when already on
fdd_motor_on_timer	dd	0	;global count until FDD reset by IRQ-0

fdd_queue_write		dd	0	;where we write next request in queue
fdd_queue_read		dd	0	;where we read events to execute  from queue
	
fdd_queue01	FDD_REQUEST 128 dup(<>)

FDD_OPERATION_READ	EQU	1
FDD_OPERATION_WRITE	EQU	2
FDD_OPERATION_FORMAT	EQU	3	;not implemented yet


;========================================
; Low level API
; ->add this request to fdc/fdd queue
;========================================
Direct_FDD_RW PROC STDCALL
	ARG fdd_operation:dword,fdd_nr:DWORD,track_nr:DWORD,head_nr:DWORD,sect_nr:DWORD,lp_buff:DWORD


	mov	eax,[fdd_queue_write]
	mov	ebx,eax			;save for later
	;--------------------------------
	; test queue overflow
	;--------------------------------
	inc	eax
	and	eax,07fh
	cmp	eax,[fdd_queue_read]
	.IF	ZERO?
		;----------------------------
		; queue overfolow
		; sorry
		; can not add new FDD event
		;----------------------------
		mov	eax,-1
		ret
	.ENDIF

	;----------------------------------------
	; setup new write position
	; but write operation in crt position
	;----------------------------------------
	mov	[fdd_queue_write],eax
	mov	eax,ebx
	;-------------------------------
	; calc destination record
	;-------------------------------
	mov	edi,offset fdd_queue01
	mov	ebx,SIZE FDD_REQUEST
	mul	ebx
	add	edi,eax

	;-------------------------------------
	; write operation record into queue
	;-------------------------------------
	; source PID
	mov	eax,0			;for now PID=0=kernel
	mov	[edi.req_pid],eax

	; operation
	mov	eax,[fdd_operation]
	mov	[edi.req_operation],eax

	; always start in zero state
	mov	[edi.req_state],0

	mov	eax,[fdd_nr]
	and	eax,03h			;no more than 4 drives ;)
	mov	[edi.req_fdd_nr],eax

	mov	eax,[track_nr]
	mov	[edi.req_fdd_track],eax

	mov	eax,[head_nr]
	mov	[edi.req_fdd_head],eax

	mov	eax,[sect_nr]
	mov	[edi.req_fdd_sector],eax

	mov	eax,[lp_buff]
	mov	[edi.req_lp_buff],eax

	; signal everything is ok
	mov	eax,0
	ret
ENDP



;==================================
; Execute FDCD queue events
; 
;==================================
Execute_Fdd_Events PROC

	mov	edi,offset fdd_queue01

	mov	eax,[fdd_queue_read]
	mov	ebx,SIZE FDD_REQUEST
	mul	ebx
	add	edi,eax

	mov	eax,[edi.req_state]
	;----------------------------------------
	; State Machine
	;----------------------------------------
	; used to avoid "busy waiting"
	; while doing long term operations
	; on slow / block devices
	; this is faster than thread switching
	; and allows overlapped I/O
	;----------------------------------------

	;----------------------------------------------
	;however a little more complicated to program
	;----------------------------------------------
	.IF eax==0
		;--------------------------
		; state 0 = just starting
		;--------------------------

		.IF [fdd_motor_on_flag]==1
			;------------------------------------------
			; jump over init states and
			; go directly to SEEK status
			; if this was already init a few time ago
			;------------------------------------------
			;mov	[edi.req_state],6
			;ret	
		.ENDIF
		;----------------------------------------
		; bring FDD/FDC in a known status
		;----------------------------------------
		Call	FDD_Motor_Off	;also FDD off
		;--------------------------------------
		; setup 1.44M speed = 500kb/s
		;--------------------------------------
		mov	edx,FDD_CCR_PORT
		mov	eax,0
		out	dx,al
		;---------------------------------
		; Mottor On again
		; take care as this will 
		; generate an interupt
		;----------------------------------
		mov	[fdc_int_status],0
		Call	FDD_Motor_On
		;-----------------------------------
		; go to wait IRQ state
		;-----------------------------------
		mov	[edi.req_state],1
		ret

	.ELSEIF eax==1
		;-----------------------------------------
		; wait IRQ 6 to come after FDC/Motor On
		;-----------------------------------------
		mov	eax,[fdc_int_status]
		test	eax,eax
		.IF !ZERO?
			;------------------------------
			; ice system counter into var1
			; for next state wait
			;------------------------------
			mov	eax,[cnt_timer]
			mov	[edi.req_var1],eax

			mov	[edi.req_state],2
		.ENDIF
		ret

	.ELSEIF eax==2	
		;-----------------------------------
		; WAIT MOTOR UP
		; wait for motor to speed up = 1 second
		;-----------------------------------
		mov	ebx,[edi.req_var1]
		mov	eax,[cnt_timer]
		sub	eax,ebx			;eax should be greater here because time++
		cmp	eax,18			;1 sec
		.IF !CARRY?
			mov	[edi.req_state],3			
		.ENDIF
		ret	
		
	.ELSEIF eax==3	
		;----------------------
		; SPECIFY
		;----------------------

		;----------------------------
		; mark motor is on
		;----------------------------
		mov	[fdd_motor_on_flag],1
		;----------------------------------
		; write specify command
		;----------------------------------
		Call	FDC_Write_Byte STDCALL,03h	;specify drive params
		Call	FDC_Write_Byte STDCALL,0DFh
		Call	FDC_Write_Byte STDCALL,0010b	;DMA mode ON
		mov	[edi.req_state],4
		ret

	.ELSEIF eax==4	
		;-------------------
		;RECALIBRATE
		;-------------------

		mov	[fdc_int_status],0
		;-------------------------------------
		; recalibrate drive
		;-------------------------------------
		Call	FDC_Write_Byte STDCALL,07h	;recalibrate
		Call	FDC_Write_Byte STDCALL,00h	;drive

		mov	[edi.req_state],5
		ret

	.ELSEIF eax==5
		;-----------------------------------------
		; wait IRQ 6 at end of RECALIBRATE
		;-----------------------------------------
		mov	eax,[fdc_int_status]
		test	eax,eax
		.IF !ZERO?
			;--------------------------
			; sense interupt command
			;--------------------------
			Call	FDC_Write_Byte STDCALL,08h	;sensei
			Call	FDC_Read_Byte STDCALL
			mov	[result_st0],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_st1],eax
			;---------------------------
			; go to next state
			;---------------------------
			mov	[edi.req_state],6
		.ENDIF
		ret

	.ELSEIF eax==6
		;--------------
		;SEEK	
		;--------------

		;-------------------------------------------
		; new int will have to come at end of seek
		;-------------------------------------------
		mov	[fdc_int_status],0

		;---------------------------
		; send seek command
		;----------------------------
		Call	FDC_Write_Byte STDCALL,0Fh			;seek
		Call	FDC_Write_Byte STDCALL,[edi.req_fdd_nr]		;drive
		Call	FDC_Write_Byte STDCALL,[edi.req_fdd_track]	;track

		;---------------------------
		; go to next state
		;---------------------------
		mov	[edi.req_state],7
		ret

	.ELSEIF eax==7
		;-----------------------------------------
		; wait IRQ 6 at end of SEEK
		;-----------------------------------------
		mov	eax,[fdc_int_status]
		test	eax,eax
		.IF !ZERO?
			;--------------------------
			; sense interupt command
			;--------------------------
			Call	FDC_Write_Byte STDCALL,08h	;sensei
			Call	FDC_Read_Byte STDCALL
			mov	[result_st0],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_st1],eax
		.ENDIF
		mov	[edi.req_state],8
		ret

	.ELSEIF eax==8
		;-------------------------------
		; we are now ON TRACK
		; send READ / WRITE COMMAND
		;-------------------------------
		mov	eax,[edi.req_operation]

		.IF eax==FDD_OPERATION_READ
			;---------------------------
			; prepare DMA2 for read
			;---------------------------
			Call	Setup_DMA2_Read

			mov	[fdc_int_status],0	;IRQ will have to come
			;---------------------------------
			; Send read data command
			;---------------------------------
			Call	FDC_Write_Byte STDCALL,46h			;read cmd code

			mov	eax,[edi.req_fdd_nr]
			and	eax,03h
			mov	ebx,[edi.req_fdd_head]
			and	ebx,01
			shl	ebx,2
			or	eax,ebx
			Call	FDC_Write_Byte STDCALL,eax			;drive & head

			Call	FDC_Write_Byte STDCALL,[edi.req_fdd_track]	;C
			Call	FDC_Write_Byte STDCALL,[edi.req_fdd_head]	;H
			Call	FDC_Write_Byte STDCALL,[edi.req_fdd_sector]	;S
			Call	FDC_Write_Byte STDCALL,02h			;N=2=512
			Call	FDC_Write_Byte STDCALL,18			;EOT
			Call	FDC_Write_Byte STDCALL,01Bh			;GPL3
			Call	FDC_Write_Byte STDCALL,0FFh			;DTL & action!
			;---------------------------
			; go to next state
			;---------------------------
			mov	[edi.req_state],9
			ret

		.ELSEIF eax==FDD_OPERATION_WRITE
			;----------------------------------
			; fixme:
			; copy from memory to DMA2 buffer here
			;----------------------------------

			;---------------------------
			; prepare DMA2 for write
			;---------------------------
			Call	Setup_DMA2_Write

			mov	[fdc_int_status],0	;IRQ will have to come
			;---------------------------------
			; Send write data command
			;---------------------------------
			Call	FDC_Write_Byte STDCALL,45h			;write cmd code
			mov	eax,[edi.req_fdd_nr]
			and	eax,03h
			mov	ebx,[edi.req_fdd_head]
			and	ebx,01
			shl	ebx,2
			or	eax,ebx
			Call	FDC_Write_Byte STDCALL,eax			;drive & head
			Call	FDC_Write_Byte STDCALL,[edi.req_fdd_track]	;C
			Call	FDC_Write_Byte STDCALL,[edi.req_fdd_head]	;H
			Call	FDC_Write_Byte STDCALL,[edi.req_fdd_sector]	;S
			Call	FDC_Write_Byte STDCALL,02h			;N=2=512
			Call	FDC_Write_Byte STDCALL,18			;EOT
			Call	FDC_Write_Byte STDCALL,01Bh			;GPL3
			Call	FDC_Write_Byte STDCALL,0FFh			;DTL & action!
			;---------------------------
			; go to next state
			;---------------------------
			mov	[edi.req_state],10
			ret
		.ENDIF

		;-------------------------------
		; unknown requested operation 
		; => invalidate 
		; and move on to next request
		;-----------------------------
		mov	eax,[fdd_queue_read]
		inc	eax	
		and	eax,07Fh
		mov	[fdd_queue_read],eax
		ret

	.ELSEIF eax==9
		;-----------------------------------------
		; wait IRQ 6 at end of READ
		;-----------------------------------------
		mov	eax,[fdc_int_status]
		test	eax,eax
		.IF !ZERO?
			;-------------------------------
			;Result phase for READ
			;-------------------------------
			Call	FDC_Read_Byte STDCALL
			mov	[result_st0],eax	
			Call	FDC_Read_Byte STDCALL
			mov	[result_st1],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_st2],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_c],eax	
			Call	FDC_Read_Byte STDCALL
			mov	[result_h],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_r],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_n],eax
			;--------------------------------------------
			; TODO:copy from dma buffer to memory here!
			;--------------------------------------------

			;----------------------------------
			; DONE, go to next request in queue
			;----------------------------------
			mov	eax,[fdd_queue_read]
			inc	eax	
			and	eax,07Fh
			mov	[fdd_queue_read],eax
			
		.ENDIF
		ret

	.ELSEIF eax==10
		;-----------------------------------------
		; wait IRQ 6 at end of WRITE
		;-----------------------------------------
		mov	eax,[fdc_int_status]
		test	eax,eax
		.IF !ZERO?
			;-------------------------------
			;Result phase for WRITE
			;-------------------------------
			Call	FDC_Read_Byte STDCALL
			mov	[result_st0],eax	
			Call	FDC_Read_Byte STDCALL
			mov	[result_st1],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_st2],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_c],eax	
			Call	FDC_Read_Byte STDCALL
			mov	[result_h],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_r],eax
			Call	FDC_Read_Byte STDCALL
			mov	[result_n],eax

			;----------------------------------
			; DONE, go to next request in queue
			;----------------------------------
			mov	eax,[fdd_queue_read]
			inc	eax	
			and	eax,07Fh
			mov	[fdd_queue_read],eax
			
		.ENDIF
		ret

	.ENDIF
	
	;-----------------------------
	; Unknown status => invalidate 
	; and move on to next request
	;-----------------------------
	mov	eax,[fdd_queue_read]
	inc	eax	
	and	eax,07Fh
	mov	[fdd_queue_read],eax

	ret
ENDP

;========================================
; FDD1 motor on 
; and also enable FDC DMA/IRQ
;========================================
ALIGN 4
FDD_Motor_On PROC STDCALL
	mov	edx,FDD_DOR_PORT	
	mov	eax,1Ch		;0001=drive A,  1100=DMA/IRQ and FDC enable
	out	dx,al		

	mov	eax,18*8			; keep motor on for some time ...
	mov	[fdd_motor_on_timer],eax	; IRQ0 will decrement this and reset FDC/FDD at zero
	ret
ENDP

;==============================================
; Force FDD stop motor and FDC disable DMA/ IRQ
;==============================================
ALIGN 4
FDD_Motor_Off PROC STDCALL
	mov	edx,FDD_DOR_PORT	
	xor	eax,eax
	out	dx,al

	xor	eax,eax
	mov	[fdd_motor_on_timer],eax
	mov	[fdd_motor_on_flag],eax

	ret
ENDP

;--------------------------------------------
; Send one command byte to FDC 8272
; use 128 try timeout
;--------------------------------------------
ALIGN 4
FDC_Write_Byte PROC STDCALL
	ARG cmd_byte:DWORD

	mov	ecx,128
	mov	edx,FDC_STATUS_PORT

@@loop_retry:
	;---------------------------------------------------------
	; wait Request for Master=Bit7=1 and DIO=Bit6=0=Write
	; TODO: check importance of BUSY=Bit4 also
	;---------------------------------------------------------
	xor	eax,eax
	in	al,dx
	and	eax,0C0h
	cmp	eax,080h
	jnz	@@try_again

	mov	edx,FDC_DATA_PORT
	mov	eax,[cmd_byte]
	out	dx,al
	ret
	
@@try_again:
;	out	0EBh,al		;IO delay
	dec	ecx
	jnz	@@loop_retry

	mov	eax,-1		;signal timeout error	
	ret
ENDP

;=====================================
; Read one  byte (result phase)
; using an 128 try timeout
;=====================================
ALIGN 4
FDC_Read_Byte PROC STDCALL

	mov	ecx,128
	mov	edx,FDC_STATUS_PORT

@@loop_retry:
	;-------------------------------------------
	; wait RQM=1 and DIO=1=Read
	;-------------------------------------------
	xor	eax,eax
	in	al,dx

	and	eax,0C0h
	cmp	eax,0C0h
	jnz	@@try_again

	xor	eax,eax
	mov	edx,FDC_DATA_PORT
	in	al,dx

	ret
	
@@try_again:
;	out	0EBh,al		;IO delay
	dec	ecx
	jnz	@@loop_retry

@@end_result:
	mov	eax,-1		;signal timeout error	
	ret
ENDP




ALIGN 4
fdc_sector_buff	db	514 dup(0)

;----------------------------
; result phase data
;----------------------------
result_st0	dd	0
result_st1	dd	0	; or pcn
result_st2	dd	0
result_c	dd	0
result_h	dd	0
result_r	dd	0
result_n	dd	0
result_extra	dd	0

result_st3	dd	0








