/*
 * uart-8250.c -- polling driver for 32-bit 8250 UART.
 * Provides _uart_8250_setup(), _read() and _write(). Please note that
 * _read() and _write() from this file can only be used for console access.
 *
 * Copyright (c) 2024 Synopsys Inc.
 *
 * The authors hereby grant permission to use, copy, modify, distribute,
 * and license this software and its documentation for any purpose, provided
 * that existing copyright notices are retained in all copies and that this
 * notice is included verbatim in any distributions. No written agreement,
 * license, or royalty fee is required for any of the authorized uses.
 * Modifications to this software may be copyrighted by their authors
 * and need not follow the licensing terms described here, provided that
 * the new terms are clearly indicated on the first page of each file where
 * they apply.
 */

#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <errno.h>

/*
 * List of UART 8250 registers with offsets:
 *
 *     0x00 - Transmit Holding Register, WO, LCR_DLAB == 0
 *     0x00 - Receive Buffer Register, RO, LCR_DLAB == 0
 *     0x00 - Divisor Latch Low, RW, LCR_DLAB == 1
 *     0x04 - Divisor Latch High, RW, LCR_DLAB == 1
 *     0x04 - Interrupt Enable Register, RW, LCR_DLAB == 0
 *     0x08 - FIFO Control Register, WO
 *     0x08 - Interrupt Identification Register, RO
 *     0x0C - Line Control Register, RW
 *     0x10 - Modem Control Register, RW
 *     0x14 - Line Status Register, RO
 *     0x18 - Modem Status Register, RO
 */

#define THR_OFFSET 0x00
#define RBR_OFFSET 0x00
#define DLL_OFFSET 0x00
#define DLH_OFFSET 0x04
#define IER_OFFSET 0x04
#define FCR_OFFSET 0x08
#define IIR_OFFSET 0x08
#define LCR_OFFSET 0x0C
#define MCR_OFFSET 0x10
#define LSR_OFFSET 0x14
#define MSR_OFFSET 0x18

/*
 * LCR (Line Control Register) fields:
 *
 *       [7] - Divisor Latch Access Bit
 *       [6] - Set Break
 *       [5] - Stick Parity
 *       [4] - Even Parity Select
 *       [3] - Parity Enable
 *       [2] - Number of Stop Bits
 *     [1:0] - Data Length Select. Values:
 *                 0b11 - 8 data bits per character (default)
 *                 0b10 - 7 data bits per character
 *                 0b01 - 6 data bits per character
 *                 0b00 - 5 data bits per character
 */

#define LCR_DLAB_SHIFT	7
#define LCR_SB_SHIFT	6
#define LCR_SP_SHIFT	5
#define LCR_EPS_SHIFT	4
#define LCR_PEN_SHIFT	3
#define LCR_STB_SHIFT	2
#define LCR_DLS_SHIFT	0
/*
 * MCR (Modem Control Register) fields:
 *
 *     [4] - LoopBack Bit
 *     [3] - Auxiliary Output 2
 *     [2] - Auxiliary Output 1
 *     [1] - Request To Send (ON by default)
 *     [0] - Data Terminal Ready (ON by default)
 */

#define	MCR_LB_SHIFT	4
#define	MCR_OUT2_SHIFT	3
#define	MCR_OUT1_SHIFT	2
#define	MCR_RTS_SHIFT	1
#define	MCR_DTR_SHIFT	0

/*
 * LSR (Line Status Register) fields:
 *
 *     [7] - Receiver FIFO Error
 *     [6] - Transmitter Empty
 *     [5] - Transmit Holding Register Empty
 *     [4] - Break Interrupt
 *     [3] - Framing Error
 *     [2] - Parity Error
 *     [1] - Overrun Error
 *     [0] - Data Ready
 */

#define LSR_RFE_SHIFT	7
#define LSR_TEMT_SHIFT	6
#define LSR_THRE_SHIFT	5
#define LSR_BI_SHIFT	4
#define LSR_FE_SHIFT	3
#define LSR_PE_SHIFT	2
#define LSR_OE_SHIFT	1
#define LSR_DR_SHIFT	0

/*
 * Default initial values for configuration registers.
 */

#define FCR_DEFAULT	0x0
#define IER_DEFAULT	0x0
#define LCR_DLS_DEFAULT	0x3
#define LCR_DEFAULT	(LCR_DLS_DEFAULT << LCR_DLS_SHIFT)
#define MCR_RTS_DEFAULT	0x1
#define MCR_DTR_DEFAULT	0x1
#define MCR_DEFAULT	((MCR_RTS_DEFAULT << MCR_RTS_SHIFT) | \
			 (MCR_DTR_DEFAULT << MCR_DTR_SHIFT))

#define LCR_MODE_SETUP	(0x1 << LCR_DLAB_SHIFT)
#define LSR_MODE_GETC	(0x1 << LSR_DR_SHIFT)
#define LSR_MODE_PUTC	((0x1 << LSR_TEMT_SHIFT) | (0x1 << LSR_THRE_SHIFT))

/* Main UART control structure.  */
struct _uart_8250 {
  volatile char *base;	/* Start of UART registers.  */
  uint32_t clk;		/* UART clock.  */
  uint32_t baud;	/* Baud rate.  */
  int aux_mapped;	/* If UART registers are mapped to AUX or memory.  */
  int ready;		/* If UART is ready to use or not.  */
};

static struct _uart_8250 _uart_8250;

/* Write 32-bit value to the UART register.  */
static inline void
_uart_8250_write_reg (const struct _uart_8250 *uart, uint32_t reg,
		      uint32_t value)
{
  if (uart->aux_mapped)
    __builtin_arc_sr (value, (uint32_t) uart->base + reg);
  else
    *(volatile uint32_t *)(uart->base + reg) = value;
}

/* Read 32-bit value from the UART register.  */
static inline uint32_t
_uart_8250_read_reg (const struct _uart_8250 *uart, uint32_t reg)
{
  if (uart->aux_mapped)
    return __builtin_arc_lr ((uint32_t) uart->base + reg);
  else
    return *(volatile uint32_t *)(uart->base + reg);
}

/* Wait until all flags are set.  */
static inline void
_uart_8250_wait (const struct _uart_8250 *uart, uint32_t reg, uint32_t flags)
{
  while (1)
    {
      if ((_uart_8250_read_reg (uart, reg) & flags) == flags)
	break;
    }
}

/* Get one character from UART.  CR is converted to NL.  */
static int
_uart_8250_getc (const struct _uart_8250 *uart)
{
  char c;

  _uart_8250_wait (uart, LSR_OFFSET, LSR_MODE_GETC);

  c = _uart_8250_read_reg (uart, RBR_OFFSET);

  if (c == '\r')
    c = '\n';

  return c;
}

/* Put one character to UART. CR is placed before NL.  */
static void
_uart_8250_putc (const struct _uart_8250 *uart, char c)
{
  if (c == '\n')
    _uart_8250_putc (uart, '\r');

  _uart_8250_wait (uart, LSR_OFFSET, LSR_MODE_PUTC);

  _uart_8250_write_reg (uart, THR_OFFSET, c);
}

/*
 * Setup UART control structure and following parameters:
 *
 *     - baudrate if clock and baudrate are passed to function
 *     - 8n1 (8 data bits, no parity bit, one stop bit)
 *     - disable interrupts
 *     - disable FIFO
 *     - set Request To Send and Data Terminal Ready
 *
 * Arguments:
 *
 *     - base - start address of UART registers
 *     - aux_mapped - set if UART registers are mapped to ARC AUX
 *     - clk - UART clock frequency
 *     - baud - UART baudrate to setup
 *
 * The function returns 0 on success.
 */
int
_uart_8250_setup (void *base, int aux_mapped, uint32_t clk, uint32_t baud)
{
  struct _uart_8250 *uart = &_uart_8250;

  uart->base = base;
  uart->aux_mapped = aux_mapped;
  uart->clk = clk;
  uart->baud = baud;

  if (clk && baud)
    {
      uint32_t div;

      div = ((clk + 8 * baud) / baud) / 16;
      _uart_8250_write_reg (uart, LCR_OFFSET, LCR_MODE_SETUP);
      _uart_8250_write_reg (uart, DLL_OFFSET, div & 0xFF);
      _uart_8250_write_reg (uart, DLH_OFFSET, div >> 8);
    }

  _uart_8250_write_reg (uart, FCR_OFFSET, FCR_DEFAULT);
  _uart_8250_write_reg (uart, IER_OFFSET, IER_DEFAULT);
  _uart_8250_write_reg (uart, LCR_OFFSET, LCR_DEFAULT);
  _uart_8250_write_reg (uart, MCR_OFFSET, MCR_DEFAULT);

  uart->ready = 1;

  return 0;
}

/* _read() is implemented only for stdin. Each read character is echoed.  */
ssize_t
_read (int fd, void *buf, size_t count)
{
  struct _uart_8250 *uart = &_uart_8250;
  size_t bytes_read;
  char *buf_char = buf;
  int c;

  if (fd != STDIN_FILENO)
    {
      errno = ENOSYS;
      return -1;
    }

  if (!uart->ready)
    {
      errno = EIO;
      return -1;
    }

  bytes_read = 0;
  c = EOF;
  /* Break on '\n' to simulate readline behavior.  */
  while (bytes_read != count && c != '\n')
    {
      c = _uart_8250_getc (uart);
      if (c == EOF)
	break;

      /* Echo character to the console.  */
      _uart_8250_putc (uart, c);

      buf_char[bytes_read] = c;
      bytes_read++;
    }

  return bytes_read;
}

/* _write() is implemented only for stdout and stderr.  */
ssize_t
_write (int fd, const char *buf, size_t nbyte)
{
  struct _uart_8250 *uart = &_uart_8250;
  uint32_t bytes_written;

  if (fd != STDOUT_FILENO && fd != STDERR_FILENO)
    {
      errno = ENOSYS;
      return -1;
    }

  if (!uart->ready)
    {
      errno = EIO;
      return -1;
    }

  bytes_written = 0;
  while (bytes_written != nbyte)
    {
      _uart_8250_putc (uart, buf[bytes_written]);
      bytes_written++;
    }

  return bytes_written;
}
