// Atari ST serial HAL -- the RS-232 Modem port via TOS.
//
// The base ST's one RS-232 line is the Modem port, driven by the 68901 MFP
// USART and wrapped by TOS as BIOS logical device 1 (AUX) with a receive-
// buffered IOREC. This HAL rides that: XBIOS Rsconf sets baud/flow/format,
// Bconin/Bconout move bytes, Bconstat/Bcostat gate them. All are GEMDOS/BIOS/
// XBIOS traps that switch to supervisor internally, so -- unlike the direct
// $FFFFFAxx pokes elsewhere in the ST HAL -- serial needs no supervisor
// handling and no new headers. RX is buffered by TOS, so jlpSerialPoll is a
// no-op. (Caveat: TOS's default RS-232 IOREC is small ~256 bytes and Bconin
// blocks, so callers gate on Available; sustained high-baud input can overrun
// -- a future revision could grow the IOREC or install an MFP RX ISR.)

#include <mint/osbind.h>

#include "joey/serial.h"
#include "port.h"


// BIOS logical device 1 = RS-232 / Modem port.
#define ST_AUX_DEV       1

// 68901 MFP USART control register (UCR) bit fields, built by serialUcr:
//   bit 7   clock divide (1 = /16, standard async)
//   bits6-5 word length  (00=8, 01=7, 10=6, 11=5)  -> (8 - dataBits)
//   bits4-3 stop bits    (01 = 1 stop, 11 = 2 stop)
//   bit 2   parity enable
//   bit 1   parity even (1) / odd (0)
#define ST_UCR_CLK_DIV16 0x80


// ----- Prototypes -----

static int16_t serialBaudCode(uint32_t baud);
static int16_t serialUcr(const jlSerialConfigT *cfg);


// ----- Helpers -----

// Rsconf speed codes (XBIOS): 0 is fastest, 15 slowest. Unknown -> 9600.
static int16_t serialBaudCode(uint32_t baud) {
    switch (baud) {
        case 19200u: return 0;
        case 9600u:  return 1;
        case 4800u:  return 2;
        case 3600u:  return 3;
        case 2400u:  return 4;
        case 2000u:  return 5;
        case 1800u:  return 6;
        case 1200u:  return 7;
        case 600u:   return 8;
        case 300u:   return 9;
        case 200u:   return 10;
        case 150u:   return 11;
        case 134u:   return 12;
        case 110u:   return 13;
        case 75u:    return 14;
        case 50u:    return 15;
        default:     return 1;
    }
}


static int16_t serialUcr(const jlSerialConfigT *cfg) {
    int16_t  ucr;

    ucr  = ST_UCR_CLK_DIV16;
    ucr |= (int16_t)((8u - cfg->dataBits) << 5);
    ucr |= (int16_t)((cfg->stopBits == 2u ? 3u : 1u) << 3);
    if (cfg->parity != JL_SERIAL_PARITY_NONE) {
        ucr |= (int16_t)(1u << 2);
        if (cfg->parity == JL_SERIAL_PARITY_EVEN) {
            ucr |= (int16_t)(1u << 1);
        }
    }
    return ucr;
}


// ----- HAL entry points (alphabetical) -----

uint16_t jlpSerialAvailable(void) {
    // Bconstat reports presence (-1/0), not a count: 1 == at least one byte.
    return Bconstat(ST_AUX_DEV) ? 1u : 0u;
}


void jlpSerialClose(void) {
    // Trap path hijacks no vector -- nothing to tear down.
}


void jlpSerialFlush(void) {
    while (Bconstat(ST_AUX_DEV)) {
        (void)Bconin(ST_AUX_DEV);
    }
}


bool jlpSerialOpen(jlSerialDeviceE device, const jlSerialConfigT *config) {
    int16_t  flow;

    (void)device;   // the base ST has one RS-232 line: the Modem port.
    switch (config->flow) {
        case JL_SERIAL_FLOW_RTSCTS:  flow = 2; break;
        case JL_SERIAL_FLOW_XONXOFF: flow = 1; break;
        default:                     flow = 0; break;
    }
    // Rsconf(speed, flow, ucr, rsr, tsr, scr); -1 leaves a field unchanged.
    Rsconf(serialBaudCode(config->baud), flow, serialUcr(config), -1, -1, -1);
    return true;
}


void jlpSerialPoll(void) {
    // TOS buffers RX in its IOREC; nothing to pump.
}


uint16_t jlpSerialRead(uint8_t *buf, uint16_t max) {
    uint16_t  n;

    n = 0u;
    // Gate each read on Bconstat first: Bconin blocks on an empty buffer.
    while (n < max && Bconstat(ST_AUX_DEV)) {
        buf[n] = (uint8_t)Bconin(ST_AUX_DEV);
        n++;
    }
    return n;
}


uint16_t jlpSerialWrite(const uint8_t *buf, uint16_t len) {
    uint16_t  n;

    for (n = 0u; n < len; n++) {
        uint32_t  guard;

        // Bounded transmitter-ready spin (Bcostat -1 = ready) so a stalled
        // link (no CTS) reports a partial write instead of hanging the frame.
        guard = 0u;
        while (!Bcostat(ST_AUX_DEV)) {
            guard++;
            if (guard > 200000ul) {
                return n;
            }
        }
        Bconout(ST_AUX_DEV, (int16_t)buf[n]);
    }
    return n;
}
