int sam_oneshot_cancel(struct sam_oneshot_s *oneshot, struct timespec *ts)
{
  irqstate_t flags;
  uint64_t usec;
  uint64_t sec;
  uint64_t nsec;
  uint32_t count;
  uint32_t rc;

  /* Was the timer running? */

  flags = irqsave();
  if (!oneshot->running)
    {
      /* No.. Just return zero timer remaining and successful cancellation.
       * This function may execute at a high rate with no timer running
       * (as when pre-emption is enabled and disabled).
       */

      ts->tv_sec  = 0;
      ts->tv_nsec = 0;
      irqrestore(flags);
      return OK;
    }

  /* Yes.. Get the timer counter and rc registers and stop the counter.  If
   * the counter expires while we are doing this, the counter clock will be
   * stopped, but the clock will not be disabled.
   *
   * The expected behavior is that the the counter register will freezes at
   * a value equal to the RC register when the timer expires.  The counter
   * should have values between 0 and RC in all other cased.
   *
   * REVISIT:  This does not appear to be the case.
   */

  tcvdbg("Cancelling...\n");

  count = sam_tc_getcounter(oneshot->tch);
  rc    = sam_tc_getregister(oneshot->tch, TC_REGC);

  /* Now we can disable the interrupt and stop the timer. */

  sam_tc_attach(oneshot->tch, NULL, NULL, 0);
  sam_tc_stop(oneshot->tch);

  oneshot->running = false;
  oneshot->handler = NULL;
  oneshot->arg     = NULL;
  irqrestore(flags);

  /* Did the caller provide us with a location to return the time
   * remaining?
   */

  if (ts)
    {
      /* Yes.. then calculate and return the time remaining on the
       * oneshot timer.
       */

      tcvdbg("rc=%lu count=%lu usec=%lu\n",
             (unsigned long)rc, (unsigned long)count, (unsigned long)usec);

      /* REVISIT: I am not certain why the timer counter value sometimes
       * exceeds RC.  Might be a bug, or perhaps the counter does not stop
       * in all cases.
       */

      if (count >= rc)
        {
          /* No time remaining (?) */

          ts->tv_sec  = 0;
          ts->tv_nsec = 0;
        }
      else
        {
          /* The total time remaining is the difference.  Convert the that
           * to units of microseconds.
           *
           *   frequency = ticks / second
           *   seconds   = ticks * frequency
           *   usecs     = (ticks * USEC_PER_SEC) / frequency;
           */

          usec        = (((uint64_t)(rc - count)) * USEC_PER_SEC) /
                        sam_tc_divfreq(oneshot->tch);

          /* Return the time remaining in the correct form */

          sec         = usec / USEC_PER_SEC;
          nsec        = ((usec) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC;

          ts->tv_sec  = (time_t)sec;
          ts->tv_nsec = (unsigned long)nsec;
        }

      tcvdbg("remaining (%lu, %lu)\n",
             (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec);
    }

  return OK;
}
int sam_oneshot_start(struct sam_oneshot_s *oneshot, oneshot_handler_t handler,
                      void *arg, const struct timespec *ts)
{
  uint64_t usec;
  uint64_t regval;
  irqstate_t flags;

  tcvdbg("handler=%p arg=%p, ts=(%lu, %lu)\n",
         handler, arg, (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec);
  DEBUGASSERT(oneshot && handler && ts);

  /* Was the oneshot already running? */

  flags = irqsave();
  if (oneshot->running)
    {
      /* Yes.. then cancel it */

      tcvdbg("Already running... cancelling\n");
      (void)sam_oneshot_cancel(oneshot, NULL);
    }

  /* Save the new handler and its argument */

  oneshot->handler = handler;
  oneshot->arg     = arg;

  /* Express the delay in microseconds */

  usec = (uint64_t)ts->tv_sec * USEC_PER_SEC + (uint64_t)(ts->tv_nsec / NSEC_PER_USEC);

  /* Get the timer counter frequency and determine the number of counts need to achieve the requested delay.
   *
   *   frequency = ticks / second
   *   ticks     = seconds * frequency
   *             = (usecs * frequency) / USEC_PER_SEC;
   */

  regval = (usec * (uint64_t)sam_tc_divfreq(oneshot->tch)) / USEC_PER_SEC;

  tcvdbg("usec=%llu regval=%08llx\n", usec, regval);
  DEBUGASSERT(regval <= UINT32_MAX);

  /* Set up to receive the callback when the interrupt occurs */

  (void)sam_tc_attach(oneshot->tch, sam_oneshot_handler, oneshot,
                      TC_INT_CPCS);

  /* Set RC so that an event will be triggered when TC_CV register counts
   * up to RC.
   */

  sam_tc_setregister(oneshot->tch, TC_REGC, (uint32_t)regval);

  /* Start the counter */

  sam_tc_start(oneshot->tch);

  /* Enable interrupts.  We should get the callback when the interrupt
   * occurs.
   */

  oneshot->running = true;
  irqrestore(flags);
  return OK;
}
Example #3
0
int sam_oneshot_cancel(struct sam_oneshot_s *oneshot, struct sam_freerun_s *freerun,
                       struct timespec *ts)
{
  irqstate_t flags;
  uint64_t usec;
  uint64_t sec;
  uint64_t nsec;
  uint32_t count;
  uint32_t rc;

  /* Was the timer running? */

  flags = enter_critical_section();
  if (!oneshot->running)
    {
      /* No.. Just return zero timer remaining and successful cancellation.
       * This function may execute at a high rate with no timer running
       * (as when pre-emption is enabled and disabled).
       */

      ts->tv_sec  = 0;
      ts->tv_nsec = 0;
      leave_critical_section(flags);
      return OK;
    }

  /* Yes.. Get the timer counter and rc registers and stop the counter.  If
   * the counter expires while we are doing this, the counter clock will be
   * stopped, but the clock will not be disabled.
   *
   * The expected behavior is that the the counter register will freezes at
   * a value equal to the RC register when the timer expires.  The counter
   * should have values between 0 and RC in all other cased.
   *
   * REVISIT:  This does not appear to be the case.
   */

  tmrinfo("Cancelling...\n");

  count = sam_tc_getcounter(oneshot->tch);
  rc    = sam_tc_getregister(oneshot->tch, TC_REGC);

  /* In the case the timer/counter was canceled very short after its start,
   * the counter register can hold the wrong value (the value of the last
   * run). To prevent this the counter value is set to zero if not at
   * least on tick passed since the start of the timer/counter.
   */

  if (count > 0 && sam_tc_getcounter(freerun->tch) == oneshot->start_count)
    {
      count = 0;
    }

  /* Now we can disable the interrupt and stop the timer. */

  sam_tc_attach(oneshot->tch, NULL, NULL, 0);
  sam_tc_stop(oneshot->tch);

  oneshot->running = false;
  oneshot->handler = NULL;
  oneshot->arg     = NULL;
  leave_critical_section(flags);

  /* Did the caller provide us with a location to return the time
   * remaining?
   */

  if (ts)
    {
      /* Yes.. then calculate and return the time remaining on the
       * oneshot timer.
       */

      tmrinfo("rc=%lu count=%lu usec=%lu\n",
              (unsigned long)rc, (unsigned long)count, (unsigned long)usec);

      /* REVISIT: I am not certain why the timer counter value sometimes
       * exceeds RC.  Might be a bug, or perhaps the counter does not stop
       * in all cases.
       */

      if (count >= rc)
        {
          /* No time remaining (?) */

          ts->tv_sec  = 0;
          ts->tv_nsec = 0;
        }
      else
        {
          /* The total time remaining is the difference.  Convert the that
           * to units of microseconds.
           *
           *   frequency = ticks / second
           *   seconds   = ticks * frequency
           *   usecs     = (ticks * USEC_PER_SEC) / frequency;
           */

          usec        = (((uint64_t)(rc - count)) * USEC_PER_SEC) /
                        sam_tc_divfreq(oneshot->tch);

          /* Each time the timer/counter is canceled the time calculated from
           * the two registers (counter and REGC) is accurate up to an error
           * between 0 and USEC_PER_TICK microseconds. To correct this error
           * one tick which means USEC_PER_TICK microseconds are subtracted.
           */

          usec        = usec > USEC_PER_TICK ? usec - USEC_PER_TICK : 0;

          /* Return the time remaining in the correct form */

          sec         = usec / USEC_PER_SEC;
          nsec        = ((usec) - (sec * USEC_PER_SEC)) * NSEC_PER_USEC;

          ts->tv_sec  = (time_t)sec;
          ts->tv_nsec = (unsigned long)nsec;
        }

      tmrinfo("remaining (%lu, %lu)\n",
              (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec);
    }

  return OK;
}
int sam_freerun_counter(struct sam_freerun_s *freerun, struct timespec *ts)
{
  uint64_t usec;
  uint32_t counter;
  uint32_t verify;
  uint32_t sr;
  uint32_t overflow;
  uint32_t sec;
  irqstate_t flags;

  DEBUGASSERT(freerun && freerun->tch && ts);

  /* Temporarily disable the overflow counter */

  flags    = irqsave();
  overflow = freerun->overflow;
  counter  = sam_tc_getcounter(freerun->tch);
  sr       = sam_tc_getpending(freerun->tch);
  verify   = sam_tc_getcounter(freerun->tch);
  irqrestore(flags);

  tcvdbg("counter=%lu (%lu) overflow=%lu, sr=%08lx\n",
         (unsigned long)counter,  (unsigned long)verify,
         (unsigned long)overflow, (unsigned long)sr);

  /* If an interrupt was pending before we re-enabled interrupts,
   * then our value of overflow needs to be incremented.
   */

  if ((sr & TC_INT_COVFS) != 0)
    {
      /* Increment the overflow count and use the value of the
       * guaranteed to be AFTER the overflow occurred.
       */

      overflow++;
      counter = verify;

      tcvdbg("counter=%lu overflow=%lu\n",
             (unsigned long)counter, (unsigned long)overflow);
    }

  /* Convert the whole thing to units of microseconds.
   *
   *   frequency = ticks / second
   *   seconds   = ticks * frequency
   *   usecs     = (ticks * USEC_PER_SEC) / frequency;
   */

  usec = ((((uint64_t)overflow << 32) + (uint64_t)counter) * USEC_PER_SEC) /
         sam_tc_divfreq(freerun->tch);

  /* And return the value of the timer */

  sec         = (uint32_t)(usec / USEC_PER_SEC);
  ts->tv_sec  = sec;
  ts->tv_nsec = (usec - (sec * USEC_PER_SEC)) * NSEC_PER_USEC;

  tcvdbg("usec=%llu ts=(%lu, %lu)\n",
          usec, (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec);

  return OK;
}
Example #5
0
int sam_oneshot_start(struct sam_oneshot_s *oneshot, struct sam_freerun_s *freerun,
                      oneshot_handler_t handler, void *arg, const struct timespec *ts)
{
  uint64_t usec;
  uint64_t regval;
  irqstate_t flags;

  tmrinfo("handler=%p arg=%p, ts=(%lu, %lu)\n",
          handler, arg, (unsigned long)ts->tv_sec, (unsigned long)ts->tv_nsec);
  DEBUGASSERT(oneshot && handler && ts);

  /* Was the oneshot already running? */

  flags = enter_critical_section();
  if (oneshot->running)
    {
      /* Yes.. then cancel it */

      tmrinfo("Already running... cancelling\n");
      (void)sam_oneshot_cancel(oneshot, freerun, NULL);
    }

  /* Save the new handler and its argument */

  oneshot->handler = handler;
  oneshot->arg     = arg;

  /* Express the delay in microseconds */

  usec = (uint64_t)ts->tv_sec * USEC_PER_SEC + (uint64_t)(ts->tv_nsec / NSEC_PER_USEC);

  /* Get the timer counter frequency and determine the number of counts need to achieve the requested delay.
   *
   *   frequency = ticks / second
   *   ticks     = seconds * frequency
   *             = (usecs * frequency) / USEC_PER_SEC;
   */

  regval = (usec * (uint64_t)sam_tc_divfreq(oneshot->tch)) / USEC_PER_SEC;

  tmrinfo("usec=%llu regval=%08llx\n", usec, regval);
  DEBUGASSERT(regval <= UINT32_MAX);

  /* Set up to receive the callback when the interrupt occurs */

  (void)sam_tc_attach(oneshot->tch, sam_oneshot_handler, oneshot,
                      TC_INT_CPCS);

  /* Set RC so that an event will be triggered when TC_CV register counts
   * up to RC.
   */

  sam_tc_setregister(oneshot->tch, TC_REGC, (uint32_t)regval);

  /* Start the counter */

  sam_tc_start(oneshot->tch);

  /* The function sam_tc_start() starts the timer/counter by setting the
   * bits TC_CCR_CLKEN and TC_CCR_SWTRG in the channel control register.
   * The first one enables the timer/counter the latter performs an
   * software trigger, which starts the clock and sets the counter
   * register to zero. This reset is performed with the next valid edge
   * of the selected clock. Thus it can take up USEC_PER_TICK microseconds
   * until the counter register becomes zero.
   *
   * If the timer is canceled within this period the counter register holds
   * the counter value for the last timer/counter run. To circumvent this
   * the counter value of the freerun timer/counter is stored at each start
   * of the oneshot timer/counter.
   *
   * The function up_timer_gettime() could also be used for this but it takes
   * too long. If up_timer_gettime() is called within this function the problem
   * vanishes at least if compiled with no optimisation.
   */

  oneshot->start_count = sam_tc_getcounter(freerun->tch);

  /* Enable interrupts.  We should get the callback when the interrupt
   * occurs.
   */

  oneshot->running = true;
  leave_critical_section(flags);
  return OK;
}