#include <string.h>

#include "cc1defs.h"
#include "intregs.h"
#include "intvecs.h"
#include "smdmport.h"
#include "charq_p.h"

#define THRE_BIT_POS    5
#define THRE_BIT_MASK   (1 << THRE_BIT_POS)
#define TSRE_BIT_POS    6
#define TSRE_BIT_MASK   (1 << TSRE_BIT_POS)
#define RXD_BIT_POS     0
#define RXD_BIT_MASK    (1 << RXD_BIT_POS)
#define CR              13

#define ena_Rx              0x01
#define ena_Tx              0x02
#define ena_RxERR           0x04
#define ena_MDM_STS         0x08

#define mdm_ena_mask        (ena_Rx + ena_Tx)

#define DCD_State_Bit       0x80
#define CTS_State_Bit       0x10
#define Delta_DCD_Bit       0x08
#define Delta_CTS_Bit       0x01
#define Ring_State_Bit      0x40
#define Trailing_Ring_Bit   0x04


#ifdef __cplusplus
    #define __CPPARGS ...
#else
    #define __CPPARGS
#endif


#pragma option -r-

static UINT mdm_prn_count;
static BOOL DCD_flag, RI_flag, MDM_XOFFED, rx_has_CR, rq_new_RI;
static UINT modem_alive, last_MSR, new_MSR;

BOOL  get_mdm_CTS(void)
{
    if (last_MSR & CTS_State_Bit)
        return TRUE;
    else
        return FALSE;
}

static q_control *mdm_tx_q, *mdm_rx_q;

void  set_mdm_tx_q(q_control *q)
{
    if (q)
        mdm_tx_q = q;
}

void  set_mdm_rx_q(q_control *q)
{
    if (q)
        mdm_rx_q = q;
}

void interrupt MdmHandler (__CPPARGS)
{ char c;

    goto first_test;

mdm_txd_0:
    if (MDM_XOFFED)
        goto Check_MSR_1;

mdm_txd_1:
    if (mdm_tx_q->count) {
        _AL = charq_getc(mdm_tx_q);
        asm out ModemBase,al
    }
    goto Check_MSR_1;

mdm_rxd_1:
    asm in ax,ModemBase   //   ; get char from UART
    c = _AL;
    if (_AL == CR)
        rx_has_CR = TRUE;

    charq_putc(mdm_rx_q, c);
    goto Check_MSR_1;

first_test:
    // first, check to see if modem is alive
    if (modem_alive)
        goto GotAModem;
    // else if not alive, don't respond to modem ints any more
    asm {
        mov   dx, Int0CtlReg
        in    ax, dx
        or    ax, Masked  // mask the cpu's INT line from the modem
        out   dx, ax
    }
    goto FalseInt;

GotAModem:    // Identify the type of interrupt
    asm {
        in      ax, ModemBase+IIRofs
        test    al,1
        jz      TryMSR
        jmp     FalseInt
    }
TryMSR:
    asm {
        cmp     al,0
        jne     TryTx
        jmp     Check_MSR_1 // interrupted by a modem status change
    }
TryTx:
    asm {
        cmp     al,2
        jne     TryRx
        jmp     mdm_txd_0   // interrupted by THRE
    }
TryRx:
    asm {
        cmp     al,4
        jne     TryRxERR
        jmp     mdm_rxd_1   // interrupted by rcvd data
    }
TryRxERR:
    asm {
        test    al,6
        jnz     Check_MSR_1
        // we have a rcv error - just clear it
        in      ax,ModemBase+LSRofs
    }

Check_MSR_1:
    asm in  ax,ModemBase+MSRofs
    _AH = 0;
    new_MSR = _AX;
    asm test    al,Delta_CTS_Bit
    asm jz      Check_RI_rq
    asm test    al,CTS_State_Bit
    asm jz      Check_RI_rq
    MDM_XOFFED = FALSE; // CTS is true, so XOFF is FALSE
    goto mdm_txd_1;

Check_RI_rq:
    if (rq_new_RI) {
        rq_new_RI = FALSE;
        RI_flag = FALSE;
    }
CheckCTS:
    if ((new_MSR & CTS_State_Bit) == 0)
        MDM_XOFFED = TRUE; //
CheckDCD:
    if (new_MSR & DCD_State_Bit) {
        DCD_flag = TRUE;
        RI_flag = FALSE;
        goto finup;
    }
DCD_Dropped:
    if (last_MSR & DCD_State_Bit) {
        DCD_flag = FALSE;
        RI_flag = FALSE;
        goto finup;
    }
RI_Check:
    if (new_MSR)
        RI_flag = TRUE;

finup:
    last_MSR = new_MSR;

FalseInt:
    asm {
        mov     dx,EOIReg
        mov     ax,Mdm212IntVec
        out     dx,ax
    }
}

BOOL  mdm_rxwaiting(void)
{
    if (mdm_rx_q->count)
        return TRUE;
    else
        return FALSE;
}


BOOL  get_DCD_flag(void)
{
    return DCD_flag;
}

BOOL  get_RI_flag(void)
{
    return RI_flag;
}

void flush_mdm_tx(void)
{
    asm pushf
    asm CLI
    asm mov   al, ena_Rx
    asm out   (ModemBase + IERofs),al    // ; turn off TX
  
    reset_charque(mdm_tx_q);
    mdm_prn_count = 0;
    MDM_XOFFED = FALSE;
  
    asm popf
}

void  flush_mdm_rx(void)
{
    asm pushf
    asm CLI
  
    reset_charque(mdm_rx_q);
    rx_has_CR = FALSE;
  
    asm popf
}

BOOL  THRE_empty(void)
{
    asm {
        pushf
        CLI
        in      ax,ModemBase+LSRofs
        popf
    }
    if (_AX & THRE_BIT_MASK)
        return TRUE;
    else
        return FALSE;
}

void  mdm_tx_restart(void)
{ char c;

    if (MDM_XOFFED)
        return;
  
    if (mdm_tx_q->count) {
        asm {
            pushf
            CLI
            in    ax, (ModemBase + LSRofs)
        }
        if (_AL & THRE_BIT_MASK) { // ready for another character
            asm mov   al, mdm_ena_mask      // enable RX and TX
            asm out   (ModemBase + IERofs),al
            c = charq_getc(mdm_tx_q);
            _AL = c;
            asm out   ModemBase,al
        }
        asm popf
    }
    else { // empty queue -- shut off the tx interrupt
        asm {
            pushf
            CLI
            mov   al,ena_Rx
            out   (ModemBase + IERofs),al
            popf
        }
    }
}

UINT mdm_tx_waiting(void)
{
    return mdm_tx_q->count;
}

UINT mdm_tx_free(void)
{
    return (mdm_tx_q->size - mdm_tx_q->count);
}

BOOL  mdm_txbuf_empty(void)
{
    if (mdm_tx_q->count)
        return FALSE;
    else
        return TRUE;
}

BOOL  mdm_tx_empty(void)
{
    asm {
        pushf
        CLI
        in    ax,ModemBase+LSRofs
        popf
    }
    if ((_AL & 0x60) == 0x60)
        return TRUE;
    else
        return FALSE;
}

UCHAR mdm_getbufc(void)
{
    return charq_getc(mdm_rx_q);
}

void  mdm_puts(UCHAR *message)
{ UINT slen;

    if (message) {
        slen = strlen(message);
        if (slen) {
            charq_puts(mdm_tx_q, message, slen);
        }
    }
}



void  mdm_putch(UCHAR c)
{
    charq_putc(mdm_tx_q, c);
}

UINT  mdm_prn2go(void)
{
    return mdm_prn_count;
}

void  ena_mdm_ints(void)
{
    asm {
        pushf
        CLI
        mov   dx,Int0CtlReg
        in    ax,dx
        and   ax,(NOT Masked)  //  unmask the SocketModem INT line
        out   dx,ax
        popf
    }
}

void  mask_mdm_ints(void)
{
    asm {
        pushf
        CLI
      
        mov   dx,Int0CtlReg
        in    ax,dx
        or    ax,Masked  // mask the cpu's INT line from the modem
        out   dx,ax
      
        popf
    }
}

void  mdm_reg_init(void)
{ UINT saved;

    asm {
        pushf
        CLI
        mov   ax, 0x5555
        out   (ModemBase + SCRofs), al
    }
    saved = _AX;
    asm {
        in    ax, ModemBase             // read the rx register
      
        mov   al, 3                     // 8 data, 1 stop, no parity
        or    al, 0x80                  //  set the DLAB
        out   (ModemBase + LCRofs), al
        xchg  bx, bx
        // set the default baud rate divisor for 2400 bps
        mov   ax, 48                    // for 1200 bps, use 96
        out   (ModemBase + BRG0ofs), al
        xchg  bx, bx
      
        mov   al,ah
        out   (ModemBase + BRG1ofs), al
        xchg  bx, bx
        //  turn off DLAB
        mov   al, 3                     // 8 data, 1 stop, no parity
        out   (ModemBase + LCRofs), al
        xchg  bx, bx
        // assert DTR, negate OUT2
        mov   al, 0x01
        out   (ModemBase + MCRofs), al
        xchg  bx, bx
      
        // read IIR to clear any pending interrupts
        in    ax, (ModemBase + IIRofs)
        xchg  bx, bx
        // clear out any line status register junk
        in    ax, (ModemBase + LSRofs)
        xchg  bx, bx
        // clear out any modem status register junk
        xor    ax, ax
        out   (ModemBase + MSRofs), ax
        xchg  bx, bx
      
        mov   al, ena_Rx                // enable rx interrupt only
        out   (ModemBase + IERofs), al
      
        in    ax, (ModemBase + SCRofs)
    }
    if (_AL == (UCHAR) saved)
        modem_alive = saved;
    
Done:
    asm popf
    
}

void  ena_mdm_EIA(void)
{
    asm pushf
    asm CLI
    rq_new_RI = TRUE;
    // enable rx, tx, and mdm status
    asm mov   al,(mdm_ena_mask OR ena_MDM_STS) 
    asm out   (ModemBase + IERofs), al
    asm nop
    asm nop
    asm popf
}


void  mask_mdm_EIA(void)
{
    asm pushf
    asm CLI
    asm mov   al, mdm_ena_mask // enable rx, tx, interrupts
    asm out   (ModemBase + IERofs), al
    asm nop
    asm nop
    asm popf
}

void  mdm_cominit(void)
{
    asm pushf
    asm CLI
    
    mask_mdm_ints();
    
    modem_alive = FALSE;  // init as 'NO MODEM'
    DCD_flag = FALSE;
    RI_flag = FALSE;
    
    reset_charque(mdm_rx_q);
    reset_charque(mdm_tx_q);
    
    MDM_XOFFED = FALSE;
    
    setvect(Mdm212IntVec, MdmHandler);
    mdm_reg_init();
    
    asm popf
}

void  clear_mdm_flags(void)
{
    asm pushf
    asm CLI
    
    DCD_flag = FALSE;
    RI_flag = FALSE;
    rq_new_RI = FALSE;
    new_MSR = FALSE;
    rx_has_CR = FALSE;
    last_MSR = DCD_State_Bit;
    
    asm popf
}

UINT  get_IER(void)
{
    asm {
        pushf
        CLI
        in    ax,ModemBase+IERofs
        popf
    }
    _AH = 0;
    return _AX;
}


void  write_mdm_port(UINT port_addr, UINT value)
{
    asm {
        push  ax
        push  dx
        mov   dx,port_addr
        mov   ax,value
      
        pushf
        CLI
        out   dx,al
        popf
        pop   dx
        pop   ax
    }
}

void reset_CR_flag(void)
{
    rx_has_CR = FALSE;
}

BOOL  rx212_has_CR(void)
{
    return rx_has_CR;
}

void reset_RI_flag(void)
{
    rq_new_RI = TRUE;
}