/****************************************************************************
 * arch/arm/src/s32k1xx/s32k1xx_lowputc.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 <stdint.h>
#include <fixedmath.h>
#include <assert.h>
#include <errno.h>

#include "hardware/s32k1xx_pinmux.h"
#include "hardware/s32k1xx_lpuart.h"

#include "s32k1xx_config.h"
#include "s32k1xx_pin.h"
#include "s32k1xx_lowputc.h"
#include "s32k1xx_periphclocks.h"
#include "arm_internal.h"

#include <arch/board/board.h> /* Include last:  has dependencies */

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

/* Configuration ************************************************************/

#ifdef HAVE_LPUART_CONSOLE
#  if defined(CONFIG_LPUART0_SERIAL_CONSOLE)
#    define S32K1XX_CONSOLE_BASE     S32K1XX_LPUART0_BASE
#    define S32K1XX_CONSOLE_BAUD     CONFIG_LPUART0_BAUD
#    define S32K1XX_CONSOLE_BITS     CONFIG_LPUART0_BITS
#    define S32K1XX_CONSOLE_PARITY   CONFIG_LPUART0_PARITY
#    define S32K1XX_CONSOLE_2STOP    CONFIG_LPUART0_2STOP
#  elif defined(CONFIG_LPUART1_SERIAL_CONSOLE)
#    define S32K1XX_CONSOLE_BASE     S32K1XX_LPUART1_BASE
#    define S32K1XX_CONSOLE_BAUD     CONFIG_LPUART1_BAUD
#    define S32K1XX_CONSOLE_BITS     CONFIG_LPUART1_BITS
#    define S32K1XX_CONSOLE_PARITY   CONFIG_LPUART1_PARITY
#    define S32K1XX_CONSOLE_2STOP    CONFIG_LPUART1_2STOP
#  elif defined(CONFIG_LPUART2_SERIAL_CONSOLE)
#    define S32K1XX_CONSOLE_BASE     S32K1XX_LPUART2_BASE
#    define S32K1XX_CONSOLE_BAUD     CONFIG_LPUART2_BAUD
#    define S32K1XX_CONSOLE_BITS     CONFIG_LPUART2_BITS
#    define S32K1XX_CONSOLE_PARITY   CONFIG_LPUART2_PARITY
#    define S32K1XX_CONSOLE_2STOP    CONFIG_LPUART2_2STOP
#  endif
#endif

/* Clocking *****************************************************************/

/* Functional clocking is provided via the  PCC.  The PCC clocking must
 * be configured by board-specific logic prior to using the LPUART.
 */

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

#ifdef HAVE_LPUART_CONSOLE
static const struct uart_config_s g_console_config =
{
  .baud      = S32K1XX_CONSOLE_BAUD,    /* Configured baud */
  .parity    = S32K1XX_CONSOLE_PARITY,  /* 0=none, 1=odd, 2=even */
  .bits      = S32K1XX_CONSOLE_BITS,    /* Number of bits (5-9) */
  .stopbits2 = S32K1XX_CONSOLE_2STOP,   /* true: Configure with 2 stop bits instead of 1 */
};
#endif

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

/****************************************************************************
 * Name: s32k1xx_lowsetup
 *
 * Description:
 *   Called at the very beginning of _start.  Performs low level
 *   initialization including setup of the console UART.  This UART done
 *   early so that the serial console is available for debugging very early
 *   in the boot sequence.
 *
 ****************************************************************************/

void s32k1xx_lowsetup(void)
{
#ifndef CONFIG_SUPPRESS_LPUART_CONFIG
#ifdef HAVE_LPUART_DEVICE

#ifdef CONFIG_S32K1XX_LPUART0

  /* Configure LPUART0 pins: RXD and TXD.  Also configure RTS and CTS if flow
   * control is enabled.
   */

  s32k1xx_pinconfig(PIN_LPUART0_RX);
  s32k1xx_pinconfig(PIN_LPUART0_TX);
#ifdef CONFIG_LPUART0_OFLOWCONTROL
  s32k1xx_pinconfig(PIN_LPUART0_CTS);
#endif
#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART0_RS485RTSCONTROL)) || \
     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART0_IFLOWCONTROL)))
  s32k1xx_pinconfig(PIN_LPUART0_RTS);
#endif
#endif

#ifdef CONFIG_S32K1XX_LPUART1

  /* Configure LPUART1 pins: RXD and TXD.  Also configure RTS and CTS if flow
   * control is enabled.
   */

  s32k1xx_pinconfig(PIN_LPUART1_RX);
  s32k1xx_pinconfig(PIN_LPUART1_TX);
#ifdef CONFIG_LPUART1_OFLOWCONTROL
  s32k1xx_pinconfig(PIN_LPUART1_CTS);
#endif
#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART1_RS485RTSCONTROL)) || \
     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART1_IFLOWCONTROL)))
  s32k1xx_pinconfig(PIN_LPUART1_RTS);
#endif
#endif

#ifdef CONFIG_S32K1XX_LPUART2

  /* Configure LPUART2 pins: RXD and TXD.  Also configure RTS and CTS if flow
   * control is enabled.
   */

  s32k1xx_pinconfig(PIN_LPUART2_RX);
  s32k1xx_pinconfig(PIN_LPUART2_TX);
#ifdef CONFIG_LPUART2_OFLOWCONTROL
  s32k1xx_pinconfig(PIN_LPUART2_CTS);
#endif
#if ((defined(CONFIG_SERIAL_RS485CONTROL) && defined(CONFIG_LPUART2_RS485RTSCONTROL)) || \
     (defined(CONFIG_SERIAL_IFLOWCONTROL) && defined(CONFIG_LPUART2_IFLOWCONTROL)))
  s32k1xx_pinconfig(PIN_LPUART2_RTS);
#endif
#endif

#ifdef HAVE_LPUART_CONSOLE
  /* Configure the serial console for initial, non-interrupt driver mode */

  s32k1xx_lpuart_configure(S32K1XX_CONSOLE_BASE, &g_console_config);
#endif
#endif /* HAVE_LPUART_DEVICE */
#endif /* CONFIG_SUPPRESS_LPUART_CONFIG */
}

/****************************************************************************
 * Name: s32k1xx_lpuart_configure
 *
 * Description:
 *   Configure a UART for non-interrupt driven operation
 *
 ****************************************************************************/

#ifdef HAVE_LPUART_DEVICE
int s32k1xx_lpuart_configure(uint32_t base,
                             const struct uart_config_s *config)
{
  enum clock_names_e clkname;
  uint32_t lpuart_freq = 0;
  uint16_t sbr;
  uint16_t temp_sbr;
  uint32_t osr;
  uint32_t temp_osr;
  uint32_t temp_diff;
  uint32_t calculated_baud;
  uint32_t baud_diff;
  uint32_t regval;
  int ret;

  /* Functional clocking is provided via the  PCC.  The PCC clocking must
   * be configured by board-specific logic prior to using the LPUART.
   */

  /* Get the PCC source clock */

#ifdef CONFIG_S32K1XX_LPUART0
  if (base == S32K1XX_LPUART0_BASE)
    {
      clkname = LPUART0_CLK;
    }
  else
#endif
#ifdef CONFIG_S32K1XX_LPUART1
  if (base == S32K1XX_LPUART1_BASE)
    {
      clkname = LPUART1_CLK;
    }
  else
#endif
#ifdef CONFIG_S32K1XX_LPUART2
  if (base == S32K1XX_LPUART2_BASE)
    {
      clkname = LPUART2_CLK;
    }
  else
#endif
    {
      DEBUGPANIC();
      return -EINVAL;
    }

  ret = s32k1xx_get_pclkfreq(clkname, &lpuart_freq);
  DEBUGASSERT(ret >= 0);
  if (ret < 0)
    {
      return ret;
    }

  /* This LPUART instantiation uses a slightly different baud rate
   * calculation.  The idea is to use the best OSR (over-sampling rate)
   * possible.
   *
   * NOTE: OSR is typically hard-set to 16 in other LPUART instantiations
   * loop to find the best OSR value possible, one that generates minimum
   * baud_diff iterate through the rest of the supported values of OSR
   */

  baud_diff = config->baud;
  osr       = 0;
  sbr       = 0;

  for (temp_osr = 4; temp_osr <= 32; temp_osr++)
    {
      /* Calculate the temporary sbr value   */

      temp_sbr = (lpuart_freq / (config->baud * temp_osr));

      /* Set temp_sbr to 1 if the sourceClockInHz can not satisfy the
       * desired baud rate.
       */

      if (temp_sbr == 0)
        {
          temp_sbr = 1;
        }

      /* Calculate the baud rate based on the temporary OSR and SBR values */

      calculated_baud = (lpuart_freq / (temp_osr * temp_sbr));
      temp_diff       = calculated_baud - config->baud;

      /* Select the better value between srb and (sbr + 1) */

      if (temp_diff >
          (config->baud - (lpuart_freq / (temp_osr * (temp_sbr + 1)))))
        {
          temp_diff = config->baud -
                      (lpuart_freq / (temp_osr * (temp_sbr + 1)));
          temp_sbr++;
        }

      if (temp_diff <= baud_diff)
        {
          baud_diff = temp_diff;
          osr       = temp_osr;
          sbr       = temp_sbr;
        }
    }

  if (baud_diff > ((config->baud / 100) * 3))
    {
      /* Unacceptable baud rate difference of more than 3% */

      return ERROR;
    }

  /* Reset all internal logic and registers, except the Global Register */

  regval  = getreg32(base + S32K1XX_LPUART_GLOBAL_OFFSET);
  regval |= LPUART_GLOBAL_RST;
  putreg32(regval, base + S32K1XX_LPUART_GLOBAL_OFFSET);

  regval &= ~LPUART_GLOBAL_RST;
  putreg32(regval, base + S32K1XX_LPUART_GLOBAL_OFFSET);

  /* Construct MODIR register */

  regval = 0;

  if (config->userts)
    {
      regval |= LPUART_MODIR_RXRTSE;
    }
  else if (config->users485)
    {
      /* Both TX and RX side can't control RTS, so this gives
       * the RX side precedence. This should have been filtered
       * in layers above anyway, but it's just a precaution.
       */

      regval |= LPUART_MODIR_TXRTSE;
    }

  if (config->usects)
    {
      regval |= LPUART_MODIR_TXCTSE;
    }

  if (config->invrts)
    {
      regval |= LPUART_MODIR_TXRTSPOL;
    }

  putreg32(regval, base + S32K1XX_LPUART_MODIR_OFFSET);

  regval = 0;

  if ((osr > 3) && (osr < 8))
    {
      regval |= LPUART_BAUD_BOTHEDGE;
    }

  if (config->stopbits2)
    {
      regval |= LPUART_BAUD_SBNS;
    }

  regval |= LPUART_BAUD_OSR(osr) | LPUART_BAUD_SBR(sbr);
  putreg32(regval, base + S32K1XX_LPUART_BAUD_OFFSET);

  regval = 0;
  if (config->parity == 1)
    {
      regval |= LPUART_CTRL_PE | LPUART_CTRL_PT_ODD;
    }
  else if (config->parity == 2)
    {
      regval |= LPUART_CTRL_PE | LPUART_CTRL_PT_EVEN;
    }

  if (config->bits == 8)
    {
      regval &= ~LPUART_CTRL_M;
    }
  else if (config->bits == 9)
    {
      regval |= LPUART_CTRL_M;
    }
  else
    {
      /* REVISIT: Here should be added support of other bit modes. */

      return -ENOSYS;
    }

  regval |= LPUART_CTRL_RE | LPUART_CTRL_TE;
  putreg32(regval, base + S32K1XX_LPUART_CTRL_OFFSET);

  return OK;
}
#endif /* HAVE_LPUART_DEVICE */

/****************************************************************************
 * Name: s32k1xx_lowputc
 *
 * Description:
 *   Output a byte with as few system dependencies as possible.  This will
 *   even work BEFORE the console is initialized if we are booting from U-
 *   Boot (and the same UART is used for the console, of course.)
 *
 ****************************************************************************/

#if defined(HAVE_LPUART_CONSOLE) && defined(CONFIG_DEBUG_FEATURES)
void s32k1xx_lowputc(int ch)
{
  while ((getreg32(S32K1XX_CONSOLE_BASE + S32K1XX_LPUART_STAT_OFFSET) &
         LPUART_STAT_TDRE) == 0)
    {
    }

  /* If the character to output is a newline,
   * then pre-pend a carriage return
   */

  if (ch == '\n')
    {
      /* Send the carriage return by writing it into the UART_TXD register. */

      putreg32((uint32_t)'\r',
                S32K1XX_CONSOLE_BASE + S32K1XX_LPUART_DATA_OFFSET);

      /* Wait for the transmit register to be emptied. When the TXFE bit is
       * non-zero, the TX Buffer FIFO is empty.
       */

      while ((getreg32(S32K1XX_CONSOLE_BASE + S32K1XX_LPUART_STAT_OFFSET) &
             LPUART_STAT_TDRE) == 0)
        {
        }
    }

  /* Send the character by writing it into the UART_TXD register. */

  putreg32((uint32_t)ch, S32K1XX_CONSOLE_BASE + S32K1XX_LPUART_DATA_OFFSET);

  /* Wait for the transmit register to be emptied. When the TXFE bit is
   * non-zero, the TX Buffer FIFO is empty.
   */

  while ((getreg32(S32K1XX_CONSOLE_BASE + S32K1XX_LPUART_STAT_OFFSET) &
         LPUART_STAT_TDRE) == 0)
    {
    }
}
#endif
