From 2650b86c9620ee65f622c031a0f29f316341d100 Mon Sep 17 00:00:00 2001 From: Peter Weingartner Date: Tue, 23 Jul 2024 14:56:46 -0400 Subject: [PATCH] Starting IEC low-level code I'm trying this code first in C. I may need to scrap it and go to assembly and follow Gadget's code more closely. --- src/C256/iec.s | 85 ++++++++++ src/dev/iec_port.c | 307 ++++++++++++++++++++++++++++++++++++ src/dev/iec_port.h | 33 ++++ src/include/F256/iec_f256.h | 16 +- 4 files changed, 440 insertions(+), 1 deletion(-) create mode 100644 src/C256/iec.s create mode 100644 src/dev/iec_port.c create mode 100644 src/dev/iec_port.h diff --git a/src/C256/iec.s b/src/C256/iec.s new file mode 100644 index 0000000..bc95bbb --- /dev/null +++ b/src/C256/iec.s @@ -0,0 +1,85 @@ +;;; +;;; 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/dev/iec_port.c b/src/dev/iec_port.c new file mode 100644 index 0000000..1a39388 --- /dev/null +++ b/src/dev/iec_port.c @@ -0,0 +1,307 @@ +/** + * @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/dev/iec_port.h b/src/dev/iec_port.h new file mode 100644 index 0000000..b5d39e3 --- /dev/null +++ b/src/dev/iec_port.h @@ -0,0 +1,33 @@ +/** + * @file iec_port.h + * @author your name (you@domain.com) + * @brief + * @version 0.1 + * @date 2024-07-22 + * + * @copyright Copyright (c) 2024 + * + */ + +#ifndef __iec_port_h__ +#define __iec_port_h__ + +#include + +/** + * @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 + */ +extern void iec_status(uint8_t device, char * buffer, short count); + +/** + * @brief Initialize the IEC port + * + * @return short 0 on success, a negative number indicates an error + */ +extern short iec_init(); + +#endif diff --git a/src/include/F256/iec_f256.h b/src/include/F256/iec_f256.h index c10091e..03c7062 100644 --- a/src/include/F256/iec_f256.h +++ b/src/include/F256/iec_f256.h @@ -7,7 +7,21 @@ #include -#define IEC_BASE ((volatile uint8_t *)0xf01680) +#define IEC_CMD_LISTEN 0x20 +#define IEC_CMD_TALK 0x40 +#define IEC_CMD_OPENCH 0x60 +#define IEC_CMD_CLOSE 0xE0 +#define IEC_CMD_OPEN 0xF0 +#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_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