/****************************************************************************
 * arch/arm/src/stm32h7/stm32_rtc.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 <stdbool.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/time.h>

#include "arm_internal.h"
#include "stm32_rcc.h"
#include "stm32_pwr.h"
#include "stm32_exti.h"
#include "stm32_rtc.h"

#include <arch/board/board.h>

#ifdef CONFIG_STM32H7_RTC

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

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

/* This RTC implementation supports
 *  - date/time RTC hardware
 *  - extended functions Alarm A and B
 * */

#ifndef CONFIG_RTC_DATETIME
#  error "CONFIG_RTC_DATETIME must be set to use this driver"
#endif

#ifdef CONFIG_RTC_HIRES
#  error "CONFIG_RTC_HIRES must NOT be set with this driver"
#endif

#ifndef CONFIG_STM32H7_PWR
#  error "CONFIG_STM32H7_PWR must selected to use this driver"
#endif

/* Constants ****************************************************************/

#if defined(CONFIG_STM32H7_RTC_HSECLOCK)
#  define  RCC_BDCR_RTCSEL RCC_BDCR_RTCSEL_HSE
#elif defined(CONFIG_STM32H7_RTC_LSICLOCK)
#  define  RCC_BDCR_RTCSEL RCC_BDCR_RTCSEL_LSI
#elif defined(CONFIG_STM32H7_RTC_LSECLOCK)
#  define  RCC_BDCR_RTCSEL RCC_BDCR_RTCSEL_LSE
#else
#  warning "RCC_BDCR_RTCSEL_NOCLK has been selected - RTC will not count"
#endif

#define SYNCHRO_TIMEOUT      (0x00020000)
#define INITMODE_TIMEOUT     (0x00010000)

/* Time conversions */

#define MINUTES_IN_HOUR 60
#define HOURS_IN_DAY 24

#define hours_add(parm_hrs) \
  time->tm_hour += parm_hrs;\
  if ((HOURS_IN_DAY-1) < (time->tm_hour))\
    {\
      time->tm_hour = (parm_hrs - HOURS_IN_DAY);\
    }

#define RTC_ALRMR_DIS_MASK            (RTC_ALRMR_MSK4 | RTC_ALRMR_MSK3 | \
                                       RTC_ALRMR_MSK2 | RTC_ALRMR_MSK1)
#define RTC_ALRMR_DIS_DATE_MASK       (RTC_ALRMR_MSK4)
#define RTC_ALRMR_ENABLE              (0)

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

#ifdef CONFIG_RTC_ALARM
typedef unsigned int rtc_alarmreg_t;

struct alm_cbinfo_s
{
  volatile alm_callback_t ac_cb; /* Client callback function */
  volatile void *ac_arg;         /* Argument to pass with the callback function */
};
#endif

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

#ifdef CONFIG_RTC_ALARM
/* Callback to use when an EXTI is activated  */

static struct alm_cbinfo_s g_alarmcb[RTC_ALARM_LAST];
#endif

#ifdef CONFIG_RTC_PERIODIC
static wakeupcb_t g_wakeupcb;
static bool g_wakeup_enabled;  /* True: Wakeup interrupts are enabled */
#endif

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

/* g_rtc_enabled is set true after the RTC has successfully initialized */

volatile bool g_rtc_enabled = false;

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

#ifdef CONFIG_RTC_ALARM
static int rtchw_check_alrawf(void);
static int rtchw_set_alrmar(rtc_alarmreg_t alarmreg);
#if CONFIG_RTC_NALARMS > 1
static int rtchw_check_alrbwf(void);
static int rtchw_set_alrmbr(rtc_alarmreg_t alarmreg);
#endif
#endif

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

/****************************************************************************
 * Name: rtc_dumpregs
 ****************************************************************************/

#ifdef CONFIG_DEBUG_RTC_INFO
static void rtc_dumpregs(const char *msg)
{
  int rtc_state;

  rtcinfo("%s:\n", msg);
  rtcinfo("      TR: %08x\n", getreg32(STM32_RTC_TR));
  rtcinfo("      DR: %08x\n", getreg32(STM32_RTC_DR));
  rtcinfo("      CR: %08x\n", getreg32(STM32_RTC_CR));
  rtcinfo("     ISR: %08x\n", getreg32(STM32_RTC_ISR));
  rtcinfo("    PRER: %08x\n", getreg32(STM32_RTC_PRER));
  rtcinfo("    WUTR: %08x\n", getreg32(STM32_RTC_WUTR));
  rtcinfo("  ALRMAR: %08x\n", getreg32(STM32_RTC_ALRMAR));
  rtcinfo("  ALRMBR: %08x\n", getreg32(STM32_RTC_ALRMBR));
  rtcinfo("  SHIFTR: %08x\n", getreg32(STM32_RTC_SHIFTR));
  rtcinfo("    TSTR: %08x\n", getreg32(STM32_RTC_TSTR));
  rtcinfo("    TSDR: %08x\n", getreg32(STM32_RTC_TSDR));
  rtcinfo("   TSSSR: %08x\n", getreg32(STM32_RTC_TSSSR));
  rtcinfo("    CALR: %08x\n", getreg32(STM32_RTC_CALR));
  rtcinfo("  TAMPCR: %08x\n", getreg32(STM32_RTC_TAMPCR));
  rtcinfo("ALRMASSR: %08x\n", getreg32(STM32_RTC_ALRMASSR));
  rtcinfo("ALRMBSSR: %08x\n", getreg32(STM32_RTC_ALRMBSSR));
  rtcinfo("MAGICREG: %08x\n", getreg32(RTC_MAGIC_REG));

  rtc_state =
    ((getreg32(STM32_EXTI_RTSR1)   & EXTI_RTC_ALARM) ? 0x1000 : 0) |
    ((getreg32(STM32_EXTI_FTSR1)   & EXTI_RTC_ALARM) ? 0x0100 : 0) |
    ((getreg32(STM32_EXTI_CPUIMR1) & EXTI_RTC_ALARM) ? 0x0010 : 0) |
    ((getreg32(STM32_EXTI_CPUEMR1) & EXTI_RTC_ALARM) ? 0x0001 : 0);
  rtcinfo("EXTI (RTSR1 FTSR1 ISR EVT): %01x\n", rtc_state);
}
#else
#  define rtc_dumpregs(msg)
#endif

/****************************************************************************
 * Name: rtc_dumptime
 ****************************************************************************/

#ifdef CONFIG_DEBUG_RTC_INFO
static void rtc_dumptime(const struct tm *tp, const uint32_t *usecs,
                         const char *msg)
{
  rtcinfo("%s:\n", msg);
  rtcinfo("  tm_sec: %08x\n", tp->tm_sec);
  rtcinfo("  tm_min: %08x\n", tp->tm_min);
  rtcinfo(" tm_hour: %08x\n", tp->tm_hour);
  rtcinfo(" tm_mday: %08x\n", tp->tm_mday);
  rtcinfo("  tm_mon: %08x\n", tp->tm_mon);
  rtcinfo(" tm_year: %08x\n", tp->tm_year);

  if (usecs != NULL)
    {
      rtcinfo("   usecs: %08x\n", (unsigned int)*usecs);
    }
}
#else
#  define rtc_dumptime(tp, usecs, msg)
#endif

/****************************************************************************
 * Name: rtc_wprunlock
 *
 * Description:
 *    Disable RTC write protection
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void rtc_wprunlock(void)
{
  /* Enable write access to the backup domain (RTC registers, RTC backup data
   * registers and backup SRAM).
   */

  stm32_pwr_enablebkp(true);

  /* The following steps are required to unlock the write protection on all
   * the RTC registers (except for RTC_ISR[13:8], RTC_TAFCR, and RTC_BKPxR).
   *
   * 1. Write 0xca into the RTC_WPR register.
   * 2. Write 0x53 into the RTC_WPR register.
   *
   * Writing a wrong key re-activates the write protection.
   */

  putreg32(0xca, STM32_RTC_WPR);
  putreg32(0x53, STM32_RTC_WPR);
}

/****************************************************************************
 * Name: rtc_wprlock
 *
 * Description:
 *    Enable RTC write protection
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static inline void rtc_wprlock(void)
{
  /* Writing any wrong key re-activates the write protection. */

  putreg32(0xff, STM32_RTC_WPR);

  /* Disable write access to the backup domain (RTC registers, RTC backup
   * data registers and backup SRAM).
   */

  stm32_pwr_enablebkp(false);
}

/****************************************************************************
 * Name: rtc_synchwait
 *
 * Description:
 *   Waits until the RTC Time and Date registers (RTC_TR and RTC_DR) are
 *   synchronized with RTC APB clock.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

static int rtc_synchwait(void)
{
  volatile uint32_t timeout;
  uint32_t regval;
  int ret;

  /* Disable the write protection for RTC registers */

  rtc_wprunlock();

  /* Clear Registers synchronization flag (RSF) */

  regval  = getreg32(STM32_RTC_ISR);
  regval &= ~RTC_ISR_RSF;
  putreg32(regval, STM32_RTC_ISR);

  /* Now wait the registers to become synchronised */

  ret = -ETIMEDOUT;
  for (timeout = 0; timeout < SYNCHRO_TIMEOUT; timeout++)
    {
      regval = getreg32(STM32_RTC_ISR);
      if ((regval & RTC_ISR_RSF) != 0)
        {
          /* Synchronized */

          ret = OK;
          break;
        }
    }

  /* Re-enable the write protection for RTC registers */

  rtc_wprlock();
  return ret;
}

/****************************************************************************
 * Name: rtc_enterinit
 *
 * Description:
 *   Enter RTC initialization mode.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

static int rtc_enterinit(void)
{
  volatile uint32_t timeout;
  uint32_t regval;
  int ret;

  /* Check if the Initialization mode is already set */

  regval = getreg32(STM32_RTC_ISR);

  ret = OK;
  if ((regval & RTC_ISR_INITF) == 0)
    {
      /* Set the Initialization mode */

      putreg32(RTC_ISR_INIT, STM32_RTC_ISR);

      /* Wait until the RTC is in the INIT state (or a timeout occurs) */

      ret = -ETIMEDOUT;
      for (timeout = 0; timeout < INITMODE_TIMEOUT; timeout++)
        {
          regval = getreg32(STM32_RTC_ISR);
          if ((regval & RTC_ISR_INITF) != 0)
            {
              ret = OK;
              break;
            }
        }
    }

  return ret;
}

/****************************************************************************
 * Name: rtc_exitinit
 *
 * Description:
 *   Exit RTC initialization mode.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

static void rtc_exitinit(void)
{
  uint32_t regval;

  regval = getreg32(STM32_RTC_ISR);
  regval &= ~(RTC_ISR_INIT);
  putreg32(regval, STM32_RTC_ISR);
}

/****************************************************************************
 * Name: rtc_bin2bcd
 *
 * Description:
 *   Converts a 2 digit binary to BCD format
 *
 * Input Parameters:
 *   value - The byte to be converted.
 *
 * Returned Value:
 *   The value in BCD representation
 *
 ****************************************************************************/

static uint32_t rtc_bin2bcd(int value)
{
  uint32_t msbcd = 0;

  while (value >= 10)
    {
      msbcd++;
      value -= 10;
    }

  return (msbcd << 4) | value;
}

/****************************************************************************
 * Name: rtc_bin2bcd
 *
 * Description:
 *   Convert from 2 digit BCD to binary.
 *
 * Input Parameters:
 *   value - The BCD value to be converted.
 *
 * Returned Value:
 *   The value in binary representation
 *
 ****************************************************************************/

static int rtc_bcd2bin(uint32_t value)
{
  uint32_t tens = (value >> 4) * 10;
  return (int)(tens + (value & 0x0f));
}

/****************************************************************************
 * Name: rtc_setup
 *
 * Description:
 *   Performs first time configuration of the RTC.  A special value written
 *   into back-up register 0 will prevent this function from being called on
 *   sub-sequent resets or power up.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

static int rtc_setup(void)
{
  uint32_t regval;
  int ret;

  /* Disable the write protection for RTC registers */

  rtc_wprunlock();

  /* Set Initialization mode */

  ret = rtc_enterinit();
  if (ret == OK)
    {
      /* Set the 24 hour format by clearing the FMT bit in the RTC
       * control register
       */

      regval = getreg32(STM32_RTC_CR);
      regval &= ~RTC_CR_FMT;
      putreg32(regval, STM32_RTC_CR);

      /* Configure RTC pre-scaler with the required values */

#ifdef CONFIG_STM32H7_RTC_HSECLOCK
      /* STMicro app note AN4759 suggests using 7999 and 124 to
       * get exactly 1MHz when using the RTC at 8MHz.
       */

      putreg32(((uint32_t)7999 << RTC_PRER_PREDIV_S_SHIFT) |
              ((uint32_t)124 << RTC_PRER_PREDIV_A_SHIFT),
              STM32_RTC_PRER);
#else
      /* Correct values for 32.768 KHz LSE clock and inaccurate LSI clock */

      putreg32(((uint32_t)0xff << RTC_PRER_PREDIV_S_SHIFT) |
              ((uint32_t)0x7f << RTC_PRER_PREDIV_A_SHIFT),
              STM32_RTC_PRER);
#endif

      /* Exit RTC initialization mode */

      rtc_exitinit();
    }

  /* Re-enable the write protection for RTC registers */

  rtc_wprlock();

  return ret;
}

/****************************************************************************
 * Name: rtc_resume
 *
 * Description:
 *   Called when the RTC was already initialized on a previous power cycle.
 *   This just brings the RTC back into full operation.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

static void rtc_resume(void)
{
#ifdef CONFIG_RTC_ALARM
  uint32_t regval;

  /* Clear the RTC alarm flags */

  regval  = getreg32(STM32_RTC_ISR);
  regval &= ~(RTC_ISR_ALRAF | RTC_ISR_ALRBF);
  putreg32(regval, STM32_RTC_ISR);

  /* Clear the RTC Alarm Pending bit */

  putreg32(EXTI_RTC_ALARM, STM32_EXTI_CPUPR1);
#endif
}

/****************************************************************************
 * Name: stm32_rtc_alarm_handler
 *
 * Description:
 *   RTC ALARM interrupt service routine through the EXTI line
 *
 * Input Parameters:
 *   irq - The IRQ number that generated the interrupt
 *   context - Architecture specific register save information.
 *
 * Returned Value:
 *   Zero (OK) on success; A negated errno value on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
static int stm32_rtc_alarm_handler(int irq, void *context, void *arg)
{
  struct alm_cbinfo_s *cbinfo;
  alm_callback_t cb;
  void *cbarg;
  uint32_t isr;
  uint32_t cr;
  int ret = OK;

  /* Enable write access to the backup domain (RTC registers, RTC
   * backup data registers and backup SRAM).
   */

  stm32_pwr_enablebkp(true);

  isr  = getreg32(STM32_RTC_ISR);

  /* Check for EXTI from Alarm A or B and handle according */

  if ((isr & RTC_ISR_ALRAF) != 0)
    {
      cr  = getreg32(STM32_RTC_CR);
      if ((cr & RTC_CR_ALRAIE) != 0)
        {
          cbinfo = &g_alarmcb[RTC_ALARMA];
          if (cbinfo->ac_cb != NULL)
            {
              /* Alarm A callback */

              cb  = cbinfo->ac_cb;
              cbarg = (void *)cbinfo->ac_arg;

              cbinfo->ac_cb  = NULL;
              cbinfo->ac_arg = NULL;

              cb(cbarg, RTC_ALARMA);
            }

          isr  = getreg32(STM32_RTC_ISR);
          isr &= ~RTC_ISR_ALRAF;
          putreg32(isr, STM32_RTC_ISR);
        }
    }

#if CONFIG_RTC_NALARMS > 1
  if ((isr & RTC_ISR_ALRBF) != 0)
    {
      cr  = getreg32(STM32_RTC_CR);
      if ((cr & RTC_CR_ALRBIE) != 0)
        {
          cbinfo = &g_alarmcb[RTC_ALARMB];
          if (cbinfo->ac_cb != NULL)
            {
              /* Alarm B callback */

              cb  = cbinfo->ac_cb;
              cbarg = (void *)cbinfo->ac_arg;

              cbinfo->ac_cb  = NULL;
              cbinfo->ac_arg = NULL;

              cb(cbarg, RTC_ALARMB);
            }

          isr  = getreg32(STM32_RTC_ISR);
          isr &= ~RTC_ISR_ALRBF;
          putreg32(isr, STM32_RTC_ISR);
        }
    }
#endif

  /* Disable write access to the backup domain (RTC registers, RTC backup
   * data registers and backup SRAM).
   */

  stm32_pwr_enablebkp(false);

  return ret;
}
#endif

/****************************************************************************
 * Name: rtchw_check_alrXwf X= a or B
 *
 * Description:
 *   Check registers
 *
 * Input Parameters:
 * None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
static int rtchw_check_alrawf(void)
{
  volatile uint32_t timeout;
  uint32_t regval;
  int ret = -ETIMEDOUT;

  /* Check RTC_ISR ALRAWF for access to alarm register,
   * Can take 2 RTCCLK cycles or timeout
   * CubeMX use GetTick.
   */

  for (timeout = 0; timeout < INITMODE_TIMEOUT; timeout++)
    {
      regval = getreg32(STM32_RTC_ISR);
      if ((regval & RTC_ISR_ALRAWF) != 0)
        {
          ret = OK;
          break;
        }
    }

  return ret;
}
#endif

#if defined(CONFIG_RTC_ALARM) && CONFIG_RTC_NALARMS > 1
static int rtchw_check_alrbwf(void)
{
  volatile uint32_t timeout;
  uint32_t regval;
  int ret = -ETIMEDOUT;

  /* Check RTC_ISR ALRBWF for access to alarm register,
   * can take 2 RTCCLK cycles or timeout
   * CubeMX use GetTick.
   */

  for (timeout = 0; timeout < INITMODE_TIMEOUT; timeout++)
    {
      regval = getreg32(STM32_RTC_ISR);
      if ((regval & RTC_ISR_ALRBWF) != 0)
        {
          ret = OK;
          break;
        }
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: stm32_rtchw_set_alrmXr X is a or b
 *
 * Description:
 *   Set the alarm (A or B) hardware registers, using the required hardware
 *   access protocol
 *
 * Input Parameters:
 *   alarmreg - the register
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
static int rtchw_set_alrmar(rtc_alarmreg_t alarmreg)
{
  int isr;
  int ret = -EBUSY;

  /* Disable the write protection for RTC registers */

  rtc_wprunlock();

  /* Disable RTC alarm A & Interrupt A */

  modifyreg32(STM32_RTC_CR, (RTC_CR_ALRAE | RTC_CR_ALRAIE), 0);

  /* Ensure Alarm A flag reset; this is edge triggered */

  isr  = getreg32(STM32_RTC_ISR) & ~RTC_ISR_ALRAF;
  putreg32(isr, STM32_RTC_ISR);

  /* Wait for Alarm A to be writable */

  ret = rtchw_check_alrawf();
  if (ret != OK)
    {
      goto errout_with_wprunlock;
    }

  /* Set the RTC Alarm A register */

  putreg32(alarmreg, STM32_RTC_ALRMAR);
  putreg32(0, STM32_RTC_ALRMASSR);
  rtcinfo("  ALRMAR: %08x\n", getreg32(STM32_RTC_ALRMAR));

  /* Enable RTC alarm A */

  modifyreg32(STM32_RTC_CR, 0, (RTC_CR_ALRAE | RTC_CR_ALRAIE));

errout_with_wprunlock:
  rtc_wprlock();
  return ret;
}
#endif

#if defined(CONFIG_RTC_ALARM) && CONFIG_RTC_NALARMS > 1
static int rtchw_set_alrmbr(rtc_alarmreg_t alarmreg)
{
  int isr;
  int ret = -EBUSY;

  /* Disable the write protection for RTC registers */

  rtc_wprunlock();

  /* Disable RTC alarm B & Interrupt B */

  modifyreg32(STM32_RTC_CR, (RTC_CR_ALRBE | RTC_CR_ALRBIE), 0);

  /* Ensure Alarm B flag reset; this is edge triggered */

  isr  = getreg32(STM32_RTC_ISR) & ~RTC_ISR_ALRBF;
  putreg32(isr, STM32_RTC_ISR);

  /* Wait for Alarm B to be writable */

  ret = rtchw_check_alrbwf();
  if (ret != OK)
    {
      goto rtchw_set_alrmbr_exit;
    }

  /* Set the RTC Alarm B register */

  putreg32(alarmreg, STM32_RTC_ALRMBR);
  putreg32(0, STM32_RTC_ALRMBSSR);
  rtcinfo("  ALRMBR: %08x\n", getreg32(STM32_RTC_ALRMBR));

  /* Enable RTC alarm B */

  modifyreg32(STM32_RTC_CR, 0, (RTC_CR_ALRBE | RTC_CR_ALRBIE));

rtchw_set_alrmbr_exit:
  rtc_wprlock();
  return ret;
}
#endif

/****************************************************************************
 * Name: stm32_rtc_getalarmdatetime
 *
 * Description:
 *   Get the current date and time for a RTC alarm.
 *
 * Input Parameters:
 *   reg - RTC alarm register
 *   tp - The location to return the high resolution time value.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
static int stm32_rtc_getalarmdatetime(rtc_alarmreg_t reg, struct tm *tp)
{
  uint32_t data;
  uint32_t tmp;

  DEBUGASSERT(tp != NULL);

  /* Sample the data time register. */

  data = getreg32(reg);

  /* Convert the RTC time to fields in struct tm format.  All of the STM32
   * ranges of values correspond between struct tm and the time register.
   */

  tmp = (data & (RTC_ALRMR_SU_MASK | RTC_ALRMR_ST_MASK)) >>
        RTC_ALRMR_SU_SHIFT;
  tp->tm_sec = rtc_bcd2bin(tmp);

  tmp = (data & (RTC_ALRMR_MNU_MASK | RTC_ALRMR_MNT_MASK)) >>
        RTC_ALRMR_MNU_SHIFT;
  tp->tm_min = rtc_bcd2bin(tmp);

  tmp = (data & (RTC_ALRMR_HU_MASK | RTC_ALRMR_HT_MASK)) >>
        RTC_ALRMR_HU_SHIFT;
  tp->tm_hour = rtc_bcd2bin(tmp);

  tmp = (data & (RTC_ALRMR_DU_MASK | RTC_ALRMR_DT_MASK)) >>
        RTC_ALRMR_DU_SHIFT;
  tp->tm_mday = rtc_bcd2bin(tmp);

  return OK;
}
#endif

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

/****************************************************************************
 * Name: up_rtc_initialize
 *
 * Description:
 *   Initialize the hardware RTC per the selected configuration.  This
 *   function is called once during the OS initialization sequence
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

int up_rtc_initialize(void)
{
  uint32_t regval;
  uint32_t tr_bkp;
  uint32_t dr_bkp;
  int ret;
  int maxretry = 10;
  int nretry = 0;

  /* Clocking for the PWR block must be provided.  However, this is done
   * unconditionally in stm32h7xxx_rcc.c on power up.  This done
   * unconditionally because the PWR block is also needed to set the
   * internal voltage regulator for maximum performance.
   */

  /* Select the clock source */

  /* Save the token before losing it when resetting */

  regval = getreg32(RTC_MAGIC_REG);

  /* Enable write access to the backup domain (RTC registers, RTC
   * backup data registers and backup SRAM).
   */

  stm32_pwr_enablebkp(true);

  if (regval != RTC_MAGIC && regval != RTC_MAGIC_TIME_SET)
    {
      /* Issue the Backup domain Reset Per Section 5.3.20 DocID028270 Rev 2
       * The LSEON, LSEBYP, RTCSEL and RTCEN bits in the RCC backup domain
       * control register (RCC_BDCR) are in the Backup domain. As a result,
       * after Reset, these bits are write-protected and the DBP bit in the
       * PWR power control register (PWR_CR1) has to be set before these can
       * be modified.  Refer to Section 5.1.1: System reset on page 148 for
       * further information.  These bits are only reset after a Backup
       * domain Reset (see Section 5.1.3: Backup domain reset).
       *
       * This has to be done here so that PWR is already enabled
       */

      modifyreg32(STM32_RCC_BDCR, 0, RCC_BDCR_BDRST);
      modifyreg32(STM32_RCC_BDCR, RCC_BDCR_BDRST, 0);

#if RCC_BDCR_RTCSEL == RCC_BDCR_RTCSEL_LSE
      /* Because of the Backup domain Reset - we must re enable the LSE */

      stm32_rcc_enablelse();
#endif

      /* Some boards do not have the external 32khz oscillator installed, for
       * those boards we must fallback to the crummy internal RC clock or the
       * external high rate clock
       */

#ifdef CONFIG_STM32H7_RTC_HSECLOCK
      /* Use the HSE clock as the input to the RTC block */

      rtc_dumpregs("On reset HSE");

#elif defined(CONFIG_STM32H7_RTC_LSICLOCK)
      /* Use the LSI clock as the input to the RTC block */

      rtc_dumpregs("On reset LSI");

#elif defined(CONFIG_STM32H7_RTC_LSECLOCK)
      /* Use the LSE clock as the input to the RTC block */

      rtc_dumpregs("On reset LSE");

#endif
      modifyreg32(STM32_RCC_BDCR, RCC_BDCR_RTCSEL_MASK, RCC_BDCR_RTCSEL);

      /* Enable the RTC Clock by setting the RTCEN bit in the RCC register */

      modifyreg32(STM32_RCC_BDCR, 0, RCC_BDCR_RTCEN);
    }
  else
    {
      uint32_t clksrc = getreg32(STM32_RCC_BDCR);

      rtc_dumpregs("On reset warm");

      /* The RTC is already in use: check if the clock source has changed */

      if ((clksrc & RCC_BDCR_RTCSEL_MASK) != RCC_BDCR_RTCSEL)
        {
          tr_bkp = getreg32(STM32_RTC_TR);
          dr_bkp = getreg32(STM32_RTC_DR);
          modifyreg32(STM32_RCC_BDCR, 0, RCC_BDCR_BDRST);
          modifyreg32(STM32_RCC_BDCR, RCC_BDCR_BDRST, 0);

# if RCC_BDCR_RTCSEL == RCC_BDCR_RTCSEL_LSE
          /* Because of the Backup domain Reset - we must re enable the LSE
           * if it is used
           */

          stm32_rcc_enablelse();
#endif
          /* Change to the new clock as the input to the RTC block */

          modifyreg32(STM32_RCC_BDCR, RCC_BDCR_RTCSEL_MASK, RCC_BDCR_RTCSEL);

          putreg32(tr_bkp, STM32_RTC_TR);
          putreg32(dr_bkp, STM32_RTC_DR);

          /* Keep the fact that the RTC is initialized */

          putreg32(RTC_MAGIC, RTC_MAGIC_REG);

          /* Enable the RTC Clock by setting the RTCEN bit in the RCC
           * register.
           */

          modifyreg32(STM32_RCC_BDCR, 0, RCC_BDCR_RTCEN);
        }
    }

  /* Loop, attempting to initialize/resume the RTC.  This loop is necessary
   * because it seems that occasionally it takes longer to initialize the
   * RTC (the actual failure is in rtc_synchwait()).
   */

  do
    {
      /* Wait for the RTC Time and Date registers to be synchronized with
       * RTC APB clock.
       */

      ret = rtc_synchwait();

      /* Check that rtc_syncwait() returned successfully */

      switch (ret)
        {
          case OK:
            {
              rtcinfo("rtc_syncwait() okay\n");
              break;
            }

          default:
            {
              rtcerr("ERROR: rtc_syncwait() failed (%d)\n", ret);
              break;
            }
        }
    }
  while (ret != OK && ++nretry < maxretry);

  /* Check if the one-time initialization of the RTC has already been
   * performed. We can determine this by checking if the magic number
   * has been written to back-up date register DR0.
   */

  if (regval != RTC_MAGIC && regval != RTC_MAGIC_TIME_SET)
    {
      rtcinfo("Do setup\n");

      /* Perform the one-time setup of the LSE clocking to the RTC */

      ret = rtc_setup();

      /* Remember that the RTC is initialized */

      putreg32(RTC_MAGIC, RTC_MAGIC_REG);
    }
  else
    {
      rtcinfo("Do resume\n");

      /* RTC already set-up, just resume normal operation */

      rtc_resume();
      rtc_dumpregs("Did resume");
    }

  /* Disable write access to the backup domain (RTC registers, RTC backup
   * data registers and backup SRAM).
   */

  stm32_pwr_enablebkp(false);

  if (ret != OK && nretry > 0)
    {
      rtcinfo("setup/resume ran %d times and failed with %d\n",
              nretry, ret);
      return -ETIMEDOUT;
    }

  g_rtc_enabled = true;
  rtc_dumpregs("After Initialization");
  return OK;
}

/****************************************************************************
 * Name: stm32_rtc_getdatetime_with_subseconds
 *
 * Description:
 *   Get the current date and time from the date/time RTC.  This interface
 *   is only supported by the date/time RTC hardware implementation.
 *   It is used to replace the system timer.  It is only used by the RTOS
 *   during initialization to set up the system time when CONFIG_RTC and
 *   CONFIG_RTC_DATETIME are selected (and CONFIG_RTC_HIRES is not).
 *
 *   NOTE: Some date/time RTC hardware is capability of sub-second accuracy.
 *   That sub-second accuracy is returned through 'nsec'.
 *
 * Input Parameters:
 *   tp - The location to return the high resolution time value.
 *   nsec - The location to return the subsecond time value.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_STM32H7_HAVE_RTC_SUBSECONDS
int stm32_rtc_getdatetime_with_subseconds(struct tm *tp, long *nsec)
#else
int up_rtc_getdatetime(struct tm *tp)
#endif
{
  uint32_t dr;
  uint32_t tr;
  uint32_t tmp;
#ifdef CONFIG_STM32H7_HAVE_RTC_SUBSECONDS
  uint32_t ssr;
  uint32_t prediv_s;
  uint32_t usecs;
#endif

  /* Sample the data time registers.  There is a race condition here... If
   * we sample the time just before midnight on December 31, the date could
   * be wrong because the day rolled over while were sampling. Thus loop for
   * checking overflow here is needed.  There is a race condition with
   * subseconds too. If we sample TR register just before second rolling
   * and subseconds are read at wrong second, we get wrong time.
   */

  do
    {
      dr  = getreg32(STM32_RTC_DR);
      tr  = getreg32(STM32_RTC_TR);
#ifdef CONFIG_STM32H7_HAVE_RTC_SUBSECONDS
      ssr = getreg32(STM32_RTC_SSR);
      tmp = getreg32(STM32_RTC_TR);
      if (tmp != tr)
        {
          continue;
        }
#endif

      tmp = getreg32(STM32_RTC_DR);
      if (tmp == dr)
        {
          break;
        }
    }
  while (1);

  rtc_dumpregs("Reading Time");

  /* Convert the RTC time to fields in struct tm format. All of the STM32
   * ranges of values correspond between struct tm and the time register.
   */

  tmp = (tr & (RTC_TR_SU_MASK | RTC_TR_ST_MASK)) >> RTC_TR_SU_SHIFT;
  tp->tm_sec = rtc_bcd2bin(tmp);

  tmp = (tr & (RTC_TR_MNU_MASK | RTC_TR_MNT_MASK)) >> RTC_TR_MNU_SHIFT;
  tp->tm_min = rtc_bcd2bin(tmp);

  tmp = (tr & (RTC_TR_HU_MASK | RTC_TR_HT_MASK)) >> RTC_TR_HU_SHIFT;
  tp->tm_hour = rtc_bcd2bin(tmp);

  /* Now convert the RTC date to fields in struct tm format:
   * Days: 1-31 match in both cases.
   * Month: STM32 is 1-12, struct tm is 0-11.
   * Years: STM32 is 00-99, struct tm is years since 1900.
   * WeekDay: STM32 is 1 = Mon - 7 = Sun
   *
   * Issue:  I am not sure what the STM32 years mean.  Are these the
   * years 2000-2099?  I'll assume so.
   */

  tmp = (dr & (RTC_DR_DU_MASK | RTC_DR_DT_MASK)) >> RTC_DR_DU_SHIFT;
  tp->tm_mday = rtc_bcd2bin(tmp);

  tmp = (dr & (RTC_DR_MU_MASK | RTC_DR_MT)) >> RTC_DR_MU_SHIFT;
  tp->tm_mon = rtc_bcd2bin(tmp) - 1;

  tmp = (dr & (RTC_DR_YU_MASK | RTC_DR_YT_MASK)) >> RTC_DR_YU_SHIFT;
  tp->tm_year = rtc_bcd2bin(tmp) + 100;

  tmp = (dr & RTC_DR_WDU_MASK) >> RTC_DR_WDU_SHIFT;
  tp->tm_wday = tmp % 7;
  tp->tm_yday = tp->tm_mday - 1 +
    clock_daysbeforemonth(tp->tm_mon, clock_isleapyear(tp->tm_year + 1900));
  tp->tm_isdst = 0;

#ifdef CONFIG_STM32H7_HAVE_RTC_SUBSECONDS
  /* Return RTC sub-seconds if a non-NULL value
   * of nsec has been provided to receive the sub-second value.
   */

  prediv_s   = getreg32(STM32_RTC_PRER) & RTC_PRER_PREDIV_S_MASK;
  prediv_s >>= RTC_PRER_PREDIV_S_SHIFT;

  ssr &= RTC_SSR_MASK;

  /* Maximum prediv_s is 0x7fff, thus we can multiply by 100000 and
   * still fit 32-bit unsigned integer.
   */

  usecs = (((prediv_s - ssr) * 100000) / (prediv_s + 1)) * 10;
  if (nsec != NULL)
    {
      *nsec = usecs * 1000;
    }

  rtc_dumptime((const struct tm *)tp, &usecs, "Returning");
#else /* CONFIG_STM32H7_HAVE_RTC_SUBSECONDS */
  rtc_dumptime((const struct tm *)tp, NULL, "Returning");
#endif

  return OK;
}

/****************************************************************************
 * Name: up_rtc_getdatetime
 *
 * Description:
 *   Get the current date and time from the date/time RTC.  This interface
 *   is only supported by the date/time RTC hardware implementation.
 *   It is used to replace the system timer.  It is only used by the RTOS
 *   during initialization to set up the system time when CONFIG_RTC and
 *   CONFIG_RTC_DATETIME are selected (and CONFIG_RTC_HIRES is not).
 *
 *   NOTE: Some date/time RTC hardware is capability of sub-second accuracy.
 *   That sub-second accuracy is lost in this interface.  However, since the
 *   system time is reinitialized on each power-up/reset, there will be no
 *   timing inaccuracy in the long run.
 *
 * Input Parameters:
 *   tp - The location to return the high resolution time value.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_STM32H7_HAVE_RTC_SUBSECONDS
int up_rtc_getdatetime(struct tm *tp)
{
  return stm32_rtc_getdatetime_with_subseconds(tp, NULL);
}
#endif

/****************************************************************************
 * Name: up_rtc_getdatetime_with_subseconds
 *
 * Description:
 *   Get the current date and time from the date/time RTC.  This interface
 *   is only supported by the date/time RTC hardware implementation.
 *   It is used to replace the system timer.  It is only used by the RTOS
 *   during initialization to set up the system time when CONFIG_RTC and
 *   CONFIG_RTC_DATETIME are selected (and CONFIG_RTC_HIRES is not).
 *
 *   NOTE: This interface exposes sub-second accuracy capability of RTC
 *   hardware.  This interface allow maintaining timing accuracy when system
 *   time needs constant resynchronization with RTC, for example with board
 *   level power-save mode utilizing deep-sleep modes such as STOP on STM32
 *   MCUs.
 *
 * Input Parameters:
 *   tp - The location to return the high resolution time value.
 *   nsec - The location to return the subsecond time value.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_ARCH_HAVE_RTC_SUBSECONDS
#  ifndef CONFIG_STM32H7_HAVE_RTC_SUBSECONDS
#    error "Invalid config, enable CONFIG_STM32H7_HAVE_RTC_SUBSECONDS."
#  endif
int up_rtc_getdatetime_with_subseconds(struct tm *tp, long *nsec)
{
  return stm32_rtc_getdatetime_with_subseconds(tp, nsec);
}
#endif

/****************************************************************************
 * Name: stm32_rtc_setdatetime
 *
 * Description:
 *   Set the RTC to the provided time. RTC implementations which provide
 *   up_rtc_getdatetime() (CONFIG_RTC_DATETIME is selected) should provide
 *   this function.
 *
 * Input Parameters:
 *   tp - the time to use
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

int stm32_rtc_setdatetime(const struct tm *tp)
{
  uint32_t tr;
  uint32_t dr;
  int ret;

  rtc_dumptime(tp, NULL, "Setting time");

  /* Then write the broken out values to the RTC */

  /* Convert the struct tm format to RTC time register fields.
   * All of the ranges of values correspond between struct tm and the
   * time register.
   */

  tr = (rtc_bin2bcd(tp->tm_sec)  << RTC_TR_SU_SHIFT) |
       (rtc_bin2bcd(tp->tm_min)  << RTC_TR_MNU_SHIFT) |
       (rtc_bin2bcd(tp->tm_hour) << RTC_TR_HU_SHIFT);
  tr &= ~RTC_TR_RESERVED_BITS;

  /* Now convert the fields in struct tm format to the RTC date register
   * fields:
   *
   *   Days: 1-31 match in both cases.
   *   Month: STM32 is 1-12, struct tm is 0-11.
   *   Years: STM32 is 00-99, struct tm is years since 1900.
   *   WeekDay: STM32 is 1 = Mon - 7 = Sun
   *
   * Issue:  I am not sure what the STM32 years mean.  Are these the
   * years 2000-2099?  I'll assume so.
   */

  dr = (rtc_bin2bcd(tp->tm_mday) << RTC_DR_DU_SHIFT) |
       ((rtc_bin2bcd(tp->tm_mon + 1))  << RTC_DR_MU_SHIFT) |
       ((tp->tm_wday == 0 ? 7 : (tp->tm_wday & 7))  << RTC_DR_WDU_SHIFT) |
       ((rtc_bin2bcd(tp->tm_year - 100)) << RTC_DR_YU_SHIFT);

  dr &= ~RTC_DR_RESERVED_BITS;

  /* Disable the write protection for RTC registers */

  rtc_wprunlock();

  /* Set Initialization mode */

  ret = rtc_enterinit();
  if (ret == OK)
    {
      /* Set the RTC TR and DR registers */

      putreg32(tr, STM32_RTC_TR);
      putreg32(dr, STM32_RTC_DR);

      /* Exit Initialization mode and wait for the RTC Time and Date
       * registers to be synchronized with RTC APB clock.
       */

      rtc_exitinit();
      ret = rtc_synchwait();
    }

  /* Remember that the RTC is initialized and had its time set. */

  if (getreg32(RTC_MAGIC_REG) != RTC_MAGIC_TIME_SET)
    {
      stm32_pwr_enablebkp(true);
      putreg32(RTC_MAGIC_TIME_SET, RTC_MAGIC_REG);
      stm32_pwr_enablebkp(false);
    }

  /* Re-enable the write protection for RTC registers */

  rtc_wprlock();
  rtc_dumpregs("New time setting");
  return ret;
}

/****************************************************************************
 * Name: stm32_rtc_havesettime
 *
 * Description:
 *   Check if RTC time has been set.
 *
 * Returned Value:
 *   Returns true if RTC date-time have been previously set.
 *
 ****************************************************************************/

bool stm32_rtc_havesettime(void)
{
  return getreg32(RTC_MAGIC_REG) == RTC_MAGIC_TIME_SET;
}

/****************************************************************************
 * Name: up_rtc_settime
 *
 * Description:
 *   Set the RTC to the provided time.  All RTC implementations must be able
 *   to set their time based on a standard timespec.
 *
 * Input Parameters:
 *   tp - the time to use
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

int up_rtc_settime(const struct timespec *tp)
{
  struct tm newtime;

  /* Break out the time values (not that the time is set only to units of
   * seconds)
   */

  gmtime_r(&tp->tv_sec, &newtime);
  return stm32_rtc_setdatetime(&newtime);
}

/****************************************************************************
 * Name: stm32_rtc_setalarm
 *
 * Description:
 *   Set an alarm to an absolute time using associated hardware.
 *
 * Input Parameters:
 *  alminfo - Information about the alarm configuration.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
int stm32_rtc_setalarm(struct alm_setalarm_s *alminfo)
{
  struct alm_cbinfo_s *cbinfo;
  rtc_alarmreg_t alarmreg;
  int ret = -EINVAL;
  static bool once = false;

  DEBUGASSERT(alminfo != NULL);
  DEBUGASSERT(RTC_ALARM_LAST > alminfo->as_id);

  /* Configure RTC interrupt to catch alarm interrupts. All RTC interrupts
   * are connected to the EXTI controller.  To enable the RTC Alarm
   * interrupt, the following sequence is required:
   *
   * 1. Configure and enable the EXTI Line 17 RTC ALARM in interrupt mode
   *    and select the rising edge sensitivity.
   *    For STM32H7:
   *    EXTI line 18 RTC Tamper, Timestamp & RCC LSECSS
   *    EXTI line 19 RTC Wakeup timer
   * 2. Configure and enable the RTC_Alarm IRQ channel in the NVIC.
   * 3. Configure the RTC to generate RTC alarms (Alarm A or Alarm B).
   */

  if (!once)
    {
      once = true;
      stm32_exti_alarm(true, false, true, stm32_rtc_alarm_handler, NULL);
    }

  /* REVISIT:  Should test that the time is in the future */

  rtc_dumptime(&alminfo->as_time, NULL, "New alarm time");

  /* Break out the values to the HW alarm register format.  The values in
   * all STM32 fields match the fields of struct tm in this case.  Notice
   * that the alarm is limited to one month.
   */

  alarmreg = (rtc_bin2bcd(alminfo->as_time.tm_sec)  << RTC_ALRMR_SU_SHIFT) |
             (rtc_bin2bcd(alminfo->as_time.tm_min)  << RTC_ALRMR_MNU_SHIFT) |
             (rtc_bin2bcd(alminfo->as_time.tm_hour) << RTC_ALRMR_HU_SHIFT) |
             (rtc_bin2bcd(alminfo->as_time.tm_mday) << RTC_ALRMR_DU_SHIFT);

  /* Set the alarm in hardware and enable interrupts */

  switch (alminfo->as_id)
    {
      case RTC_ALARMA:
        {
          cbinfo         = &g_alarmcb[RTC_ALARMA];
          cbinfo->ac_cb  = alminfo->as_cb;
          cbinfo->ac_arg = alminfo->as_arg;

          ret = rtchw_set_alrmar(alarmreg | RTC_ALRMR_ENABLE);
          if (ret < 0)
            {
              cbinfo->ac_cb  = NULL;
              cbinfo->ac_arg = NULL;
            }
        }
        break;

#if CONFIG_RTC_NALARMS > 1
      case RTC_ALARMB:
        {
          cbinfo         = &g_alarmcb[RTC_ALARMB];
          cbinfo->ac_cb  = alminfo->as_cb;
          cbinfo->ac_arg = alminfo->as_arg;

          ret = rtchw_set_alrmbr(alarmreg | RTC_ALRMR_ENABLE);
          if (ret < 0)
            {
              cbinfo->ac_cb  = NULL;
              cbinfo->ac_arg = NULL;
            }
        }
        break;
#endif

      default:
        rtcerr("ERROR: Invalid ALARM%d\n", alminfo->as_id);
        break;
    }

  rtc_dumpregs("After alarm setting");

  return ret;
}
#endif

/****************************************************************************
 * Name: stm32_rtc_cancelalarm
 *
 * Description:
 *   Cancel an alarm.
 *
 * Input Parameters:
 *  alarmid - Identifies the alarm to be canceled
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
int stm32_rtc_cancelalarm(enum alm_id_e alarmid)
{
  int ret = -EINVAL;

  DEBUGASSERT(RTC_ALARM_LAST > alarmid);

  /* Cancel the alarm in hardware and disable interrupts */

  switch (alarmid)
    {
      case RTC_ALARMA:
        {
          /* Cancel the global callback function */

           g_alarmcb[alarmid].ac_cb  = NULL;
           g_alarmcb[alarmid].ac_arg = NULL;

          /* Disable the write protection for RTC registers */

          rtc_wprunlock();

          /* Disable RTC alarm and interrupt */

          modifyreg32(STM32_RTC_CR, (RTC_CR_ALRAE | RTC_CR_ALRAIE), 0);

          ret = rtchw_check_alrawf();
          if (ret < 0)
            {
              goto errout_with_wprunlock;
            }

          /* Unset the alarm */

          putreg32(-1, STM32_RTC_ALRMAR);
          modifyreg32(STM32_RTC_ISR, RTC_ISR_ALRAF, 0);
          rtc_wprlock();
          ret = OK;
        }
        break;

#if CONFIG_RTC_NALARMS > 1
      case RTC_ALARMB:
        {
          /* Cancel the global callback function */

           g_alarmcb[alarmid].ac_cb  = NULL;
           g_alarmcb[alarmid].ac_arg = NULL;

          /* Disable the write protection for RTC registers */

          rtc_wprunlock();

          /* Disable RTC alarm and interrupt */

          modifyreg32(STM32_RTC_CR, (RTC_CR_ALRBE | RTC_CR_ALRBIE), 0);

          ret = rtchw_check_alrbwf();
          if (ret < 0)
            {
              goto errout_with_wprunlock;
            }

          /* Unset the alarm */

          putreg32(-1, STM32_RTC_ALRMBR);
          modifyreg32(STM32_RTC_ISR, RTC_ISR_ALRBF, 0);
          rtc_wprlock();
          ret = OK;
        }
        break;
#endif

      default:
        rtcerr("ERROR: Invalid ALARM%d\n", alarmid);
        break;
    }

  return ret;

errout_with_wprunlock:
  rtc_wprlock();
  return ret;
}
#endif

/****************************************************************************
 * Name: stm32_rtc_rdalarm
 *
 * Description:
 *   Query an alarm configured in hardware.
 *
 * Input Parameters:
 *  alminfo - Information about the alarm configuration.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_ALARM
int stm32_rtc_rdalarm(struct alm_rdalarm_s *alminfo)
{
  rtc_alarmreg_t alarmreg;
  int ret = -EINVAL;

  DEBUGASSERT(alminfo != NULL);
  DEBUGASSERT(RTC_ALARM_LAST > alminfo->ar_id);

  switch (alminfo->ar_id)
    {
      case RTC_ALARMA:
        {
          alarmreg = STM32_RTC_ALRMAR;
          ret = stm32_rtc_getalarmdatetime(alarmreg,
                                           (struct tm *)alminfo->ar_time);
        }
        break;

#if CONFIG_RTC_NALARMS > 1
      case RTC_ALARMB:
        {
          alarmreg = STM32_RTC_ALRMBR;
          ret = stm32_rtc_getalarmdatetime(alarmreg,
                                          (struct tm *)alminfo->ar_time);
        }
        break;
#endif

      default:
        rtcerr("ERROR: Invalid ALARM%d\n", alminfo->ar_id);
        break;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: stm32_rtc_wakeup_handler
 *
 * Description:
 *   RTC WAKEUP interrupt service routine through the EXTI line
 *
 * Input Parameters:
 *   irq - The IRQ number that generated the interrupt
 *
 * Returned Value:
 *   Zero (OK) on success; A negated errno value on failure.
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_PERIODIC
static int stm32_rtc_wakeup_handler(int irq, void *context,
                                    void *arg)
{
  uint32_t regval = 0;

  stm32_pwr_enablebkp(true);

  regval = getreg32(STM32_RTC_ISR);
  regval &= ~RTC_ISR_WUTF;
  putreg32(regval, STM32_RTC_ISR);

  stm32_pwr_enablebkp(false);

  if (g_wakeupcb != NULL)
    {
      g_wakeupcb();
    }

  return OK;
}
#endif

/****************************************************************************
 * Name: rtc_enable_wakeup
 *
 * Description:
 *   Enable periodic wakeup interrupts
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_PERIODIC
static inline void rtc_enable_wakeup(void)
{
  if (!g_wakeup_enabled)
    {
      stm32_exti_wakeup(true, false, true, stm32_rtc_wakeup_handler,
                        NULL);
      g_wakeup_enabled = true;
    }
}
#endif

/****************************************************************************
 * Name: rtc_set_wcksel
 *
 * Description:
 *   Sets RTC wakeup clock selection value
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_PERIODIC
static inline void rtc_set_wcksel(unsigned int wucksel)
{
  uint32_t regval = 0;

  regval = getreg32(STM32_RTC_CR);
  regval &= ~RTC_CR_WUCKSEL_MASK;
  regval |= wucksel;
  putreg32(regval, STM32_RTC_CR);
}
#endif

/****************************************************************************
 * Name: stm32_rtc_setperiodic
 *
 * Description:
 *   Set a periodic RTC wakeup
 *
 * Input Parameters:
 *   period   - Time to sleep between wakeups
 *   callback - Function to call when the period expires.
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_PERIODIC
int stm32_rtc_setperiodic(const struct timespec *period,
                          wakeupcb_t callback)
{
  unsigned int wutr_val;
  int ret;
  int timeout;
  uint32_t regval;
  uint32_t secs;
  uint32_t millisecs;

#if defined(CONFIG_STM32H7_RTC_HSECLOCK)
#  error "Periodic wakeup not available for HSE"
#elif defined(CONFIG_STM32H7_RTC_LSICLOCK)
#  error "Periodic wakeup not available for LSI (and it is too inaccurate!)"
#elif defined(CONFIG_STM32H7_RTC_LSECLOCK)
  const uint32_t rtc_div16_max_msecs = 16 * 1000 * 0xffffu /
                                       STM32_LSE_FREQUENCY;
#else
#  error "No clock for RTC!"
#endif

  /* Lets use RTC wake-up with 0.001 sec to ~18 hour range.
   *
   * TODO: scale to higher periods, with necessary losing some precision.
   * We currently go for subseconds accuracy instead of maximum period.
   */

  if (period->tv_sec > 0xffffu ||
     (period->tv_sec == 0xffffu && period->tv_nsec > 0))
    {
      /* More than max. */

      secs = 0xffffu;
      millisecs = secs * 1000;
    }
  else
    {
      secs = period->tv_sec;
      millisecs = secs * 1000 + period->tv_nsec / NSEC_PER_MSEC;
    }

  if (millisecs == 0)
    {
      return -EINVAL;
    }

  /* Make sure the alarm interrupt is enabled at the NVIC */

  rtc_enable_wakeup();

  rtc_wprunlock();

  /* Clear WUTE in RTC_CR to disable the wakeup timer */

  regval = getreg32(STM32_RTC_CR);
  regval &= ~RTC_CR_WUTE;
  putreg32(regval, STM32_RTC_CR);

  /* Poll WUTWF until it is set in RTC_ISR (takes around 2 RTCCLK clock
   * cycles)
   */

  ret = -ETIMEDOUT;
  for (timeout = 0; timeout < SYNCHRO_TIMEOUT; timeout++)
    {
      regval = getreg32(STM32_RTC_ISR);
      if ((regval & RTC_ISR_WUTWF) != 0)
        {
          /* Synchronized */

          ret = OK;
          break;
        }
    }

  /* Set callback function pointer. */

  g_wakeupcb = callback;

  if (millisecs <= rtc_div16_max_msecs)
    {
      unsigned int ticks;

      /* Select wake-up with 32768/16 hz counter. */

      rtc_set_wcksel(RTC_CR_WUCKSEL_RTCDIV16);

      /* Get number of ticks. */

      ticks = millisecs * STM32_LSE_FREQUENCY / (16 * 1000);

      /* Wake-up is after WUT+1 ticks. */

      wutr_val = ticks - 1;
    }
  else
    {
      /* Select wake-up with 1hz counter. */

      rtc_set_wcksel(RTC_CR_WUCKSEL_CKSPRE);

      /* Wake-up is after WUT+1 ticks. */

      wutr_val = secs - 1;
    }

  /* Program the wakeup auto-reload value WUT[15:0], and the wakeup clock
   * selection.
   */

  putreg32(wutr_val, STM32_RTC_WUTR);

  regval = getreg32(STM32_RTC_CR);
  regval |= RTC_CR_WUTIE | RTC_CR_WUTE;
  putreg32(regval, STM32_RTC_CR);

  /* Just in case resets the WUTF flag in RTC_ISR */

  regval = getreg32(STM32_RTC_ISR);
  regval &= ~RTC_ISR_WUTF;
  putreg32(regval, STM32_RTC_ISR);

  rtc_wprlock();

  return ret;
}
#endif

/****************************************************************************
 * Name: stm32_rtc_cancelperiodic
 *
 * Description:
 *   Cancel a periodic wakeup
 *
 * Input Parameters:
 *
 * Returned Value:
 *   Zero (OK) on success; a negated errno on failure
 *
 ****************************************************************************/

#ifdef CONFIG_RTC_PERIODIC
int stm32_rtc_cancelperiodic(void)
{
  int ret = OK;
  int timeout = 0;
  uint32_t regval = 0;

  rtc_wprunlock();

  /* Clear WUTE and WUTIE in RTC_CR to disable the wakeup timer */

  regval = getreg32(STM32_RTC_CR);
  regval &= ~(RTC_CR_WUTE | RTC_CR_WUTIE);
  putreg32(regval, STM32_RTC_CR);

  /* Poll WUTWF until it is set in RTC_ISR (takes around 2 RTCCLK clock
   * cycles)
   */

  ret = -ETIMEDOUT;
  for (timeout = 0; timeout < SYNCHRO_TIMEOUT; timeout++)
    {
      regval = getreg32(STM32_RTC_ISR);
      if ((regval & RTC_ISR_WUTWF) != 0)
        {
          /* Synchronized */

          ret = OK;
          break;
        }
    }

  /* Clears RTC_WUTR register */

  regval = getreg32(STM32_RTC_WUTR);
  regval &= ~RTC_WUTR_MASK;
  putreg32(regval, STM32_RTC_WUTR);

  rtc_wprlock();

  return ret;
}
#endif

#endif /* CONFIG_STM32H7_RTC */
