/*
 * SPDX-FileCopyrightText: 2016 Cesanta Software Limited
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * SPDX-FileContributor: 2016-2022 Espressif Systems (Shanghai) CO LTD
 */

#include <stdlib.h>
#include "stub_io.h"
#include "rom_functions.h"
#include "soc_support.h"


#define UART_RX_INTS (UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA)


static void(*s_rx_cb_func)(char);
#ifdef WITH_USB_OTG
static uint32_t s_cdcacm_old_rts;
static volatile bool s_cdcacm_reset_requested;
static char s_cdcacm_txbuf[ACM_BYTES_PER_TX];
static size_t s_cdcacm_txpos;
#endif // WITH_USB_OTG


void uart_isr(void *arg) {
  uint32_t int_st = READ_REG(UART_INT_ST(0));
  while (1) {
    uint32_t fifo_len = READ_REG(UART_STATUS(0)) & UART_RXFIFO_CNT_M;
    if (fifo_len == 0) {
      break;
    }
    while (fifo_len-- > 0) {
      uint8_t byte = READ_REG(UART_FIFO(0)) & 0xff;
      (*s_rx_cb_func)(byte);
    }
  }
  WRITE_REG(UART_INT_CLR(0), int_st);
}

#if WITH_USB_JTAG_SERIAL
bool stub_uses_usb_jtag_serial(void)
{
  UartDevice *uart = GetUartDevice();

  /* buff_uart_no indicates which UART is used for SLIP communication) */
  return uart->buff_uart_no == UART_USB_JTAG_SERIAL;
}

void jtag_serial_isr(void *arg)
{
    WRITE_REG(USB_DEVICE_INT_CLR_REG, USB_DEVICE_SERIAL_OUT_RECV_PKT_INT_CLR); //ack interrupt
    while (READ_REG(USB_DEVICE_EP1_CONF_REG) & USB_DEVICE_SERIAL_OUT_EP_DATA_AVAIL)
    {
      uint8_t byte = READ_REG(USB_DEVICE_EP1_REG);
      (*s_rx_cb_func)(byte);
    }
}
#endif // WITH_USB_JTAG_SERIAL

static void stub_configure_rx_uart(void)
{
  /* All UART reads come via uart_isr or jtag_serial_isr */
#if WITH_USB_JTAG_SERIAL
  if (stub_uses_usb_jtag_serial()) {
    #if IS_RISCV
      WRITE_REG(INTERRUPT_CORE0_USB_INTR_MAP_REG, ETS_USB_INUM);  // Route USB interrupt to CPU
      esprv_intc_int_set_priority(ETS_USB_INUM, 1);
    #else
      WRITE_REG(INTERRUPT_CORE0_USB_DEVICE_INT_MAP_REG, ETS_USB_INUM);
    #endif // IS_RISCV
    ets_isr_attach(ETS_USB_INUM, jtag_serial_isr, NULL);
    REG_SET_MASK(USB_DEVICE_INT_ENA_REG, USB_DEVICE_SERIAL_OUT_RECV_PKT_INT_ENA);
    ets_isr_unmask(1 << ETS_USB_INUM);
    return;
  }
#endif // WITH_USB_JTAG_SERIAL
  ets_isr_attach(ETS_UART0_INUM, uart_isr, NULL);
  REG_SET_MASK(UART_INT_ENA(0), UART_RX_INTS);
  ets_isr_unmask(1 << ETS_UART0_INUM);
}

#ifdef WITH_USB_OTG

void stub_cdcacm_cb(cdc_acm_device *dev, int status)
{
  if (status == ACM_STATUS_RX) {
    while (cdc_acm_rx_fifo_cnt(uart_acm_dev) > 0) {
      uint8_t c;
      cdc_acm_fifo_read(uart_acm_dev, &c, 1);
      (*s_rx_cb_func)((char) c);
    }
  } else if (status == ACM_STATUS_LINESTATE_CHANGED) {
    uint32_t rts = 0;
    cdc_acm_line_ctrl_get(dev, LINE_CTRL_RTS, &rts);
    if (rts == 0 && s_cdcacm_old_rts == 1) {
      s_cdcacm_reset_requested = true;
    }
    s_cdcacm_old_rts = rts;
  }
}

static void stub_cdcacm_flush(void)
{
    cdc_acm_fifo_fill(uart_acm_dev, (const uint8_t *) s_cdcacm_txbuf, s_cdcacm_txpos);
    /* return value ignored — if bootloader fails to log something, proceed anyway */
    s_cdcacm_txpos = 0;
}

static void stub_cdcacm_write_char(char ch)
{
    s_cdcacm_txbuf[s_cdcacm_txpos++] = ch;
    if (ch == '\xc0' || s_cdcacm_txpos == sizeof(s_cdcacm_txbuf)) {
        stub_cdcacm_flush();
    }
}

bool stub_uses_usb_otg(void)
{
  UartDevice *uart = GetUartDevice();

  /* buff_uart_no indicates which UART is used for SLIP communication) */
  return uart->buff_uart_no == UART_USB_OTG;
}

#ifdef ESP32S3
static void usb_dw_isr_handler_wrapper(void *arg)
{
    /* ISR handler wrapper added as a workaround for the failure of compressed flashing using USB OTG.
     *
     * LoadStoreException happens inside the first call of tinfl_decompress() where the a13 register gets corrupted by
     * the address of usb_dw_isr_handler(). The corruption probably still happens with this workaround but because of
     * the nested call another register window is used.
     *
     * Other possible workarounds:
     * - wait at least 25 ms before the tinfl_decompress() so usb_dw_isr_handler() would finish and not corrupt the
     *   pointer inside of tinfl_decompress(), or
     * - disable the USB interrupt during tinfl_decompress().
     */
    usb_dw_isr_handler(arg);
}
#else
#define usb_dw_isr_handler_wrapper usb_dw_isr_handler
#endif //ESP32S3

static void stub_configure_rx_usb(void)
{
  cdc_acm_line_ctrl_get(uart_acm_dev, LINE_CTRL_RTS, &s_cdcacm_old_rts);
  #if ESP32S2
    intr_matrix_set(0, ETS_USB_INTR_SOURCE, ETS_USB_INUM);
  #elif ESP32S3
    WRITE_REG(INTERRUPT_CORE0_USB_INTR_MAP_REG, ETS_USB_INUM);
  #endif
  ets_isr_attach(ETS_USB_INUM, usb_dw_isr_handler_wrapper, NULL);
  ets_isr_unmask(1 << ETS_USB_INUM);
  cdc_acm_irq_callback_set(uart_acm_dev, &stub_cdcacm_cb);
  cdc_acm_irq_rx_enable(uart_acm_dev);
  cdc_acm_irq_state_enable(uart_acm_dev);
  REG_SET_MASK(USB_GAHBCFG_REG, USB_GLBLLNTRMSK);
}
#endif // WITH_USB_OTG

void stub_tx_one_char(char c)
{
#if WITH_USB_OTG
  if (stub_uses_usb_otg()) {
    stub_cdcacm_write_char(c);
    return;
  }
#endif // WITH_USB_OTG
  uart_tx_one_char(c);
#if WITH_USB_JTAG_SERIAL
  if (stub_uses_usb_jtag_serial()){
    stub_tx_flush();
  }
#endif // WITH_USB_JTAG_SERIAL
}

void stub_tx_flush(void)
{
#if WITH_USB_OTG
  if (stub_uses_usb_otg()) {
    if (s_cdcacm_txpos > 0) {
      stub_cdcacm_flush();
    }
  }
#endif // WITH_USB_OTG
#if WITH_USB_JTAG_SERIAL
  if (stub_uses_usb_jtag_serial()){
      uart_tx_flush(UART_USB_JTAG_SERIAL);
      return;
  }
#endif // WITH_USB_JTAG_SERIAL
#if ESP32_OR_LATER
  uart_tx_flush(0);
#endif
}

char stub_rx_one_char(void)
{
  char c = 0;
  /* Using uart_rx_one_char here instead of uart_rx_one_char_block,
   * because the latter simply returns (char) 0 if no bytes
   * are available, when used with USB CDC.
   */
  while (uart_rx_one_char((uint8_t*) &c) != 0) { }
  return c;
}

void stub_rx_async_enable(bool enable)
{
  uint32_t mask;
#if WITH_USB_OTG
  if (stub_uses_usb_otg()) {
    mask = 1 << ETS_USB_INUM;
    if (enable) {
      cdc_acm_irq_rx_enable(uart_acm_dev);
      ets_isr_unmask(mask);
    } else {
      ets_isr_mask(mask);
      cdc_acm_irq_rx_disable(uart_acm_dev);
    }
    return;
  }
#endif // WITH_USB_OTG
#if WITH_USB_JTAG_SERIAL
  mask = stub_uses_usb_jtag_serial() ? 1 << ETS_USB_INUM : 1 << ETS_UART0_INUM;
#else
  mask = 1 << ETS_UART0_INUM;
#endif
  if (enable) {
    ets_isr_unmask(mask);
  } else {
    ets_isr_mask(mask);
  }
}

void stub_io_idle_hook(void)
{
#if WITH_USB_OTG
  if (s_cdcacm_reset_requested)
  {
    s_cdcacm_reset_requested = false;
    ets_isr_mask(1 << ETS_USB_INUM);
    ets_delay_us(10000);
    /* Handle the last few interrupts as they come in before the USB peripheral is idle */
    usb_dc_check_poll_for_interrupts();
    REG_CLR_MASK(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
    chip_usb_set_persist_flags(USBDC_PERSIST_ENA);
    usb_dc_prepare_persist();
    software_reset_cpu(0);
  }
#endif // WITH_USB_OTG
}

void stub_io_init(void(*rx_cb_func)(char))
{
  s_rx_cb_func = rx_cb_func;
#if WITH_USB_OTG
  if (stub_uses_usb_otg()) {
    stub_configure_rx_usb();
    return;
  }
#endif // WITH_USB_OTG
  stub_configure_rx_uart();
}

static uint32_t get_new_uart_divider(uint32_t current_baud, uint32_t new_baud)
{
  uint32_t master_freq;
  /* ESP32 has ROM code to detect the crystal freq but ESP8266 does not have this...
     So instead we use the previously auto-synced 115200 baud rate (which we know
     is correct wrt the relative crystal accuracy of the ESP & the USB/serial adapter).
     From this we can estimate crystal freq, and update for a new baud rate relative to that.
  */
  uint32_t uart_reg = READ_REG(UART_CLKDIV_REG(0));
  uint32_t uart_div = uart_reg & UART_CLKDIV_M;
#if ESP32_OR_LATER
  // account for fractional part of divider (bottom 4 bits)
  uint32_t fraction = (uart_reg >> UART_CLKDIV_FRAG_S) & UART_CLKDIV_FRAG_V;
  uart_div = (uart_div << 4) + fraction;
#endif
  master_freq = uart_div * current_baud;
  return master_freq / new_baud;
}

void stub_io_set_baudrate(uint32_t current_baud, uint32_t new_baud)
{
#if WITH_USB_OTG
  /* Technically no harm in increasing UART baud rate when communicating over USB,
   * however for debugging the USB part it is occasionally useful to ets_printf
   * something to UART. Not changing the baud rate helps in such case.
   */
  if (stub_uses_usb_otg()) {
    return;
  }
#endif // WITH_USB_OTG
  ets_delay_us(10000);
  uart_div_modify(0, get_new_uart_divider(current_baud, new_baud));
  ets_delay_us(1000);
}
