Further IEC coding (switching to assembly)

This commit is contained in:
Peter Weingartner 2024-07-29 14:41:14 -04:00
parent 2650b86c96
commit 01b2765ab0
12 changed files with 873 additions and 412 deletions

10
docs/f256k2_registers.txt Normal file
View file

@ -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)*

View file

@ -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

662
src/C256/iecll.s Normal file
View file

@ -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

View file

@ -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)

View file

@ -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

75
src/dev/iec.c Normal file
View file

@ -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 <stdbool.h>
#include <stdint.h>
#include <string.h>
#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;
}

View file

@ -9,8 +9,8 @@
*
*/
#ifndef __iec_port_h__
#define __iec_port_h__
#ifndef __iec_h__
#define __iec_h__
#include <stdint.h>
@ -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

View file

@ -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 <stdint.h>
#include <calypsi/intrinsics65816.h>
#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;
}
}

View file

@ -5,8 +5,6 @@
#ifndef __iec_f256_h__
#define __iec_f256_h__
#include <stdint.h>
#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

View file

@ -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

91
src/include/iecll.h Normal file
View file

@ -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 <stdint.h>
/**
* @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

View file

@ -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();