84b0631e创建于 4月20日历史提交
/****************************************************************************
 * arch/arm/src/nrf53/nrf53_tickless_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 <sys/types.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/arch.h>

#include "arm_internal.h"
#include "hardware/nrf53_rtc.h"
#include "nrf53_rtc.h"

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

/* Check configuration */

#ifdef CONFIG_TIMER_ARCH
#  error CONFIG_TIMER_ARCH must be not set
#endif

/* Check corresponding RTC support */

#if (CONFIG_NRF53_SYSTIMER_RTC_INSTANCE == 0) && !defined(CONFIG_NRF53_RTC0)
#  error "Support for RTC0 is not enabled"
#elif (CONFIG_NRF53_SYSTIMER_RTC_INSTANCE == 1) && !defined(CONFIG_NRF53_RTC1)
#  error "Support for RTC1 is not enabled"
#endif

#define NRF53_RTC_PERIOD   (512)
#define NRF53_RTC_MAX      (0x00ffffff)
#define NRF53_RTC_MAX_TIME (NRF53_RTC_MAX * 31)

/* Convert uS to timer count for f = 32768Hz, using more precision
 * when possible:
 * (1 / 32768) s ~ 30.51 uS ~ 31 uS
 * 512 * (1 / 32768) s = 0.015625 s = 15625 uS
 * So, instead of always dividing by 31, if t * 512 < (2**32 - 1), we can do:
 * (t * 512) / 15625 ~ t / 30.51
 */

#define USEC_TO_COUNTER(t) (t > 0x7fffff ? (t / 31) : ((t * 512) / 15625))

/* To convert from counter to uS we split the counter into one second worth
 * of counts (32768) and a fractional part we can safely multiply first
 * by (USEC_PER_SEC/8) and still be within 32 bit value
 */

#define COUNTER_TO_USEC(c) ((c / 32768) * USEC_PER_SEC) + \
                            (((c % 32768) * (USEC_PER_SEC / 8)) / (32768 / 8))

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

struct nrf53_tickless_dev_s
{
  struct nrf53_rtc_dev_s *rtc; /* nrf53 RTC driver */
  uint32_t periods;            /* how many times the timer overflowed */
  bool alarm_set;              /* is the alarm set? */
  struct timespec alarm;       /* absolute time of alarm */
};

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

static int rtc_handler(int irq, void *context, void *arg);

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

struct nrf53_tickless_dev_s g_tickless_dev;

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

static inline void rtc_counter_to_ts(uint32_t counter, struct timespec *now)
{
  uint32_t usec;

  usec = COUNTER_TO_USEC(counter);
  now->tv_sec  = usec / USEC_PER_SEC;
  now->tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;

  now->tv_sec += g_tickless_dev.periods * NRF53_RTC_PERIOD;
}

static void rtc_prepare_alarm(void)
{
  struct timespec now;
  struct timespec delta;
  uint32_t usec;
  uint32_t counter;
  uint32_t target_counter;
  const uint32_t rtc_base = NRF53_RTC_GETBASE(g_tickless_dev.rtc);

  /* Get current absolute time */

  counter = NRF53_RTC_GETCOUNTER_REG(rtc_base);
  rtc_counter_to_ts(counter, &now);

  /* Obtain relative time to alarm */

  clock_timespec_subtract(&g_tickless_dev.alarm, &now, &delta);
  usec = delta.tv_sec * USEC_PER_SEC + delta.tv_nsec / NSEC_PER_USEC;

  /* Check if the alarm is to expire within one RTC period, if so we can set
   * the CC.
   */

  if (usec < NRF53_RTC_PERIOD * USEC_PER_SEC)
    {
      /* Obtain absolute number of microseconds of alarm within current
       * RTC period.
       */

      usec = (g_tickless_dev.alarm.tv_sec % NRF53_RTC_PERIOD) *
             USEC_PER_SEC + g_tickless_dev.alarm.tv_nsec / NSEC_PER_USEC;

      /* Compute counter value for that point in time */

      target_counter = USEC_TO_COUNTER(usec);

      /* Enable interrupt. First set CC to distant value to ensure
       * no match will be generated. Doing things this way we now that
       * once we write a CC value it should be ready to match.
       */

      NRF53_RTC_SETCC_REG(rtc_base, 0, counter - 1);
      NRF53_RTC_ENABLEINT(g_tickless_dev.rtc, NRF53_RTC_EVT_COMPARE0);

      /* Set CC to desired value */

      NRF53_RTC_SETCC_REG(rtc_base, 0, target_counter);

      /* Ensure counter fires: from nrf53832_PS_v1.4 (p. 245) we know that
       * "If the COUNTER is N, writing N+2 to a CC register is
       * guaranteed to trigger a COMPARE event at N+2.", so anything
       * less than that is not guaranteed and it may not ever match.
       *
       * To ensure this, we check if CC < N + 2, and if so we set CC = N+2.
       * We repeat this until this is satisfied (as the counter may change
       * in between calculations).
       */

      while (NRF53_RTC_GETCC_REG(rtc_base, 0) <
             NRF53_RTC_GETCOUNTER_REG(rtc_base) + 2)
        {
          NRF53_RTC_SETCC_REG(rtc_base, 0,
                              NRF53_RTC_GETCOUNTER_REG(rtc_base) + 2);
        }
    }
}

/****************************************************************************
 * Name: rtc_handler
 ****************************************************************************/

static int rtc_handler(int irq, void *context, void *arg)
{
  irqstate_t flags;

  flags = enter_critical_section();

  /* if the timer wrapped-around */

  if (NRF53_RTC_CHECKINT(g_tickless_dev.rtc, NRF53_RTC_EVT_OVRFLW))
    {
      /* ack interrupt */

      NRF53_RTC_ACKINT(g_tickless_dev.rtc, NRF53_RTC_EVT_OVRFLW);

      /* count one more period */

      g_tickless_dev.periods++;

      /* check if the currently set alarm is to fire in this new period */

      if (g_tickless_dev.alarm_set)
        {
          rtc_prepare_alarm();
        }
    }

  /* if the compare event fired */

  if (NRF53_RTC_CHECKINT(g_tickless_dev.rtc, NRF53_RTC_EVT_COMPARE0))
    {
      struct timespec now;

      /* cancel alarm and get current time */

      up_alarm_cancel(&now);

      /* let scheduler now of alarm firing */

      nxsched_alarm_expiration(&now);
    }

  leave_critical_section(flags);

  return OK;
}

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

/****************************************************************************
 * Name: up_alarm_cancel
 ****************************************************************************/

int up_alarm_cancel(struct timespec *ts)
{
  uint32_t counter;
  irqstate_t flags;

  flags = enter_critical_section();

  NRF53_RTC_DISABLEINT(g_tickless_dev.rtc, NRF53_RTC_EVT_COMPARE0);
  NRF53_RTC_GETCOUNTER(g_tickless_dev.rtc, &counter);
  rtc_counter_to_ts(counter, ts);

  NRF53_RTC_ACKINT(g_tickless_dev.rtc, NRF53_RTC_EVT_COMPARE0);
  g_tickless_dev.alarm_set = false;

  leave_critical_section(flags);

  return OK;
}

/****************************************************************************
 * Name: up_alarm_start
 ****************************************************************************/

int up_alarm_start(const struct timespec *ts)
{
  irqstate_t flags;
  flags = enter_critical_section();

  /* remember the alarm time */

  g_tickless_dev.alarm_set = true;
  g_tickless_dev.alarm     = *ts;

  rtc_prepare_alarm();

  leave_critical_section(flags);

  return OK;
}

/****************************************************************************
 * Name: up_timer_gettime
 ****************************************************************************/

int up_timer_gettime(struct timespec *ts)
{
  uint32_t counter;
  irqstate_t flags;

  flags = enter_critical_section();

  NRF53_RTC_GETCOUNTER(g_tickless_dev.rtc, &counter);
  rtc_counter_to_ts(counter, ts);

  leave_critical_section(flags);

  return OK;
}

/****************************************************************************
 * Name: up_timer_initialize
 ****************************************************************************/

void up_timer_initialize(void)
{
  struct timespec ts;

  memset(&g_tickless_dev, 0, sizeof(struct nrf53_tickless_dev_s));

  g_tickless_dev.rtc = nrf53_rtc_init(CONFIG_NRF53_SYSTIMER_RTC_INSTANCE);

  /* Ensure we have support for the selected RTC instance */

  ASSERT(g_tickless_dev.rtc);

  /* Configure prescaler */

  NRF53_RTC_SETPRE(g_tickless_dev.rtc, 0);

  /* Configure ISR */

  NRF53_RTC_SETISR(g_tickless_dev.rtc, rtc_handler, NULL);

  /* Enable overflow interrupt */

  NRF53_RTC_ENABLEINT(g_tickless_dev.rtc, NRF53_RTC_EVT_OVRFLW);

  /* Start counting */

  NRF53_RTC_CLEAR(g_tickless_dev.rtc);
  NRF53_RTC_ACKINT(g_tickless_dev.rtc, NRF53_RTC_EVT_OVRFLW);
  NRF53_RTC_ACKINT(g_tickless_dev.rtc, NRF53_RTC_EVT_COMPARE0);
  NRF53_RTC_START(g_tickless_dev.rtc);

  /* kick off alarm scheduling */

  ts.tv_sec = ts.tv_nsec = 0;
  nxsched_alarm_expiration(&ts);
}