/**
 * @file
 *
 * @brief Driver for Exar XR17D15x Multiport UARTs
 *
 * This driver supports 2, 4 or 8 port Exar parts which are NS16550
 * compatible.
 */

/*
 *  COPYRIGHT (c) 1989-2012.
 *  On-Line Applications Research Corporation (OAR).
 *
 *  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 <bsp.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>

#include <rtems/termiostypes.h>
#include <libchip/serial.h>
#include <libchip/ns16550.h>
#include <rtems/bspIo.h>
#include <rtems/pci.h>
#include <bsp/exar17d15x.h>
#include "../../shared/dev/serial/legacy-console.h"

#define MAX_BOARDS 4

/*
 *  This is the rate for the clock internal to the parts.
 */
#define EXAR_CLOCK_RATE  (921600*16)

/*
 *  Supported PCI Ids
 */
#define PCI_VENDOR_ID_EXAR 0x13A8
#define PCI_VENDOR_ID_EXAR_XR17D158 0x0158
#define PCI_VENDOR_ID_EXAR_XR17D154 0x0154
#define PCI_VENDOR_ID_EXAR_XR17D152 0x0152

/*
 * Structure to manage each instance found.
 */
typedef struct {
  uint16_t  vendor;
  uint16_t  device;
  uint8_t   ports;
} exar_parts_t;

static exar_parts_t Supported[] = {
  { PCI_VENDOR_ID_EXAR, PCI_VENDOR_ID_EXAR_XR17D158, 8 },
  { PCI_VENDOR_ID_EXAR, PCI_VENDOR_ID_EXAR_XR17D154, 4 },
  { PCI_VENDOR_ID_EXAR, PCI_VENDOR_ID_EXAR_XR17D152, 2 },
  { 0, 0, 0 }
};

/*
 * Information saved from PCI scan
 */
typedef struct {
  bool      found;
  uint32_t  base;
  uint8_t   irq;
  uint8_t   bus;
  uint8_t   slot;
  uint8_t   ports;
} exar17d15x_conf_t;

/*
 *  Register Access Routines
 */
static uint8_t xr17d15x_get_register(uint32_t addr, uint8_t i)
{
  uint8_t          val = 0;
  volatile uint8_t *reg = (volatile uint8_t *)(addr + i);

  val = *reg;
  // printk( "RD %p -> 0x%02x\n", reg, val );
  return val;
}

static void xr17d15x_set_register(uint32_t addr, uint8_t i, uint8_t val)
{
  volatile uint8_t *reg = (volatile uint8_t *)(addr + i);

  // printk( "WR %p <- 0x%02x\n", reg, val );
  *reg = val;
}

rtems_device_driver exar17d15x_initialize(
  rtems_device_major_number  major,
  rtems_device_minor_number  minor_arg,
  void                      *arg
)
{
  // int  pbus, pdev, pfun;
  exar17d15x_conf_t  conf[MAX_BOARDS];
  int              boards = 0;
  int              b = 0;
  int              p;
  console_tbl     *ports;
  console_tbl     *port_p;
  int              pbus;
  int              pdev;
  int              pfun;
  int              status;
  int              instance;
  int              i;
  int              total_ports = 0;

  for ( b=0 ; b<MAX_BOARDS ; b++ ) {
    conf[b].found = false;
  }

  /*
   *  Scan for Serial port boards
   *
   *  NOTE: There appear to be Exar parts with 2 and 4 ports which would
   *        be easy to support.  Just change the hard-coded 8 ports per
   *        board to variable and adjust.
   *
   *  NOTE: There are likely other board vendors which could be supported
   *        by this.
   */
  for ( instance=0 ; instance < MAX_BOARDS ; instance++ ) {

    for ( i=0 ; Supported[i].ports != 0 ; i++ ) {
      status = pci_find_device(
        Supported[i].vendor,
        Supported[i].device,
        instance,
        &pbus,
        &pdev,
        &pfun
      );
      if ( status == PCIB_ERR_SUCCESS ) {
        boards++;
        conf[instance].found = true;
        conf[instance].ports = Supported[i].ports;
        total_ports += conf[instance].ports;
        break;
      }
    }

    if ( status != PCIB_ERR_SUCCESS )
      continue;

    pci_read_config_byte(
      pbus,
      pdev,
      pfun,
      PCI_INTERRUPT_LINE,
      &conf[instance].irq
    );
    pci_read_config_dword(
      pbus,
      pdev,
      pfun,
      PCI_BASE_ADDRESS_0,
      &conf[instance].base
    );
    printk(
      "Found Exar 17D15x %d at 0x%08lx IRQ %d with %d ports\n",
      instance,
      conf[instance].base,
      conf[instance].irq,
      conf[instance].ports
    );
  }

  /*
   *  Now allocate array of device structures and fill them in
   */
  ports = calloc( total_ports, sizeof( console_tbl ) );
  port_p = ports;
  for ( b=0 ; b<MAX_BOARDS ; b++ ) {
    if ( conf[b].found == false )
      continue;
    for ( p=0 ; p<conf[b].ports ; p++ ) {
      char name[32];

      sprintf( name, "/dev/exar17d15x_%d_%d", b, p );
      //printk("Found %s\n", name );
      port_p->sDeviceName   = strdup( name );
      port_p->deviceType    = SERIAL_NS16550;
      #if 1
        port_p->pDeviceFns    = &ns16550_fns_polled;
      #else
        port_p->pDeviceFns    = &ns16550_fns;
      #endif

      port_p->deviceProbe   = NULL;
      port_p->pDeviceFlow   = NULL;
      port_p->ulMargin      = 16;
      port_p->ulHysteresis  = 8;
      port_p->pDeviceParams = (void *) 9600;
      port_p->ulCtrlPort1   = conf[b].base + (p * 0x0200);
      port_p->ulCtrlPort2   = 0;                   /* NA */
      port_p->ulDataPort    = 0;                   /* NA */
      port_p->getRegister   = xr17d15x_get_register;
      port_p->setRegister   = xr17d15x_set_register;
      port_p->getData       = NULL;                /* NA */
      port_p->setData       = NULL;                /* NA */
      port_p->ulClock       = EXAR_CLOCK_RATE;
      port_p->ulIntVector   = conf[b].irq;

      port_p++;
    }  /* end ports */
  }    /* end boards */

  /*
   *  Register the devices
   */
  if ( boards )
    console_register_devices( ports, total_ports );

  return RTEMS_SUCCESSFUL;
}
