/*  dv-m68hc11sio.c -- Simulation of the 68HC11 serial device.
    Copyright (C) 1999-2024 Free Software Foundation, Inc.
    Written by Stephane Carrez (stcarrez@worldnet.fr)
    (From a driver model Contributed by Cygnus Solutions.)

    This file is part of the program GDB, the GNU debugger.
    
    This program 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 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
    */

/* This must come before any other includes.  */
#include "defs.h"

#include "sim-main.h"
#include "hw-main.h"
#include "dv-sockser.h"
#include "sim-assert.h"

#include "m68hc11-sim.h"

/* DEVICE

        m68hc11sio - m68hc11 serial I/O

   
   DESCRIPTION

        Implements the m68hc11 serial I/O controller described in the m68hc11
        user guide. The serial I/O controller is directly connected to the CPU
        interrupt. The simulator implements:

            - baud rate emulation
            - 8-bits transfers
    
   PROPERTIES

   backend {tcp | stdio}

        Use dv-sockser TCP-port backend or stdio for backend.  Default: stdio.

   
   PORTS

   reset (input)

        Reset port. This port is only used to simulate a reset of the serial
        I/O controller. It should be connected to the RESET output of the cpu.

   */



/* port ID's */

enum
{
  RESET_PORT
};


static const struct hw_port_descriptor m68hc11sio_ports[] = 
{
  { "reset", RESET_PORT, 0, input_port, },
  { NULL, },
};


/* Serial Controller information.  */
struct m68hc11sio 
{
  enum {sio_tcp, sio_stdio} backend; /* backend */

  /* Number of cpu cycles to send a bit on the wire.  */
  unsigned long baud_cycle;

  /* Length in bits of characters sent, this includes the
     start/stop and parity bits.  Together with baud_cycle, this
     is used to find the number of cpu cycles to send/receive a data.  */
  unsigned int  data_length;

  /* Information about next character to be transmited.  */
  unsigned char tx_has_char;
  unsigned char tx_char;

  unsigned char rx_char;
  unsigned char rx_clear_scsr;
  
  /* Periodic I/O polling.  */
  struct hw_event* tx_poll_event;
  struct hw_event* rx_poll_event;
};



/* Finish off the partially created hw device.  Attach our local
   callbacks.  Wire up our port names etc.  */

static hw_io_read_buffer_method m68hc11sio_io_read_buffer;
static hw_io_write_buffer_method m68hc11sio_io_write_buffer;
static hw_port_event_method m68hc11sio_port_event;
static hw_ioctl_method m68hc11sio_ioctl;

#define M6811_SCI_FIRST_REG (M6811_BAUD)
#define M6811_SCI_LAST_REG  (M6811_SCDR)


static void
attach_m68hc11sio_regs (struct hw *me,
                        struct m68hc11sio *controller)
{
  hw_attach_address (hw_parent (me), M6811_IO_LEVEL, io_map,
                     M6811_SCI_FIRST_REG,
                     M6811_SCI_LAST_REG - M6811_SCI_FIRST_REG + 1,
		     me);

  if (hw_find_property(me, "backend") != NULL)
    {
      const char *value = hw_find_string_property(me, "backend");
      if(! strcmp(value, "tcp"))
	controller->backend = sio_tcp;
      else if(! strcmp(value, "stdio"))
	controller->backend = sio_stdio;
      else
	hw_abort (me, "illegal value for backend parameter `%s':"
                  "use tcp or stdio", value);
    }
}


static void
m68hc11sio_finish (struct hw *me)
{
  struct m68hc11sio *controller;

  controller = HW_ZALLOC (me, struct m68hc11sio);
  set_hw_data (me, controller);
  set_hw_io_read_buffer (me, m68hc11sio_io_read_buffer);
  set_hw_io_write_buffer (me, m68hc11sio_io_write_buffer);
  set_hw_ports (me, m68hc11sio_ports);
  set_hw_port_event (me, m68hc11sio_port_event);
#ifdef set_hw_ioctl
  set_hw_ioctl (me, m68hc11sio_ioctl);
#else
  me->to_ioctl = m68hc11sio_ioctl;
#endif

  /* Preset defaults.  */
  controller->backend = sio_stdio;

  /* Attach ourself to our parent bus.  */
  attach_m68hc11sio_regs (me, controller);

  /* Initialize to reset state.  */
  controller->tx_poll_event = NULL;
  controller->rx_poll_event = NULL;
  controller->tx_char       = 0;
  controller->tx_has_char   = 0;
  controller->rx_clear_scsr = 0;
  controller->rx_char       = 0;
}



/* An event arrives on an interrupt port.  */

static void
m68hc11sio_port_event (struct hw *me,
                       int my_port,
                       struct hw *source,
                       int source_port,
                       int level)
{
  SIM_DESC sd;
  struct m68hc11sio *controller;
  sim_cpu *cpu;
  struct m68hc11_sim_cpu *m68hc11_cpu;
  uint8_t val;
  
  controller = hw_data (me);
  sd         = hw_system (me);
  cpu        = STATE_CPU (sd, 0);  
  m68hc11_cpu  = M68HC11_SIM_CPU (cpu);
  switch (my_port)
    {
    case RESET_PORT:
      {
	HW_TRACE ((me, "SCI reset"));

        /* Reset the state of SCI registers.  */
        val = 0;
        m68hc11sio_io_write_buffer (me, &val, io_map,
                                    (unsigned_word) M6811_BAUD, 1);
        m68hc11sio_io_write_buffer (me, &val, io_map,
                                    (unsigned_word) M6811_SCCR1, 1);
        m68hc11sio_io_write_buffer (me, &val, io_map,
                                    (unsigned_word) M6811_SCCR2, 1);
        
        m68hc11_cpu->ios[M6811_SCSR]    = M6811_TC | M6811_TDRE;
        controller->rx_char     = 0;
        controller->tx_char     = 0;
        controller->tx_has_char = 0;
        controller->rx_clear_scsr = 0;
        if (controller->rx_poll_event)
          {
            hw_event_queue_deschedule (me, controller->rx_poll_event);
            controller->rx_poll_event = 0;
          }
        if (controller->tx_poll_event)
          {
            hw_event_queue_deschedule (me, controller->tx_poll_event);
            controller->tx_poll_event = 0;
          }

        /* In bootstrap mode, initialize the SCI to 1200 bauds to
           simulate some initial setup by the internal rom.  */
        if (((m68hc11_cpu->ios[M6811_HPRIO]) & (M6811_SMOD | M6811_MDA)) == M6811_SMOD)
          {
            val = 0x33;
            
            m68hc11sio_io_write_buffer (me, &val, io_map,
                                        (unsigned_word) M6811_BAUD, 1);
            val = 0x12;
            m68hc11sio_io_write_buffer (me, &val, io_map,
                                        (unsigned_word) M6811_SCCR2, 1);
          }
        break;
      }

    default:
      hw_abort (me, "Event on unknown port %d", my_port);
      break;
    }
}


static void
m68hc11sio_rx_poll (struct hw *me, void *data)
{
  SIM_DESC sd;
  struct m68hc11sio *controller;
  sim_cpu *cpu;
  struct m68hc11_sim_cpu *m68hc11_cpu;
  char cc;
  int cnt;
  int check_interrupt = 0;
  
  controller = hw_data (me);
  sd         = hw_system (me);
  cpu        = STATE_CPU (sd, 0);
  m68hc11_cpu  = M68HC11_SIM_CPU (cpu);
  switch (controller->backend)
    {
    case sio_tcp:
      cnt = dv_sockser_read (sd);
      if (cnt != -1)
        {
          cc = (char) cnt;
          cnt = 1;
        }
      break;

    case sio_stdio:
      cnt = sim_io_poll_read (sd, 0 /* stdin */, &cc, 1);
      break;

    default:
      cnt = 0;
      break;
    }

  if (cnt == 1)
    {
      /* Raise the overrun flag if the previous character was not read.  */
      if (m68hc11_cpu->ios[M6811_SCSR] & M6811_RDRF)
        m68hc11_cpu->ios[M6811_SCSR] |= M6811_OR;

      m68hc11_cpu->ios[M6811_SCSR]     |= M6811_RDRF;
      controller->rx_char       = cc;
      controller->rx_clear_scsr = 0;
      check_interrupt = 1;
    }
  else
    {
      /* handle idle line detect here.  */
      ;
    }

  if (controller->rx_poll_event)
    {
      hw_event_queue_deschedule (me, controller->rx_poll_event);
      controller->rx_poll_event = 0;
    }

  if (m68hc11_cpu->ios[M6811_SCCR2] & M6811_RE)
    {
      unsigned long clock_cycle;

      /* Compute CPU clock cycles to wait for the next character.  */
      clock_cycle = controller->data_length * controller->baud_cycle;

      controller->rx_poll_event = hw_event_queue_schedule (me, clock_cycle,
                                                           m68hc11sio_rx_poll,
                                                           NULL);
    }

  if (check_interrupt)
      interrupts_update_pending (&m68hc11_cpu->cpu_interrupts);
}


static void
m68hc11sio_tx_poll (struct hw *me, void *data)
{
  SIM_DESC sd;
  struct m68hc11sio *controller;
  sim_cpu *cpu;
  struct m68hc11_sim_cpu *m68hc11_cpu;
  
  controller = hw_data (me);
  sd         = hw_system (me);
  cpu        = STATE_CPU (sd, 0);
  m68hc11_cpu  = M68HC11_SIM_CPU (cpu);

  m68hc11_cpu->ios[M6811_SCSR] |= M6811_TDRE;
  m68hc11_cpu->ios[M6811_SCSR] |= M6811_TC;
  
  /* Transmitter is enabled and we have something to send.  */
  if ((m68hc11_cpu->ios[M6811_SCCR2] & M6811_TE) && controller->tx_has_char)
    {
      m68hc11_cpu->ios[M6811_SCSR] &= ~M6811_TDRE;
      m68hc11_cpu->ios[M6811_SCSR] &= ~M6811_TC;
      controller->tx_has_char = 0;
      switch (controller->backend)
        {
        case sio_tcp:
          dv_sockser_write (sd, controller->tx_char);
          break;

        case sio_stdio:
          sim_io_write_stdout (sd, (const char *)&controller->tx_char, 1);
          sim_io_flush_stdout (sd);
          break;

        default:
          break;
        }
    }

  if (controller->tx_poll_event)
    {
      hw_event_queue_deschedule (me, controller->tx_poll_event);
      controller->tx_poll_event = 0;
    }
  
  if ((m68hc11_cpu->ios[M6811_SCCR2] & M6811_TE)
      && ((m68hc11_cpu->ios[M6811_SCSR] & M6811_TC) == 0))
    {
      unsigned long clock_cycle;
      
      /* Compute CPU clock cycles to wait for the next character.  */
      clock_cycle = controller->data_length * controller->baud_cycle;

      controller->tx_poll_event = hw_event_queue_schedule (me, clock_cycle,
                                                           m68hc11sio_tx_poll,
                                                           NULL);
    }

  interrupts_update_pending (&m68hc11_cpu->cpu_interrupts);
}

/* Descriptions of the SIO I/O ports.  These descriptions are only used to
   give information of the SIO device under GDB.  */
io_reg_desc sccr2_desc[] = {
  { M6811_TIE,   "TIE  ", "Transmit Interrupt Enable" },
  { M6811_TCIE,  "TCIE ", "Transmit Complete Interrupt Enable" },
  { M6811_RIE,   "RIE  ", "Receive Interrupt Enable" },
  { M6811_ILIE,  "ILIE ", "Idle Line Interrupt Enable" },
  { M6811_TE,    "TE   ", "Transmit Enable" },
  { M6811_RE,    "RE   ", "Receive Enable" },
  { M6811_RWU,   "RWU  ", "Receiver Wake Up" },
  { M6811_SBK,   "SBRK ", "Send Break" },
  { 0,  0, 0 }
};

io_reg_desc sccr1_desc[] = {
  { M6811_R8,    "R8   ", "Receive Data bit 8" },
  { M6811_T8,    "T8   ", "Transmit Data bit 8" },
  { M6811_M,     "M    ", "SCI Character length (0=8-bits, 1=9-bits)" },
  { M6811_WAKE,  "WAKE ", "Wake up method select (0=idle, 1=addr mark" },
  { 0,  0, 0 }
};

io_reg_desc scsr_desc[] = {
  { M6811_TDRE,  "TDRE ", "Transmit Data Register Empty" },
  { M6811_TC,    "TC   ", "Transmit Complete" },
  { M6811_RDRF,  "RDRF ", "Receive Data Register Full" },
  { M6811_IDLE,  "IDLE ", "Idle Line Detect" },
  { M6811_OR,    "OR   ", "Overrun Error" },
  { M6811_NF,    "NF   ", "Noise Flag" },
  { M6811_FE,    "FE   ", "Framing Error" },
  { 0,  0, 0 }
};

io_reg_desc baud_desc[] = {
  { M6811_TCLR,  "TCLR ", "Clear baud rate (test mode)" },
  { M6811_SCP1,  "SCP1 ", "SCI baud rate prescaler select (SCP1)" },
  { M6811_SCP0,  "SCP0 ", "SCI baud rate prescaler select (SCP0)" },
  { M6811_RCKB,  "RCKB ", "Baur Rate Clock Check (test mode)" },
  { M6811_SCR2,  "SCR2 ", "SCI Baud rate select (SCR2)" },
  { M6811_SCR1,  "SCR1 ", "SCI Baud rate select (SCR1)" },
  { M6811_SCR0,  "SCR0 ", "SCI Baud rate select (SCR0)" },
  { 0,  0, 0 }
};

static void
m68hc11sio_info (struct hw *me)
{
  SIM_DESC sd;
  uint16_t base = 0;
  sim_cpu *cpu;
  struct m68hc11_sim_cpu *m68hc11_cpu;
  struct m68hc11sio *controller;
  uint8_t val;
  long clock_cycle;
  
  sd = hw_system (me);
  cpu = STATE_CPU (sd, 0);
  m68hc11_cpu = M68HC11_SIM_CPU (cpu);
  controller = hw_data (me);
  
  sim_io_printf (sd, "M68HC11 SIO:\n");

  base = cpu_get_io_base (cpu);

  val  = m68hc11_cpu->ios[M6811_BAUD];
  print_io_byte (sd, "BAUD ", baud_desc, val, base + M6811_BAUD);
  sim_io_printf (sd, " (%ld baud)\n",
                 (m68hc11_cpu->cpu_frequency / 4) / controller->baud_cycle);

  val = m68hc11_cpu->ios[M6811_SCCR1];
  print_io_byte (sd, "SCCR1", sccr1_desc, val, base + M6811_SCCR1);
  sim_io_printf (sd, "  (%d bits) (%dN1)\n",
                 controller->data_length, controller->data_length - 2);

  val = m68hc11_cpu->ios[M6811_SCCR2];
  print_io_byte (sd, "SCCR2", sccr2_desc, val, base + M6811_SCCR2);
  sim_io_printf (sd, "\n");

  val = m68hc11_cpu->ios[M6811_SCSR];
  print_io_byte (sd, "SCSR ", scsr_desc, val, base + M6811_SCSR);
  sim_io_printf (sd, "\n");

  clock_cycle = controller->data_length * controller->baud_cycle;
  
  if (controller->tx_poll_event)
    {
      int64_t t;
      int n;

      t = hw_event_remain_time (me, controller->tx_poll_event);
      n = (clock_cycle - t) / controller->baud_cycle;
      n = controller->data_length - n;
      sim_io_printf (sd, "  Transmit finished in %s (%d bit%s)\n",
		     cycle_to_string (cpu, t, PRINT_TIME | PRINT_CYCLE),
                     n, (n > 1 ? "s" : ""));
    }
  if (controller->rx_poll_event)
    {
      int64_t t;

      t = hw_event_remain_time (me, controller->rx_poll_event);
      sim_io_printf (sd, "  Receive finished in %s\n",
		     cycle_to_string (cpu, t, PRINT_TIME | PRINT_CYCLE));
    }
  
}

static int
m68hc11sio_ioctl (struct hw *me,
                  hw_ioctl_request request,
                  va_list ap)
{
  m68hc11sio_info (me);
  return 0;
}

/* generic read/write */

static unsigned
m68hc11sio_io_read_buffer (struct hw *me,
                           void *dest,
                           int space,
                           unsigned_word base,
                           unsigned nr_bytes)
{
  SIM_DESC sd;
  struct m68hc11sio *controller;
  sim_cpu *cpu;
  struct m68hc11_sim_cpu *m68hc11_cpu;
  uint8_t val;
  
  HW_TRACE ((me, "read 0x%08lx %d", (long) base, (int) nr_bytes));

  sd  = hw_system (me);
  cpu = STATE_CPU (sd, 0);
  m68hc11_cpu = M68HC11_SIM_CPU (cpu);
  controller = hw_data (me);

  switch (base)
    {
    case M6811_SCSR:
      controller->rx_clear_scsr = m68hc11_cpu->ios[M6811_SCSR]
        & (M6811_RDRF | M6811_IDLE | M6811_OR | M6811_NF | M6811_FE);
      ATTRIBUTE_FALLTHROUGH;
      
    case M6811_BAUD:
    case M6811_SCCR1:
    case M6811_SCCR2:
      val = m68hc11_cpu->ios[base];
      break;
      
    case M6811_SCDR:
      if (controller->rx_clear_scsr)
        {
          m68hc11_cpu->ios[M6811_SCSR] &= ~controller->rx_clear_scsr;
        }
      val = controller->rx_char;
      break;
      
    default:
      return 0;
    }
  *((uint8_t*) dest) = val;
  return 1;
}

static unsigned
m68hc11sio_io_write_buffer (struct hw *me,
                            const void *source,
                            int space,
                            unsigned_word base,
                            unsigned nr_bytes)
{
  SIM_DESC sd;
  struct m68hc11sio *controller;
  sim_cpu *cpu;
  struct m68hc11_sim_cpu *m68hc11_cpu;
  uint8_t val;

  HW_TRACE ((me, "write 0x%08lx %d", (long) base, (int) nr_bytes));

  sd  = hw_system (me);
  cpu = STATE_CPU (sd, 0);
  m68hc11_cpu = M68HC11_SIM_CPU (cpu);
  controller = hw_data (me);
  
  val = *((const uint8_t*) source);
  switch (base)
    {
    case M6811_BAUD:
      {
        long divisor;
        long baud;

        m68hc11_cpu->ios[M6811_BAUD] = val;
        switch (val & (M6811_SCP1|M6811_SCP0))
          {
          case M6811_BAUD_DIV_1:
            divisor = 1 * 16;
            break;

          case M6811_BAUD_DIV_3:
            divisor = 3 * 16;
            break;

          case M6811_BAUD_DIV_4:
            divisor = 4 * 16;
            break;

          default:
          case M6811_BAUD_DIV_13:
            divisor = 13 * 16;
            break;
          }
        val &= (M6811_SCR2|M6811_SCR1|M6811_SCR0);
        divisor *= (1 << val);

        baud = (m68hc11_cpu->cpu_frequency / 4) / divisor;

        HW_TRACE ((me, "divide rate %ld, baud rate %ld",
                   divisor, baud));

        controller->baud_cycle = divisor;
      }
      break;
      
    case M6811_SCCR1:
      {
        if (val & M6811_M)
          controller->data_length = 11;
        else
          controller->data_length = 10;

        m68hc11_cpu->ios[M6811_SCCR1] = val;
      }
      break;
      
    case M6811_SCCR2:
      if ((val & M6811_RE) == 0)
        {
          val &= ~(M6811_RDRF|M6811_IDLE|M6811_OR|M6811_NF|M6811_NF);
          val |= (m68hc11_cpu->ios[M6811_SCCR2]
                  & (M6811_RDRF|M6811_IDLE|M6811_OR|M6811_NF|M6811_NF));
          m68hc11_cpu->ios[M6811_SCCR2] = val;
          break;
        }

      /* Activate reception.  */
      if (controller->rx_poll_event == 0)
        {
          long clock_cycle;
          
          /* Compute CPU clock cycles to wait for the next character.  */
          clock_cycle = controller->data_length * controller->baud_cycle;

          controller->rx_poll_event = hw_event_queue_schedule (me, clock_cycle,
                                                               m68hc11sio_rx_poll,
                                                               NULL);
        }      
      m68hc11_cpu->ios[M6811_SCCR2] = val;
      interrupts_update_pending (&m68hc11_cpu->cpu_interrupts);
      break;

      /* No effect.  */
    case M6811_SCSR:
      return 1;
      
    case M6811_SCDR:
      if (!(m68hc11_cpu->ios[M6811_SCSR] & M6811_TDRE))
        {
          return 0;
        }

      controller->tx_char     = val;
      controller->tx_has_char = 1;
      if ((m68hc11_cpu->ios[M6811_SCCR2] & M6811_TE)
          && controller->tx_poll_event == 0)
        {
          m68hc11sio_tx_poll (me, NULL);
        }
      return 1;
      
    default:
      return 0;
    }
  return nr_bytes;
}     


const struct hw_descriptor dv_m68hc11sio_descriptor[] = {
  { "m68hc11sio", m68hc11sio_finish },
  { "m68hc12sio", m68hc11sio_finish },
  { NULL },
};

