/****************************************************************************
 * arch/mips/src/pic32mx/pic32mx_lowconsole.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <assert.h>
#include <debug.h>

#include <arch/irq.h>
#include <arch/board/board.h>

#include "mips_internal.h"
#include "pic32mx_config.h"
#include "pic32mx.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Select UART parameters for the selected console */

#ifdef HAVE_SERIAL_CONSOLE
#  if defined(CONFIG_UART1_SERIAL_CONSOLE)
#    define PIC32MX_CONSOLE_BASE     PIC32MX_UART1_K1BASE
#    define PIC32MX_CONSOLE_BAUD     CONFIG_UART1_BAUD
#    define PIC32MX_CONSOLE_BITS     CONFIG_UART1_BITS
#    define PIC32MX_CONSOLE_PARITY   CONFIG_UART1_PARITY
#    define PIC32MX_CONSOLE_2STOP    CONFIG_UART1_2STOP
#  elif defined(CONFIG_UART2_SERIAL_CONSOLE)
#    define PIC32MX_CONSOLE_BASE     PIC32MX_UART2_K1BASE
#    define PIC32MX_CONSOLE_BAUD     CONFIG_UART2_BAUD
#    define PIC32MX_CONSOLE_BITS     CONFIG_UART2_BITS
#    define PIC32MX_CONSOLE_PARITY   CONFIG_UART2_PARITY
#    define PIC32MX_CONSOLE_2STOP    CONFIG_UART2_2STOP
#  elif defined(CONFIG_UART3_SERIAL_CONSOLE)
#    define PIC32MX_CONSOLE_BASE     PIC32MX_UART3_K1BASE
#    define PIC32MX_CONSOLE_BAUD     CONFIG_UART3_BAUD
#    define PIC32MX_CONSOLE_BITS     CONFIG_UART3_BITS
#    define PIC32MX_CONSOLE_PARITY   CONFIG_UART3_PARITY
#    define PIC32MX_CONSOLE_2STOP    CONFIG_UART3_2STOP
#  elif defined(CONFIG_UART4_SERIAL_CONSOLE)
#    define PIC32MX_CONSOLE_BASE     PIC32MX_UART4_K1BASE
#    define PIC32MX_CONSOLE_BAUD     CONFIG_UART4_BAUD
#    define PIC32MX_CONSOLE_BITS     CONFIG_UART4_BITS
#    define PIC32MX_CONSOLE_PARITY   CONFIG_UART4_PARITY
#    define PIC32MX_CONSOLE_2STOP    CONFIG_UART4_2STOP
#  elif defined(CONFIG_UART5_SERIAL_CONSOLE)
#    define PIC32MX_CONSOLE_BASE     PIC32MX_UART5_K1BASE
#    define PIC32MX_CONSOLE_BAUD     CONFIG_UART5_BAUD
#    define PIC32MX_CONSOLE_BITS     CONFIG_UART5_BITS
#    define PIC32MX_CONSOLE_PARITY   CONFIG_UART5_PARITY
#    define PIC32MX_CONSOLE_2STOP    CONFIG_UART5_2STOP
#  elif defined(CONFIG_UART6_SERIAL_CONSOLE)
#    define PIC32MX_CONSOLE_BASE     PIC32MX_UART6_K1BASE
#    define PIC32MX_CONSOLE_BAUD     CONFIG_UART6_BAUD
#    define PIC32MX_CONSOLE_BITS     CONFIG_UART6_BITS
#    define PIC32MX_CONSOLE_PARITY   CONFIG_UART6_PARITY
#    define PIC32MX_CONSOLE_2STOP    CONFIG_UART6_2STOP
#  else
#    error "No CONFIG_UARTn_SERIAL_CONSOLE Setting"
#  endif
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: pic32mx_putreg
 *
 * Description:
 *   Write a value to a UART register
 *
 ****************************************************************************/

#ifdef HAVE_UART_DEVICE
static inline void pic32mx_putreg(uintptr_t uart_base, unsigned int offset,
                                      uint32_t value)
{
  putreg32(value, uart_base + offset);
}
#endif

/****************************************************************************
 * Name: pic32mx_getreg
 *
 * Description:
 *   Get a value from a UART register
 *
 ****************************************************************************/

#ifdef HAVE_SERIAL_CONSOLE
static inline uint32_t pic32mx_getreg(uintptr_t uart_base,
                                          unsigned int offset)
{
  return getreg32(uart_base + offset);
}
#endif

/****************************************************************************
 * Name: pic32mx_uartsetbaud
 *
 * Description:
 *   Configure the UART baud rate.
 *
 *   With BRGH=0
 *     BAUD = PBCLK / 16 / (BRG+1)
 *     BRG  = PBCLK / 16 / BAUD - 1
 *   With BRGH=1
 *     BAUD = PBCLK / 4 / (BRG+1)
 *     BRG  = PBCLK / 4 / BAUD - 1
 *
 *
 ****************************************************************************/

#ifdef HAVE_UART_DEVICE
static void pic32mx_uartsetbaud(uintptr_t uart_base, uint32_t baudrate)
{
  uint32_t tmp;
  uint32_t brg;
  unsigned int mode;

  /* We want the largest value of BRG divisor possible
   * (for the best accuracy), subject to BRG <= 65536.
   */

  tmp = BOARD_PBCLOCK / baudrate;

  /* Try BRGH=1 first.  This will select the 4x divisor and will produce the
   * larger BRG divisor, given all other things equal.
   */

  brg  = (tmp + 2) >> 2;
  mode = PIC32MX_UART_MODESET_OFFSET;

  if (brg > 65536)
    {
      /* Nope, too big.. try BRGH=0 */

      brg  = (tmp + 8) >> 4;
      mode = PIC32MX_UART_MODECLR_OFFSET;
    }

  DEBUGASSERT(brg <= 65536);

  /* Set the BRG divisor */

  pic32mx_putreg(uart_base, mode, UART_MODE_BRGH);
  pic32mx_putreg(uart_base, PIC32MX_UART_BRG_OFFSET, brg);
}
#endif

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: pic32mx_uartreset
 *
 * Description:
 *   Reset hardware and disable Rx and Tx.
 *
 ****************************************************************************/

#ifdef HAVE_UART_DEVICE
void pic32mx_uartreset(uintptr_t uart_base)
{
  /* Doesn't reset the hardware... just shuts it down */

  pic32mx_putreg(uart_base, PIC32MX_UART_STACLR_OFFSET,
                 UART_STA_UTXEN | UART_STA_URXEN);
  pic32mx_putreg(uart_base, PIC32MX_UART_MODECLR_OFFSET, UART_MODE_ON);
}
#endif

/****************************************************************************
 * Name: pic32mx_uartconfigure
 *
 * Description:
 *   Configure a UART as a RS-232 UART.
 *
 ****************************************************************************/

#ifdef HAVE_UART_DEVICE
void pic32mx_uartconfigure(uintptr_t uart_base, uint32_t baudrate,
                           unsigned int parity, unsigned int nbits,
                           bool stop2)
{
  /* Clear mode and sta bits */

  pic32mx_putreg(uart_base, PIC32MX_UART_MODECLR_OFFSET,
                 UART_MODE_STSEL    | UART_MODE_PDSEL_MASK |
                 UART_MODE_BRGH     | UART_MODE_RXINV      |
                 UART_MODE_WAKE     | UART_MODE_LPBACK     |
                 UART_MODE_UEN_MASK | UART_MODE_RTSMD      |
                 UART_MODE_IREN     | UART_MODE_SIDL       |
                 UART_MODE_ON);

  /* Configure the FIFOs:
   *
   *   RX: Interrupt at 6 of 8 (for 8-deep FIFO) or 3 o 4 (4-deep FIFO)
   *   TX: Interrupt on FIFO empty
   *   Invert transmit polarity.
   *
   * NOTE that there are not many options on trigger TX interrupts.
   * The FIFO not full might generate better through-put but with a higher
   * interrupt rate.  FIFO empty should lower the interrupt rate but result
   * in a burstier output.  If you change this, please read the comment for
   * acknowledging the interrupt in pic32mx_serial.c
   */

#ifdef UART_STA_URXISEL_RXB6
  pic32mx_putreg(uart_base, PIC32MX_UART_STACLR_OFFSET,
                 UART_STA_UTXINV    | UART_STA_UTXISEL_TXBE |
                 UART_STA_URXISEL_RXB6);
#else
  pic32mx_putreg(uart_base, PIC32MX_UART_STACLR_OFFSET,
                 UART_STA_UTXINV    | UART_STA_UTXISEL_TXBE |
                 UART_STA_URXISEL_RXB3);
#endif

  /* Configure the FIFO interrupts */

  pic32mx_putreg(uart_base, PIC32MX_UART_STASET_OFFSET,
                 UART_STA_UTXISEL_TXBNF  | UART_STA_URXISEL_RECVD);

  /* Configure word size and parity */

  if (nbits == 9)
    {
      DEBUGASSERT(parity == 0);
      pic32mx_putreg(uart_base, PIC32MX_UART_MODESET_OFFSET,
                     UART_MODE_PDSEL_9NONE);
    }
  else
    {
      DEBUGASSERT(nbits == 8);
      if (parity == 1)
        {
          pic32mx_putreg(uart_base, PIC32MX_UART_MODESET_OFFSET,
                         UART_MODE_PDSEL_8ODD);
        }
      else if (parity == 2)
        {
          pic32mx_putreg(uart_base, PIC32MX_UART_MODESET_OFFSET,
                         UART_MODE_PDSEL_8EVEN);
        }
    }

  /* Configure 1 or 2 stop bits */

  if (stop2)
    {
      pic32mx_putreg(uart_base, PIC32MX_UART_MODESET_OFFSET,
                     UART_MODE_STSEL);
    }

  /* Set the BRG divisor */

  pic32mx_uartsetbaud(uart_base, baudrate);

  /* Enable the UART */

  pic32mx_putreg(uart_base, PIC32MX_UART_STASET_OFFSET,
                 UART_STA_UTXEN | UART_STA_URXEN);
  pic32mx_putreg(uart_base, PIC32MX_UART_MODESET_OFFSET,
                 UART_MODE_ON);
}
#endif

/****************************************************************************
 * Name: pic32mx_consoleinit
 *
 * Description:
 *   Initialize a low-level console for debug output.  This function is
 *   called very early in the initialization sequence to configure the
 *   serial console UART (only).
 *
 ****************************************************************************/

#ifdef HAVE_SERIAL_CONSOLE
void pic32mx_consoleinit(void)
{
  pic32mx_uartconfigure(PIC32MX_CONSOLE_BASE, PIC32MX_CONSOLE_BAUD,
                        PIC32MX_CONSOLE_PARITY, PIC32MX_CONSOLE_BITS,
                        PIC32MX_CONSOLE_2STOP);
}
#endif

/****************************************************************************
 * Name: mips_lowputc
 *
 * Description:
 *   Output one byte on the serial console.
 *
 ****************************************************************************/

void mips_lowputc(char ch)
{
#ifdef HAVE_SERIAL_CONSOLE
  /* Wait for the transmit buffer not full */

  while ((pic32mx_getreg(PIC32MX_CONSOLE_BASE, PIC32MX_UART_STA_OFFSET) &
          UART_STA_UTXBF) != 0);

  /* Then write the character to the TX data register */

  pic32mx_putreg(PIC32MX_CONSOLE_BASE, PIC32MX_UART_TXREG_OFFSET,
                 (uint32_t)ch);
#endif
}