aboutsummaryrefslogtreecommitdiff
path: root/libpsn00b/psxsio/sio.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpsn00b/psxsio/sio.c')
-rw-r--r--libpsn00b/psxsio/sio.c269
1 files changed, 269 insertions, 0 deletions
diff --git a/libpsn00b/psxsio/sio.c b/libpsn00b/psxsio/sio.c
new file mode 100644
index 0000000..6b80352
--- /dev/null
+++ b/libpsn00b/psxsio/sio.c
@@ -0,0 +1,269 @@
+/*
+ * PSn00bSDK buffered serial port driver
+ * (C) 2022 spicyjpeg - MPL licensed
+ *
+ * TODO: add proper support for DTR/DSR flow control
+ */
+
+#include <stdint.h>
+#include <assert.h>
+#include <psxetc.h>
+#include <psxapi.h>
+#include <psxsio.h>
+#include <hwregs_c.h>
+
+#define BUFFER_LENGTH 128
+#define SIO_SYNC_TIMEOUT 0x100000
+
+/* Private types */
+
+typedef struct {
+ uint8_t data[BUFFER_LENGTH];
+ uint8_t head, tail, length;
+} RingBuffer;
+
+/* Internal globals */
+
+static SIO_FlowControl _flow_control;
+static uint16_t _ctrl_reg_flag;
+
+static int (*_read_callback)(uint8_t) = (void *) 0;
+static void (*_old_sio_handler)(void) = (void *) 0;
+
+static volatile RingBuffer _tx_buffer, _rx_buffer;
+
+/* Private interrupt handler */
+
+#define _ENTER_CRITICAL() uint16_t mask = IRQ_MASK; IRQ_MASK = 0;
+#define _EXIT_CRITICAL() IRQ_MASK = mask;
+
+static void _sio_handler(void) {
+ // Handle any incoming bytes.
+ while (SIO_STAT & SR_RXRDY) {
+ uint8_t value = SIO_TXRX;
+
+ // Skip storing this byte into the RX buffer if the callback returns a
+ // non-zero value.
+ if (_read_callback) {
+ if (_read_callback(value))
+ continue;
+ }
+
+ int length = _rx_buffer.length;
+
+ if (length >= BUFFER_LENGTH) {
+ //_sdk_log("RX overrun, dropping bytes\n");
+ break;
+ }
+
+ int tail = _rx_buffer.tail;
+ _rx_buffer.tail = (tail + 1) % BUFFER_LENGTH;
+ _rx_buffer.length = length + 1;
+
+ _rx_buffer.data[tail] = value;
+ }
+
+ // Send the next byte in the buffer if the TX unit is ready. Note that
+ // checking for CTS is unnecessary as the serial port is already hardwired
+ // to do so.
+ if (SIO_STAT & (SR_TXRDY | SR_TXU)) {
+ int length = _tx_buffer.length;
+
+ if (length) {
+ int head = _tx_buffer.head;
+ _tx_buffer.head = (head + 1) % BUFFER_LENGTH;
+ _tx_buffer.length = length - 1;
+
+ SIO_CTRL |= CR_TXIEN;
+ SIO_TXRX = _tx_buffer.data[head];
+ } else {
+ SIO_CTRL &= CR_TXIEN ^ 0xffff;
+ }
+ }
+
+ // Acknowledge the IRQ and update flow control signals.
+ if (_rx_buffer.length < BUFFER_LENGTH)
+ SIO_CTRL = CR_INTRST | (SIO_CTRL | _ctrl_reg_flag);
+ else
+ SIO_CTRL = CR_INTRST | (SIO_CTRL & (_ctrl_reg_flag ^ 0xffff));
+}
+
+/* Serial port initialization API */
+
+void SIO_Init(int baud, uint16_t mode) {
+ EnterCriticalSection();
+ _old_sio_handler = InterruptCallback(8, &_sio_handler);
+
+ SIO_CTRL = CR_ERRRST;
+ SIO_MODE = (mode & 0xfffc) | MR_BR_16;
+ SIO_BAUD = (uint16_t) ((int) 0x1fa400 / baud);
+ SIO_CTRL = CR_TXEN | CR_RXEN | CR_RXIEN;
+
+ _tx_buffer.head = 0;
+ _tx_buffer.tail = 0;
+ _tx_buffer.length = 0;
+ _rx_buffer.head = 0;
+ _rx_buffer.tail = 0;
+ _rx_buffer.length = 0;
+
+ _flow_control = SIO_FC_NONE;
+ _ctrl_reg_flag = 0;
+
+ ExitCriticalSection();
+}
+
+void SIO_Quit(void) {
+ EnterCriticalSection();
+ InterruptCallback(8, _old_sio_handler);
+
+ SIO_CTRL = CR_ERRRST;
+
+ ExitCriticalSection();
+}
+
+void SIO_SetFlowControl(SIO_FlowControl mode) {
+ _ENTER_CRITICAL();
+
+ switch (mode) {
+ case SIO_FC_NONE:
+ _flow_control = SIO_FC_NONE;
+ _ctrl_reg_flag = 0;
+
+ SIO_CTRL &= 0xffff ^ CR_DSRIEN;
+ break;
+
+ case SIO_FC_RTS_CTS:
+ _flow_control = SIO_FC_RTS_CTS;
+ _ctrl_reg_flag = CR_RTS;
+
+ SIO_CTRL &= 0xffff ^ CR_DSRIEN;
+ break;
+
+ /*case SIO_FC_DTR_DSR:
+ _flow_control = SIO_FC_DTR_DSR;
+ _ctrl_reg_flag = CR_DTR;
+
+ SIO_CTRL |= CR_DSRIEN;
+ break;*/
+ }
+
+ _EXIT_CRITICAL();
+}
+
+/* Reading API */
+
+int SIO_ReadByte(void) {
+ /*for (int i = SIO_SYNC_TIMEOUT; i; i--) {
+ if (_rx_buffer.length)
+ return SIO_ReadByte2();
+ }*/
+ while (!_rx_buffer.length)
+ __asm__ volatile("");
+
+ return SIO_ReadByte2();
+}
+
+int SIO_ReadByte2(void) {
+ if (!_rx_buffer.length)
+ return -1;
+
+ _ENTER_CRITICAL();
+
+ int head = _rx_buffer.head;
+ _rx_buffer.head = (head + 1) % BUFFER_LENGTH;
+ _rx_buffer.length--;
+
+ _EXIT_CRITICAL();
+ return _rx_buffer.data[head];
+}
+
+int SIO_ReadSync(int mode) {
+ if (mode)
+ return _rx_buffer.length;
+
+ /*for (int i = SIO_SYNC_TIMEOUT; i; i--) {
+ if (_rx_buffer.length)
+ return 0;
+ }*/
+ while (!_rx_buffer.length)
+ __asm__ volatile("");
+
+ return 0;
+}
+
+void *SIO_ReadCallback(int (*func)(uint8_t)) {
+ EnterCriticalSection();
+
+ void *old_callback = _read_callback;
+ _read_callback = func;
+
+ ExitCriticalSection();
+}
+
+/* Writing API */
+
+int SIO_WriteByte(uint8_t value) {
+ for (int i = SIO_SYNC_TIMEOUT; i; i--) {
+ if (_tx_buffer.length < BUFFER_LENGTH)
+ return SIO_WriteByte2(value);
+ }
+
+ //_sdk_log("SIO_WriteByte() timeout\n");
+ return -1;
+}
+
+int SIO_WriteByte2(uint8_t value) {
+ // If the TX unit is currently busy, append the byte to the buffer instead
+ // of sending it immediately. Note that interrupts must be disabled *prior*
+ // to checking if TX is busy; disabling them afterwards would create a race
+ // condition where the transfer could end while interrupts are being
+ // disabled. Interrupts are disabled through the IRQ_MASK register rather
+ // than via syscalls for performance reasons.
+ _ENTER_CRITICAL();
+
+ if (SIO_STAT & (SR_TXRDY | SR_TXU)) {
+ SIO_TXRX = value;
+ _EXIT_CRITICAL();
+ return 0;
+ }
+
+ int length = _tx_buffer.length;
+
+ if (length >= BUFFER_LENGTH) {
+ _EXIT_CRITICAL();
+
+ //_sdk_log("TX overrun, dropping bytes\n");
+ return -1;
+ }
+
+ int tail = _tx_buffer.tail;
+ _tx_buffer.tail = (tail + 1) % BUFFER_LENGTH;
+ _tx_buffer.length = length + 1;
+
+ _tx_buffer.data[tail] = value;
+ SIO_CTRL |= CR_TXIEN;
+
+ _EXIT_CRITICAL();
+ return length;
+}
+
+int SIO_WriteSync(int mode) {
+ if (mode)
+ return _tx_buffer.length;
+
+ // Wait for the buffer to become empty.
+ for (int i = SIO_SYNC_TIMEOUT; i; i--) {
+ if (!_tx_buffer.length)
+ break;
+ }
+
+ if (!_tx_buffer.length) {
+ // Wait for the TX unit to finish sending the last byte.
+ while (!(SIO_STAT & (SR_TXRDY | SR_TXU)))
+ __asm__ volatile("");
+ } else {
+ //_sdk_log("SIO_WriteSync() timeout\n");
+ }
+
+ return _tx_buffer.length;
+}