Index: trunk/XTIDE_Universal_BIOS/Src/Initialization/AdvAtaInit.asm
===================================================================
--- trunk/XTIDE_Universal_BIOS/Src/Initialization/AdvAtaInit.asm	(revision 363)
+++ trunk/XTIDE_Universal_BIOS/Src/Initialization/AdvAtaInit.asm	(revision 363)
@@ -0,0 +1,135 @@
+; Project name	:	XTIDE Universal BIOS
+; Description	:	Common functions for initializing different
+;					VLB and PCI IDE Controllers.
+
+; Section containing code
+SECTION .text
+
+;--------------------------------------------------------------------
+; AdvAtaInit_DetectControllerForIdeBaseInDX
+;	Parameters:
+;		DX:		IDE Controller base port
+;	Returns:
+;		AX:		ID WORD specific for detected controller
+;				Zero if no controller detected
+;		CX:		Controller base port (not IDE)
+;		CF:		Set if controller detected
+;				Cleared if no controller
+;	Corrupts registers:
+;		BX
+;--------------------------------------------------------------------
+AdvAtaInit_DetectControllerForIdeBaseInDX:
+	call	Vision_DetectAndReturnIDinAXandPortInCXifControllerPresent
+	jne		SHORT .NoAdvancedControllerForPortDX
+	call	Vision_DoesIdePortInDXbelongToControllerWithIDinAX
+	jne		SHORT .NoAdvancedControllerForPortDX
+
+	stc		; Advanced Controller found for port DX
+	ret
+
+.NoAdvancedControllerForPortDX:
+	xor		ax, ax
+	ret
+
+
+;--------------------------------------------------------------------
+; AdvAtaInit_GetControllerMaxPioModeToAL
+;	Parameters:
+;		AX:		ID WORD specific for detected controller
+;	Returns:
+;		AL:		Max supported PIO mode (if CF set)
+;		CF:		Set if PIO limit necessary
+;				Cleared if no need to limit timings
+;	Corrupts registers:
+;		Nothing
+;--------------------------------------------------------------------
+AdvAtaInit_GetControllerMaxPioModeToAL	equ	Vision_GetMaxPioModeToAL
+
+
+;--------------------------------------------------------------------
+; AdvAtaInit_InitializeControllerForDPTinDSDI
+;	Parameters:
+;		DS:DI:	Ptr to DPT for Single or Slave Drive
+;	Returns:
+;		CF:		Cleared if success or no controller to initialize
+;				Set if error
+;	Corrupts registers:
+;		AX, BX, CX, DX
+;--------------------------------------------------------------------
+AdvAtaInit_InitializeControllerForDPTinDSDI:
+	push	ds
+	push	si
+	push	di
+
+	; PIO and Advanced Controller variables are stored to BOOTMENUINFO
+	; to keep the DPTs as small as possible.
+	call	GetMasterAndSlaveBootMenuInfosToSIandDI
+	cmp		WORD [BOOTVARS.wMagicWord], BOOTVARS_MAGIC_WORD
+	clc
+	jne		SHORT .BootMenuInfosAreNoLongerAvailable
+
+	; Call Controller Specific initialization function
+	mov		ax, [si+BOOTMENUINFO.wControllerID]
+	test	ax, ax
+	jz		SHORT .NoAdvancedController
+	call	Vision_InitializeWithIDinAHandConfigInAL	; The only we support at the moment
+
+.BootMenuInfosAreNoLongerAvailable:
+.NoAdvancedController:
+	pop		di
+	pop		si
+	pop		ds
+	ret
+
+
+;--------------------------------------------------------------------
+; AdvAtaInit_GetMasterAndSlaveBootMenuInfosToSIandDI
+;	Parameters:
+;		DS:DI:	Ptr to DPT for Single or Slave Drive
+;	Returns:
+;		DS:SI:	Ptr to Single or Master Drive BOOTMENUINFO
+;		DI:		Offset to Slave Drive BOOTMENUINFO
+;				Zero if Slave Drive not present
+;	Corrupts registers:
+;		BX, DX, (DS will change!)
+;--------------------------------------------------------------------
+GetMasterAndSlaveBootMenuInfosToSIandDI:
+	call	BootMenuInfo_ConvertDPTtoBX
+	LOAD_BDA_SEGMENT_TO	ds, di, !				; Zero DI to assume no Slave Drive present
+
+	mov		dx, [bx+BOOTMENUINFO.wIdeBasePort]	; Load IDE Port from Single or Slave Drive
+	lea		si, [bx+BOOTMENUINFO_size]			; SI points to Slave Drive if present
+	cmp		dx, [si+BOOTMENUINFO.wIdeBasePort]
+	jne		SHORT .BootMenuInfoForSingleDriveInDSBX
+
+	mov		di, si								; Slave Drive detected, copy pointer to DS:DI
+.BootMenuInfoForSingleDriveInDSBX:
+	mov		si, bx
+	ret
+
+
+;--------------------------------------------------------------------
+; AdvAtaInit_SelectSlowestTimingsToBXandCX
+;	Parameters:
+;		DS:SI:	Ptr to BOOTMENUINFO for Master or Single Drive
+;		DI:		Offset to BOOTMENUINFO for Slave Drive
+;				Zero if Slave Drive not present
+;	Returns:
+;		BX:		Min Active Time in nanosecs
+;		CX:		Min Recovery Time in nanosecs
+;	Corrupts registers:
+;		Nothing
+;--------------------------------------------------------------------
+AdvAtaInit_SelectSlowestTimingsToBXandCX:
+	mov		bx, [si+BOOTMENUINFO.wMinPioActiveTimeNs]
+	mov		cx, [si+BOOTMENUINFO.wMinPioRecoveryTimeNs]
+	test	di, di
+	jz		SHORT .ReturnSlowestTimingInBXandCX	; No Slave Drive
+
+	; If Active Time is greater, then must be the Recovery Time as well
+	cmp		bx, [di+BOOTMENUINFO.wMinPioActiveTimeNs]
+	jbe		SHORT .ReturnSlowestTimingInBXandCX
+	mov		bx, [di+BOOTMENUINFO.wMinPioActiveTimeNs]
+	mov		cx, [di+BOOTMENUINFO.wMinPioRecoveryTimeNs]
+.ReturnSlowestTimingInBXandCX:
+	ret
Index: trunk/XTIDE_Universal_BIOS/Src/Initialization/AtaID.asm
===================================================================
--- trunk/XTIDE_Universal_BIOS/Src/Initialization/AtaID.asm	(revision 334)
+++ trunk/XTIDE_Universal_BIOS/Src/Initialization/AtaID.asm	(revision 363)
@@ -72,2 +72,84 @@
 	mul		WORD [di+ATA1.wCylCnt]	; DX:AX=Sectors per track * number of heads * number of cylinders
 	ret
+
+
+%ifdef MODULE_ADVANCED_ATA
+;--------------------------------------------------------------------
+; AtaID_GetMaxPioModeToAXandMinCycleTimeToDX
+;	Parameters:
+;		ES:SI:	Ptr to 512-byte ATA information read from the drive
+;	Returns:
+;		AX:		Max supported PIO mode
+;		DX:		Minimum Cycle Time in nanosecs
+;	Corrupts registers:
+;		BX
+;--------------------------------------------------------------------
+AtaID_GetMaxPioModeToAXandMinCycleTimeToDX:
+	; Get PIO mode and cycle time for PIO 0...2
+	mov		bx, [es:si+ATA1.bPioMode]
+	shl		bx, 1					; Shift for WORD lookup
+	mov		dx, [cs:bx+.rgwPio0to2CycleTimeInNanosecs]
+	shr		bx, 1
+	xchg	ax, bx					; AL = PIO mode 0, 1 or 2
+
+	; Check if Advanced PIO modes are supported (3 and above)
+	test	BYTE [es:si+ATA2.wFields], A2_wFields_64to70
+	jz		SHORT .ReturnPioTimings
+
+	; Get Advanced PIO mode
+	; (Hard Disks supports up to 4 but CF cards might support 5)
+	mov		bx, [es:si+ATA2.bPIOSupp]
+.CheckNextFlag:
+	inc		ax
+	shr		bx, 1
+	jnz		SHORT .CheckNextFlag
+	mov		dx, [es:si+ATA2.wPIOMinCyF]	; Advanced modes use IORDY
+.ReturnPioTimings:
+	ret
+
+
+.rgwPio0to2CycleTimeInNanosecs:
+	dw		PIO_0_MIN_CYCLE_TIME_NS
+	dw		PIO_1_MIN_CYCLE_TIME_NS
+	dw		PIO_2_MIN_CYCLE_TIME_NS
+
+
+;--------------------------------------------------------------------
+; AtaID_ConvertPioModeFromAXandMinCycleTimeFromDXtoActiveAndRecoveryTime
+;	Parameters:
+;		AX:		Max supported PIO mode
+;		DX:		Minimum PIO Cycle Time in nanosecs
+;	Returns:
+;		CX:		Minimum Active time in nanosecs
+;		DX:		Minimum Recovery time in nanosecs
+;	Corrupts registers:
+;		BX
+;--------------------------------------------------------------------
+AtaID_ConvertPioModeFromAXandMinCycleTimeFromDXtoActiveAndRecoveryTime:
+	; Subtract Address Valid Time (t1) from Cycle Time (t0)
+	mov		bx, ax
+	eMOVZX	cx, BYTE [cs:bx+.rgbPioModeToAddressValidTimeNs]
+	sub		dx, cx
+
+	; Subtract Active Time (t2) from previous result to get Recovery Time (t2i)
+	shl		bx, 1			; Shift PIO Mode for WORD lookup
+	mov		cx, [cs:bx+.rgwPioModeToActiveTimeNs]
+	sub		dx, cx
+	ret
+
+
+.rgbPioModeToAddressValidTimeNs:
+	db		PIO_0_MIN_ADDRESS_VALID_NS
+	db		PIO_1_MIN_ADDRESS_VALID_NS
+	db		PIO_2_MIN_ADDRESS_VALID_NS
+	db		PIO_3_MIN_ADDRESS_VALID_NS
+	db		PIO_4_MIN_ADDRESS_VALID_NS
+
+.rgwPioModeToActiveTimeNs:
+	dw		PIO_0_MIN_ACTIVE_TIME_NS
+	dw		PIO_1_MIN_ACTIVE_TIME_NS
+	dw		PIO_2_MIN_ACTIVE_TIME_NS
+	dw		PIO_3_MIN_ACTIVE_TIME_NS
+	dw		PIO_4_MIN_ACTIVE_TIME_NS
+
+%endif ; MODULE_ADVANCED_ATA
Index: trunk/XTIDE_Universal_BIOS/Src/Initialization/DetectDrives.asm
===================================================================
--- trunk/XTIDE_Universal_BIOS/Src/Initialization/DetectDrives.asm	(revision 334)
+++ trunk/XTIDE_Universal_BIOS/Src/Initialization/DetectDrives.asm	(revision 363)
@@ -204,4 +204,2 @@
 	call	BootMenuInfo_CreateForHardDisk
 	jmp		short DetectPrint_DriveNameFromBootnfoInESBX
-
-
Index: trunk/XTIDE_Universal_BIOS/Src/Initialization/Initialize.asm
===================================================================
--- trunk/XTIDE_Universal_BIOS/Src/Initialization/Initialize.asm	(revision 334)
+++ trunk/XTIDE_Universal_BIOS/Src/Initialization/Initialize.asm	(revision 363)
@@ -53,4 +53,5 @@
 ;--------------------------------------------------------------------
 Initialize_AndDetectDrives:
+	mov		WORD [es:BOOTVARS.wMagicWord], BOOTVARS_MAGIC_WORD
 	call	BootMenuPrint_InitializeDisplayContext
 	call	DetectPrint_RomFoundAtSegment
@@ -98,3 +99,4 @@
 	call	AH0h_ResetAllOurHardDisksAtTheEndOfDriveInitialization
 	add		sp, BYTE EXTRA_BYTES_FOR_INTPACK
+	mov		WORD [es:BOOTVARS.wMagicWord], 0
 	ret
Index: trunk/XTIDE_Universal_BIOS/Src/Initialization/Vision.asm
===================================================================
--- trunk/XTIDE_Universal_BIOS/Src/Initialization/Vision.asm	(revision 363)
+++ trunk/XTIDE_Universal_BIOS/Src/Initialization/Vision.asm	(revision 363)
@@ -0,0 +1,223 @@
+; Project name	:	XTIDE Universal BIOS
+; Description	:	Functions for initializing QDI Vision
+;					QD6500 and QD6580 VLB IDE Controllers.
+
+; Section containing code
+SECTION .text
+
+;--------------------------------------------------------------------
+; Vision_DetectAndReturnIDinAXandPortInCXifControllerPresent
+;	Parameters:
+;		Nothing
+;	Returns:
+;		AX:		ID WORD specific for QDI Vision Controllers
+;				(AL = QD65xx Config Register contents)
+;				(AH = QDI Vision Controller ID (bits 4...7))
+;		CX:		Controller port (not IDE port)
+;		ZF:		Set if controller found
+;				Cleared if supported controller not found (AX,DX = undefined)
+;	Corrupts registers:
+;		Nothing
+;--------------------------------------------------------------------
+Vision_DetectAndReturnIDinAXandPortInCXifControllerPresent:
+	; Check QD65xx base port
+	mov		cx, QD65XX_BASE_PORT
+	in		al, QD65XX_BASE_PORT + QD65XX_CONFIG_REGISTER_in
+	call	IsConfigRegisterWithIDinAL
+	je		SHORT VisionControllerDetected
+
+	; Check QD65xx alternative base port
+	or		cl, QD65XX_ALTERNATIVE_BASE_PORT
+	in		al, QD65XX_ALTERNATIVE_BASE_PORT + QD65XX_CONFIG_REGISTER_in
+	; Fall to IsConfigRegisterWithIDinAL
+
+;--------------------------------------------------------------------
+; IsConfigRegisterWithIDinAL
+;	Parameters:
+;		AL:		Possible QD65xx Config Register contents
+;	Returns:
+;		AH		QDI Vision Controller ID or undefined
+;		ZF:		Set if controller found
+;				Cleared if supported controller not found (AH = undefined)
+;	Corrupts registers:
+;		Nothing
+;--------------------------------------------------------------------
+IsConfigRegisterWithIDinAL:
+	mov		ah, al
+	and		ah, MASK_QDCONFIG_CONTROLLER_ID
+	cmp		ah, ID_QD6500 << 4
+	je		SHORT VisionControllerDetected
+	cmp		ah, ID_QD6580 << 4
+	je		SHORT VisionControllerDetected
+	cmp		ah, ID_QD6580_ALTERNATE << 4
+VisionControllerDetected:
+	ret
+
+
+;--------------------------------------------------------------------
+; Vision_DoesIdePortInDXbelongToControllerWithIDinAX
+;	Parameters:
+;		AX:		ID WORD specific for QDI Vision Controllers
+;				(AL = QD65xx Config Register contents)
+;				(AH = QDI Vision Controller ID (bits 4...7))
+;		CX:		Vision Controller port
+;		DX:		IDE base port to check
+;	Returns:
+;		ZF:		Set if port belongs to controller
+;				Cleared if port belongs to another controller
+;	Corrupts registers:
+;		BX
+;--------------------------------------------------------------------
+Vision_DoesIdePortInDXbelongToControllerWithIDinAX:
+	cmp		ah, ID_QD6500 << 4
+	je		SHORT .DoesIdePortInDXbelongToQD6500
+
+	; QD6580 always have Primary IDE at 1F0h
+	; Secondary IDE at 170h can be enabled or disabled
+	cmp		dx, DEVICE_ATA_DEFAULT_PORT
+	je		SHORT .ReturnResultInZF
+
+	; Check if Secondary IDE channel is enabled
+	xchg	bx, ax		; Backup AX
+	xchg	dx, cx		; Swap ports
+
+	add		dx, BYTE QD6580_CONTROL_REGISTER
+	in		al, dx
+	sub		dx, BYTE QD6580_CONTROL_REGISTER
+
+	xchg	cx, dx
+	xchg	ax, bx		; Restore AX, Control Register to BL
+	test	bl, FLG_QDCONTROL_SECONDARY_DISABLED_in
+	jz		SHORT .CompareDXtoSecondaryIDE
+	ret
+
+	; QD6500 has only one IDE channel that can be at 1F0h or 170h
+.DoesIdePortInDXbelongToQD6500:
+	test	al, FLG_QDCONFIG_PRIMARY_IDE
+	jz		SHORT .CompareDXtoSecondaryIDE
+	cmp		dx, DEVICE_ATA_DEFAULT_PORT
+	ret
+
+.CompareDXtoSecondaryIDE:
+	cmp		dx, DEVICE_ATA_DEFAULT_SECONDARY_PORT
+.ReturnResultInZF:
+	ret
+
+
+;--------------------------------------------------------------------
+; Vision_GetMaxPioModeToAL
+;	Parameters:
+;		AX:		ID WORD specific for QDI Vision Controllers
+;				(AH = QDI Vision Controller ID (bits 4...7))
+;	Returns:
+;		AL:		Max supported PIO mode (if CF set)
+;		CF:		Set if PIO limit necessary
+;				Cleared if no need to limit timings
+;	Corrupts registers:
+;		Nothing
+;--------------------------------------------------------------------
+Vision_GetMaxPioModeToAL:
+	cmp		ah, ID_QD6500 << 4
+	clc
+	jne		SHORT .NoNeedToLimitForQD6580
+
+	mov		al, 2	; Limit to PIO 2 because QD6500 supports PIO 3 but without IORDY
+	stc
+.NoNeedToLimitForQD6580:
+	ret
+
+
+;--------------------------------------------------------------------
+; Vision_InitializeWithIDinAHandConfigInAL
+;	Parameters:
+;		AX:		ID WORD specific for QDI Vision Controllers
+;				(AL = QD65xx Config Register contents)
+;				(AH = QDI Vision Controller ID (bits 4...7))
+;		DS:SI:	Ptr to BOOTMENUINFO for Single or Master Drive
+;		DS:DI:	Ptr to BOOTMENUINFO for Slave Drive
+;				Zero if Slave not present
+;	Returns:
+;		CF:		Cleared if success
+;				Set if error
+;	Corrupts registers:
+;		AX, BX, CX, DX, SI, DI
+;--------------------------------------------------------------------
+Vision_InitializeWithIDinAHandConfigInAL:
+	; QD6580 has a Control Register that needs to be programmed
+	mov		dx, [si+BOOTMENUINFO.wControllerBasePort]
+	cmp		ah, ID_QD6500 << 4
+	je		SHORT .GetPioTimingsInNanosecs
+
+	; Program QD6580 Control Register (not available on QD6500) to
+	; Enable or Disable Read-Ahead and Post-Write Buffer to match
+	; jumper setting on the multi I/O card.
+.ProgramControlRegisterForQD6580:
+	xchg	bx, ax									; Backup AX
+	add		dx, BYTE QD6580_CONTROL_REGISTER
+	in		al, dx									; Read to get ATAPI jumper status
+	test	al, FLG_QDCONTROL_HDONLY_in
+	mov		al, MASK_QDCONTROL_FLAGS_TO_SET
+	jz		SHORT .SkipHdonlyBitSinceAtapiPossible
+	or		al, FLG_QDCONTROL_NONATAPI
+.SkipHdonlyBitSinceAtapiPossible:
+	out		dx, al
+	sub		dx, BYTE QD6580_CONTROL_REGISTER		; Back to base port
+	xchg	ax, bx									; Restore AX
+
+	; If we have Master and Slave drive in the system, we must select
+	; timings from the slower drive (this is why it is a bad idea to use
+	; fast and slow drive on the same IDE channel)
+.GetPioTimingsInNanosecs:
+	call	AdvAtaInit_SelectSlowestTimingsToBXandCX
+
+	; Now we need to determine is the drive connected to the Primary or Secondary channel.
+	; QD6500 has only one channel that can be Primary at 1F0h or Secondary at 170h.
+	; QD6580 always has Primary channel at 1F0h. Secondary channel at 170h can be Enabled or Disabled.
+	cmp		ah, ID_QD6500 << 4
+	je		SHORT .CalculateTimingTicksForQD6500
+	cmp		WORD [si+BOOTMENUINFO.wIdeBasePort], DEVICE_ATA_DEFAULT_PORT
+	je		SHORT .CalculateTimingTicksForQD6580
+	times 2 inc dx									; Secondary Channel IDE Timing Register
+
+	; Now we must translate the PIO timing nanosecs in CX and DX to VLB ticks
+	; suitable for QD65xx IDE Timing Register.
+	; Both of the controllers require slightly different calculations.
+.CalculateTimingTicksForQD6580:
+	mov		si, QD6580_MIN_ACTIVE_TIME_CLOCKS
+	mov		di, QD6580_MAX_ACTIVE_TIME_CLOCKS
+	jmp		SHORT .CalculateTimingForQD65xx
+
+.CalculateTimingTicksForQD6500:
+	mov		si, QD6500_MIN_ACTIVE_TIME_CLOCKS
+	mov		di, QD6500_MAX_ACTIVE_TIME_CLOCKS
+
+.CalculateTimingForQD65xx:
+	test	al, FLG_QDCONFIG_ID3		; Set ZF if 40 MHz VLB bus
+	mov		al, VLB_33MHZ_CYCLE_TIME	; Assume 33 MHz or slower VLB bus
+	xchg	ax, bx						; Active Time to AX
+	eCMOVZ	bl, VLB_40MHZ_CYCLE_TIME
+
+	div		bl
+	inc		ax							; Round up
+	xor		ah, ah
+	xchg	cx, ax						; CX = Active Time in VLB ticks
+	MAX_U	cx, si						; Limit ticks to valid values...
+	MIN_U	cx, di						; ...for QD65xx
+
+	div		bl
+	inc		ax							; Round up
+	xchg	bx, ax						; BL = Recovery Time in VLB ticks
+	mov		al, QD65xx_MAX_RECOVERY_TIME_CLOCKS
+	MAX_U	bl, QD65xx_MIN_RECOVERY_TIME_CLOCKS
+	MIN_U	bl, al
+
+	; Not done yet, we need to invert the ticks since 0 is the slowest
+	; value on the timing register
+	sub		di, cx						; DI = Active Time value to program
+	sub		al, bl						; AL = Recovery Time value to program
+
+	; Finally we can shift the values in places and program the Timing Register
+	eSHIFT_IM	al, POSITON_QD65XXIDE_RECOVERY_TIME, shl
+	or		ax, di
+	out		dx, al
+	ret									; Return with CF cleared
