From 01b2765ab08f7ea16e1252dee7cfad332e8f82cf Mon Sep 17 00:00:00 2001 From: Peter Weingartner Date: Mon, 29 Jul 2024 14:41:14 -0400 Subject: [PATCH] Further IEC coding (switching to assembly) --- docs/f256k2_registers.txt | 10 + src/C256/iec.s | 85 ----- src/C256/iecll.s | 662 ++++++++++++++++++++++++++++++++++ src/Makefile | 6 +- src/dev/Makefile | 4 +- src/dev/iec.c | 75 ++++ src/dev/{iec_port.h => iec.h} | 7 +- src/dev/iec_port.c | 307 ---------------- src/include/F256/iec_f256.h | 21 +- src/include/features.h | 5 + src/include/iecll.h | 91 +++++ src/toolbox.c | 12 +- 12 files changed, 873 insertions(+), 412 deletions(-) create mode 100644 docs/f256k2_registers.txt delete mode 100644 src/C256/iec.s create mode 100644 src/C256/iecll.s create mode 100644 src/dev/iec.c rename src/dev/{iec_port.h => iec.h} (78%) delete mode 100644 src/dev/iec_port.c create mode 100644 src/include/iecll.h diff --git a/docs/f256k2_registers.txt b/docs/f256k2_registers.txt new file mode 100644 index 0000000..8adc6ee --- /dev/null +++ b/docs/f256k2_registers.txt @@ -0,0 +1,10 @@ + +https://discord.com/channels/691915291721990194/934618943400837130/1266710378612789278: + +$F0_1D00 - $F0_1D1F - SDCARD0 +$F0_1D20 - $F0_1D3F - SDCARD1 *** This one has moved *** +$F0_1D20 - $F0_1D3F - SPLASH LCD (SPI Port) +$F0_1D60 - $F0_1D7F - Wiznet Copper SPI Interface +$F0_1D80 - $F0_1D9F - Wiznet WIFI UART interface (115K or 2M) +$F0_1DA0 - $F0_1DBF - MIDI UART (Fixed @ 31,250Baud) +$F0_1DC0 - $F0_1DDF - Master SPI Interface to Supervisor (RP2040)* diff --git a/src/C256/iec.s b/src/C256/iec.s deleted file mode 100644 index bc95bbb..0000000 --- a/src/C256/iec.s +++ /dev/null @@ -1,85 +0,0 @@ -;;; -;;; Low-level support code for the IEC port -;;; - - .public sleep_20us - .public sleep_100us - .public sleep_300us - .public sleep_1ms - -#include "F256/iec_f256.h" - -; -; Macros -; - -; -; Macro to set IEC output pins -; -; @param pins the bit masks for the pins to set -; -set_pins: .macro pins - lda far:IEC_OUT - and #~(\pins) - sta far:IEC_OUT - .mend - -; -; Macro to clear IEC output pins -; -; @param pins the bit masks for the pins to clear -; -clr_pins: .macro pins - lda far:IEC_OUT - ora #(\pins) - sta far:IEC_OUT - .mend - -; -; Macro to set and clear pins in one shot -; -; @param set_pins the bit masks for the pins to set -; @param clr_pins the bit masks for the pins to clear -; -set_clr_pins: .macro set_pins, clr_pins - lda far:IEC_OUT - and #~(\set_pins) - ora #(\clr_pins) - sta far:IEC_OUT - .mend - -read_pin: .macro pin - pha - lda far:IEC_IN - bit #(\pin) - pla - .mend - -; -; Routines -; - -sleep_20us: phx - ldx #20 -_loop$ dex - bne _loop$ - plx - rtl - -sleep_100us: phx - ldx #5 -_loop$ jsl sleep_20us - dex - bne _loop$ - plx - rtl - -sleep_300us: jsl sleep_100us - jsl sleep_100us - jsl sleep_100us - rtl - -sleep_1ms: jsl sleep_300us - jsl sleep_300us - jsl sleep_300us - jmp sleep_100us diff --git a/src/C256/iecll.s b/src/C256/iecll.s new file mode 100644 index 0000000..791e107 --- /dev/null +++ b/src/C256/iecll.s @@ -0,0 +1,662 @@ +;;; +;;; Low-level support code for the IEC port +;;; +;;; NOTE: routines will be split into private routines and public routines. +;;; Private routines will assume near JSRs, 8-bit accumulator and index registers, and interrupts disabled. +;;; Public routines will assume far JSRs and 16-bit accumulator and index registers. +;;; Public routines will also assume Calypsi calling conventions. +;;; + + .public iecll_ioinit + .public iecll_in + .public iecll_eoi + .public iecll_out + .public iecll_talk + .public iecll_talk_sa + .public iecll_untalk + .public iecll_listen + .public iecll_listen_sa + .public iecll_unlisten + +#include "F256/iec_f256.h" + + .section data,data + +eoi_pending: .byte 0 +rx_eoi: .byte 0 +delayed: .byte 0 +queue: .byte 0 + + .section code + +;; +;; Macros +;; + +assert_bit: .macro pin + pha + lda IEC_OUTPUT_PORT + and #(\pin ^ 0xff) + sta IEC_OUTPUT_PORT + pla + rts + .endm + +release_bit: .macro pin + pha + lda IEC_OUTPUT_PORT + ora #(\pin) + sta IEC_OUTPUT_PORT + pla + rts + .endm + +read_bit: .macro pin + pha +loop$ lda IEC_INPUT_PORT + cmp IEC_INPUT_PORT + bne loop$ + and #(\pin) + cmp #1 + pla + rts + .endm + +;; +;; Private functions +;; + +; +; Pin accessor functions to assert, release, or read a pin +; + +read_SREQ: read_bit IEC_SREQ_i +assert_SREQ: assert_bit IEC_SREQ_o +release_SREQ: release_bit IEC_SREQ_o + +read_ATN: read_bit IEC_ATN_i +assert_ATN: assert_bit IEC_ATN_o +release_ATN: release_bit IEC_ATN_o + +read_CLOCK: read_bit IEC_CLK_i +assert_CLOCK: assert_bit IEC_CLK_o +release_CLOCK: release_bit IEC_CLK_o + +read_DATA: read_bit IEC_DATA_i +assert_DATA: assert_bit IEC_DATA_o +release_DATA: release_bit IEC_DATA_o + +;; +;; Routines to wait various amounts of time +;; + +sleep_20us: phx + ldx #20 +_loop$ dex + bne _loop$ + plx + rts + +sleep_100us: phx + ldx #5 +_loop$ jsl sleep_20us + dex + bne _loop$ + plx + rts + +sleep_300us: jsl sleep_100us + jsl sleep_100us + jsl sleep_100us + rts + +sleep_1ms: jsl sleep_300us + jsl sleep_300us + jsl sleep_300us + jmp sleep_100us + +;; +;; Code to handle the IEC port +;; + +init: jsr sleep_1ms + jsr release_ATN + jsr release_DATA + jsr release_SREQ + jsr assert_CLOCK ; IDLE state + jsr sleep_1ms + jsr sleep_1ms + jsr sleep_1ms + + ; Bail if ATN and SRQ fail to float back up. + ; We'll have a more thorough test when we send + ; our first command. + nop + nop + nop + jsr read_SREQ + bcc err$ + jsr read_ATN + bcc err$ + + jsr sleep_1ms + + clc + rts + +err$ sec + rts + +; +; Send a command byte and release the DATA and CLOCK lines afterwards +; +; A = the command byte to send +; +atn_release_data + jsr release_DATA + jsr release_CLOCK ; TODO: makes /no/ sense; maybe we can remove... + jsr atn_common ; NOTE: does NOT release ATN! Does NOT release IRQs! + rts + +; +; Send a command byte and release the ATN, DATA, and CLOCK lines +; +; A = the command byte to send +; +atn_release jsr atn_common + + jsr release_ATN + jsr sleep_20us + jsr sleep_20us + jsr sleep_20us + jsr release_CLOCK + jsr release_DATA + rts + +; +; Send a command byte with ATN asserted +; +; A = the command byte to send +; +atn_common + ; Assert ATN; if we aren't already in sending mode, + ; get there: + + jsr assert_ATN + jsr assert_CLOCK + jsr release_DATA + + ; Now give the devices ~1ms to start listening. + jsr sleep_1ms + + ; If no one is listening, there's nothing on + ; the bus, so signal an error. + jsr read_DATA + bcs err$ + + ; ATN bytes are technically never EOI bytes + stz eoi_pending + + jmp send + +err$ + ; Always release the ATN line on error; TODO: add post delay + jmp release_ATN + +; +; Send a byte over the IEC bus but mark it as the last byte +; +; A = the byte to send +; +send_eoi jsr set_eoi + jmp send + +; +; Set that the EOI byte is the next to be sent +; +set_eoi stz eoi_pending + dec eoi_pending + rts + +; +; Sends the queued byte with an EOI +; +flush bit delayed + bpl done$ + pha + lda queue + stz delayed + jsr send_eoi + pla +done$: rts + +; +; Send a byte over the IEC bus +; +; A = the byte to send +; +send + ; Assumes we are in the sending state: + ; the host is asserting CLOCK and the devices are asserting DATA. + + ; There must be at least 100us between bytes. + jsr sleep_300us + + ; ; Clever cheating (PJW: removed since we disable interrupts for all this code) + + ; ; Act as an ersatz listener to keep the other listeners busy + ; ; until we are ready to receive. This is NOT part of the + ; ; IEC protocol -- we are doing this in lieu of an interrupt. + ; jsr assert_DATA + + ; Release CLOCK to signal that we are ready to send + ; We can do this without disabling interrupts because + ; we are also asserting DATA. + jsr release_CLOCK + + ; Now we wait for all of the listeners to acknowledge. +wait$ + jsr sleep_20us + + ; Check to see if all listeners have acknowledged + jsr read_DATA + bcs ready$ + + ; Other listeners are still busy; go back to sleep. + bra wait$ + +ready$ bit eoi_pending + bpl send$ + +eoi$ + ; Alas, we can't get too clever here, or the 1541 hates us. + + ; Hard-wait the 200us for the drive to acknowledge the EOI. + ; This duration is technically unbounded, but the ersatz + ; listener trick during the EOI signal, and the drive + ; already had the opportunity to delay before starting the + ; ack, so hopefully it will stay in nominal 250us range. + +TYE$ jsr read_DATA + bcs TYE$ + + ; Now we're basically back to the point where we are waiting + ; for Rx ack. The trick does work here. + + ; Clear the eoi minus flag, so our next send will be data. + lsr eoi_pending + + ; The drive should hold DATA for at least 60us. Give it + ; 20us, and then repeat our ersatz listener trick. + jsr sleep_20us + ; jsr assert_DATA (PJW: removed trick) + bra wait$ + +send$ + ; Give the listeners time to notice that the've all ack'd + jsr sleep_20us ; NOT on the C64 + + ; Now start pushing out the bits. Note that the timing + ; is not critical, but each clock state must last at + ; least 20us (with 70us more typical for clock low). + + phx + ldx #8 +loop$ + ; TODO: opt test for a frame error + + ; Clock out the next bit + jsr assert_CLOCK + jsr sleep_20us + lsr a + bcs one$ + +zero$ jsr assert_DATA + bra clock$ + +one$ jsr release_DATA + +clock$ + ; Toggle the clock + jsr sleep_20us ; TODO: Maybe extend this. + + jsr sleep_20us ; 1541 needs this. + jsr release_CLOCK + + jsr sleep_20us + dex + bne loop$ + plx + + ; Finish the last bit and wait for the listeners to ack. + jsr release_DATA + jsr assert_CLOCK + + ; Now wait for listener ack. Of course, if there are + ; multiple listeners, we can only know that one ack'd. + ; This can take up to a millisecond, so another good + ; candidate for a kernel thread or interrupt. + + ; TODO: ATN release timing appears to be semi-critical; we may need + ; to completely change the code below. + +ack$ jsr read_DATA + bcs ack$ + clc + rts + +; +; Wait for a byte of data to be read from the IEC port +; +recv_data + + ; Assume not EOI until proved otherwise + stz rx_eoi + + ; Wait for the sender to have a byte +wait1$ jsr read_CLOCK + bcc wait1$ + + ; TODO: start and check a timer + + ; Signal we are ready to receive +ready$ jsr release_DATA + + ; Wait for all other listeners to signal +wait2$ jsr read_DATA + bcc wait2$ + + ; Wait for the first bit or an EOI condition + ; Each iteration takes 6-7us + lda #0 ; counter +wait3$ inc a + beq eoi$ + jsr read_CLOCK + bcc recv$ + adc #7 ; microseconds per loop + bcc wait3$ + +eoi$ lda rx_eoi + bmi error$ + + ; Ack the EOI + jsr assert_DATA + jsr sleep_20us + jsr sleep_20us + jsr sleep_20us + + ; Set the EOI flag. + dec rx_eoi ; TODO: error on second round + + ; Go back to the ready state + bra ready$ + +error$ sec + rts + +recv$ + ; Clock in the bits + phx + ldx #8 + +wait_fall$ jsr read_CLOCK + bcs wait_fall$ + +wait_rise$ jsr read_CLOCK + bcc wait_rise$ + + jsr read_DATA + ror a + dex + bne wait_fall$ + plx + + ; Ack + jsr sleep_20us + jsr assert_DATA + + ; Drives /usually/ work with a lot less, but + ; I see failures on the SD2IEC on a status check + ; after file-not-found when debugging is turned off. + jsr sleep_20us ; Seems to be missing the ack. + jsr sleep_20us ; Seems to be missing the ack. + jsr sleep_20us ; Seems to be missing the ack. + jsr sleep_20us ; Seems to be missing the ack. + + ; Return EOI in NV + clc + bit rx_eoi + ora #0 + rts + +;; +;; Public Functions +;; + +; +; Initialize the IEC interface +; +; extern short iecll_ioinit() +; +; Returns 0 on success, -1 if no devices found +; +iecll_ioinit php + sei ; Disable interrupts + sep #0x30 ; Switch to 8-bit registers + + stz delayed + + jsr init + bcs err$ + + plp + lda ##0 + rtl + +err$ plp + lda ##0xffff + rtl + +; +; Send a TALK command to a device +; +; extern short iecll_talk(uint8_t device) +; +; A = the number of the device to become the talker +; +iecll_talk php + sei + sep #0x30 + + ora #0x40 + jsr flush + jsr atn_release_data ; NOTE: does NOT drop ATN! + + plp + rtl + +; +; Send the secondary address to the TALK command, release ATN, and turn around control of the bus +; +; extern short iecll_talk_sa(uint8_t secondary_address) +; +; A = the secondary address to send +; +iecll_talk_sa php + sei + sep #0x30 + + jsr atn_common + + jsr assert_DATA + jsr release_ATN + jsr release_CLOCK +1$ jsr read_CLOCK + bcs 1$ ; TODO: should time out. + + plp + rtl + +; +; Send a LISTEN command to a device +; +; extern short iecll_listen(uint8_t device) +; +; A = the number of the device to become the listener +; +iecll_listen php + sei + sep #0x30 + + ora #0x20 + jsr flush + jsr atn_release_data ; NOTE: does NOT drop ATN! + + plp + rtl + +; +; Send the secondary address to the LISTEN command and release ATN +; +; extern short iecll_listen_sa(uint8_t secondary_address) +; +; A = the secondary address to send +; +iecll_listen_sa php + sei + sep #0x30 + + jsr atn_common + jsr release_ATN + + ; TODO: WARNING! No delay here! + ; TODO: IMHO, should wait at least 100us to avoid accidental turn-around! + ; TODO: tho we do protect against this in the send code. + + plp + rtl + +; +; Send the UNTALK command to all devices and drop ATN +; +; extern void iecll_untalk() +; +iecll_untalk php + sei + sep #0x30 + + ; Detangled from C64 sources; TODO: compare with Stef's + + lda #0x5f + + ; There should never be a need to flush here, and if you + ; do manage to call IECOUT between a TALK/TALKSA and an + ; UNTALK, the C64 will flush it while ATN is asserted and + ; the drive will be mighty confused. + ; + ; TODO: track the state and cause calls to IECOUT to fail. + + ; pre-sets CLOCK IMMEDIATELY before the ATN ... again, TODO: makes no sense + jsr assert_CLOCK + jsr atn_release + + plp + rtl + +; +; Send the UNLISTEN command to all devices +; +; extern void iecll_unlisten() +; +iecll_unlisten php + sei + sep #0x30 + + ; Detangled from C64 sources; TODO: compare with Stef's + + lda #0x3f + jsr flush + jsr atn_release + + plp + rtl + +; +; Try to get a byte from the IEC bus +; +; NOTE: EOI flag is set, if this character is the last to be read from the active talker. +; +; extern uint8_t iecll_in() +; +; Returns: +; A = the byte read +; +iecll_in php + sei ; Disable interrupts + sep #0x30 ; Switch to 8-bit registers + + jsr recv_data + + ; NOTE: we'll just read from the eoi variable in a separate function + ; We might need to return it here folded in with the data somwhow + + plp + and ##0x00ff + rtl + +; +; Check to see if the last byte read was an EOI byte +; +; extern short iecll_eoi() +; +; Returns: +; A = 0 if not EOI, -1 if EOI +; +iecll_eoi php + sep #0x30 + + lda rx_eoi + beq not_eoi$ + + plp + lda ##0xffff + rtl + +not_eoi$ plp + lda #0 + rtl + +; +; Send a byte to the IEC bus. Actually sends the previous byte and queues the current byte. +; +; extern void iecll_out(uint8_t byte) +; +; Inputs: +; A = the byte to send +; +iecll_out php + sei + sep #0x30 + + ; Sends the byte in A. + ; Actually, sends the previous IECOUT byte, and queues + ; this one for later transmission. This is done to + ; ensure that we can mark the last data byte with an EOI. + + clc + bit delayed + bpl queue$ + + ; Send the old byte + pha + lda queue + jsr send + pla + stz delayed + + ; Queue the new byte +queue$ sta queue + dec delayed + + plp + rtl \ No newline at end of file diff --git a/src/Makefile b/src/Makefile index e1ed53b..c1c3ff7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ # VPATH=.:../../module/Calypsi-remote-debug/src DEBUGGER=../module/Calypsi-remote-debug/src -UNIT := F256K +UNIT := F256 MEMORY := RAM # Define OS-dependent variables @@ -40,7 +40,7 @@ else ifeq ($(UNIT),C256_FMX) else ifeq ($(UNIT),F256) CPU=w65816 C_SRCS_DEBUGGER=$(DEBUGGER)/agent.c $(DEBUGGER)/c256-uart.c $(DEBUGGER)/low_level_WDC65816.s - SRCS_FOR_UNIT=C256/jumptable.s C256/io_stubs.c C256/extras.s + SRCS_FOR_UNIT=C256/jumptable.s C256/io_stubs.c C256/extras.s C256/iecll.s CFLAGS_FOR_UNIT=-DMODEL=2 -DCPU=255 --code-model large --data-model large ifeq ($(MEMORY),ROM) @@ -51,7 +51,7 @@ else ifeq ($(UNIT),F256) else ifeq ($(UNIT),F256K) CPU=w65816 C_SRCS_DEBUGGER=$(DEBUGGER)/agent.c $(DEBUGGER)/c256-uart.c $(DEBUGGER)/low_level_WDC65816.s - SRCS_FOR_UNIT=C256/jumptable.s C256/io_stubs.c C256/extras.s + SRCS_FOR_UNIT=C256/jumptable.s C256/io_stubs.c C256/extras.s C256/iecll.s CFLAGS_FOR_UNIT=-DMODEL=2 -DCPU=255 --code-model large --data-model large ifeq ($(MEMORY),ROM) diff --git a/src/dev/Makefile b/src/dev/Makefile index 2811a51..7756c6c 100644 --- a/src/dev/Makefile +++ b/src/dev/Makefile @@ -37,14 +37,14 @@ else ifeq ($(UNIT),F256) AS=as65816 AR=nlib - SRCS_FOR_UNIT=txt_f256.c kbd_f256.c kbd_f256jr.c indicators_c256.c interrupts_f256.c sdc_f256.c # timers_c256.c + SRCS_FOR_UNIT=txt_f256.c kbd_f256.c kbd_f256jr.c indicators_c256.c interrupts_f256.c sdc_f256.c iec.c # timers_c256.c CFLAGS_FOR_UNIT=-DMODEL=2 -DCPU=255 --code-model large --data-model large # --target Foenix else ifeq ($(UNIT),F256K) CC=cc65816 AS=as65816 AR=nlib - SRCS_FOR_UNIT=txt_f256.c kbd_f256.c kbd_f256k.c indicators_c256.c interrupts_f256.c sdc_f256.c # timers_c256.c + SRCS_FOR_UNIT=txt_f256.c kbd_f256.c kbd_f256k.c indicators_c256.c interrupts_f256.c sdc_f256.c iec.c # timers_c256.c CFLAGS_FOR_UNIT=-DMODEL=2 -DCPU=255 --code-model large --data-model large # --target Foenix endif diff --git a/src/dev/iec.c b/src/dev/iec.c new file mode 100644 index 0000000..e975b7e --- /dev/null +++ b/src/dev/iec.c @@ -0,0 +1,75 @@ +/** + * @file iec_port.c + * @author your name (you@domain.com) + * @brief Implement the low level control code for the IEC port on the F256 + * @version 0.1 + * @date 2024-07-19 + * + * @copyright Copyright (c) 2024 + * + */ + +#include +#include +#include + +#include "iecll.h" +#include "dev/txt_screen.h" + +short iec_open(uint8_t device, uint8_t channel, bool is_write, char * command) { + return -1; +} + +void iec_close(uint8_t device, uint8_t channel) { + iecll_listen(device); + iecll_listen_sa(0xe0 | (channel & 0x0f)); + iecll_unlisten(); +} + +/** + * @brief Retrieve the raw status string from an IEC device + * + * @param device number of the IEC device to query + * @param buffer character buffer in which to write the status + * @param count the maximum number of bytes to fetch + * @return the number of bytes read from the device + */ +short iec_status(uint8_t device, char * buffer, short count) { + short i = 0; + + // Zero out the buffer + memset((void *)buffer, 0, count); + + // Open the device, channel 15 + txt_put(0, '-'); + iecll_talk(device); + txt_put(0, '.'); + iecll_talk_sa(0x6f); + txt_put(0, '.'); + + // Copy the status bytes + for (i = 0; i < count; i++) { + uint8_t byte = iecll_in(); + txt_put(0, (char)byte); + buffer[i] = (char)byte; + if (iecll_eoi()) { + break; + } + } + buffer[i+1] = 0; + + iecll_untalk(); + + return i; +} + +/** + * @brief Initialize the IEC port + * + * @return short 0 on success, a negative number indicates an error + */ +short iec_init() { + short result = iecll_ioinit(); + + return result; +} \ No newline at end of file diff --git a/src/dev/iec_port.h b/src/dev/iec.h similarity index 78% rename from src/dev/iec_port.h rename to src/dev/iec.h index b5d39e3..1004323 100644 --- a/src/dev/iec_port.h +++ b/src/dev/iec.h @@ -9,8 +9,8 @@ * */ -#ifndef __iec_port_h__ -#define __iec_port_h__ +#ifndef __iec_h__ +#define __iec_h__ #include @@ -20,8 +20,9 @@ * @param device number of the IEC device to query * @param buffer character buffer in which to write the status * @param count the maximum number of bytes to fetch + * @return the number of bytes read from the device */ -extern void iec_status(uint8_t device, char * buffer, short count); +extern short iec_status(uint8_t device, char * buffer, short count); /** * @brief Initialize the IEC port diff --git a/src/dev/iec_port.c b/src/dev/iec_port.c deleted file mode 100644 index 1a39388..0000000 --- a/src/dev/iec_port.c +++ /dev/null @@ -1,307 +0,0 @@ -/** - * @file iec_port.c - * @author your name (you@domain.com) - * @brief Implement the low level control code for the IEC port on the F256 - * @version 0.1 - * @date 2024-07-19 - * - * @copyright Copyright (c) 2024 - * - */ - -#include -#include - -#include "F256/iec_f256.h" -#include "errors.h" - -/** - * @brief Wait for 20 microseconds - * - */ -static void sleep_20us() { - __asm( - " phx" - " ldx ##20" - "1$ dex" - " bne 1$" - " plx" - ); -} - -/** - * @brief Wait for 100 microseconds - * - */ -static void sleep_100us() { - __asm( - " phx" - " ldx #5" - "1$ jsl sleep_20us" - " dex" - " bne 1$" - " plx" - ); -} - -/** - * @brief Wait for 300 microseconds - * - */ -static void sleep_300us() { - sleep_100us(); - sleep_100us(); - sleep_100us(); -} - -/** - * @brief Wait for 1 millisecond - * - */ -static void sleep_1ms() { - sleep_300us(); - sleep_300us(); - sleep_300us(); -} - -/** - * @brief Assert pins on the IEC port - * - * @param pins mask bits of the pins to assert - */ -static void iec_assert(uint8_t pins) { - *IEC_OUT &= ~pins; -} - -/** - * @brief Release pins on the IEC port - * - * @param pins mask bits of the pins to release - */ -static void iec_release(uint8_t pins) { - *IEC_OUT |= pins; -} - -/** - * @brief Assert and release pins in one transaction - * - * @param assert_pins mask bits of pins to assert - * @param release_pins mask bits of pins to release - */ -static void iec_assert_release(uint8_t assert_pins, uint8_t release_pins) { - *IEC_OUT = (*IEC_OUT & ~assert_pins) | release_pins; -} - -/** - * @brief Read a pin on the IEC port, given its mask bit - * - * @param pin mask bit of the pin to read - * @return uint8_t 0 = pin released, 1 = pin asserted - */ -static uint8_t iec_read(uint8_t pin) { - uint8_t v1 = *IEC_IN; - uint8_t v2 = *IEC_IN; - while (v1 != v2) { - v1 = v2; - v2 = *IEC_IN; - } - return ((v2 & pin == 0) ? 1 : 0); -} - -static short iec_send_common(uint8_t data, bool eoi) { - sleep_300ms(); - - iec_release(IEC_PIN_CLK | IEC_PIN_DATA); - - // Wait for the DATA line to be released by all the listeners - // NOTE: we may need to use Gadget's clever DATA hack - do { - sleep_20us(); - } while(iec_read(IEC_PIN_DATA)); - - if (eoi) { - // If EOI, we wait until the listener ACKs the EOI - while (!iec_read(IEC_PIN_DATA)) ; - - // Thgen wait for the DATA - do { - sleep_20us(); - } while(iec_read(IEC_PIN_DATA)); - } - - // Actually send the bits... - - sleep_20us(); - for (short bit = 0; bit < 8; bit++) { - iec_assert(IEC_PIN_CLOCK); - if (data & 0x01) { - iec_assert(IEC_PIN_DATA); - } else { - iec_release(IEC_PIN_DATA); - } - data = data >> 1; - - sleep_20us(); - sleep_20us(); - iec_release(IEC_PIN_CLOCK); - sleep_20us(); - } - - iec_release(IEC_PIN_DATA); - iec_assert(IEC_PIN_CLK); - - for (short count = 0; count < 50; count++) { - if (iec_read(IEC_PIN_DATA)) { - break; - } - sleep_20us(); - } - - return 0; -} - -static short iec_atn_common(uint8_t data) { - __interrupt_state_t state = __get_interrupt_state(); - __disable_interrupts; - - iec_assert_release((IEC_PIN_ATN | IEC_PIN_CLK), IEC_PIN_DATA); - sleep_1ms(); - - if (iec_read(IEC_PIN_DATA)) { - iec_release(IEC_PIN_ATN); - __restore_interrupt_state(state); - return ERR_GENERAL; - } - - short result = iec_send_common(data, false); - __restore_interrupt_state(state); - return result; -} - -static short iec_atn_release(uint8_t data) { - iec_atn_common(data); - iec_release(IEC_PIN_ATN); - sleep_20us(); - sleep_20us(); - sleep_20us(); - iec_release(IEC_PIN_CLK | IEC_PIN_DATA); -} - -static uint8_t iec_read_b() { - bool is_eoi = false; - while(iec_read(IEC_PIN_CLK)) { - // TODO: detect EOI here - ; - } - - iec_release(IEC_PIN_DATA); - - while(iec_read(IEC_PIN_CLK)) ; - - uint8_t data = 0; - for (short count = 0; count < 8; count++) { - data = data >> 1; - - while(iec_read(IEC_PIN_CLK)) ; - - if (iec_read(IEC_PIN_DATA)) { - data |= 0x80; - } - - while(!iec_read(IEC_PIN_CLK)) ; - } - - iec_assert(IEC_PIN_DATA); - sleep_20us(); - iec_release(IEC_PIN_DATA); - - if (eoi) { - // We have the last byte, wait for the talker to turn around the bus - while (iec_read(IEC_PIN_CLK)) ; - - // Take back control of the bus - iec_assert(IEC_PIN_CLK); - } - - return data; -} - -/** - * @brief Send the TALK command to an IEC device - * - * @param device the number of the IEC device to make a talker - */ -void iec_talk(uint8_t device) { - return iec_atn_common(IEC_CMD_TALK | (device & 0x0f)); -} - -/** - * @brief Send the secondary address for the talk command - * - * @param channel the channel for the secondary address - */ -static void iec_talk_sa(uint8_t channel) { - iec_atn_release_release(channel & 0x0f); - - // Turn around control of the bus to the talker - iec_assert(IEC_PIN_DATA); - iec_release(IEC_PIN_CLK); - - // Wait for acknowledgement that we have a talker - while(!iec_read(IEC_PIN_CLK)) ; -} - -/** - * @brief Send the UNTALK command - * - */ -void iec_untalk() { - iec_atn_release(IEC_CMD_UNTALK); -} - -/** - * @brief Send the UNLISTEN command - * - */ -static void iec_unlisten() { - iec_atn_release(IEC_CMD_UNLISTEN); -} - -/** - * @brief Retrieve the raw status string from an IEC device - * - * @param device number of the IEC device to query - * @param buffer character buffer in which to write the status - * @param count the maximum number of bytes to fetch - */ -void iec_status(uint8_t device, char * buffer, short count) { - iec_talk(device); - iec_talk_sa(IEC_CMD_OPENCH | 0x0f); - - // TODO: handle the EOI - - for (short i = 0; i < count; i++) { - char c = iec_read_b(); - buffer[i] = c; - } - - iec_untalk(); -} - -/** - * @brief Initialize the IEC port - * - * @return short 0 on success, a negative number indicates an error - */ -short iec_init() { - iec_assert_release(IEC_PIN_CLK, (IEC_PIN_ATN | IEC_PIN_DATA | IEC_PIN_SREQ)); - sleep_1ms(); - sleep_1ms(); - sleep_1ms(); - - if (iec_read(IEC_PIN_ATN) || iec_read(IEC_PIN_SREQ)) { - return ERR_NOT_READY; - } else { - return 0; - } -} \ No newline at end of file diff --git a/src/include/F256/iec_f256.h b/src/include/F256/iec_f256.h index 03c7062..c3154fc 100644 --- a/src/include/F256/iec_f256.h +++ b/src/include/F256/iec_f256.h @@ -5,8 +5,6 @@ #ifndef __iec_f256_h__ #define __iec_f256_h__ -#include - #define IEC_CMD_LISTEN 0x20 #define IEC_CMD_TALK 0x40 #define IEC_CMD_OPENCH 0x60 @@ -15,14 +13,17 @@ #define IEC_CMD_UNLISTEN 0x3F #define IEC_CMD_UNTALK 0x5F -#define IEC_IN ((volatile uint8_t *)0xf01680) -#define IEC_OUT ((volatile uint8_t *)0xf01681) +#define IEC_INPUT_PORT 0xf01680 +#define IEC_DATA_i 0x01 +#define IEC_CLK_i 0x02 +#define IEC_ATN_i 0x10 +#define IEC_SREQ_i 0x80 -#define IEC_PIN_DATA 0x01 -#define IEC_PIN_CLK 0x02 -#define IEC_PIN_ATN 0x10 -#define IEC_PIN_SREQ 0x80 - -// TODO: fill out with the actual registers +#define IEC_OUTPUT_PORT 0xf01681 +#define IEC_DATA_o 0x01 +#define IEC_CLK_o 0x02 +#define IEC_ATN_o 0x10 +#define IEC_RST_o 0x40 +#define IEC_SREQ_o 0x80 #endif \ No newline at end of file diff --git a/src/include/features.h b/src/include/features.h index a2ddf09..40f4c50 100644 --- a/src/include/features.h +++ b/src/include/features.h @@ -44,6 +44,7 @@ #elif MODEL == MODEL_FOENIX_F256KE #define HAS_EXTERNAL_SIDS 1 #define HAS_OPL3 1 + #define HAS_IEC 1 #endif @@ -89,4 +90,8 @@ #define HAS_SNES_GAMEPAD 0 #endif +#ifndef HAS_IEC + #define HAS_IEC 0 +#endif + #endif \ No newline at end of file diff --git a/src/include/iecll.h b/src/include/iecll.h new file mode 100644 index 0000000..9d78df9 --- /dev/null +++ b/src/include/iecll.h @@ -0,0 +1,91 @@ +/** + * @file ieecll.h + * @author your name (you@domain.com) + * @brief Declare the functions that provide low-level access to the IEC port + * @version 0.1 + * @date 2024-07-27 + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef __iecll_h__ +#define __iecll_h__ + +#include + +/** + * @brief Initialize the IEC interface + * + * @return short 0 on success, -1 if no devices found + */ +extern short iecll_ioinit(); + +/** + * @brief Send a TALK command to a device + * + * @param device the number of the device to become the talker + * @return short + */ +extern short iecll_talk(uint8_t device); + +/** + * @brief Send the secondary address to the TALK command, release ATN, and turn around control of the bus + * + * @param secondary_address the secondary address to send + * @return short + */ +extern short iecll_talk_sa(uint8_t secondary_address); + +/** + * @brief Send a LISTEN command to a device + * + * @param device + * @return short the number of the device to become the listener + */ +extern short iecll_listen(uint8_t device); + +/** + * @brief Send the secondary address to the LISTEN command and release ATN + * + * @param secondary_address the secondary address to send + * @return short + */ +extern short iecll_listen_sa(uint8_t secondary_address); + +/** + * @brief Send the UNTALK command to all devices and drop ATN + * + */ +extern void iecll_untalk(); + +/** + * @brief Send the UNLISTEN command to all devices + * + */ +extern void iecll_unlisten(); + +/** + * @brief Try to get a byte from the IEC bus + * + * NOTE: EOI flag is set, if this character is the last to be read from the active talker. + * + * @return uint8_t the byte read + */ +extern uint8_t iecll_in(); + +/** + * @brief Check to see if the last byte read was an EOI byte + * + * @return short 0 if not EOI, any other number if EOI + */ +extern short iecll_eoi(); + +/** + * @brief Send a byte to the IEC bus. Actually sends the previous byte and queues the current byte. + * + * @param byte the byte to send + */ +extern void iecll_out(uint8_t byte); + +#endif \ No newline at end of file diff --git a/src/toolbox.c b/src/toolbox.c index 0bfeda8..80d91aa 100644 --- a/src/toolbox.c +++ b/src/toolbox.c @@ -50,11 +50,11 @@ #include "dev/dma.h" #include "dev/fdc.h" #include "dev/fsys.h" +#include "dev/iec.h" #include "dev/ps2.h" #include "dev/rtc.h" #include "dev/sdc.h" #include "dev/txt_screen.h" - #include "dev/uart.h" #include "snd/codec.h" #include "snd/psg.h" @@ -176,6 +176,10 @@ void initialize() { INFO("Console installed."); } +#if HAS_IEC + iec_init(); +#endif + /* Initialize the timers the MCP uses */ timers_init(); INFO("Timers initialized"); @@ -482,10 +486,14 @@ int main(int argc, char * argv[]) { test_sysinfo(); // test_psg(); - test_kbd(); + // test_kbd(); long jiffies = timers_jiffies(); printf("Jiffies: %ld\n", jiffies); + printf("Attempting to get status for IEC drive #8: "); + short n = iec_status(8, message, 256); + printf("\"%s\"\n", message); + // Attempt to start up the user code // log(LOG_INFO, "Looking for user startup code:"); // boot_launch();