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.
This commit is contained in:
Peter Weingartner 2024-07-23 14:56:46 -04:00
parent 2e966e481d
commit 2650b86c96
4 changed files with 440 additions and 1 deletions

85
src/C256/iec.s Normal file
View file

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

307
src/dev/iec_port.c Normal file
View file

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

33
src/dev/iec_port.h Normal file
View file

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

View file

@ -7,7 +7,21 @@
#include <stdint.h> #include <stdint.h>
#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 // TODO: fill out with the actual registers