/* DANG_BEGIN_MODULE
 *
 * REMARK
 * ser_ports.c: Serial ports for DOSEMU: Software emulated 16550 UART!
 * Please read the README.serial file in this directory for more info!
 *
 * Copyright (C) 1995 by Mark Rejhon
 *
 * The code in this module is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This module is maintained by Stas Sergeev <stsp@users.sourceforge.net>
 *
 * /REMARK
 * DANG_END_MODULE
 */

/**************************** DECLARATIONS *******************************/

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "emu.h"
#include "ser_defs.h"
#include "tty_io.h"


/*************************************************************************/
/*                 MISCELLANOUS serial support functions                 */
/*************************************************************************/

/* This is a function used for translating a bit to a different bit,
 * depending on a bit inside an integer 'testval'.  It will return 'outbit'
 * if the bit 'inbit' is set in 'testval'.
 */
inline int convert_bit(int testval, int inbit, int outbit)
{
  if (testval & inbit)
    return outbit;
  else
    return 0;
}


/* This function slides the contents of the receive buffer to the
 * bottom of the buffer.  A sliding buffer is used instead of
 * a circular buffer because this way, a single read() can easily
 * put data straight into our internal receive buffer!
 */
void rx_buffer_slide(int num)
{
  if (com[num].rx_buf_start == 0)
    return;
  /* Move existing chars in receive buffer to the start of buffer */
  memmove(com[num].rx_buf, com[num].rx_buf + com[num].rx_buf_start,
    RX_BUF_BYTES(num));

  /* Update start and end pointers in buffer */
  com[num].rx_buf_end -= com[num].rx_buf_start;
  com[num].rx_buf_start = 0;
}

static void clear_int_cond(int num, u_char val)
{
  com[num].int_condition &= ~val;
  /* reset IIR too, to recalculate later */
  com[num].IIR.mask = 0;
}

static void recalc_IIR(int num)
{
  int tmp;
#if 0
  /* disabled being too expensive... */
  serial_update(num);
#endif
  tmp = INT_REQUEST(num);
  if (!tmp)
    com[num].IIR.mask = 0;
  else if (tmp & LS_INTR)
    com[num].IIR.mask = LS_INTR;
  else if (tmp & RX_INTR)
    com[num].IIR.mask = RX_INTR;
  else if (tmp & TX_INTR)
    com[num].IIR.mask = TX_INTR;
  else if (tmp & MS_INTR)
    com[num].IIR.mask = MS_INTR;
}

static u_char get_IIR_val(int num)
{
  u_char val = com[num].IIR.flags << 3;
  switch (com[num].IIR.mask) {
  case 0:
    val |= UART_IIR_NO_INT;
    break;
  case LS_INTR:
    val |= UART_IIR_RLSI;
    break;
  case RX_INTR:
    val |= UART_IIR_RDI;
    break;
  case TX_INTR:
    val |= UART_IIR_THRI;
    break;
  case MS_INTR:
    val |= UART_IIR_MSI;
    break;
  }
  return val;
}

/* This function clears the specified XMIT and/or RCVR FIFO's.  This is
 * done on initialization, and when changing from 16450 to 16550 mode, and
 * vice versa.  [num = port, fifo = flags to indicate which fifos to clear]
 */
void uart_clear_fifo(int num, int fifo)
{
  /* DANG_FIXTHIS Should clearing UART cause THRE int if it's enabled? */

  if(s1_printf) s_printf("SER%d: Clear FIFO.\n",num);

  /* Clear the receive FIFO */
  if (fifo & UART_FCR_CLEAR_RCVR) {
    /* Preserve THR empty state, clear error bits and recv data ready bit */
    com[num].LSR &= ~(UART_LSR_ERR | UART_LSR_DR);
    com[num].rx_buf_start = 0;		/* Beginning of rec FIFO queue */
    com[num].rx_buf_end = 0;		/* End of rec FIFO queue */
    com[num].IIR.flg.cti = 0;		/* clear timeout */
    com[num].rx_timeout = 0;		/* Receive intr already occured */
    clear_int_cond(num, LS_INTR | RX_INTR);  /* Clear LS/RX conds */
    rx_buffer_dump(num);		/* Clear receive buffer */
  }

  /* Clear the transmit FIFO */
  if (fifo & UART_FCR_CLEAR_XMIT) {
    /* Preserve recv data ready bit and error bits, and set THR empty */
    com[num].LSR |= UART_LSR_TEMT | UART_LSR_THRE;
    clear_int_cond(num, TX_INTR);	/* Clear TX int condition */
    tx_buffer_dump(num);		/* Clear transmit buffer */
  }
}


/*************************************************************************/
/*                      RECEIVE handling functions                       */
/*************************************************************************/

/* This function returns a received character.
 * The character comes from the RBR register (or FIFO), which would have
 * been previously filled by the uart_fill routine (which does the
 * actual reads from the serial line) or loopback code.  This function
 * usually runs through the do_serial_in routine.    [num = port]
 */
static int get_rx(int num)
{
  int val;
  com[num].rx_timeout = 0;		/* Reset timeout counter */
  com[num].IIR.flg.cti = 0;

  /* if no data, try to get some */
  if (!RX_BUF_BYTES(num)) {
    int size = uart_fill(num);
    if (size > 0)
      receive_engine(num, size);
  }
  /* if still no data, go out */
  if (!RX_BUF_BYTES(num)) {
    if (com[num].LSR & UART_LSR_DR) {
      error("COM%i: DR set but buffer empty\n", num);
      com[num].LSR &= ~UART_LSR_DR;
    }
    return 0;
  }

  /* Get byte from internal receive queue */
  val = com[num].rx_buf[com[num].rx_buf_start++];
  /* Clear data waiting status and interrupt condition flag */
  clear_int_cond(num, RX_INTR);
  /* and see if more to read */
  receive_engine(num, 0);

  if (!RX_BUF_BYTES(num))
    com[num].LSR &= ~UART_LSR_DR;

  return val;		/* Return received byte */
}


/*************************************************************************/
/*                    MODEM STATUS handling functions                    */
/*************************************************************************/




/* The following computes the MSR delta bits.  It is done by computing
 * the difference between two MSR values (oldmsr and newmsr) by xor'ing
 * bits 4-7 of them.   The exception is the RI (ring) bit which is a
 * trailing edge bit (the RI trailing edge bit is set on only when RI
 * goes from on to off).  [oldmsr = old value, newmsr = new value]
 */
int msr_compute_delta_bits(int oldmsr, int newmsr)
{
  int delta;

  /* Compute difference bits, and restrict RI bit to trailing-edge */
  delta = (oldmsr ^ newmsr) & ~(newmsr & UART_MSR_RI);

  /* Shift bits to lowest 4 bits and mask other bits except delta bits */
  delta = (delta >> 4) & UART_MSR_DELTA;

  return delta;
}


/* This function returns the value in the MSR (Modem Status Register)
 * and does the expected UART operation whenever the MSR is read.
 * [num = port]
 */
static int
get_msr(int num)
{
  int val;

  modstat_engine(num);				/* Get the fresh MSR status */
  val = com[num].MSR;				/* Save old MSR value */
  com[num].MSR &= UART_MSR_STAT; 		/* Clear delta bits */
  clear_int_cond(num, MS_INTR);		/* MSI condition satisfied */

  return val;					/* Return MSR value */
}


/*************************************************************************/
/*                     LINE STATUS handling functions                    */
/*************************************************************************/

/* This function returns the value in the LSR (Line Status Register)
 * and does the expected UART operation whenever the LSR is read.
 * [num = port]
 */
static int
get_lsr(int num)
{
  int val;

  val = com[num].LSR;			/* Save old LSR value */
  clear_int_cond(num, LS_INTR);	/* RLSI int condition satisfied */
  com[num].LSR &= ~UART_LSR_ERR;	/* Clear error bits */

  return (val);                         /* Return LSR value */
}


/*************************************************************************/
/*                      TRANSMIT handling functions                      */
/*************************************************************************/

/* This function transmits a character.  This function is called mainly
 * through do_serial_out, when the Transmit Register is written to.
 * The end result is that the character is put into the THR or the
 * transmit FIFO. (or receive fifo if in Loopback test mode)
 * [num = port, val = character to transmit]
 */
static void put_tx(int num, char val)
{
  int rtrn;
#if 0
  /* Update the transmit timer */
  com[num].tx_timer += com[num].tx_char_time;
#endif
  clear_int_cond(num, TX_INTR);	/* TX interrupt condition satisifed */

  /* Loop-back writes.  Parity is currently not calculated.  No
   * UART diagnostics programs including COMTEST.EXE, that I tried,
   * complained about parity problems during loopback tests anyway!
   * Even some real UART clones don't calculate parity during loopback.
   */
  if (com[num].MCR & UART_MCR_LOOP) {
    com[num].rx_timeout = 0;		/* Reset timeout counter */
    switch (com[num].LCR & UART_LCR_WLEN8) {	/* Word size adjustment */
    case UART_LCR_WLEN7:  val &= 0x7f;  break;
    case UART_LCR_WLEN6:  val &= 0x3f;  break;
    case UART_LCR_WLEN5:  val &= 0x1f;  break;
    }
    if (FIFO_ENABLED(num)) {		/* Is it in FIFO mode? */
      /* Is the FIFO full? */
      if (RX_BUF_BYTES(num) >= com[num].rx_fifo_size) {
        if(s3_printf) s_printf("SER%d: Func put_tx loopback overrun requesting LS_INTR\n",num);
        com[num].LSR |= UART_LSR_OE;		/* Indicate overrun error */
        serial_int_engine(num, LS_INTR);	/* Update interrupt status */
      }
      else { /* FIFO not full */
        /* Put char into recv FIFO */
        com[num].rx_buf[com[num].rx_buf_end] = val;
        com[num].rx_buf_end++;

	/* If the buffer touches the top, slide chars to bottom of buffer */
        if ((com[num].rx_buf_end - 1) >= RX_BUFFER_SIZE) rx_buffer_slide(num);
        /* Is it the past the receive FIFO trigger level? */
        if (RX_BUF_BYTES(num) >= com[num].rx_fifo_trigger) {
          com[num].rx_timeout = 0;
          if(s3_printf) s_printf("SER%d: Func put_tx loopback requesting RX_INTR\n",num);
          serial_int_engine(num, RX_INTR);	/* Update interrupt status */
        }
      }
      com[num].LSR |= UART_LSR_DR;	/* Flag Data Ready bit */
    }
    else {				/* FIFOs not enabled */
      com[num].rx_buf[com[num].rx_buf_end] = val;  /* Overwrite old byte */
      com[num].rx_buf_end++;
      if (com[num].LSR & UART_LSR_DR) {		/* Was data waiting? */
        com[num].LSR |= UART_LSR_OE;		/* Indicate overrun error */
        if(s3_printf) s_printf("SER%d: Func put_tx loopback overrun requesting LS_INTR\n",num);
        serial_int_engine(num, LS_INTR);	/* Update interrupt status */
      }
      else {
        com[num].LSR |= UART_LSR_DR; 		/* Flag Data Ready bit */
        com[num].rx_timeout = 0;
        if(s3_printf) s_printf("SER%d: Func put_tx loopback requesting RX_INTR\n",num);
        serial_int_engine(num, RX_INTR);	/* Update interrupt status */
      }
    }
    return;
  }
  /* Else, not in loopback mode */

  if (!FIFO_ENABLED(num) && !(com[num].LSR & UART_LSR_THRE)) {
    s_printf("SER%d: ERROR: TX overrun\n", num);
    /* no indication bit for this??? */
    return;
  }

  rtrn = serial_write(num, &val, 1);
  if (rtrn != 1) {				/* Did transmit fail? */
    s_printf("SER%d: write failed! %s\n", num, strerror(errno)); 		/* Set overflow flag */
  } else {
    com[num].LSR &= ~(UART_LSR_THRE | UART_LSR_TEMT);		/* THR full */
    com[num].tx_cnt++;
  }

  transmit_engine(num);
}


/*************************************************************************/
/*            Miscallenous UART REGISTER handling functions              */
/*************************************************************************/

/* This function handles writes to the FCR (FIFO Control Register).
 * [num = port, val = new value to write to FCR]
 */
static void
put_fcr(int num, int val)
{
  val &= 0xcf;				/* bits 4,5 are reserved. */
  /* Bits 1-6 are only programmed when bit 0 is set (FIFO enabled) */
  if (val & UART_FCR_ENABLE_FIFO) {
    /* fifos are reset when we change from 16450 to 16550 mode.*/
    if (!(com[num].FCReg & UART_FCR_ENABLE_FIFO))
      uart_clear_fifo(num, UART_FCR_CLEAR_CMD);
    else if (val & UART_FCR_CLEAR_CMD)
      /* Commands to reset either of the two fifos.  The clear-FIFO bits
       * are disposed right after the FIFO's are cleared, and are not saved.
       */
      uart_clear_fifo(num, val & UART_FCR_CLEAR_CMD);

    /* Various flags to indicate that fifos are enabled. */
    com[num].FCReg = (val & ~UART_FCR_TRIGGER_14) | UART_FCR_ENABLE_FIFO;
    com[num].IIR.flg.fifo_enable = IIR_FIFO_ENABLE;

    /* Don't need to emulate RXRDY,TXRDY pins, used for bus-mastering. */
    if (val & UART_FCR_DMA_SELECT) {
      if(s1_printf) s_printf("SER%d: FCR: Attempt to change RXRDY & TXRDY pin modes\n",num);
    }

    /* Assign special variable for trigger value for easy access. */
    switch (val & UART_FCR_TRIGGER_14) {
    case UART_FCR_TRIGGER_1:   com[num].rx_fifo_trigger = 1;	break;
    case UART_FCR_TRIGGER_4:   com[num].rx_fifo_trigger = 4;	break;
    case UART_FCR_TRIGGER_8:   com[num].rx_fifo_trigger = 8;	break;
    case UART_FCR_TRIGGER_14:  com[num].rx_fifo_trigger = 14;	break;
    }
    if(s2_printf) s_printf("SER%d: FCR: Trigger Level is %d\n",num,com[num].rx_fifo_trigger);
  }
  else {				/* FIFO are disabled */
    /* If uart was set in 16550 mode, this will reset it back to 16450 mode.
     * If it already was in 16450 mode, there is no harm done.
     */
    com[num].IIR.flg.fifo_enable = 0;		/* Disabled FIFO flag */
    com[num].FCReg &= (~UART_FCR_ENABLE_FIFO);	/* Flag FIFO as disabled */
    uart_clear_fifo(num, UART_FCR_CLEAR_CMD);	/* Clear FIFO */
  }
}


/* This function handles writes to the LCR (Line Control Register).
 * [num = port, val = new value to write to LCR]
 */
static void
put_lcr(int num, int val)
{
  int changed = com[num].LCR ^ val;    /* bitmask of changed bits */

  com[num].LCR = val;                  /* Set new LCR value */

  if (val & UART_LCR_DLAB) {		/* Is Baudrate Divisor Latch set? */
    s_printf("SER%d: LCR = 0x%x, DLAB high.\n", num, val);
  }
  else {
    s_printf("SER%d: LCR = 0x%x, DLAB low.\n", num, val);
  }

  if (changed & UART_LCR_SBC)
    serial_brkctl(num, !!(val & UART_LCR_SBC));

  /* obviously the writes to LCR (except BREAK state) would
   * invalidate the rx fifo. We clear tx too. */
  if ((changed & ~UART_LCR_SBC) && !DLAB(num)) {
    uart_clear_fifo(num, UART_FCR_CLEAR_CMD);
    ser_termios(num);			/* Sets new line settings */
  }
}


/* This function handles writes to the MCR (Modem Control Register).
 * [num = port, val = new value to write to MCR]
 */
static void
put_mcr(int num, int val)
{
  int newmsr, delta;
  int changed;
  changed = com[num].MCR ^ val;			/* Bitmask of changed bits */
  com[num].MCR = val & UART_MCR_VALID;		/* Set valid bits for MCR */

  if (val & UART_MCR_LOOP) {		/* Is Loopback Mode set? */
    /* If loopback just enabled, clear FIFO and turn off DTR & RTS on line */
    if (changed & UART_MCR_LOOP) {		/* Was loopback just set? */
      if (FIFO_ENABLED(num))
        uart_clear_fifo(num,UART_FCR_CLEAR_CMD);	/* Clear FIFO's */
      serial_rts(num, 0);
      serial_dtr(num, 0);
    }

    /* During a UART Loopback test these bits are, Write(Out) => Read(In)
     * MCR Bit 1 RTS => MSR Bit 4 CTS,	MCR Bit 2 OUT1 => MSR Bit 6 RI
     * MCR Bit 0 DTR => MSR Bit 5 DSR,	MCR Bit 3 OUT2 => MSR Bit 7 DCD
     */
    newmsr  = convert_bit(val, UART_MCR_RTS, UART_MSR_CTS);
    newmsr |= convert_bit(val, UART_MCR_DTR, UART_MSR_DSR);
    newmsr |= convert_bit(val, UART_MCR_OUT1, UART_MSR_RI);
    newmsr |= convert_bit(val, UART_MCR_OUT2, UART_MSR_DCD);

    /* Compute delta bits of MSR */
    delta = msr_compute_delta_bits(com[num].MSR, newmsr);

    /* Update the MSR and its delta bits, not erasing old delta bits!! */
    com[num].MSR = (com[num].MSR & UART_MSR_DELTA) | delta | newmsr;

    /* Set the MSI interrupt flag if loopback changed the modem status */
    if(s3_printf) s_printf("SER%d: Func put_mcr loopback requesting MS_INTR\n",num);
    if (delta) serial_int_engine(num, MS_INTR);    /* Update interrupt status */

  }
  else {				/* It's not in Loopback Mode */
    /* Was loopback mode just turned off now?  Then reset FIFO */
    if (changed & UART_MCR_LOOP) {
      if (FIFO_ENABLED(num))
        uart_clear_fifo(num,UART_FCR_CLEAR_CMD);
    }

    /* Set interrupt enable flag according to OUT2 bit in MCR */
    if (INT_ENAB(num)) {
      if(s3_printf) s_printf("SER%d: Update interrupt status after MCR update\n",num);
    }

    /* Force RTS & DTR reinitialization if the loopback state has changed */
    if (UART_MCR_LOOP) changed |= UART_MCR_RTS | UART_MCR_DTR;

    /* Update DTR setting on serial device only if DTR state changed */
    if (changed & UART_MCR_DTR) {
      if(s1_printf) s_printf("SER%d: MCR: DTR -> %d\n",num,(val & UART_MCR_DTR));
      serial_dtr(num, !!(val & UART_MCR_DTR));
    }

    /* Update RTS setting on serial device only if RTS state changed */
    if ((changed & UART_MCR_RTS) && !com_cfg[num].system_rtscts) {
      if(s1_printf) s_printf("SER%d: MCR: RTS -> %d\n",num,(val & UART_MCR_RTS));
      serial_rts(num, !!(val & UART_MCR_RTS));
    }
  }
}


/* This function handles writes to the LSR (Line Status Register).
 * [num = port, val = new value to write to LSR]
 */
static void
put_lsr(int num, int val)
{
  int int_type = 0;

  com[num].LSR = val & 0x1F;			/* Bits 6,7,8 are ignored */

  if (val & UART_LSR_ERR)                       /* Any error bits set? */
    int_type |= LS_INTR;			/* Flag RLSI interrupt */
  else
    clear_int_cond(num, LS_INTR);         /* Unflag RLSI condition */

  if (val & UART_LSR_DR)                        /* Is data ready bit set? */
    int_type |= RX_INTR;			/* Flag RDI interrupt */
  else
    clear_int_cond(num, RX_INTR);         /* Unflag RDI condition */

  if (val & UART_LSR_THRE) {
    com[num].LSR |= UART_LSR_TEMT | UART_LSR_THRE;       /* Set THRE state */
    int_type |= TX_INTR;			/* Flag TX interrupt */
  }
  else {
    com[num].LSR &= ~(UART_LSR_THRE | UART_LSR_TEMT);   /* Clear THRE state */
    clear_int_cond(num, TX_INTR);         /* Unflag THRI condition */
  }

  if (int_type) {
    /* Update interrupt status */
    if(s3_printf) s_printf("SER%d: Func put_lsr caused int_type = %d\n",num,int_type);
    serial_int_engine(num, int_type);
  }
  /* need to sync back DR */
  receive_engine(num, 0);
}


/* This function handles writes to the MSR (Modem Status Register).
 * [num = port, val = new value to write to MSR]
 */
static inline void
put_msr(int num, int val)
{
  /* Update MSR register */
  com[num].MSR = (com[num].MSR & UART_MSR_STAT) | (val & UART_MSR_DELTA);

  /* Update interrupt status */
  if (com[num].MSR & UART_MSR_DELTA) {
    if(s3_printf) s_printf("SER%d: Func put_msr requesting MS_INTR\n",num);
    serial_int_engine(num, MS_INTR);
  }
}


/*************************************************************************/
/*            PORT I/O to UART registers: INPUT AND OUTPUT               */
/*************************************************************************/

/* DANG_BEGIN_FUNCTION do_serial_in
 * The following function returns a value from an I/O port.  The port
 * is an I/O address such as 0x3F8 (the base port address of COM1).
 * There are 8 I/O addresses for each serial port which ranges from
 * the base port (ie 0x3F8) to the base port plus seven (ie 0x3FF).
 * [num = abritary port number for serial line, address = I/O port address]
 * DANG_END_FUNCTION
 */
int
do_serial_in(int num, ioport_t address)
{
  int val;

  /* delayed open happens here */
  if (!com[num].opened)
    com[num].opened = ser_open(num);
  if (com[num].opened <= 0)
    return 0;

  switch (address - com_cfg[num].base_port) {
  case UART_RX:		/* Read from Received Byte Register */
/*case UART_DLL:*/      /* or Read from Baudrate Divisor Latch LSB */
    if (DLAB(num)) {	/* Is DLAB set? */
      val = com[num].dll;	/* Then read Divisor Latch LSB */
      if(s1_printf) s_printf("SER%d: Read Divisor LSB = 0x%x\n",num,val);
    }
    else {
      val = get_rx(num);	/* Else, read Received Byte Register */
      if(s2_printf) s_printf("SER%d: Receive 0x%x\n",num,val);
    }
    break;

  case UART_IER:	/* Read from Interrupt Enable Register */
/*case UART_DLM:*/      /* or Read from Baudrate Divisor Latch MSB */
    if (DLAB(num)) {	/* Is DLAB set? */
      val = com[num].dlm;	/* Then read Divisor Latch MSB */
      if(s1_printf) s_printf("SER%d: Read Divisor MSB = 0x%x\n",num,val);
    }
    else {
      val = com[num].IER;	/* Else, read Interrupt Enable Register */
      if(s1_printf) s_printf("SER%d: Read IER = 0x%x\n",num,val);
    }
    break;

  case UART_IIR:	/* Read from Interrupt Identification Register */
    if (!com[num].IIR.mask)
      recalc_IIR(num);
    val = get_IIR_val(num);
    if (val & UART_IIR_THRI) {
      if(s2_printf) s_printf("SER%d: Read IIR = 0x%x (THRI now cleared)\n",num,val);
      clear_int_cond(num, TX_INTR);	/* Unflag TX int condition */
    }
    else {
      if(s2_printf) s_printf("SER%d: Read IIR = 0x%x\n",num,val);
    }
    break;

  case UART_LCR:	/* Read from Line Control Register */
    val = com[num].LCR;
    if(s2_printf) s_printf("SER%d: Read LCR = 0x%x\n",num,val);
    break;

  case UART_MCR:	/* Read from Modem Control Register */
    val = com[num].MCR;
    if(s2_printf) s_printf("SER%d: Read MCR = 0x%x\n",num,val);
    break;

  case UART_LSR:	/* Read from Line Status Register */
    val = get_lsr(num);
    if(s2_printf) s_printf("SER%d: Read LSR = 0x%x\n",num,val);
    break;

  case UART_MSR:	/* Read from Modem Status Register */
    val = get_msr(num);
    if(s2_printf) s_printf("SER%d: Read MSR = 0x%x\n",num,val);
    break;

  case UART_SCR:   	/* Read from Scratch Register */
    val = com[num].SCR;
    if(s2_printf) s_printf("SER%d: Read SCR = 0x%x\n",num,val);
    break;

  default:		/* The following code should never execute. */
    s_printf("ERROR: Port read 0x%x out of bounds for serial port %d\n",
    	address,num);
    val = 0;
    break;
  }

  return val;
}


/* DANG_BEGIN_FUNCTION do_serial_out
 * The following function writes a value to an I/O port.  The port
 * is an I/O address such as 0x3F8 (the base port address of COM1).
 * [num = abritary port number for serial line, address = I/O port address,
 * val = value to write to I/O port address]
 * DANG_END_FUNCTION
 */
int
do_serial_out(int num, ioport_t address, int val)
{

  /* delayed open happens here */
  if (!com[num].opened)
    com[num].opened = ser_open(num);
  if (com[num].opened <= 0)
    return 0;

  switch (address - com_cfg[num].base_port) {
  case UART_TX:		/* Write to Transmit Holding Register */
/*case UART_DLL:*/	/* or write to Baudrate Divisor Latch LSB */
    if (DLAB(num)) {	/* If DLAB set, */
      com[num].dll = val;	/* then write to Divisor Latch LSB */
      if(s2_printf) s_printf("SER%d: Divisor LSB = 0x%02x\n", num, val);
    }
    else {
      if (s2_printf) {
        if (com[num].MCR & UART_MCR_LOOP)
          s_printf("SER%d: Transmit 0x%x Loopback\n",num,val);
        else
          s_printf("SER%d: Transmit 0x%x\n",num,val);
      }
      put_tx(num, val);		/* else, Transmit character (write to THR) */
    }
    break;

  case UART_IER:	/* Write to Interrupt Enable Register */
/*case UART_DLM:*/	/* or write to Baudrate Divisor Latch MSB */
    if (DLAB(num)) {	/* If DLAB set, */
      com[num].dlm = val;	/* then write to Divisor Latch MSB */
      if(s2_printf) s_printf("SER%d: Divisor MSB = 0x%x\n", num, val);
    }
    else {			/* Else, write to Interrupt Enable Register */
      int tflg = 0;
      if ( !(com[num].IER & UART_IER_THRI) && (val & UART_IER_THRI) ) {
        /* Flag to allow THRI if enable THRE went from state 0 -> 1 */
        tflg = 1;
        com[num].int_condition |= TX_INTR;	// is this needed?
      }
      com[num].IER = (val & UART_IER_VALID);	/* Write to IER */
      if(s1_printf) s_printf("SER%d: Write IER = 0x%x\n", num, val);
      if (tflg)
        serial_int_engine(num, 0);
    }
    break;

  case UART_FCR:	/* Write to FIFO Control Register */
    if(s1_printf) s_printf("SER%d: FCR = 0x%x -> 0x%x\n", num,
        com[num].FCReg, val);
    put_fcr(num, val);
    break;

  case UART_LCR: 	/* Write to Line Control Register */
    /* The debug message is in the ser_termios routine via put_lcr */
    put_lcr(num, val);
    break;

  case UART_MCR:	/* Write to Modem Control Register */
    if(s1_printf) s_printf("SER%d: MCR = 0x%x -> 0x%x\n", num,
        com[num].MCR, val);
    put_mcr(num, val);
    break;

  case UART_LSR:	/* Write to Line Status Register */
    if(s1_printf) s_printf("SER%d: LSR = 0x%x -> 0x%x\n", num,
        com[num].LSR, val);
    put_lsr(num, val);		/* writeable only to lower 6 bits */
    break;

  case UART_MSR:	/* Write to Modem Status Register */
    if(s1_printf) s_printf("SER%d: MSR = 0x%x -> 0x%x\n", num,
        com[num].MSR, val);
    put_msr(num, val);		/* writeable only to lower 4 bits */
    break;

  case UART_SCR:	/* Write to Scratch Register */
    if(s1_printf) s_printf("SER%d: SCR = 0x%x -> 0x%x\n", num,
        com[num].SCR, val);
    com[num].SCR = val;
    break;

  default:		/* The following should never execute */
    s_printf("ERROR: Port write 0x%x out of bounds for serial port %d\n",
             address,num);
    break;
  }

  serial_int_engine(num, 0);		/* Update interrupt status */

  return 0;
}