/****************************************************************************
 * arch/xtensa/src/esp32/esp32_wdt.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 <nuttx/arch.h>
#include <nuttx/irq.h>
#include <stdbool.h>
#include <assert.h>
#include <debug.h>
#include "xtensa.h"
#include "hardware/esp32_tim.h"
#include "hardware/esp32_rtccntl.h"
#include "esp32_wdt.h"
#include "esp32_irq.h"
#include "esp32_rtc.h"
#include "esp32_rtc_gpio.h"

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

/* Helpers for converting from Q13.19 fixed-point format to float */

#define N 19
#define Q_TO_FLOAT(x) ((float)x/(float)(1<<N))

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

struct esp32_wdt_priv_s
  {
    struct esp32_wdt_ops_s *ops;
    uint32_t                    base;    /* WDT register base address */
    uint8_t                     cpu;     /* CPU ID */
    uint8_t                     periph;  /* Peripheral ID */
    uint8_t                     irq;     /* Interrupt ID */
    int                         cpuint;  /* CPU interrupt assigned to this wdt */
    bool                        inuse;   /* Flag indicating if this wdt is in use */
  };

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

/* WDT registers access *****************************************************/

static void esp32_wdt_putreg(struct esp32_wdt_dev_s *dev,
                             uint32_t offset,
                             uint32_t value);
static void esp32_wdt_modifyreg32(struct esp32_wdt_dev_s *dev,
                                  uint32_t offset,
                                  uint32_t clearbits,
                                  uint32_t setbits);
static uint32_t esp32_wdt_getreg(struct esp32_wdt_dev_s *dev,
                                 uint32_t offset);

/* WDT operations ***********************************************************/

static int esp32_wdt_start(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_stop(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_enablewp(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_disablewp(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_pre(struct esp32_wdt_dev_s *dev, uint16_t value);
static int esp32_wdt_settimeout(struct esp32_wdt_dev_s *dev,
                                uint32_t value, uint8_t stage);
static int esp32_wdt_feed_dog(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_set_stg_conf(struct esp32_wdt_dev_s *dev,
                                   uint8_t stage, uint8_t conf);
static uint16_t esp32_rtc_clk(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_setisr(struct esp32_wdt_dev_s *dev, xcpt_t handler,
                            void * arg);
static int esp32_wdt_enableint(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_disableint(struct esp32_wdt_dev_s *dev);
static int esp32_wdt_ackint(struct esp32_wdt_dev_s *dev);

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

/* ESP32 WDT ops */

struct esp32_wdt_ops_s esp32_mwdt_ops =
{
  .start         = esp32_wdt_start,
  .stop          = esp32_wdt_stop,
  .enablewp      = esp32_wdt_enablewp,
  .disablewp     = esp32_wdt_disablewp,
  .pre           = esp32_wdt_pre,
  .settimeout    = esp32_wdt_settimeout,
  .feed          = esp32_wdt_feed_dog,
  .stg_conf      = esp32_wdt_set_stg_conf,
  .rtc_clk       = NULL,
  .setisr        = esp32_wdt_setisr,
  .enableint     = esp32_wdt_enableint,
  .disableint    = esp32_wdt_disableint,
  .ackint        = esp32_wdt_ackint,
};

struct esp32_wdt_ops_s esp32_rwdt_ops =
{
  .start         = esp32_wdt_start,
  .stop          = esp32_wdt_stop,
  .enablewp      = esp32_wdt_enablewp,
  .disablewp     = esp32_wdt_disablewp,
  .pre           = NULL,
  .settimeout    = esp32_wdt_settimeout,
  .feed          = esp32_wdt_feed_dog,
  .stg_conf      = esp32_wdt_set_stg_conf,
  .rtc_clk       = esp32_rtc_clk,
  .setisr        = esp32_wdt_setisr,
  .enableint     = esp32_wdt_enableint,
  .disableint    = esp32_wdt_disableint,
  .ackint        = esp32_wdt_ackint,
};

#ifdef CONFIG_ESP32_MWDT0

struct esp32_wdt_priv_s g_esp32_mwdt0_priv =
{
  .ops    = &esp32_mwdt_ops,
  .base   = TIMG_WDTCONFIG0_REG(0),
  .periph = ESP32_PERIPH_TG_WDT_LEVEL, /* Peripheral ID */
  .irq    = ESP32_IRQ_TG_WDT_LEVEL,    /* Interrupt ID */
  .cpuint = -ENOMEM,                   /* CPU interrupt assigned to this wdt */
  .inuse = false,
};
#endif

#ifdef CONFIG_ESP32_MWDT1

struct esp32_wdt_priv_s g_esp32_mwdt1_priv =
{
  .ops   = &esp32_mwdt_ops,
  .base  = TIMG_WDTCONFIG0_REG(1),
  .periph = ESP32_PERIPH_TG1_WDT_LEVEL, /* Peripheral ID */
  .irq    = ESP32_IRQ_TG1_WDT_LEVEL,    /* Interrupt ID */
  .cpuint = -ENOMEM,                    /* CPU interrupt assigned to this wdt */
  .inuse = false,
};
#endif

#ifdef CONFIG_ESP32_RWDT

struct esp32_wdt_priv_s g_esp32_rwdt_priv =
{
  .ops   = &esp32_rwdt_ops,
  .base  = RTC_CNTL_OPTIONS0_REG,
  .periph = ESP32_PERIPH_RTC_CORE,  /* Peripheral ID */
  .irq    = ESP32_IRQ_RTC_WDT,      /* Interrupt ID */
  .cpuint = -ENOMEM,                /* CPU interrupt assigned to this wdt */
  .inuse = false,
};
#endif

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

/****************************************************************************
 * Name: esp32_wdt_putreg
 *
 * Description:
 *   Put a 32-bit register value by offset
 *
 ****************************************************************************/

static void esp32_wdt_putreg(struct esp32_wdt_dev_s *dev,
                             uint32_t offset,
                             uint32_t value)
{
  DEBUGASSERT(dev);

  putreg32(value, ((struct esp32_wdt_priv_s *)dev)->base + offset);
}

/****************************************************************************
 * Name: esp32_wdt_modifyreg32
 *
 * Description:
 *   Modify a reg of 32 bits
 *
 ****************************************************************************/

static void esp32_wdt_modifyreg32(struct esp32_wdt_dev_s *dev,
                                  uint32_t offset,
                                  uint32_t clearbits,
                                  uint32_t setbits)
{
  DEBUGASSERT(dev);

  modifyreg32(((struct esp32_wdt_priv_s *)dev)->base + offset,
                clearbits, setbits);
}

/****************************************************************************
 * Name: esp32_wdt_getreg
 *
 * Description:
 *   Get a 32-bit register value by offset
 *
 ****************************************************************************/

static uint32_t esp32_wdt_getreg(struct esp32_wdt_dev_s *dev,
                                 uint32_t offset)
{
  DEBUGASSERT(dev);

  return getreg32(((struct esp32_wdt_priv_s *)dev)->base + offset);
}

/****************************************************************************
 * Name: esp32_wdt_start
 *
 * Description:
 *   Releases the counter
 *
 ****************************************************************************/

static int esp32_wdt_start(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base ==
        RTC_CNTL_OPTIONS0_REG)
    {
      esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET, 0, RTC_CNTL_WDT_EN);
    }

  /* If it is a MWDT */

  else
    {
      esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET, 0, TIMG_WDT_EN);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_set_stg_conf
 *
 * Description:
 *   It configures the action to be triggered by a stage on expiration.
 *   The available actions are:
 *   0 - off
 *   1 - interrupt
 *   2 - reset CPU
 *   3 - reset main System (CPU + Peripherals - RTC)
 *   4 - reset main System and RTC ( NOTE: only available for RWDT)
 *
 ****************************************************************************/

static int esp32_wdt_set_stg_conf(struct esp32_wdt_dev_s *dev,
                                   uint8_t stage, uint8_t conf)
{
  uint32_t mask;
  DEBUGASSERT(dev);

    switch (stage)
    {
      case 0:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
                RTC_CNTL_OPTIONS0_REG)
            {
              mask = (uint32_t)conf << RTC_CNTL_WDT_STG0_S;
              esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET,
                                    RTC_CNTL_WDT_STG0_M, mask);
            }

          /* If it is a MWDT */

          else
            {
              mask = (uint32_t)conf << TIMG_WDT_STG0_S;
              esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET,
                                    TIMG_WDT_STG0_M, mask);
            }
          break;
        }

      case 1:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
                RTC_CNTL_OPTIONS0_REG)
            {
              mask = (uint32_t)conf << RTC_CNTL_WDT_STG1_S;
              esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET,
                                    RTC_CNTL_WDT_STG1_M, mask);
            }

          /* If it is a MWDT */

          else
            {
              mask = (uint32_t)conf << TIMG_WDT_STG1_S;
              esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET,
                                    TIMG_WDT_STG1_M, mask);
            }
          break;
        }

      case 2:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
               RTC_CNTL_OPTIONS0_REG)
            {
              mask = (uint32_t)conf << RTC_CNTL_WDT_STG2_S;
              esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET,
                                    RTC_CNTL_WDT_STG2_M, mask);
            }

          /* If it is a MWDT */

          else
            {
              mask = (uint32_t)conf << TIMG_WDT_STG2_S;
              esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET,
                                    TIMG_WDT_STG2_M, mask);
            }
          break;
        }

      case 3:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
               RTC_CNTL_OPTIONS0_REG)
            {
              mask = (uint32_t)conf << RTC_CNTL_WDT_STG3_S;
              esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET,
                                    RTC_CNTL_WDT_STG3_M, mask);
            }

          /* If it is a MWDT */

          else
            {
              mask = (uint32_t)conf << TIMG_WDT_STG3_S;
              esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET,
                                    TIMG_WDT_STG3_M, mask);
            }
          break;
        }

      default:
        {
          tmrerr("ERROR: unsupported stage %d\n", stage);
          return -EINVAL;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_stop
 *
 * Description:
 *   Disables the watchdog
 *
 ****************************************************************************/

static int esp32_wdt_stop(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET, RTC_CNTL_WDT_EN, 0);
    }

  /* If it is a MWDT */

  else
    {
      esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET, TIMG_WDT_EN, 0);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_enablewp
 *
 * Description:
 *   Enables write protection (WP) on registers against accidental writing.
 *   TRM recommends to change any WDT register through this sequence:
 *   - Disable WP
 *   - Do the op
 *   - Re-enable WP
 *
 ****************************************************************************/

static int esp32_wdt_enablewp(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      esp32_wdt_putreg(dev, RWDT_WP_REG, 0);
    }

  /* If it is a MWDT */

  else
    {
      esp32_wdt_putreg(dev, MWDT_WP_REG, 0);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_disablewp
 *
 * Description:
 *   Disables write protection (WP) on registers against accidental writing.
 *   TRM recommends to change any WDT register through this sequence:
 *   - Disable WP
 *   - Do the op
 *   - Re-enable WP
 *
 ****************************************************************************/

static int esp32_wdt_disablewp(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      esp32_wdt_putreg(dev, RWDT_WP_REG, WRITE_PROTECTION_KEY);
    }

  /* If it is a MWDT */

  else
    {
      esp32_wdt_putreg(dev, MWDT_WP_REG, WRITE_PROTECTION_KEY);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_pre
 *
 * Description:
 *   Sets  a prescale value. The MWDT clock period is 12.5 ns * value (pre).
 *   NOTE: There's no prescaler register for RWDT and its source clock is
 *   clocked from the RTC slow clock.
 *
 ****************************************************************************/

static int esp32_wdt_pre(struct esp32_wdt_dev_s *dev, uint16_t pre)
{
  uint32_t mask = (uint32_t)pre << TIMG_WDT_CLK_PRESCALE_S;

  DEBUGASSERT(dev);

  esp32_wdt_modifyreg32(dev, MWDT_CLK_PRESCALE_OFFSET,
                        TIMG_WDT_CLK_PRESCALE_M, mask);

  return OK;
}

/****************************************************************************
 * Name: esp32_rtc_clk
 *
 * Description:
 *   Check the RTC clock source and return the necessary cycles to complete
 *   1 ms.
 *
 ****************************************************************************/

static uint16_t esp32_rtc_clk(struct esp32_wdt_dev_s *dev)
{
  enum esp32_rtc_slow_freq_e slow_clk_rtc;
  uint32_t period_13q19;
  float period;
  float cycles_ms;
  uint16_t cycles_ms_int;

  /* Calibration map: Maps each RTC SLOW_CLK source to the number
   * used to calibrate this source.
   */

  static const enum esp32_rtc_cal_sel_e cal_map[] =
  {
    RTC_CAL_RTC_MUX,
    RTC_CAL_32K_XTAL,
    RTC_CAL_8MD256
  };

  DEBUGASSERT(dev);

  /* Check which clock is sourcing the slow_clk_rtc */

  slow_clk_rtc = esp32_rtc_get_slow_clk();

  /* Get the slow_clk_rtc period in us in Q13.19 fixed point format */

  period_13q19 = esp32_rtc_clk_cal(cal_map[slow_clk_rtc],
                                   SLOW_CLK_CAL_CYCLES);

  /* Assert no error happened during the calibration */

  DEBUGASSERT(period_13q19 != 0);

  /* Convert from Q13.19 format to float */

  period = Q_TO_FLOAT(period_13q19);

  /* Get the number of cycles necessary to count 1 ms */

  cycles_ms = 1000.0 / period;

  /* Get the integer number of cycles */

  cycles_ms_int = (uint16_t)cycles_ms;

  return cycles_ms_int;
}

/****************************************************************************
 * Name: esp32_wdt_settimeout
 *
 * Description:
 *   Sets the wdt timeout.
 *
 ****************************************************************************/

static int esp32_wdt_settimeout(struct esp32_wdt_dev_s *dev,
                                uint32_t value, uint8_t stage)
{
  DEBUGASSERT(dev);

    switch (stage)
    {
      case 0:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
                RTC_CNTL_OPTIONS0_REG)
            {
              esp32_wdt_putreg(dev, RWDT_STAGE0_TIMEOUT_OFFSET, value);
            }

          /* If it is a MWDT */

          else
            {
              esp32_wdt_putreg(dev, MWDT_STAGE0_TIMEOUT_OFFSET, value);
            }
          break;
        }

      case 1:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
                RTC_CNTL_OPTIONS0_REG)
            {
              esp32_wdt_putreg(dev, RWDT_STAGE1_TIMEOUT_OFFSET, value);
            }

          /* If it is a MWDT */

          else
            {
              esp32_wdt_putreg(dev, MWDT_STAGE1_TIMEOUT_OFFSET, value);
            }
          break;
        }

      case 2:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
                RTC_CNTL_OPTIONS0_REG)
            {
              esp32_wdt_putreg(dev, RWDT_STAGE2_TIMEOUT_OFFSET, value);
            }

          /* If it is a MWDT */

          else
            {
              esp32_wdt_putreg(dev, MWDT_STAGE2_TIMEOUT_OFFSET, value);
            }
          break;
        }

      case 3:
        {
          /* If it is a RWDT */

          if (((struct esp32_wdt_priv_s *)dev)->base ==
                RTC_CNTL_OPTIONS0_REG)
            {
              esp32_wdt_putreg(dev, RWDT_STAGE3_TIMEOUT_OFFSET, value);
            }

          /* If it is a MWDT */

          else
            {
              esp32_wdt_putreg(dev, MWDT_STAGE3_TIMEOUT_OFFSET, value);
            }
          break;
        }

      default:
        {
          tmrerr("ERROR: unsupported stage %d\n", stage);
          return -EINVAL;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_feed_dog
 *
 * Description:
 *   Feeds the dog.  When software feeds the watchdog timer, it returns to
 *   stage 0 and its counter restarts from 0.
 *
 ****************************************************************************/

static int esp32_wdt_feed_dog(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      esp32_wdt_putreg(dev, RWDT_FEED_OFFSET , FEED_DOG);
    }

  /* If it is a MWDT */

  else
    {
      esp32_wdt_putreg(dev, MWDT_FEED_OFFSET , FEED_DOG);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_setisr
 *
 * Description:
 *   Allocates a Level CPU Interrupt, connects the peripheral source to this
 *   Interrupt, register the callback and enables the Interruption. It does
 *   the opposite if the handler and arg are NULL.
 *
 ****************************************************************************/

static int esp32_wdt_setisr(struct esp32_wdt_dev_s *dev, xcpt_t handler,
                            void *arg)
{
  struct esp32_wdt_priv_s *wdt = NULL;
  int ret = OK;

  DEBUGASSERT(dev);

  wdt = (struct esp32_wdt_priv_s *)dev;

  /* Disable interrupt when callback is removed */

  if (handler == NULL)
    {
      /* If a CPU Interrupt was previously allocated, then deallocate it */

#ifdef CONFIG_ESP32_RWDT
      if (wdt->irq == ESP32_IRQ_RTC_WDT)
        {
          esp32_rtcioirqdisable(wdt->irq);
          irq_detach(wdt->irq);
        }
      else
#endif
        {
          if (wdt->cpuint >= 0)
            {
              /* Disable CPU Interrupt, free a previously allocated
               * CPU Interrupt
               */

              up_disable_irq(wdt->irq);
              esp32_teardown_irq(wdt->cpu, wdt->periph, wdt->cpuint);
              irq_detach(wdt->irq);
            }

          goto errout;
        }
    }

  /* Otherwise set callback and enable interrupt */

  else
    {
      /* Set up to receive peripheral interrupts on the current CPU */

#ifdef CONFIG_ESP32_RWDT
      if (wdt->irq == ESP32_IRQ_RTC_WDT)
        {
          ret = irq_attach(wdt->irq, handler, arg);

          if (ret != OK)
            {
              esp32_rtcioirqdisable(wdt->irq);
              tmrerr("ERROR: Failed to associate an IRQ Number");
              goto errout;
            }

          esp32_rtcioirqenable(wdt->irq);
        }
      else
#endif
        {
          wdt->cpu = this_cpu();
          wdt->cpuint = esp32_setup_irq(wdt->cpu, wdt->periph,
                                        1, ESP32_CPUINT_LEVEL);
          if (wdt->cpuint < 0)
            {
              tmrerr("ERROR: No CPU Interrupt available");
              ret = wdt->cpuint;
              goto errout;
            }

          /* Associate an IRQ Number (from the WDT) to an ISR */

          ret = irq_attach(wdt->irq, handler, arg);

          if (ret != OK)
            {
              esp32_teardown_irq(wdt->cpu, wdt->periph, wdt->cpuint);
              tmrerr("ERROR: Failed to associate an IRQ Number");
              goto errout;
            }

          /* Enable the CPU Interrupt that is linked to the wdt */

          up_enable_irq(wdt->irq);
        }
    }

errout:
  return ret;
}

/****************************************************************************
 * Name: esp32_wdt_enableint
 *
 * Description:
 *   Enables a Level Interrupt at timeout.
 *
 ****************************************************************************/

static int esp32_wdt_enableint(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* Set the level interrupt bit */

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      /* Level Interrupt */

      esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET, 0,
                            RTC_CNTL_WDT_LEVEL_INT_EN);

      /* Enable Interrupt */

      esp32_wdt_modifyreg32(dev, RWDT_INT_ENA_REG_OFFSET, 0,
                            RTC_CNTL_WDT_INT_ENA);
    }

  /* If it is a MWDT */

  else
    {
      /* Level Interrupt */

      esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET, 0,
                            TIMG_WDT_LEVEL_INT_EN);

      /* Enable Interrupt */

      esp32_wdt_modifyreg32(dev, MWDT_INT_ENA_REG_OFFSET, 0,
                            TIMG_WDT_INT_ENA);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_disableint
 *
 * Description:
 *   Disables a Level Interrupt at timeout.
 *
 ****************************************************************************/

static int esp32_wdt_disableint(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      /* Level Interrupt */

      esp32_wdt_modifyreg32(dev, RWDT_CONFIG0_OFFSET,
                            RTC_CNTL_WDT_LEVEL_INT_EN, 0);

      /* Enable Interrupt */

      esp32_wdt_modifyreg32(dev, RWDT_INT_ENA_REG_OFFSET,
                            RTC_CNTL_WDT_INT_ENA, 0);
    }

  /* If it is a MWDT */

  else
    {
      /* Level Interrupt */

      esp32_wdt_modifyreg32(dev, MWDT_CONFIG0_OFFSET,
                            TIMG_WDT_LEVEL_INT_EN, 0);

      /* Enable Interrupt */

      esp32_wdt_modifyreg32(dev, MWDT_INT_ENA_REG_OFFSET,
                            TIMG_WDT_INT_ENA, 0);
    }

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_ackint
 *
 *   Description:
 *   Acknowledges an interrupt
 *
 ****************************************************************************/

static int esp32_wdt_ackint(struct esp32_wdt_dev_s *dev)
{
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base == RTC_CNTL_OPTIONS0_REG)
    {
      esp32_wdt_putreg(dev, RWDT_INT_CLR_REG_OFFSET, RTC_CNTL_WDT_INT_CLR);
    }

  /* If it is a MWDT */

  else
    {
      esp32_wdt_putreg(dev, MWDT_INT_CLR_REG_OFFSET, TIMG_WDT_INT_CLR);
    }

  return OK;
}

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

/****************************************************************************
 * Name: esp32_wdt_init
 *
 * Description:
 *   Initialize WDT device
 *
 ****************************************************************************/

struct esp32_wdt_dev_s *esp32_wdt_init(uint8_t wdt_id)
{
  struct esp32_wdt_priv_s *wdt = NULL;

  /* Get wdt instance */

  switch (wdt_id)
    {
#ifdef CONFIG_ESP32_MWDT0
      case 0:
        {
          wdt = &g_esp32_mwdt0_priv;
          break;
        }

#endif
#ifdef CONFIG_ESP32_MWDT1
      case 1:
        {
          wdt = &g_esp32_mwdt1_priv;
          break;
        }

#endif
#ifdef CONFIG_ESP32_RWDT
      case 2:
        {
          wdt = &g_esp32_rwdt_priv;

          /* If RTC was not initialized in a previous
           * stage by the PM or by clock_initialize()
           * Then, init the RTC clock configuration here.
           */

#if !defined(CONFIG_PM) && !defined(CONFIG_RTC)
          /* Initialize RTC controller parameters */

          esp32_rtc_init();
          esp32_rtc_clk_set();
#endif
          break;
        }

#endif
      default:
        {
          tmrerr("ERROR: unsupported WDT %d\n", wdt_id);
          return NULL;
        }
    }

  /* If some code is using it then sends an error message,
   * Otherwise, inform it has been used.
   */

  if (wdt->inuse == true)
    {
      tmrerr("ERROR: WDT %d is already in use\n", wdt_id);
      return NULL;
    }
  else
    {
      wdt->inuse = true;
    }

  return (struct esp32_wdt_dev_s *)wdt;
}

/****************************************************************************
 * Name: esp32_wdt_early_deinit
 *
 * Description:
 *   Disable the WDT(s) that was/were enabled by the bootloader.
 *
 ****************************************************************************/

void esp32_wdt_early_deinit(void)
{
  uint32_t regval;
  putreg32(RTC_CNTL_WDT_WKEY_VALUE, RTC_CNTL_WDTWPROTECT_REG);
  regval  = getreg32(RTC_CNTL_WDTCONFIG0_REG);
  regval &= ~RTC_CNTL_WDT_EN;
  putreg32(regval, RTC_CNTL_WDTCONFIG0_REG);
  putreg32(0, RTC_CNTL_WDTWPROTECT_REG);
}

/****************************************************************************
 * Name: esp32_wdt_deinit
 *
 * Description:
 *   Deinit WDT device
 *
 ****************************************************************************/

int esp32_wdt_deinit(struct esp32_wdt_dev_s *dev)
{
  struct esp32_wdt_priv_s *wdt = NULL;

  DEBUGASSERT(dev);

  wdt = (struct esp32_wdt_priv_s *)dev;

  wdt->inuse = false;

  return OK;
}

/****************************************************************************
 * Name: esp32_wdt_is_running
 *
 * Description:
 *   Checks if the wdt was already turned on. For example, RTC may has been
 *   enabled in bootloader.
 *
 ****************************************************************************/

bool esp32_wdt_is_running(struct esp32_wdt_dev_s *dev)
{
  uint32_t status = 0;
  DEBUGASSERT(dev);

  /* If it is a RWDT */

  if (((struct esp32_wdt_priv_s *)dev)->base ==
        RTC_CNTL_OPTIONS0_REG)
    {
      status = esp32_wdt_getreg(dev, RWDT_CONFIG0_OFFSET);
      if (status & RTC_CNTL_WDT_EN)
        {
          return true;
        }
    }

  /* If it is a MWDT */

  else
    {
      status = esp32_wdt_getreg(dev, MWDT_CONFIG0_OFFSET);
      if (status & TIMG_WDT_EN)
        {
          return true;
        }
    }

  return false;
}