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