; Somewhat faster communication with an IDE disk drive ; see http://www.pjrc.com/tech/8051/ide/ for more info ; This code is an original work by Paul Stoffregen, written ; in December 1999. This code has been placed in the ; public domain. You may use it without any restrictions. ; You may include it in your own projects, even commercial ; (for profit) products. ; This code is distributed in the hope that they will be useful, ; but without any warranty; without even the implied warranty of ; merchantability or fitness for a particular purpose. ; this code contains a modified copy of the code to read from an ; IDE hard drive, which is optimized for speed. These routines ; were used in a homebrew MP3 player, which was able to sustain ; reads of 192 kbit/sec data while feeding it to a MAS3507D MP3 ; decoder chip, using the UART in sync mode. Reading 256 kbit/sec ; did not not work. The 8051 was clocked with a 14.7456 MHz crystal. ; there is no documentation specific to this modified version, ; other than the comments within this code. .equ location, 0x2000 ;where this program will exist .equ sect_buf, 0x2E00 ;512 byte buffer ;------------------------------------------------------------------ ; Hardware Configuration ;8255 chip. Change these to specify where the 8255 is addressed, ;and which of the 8255's ports are connected to which ide signals. ;The first three control which 8255 ports have the control signals, ;upper and lower data bytes. The last two are mode setting for the ;8255 to configure its ports, which must correspond to the way that ;the first three lines define which ports are connected. .equ ide_8255_lsb, 0x4000 ;lower 8 bits .equ ide_8255_msb, 0x4001 ;upper 8 bits .equ ide_8255_ctl, 0x4002 ;control lines .equ cfg_8255, 0x4003 .equ rd_ide_8255, 10010010b ;ide_8255_ctl out, ide_8255_lsb/msb input .equ wr_ide_8255, 10000000b ;all three ports output ;ide control lines for use with ide_8255_ctl. Change these 8 ;constants to reflect where each signal of the 8255 each of the ;ide control signals is connected. All the control signals must ;be on the same port, but these 8 lines let you connect them to ;whichever pins on that port. .equ ide_a0_line, 0x01 ;direct from 8255 to ide interface .equ ide_a1_line, 0x02 ;direct from 8255 to ide interface .equ ide_a2_line, 0x04 ;direct from 8255 to ide interface .equ ide_cs0_line, 0x08 ;inverter between 8255 and ide interface .equ ide_cs1_line, 0x10 ;inverter between 8255 and ide interface .equ ide_wr_line, 0x20 ;inverter between 8255 and ide interface .equ ide_rd_line, 0x40 ;inverter between 8255 and ide interface .equ ide_rst_line, 0x80 ;inverter between 8255 and ide interface ;------------------------------------------------------------------ ; More symbolic constants... these should not be changed, unless of ; course the IDE drive interface changes, perhaps when drives get ; to 128G and the PC industry will do yet another kludge. ;some symbolic constants for the ide registers, which makes the ;code more readable than always specifying the address pins .equ ide_data, ide_cs0_line .equ ide_err, ide_cs0_line + ide_a0_line .equ ide_sec_cnt, ide_cs0_line + ide_a1_line .equ ide_sector, ide_cs0_line + ide_a1_line + ide_a0_line .equ ide_cyl_lsb, ide_cs0_line + ide_a2_line .equ ide_cyl_msb, ide_cs0_line + ide_a2_line + ide_a0_line .equ ide_head, ide_cs0_line + ide_a2_line + ide_a1_line .equ ide_command, ide_cs0_line + ide_a2_line + ide_a1_line + ide_a0_line .equ ide_status, ide_cs0_line + ide_a2_line + ide_a1_line + ide_a0_line .equ ide_control, ide_cs1_line + ide_a2_line + ide_a1_line .equ ide_astatus, ide_cs1_line + ide_a2_line + ide_a1_line + ide_a0_line ;IDE Command Constants. These should never change. .equ ide_cmd_recal, 0x10 .equ ide_cmd_read, 0x20 .equ ide_cmd_write, 0x30 .equ ide_cmd_init, 0x91 .equ ide_cmd_id, 0xEC .equ ide_cmd_spindown, 0xE0 .equ ide_cmd_spinup, 0xE1 ;------------------------------------------------------------------ ;internal ram usage .equ lba, 0x10 ;4 bytes, 28 bit Logical Block Address .equ stack, 0x40 ;------------------------------------------------------------------ ; Routines that talk with the IDE drive, these should be called by ; the main program. ;read a sector, specified by the 4 bytes in "lba" ;Return, acc is zero on success, non-zero for an error read_sector: acall wr_lba mov a, #ide_command mov r2, #ide_cmd_read acall ide_wr acall ide_drq jb acc.0, rs_err clr a ret rs_err: mov a, #ide_err acall ide_rd mov a, r2 jz rs_err2 ret rs_err2:mov a, #255 ret ;initialize the ide drive ide_init: ;acall ide_hard_reset ;usually not necessary mov a, #ide_head mov r2, #10100000b acall ide_wr ;select the master device mov a, #ide_status acall ide_rd mov a, r2 ;should probably check for a timeout here jnb acc.6, ide_init ;wait for RDY bit to be set jb acc.7, ide_init ;wait for BSY bit to be clear mov a, #ide_head mov r2, #0xAF acall ide_wr ;what should this config parm be? mov a, #ide_sec_cnt mov r2, #64 acall ide_wr ;what should this config parm be? mov a, #ide_command mov r2, #ide_cmd_init acall ide_wr ;do init parameters command acall ide_busy mov a, #ide_command mov r2, #ide_cmd_recal ;do recal command (is this necessary?) acall ide_wr acall ide_busy ret ;------------------------------------------------------------------ ; Not quite as low, low level I/O. These routines talk to the drive, ; using the low level I/O. Normally a main program should not call ; directly to these. ;This is the parts that's faster. Notice that it doesn't ;call to ide_rd and ide_wr, and it does less manipulation ;with the 8255. ;Read a block of 512 bytes (one sector) from the drive ;and store it in memory @ DPTR read_data: mov r0, #0 mov r1, #2 mov r2, dph mov p2, dph mov dptr, #cfg_8255 mov a, #rd_ide_8255 movx @dptr, a ;config 8255 chip, read mode rdataloop: mov dptr, #ide_8255_ctl ;mov a, #ide_data ;movx @dptr, a ;drive address onto control lines mov a, #ide_data | ide_rd_line movx @dptr, a ;assert read pin mov dptr, #ide_8255_lsb clr a movc a, @a+dptr ;read the lower byte movx @r0, a inc r0 mov dptr, #ide_8255_msb clr a movc a, @a+dptr ;read the upper byte movx @r0, a inc r0 mov dptr, #ide_8255_ctl clr a movx @dptr, a ;deassert all control pins mov a, r0 jnz rdataloop mov a, r2 inc a mov p2, a djnz r1, rdataloop mov p2, #255 ret ;write the logical block address to the drive's registers wr_lba: mov a, lba+3 anl a, #0x0F orl a, #0xE0 mov r2, a mov a, #ide_head acall ide_wr mov a, #ide_cyl_msb mov r2, lba+2 acall ide_wr mov a, #ide_cyl_lsb mov r2, lba+1 acall ide_wr mov a, #ide_sector mov r2, lba+0 acall ide_wr ;mov a, #ide_sec_cnt ;mov r2, #1 ;acall ide_wr ret ;Wait for the ide drive to not be busy. ;Returns the drive's status in Acc ide_busy: mov a, #ide_status ;wait for RDY bit to be set acall ide_rd mov a, r2 ;should probably check for a timeout here jb acc.7, ide_busy ret ;note that this function is changed to have a call to an ;idle loop (commented out). ;Wait for the drive to be ready to transfer data. ;Returns the drive's status in Acc ide_drq: mov a, #ide_status ;wait for DRQ bit to be set acall ide_rd ;acall idle mov a, r2 ;should probably check for a timeout here jb acc.7, ide_drq ;wait for BSY to be clear jnb acc.3, ide_drq ;wait for DRQ to be set ret ;------------------------------------------------------------------ ; Low Level I/O to the drive. These are the routines that talk ; directly to the drive, via the 8255 chip. Normally a main ; program would not call to these. ;Do a read bus cycle to the drive, using the 8255. This ;is slow, because we have to manipulate the 8255 and use ;the 8051's limited moxv and movx to do it (via dptr). ;Note that the drive is read using MOVC, to run on a board ;where the 8255 is read using PSEN. If your board uses ;RD instead of PSEN, chance the MOVC's to MOVX's. ;input acc = ide regsiter address ;output r2 = lower byte read from ide drive ;output r3 = upper byte read from ide drive ;dptr is changed ide_rd: push acc mov dptr, #cfg_8255 mov a, #rd_ide_8255 movx @dptr, a ;config 8255 chip, read mode mov dptr, #ide_8255_ctl pop acc movx @dptr, a ;drive address onto control lines orl a, #ide_rd_line movx @dptr, a ;assert read pin mov dptr, #ide_8255_msb ;clr a ;movc a, @a+dptr ;read the upper byte ;mov r3, a mov dptr, #ide_8255_lsb clr a movc a, @a+dptr ;read the lower byte mov r2, a mov dptr, #ide_8255_ctl clr a movx @dptr, a ;deassert all control pins ret ;Do a write bus cycle to the drive, via the 8255 ;input acc = ide register address ;input r2 = lsb to write ;input r3 = msb to write ;dptr is changed ide_wr: ;push acc mov r4, a mov dptr, #cfg_8255 mov a, #wr_ide_8255 movx @dptr, a ;config 8255 chip, write mode mov dptr, #ide_8255_lsb mov a, r2 movx @dptr, a ;drive lower lines with lsb (r2) ;mov dptr, #ide_8255_msb ;mov a, r3 ;movx @dptr, a ;drive upper lines with msb (r3) mov dptr, #ide_8255_ctl ;pop acc mov a, r4 movx @dptr, a ;drive address onto control lines orl a, #ide_wr_line movx @dptr, a ;assert write pin ;nop clr a movx @dptr, a ;deassert all control pins ;mov dptr, #cfg_8255 ;mov a, #rd_ide_8255 ;movx @dptr, a ;config 8255 chip, read mode ret ;do a hard reset on the drive, by pulsing its reset pin. ;this should usually be followed with a call to "ide_init". ide_hard_reset: mov dptr, #cfg_8255 mov a, #wr_ide_8255 movx @dptr, a ;config 8255 chip, write mode mov dptr, #ide_8255_ctl mov a, #ide_rst_line movx @dptr, a ;hard reset the disk drive mov r2, #250 djnz r2, * ;delay > 25 us (reset pulse width) clr a movx @dptr, a ;no ide control lines asserted ret