	NOLIST
;	This file is part of picide, ATA(PI) interface to PIC18 family MCUs.
;	Copyright (C) 2004-5 Toby Thain, toby@telegraphics.com.au

;	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.

;	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
	LIST

	TITLE picide
	SUBTITLE "IDE module"

;; Version history
;; 16-Oct-2004: 0.1 - started by Toby
;; 23-Feb-2005: 1.0 - public release under GPL

; contents of this module:

	global init_ide		; initialise interface
	global ide_checkerr	; check for error after command

	; addresses of IDE command block
	; most commands use the drive/head select value in lba24
	; and some or all of the following fields
	global idecmd,feat,lba0,lba8,lba16,lba24

	; high level commands
	; all use command data block @ idecmd, in particular drive select in lba24
	global ide_probe	; probe for slave or master device
				; returns device type in W (see idecmd.inc)
	global ide_identify	; ATA IDENTIFY command
				; follow by ide_rdata
	global ide_lbacmd	; any ATA block addressing command (e.g. READ/WRITE)
				; command code in W on entry
				; follow with ide_{r,w}data to complete command
	global ide_idle		; ATA IDLE command
				; standby timer value in W on entry
	global ide_atapipacket	; ATA ATAPI PACKET command (used by atapi.asm)
				; packet is in data block @ atapipkt
	global ide_atapiiddev	; ATA ATAPI IDENTIFY DEVICE command

	; do command read/write phase
	; need count of 16-bit words in W (zero for whole 512 byte sector)
	; and sector count (complete or partial 512 byte sectors) in seccnt
	; data is read into or written from secbuf
	global ide_rdata	; complete an ATA READ (ide_lbacmd)
	global ide_wdata	; complete an ATA WRITE (ide_lbacmd)
	
	; internal entry points (used by ATAPI.asm)
	global ide_rblk,ide_wblk
	global datasetup	; prepare for data phase
	global wdcnt		; # of 16 bit words to transfer
	global seccnt		; # of whole or partial sectors to transfer
	; polling loops
	global longwaitbsy	; wait until drive not BSY
	global shortwaitbsydrq	; wait until drive not BSY
	; helper routines for macros _wrregf, _wrregl, _rdreg
	global rdreg,wrreg	; read/write ATA registers

#include "defs.inc"
#include "idecmd.inc"
#include "idedefs.inc"
#include "atapidefs.inc"

; these macros are not needed for now,
; the IDE code seems unaffected by the 1kHz timer interrupt
_disable_int	macro
	;bcf INTCON,GIEH
	endm
_enable_int	macro
	;bsf INTCON,GIEH
	endm

;#define IDE_DEBUG

	extern putch,puthex,dumpsec,dumpstuff
	extern delay100us,time400ms,time1500ms,delay1ms,delay2ms,delay150ms
	extern atapipkt,dec16,putdec16

;; VARIABLES

	global dasplo,dasphi

	udata_acs
wdcnt	res 1	; used by ide_rw
dasplo	res 1
dasphi	res 1
temp	res 1
status	res 1
busytries	res 1	; used by ide_waitbusy
	; command parameters
idecmd	res 1	; command code
feat	res 1	; features
seccnt	res 1	; sector count
lba0	res 1	; LBA sector address
lba8	res 1
lba16	res 1
lba24	res 1	; Drive/Head reg, incl drive select

;;; PROGRAM
	code

;---------------------------------------------------------
	; initialise IDE: reset bus, look for slave drive,
	; return # of possible drives in W.
	; there are no error returns.
init_ide:
	; I/O setup - PORTD,E,F,G
	movlw b'00001111'	; configure pins AN0-15 as digital I/O
	movwf ADCON1
	movlw b'00000111'	; turn comparators OFF
	movwf CMCON

	movlw b'11111000' ; negate IDE signals: DMACK_, DIOW_, DIOR_, CS3FX_, CS1FX_, DA<2:0>
	movwf LAT_IO
	clrf TRIS_IO		; I/O register = all outputs
	setf TRIS_DDLO		; data bus = all inputs for now
	setf TRIS_DDHI

	movlw b'00011101' ; set DASP_, IORDY, IOCS16_, DMARQ as inputs
	movwf TRIS_MISC

	bsf TRIS_INTRQ,3	; INTRQ is input

	; reset drive
	bcf IDE_RESET_ ; assert RESET
	call delay100us
	bsf IDE_RESET_

	call time400ms ; setup timer

	; need to wait 2ms here (at least 1ms anyway) for DASP_ to be negated
	;call delay2ms	; Linux libata-core.c suggests 150ms?
  IF 1
	; don't start polling DASP_ until 150ms have elapsed
	; the high byte of TMR0H increments every 65536 cycles
	; (6.5536ms) so 150 ms = ~23 of these ticks
	; high byte starts at 194... drop out of loop when it reaches 217
notyet:	movf TMR0L,w	; latch TMR0H 
	movlw 194+23
	cpfsgt TMR0H	; if f > Wreg, stop looping
	bra notyet
  ENDIF
	; watch DASP_ for presence of slave drive (drive1)
polldasp: btfss IDE_DASP_
	  bra gotdasp
	  btfss INTCON,TMR0IF ; timer overflow?
	  bra polldasp
#ifdef IDE_DEBUG
	_puts "\r\n  init_ide: No slave seen\r\n\0"
#endif
	retlw 1 ; timer expired (>400ms) without seeing slave
gotdasp: ; there could be a slave
	movff TMR0L,dasplo	; store timer value, for curiosity purposes only
	movff TMR0H,dasphi	; (checking on device quirks)
#ifdef IDE_DEBUG
	_puts "\r\n  init_ide: Possible slave\r\n\0"
#endif
	retlw 2
;---------------------------------------------------------
	; issue identify command
	; after calling this routine, ide_rdata must be called
	; and then call ide_checkerr to test success
ide_identify:
	;_puts "ide_identify\r\n\0"
	rcall ide_devselect
	_returniferr
	_wrregl IDE_REG_CMD, IDE_CMD_IDENTIFYDRIVE
	; expects data-read phase (256 words)
	retlw NO_ERR

;---------------------------------------------------------
  IF 0	; NOT USED
	; issue init drive parameters command
	; no data phase. call ide_checkerr to test success
ide_initdriveparams:
	rcall ide_devselect
	_returniferr
	_wrregf IDE_REG_SECCNT, seccnt
	_wrregl IDE_REG_CMD, IDE_CMD_INITDRIVEPARAMS
	retlw NO_ERR
  ENDIF
;---------------------------------------------------------
	; issue idle command. W is standby timer setting
	; e.g. 0 = disable, 1-240 = timer is value x 5 secs, etc
	; no data phase: caller should use ide_checkerr to test success
ide_idle:
	movwf seccnt
	;_puts "ide_idle\r\n\0"
	rcall ide_devselect
	_returniferr
	_wrregf IDE_REG_SECCNT, seccnt
	_wrregl IDE_REG_CMD, IDE_CMD_IDLE
	; no data phase
	retlw NO_ERR

;---------------------------------------------------------
	; issue a command that uses LBA and sector count registers,
	; typically Read or Write
	; command code is in Wreg on entry
	; sector count should be no more than 4 ...
	; if there is a data phase, caller must call ide_rdata or ide_wdata
ide_lbacmd:
	movwf idecmd
	;_puts "ide_lbacmd\r\n\0"
	; caller puts 28 bit LBA in lba0,8,16,24
	; lba24 must have correct bit settings for drive select and reserved bits
	rcall ide_devselect
	_returniferr
	_wrregf IDE_REG_SECCNT, seccnt
	_wrregf IDE_REG_LBA0, lba0
	_wrregf IDE_REG_LBA8, lba8
	_wrregf IDE_REG_LBA16, lba16
	;_wrregf IDE_REG_LBA24, lba24	; done by ide_devselect
	_wrregf IDE_REG_CMD, idecmd
	; possible data phase
	retlw NO_ERR

;---------------------------------------------------------
	; issue an ATAPI Packet Command
ide_atapipacket:
#ifdef IDE_DEBUG
	_puts "ide_atapipacket:\r\n\0"
	lfsr 1,atapipkt
	movlw 1
	call dumpstuff
#endif
	rcall ide_devselect
	_returniferr
	_wrregl IDE_REG_FEATURES, ATAPI_FEATURES_PIO
	; advise our transfer limit (bytes)
	_wrregl ATAPI_REG_BYTECNTHI, high ATAPI_TRANSFER_MAX
	_wrregl ATAPI_REG_BYTECNTLO, low ATAPI_TRANSFER_MAX
	_wrregl IDE_REG_CMD, IDE_CMD_ATAPI_PACKET
	movlw 1
	movwf seccnt	; sector count must be 1 for transfers < 256 words!!
	movlw 6		; command block is 12 bytes of data to write
	movwf wdcnt
	lfsr 0,atapipkt
	rcall wsec	; write command packet
	return		; pass any error to caller in W

;---------------------------------------------------------
	; issue an ATAPI Identify Device
ide_atapiiddev:
	;_puts "ide_atapiiddev\r\n\0"
	rcall ide_devselect	; don't have to check DRDY
	_returniferr
	_wrregl IDE_REG_FEATURES, ATAPI_FEATURES_PIO
	; advise our transfer limit (bytes)
	_wrregl ATAPI_REG_BYTECNTLO, low ATAPI_TRANSFER_MAX
	_wrregl ATAPI_REG_BYTECNTHI, high ATAPI_TRANSFER_MAX
	_wrregl IDE_REG_CMD, IDE_CMD_ATAPI_IDDEV
	call delay150ms	; Linux libata.c does this for ATAPI - FIXME!
	; expects data phase (256 words)
	retlw NO_ERR

;---------------------------------------------------------
	; detect ATA or ATAPI device
ide_probe:
	;_puts "ide_probe\r\n\0"
	rcall ide_devselect
	_returniferr
	call delay100us	; need some delay here, before checking signature
			; (e.g. ATAPI DVD-ROM drive BTC model BDV 108A needs it)
	;_wrregf IDE_REG_DRVHEAD, lba24
	_rdreg IDE_REG_CYLHI
	bz checkata	; CYLHI = 0, could be ATA
	xorlw 0xEB	; check ATAPI sig
	bnz devunk	; no...
	_rdreg IDE_REG_CYLLO
	xorlw 0x14	; check low sig
	bnz devunk	; no...
	; ATAPI sig matched
	_puts "ATAPI\r\n\0"
	retlw DEV_ATAPI
checkata:
	_rdreg IDE_REG_CYLLO
	bnz devunk	; not ATA
	; ATA sig matched
	_puts "ATA\r\n\0"
	retlw DEV_ATA
devunk:	_puts "??\r\n\0"
	retlw DEV_UNKNOWN

;---------------------------------------------------------
	; handle data transfers in PIO mode

	; loop is 10 cycles per word
	; for a peak PIO data rate of approx 2 megabytes/sec
	; this loop is slow enough for a Mode 0 drive

	; move Wreg x 2 bytes from drive to sector buffer
ide_rdata: ; PIO data in
	movwf wdcnt	; number of words in Wreg
#ifdef IDE_DEBUG
	_puts "ide_rdata: seccnt=\0"
	movf seccnt,w
	call puthex
	_crlf
#endif
	lfsr 0,secbuf		; set up indirection reg to access sector buffer
rsec:	  rcall datasetup
	  _returniferr
	  rcall ide_rblk
	  decfsz seccnt
	    bra rsec
	retlw NO_ERR	; well, we know there wasn't a timeout, anyway
;---------------------------------------------------------
	; helper - read number of words, set by wdcnt
ide_rblk:	
#ifdef IDE_DEBUG
	_puts " rblk wdcnt=\0"
	movf wdcnt,w
	call puthex
	_crlf
#endif
	setf TRIS_DDLO	; set IDE data lines to INPUT
	setf TRIS_DDHI
	movlw IDE_REG_DATA
	movwf LAT_IO
	_disable_int
rword:
#ifdef IDE_DEBUG
	 _putchar 'r'
#endif
	bcf IDE_DIOR_	; assert read strobe
		; falling edge of strobe enables data on DD15:0
	  nop ; ### IMPORTANT...without this, transfers are corrupted (?!)
	  movff IDE_DDLO,POSTINC0 ; lsbyte
	  movff IDE_DDHI,POSTINC0 ; msbyte
	  bsf IDE_DIOR_ ; keep negated for at least 290ns (Modes 0-2)
		; rising edge latches data at host (?)
	  decfsz wdcnt
	    bra rword
	_enable_int
#ifdef IDE_DEBUG
	_crlf
#endif
	return
;---------------------------------------------------------
	; move Wreg x 2 bytes from sector buffer to drive
ide_wdata: ; PIO data out
	movwf wdcnt	; number of words in Wreg
	;_puts "ide_wdata\r\n\0"
	lfsr 0,secbuf		; set up indirection reg to access sector buffer
wsec:	  rcall datasetup	; wait for !BSY, then DRQ
	  _returniferr		; check timeout
	  rcall ide_wblk
	  decfsz seccnt
	    bra wsec
	setf TRIS_DDLO	; reset direction
	setf TRIS_DDHI
	retlw NO_ERR	; well, we know there wasn't a timeout, anyway
;---------------------------------------------------------
	; helper - write number of words, set by wdcnt
ide_wblk:	_disable_int
	clrf TRIS_DDLO	; set IDE data lines to OUTPUT
	clrf TRIS_DDHI
	movlw IDE_REG_DATA
	movwf LAT_IO
wloop:	  movff POSTINC0,IDE_DDLO ; lsbyte
	  movff POSTINC0,IDE_DDHI ; msbyte
	  bcf IDE_DIOW_ 	; pulse write strobe
	  bsf IDE_DIOW_ 	; rising edge clocks data to drive
		; & keep negated for at least 290ns (Modes 0-2)
		; at least 700ns is built into this loop before strobe reasserted :(
#ifdef IDE_DEBUG
	_putchar 'w'
	movf LAT_DDHI,w
	call puthex
	movf LAT_DDLO,w
	call puthex
#endif
	  decfsz wdcnt
	    bra wloop
	_enable_int
#ifdef IDE_DEBUG
	_crlf
#endif
	return
;---------------------------------------------------------
	; common setup for PIO r/w.
	; - wait for device to clear BSY status
	; - check DRQ to see if it expects to transfer data, 
	;   return NODATA_ERR if not
datasetup:
	rcall longwaitbsy	; wait for drive !BSY
	_returniferr		; check for timeout
	_rdreg IDE_REG_STATUS
	; ERR status might be indicated here (e.g., invalid LBA),
	; but the drive may still want to do data phase (DRQ),
	; so we return zero even on ERR... must check ERR later.
	;btfsc WREG,0	; ERR bit
	;  return	; return status (non zero)
	btfsc WREG,3	; test DRQ - does drive want to transfer data?
	  retlw NO_ERR	; yes
;#ifdef IDE_DEBUG
	_puts "datasetup: no DRQ!\r\n\0"
;#endif
	retlw NODATA_ERR	; if no DRQ, or ERR set, abort!

;---------------------------------------------------------
	; helper: wait for drive to clear BSY and DRQ status
	; return TIMEOUT_ERR if waited too long (timeout)
	; check ERR bit - return non-zero if set
	; return zero if drive not busy and no error condition
longwaitbsydrq:
	movlw 20
	movwf busytries
	;_puts "longwaitbsydrq \0"
	bra waitbsydrq
shortwaitbsydrq:
	movlw 1
	movwf busytries
	;_puts "shortwaitbsydrq \0"
waitbsydrq:
	call time1500ms ; set up Timer0 - what is a good timeout here?
testbsyd: _rdreg IDE_REG_STATUS	; should this be ALTSTATUS (?)
	  movwf status
	  andlw IDE_STATUS_BSY|IDE_STATUS_DRQ
	  bz notbsyd
	  btfss INTCON,TMR0IF ; timeout occurred?
	  bra testbsyd
	_putchar ','
#ifdef IDE_DEBUG
	movf status,w
	rcall printstatus
#endif
	decfsz busytries
	bra waitbsydrq
	_puts "waitbsydrq: GAVE UP\r\n\0"
	retlw TIMEOUT_ERR
notbsyd:
#ifdef IDE_DEBUG
	_puts "waitbsydrq out \0"
	movf status,w
	rcall printstatus
#endif
	retlw NO_ERR
;---------------------------------------------------------

longwaitbsy:
	movlw 20
	movwf busytries
	;_puts "longwaitbsy \0"
	bra waitbsy1
waitbsy: ; wait for !BSY only - timeout after 1.5sec
	movlw 1
	movwf busytries
	;_puts "waitbsy \0"
waitbsy1:
	call time1500ms ; set up Timer0 - what is a good timeout here?
testbsy:  _rdreg IDE_REG_STATUS	; should this be ALTSTATUS register (?)
	  movwf status
	  andlw IDE_STATUS_BSY
	  bz notbsy
	  btfss INTCON,TMR0IF ; timeout occurred?
	  bra testbsy
	_putchar '.'
	decfsz busytries
	bra waitbsy1
	_puts "waitbsy: GAVE UP\r\n\0"
#ifdef IDE_DEBUG
	movf status,w
	rcall printstatus
#endif
	retlw TIMEOUT_ERR
notbsy:	
#ifdef IDE_DEBUG
	_puts "waitbsy out \0"
	movf status,w
	rcall printstatus
#endif
	retlw NO_ERR

;---------------------------------------------------------
ide_checkerr:
	rcall waitbsy
	btfss status,0	; ERR bit in saved STATUS reg 
	  return
	_puts "\r\nError: \0"
	_rdreg IDE_REG_ERROR
	_putstrbits " 7\000 UNC\000 MC\000 IDNF\000 MCR\000 ABRT\000 TK0NF\000 AMNF\000"
	retlw 1

;---------------------------------------------------------
	; device select logic from flowchart, ATA(PI)-4, section 9.6
ide_devselect:
#ifdef IDE_DEBUG
	_puts "\r\nide_devselect \0"
	movf lba24,w
	call puthex
	_crlf
#endif
	rcall longwaitbsydrq
	_returniferr
	_wrregf IDE_REG_DRVHEAD,lba24
	;call delay1ms
	;call delay150ms		; Linux libata.c does this for ATAPI, FIXME !
	;_rdreg IDE_REG_ALTSTATUS ; Linux libata.c does it this way too (?)
	rcall longwaitbsydrq	; just because the first delay wait finished,
	return			; doesn't mean this one will be short (e.g. drive
				; leaving standby)
;---------------------------------------------------------
#ifdef IDE_DEBUG
printstatus:
	_putstrbits " BSY\000 DRDY\000 5\000 4\000 DRQ\000 2\000 1\000 ERR\000"
	_crlf
	return
#endif
;---------------------------------------------------------
	; helper code for macros, not called directly

wrreg:	; IDE register address is in W
	movwf LAT_IO
	clrf TRIS_DDLO	; set IDE data lines to OUTPUT
	;clrf TRIS_DDHI	; register I/O is only 8-bit
	; data is already on LAT_DDLO (see macro)
	_disable_int
	bcf IDE_DIOW_	; pulse write strobe
	bsf IDE_DIOW_
	_enable_int
	setf TRIS_DDLO	; reset direction
	;setf TRIS_DDHI
#ifdef xIDE_DEBUG
	_puts "  Wr Reg \0"
	movf LAT_IO,w	; reg address is in latch
	rcall puthex
	_putchar '='
	movf LAT_DDLO,w	; value written is in data latch
	rcall puthex
	_crlf
#endif
	return

rdreg:	; IDE register address is in W
	movwf LAT_IO
	setf TRIS_DDLO	; set IDE data lines to INPUT
	;setf TRIS_DDHI	; register I/O is 8-bit
	_disable_int
	bcf IDE_DIOR_	; assert read strobe
	nop		; falling edge of strobe enables data on DDLO
	movf IDE_DDLO,w	; get byte in W
	bsf IDE_DIOR_ 
	_enable_int
#ifdef xIDE_DEBUG
	movwf temp	; save for later
	_puts "  Rd Reg \0"
	movf LAT_IO,w	; reg address is in latch
	rcall puthex
	_putchar ':'
	movf temp,w	; grab saved
	rcall puthex
	_crlf
	movf temp,w	; grab saved again
#endif
	return

;---------------------------------------------------------

	end
