/*
 *  console.c  -- console I/O package
 *
 *  Copyright (C) 1999 Eric Valette. valette@crf.canon.fr
 *
 * This code is based on the pc386 BSP console.c so the following
 * copyright also applies :
 *
 * (C) Copyright 1997 -
 * - NavIST Group - Real-Time Distributed Systems and Industrial Automation
 *
 * Till Straumann, <strauman@slac.stanford.edu>, 12/20/2001
 * separate BSP specific stuff from generics...
 *
 * http://pandora.ist.utl.pt
 *
 * Instituto Superior Tecnico * Lisboa * PORTUGAL
 *  The license and distribution terms for this file may be
 *  found in the file LICENSE in this distribution or at
 *  http://www.rtems.org/license/LICENSE.
 */

#include <stdlib.h>
#include <assert.h>
#include <inttypes.h>

#include <bsp.h>
#include <bsp/irq.h>
#include <rtems/bspIo.h>
#include <rtems/libio.h>
#include <rtems/console.h>
#include <rtems/termiostypes.h>
#include <termios.h>
#include <bsp/uart.h>
#include <rtems/bspIo.h>  /* printk */

/* Definitions for BSPConsolePort */
/*
 * Possible value for console input/output :
 *    BSP_CONSOLE_PORT_CONSOLE
 *    BSP_UART_COM1
 *    BSP_UART_COM2
 */
int BSPConsolePort = BSP_CONSOLE_PORT;

int BSPBaseBaud    = BSP_UART_BAUD_BASE;

/*
 * TERMIOS_OUTPUT_MODE should be a 'bspopts.h/configure'-able option;
 * we could even make it a link-time option (but that would require
 * small changes)...
 */
#if defined(USE_POLLED_IO)
  #define TERMIOS_OUTPUT_MODE TERMIOS_POLLED
#elif defined(USE_TASK_DRIVEN_IO)
  #define TERMIOS_OUTPUT_MODE TERMIOS_TASK_DRIVEN
#else
  #define TERMIOS_OUTPUT_MODE TERMIOS_IRQ_DRIVEN
#endif

/*-------------------------------------------------------------------------+
| External Prototypes
+--------------------------------------------------------------------------*/

static int  conSetAttr(int minor, const struct termios *);

typedef struct TtySTblRec_ {
  char          *name;
  rtems_irq_hdl  isr;
} TtySTblRec, *TtySTbl;

static TtySTblRec ttyS[]={
    { "/dev/ttyS0",
#ifdef BSP_UART_IOBASE_COM1
      BSP_uart_termios_isr_com1
#else
      0
#endif
    },
    { "/dev/ttyS1",
#ifdef BSP_UART_IOBASE_COM2
      BSP_uart_termios_isr_com2
#else
      0
#endif
    },
};

/*-------------------------------------------------------------------------+
| Console device driver INITIALIZE entry point.
+--------------------------------------------------------------------------+
| Initilizes the I/O console (keyboard + VGA display) driver.
+--------------------------------------------------------------------------*/
rtems_device_driver console_initialize(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                      *arg
)
{
  rtems_status_code status;

  /*
   *  The video was initialized in the start.s code and does not need
   *  to be reinitialized.
   */

  /*
   * Set up TERMIOS
   */
  rtems_termios_initialize();

  /*
   * Do device-specific initialization
   */

  /* RTEMS calls this routine once with 'minor'==0; loop through
   * all known instances...
   */

  for (minor=0; minor < sizeof(ttyS)/sizeof(ttyS[0]); minor++) {
    char *nm;
    /*
     * Skip ports (possibly not supported by BSP...) we have no ISR for
     */
    if ( ! ttyS[minor].isr )
      continue;
    /*
     * Register the device
     */
    status = rtems_io_register_name ((nm=ttyS[minor].name), major, minor);
    if ( RTEMS_SUCCESSFUL==status && BSPConsolePort == minor) {
      printk("Registering /dev/console as minor %" PRIu32 " (==%s)\n",
              minor,
              ttyS[minor].name);
      /* also register an alias */
      status = rtems_io_register_name ( (nm="/dev/console"), major, minor);
    }

    if (status != RTEMS_SUCCESSFUL) {
      printk("Error registering %s!\n",nm);
      rtems_fatal_error_occurred (status);
    }
  }

  return RTEMS_SUCCESSFUL;
} /* console_initialize */

#if !defined(USE_POLLED_IO)
static int console_first_open(int major, int minor, void *arg)
{
  rtems_status_code status;

  /* must not open a minor device we have no ISR for */
  assert( minor>=0 && minor < sizeof(ttyS)/sizeof(ttyS[0]) && ttyS[minor].isr );

  /* 9600-8-N-1 */
  BSP_uart_init(minor, 9600, 0);
  status = BSP_uart_install_isr(minor, ttyS[minor].isr);
  if (!status) {
    printk("Error installing serial console interrupt handler for '%s'!\n",
      ttyS[minor].name);
    rtems_fatal_error_occurred(status);
  }

  /*
   * Pass data area info down to driver
   */
  BSP_uart_termios_set(minor, ((rtems_libio_open_close_args_t *)arg)->iop->data1);

  /* Enable interrupts  on channel */
  BSP_uart_intr_ctrl(minor, BSP_UART_INTR_CTRL_TERMIOS);

  return 0;
}
#endif

#if !defined(USE_POLLED_IO)
static int console_last_close(int major, int minor, void *arg)
{
  BSP_uart_remove_isr(minor, ttyS[minor].isr);
  return 0;
}
#endif

/*-------------------------------------------------------------------------+
| Console device driver OPEN entry point
+--------------------------------------------------------------------------*/
rtems_device_driver console_open(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                      *arg
)
{
  rtems_status_code              status;
  static rtems_termios_callbacks cb =
#if defined(USE_POLLED_IO)
  {
     NULL,                              /* firstOpen */
     NULL,                              /* lastClose */
     NULL,                              /* pollRead */
     BSP_uart_termios_write_polled,     /* write */
     conSetAttr,                        /* setAttributes */
     NULL,                              /* stopRemoteTx */
     NULL,                              /* startRemoteTx */
     TERMIOS_POLLED                     /* outputUsesInterrupts */
  };
#else
  {
     console_first_open,                /* firstOpen */
     console_last_close,                /* lastClose */
#ifdef USE_TASK_DRIVEN_IO
     BSP_uart_termios_read_com,         /* pollRead */
#else
     NULL,                              /* pollRead */
#endif
     BSP_uart_termios_write_com,        /* write */
     conSetAttr,                        /* setAttributes */
     NULL,                              /* stopRemoteTx */
     NULL,                              /* startRemoteTx */
     TERMIOS_OUTPUT_MODE                /* outputUsesInterrupts */
  };
#endif

  status = rtems_termios_open (major, minor, arg, &cb);

  if (status != RTEMS_SUCCESSFUL) {
    printk("Error opening console device\n");
    return status;
  }

  return RTEMS_SUCCESSFUL;
}

/*-------------------------------------------------------------------------+
| Console device driver CLOSE entry point
+--------------------------------------------------------------------------*/
rtems_device_driver
console_close(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                      *arg
)
{
  rtems_device_driver res = RTEMS_SUCCESSFUL;

  res =  rtems_termios_close (arg);

  return res;
} /* console_close */

/*-------------------------------------------------------------------------+
| Console device driver READ entry point.
+--------------------------------------------------------------------------+
| Read characters from the I/O console. We only have stdin.
+--------------------------------------------------------------------------*/
rtems_device_driver console_read(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                      *arg
)
{
  return rtems_termios_read (arg);
} /* console_read */

/*-------------------------------------------------------------------------+
| Console device driver WRITE entry point.
+--------------------------------------------------------------------------+
| Write characters to the I/O console. Stderr and stdout are the same.
+--------------------------------------------------------------------------*/
rtems_device_driver console_write(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                      *arg
)
{
  return rtems_termios_write (arg);
} /* console_write */

/*
 * Handle ioctl request.
 */
rtems_device_driver console_control(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void                      *arg
)
{
/* does the BSP support break callbacks ? */
#if defined(BIOCSETBREAKCB) && defined(BIOCGETBREAKCB)
  rtems_libio_ioctl_args_t  *ioa=arg;
  switch (ioa->command) {
    case BIOCSETBREAKCB: return BSP_uart_set_break_cb(minor, ioa);
    case BIOCGETBREAKCB: return BSP_uart_get_break_cb(minor, ioa);
    default:             break;
  }
#endif
  return rtems_termios_ioctl (arg);
}

static int conSetAttr(
  int                   minor,
  const struct termios *t
)
{
  rtems_termios_baud_t baud;

  baud = rtems_termios_baud_to_number(t->c_ospeed);
  if ( baud > 115200 )
    rtems_fatal_error_occurred (RTEMS_INTERNAL_ERROR);

  BSP_uart_set_baud(minor, baud);

  return 0;
}
