/****************************************************************************
 * arch/mips/src/pic32mz/pic32mz-lowconsole.c
 *
 *   Copyright (C) 2015 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <gnutt@nuttx.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

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

#include <arch/irq.h>

#include "up_arch.h"
#include "up_internal.h"

#include "pic32mz-config.h"
#include "chip/pic32mz-uart.h"
#include "chip/pic32mz-pps.h"

#include <arch/board/board.h>

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

/* Select UART parameters for the selected console */

#ifdef HAVE_SERIAL_CONSOLE
#  if defined(CONFIG_UART1_SERIAL_CONSOLE)
#    define PIC32MZ_CONSOLE_BASE     PIC32MZ_UART1_K1BASE
#    define PIC32MZ_CONSOLE_BAUD     CONFIG_UART1_BAUD
#    define PIC32MZ_CONSOLE_BITS     CONFIG_UART1_BITS
#    define PIC32MZ_CONSOLE_PARITY   CONFIG_UART1_PARITY
#    define PIC32MZ_CONSOLE_2STOP    CONFIG_UART1_2STOP
#  elif defined(CONFIG_UART2_SERIAL_CONSOLE)
#    define PIC32MZ_CONSOLE_BASE     PIC32MZ_UART2_K1BASE
#    define PIC32MZ_CONSOLE_BAUD     CONFIG_UART2_BAUD
#    define PIC32MZ_CONSOLE_BITS     CONFIG_UART2_BITS
#    define PIC32MZ_CONSOLE_PARITY   CONFIG_UART2_PARITY
#    define PIC32MZ_CONSOLE_2STOP    CONFIG_UART2_2STOP
#  elif defined(CONFIG_UART3_SERIAL_CONSOLE)
#    define PIC32MZ_CONSOLE_BASE     PIC32MZ_UART3_K1BASE
#    define PIC32MZ_CONSOLE_BAUD     CONFIG_UART3_BAUD
#    define PIC32MZ_CONSOLE_BITS     CONFIG_UART3_BITS
#    define PIC32MZ_CONSOLE_PARITY   CONFIG_UART3_PARITY
#    define PIC32MZ_CONSOLE_2STOP    CONFIG_UART3_2STOP
#  elif defined(CONFIG_UART4_SERIAL_CONSOLE)
#    define PIC32MZ_CONSOLE_BASE     PIC32MZ_UART4_K1BASE
#    define PIC32MZ_CONSOLE_BAUD     CONFIG_UART4_BAUD
#    define PIC32MZ_CONSOLE_BITS     CONFIG_UART4_BITS
#    define PIC32MZ_CONSOLE_PARITY   CONFIG_UART4_PARITY
#    define PIC32MZ_CONSOLE_2STOP    CONFIG_UART4_2STOP
#  elif defined(CONFIG_UART5_SERIAL_CONSOLE)
#    define PIC32MZ_CONSOLE_BASE     PIC32MZ_UART5_K1BASE
#    define PIC32MZ_CONSOLE_BAUD     CONFIG_UART5_BAUD
#    define PIC32MZ_CONSOLE_BITS     CONFIG_UART5_BITS
#    define PIC32MZ_CONSOLE_PARITY   CONFIG_UART5_PARITY
#    define PIC32MZ_CONSOLE_2STOP    CONFIG_UART5_2STOP
#  elif defined(CONFIG_UART6_SERIAL_CONSOLE)
#    define PIC32MZ_CONSOLE_BASE     PIC32MZ_UART6_K1BASE
#    define PIC32MZ_CONSOLE_BAUD     CONFIG_UART6_BAUD
#    define PIC32MZ_CONSOLE_BITS     CONFIG_UART6_BITS
#    define PIC32MZ_CONSOLE_PARITY   CONFIG_UART6_PARITY
#    define PIC32MZ_CONSOLE_2STOP    CONFIG_UART6_2STOP
#  else
#    error "No CONFIG_UARTn_SERIAL_CONSOLE Setting"
#  endif
#endif

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

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

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

/****************************************************************************
 * Private Variables
 ****************************************************************************/

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

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

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

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

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

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

#ifdef HAVE_UART_DEVICE
static void pic32mz_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_PBCLK2 / 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 = PIC32MZ_UART_MODESET_OFFSET;

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

      brg  = (tmp + 8) >> 4;
      mode = PIC32MZ_UART_MODECLR_OFFSET;
    }
  DEBUGASSERT(brg <= 65536);

  /* Set the BRG divisor */

  pic32mz_putreg(uart_base, mode, UART_MODE_BRGH);
  pic32mz_putreg(uart_base, PIC32MZ_UART_BRG_OFFSET, brg);
}
#endif

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

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

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

  pic32mz_putreg(uart_base, PIC32MZ_UART_STACLR_OFFSET,
                 UART_STA_UTXEN | UART_STA_URXEN);
  pic32mz_putreg(uart_base, PIC32MZ_UART_MODECLR_OFFSET, UART_MODE_ON);
}
#endif

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

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

  pic32mz_putreg(uart_base, PIC32MZ_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 75% FIFO full (6 of 8 for 8-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
   * pic32mz-serial.c
   */

  pic32mz_putreg(uart_base, PIC32MZ_UART_STACLR_OFFSET,
                 UART_STA_UTXINV | UART_STA_UTXISEL_TXBE | UART_STA_URXISEL_RXB75);

  /* Configure the FIFO interrupts */

  pic32mz_putreg(uart_base, PIC32MZ_UART_STASET_OFFSET,
                 UART_STA_UTXISEL_TXBNF  | UART_STA_URXISEL_RECVD);

  /* Configure word size and parity */

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

  /* Configure 1 or 2 stop bits */

  if (stop2)
    {
      pic32mz_putreg(uart_base, PIC32MZ_UART_MODESET_OFFSET,
                     UART_MODE_STSEL);
    }

  /* Set the BRG divisor */

  pic32mz_uartsetbaud(uart_base, baudrate);

  /* Enable the UART */

  pic32mz_putreg(uart_base, PIC32MZ_UART_STASET_OFFSET,
                 UART_STA_UTXEN | UART_STA_URXEN);
  pic32mz_putreg(uart_base, PIC32MZ_UART_MODESET_OFFSET,
                 UART_MODE_ON);
}
#endif

/****************************************************************************
 * Name: pic32mz_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).
 *
 ****************************************************************************/

void pic32mz_consoleinit(void)
{
#ifdef HAVE_UART_DEVICE

  /* Setup up pin selection registers for all configured UARTs.  The board.h
   * header file must provide these definitions to select the correct pin
   * configuration for each enabled UARt.
   */

#ifdef CONFIG_PIC32MZ_UART1
  /* Configure UART1 RX (input) and TX (output) pins */

  putreg32(BOARD_U1RX_PPS, PIC32MZ_U1RXR);
  putreg32(PPS_OUTPUT_REGVAL(BOARD_U1TX_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U1TX_PPS));

#ifdef CONFIG_UART1_OFLOWCONTROL
  /* Configure the UART1 CTS input pin */

  putreg32(BOARD_U1CTS_PPS, PIC32MZ_U1CTSR);
#endif
#ifdef CONFIG_UART1_IFLOWCONTROL
  /* Configure the UART1 RTS output pin */

  putreg32(PPS_OUTPUT_REGVAL(BOARD_U1RTS_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U1RTS_PPS));

#endif /* CONFIG_UART1_IFLOWCONTROL */
#endif /* CONFIG_PIC32MZ_UART1 */

#ifdef CONFIG_PIC32MZ_UART2
  /* Configure UART2 RX (input) and TX (output) pins */

  putreg32(BOARD_U2RX_PPS, PIC32MZ_U2RXR);
  putreg32(PPS_OUTPUT_REGVAL(BOARD_U2TX_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U2TX_PPS));

#ifdef CONFIG_UART2_OFLOWCONTROL
  /* Configure the UART2 CTS input pin */

  putreg32(BOARD_U2CTS_PPS, PIC32MZ_U2CTSR);
#endif
#ifdef CONFIG_UART2_IFLOWCONTROL
  /* Configure the UART2 RTS output pin */

  putreg32(PPS_OUTPUT_REGVAL(BOARD_U2RTS_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U2RTS_PPS));

#endif /* CONFIG_UART2_IFLOWCONTROL */
#endif /* CONFIG_PIC32MZ_UART2 */

#ifdef CONFIG_PIC32MZ_UART3
  /* Configure UART3 RX (input) and TX (output) pins */

  putreg32(BOARD_U3RX_PPS, PIC32MZ_U3RXR);
  putreg32(PPS_OUTPUT_REGVAL(BOARD_U3TX_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U3TX_PPS));

#ifdef CONFIG_UART3_OFLOWCONTROL
  /* Configure the UART3 CTS input pin */

  putreg32(BOARD_U3CTS_PPS, PIC32MZ_U3CTSR);
#endif
#ifdef CONFIG_UART3_IFLOWCONTROL
  /* Configure the UART3 RTS output pin */

  putreg32(PPS_OUTPUT_REGVAL(BOARD_U3RTS_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U3RTS_PPS));

#endif /* CONFIG_UART3_IFLOWCONTROL */
#endif /* CONFIG_PIC32MZ_UART3 */

#ifdef CONFIG_PIC32MZ_UART4
  /* Configure UART4 RX (input) and TX (output) pins */

  putreg32(BOARD_U4RX_PPS, PIC32MZ_U4RXR);
  putreg32(PPS_OUTPUT_REGVAL(BOARD_U4TX_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U4TX_PPS));

#ifdef CONFIG_UART4_OFLOWCONTROL
  /* Configure the UART4 CTS input pin */

  putreg32(BOARD_U4CTS_PPS, PIC32MZ_U4CTSR);
#endif
#ifdef CONFIG_UART4_IFLOWCONTROL
  /* Configure the UART4 RTS output pin */

  putreg32(PPS_OUTPUT_REGVAL(BOARD_U4RTS_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U4RTS_PPS));

#endif /* CONFIG_UART4_IFLOWCONTROL */
#endif /* CONFIG_PIC32MZ_UART4 */

#ifdef CONFIG_PIC32MZ_UART5
  /* Configure UART5 RX (input) and TX (output) pins */

  putreg32(BOARD_U5RX_PPS, PIC32MZ_U5RXR);
  putreg32(PPS_OUTPUT_REGVAL(BOARD_U5TX_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U5TX_PPS));

#ifdef CONFIG_UART5_OFLOWCONTROL
  /* Configure the UART5 CTS input pin */

  putreg32(BOARD_U5CTS_PPS, PIC32MZ_U5CTSR);
#endif
#ifdef CONFIG_UART5_IFLOWCONTROL
  /* Configure the UART5 RTS output pin */

  putreg32(PPS_OUTPUT_REGVAL(BOARD_U5RTS_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U5RTS_PPS));

#endif /* CONFIG_UART5_IFLOWCONTROL */
#endif /* CONFIG_PIC32MZ_UART5 */

#ifdef CONFIG_PIC32MZ_UART6
  /* Configure UART6 RX (input) and TX (output) pins */

  putreg32(BOARD_U6RX_PPS, PIC32MZ_U6RXR);
  putreg32(PPS_OUTPUT_REGVAL(BOARD_U6TX_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U6TX_PPS));

#ifdef CONFIG_UART6_OFLOWCONTROL
  /* Configure the UART6 CTS input pin */

  putreg32(BOARD_U6CTS_PPS, PIC32MZ_U6CTSR);
#endif
#ifdef CONFIG_UART6_IFLOWCONTROL
  /* Configure the UART6 RTS output pin */

  putreg32(PPS_OUTPUT_REGVAL(BOARD_U6RTS_PPS),
           PPS_OUTPUT_REGADDR(BOARD_U6RTS_PPS));

#endif /* CONFIG_UART6_IFLOWCONTROL */
#endif /* CONFIG_PIC32MZ_UART6 */

#ifdef HAVE_SERIAL_CONSOLE
  /* Configure the console uart */

  pic32mz_uartconfigure(PIC32MZ_CONSOLE_BASE, PIC32MZ_CONSOLE_BAUD,
                        PIC32MZ_CONSOLE_PARITY, PIC32MZ_CONSOLE_BITS,
                        PIC32MZ_CONSOLE_2STOP);

#endif /* HAVE_SERIAL_CONSOLE */
#endif /* HAVE_UART_DEVICE */
}

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

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

  while ((pic32mz_getreg(PIC32MZ_CONSOLE_BASE, PIC32MZ_UART_STA_OFFSET) & UART_STA_UTXBF) != 0);

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

  pic32mz_putreg(PIC32MZ_CONSOLE_BASE, PIC32MZ_UART_TXREG_OFFSET, (uint32_t)ch);
#endif
}
