/****************************************************************************
 * drivers/segger/serial_rtt.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 <assert.h>
#include <sys/types.h>
#include <syslog.h>

#include <nuttx/kmalloc.h>
#include <nuttx/segger/rtt.h>
#include <nuttx/serial/serial.h>
#include <nuttx/wdog.h>

#include <SEGGER_RTT.h>

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

#ifdef SEGGER_RTT_BUFFER_SECTION
#  define SERIAL_RTT_BUFFER_SECTION locate_data(SEGGER_RTT_BUFFER_SECTION)
#else
#  define SERIAL_RTT_BUFFER_SECTION
#endif

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

struct serial_rtt_s
{
  struct uart_dev_s uart;
  struct wdog_s wdog;
  int channel;
  FAR char *up_buffer;
  FAR char *down_buffer;
};

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

static int serial_rtt_setup(FAR struct uart_dev_s *dev);
static void serial_rtt_shutdown(FAR struct uart_dev_s *dev);
static int serial_rtt_attach(FAR struct uart_dev_s *dev);
static void serial_rtt_detach(FAR struct uart_dev_s *dev);
static int serial_rtt_ioctl(FAR struct file *filep, int cmd,
                            unsigned long arg);
static int serial_rtt_receive(FAR struct uart_dev_s *dev,
                              FAR unsigned int *status);
static void serial_rtt_rxint(FAR struct uart_dev_s *dev, bool enable);
static bool serial_rtt_rxavailable(FAR struct uart_dev_s *dev);
static void serial_rtt_dmasend(FAR struct uart_dev_s *dev);
static void serial_rtt_dmareceive(FAR struct uart_dev_s *dev);
static void serial_rtt_dmarxfree(FAR struct uart_dev_s *dev);
static void serial_rtt_dmatxavail(FAR struct uart_dev_s *dev);
static void serial_rtt_send(FAR struct uart_dev_s *dev, int ch);
static void serial_rtt_txint(FAR struct uart_dev_s *dev, bool enable);
static bool serial_rtt_txready(FAR struct uart_dev_s *dev);
static bool serial_rtt_txempty(FAR struct uart_dev_s *dev);

static void serial_rtt_timeout(wdparm_t arg);

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

static const struct uart_ops_s g_serial_rtt_ops =
{
  serial_rtt_setup,
  serial_rtt_shutdown,
  serial_rtt_attach,
  serial_rtt_detach,
  serial_rtt_ioctl,
  serial_rtt_receive,
  serial_rtt_rxint,
  serial_rtt_rxavailable,
#ifdef CONFIG_SERIAL_IFLOWCONTROL
  NULL,
#endif
  serial_rtt_dmasend,
  serial_rtt_dmareceive,
  serial_rtt_dmarxfree,
  serial_rtt_dmatxavail,
  serial_rtt_send,
  serial_rtt_txint,
  serial_rtt_txready,
  serial_rtt_txempty,
};

#ifdef CONFIG_SERIAL_RTT0
static char g_rtt0_xmit_buffer[CONFIG_SEGGER_RTT_BUFFER_SIZE_UP];
static char g_rtt0_recv_buffer[CONFIG_SEGGER_RTT_BUFFER_SIZE_DOWN];

static struct serial_rtt_s g_serial_rtt0 =
{
  .uart =
  {
#ifdef CONFIG_SERIAL_RTT_CONSOLE
    .isconsole = CONFIG_SERIAL_RTT_CONSOLE_CHANNEL == 0,
#endif
    .recv =
    {
      .buffer = g_rtt0_recv_buffer,
      .size = CONFIG_SEGGER_RTT_BUFFER_SIZE_DOWN,
    },
    .xmit =
    {
      .buffer = g_rtt0_xmit_buffer,
      .size = CONFIG_SEGGER_RTT_BUFFER_SIZE_UP,
    },
    .ops = &g_serial_rtt_ops,
    .priv = &g_serial_rtt0,
  },
  .channel = 0,
  .up_buffer = NULL,
  .down_buffer = NULL,
};
#endif

#ifdef CONFIG_SERIAL_RTT1
static char g_rtt1_xmit_buffer[CONFIG_SEGGER_RTT1_BUFFER_SIZE_UP];
static char g_rtt1_recv_buffer[CONFIG_SEGGER_RTT1_BUFFER_SIZE_DOWN];

static char SERIAL_RTT_BUFFER_SECTION
g_rtt1_up_buffer[CONFIG_SEGGER_RTT1_BUFFER_SIZE_UP];
static char SERIAL_RTT_BUFFER_SECTION
g_rtt1_down_buffer[CONFIG_SEGGER_RTT1_BUFFER_SIZE_DOWN];

static struct serial_rtt_s g_serial_rtt1 =
{
  .uart =
  {
#ifdef CONFIG_SERIAL_RTT_CONSOLE
    .isconsole = CONFIG_SERIAL_RTT_CONSOLE_CHANNEL == 1,
#endif
    .recv =
    {
      .buffer = g_rtt1_recv_buffer,
      .size = CONFIG_SEGGER_RTT1_BUFFER_SIZE_DOWN,
    },
    .xmit =
    {
      .buffer = g_rtt1_xmit_buffer,
      .size = CONFIG_SEGGER_RTT1_BUFFER_SIZE_UP,
    },
    .ops = &g_serial_rtt_ops,
    .priv = &g_serial_rtt1,
  },
  .channel = 1,
  .up_buffer = g_rtt1_up_buffer,
  .down_buffer = g_rtt1_down_buffer,
};
#endif

#ifdef CONFIG_SERIAL_RTT2
static char g_rtt2_xmit_buffer[CONFIG_SEGGER_RTT2_BUFFER_SIZE_UP];
static char g_rtt2_recv_buffer[CONFIG_SEGGER_RTT2_BUFFER_SIZE_DOWN];

static char SERIAL_RTT_BUFFER_SECTION
g_rtt2_up_buffer[CONFIG_SEGGER_RTT2_BUFFER_SIZE_UP];
static char SERIAL_RTT_BUFFER_SECTION
g_rtt2_down_buffer[CONFIG_SEGGER_RTT2_BUFFER_SIZE_DOWN];

static struct serial_rtt_s g_serial_rtt2 =
{
  .uart =
  {
#ifdef CONFIG_SERIAL_RTT_CONSOLE
    .isconsole = CONFIG_SERIAL_RTT_CONSOLE_CHANNEL == 2,
#endif
    .recv =
    {
      .buffer = g_rtt2_recv_buffer,
      .size = CONFIG_SEGGER_RTT2_BUFFER_SIZE_DOWN,
    },
    .xmit =
    {
      .buffer = g_rtt2_xmit_buffer,
      .size = CONFIG_SEGGER_RTT2_BUFFER_SIZE_UP,
    },
    .ops = &g_serial_rtt_ops,
    .priv = &g_serial_rtt2,
  },
  .channel = 2,
  .up_buffer = g_rtt2_up_buffer,
  .down_buffer = g_rtt2_down_buffer,
};
#endif

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

/****************************************************************************
 * Name: serial_rtt_setup
 ****************************************************************************/

static int serial_rtt_setup(FAR struct uart_dev_s *dev)
{
  return OK;
}

/****************************************************************************
 * Name: serial_rtt_shutdown
 ****************************************************************************/

static void serial_rtt_shutdown(FAR struct uart_dev_s *dev)
{
}

/****************************************************************************
 * Name: serial_rtt_attach
 ****************************************************************************/

static int serial_rtt_attach(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  wd_start(&rtt->wdog, USEC2TICK(CONFIG_SERIAL_RTT_POLLING_INTERVAL),
           serial_rtt_timeout, (wdparm_t)dev);
  return OK;
}

/****************************************************************************
 * Name: serial_rtt_detach
 ****************************************************************************/

static void serial_rtt_detach(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  wd_cancel(&rtt->wdog);
}

/****************************************************************************
 * Name: serial_rtt_ioctl
 ****************************************************************************/

static int serial_rtt_ioctl(FAR struct file *filep, int cmd,
                            unsigned long arg)
{
  return -ENOTTY;
}

/****************************************************************************
 * Name: serial_rtt_receive
 ****************************************************************************/

static int serial_rtt_receive(FAR struct uart_dev_s *dev,
                              FAR unsigned int *status)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  int ret;
  int ch;

  ret = SEGGER_RTT_ReadNoLock(rtt->channel, &ch, 1);
  *status = ret == 1 ? 0 : -EAGAIN;
  return ch;
}

/****************************************************************************
 * Name: serial_rtt_rxint
 ****************************************************************************/

static void serial_rtt_rxint(FAR struct uart_dev_s *dev, bool enable)
{
}

/****************************************************************************
 * Name: serial_rtt_rxavailable
 ****************************************************************************/

static bool serial_rtt_rxavailable(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  return SEGGER_RTT_HasData(rtt->channel) != 0;
}

/****************************************************************************
 * Name: serial_rtt_dmasend
 ****************************************************************************/

static void serial_rtt_dmasend(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  FAR struct uart_dmaxfer_s *xfer = &dev->dmatx;
  size_t len;

  SEGGER_RTT_BLOCK_IF_FIFO_FULL(rtt->channel);
  len = SEGGER_RTT_WriteNoLock(rtt->channel, xfer->buffer, xfer->length);
  if (len == xfer->length && xfer->nlength)
    {
      len += SEGGER_RTT_WriteNoLock(rtt->channel, xfer->nbuffer,
                                    xfer->nlength);
    }

  xfer->nbytes = len;
  uart_xmitchars_done(dev);
}

/****************************************************************************
 * Name: serial_rtt_dmareceive
 ****************************************************************************/

static void serial_rtt_dmareceive(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  FAR struct uart_dmaxfer_s *xfer = &dev->dmarx;
  size_t len;

  len = SEGGER_RTT_ReadNoLock(rtt->channel, xfer->buffer, xfer->length);
  if (len == xfer->length && xfer->nbuffer &&
      SEGGER_RTT_HasData(rtt->channel))
    {
      len += SEGGER_RTT_ReadNoLock(rtt->channel, xfer->nbuffer,
                                   xfer->nlength);
    }

  xfer->nbytes = len;
  uart_recvchars_done(dev);
}

/****************************************************************************
 * Name: serial_rtt_dmarxfree
 ****************************************************************************/

static void serial_rtt_dmarxfree(FAR struct uart_dev_s *dev)
{
  /* When the DMA buffer is empty, check whether there is data to read */

  if (serial_rtt_rxavailable(dev))
    {
      uart_recvchars_dma(dev);
    }
}

/****************************************************************************
 * Name: serial_rtt_dmatxavail
 ****************************************************************************/

static void serial_rtt_dmatxavail(FAR struct uart_dev_s *dev)
{
  if (serial_rtt_txready(dev))
    {
      uart_xmitchars_dma(dev);
    }
}

/****************************************************************************
 * Name: serial_rtt_send
 ****************************************************************************/

static void serial_rtt_send(FAR struct uart_dev_s *dev, int ch)
{
  FAR struct serial_rtt_s *rtt = dev->priv;

  SEGGER_RTT_BLOCK_IF_FIFO_FULL(rtt->channel);
  SEGGER_RTT_PutChar(rtt->channel, ch);
}

/****************************************************************************
 * Name: serial_rtt_txint
 ****************************************************************************/

static void serial_rtt_txint(FAR struct uart_dev_s *dev, bool enable)
{
}

/****************************************************************************
 * Name: serial_rtt_txready
 ****************************************************************************/

static bool serial_rtt_txready(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  return SEGGER_RTT_GetAvailWriteSpace(rtt->channel) != 0;
}

/****************************************************************************
 * Name: serial_rtt_txempty
 ****************************************************************************/

static bool serial_rtt_txempty(FAR struct uart_dev_s *dev)
{
  FAR struct serial_rtt_s *rtt = dev->priv;
  return SEGGER_RTT_GetBytesInBuffer(rtt->channel) == 0;
}

/****************************************************************************
 * Name: serial_rtt_timeout
 ****************************************************************************/

static void serial_rtt_timeout(wdparm_t arg)
{
  FAR struct serial_rtt_s *rtt = (FAR struct serial_rtt_s *)arg;

  serial_rtt_dmarxfree(&rtt->uart);
  serial_rtt_dmatxavail(&rtt->uart);
  wd_start(&rtt->wdog, USEC2TICK(CONFIG_SERIAL_RTT_POLLING_INTERVAL),
           serial_rtt_timeout, arg);
}

/****************************************************************************
 * Name: serial_rtt_register
 ****************************************************************************/

static void serial_rtt_register(FAR const char *name,
                                FAR struct serial_rtt_s *rtt)
{
  SEGGER_RTT_ConfigUpBuffer(rtt->channel, name, rtt->up_buffer,
                            rtt->uart.xmit.size,
                            SEGGER_RTT_MODE_NO_BLOCK_TRIM);
  SEGGER_RTT_ConfigDownBuffer(rtt->channel, name, rtt->down_buffer,
                              rtt->uart.recv.size,
                              SEGGER_RTT_MODE_NO_BLOCK_TRIM);

#ifdef CONFIG_SERIAL_RTT_CONSOLE
  if (rtt->uart.isconsole)
    {
      uart_register("/dev/console", &rtt->uart);
    }
#endif

  uart_register(name, &rtt->uart);
}

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

/****************************************************************************
 * serial_rtt_initialize
 ****************************************************************************/

void serial_rtt_initialize(void)
{
#ifdef CONFIG_SERIAL_RTT0
  serial_rtt_register("/dev/ttyR0", &g_serial_rtt0);
#endif

#ifdef CONFIG_SERIAL_RTT1
  serial_rtt_register("/dev/ttyR1", &g_serial_rtt1);
#endif

#ifdef CONFIG_SERIAL_RTT2
  serial_rtt_register("/dev/ttyR2", &g_serial_rtt2);
#endif
}