; ;======================================================================= ; I2C Serial ROM Read/Write Utility (SROM) ;======================================================================= ; ; Read or write the contents of a 24LC512 Serial EEPROM via an I2C ; PCF8584 controller. ; ; WBW 2023-09-05: Initial release ; WBW 2023-09-07: Code clean up ; ;======================================================================= ; ; PCF8584 controller port addresses (adjust as needed) ; pcfbase_sbc .equ $F0 ; SBC PCF8584 I/O base port address pcfbase_duo .equ $56 ; Duodyne PCF8584 I/O base port address ; ; I2C identification (own slave id) ; pcf_adr .equ $55 ; Our "own" I2C slave address ; ; 24LC512 ROM id (target device) ; rom0_adr .equ $50 ; I2C SROM first I2C address rom_pgsiz .equ 128 ; SROM page size ; ; General operational equates (should not require adjustment) ; stksiz .equ $40 ; Working stack size ; restart .equ $0000 ; CP/M restart vector bdos .equ $0005 ; BDOS invocation vector fcb1 .equ $005C ; first CP/M parsed FCB fcb2 .equ $006C ; second CP/M parsed FCB ; cr .equ 13 ; carriage return lf .equ 10 ; line feed ; ident .equ $FFFC ; loc of RomWBW HBIOS ident ptr ; bf_sysver .equ $F1 ; BIOS: VER function bf_sysget .equ $F8 ; HBIOS: SYSGET function bf_sysset .equ $F9 ; HBIOS: SYSGET function bf_sysgettimer .equ $D0 ; TIMER subfunction bf_syssettimer .equ $D0 ; TIMER subfunction bf_sysgetsecs .equ $D1 ; SECONDS subfunction bf_syssetsecs .equ $D1 ; SECONDS subfunction ; ; Control register bits ; pcf_ctl_pin .equ %10000000 ; reset ; 0x80 pcf_ctl_eso .equ %01000000 ; enable serial output ; 0x40 pcf_ctl_es1 .equ %00100000 ; register selection bit 1 ; 0x20 pcf_ctl_es2 .equ %00010000 ; register selection bit 2 ; 0x10 pcf_ctl_eni .equ %00001000 ; enable external interrupt ; 0x08 pcf_ctl_sta .equ %00000100 ; generate start ; 0x04 pcf_ctl_sto .equ %00000010 ; generate stop ; 0x02 pcf_ctl_ack .equ %00000001 ; enable auto acknowledge ; 0x01 ; pcf_op_start .equ (pcf_ctl_pin | pcf_ctl_eso | pcf_ctl_sta | pcf_ctl_ack) ; 0xC5 pcf_op_stop .equ (pcf_ctl_pin | pcf_ctl_eso | pcf_ctl_sto | pcf_ctl_ack) ; 0xC3 pcf_op_repstart .equ (pcf_ctl_eso | pcf_ctl_sta | pcf_ctl_ack) ; 0x45 pcf_op_idle .equ (pcf_ctl_pin | pcf_ctl_eso | pcf_ctl_ack) ; 0xC1 ; ; Status register bits ; pcf_st_pin .equ %10000000 ; pending interrupt not ; 0x80 pcf_st_ini .equ %01000000 ; normally 0, 1 if not initialized ; 0x40 pcf_st_sts .equ %00100000 ; stop detected ; 0x20 pcf_st_ber .equ %00010000 ; bus error detected ; 0x10 pcf_st_ad0 .equ %00001000 ; slave address received = 0x00 ; 0x08 pcf_st_lrb .equ %00001000 ; last received bit ; 0x08 pcf_st_aas .equ %00000100 ; addressed as slave ; 0x04 pcf_st_lab .equ %00000010 ; lost arbitration ; 0x02 pcf_st_bb .equ %00000001 ; bus busy not ; 0x01 ; ; Transmission frequencies ; pcf_trns_90 .equ $00 ; 90 KHz pcf_trns_45 .equ $01 ; 45 KHz pcf_trns_11 .equ $02 ; 11 KHz pcf_trns_15 .equ $03 ; 1.5 KHz ; ; Clock chip frequencies ; pcf_clk_3 .equ $00 ; 3 MHz pcf_clk_443 .equ $10 ; 4.43 MHz pcf_clk_6 .equ $14 ; 6 MHz pcf_clk_8 .equ $18 ; 8 MHz pcf_clk_12 .equ $1C ; 12 MHz ; ; Divisor settings ; pcf_clk .equ pcf_clk_12 ; $1C pcf_trns .equ pcf_trns_90 ; $00 ; ; Error codes ; ec_ok .equ 0 ; No error ec_bio .equ -1 ; HBIOS invalid or not present ec_plt .equ -2 ; HBIOS platform not supported ec_usage .equ -3 ; Command line usage error ec_init .equ -4 ; PCF8584 init failed ec_timeout .equ -5 ; I2C protocol timeout ec_nak .equ -6 ; Unexpected NAK ec_fopen .equ -7 ; File open error ec_fio .equ -8 ; File I/O error ec_exists .equ -9 ; File already exists ec_verify .equ -10 ; Data verification mismatch ; ;======================================================================= ; .org $100 ; standard CP/M executable ; ; ; Setup stack (save old value) ld (stksav),sp ; save stack ld sp,stack ; set new stack ; ; Announce program call crlf ld de,str_banner ; banner call prtstr ; call parse ; parse options jr nz,exit ; abort if problems ; call init ; initialize jr nz,exit ; abort if problems ; call main ; do the real work ; exit: call prterr ; ; Announce end of program call crlf2 ld de,str_exit call prtstr ; ; Restore stack and return to OS call crlf ; formatting ld sp,(stksav) ; restore stack jp restart ; return to CP/M via restart ; ;======================================================================= ; Command Line Parsing ;======================================================================= ; ; We take advantage of CP/M OS command line processing which treats ; the first two parameters on the command line as filenames and places ; corresponding FCBs at $5C and $6C. ; ; The first FCB is not actually handled as a file. Instead, the first ; two characters are used as control parameters. First character is ; the function to perform (R=read, W=write) and the second character ; is the serial ROM address (0-7) which maps to I2C addresses $50-$57. ; parse: ld a,(fcb1+1) ; function parm cp 'R' ; read? jr z,parse1 ; if so, valid, continue cp 'W' ; write? jr z,parse1 ; if so, valid, continue cp 'T' ; write? jr z,parse1 ; if so, valid, continue cp 'D' ; write? jr z,parse1 ; if so, valid, continue jp err_usage ; else, handle usage error ; parse1: ld (func),a ; save function ld a,(fcb1+2) ; ROM adr parm cp '0' ; start of range jp c,err_usage ; if less, handle usage error cp '7' + 1 ; end of range jp nc,err_usage ; if more, handle usage error sub '0' ; convert to binary add a,rom0_adr ; offset to first srom adr ld (romadr),a ; save it ; ld a,(func) ; recall function cp 'T' ; test command? jr z,parse_z ; ld a,(fcb2+1) ; first char of page/filename cp ' ' ; blank? jp z,err_usage ; if so, handle usage error ; ld a,(func) ; recall function cp 'D' ; dump command? jr z,parse2 ; parse page number ; ; copy FCB to working location ld hl,fcb2 ; parsed CP/M FCB 2 ld de,fcb ; our FCB buffer ld bc,16 ; only first 16 bytes ldir ; copy it jr parse_z ; parse2: ; parse page number ld hl,0 ; initialize page number ld de,fcb2+1 ; pointer to start of page num parse3: ld a,(de) ; get next char inc de cp ' ' ; space char? jr z,parse4 ; return w/ ZF set cp '0' ; start of range jp c,err_usage ; if less, handle usage error cp '9' + 1 ; end of range jp nc,err_usage ; if more, handle usage error ; ; multiply working page num by 10, then add new digit push hl pop bc add hl,hl add hl,hl add hl,bc add hl,hl sub '0' call addhla ; ; check for overflow ld a,h cp 2 jp nc,err_usage ; jr parse3 ; parse4: ld (page),hl ; parse_z: xor a ret ; ;======================================================================= ; Hardware Initialization ;======================================================================= ; init: call idbio ; identify hardware BIOS cp 1 ; is it RomWBW? jp nz,err_bio ; if not, handle error ; ; Setup I/O ports based on HBIOS platform ID ld a,l ; idbio puts platform id in L ld c,pcfbase_sbc ; assume SBC cp 1 ; compare to platform id jr z,init1 ; if SBC, commit ld c,pcfbase_duo ; assume SBC cp 17 ; compare to platform id jr z,init1 ; if DUO, commit jp err_plt ; unsupported platform error ; init1: ; Record and display PCF8584 port addresses call crlf2 ; formatting ld de,str_hwmsg1 ; first part of h/w message call prtstr ; print it ld a,c ; get base port call prthex ; print port number ld (pcf_dat),a ; save data port ld de,str_hwmsg2 ; second part of h/w message call prtstr ; print port number inc a ; bump to data port ld (pcf_ctl),a ; save control port call prthex ; print port number ld de,str_hwmsg3 ; third part of h/w message call prtstr ; print it ; ; Initialize PCF8584 call pcf_init ; sets A with result ret nz ; ; "Reading/Writing/Testing Serial ROM # (I2C address 0xnn)" call crlf2 ld a,(func) ld de,str_inforead cp 'R' call z,prtstr ; "Reading" ld de,str_infowrite cp 'W' call z,prtstr ; "Writing" ld de,str_infotest cp 'T' call z,prtstr ; "Testing" ld de,str_infodump cp 'D' call z,prtstr ; "Dumping" ld de,str_info1 call prtstr ; " Serial ROM #" ld a,(romadr) push af sub rom0_adr call prtdecb ; # pop af ld de,str_info2 call prtstr ; " (I2C address " call prthex ; 0x## ld de,str_info3 call prtstr ; ")" ; init2: xor a ret ; ;======================================================================= ; Mainline ;======================================================================= ; main: ; Get requested function and dispatch ld a,(func) ; get function cp 'T' ; SROM test? jp z,test ; if so, do it cp 'D' ; SROM dump? jp z,dump ; if so, do it cp 'R' ; SROM read jp z,read ; if so, do it cp 'W' ; SROM write jp z,write ; if so, do it ret ; this should never happen ; ; ; test: call confirm ret nz ; call fillbufseq ld hl,0 call test_write ret nz call fillbufrev ld hl,1 call test_write ret nz call fillbufseq ld hl,510 call test_write ret nz call fillbufrev ld hl,511 call test_write ret nz ; ld hl,0 call test_read ret nz call checkseq ret nz ld hl,1 call test_read ret nz call checkrev ret nz ld hl,510 call test_read ret nz call checkseq ret nz ld hl,511 call test_read ret nz call checkrev ret nz ; xor a ret ; ; ; test_read: call crlf2 ld de,str_readpage call prtstr call prtdecw call clrbuf ; call readpage ret nz ; ld hl,pagebuf ld c,rom_pgsiz call crlf call dumpbuf ; xor a ret ; ; ; test_write: call crlf2 ld de,str_writepage call prtstr call prtdecw jp writepage ; ; ; dump: ld hl,(page) call crlf2 ld de,str_readpage call prtstr call prtdecw call clrbuf ; call readpage ret nz ; ld hl,pagebuf ld c,rom_pgsiz call crlf call dumpbuf ; xor a ret ; ; ; read: ; Ensure output file does not exist! ld de,fcb ; FCB pointer ld c,15 ; BDOS open file function call bdos ; do it cp $FF ; failed to open? jr z,read0 ; if so, good, continue call read_z ; close the file jp err_exists ; handle error ; read0: ; Create output file (must not exist) ld de,fcb ; FCB pointer ld c,22 ; BDOS create file function call bdos ; do it cp $FF ; error? jp z,err_fopen ; handle file open error ; ; SROM read / File write loop call crlf ; formatting ld de,pagebuf ; BDOS DMA is pagebuf ld c,26 ; BDOS set DMA function call bdos ; do it ld hl,0 ; init SROM page num ; read1: ; Read SROM page push hl ; save SROM page num call readpage ; get SROM page pop hl ; restore page num jr nz,read_z ; bail out on error inc hl ; inc page num ; ; Write page to file push hl ; save SROM page num ld de,fcb ; point to FCB ld c,21 ; BDOS write seq function call bdos ; do it pop hl ; restore page num or a ; set flags call nz,err_fio ; handle file I/O error jr nz,read_z ; close file and bail out ; ; Show progress and loop till done call prtdot ; display progress ld a,h ; check high byte cp 2 ; done when we hit 512 pages jr nz,read1 ; loop till done xor a ; signal success ; read_z: ; Close file push af ; preserve status ld de,fcb ; FCB pointer ld c,16 ; BDOS close file function call bdos ; do it pop af ; restore status ; ret ; ; ; write: ; Confirm intent to overwrite SROM call confirm ; confirm SROM overwrite ret nz ; bail out if not confirmed ; ; Open file ld de,fcb ; FCB pointer ld c,15 ; BDOS open file function call bdos ; do it' cp $FF ; error? jp z,err_fopen ; handle file open error ; ; File read / SROM write loop call crlf ; formatting ld de,pagebuf ; BDOS DMA is pagebuf ld c,26 ; BDOS set DMA function call bdos ; do it ld hl,0 ; init SROM page num ; write1: ; Read page data from file push hl ; save SROM page num ld de,fcb ; FCB pointer ld c,20 ; BDOS read file function call bdos ; do it pop hl ; restore SROM page num or a ; set flags call nz,err_fio ; handle file I/O error jr nz,write_z ; close file and bail out ; ; Write SROM page push hl ; save SROM page num call writepage ; write SROM page pop hl ; restore page num jr nz,read_z ; bail out on error inc hl ; inc page num ; ; Show progress and loop till done call prtdot ; display progress ld a,h ; check high byte cp 2 ; done when we hit 512 pages jr nz,write1 ; loop till done xor a ; signal success ; write_z: ; Close file push af ; preserve status ld de,fcb ; FCB pointer ld c,16 ; BDOS close file function call bdos ; do it pop af ; restore status ; ret ; ; ; readpage: ; Convert page number to byte offset add hl,hl ; * 2 add hl,hl ; * 4 add hl,hl ; * 8 add hl,hl ; * 16 add hl,hl ; * 32 add hl,hl ; * 64 add hl,hl ; * 128 ; ; Read page ld de,pagebuf ld bc,rom_pgsiz jp rom_read ; ; ; writepage: ; Convert page number to byte offset add hl,hl ; * 2 add hl,hl ; * 4 add hl,hl ; * 8 add hl,hl ; * 16 add hl,hl ; * 32 add hl,hl ; * 64 add hl,hl ; * 128 ; ; Write page ld de,pagebuf ld bc,rom_pgsiz jp rom_write ; ; ; clrbuf: xor a fillbuf: push af push bc push de push hl ld hl,pagebuf ld de,pagebuf + 1 ld bc,rom_pgsiz - 1 ld (hl),a ldir pop hl pop de pop bc pop af ret ; ; ; fillbufseq: push af push bc push de push hl xor a ld b,rom_pgsiz ld hl,pagebuf fillbufseq1: ld (hl),a inc a inc hl djnz fillbufseq1 pop hl pop de pop bc pop af ret ; ; ; fillbufrev: push af push bc push de push hl ld a,rom_pgsiz - 1 ld b,rom_pgsiz ld hl,pagebuf fillbufrev1: ld (hl),a dec a inc hl djnz fillbufrev1 pop hl pop de pop bc pop af ret ; ; ; checkseq: push bc push de push hl xor a ld b,rom_pgsiz ld hl,pagebuf checkseq1: cp (hl) call nz,err_verify jr nz,checkseq_z inc a inc hl djnz checkseq1 xor a checkseq_z: pop hl pop de pop bc ret ; ; ; checkrev: push bc push de push hl ld a,rom_pgsiz - 1 ld b,rom_pgsiz ld hl,pagebuf checkrev1: cp (hl) call nz,err_verify jr nz,checkrev_z dec a inc hl djnz checkrev1 xor a checkrev_z: pop hl pop de pop bc ret ; ; Confirm intention to overwrite SROM ; confirm: call crlf2 ld de,str_confirm call prtstr ; ld c,10 ; CP/M read line ld de,confirm_buf ; line buffer call bdos ; get line from user ; ld a,(confirm_buf + 1) ; number of chars returned or a ; set flags jr z,confirm1 ld a,(confirm_buf + 2) ; get first char entered cp 'Y' ; confirmed? ret z ; if so, done, ZF set cp 'y' ; lower case variant ret z ; if so, done, ZF set ; confirm1: or $FF ; signal non-confirm ret ; ;======================================================================= ; 24LC512 ROM Routines ;======================================================================= ; ; Write a buffer of data to ROM ; DE=buffer adr ; HL=ROM byte offset ; BC=buffer len ; rom_write: ; Move offset to buffer, convert to big endian!!! ld a,h ; high byte ld (adrbuf+0),a ; ... to first buffer pos ld a,l ; low byte ld (adrbuf+1),a ; ... to second buffer pos ; ; Save count to HL for later push bc ; move count from BC pop hl ; ... to HL ; ; Generate start condition ld a,(romadr) ; load ROM I2C adress rlca ; move to top 7 bits res 0,a ; clear low bit for write call pcf_start ; generate start jr nz,rom_write_z ; if error, skip write ; ; Send ROM address push de ; save buffer pointer push hl ; save buffer length ld hl,2 ; 2 byte address ld de,adrbuf ; memory address pointer call pcf_write ; set memory pointer pop hl ; restore buffer length pop de ; restore buffer pointer jr nz,rom_write_z ; if error, skip write ; ; Write data from buffer call pcf_write ; write the page ; rom_write_z: push af ; save current status call pcf_stop ; generate stop pop af ; restore status ret nz ; bail out if error status ; jr rom_write_wait ; exit via write wait ; ; ; rom_write_wait: ; While SROM is updating page data, it will NAK any ; start request. Loop on start requests until an ACK is ; received or loop timeout. ld b,0 ; try 256 times rom_write_wait1: push bc ; save loop control ld a,(romadr) ; load ROM I2C adress rlca ; move to top 7 bits res 0,a ; clear low bit for write call pcf_start ; generate start jr nz,rom_write_wait2 ; skip ahead call pcf_waitpin ; wait for bus and get status ;call prthex rom_write_wait2: push af ; save current status call pcf_stop ; generate stop pop af ; restore status pop bc ; restore loop control cp $FF ; timeout? jp z,err_timeout ; handle timeout error and pcf_st_lrb ; isolate LRB (ACK bit) jr z,rom_write_wait3 ; done djnz rom_write_wait1 ; else loop until timeout jp err_timeout ; handle timeout ; rom_write_wait3: ;ld a,b ;neg ;call prthex xor a ; set flags ret ; done ; ; Read ROM data into buffer ; DE=buffer adr ; HL=ROM byte offset ; BC=buffer len ; rom_read: ; Move offset to buffer, convert to big endian!!! ld a,h ; high byte ld (adrbuf+0),a ; ... to first buffer pos ld a,l ; low byte ld (adrbuf+1),a ; ... to second buffer pos ; ; Save count to HL for later push bc ; move count from BC pop hl ; ... to HL ; ; Generate start condition ld a,(romadr) ; load ROM I2C adress rlca ; move to top 7 bits res 0,a ; clear low bit for write call pcf_start ; generate start jr nz,rom_read_z ; if error, skip write ; rom_read2: ; Send ROM address push de ; save buffer pointer push hl ; save buffer length ld hl,2 ; 2 byte address ld de,adrbuf ; memory address pointer call pcf_write ; set memory pointer pop hl ; restore buffer length pop de ; restore buffer pointer jr nz,rom_read_z ; if error, bail out ; ; Repeat start, switch to read ld a,(romadr) ; load ROM I2C address rlca ; move to top 7 bits set 0,a ; set low bit for read call pcf_repstart ; generate repeat start jr nz,rom_read_z ; if error, bail out ; ; Read data into buffer call pcf_read ; read the page ; rom_read_z: push af ; save current status call pcf_stop ; generate stop pop af ; restore status or a ; set flags ret ; done ; ;======================================================================= ; PCF8584 Routines ;======================================================================= ; ; General PCF8584 initialization ; pcf_init: ; Select S0' (own address) 0x80 -> 0x00 ld a,(pcf_ctl) ; ctl port ld c,a ; to C ld a,pcf_ctl_pin ; PCF reset, select S0' out (c),a ; do it nop in a,(c) ; read status ;call crlf2 ;call prtsp ;call prthex and $7F ; remove pin bit jp nz,err_init ; all should be zero ; ; Set S0' (own address) 0x55 dec c ; data port ld a,pcf_adr ; own address out (c),a ; set own address in S0' (own << 1) nop in a,(c) ; read back S0' ;call prtsp ;call prthex cp pcf_adr ; correct? jp nz,err_init ; if not, init error ; ; Select S2 (clock) 0xA0 -> 0x20 inc c ; ctl port ld a,pcf_ctl_pin | pcf_ctl_es1 ; select S2 out (c),a ; do it nop in a,(c) ; read status ;call prtsp ;call prthex and 07fh ; remove pin bit cp pcf_ctl_es1 ; verify S2 selected jp nz,err_init ; it not, init error ; ; Set S2 (clock) 0x1C dec c ; data port ld a,pcf_trns | pcf_clk ; load clock register s2 out (c),a ; do it nop in a,(c) ; read back S2 ;call prtsp ;call prthex and $1F ; only the lower 5 bits are used cp pcf_trns | pcf_clk jp nz,err_init ; ; Enter idle 0xC1 inc c ; ctl port ld a,pcf_op_idle out (c),a ; do it nop in a,(c) ; read status ;call prtsp ;call prthex cp pcf_st_pin | pcf_st_bb ; expected status jp nz,err_init ; xor a ret ; ; Generate an I2C start condition ; A=start byte value (slave address + r/w bit) ; pcf_start: ;call crlf ;push de ;ld de,str_start ;call prtstr ;pop de ; ; Wait for I2C bus clear ld b,a ; move start byte to B call pcf_waitbb ; wait while bus busy cp $FF ; timeout? jp z,err_timeout ; timeout error return ; ; Set start byte w/ slave address in S0 ld a,(pcf_dat) ; data port ld c,a ; ... into C ;ld a,b ;call prtsp ;call prthex out (c),b ; send start byte ; ; Initiate start operation inc c ; ctl port ld a,pcf_op_start ; command out (c),a ; do it ; xor a ; signal success ret ; done ; ; Generate an I2C repeat start condition ; A=start byte value (slave address + r/w bit) ; pcf_repstart: ;call crlf ;push de ;ld de,str_repstart ;call prtstr ;pop de ; ; Send repeat start command ld b,a ; move start byte to B ld a,(pcf_ctl) ; control port ld c,a ; ... into C ld a,pcf_op_repstart ; command out (c),a ; do it ; ; Set start byte w/ slave address in S0 ld a,(pcf_dat) ; data port ld c,a ; ... into C ;ld a,b ;call prtsp ;call prthex out (c),b ; send start byte ; xor a ; signal success ret ; done ; ; Generate an I2C stop condition ; pcf_stop: ;call crlf ;push de ;ld de,str_stop ;call prtstr ;pop de ; ld a,(pcf_ctl) ; control port ld c,a ; ... into C ld a,pcf_op_stop ; command ;call prtsp ;call prthex out (c),a ; do it ; xor a ; signal success ret ; done ; ; Write bytes to I2C ; HL=byte count to write ; DE=buffer pointer ; pcf_write: ;push af ;push bc ;push de ;push hl ;push hl ;push de ;pop hl ;call crlf ;ld de,str_write ;call prtstr ;call prtsp ;call prthexword ;call prtsp ;pop hl ;call prthexword ;call crlf ;pop hl ;pop de ;pop bc ;pop af ; pcf_write1: call pcf_waitack ; wait for ack ret nz ; abort on failure ; ld a,h ; check for or l ; ... counter exhausted ret z ; if so, done, exit w/ ZF set ; ld a,(pcf_dat) ; data port ld c,a ; ... into C ld a,(de) ; get byte to write ;call prtsp ;call prthex out (c),a ; send it ; dec hl ; decrement byte counter inc de ; bump buf ptr jr pcf_write1 ; loop till done ; ; Read bytes from I2C ; HL=byte count to read ; pcf_read: ;push af ;push bc ;push de ;push hl ;call crlf ;ld de,str_read ;call prtstr ;call prtsp ;call prthexword ;call crlf ;pop hl ;pop de ;pop bc ;pop af ; ; First byte is a "dummy", must be discarded call pcf_waitack ; wait for ack ret nz ; abort on failure ld a,(pcf_dat) ; data port ld c,a ; ... into C in a,(c) ; get dummy byte ; pcf_read0: call pcf_waitack ; wait for ack ret nz ; abort on failure ; ; Loop control dec hl ; pre-decrement byte counter ld a,h ; check for or l ; ... counter exhausted - 1 jr z,pcf_read2 ; handle end game ; pcf_read1: ; Get next data byte ld a,(pcf_dat) ; data port ld c,a ; ... into C in a,(c) ; get next byte of ROM ;call prtsp ;call prthex ld (de),a ; save byte received in buf inc de ; bump buf pointer ; jr pcf_read0 ; loop till done ; pcf_read2: ; Special treatment for final character ld a,(pcf_ctl) ; control port ld c,a ; ... into C ld a,$40 ; prep for neg ack out (c),a ; send it ; ; Get final data byte ld a,(pcf_dat) ; data port ld c,a ; ... into C in a,(c) ; get next byte of ROM ;call prtsp ;call prtdot ;call prthex ld (de),a ; save byte received in buf ; call pcf_waitpin ; wait for PIN cp $FF ; timeout? jp z,err_timeout ; handle it ; xor a ; signal success ret ; done ; ; Wait for I2C bus to not be busy (BB = 1) ; Return PCF status in A, 0xFF for timeout ; pcf_waitbb: push bc ; save BC ld a,(pcf_ctl) ; control port value ld c,a ; ... into C ld b,0 ; timeout counter ; pcf_waitbb1: in a,(c) ; get status byte bit 0,a ; test busy bit (inverted) jr nz,pcf_waitbb_z ; if BB=1, bus clear, return djnz pcf_waitbb1 ; loop to keep trying or $FF ; signal timeout ; pcf_waitbb_z: pop bc ; restore BC ret ; done ; ; Wait for PIN (PIN = 0) ; Return PCF status in A, 0xFF for timeout ; pcf_waitpin: push bc ; save BC ld a,(pcf_ctl) ; control port value ld c,a ; ... into C ld b,0 ; timeout counter ; pcf_waitpin1: ; Wait till done with send/receive (PIN=0) in a,(c) ; get status byte bit 7,a ; test PIN bit jr z,pcf_waitpin_z ; if 0, done djnz pcf_waitpin1 ; loop till timeout or $FF ; signal timeout ; pcf_waitpin_z: pop bc ; restore BC ret ; done ; ; Wait for slave (PIN = 0) and check for acknowledge (LRB = 0) ; Return error code ; pcf_waitack: call pcf_waitpin ; wait for PIN cp $FF ; timeout? jp z,err_timeout ; handle it ; Evaluate response and pcf_st_lrb ; isolate LRB bit jp nz,err_nak ; handle NAK error xor a ; set status ret ; ; Error Handlers ; err_bio: ld a,ec_bio jr err_ret ; err_plt: ld a,ec_plt jr err_ret ; err_usage: ld a,ec_usage jr err_ret ; err_init: ld a,ec_init jr err_ret ; err_timeout: ld a,ec_timeout jr err_ret ; err_nak: ld a,ec_nak jr err_ret ; err_fopen: ld a,ec_fopen jr err_ret ; err_fio: ld a,ec_fio jr err_ret ; err_exists: ld a,ec_exists jr err_ret ; err_verify: ld a,ec_verify jr err_ret ; err_ret: or a ; set flags ret ; ; ; prterr: push de push hl neg rlca ld hl,str_err_table call addhla ld e,(hl) inc hl ld d,(hl) call crlf2 call prtstr pop hl pop de ret ; ;======================================================================= ; Utility Routines ;======================================================================= ; ; Identify active BIOS. RomWBW HBIOS=1, UNA UBIOS=2, else 0 ; idbio: ; ; Check for UNA (UBIOS) ld a,($FFFD) ; fixed location of UNA API vector cp $C3 ; jp instruction? jr nz,idbio1 ; if not, not UNA ld hl,($FFFE) ; get jp address ld a,(hl) ; get byte at target address cp $FD ; first byte of UNA push ix instruction jr nz,idbio1 ; if not, not UNA inc hl ; point to next byte ld a,(hl) ; get next byte cp $E5 ; second byte of UNA push ix instruction jr nz,idbio1 ; if not, not UNA, check others ; ld bc,$04FA ; UNA: get BIOS date and version rst 08 ; DE := ver, HL := date ; ld a,2 ; UNA BIOS id = 2 ret ; and done ; idbio1: ; Check for RomWBW (HBIOS) ld hl,($FFFE) ; HL := HBIOS ident location ld a,'W' ; First byte of ident cp (hl) ; Compare jr nz,idbio2 ; Not HBIOS inc hl ; Next byte of ident ld a,~'W' ; Second byte of ident cp (hl) ; Compare jr nz,idbio2 ; Not HBIOS ; ld b,bf_sysver ; HBIOS: VER function ld c,0 ; required reserved value rst 08 ; DE := version, L := platform id ; ld a,1 ; HBIOS BIOS id = 1 ret ; and done ; idbio2: ; No idea what this is xor a ; Setup return value of 0 ret ; and done ; ; Print character in A without destroying any registers ; prtchr: push af ; save registers push bc push de push hl ld e,a ; character to print in E ld c,$02 ; BDOS function to output a character call bdos ; do it pop hl ; restore registers pop de pop bc pop af ret ; prtsp3: call prtsp prtsp2: call prtsp prtsp: ; ; shortcut to print a space character preserving all regs push af ; save af ld a,' ' ; load dot char call prtchr ; print it pop af ; restore af ret ; done ; prtdot: ; ; shortcut to print a dot character preserving all regs push af ; save af ld a,'.' ; load dot char call prtchr ; print it pop af ; restore af ret ; done ; ; Uppercase character in A ; upcase: cp 'a' ; below 'a'? ret c ; if so, nothing to do cp 'z'+1 ; above 'z'? ret nc ; if so, nothing to do and ~$20 ; convert character to lower ret ; done ; ; Print a zero terminated string at (de) without destroying any registers ; prtstr: push af push de ; prtstr1: ld a,(de) ; get next char or a jr z,prtstr2 call prtchr inc de jr prtstr1 ; prtstr2: pop de ; restore registers pop af ret ; ; Print a hex value prefix "0x" ; prthexpre: push af ld a,'0' call prtchr ld a,'x' call prtchr pop af ret ; ; Print the value in A in hex without destroying any registers ; prthex: call prthexpre prthex1: push af ; save AF push de ; save DE call hexascii ; convert value in A to hex chars in DE ld a,d ; get the high order hex char call prtchr ; print it ld a,e ; get the low order hex char call prtchr ; print it pop de ; restore DE pop af ; restore AF ret ; done ; ; print the hex word value in hl ; prthexword: call prthexpre prthexword1: push af ld a,h call prthex1 ld a,l call prthex1 pop af ret ; ; print the hex dword value in de:hl ; prthex32: call prthexpre push bc push de pop bc call prthexword1 push hl pop bc call prthexword1 pop bc ret ; ; Convert binary value in A to ascii hex characters in DE ; hexascii: ld d,a ; save A in D call hexconv ; convert low nibble of A to hex ld e,a ; save it in E ld a,d ; get original value back rlca ; rotate high order nibble to low bits rlca rlca rlca call hexconv ; convert nibble ld d,a ; save it in D ret ; done ; ; Convert low nibble of A to ascii hex ; hexconv: and $0F ; low nibble only add a,$90 daa adc a,$40 daa ret ; ; Print value of A or HL in decimal with leading zero suppression ; Use prtdecb for A or prtdecw for HL ; prtdecb: push hl ld h,0 ld l,a call prtdecw ; print it pop hl ret ; prtdecw: push af push bc push de push hl call prtdec0 pop hl pop de pop bc pop af ret ; prtdec0: ld e,'0' ld bc,-10000 call prtdec1 ld bc,-1000 call prtdec1 ld bc,-100 call prtdec1 ld c,-10 call prtdec1 ld e,0 ld c,-1 prtdec1: ld a,'0' - 1 prtdec2: inc a add hl,bc jr c,prtdec2 sbc hl,bc cp e ret z ld e,0 call prtchr ret ; ; Start a new line ; crlf2: call crlf ; two of them crlf: push af ; preserve AF ld a,13 ; call prtchr ; print it ld a,10 ; call prtchr ; print it pop af ; restore AF ret ; ; Dump a buffer in hex and ascii ; ; HL=buffer address ; C=buffer length, 0 for 256 bytes ; Uses AF, BC, DE, HL ; dumpbuf: ld de,0 ; init buffer offset dumpbuf1: call dumpline ld a,d inc a ret z jr dumpbuf1 ; dumpline: ; HL=buf ptr, DE=buf offset, C=bytes left to print call crlf ; start line ex de,hl ; offset in HL call prthexword1 ; print in hex ld a,16 ; increment call addhla ; ... for next time ex de,hl ; restore DE/HL ld a,':' call prtchr ; ; hex byte loop, C=bytes to print ld b,16 ; bytes per row push bc push hl dumpline1: ld a,b cp 8 jr nz,dumpline2 call prtsp ld a,'-' call prtchr ; dumpline2: call prtsp ld a,d inc a jr z,dumpline3 ld a,(hl) ; get byte inc hl ; bump position call prthex1 ; print it dec c jr nz,dumpline4 ld d,$FF ; flag end of buf jr dumpline4 dumpline3: call prtsp call prtsp dumpline4: djnz dumpline1 ; call prtsp call prtsp ld a,'|' call prtchr ; ; ascii byte loop, C=bytes to print pop hl pop bc dumpline5: ld a,(hl) ; get real byte inc hl call dumpchar dec c jr z,dumpline6 ; if done, just exit loop djnz dumpline5 dumpline6: ld a,'|' call prtchr ret ; dumpchar: ; Print character. Replace non-printable with '.' cp ' ' jr c,dumpchar1 ; first printable char is ' ' cp '~' + 1 jr nc,dumpchar1 ; last printable char is '~' jp prtchr ; print and return dumpchar1: ld a,'.' ; replace with '.' jp prtchr ; print and return ; ; Add hl,a ; ; A register is destroyed! ; addhla: add a,l ld l,a ret nc inc h ret ; ; ; delay: push af push hl ld hl,0 delay1: ld a,h or l jr nz,delay1 pop hl pop af ret ; ;======================================================================= ; String Data ;======================================================================= ; str_banner .db "I2C Serial ROM Utility v0.1, 26-Aug-2023",0 str_hwmsg1 .db "PCF8584 Data port=",0 str_hwmsg2 .db ", Control/Status port=",0 str_hwmsg3 .db "",0 str_inforead .db "Reading",0 str_infowrite .db "Writing",0 str_infotest .db "Testing",0 str_infodump .db "Dumping",0 str_infodump2 .db ", Page #",0 str_info1 .db " Serial ROM #",0 str_info2 .db " (I2C address ",0 str_info3 .db ")",0 str_exit .db "Done, Thank you for using I2C Serial ROM Utility!",0 str_confirm .db "Serial ROM will be overwritten, continue (y/N)?",0 str_err_ok .db "Successful completion",0 str_err_bio .db "RomWBW BIOS required, but not present!",0 str_err_plt .db "Hardware platform not currently supported!",0 str_err_usage .db "Usage:", cr, lf .db " SROM Tn Test SROM", cr, lf .db " SROM Dn Dump SROM n (0-511)", cr, lf .db " SROM Rn Read SROM n into ", cr, lf .db " SROM Wn Write SROM n from ", cr, lf .db "", cr, lf .db " n=SROM Id (0-7)", 0 str_err_init .db "PCF8584 failed during initialization!",0 str_err_timeout .db "I2C protocol timeout!",0 str_err_nak .db "Slave negative acknowledge!",0 str_err_fopen .db "Failed to open specified file!",0 str_err_fio .db "File input/output error!",0 str_err_exists .db "Output file already exists!",0 str_err_verify .db "Data mismatch during verification!",0 str_start .db "I2C Start...",0 str_repstart .db "I2C Repeat Start...",0 str_stop .db "I2C Stop...",0 str_read .db "I2C Read...",0 str_write .db "I2C Write...",0 str_readpage .db "Reading ROM page ",0 str_writepage .db "Writing ROM page ",0 str_writepage2 .db " with Data=",0 ; str_err_table: .dw str_err_ok .dw str_err_bio .dw str_err_plt .dw str_err_usage .dw str_err_init .dw str_err_timeout .dw str_err_nak .dw str_err_fopen .dw str_err_fio .dw str_err_exists .dw str_err_verify ; ;======================================================================= ; Working Data ;======================================================================= ; stksav .dw 0 ; stack pointer saved at start .fill stksiz,0 ; stack stack .equ $ ; stack top ; pcf_dat .db 0 ; PCF8584 data port pcf_ctl .db 0 ; PCF8584 control/status port ; func .db 0 ; Function requested: T/D/R/W romadr .db 0 ; ROM device I2C address page .dw 0 ; Page requested for dump ; confirm_buf .db 3 ; 3 bytes in buffer .db 0 ; bytes filled by BDOS .fill 3,0 ; actual character buffer ; fcb .fill 36,0 ; FCB ; adrbuf .fill 2,0 ; ROM address buffer (big endian!!!) pagebuf .fill rom_pgsiz,$55 ; ROM page buffer ; ;======================================================================= ; .end