text: /**
   * Recreated Espressif libmain ets_timer.o contents.
   *
   * Copyright (C) 2015 Espressif Systems. Derived from MIT Licensed SDK libraries.
   * BSD Licensed as described in the file LICENSE
   *
   * Copyright (c) 2016 sheinz (https://github.com/sheinz)
   *
   * This module seems to be adapted from NONOS SDK by Espressif to fit into
   * RTOS SDK. Function sdk_ets_timer_handler_isr is no longer an ISR handler
   * but still holds its name. Espressif just added a task that receives events
   * from the real FRC2 timer ISR handler and calls former ISR handler.
   * So, timer callbacks are called from the task context rather than an interrupt.
   *
   * Modifications from the original reverse engineered version:
   *  - FreeRTOS queue is replaced with Task notifications.
   *  - Removed unknown queue length monitoring and parameters allocation.
   *  - Removed unused debug variables
   *  - xTaskGenericCreate is replaced with xTaskCreate
   *  - simplified time to ticks conversion (simply multiply by 5)
   *
   * This timer should be used with caution together with other tasks. As the
   * timer callback is executed within timer task context, access to data that
   * other tasks accessing should be protected.
   */
  #include "open_esplibs.h"
  
  #if OPEN_LIBMAIN_ETS_TIMER
  
  #if 0
  
  #include "etstimer.h"
  #include "espressif/osapi.h"
  
  void sdk_ets_timer_setfn(ETSTimer *timer, ETSTimerFunc *func, void *parg) {
      sdk_os_timer_setfn(timer, func, parg);
  }
  
  void sdk_ets_timer_arm(ETSTimer *timer, uint32_t value, bool repeat_flag) {
      sdk_os_timer_arm(timer, value, repeat_flag);
  }
  
  void sdk_ets_timer_arm_ms_us(ETSTimer *timer, uint32_t value,
                               bool repeat_flag, bool value_in_ms) {
      sdk_os_timer_arm(timer, value * 1000 + value_in_ms, repeat_flag);
  }
  
  void sdk_ets_timer_disarm(ETSTimer *timer) {
      sdk_os_timer_disarm(timer);
  }
  
  void sdk_ets_timer_init() {
  }
  
  #else
  
  #include "open_esplibs.h"
  #include <stdint.h>
  #include <stdbool.h>
  #include <esp/timer_regs.h>
  #include <FreeRTOS.h>
  #include <timers.h>
  #include <queue.h>
  #include <stdio.h>
  
  typedef void ets_timer_func_t(void *);
  
  /**
   * This structure is used for both timers: ets_timer.c and timer.c
   */
  typedef struct ets_timer_st {
      struct ets_timer_st  *next;
      TimerHandle_t timer_handle;  // not used in ets_timer.c
      uint32_t fire_ticks;         // FRC2 timer value when timer should fire
      uint32_t period_ticks;       // timer value in FRC2 ticks for rpeating timers
      ets_timer_func_t *callback;
      bool repeat;                 // not used in ets_timer.c
      void *timer_arg;
  } ets_timer_t;
  
  /**
   * Special values of ets_timer_t::next field
   */
  #define ETS_TIMER_NOT_ARMED (ets_timer_t*)(0xffffffff)
  #define ETS_TIMER_LIST_END  (ets_timer_t*)(0)
  
  /**
   * Linked list of timers
   */
  static ets_timer_t* timer_list = 0;
  
  static TaskHandle_t task_handle = NULL;
  
  void sdk_ets_timer_setfn(ets_timer_t *timer, ets_timer_func_t *func, void *parg)
  {
      timer->callback = func;
      timer->timer_arg = parg;
      timer->fire_ticks = 0;
      timer->period_ticks = 0;
      timer->next = ETS_TIMER_NOT_ARMED;
  }
  
  static inline void set_alarm_value(uint32_t value)
  {
      TIMER_FRC2.ALARM = value;
  }
  
  /**
   * Set timer alarm and make sure the alarm is set in the future
   * and will not be missed by the timer.
   */
  static void set_alarm(uint32_t ticks)
  {
      uint32_t curr_time = TIMER_FRC2.COUNT;
      int32_t delta = (int32_t)ticks - curr_time;
      if ((delta - 40) < 1) {
          if (delta < 1) {
              set_alarm_value(curr_time + 40);
          } else {
              set_alarm_value(ticks + 44);
          }
      } else {
          set_alarm_value(ticks);
      }
  }
  
  /**
   *
   * Pending timer list example:
   *
   * | Timer:      | T0 | T1 | T2 | T3 |
   * |-------------|----|----|----|----|
   * | fire_ticks: | 10 | 20 | 30 | 40 |
   * | next:       | T1 | T2 | T3 | 0  |
   *
   *
   * For example we need to add a timer that should fire at 25 ticks:
   *
   * | Timer:      | T0 | T1  | new | T2 | T3 |
   * |-------------|----|-----|-----|----|----|
   * | fire_ticks: | 10 | 20  | 25  | 30 | 40 |
   * | next:       | T1 | new | T2  | T3 | 0  |
   *
   * We squeeze the timer into the list so the list will always remain sorted
   *
   * Note: if add the same timer twice the system halts
  */
  static void add_pending_timer(uint32_t ticks, ets_timer_t *timer)
  {
      ets_timer_t *prev = 0;
      ets_timer_t *curr = timer_list;
      while (curr) {
          if (((int32_t)ticks - (int32_t)curr->fire_ticks) < 1) {
              // found a timer that should fire later
              // so our timer should fire earlier
              break;
          }
          prev = curr;
          curr = curr->next;
      }
  
      timer->next = curr;
      timer->fire_ticks = ticks;
  
      if (prev != 0) {
          prev->next = timer;
      } else {
          // Our timer is the first in the line to fire
          timer_list = timer;
          set_alarm(ticks);
      }
  
      // This situation might happen if adding the same timer twice
      if (timer == timer->next) {
          // This seems like an error: %s is used for line number
          // In the recent SDK Espressif fixed the format to "%s %u\n"
          printf("%s %s \n", "ets_timer.c", (char*)209);
          while (1);
      }
  }
  
  /**
   * In the Espressif SDK 0.9.9 if try to arm already armed timer the system halts
   * with error message. In the later SDK version Espressif changed the behavior.
   * If the timer was previously armed it is disarmed and then armed without errors.
   * This version recreates behavior of SDK 0.9.9
   */
  void sdk_ets_timer_arm_ms_us(ets_timer_t *timer, uint32_t value,
          bool repeat_flag, bool value_in_ms)
  {
      uint32_t ticks = 0;
  
      if (timer->next != ETS_TIMER_NOT_ARMED) {
          // The error message doesn't tell what is wrong
          printf("arm new %x %x\n", (uint32_t)timer, (uint32_t)timer->next);
          while(1);  // halt
      }
  
      if (value_in_ms) {
          ticks = value * 5000;
      } else {
          ticks = value * 5;
      }
  
      if (repeat_flag) {
          timer->period_ticks = ticks;
      }
      vPortEnterCritical();
      add_pending_timer(TIMER_FRC2.COUNT + ticks, timer);
      vPortExitCritical();
  }
  
  void sdk_ets_timer_arm(ets_timer_t *timer, uint32_t milliseconds,
          bool repeat_flag)
  {
      sdk_ets_timer_arm_ms_us(timer, milliseconds, repeat_flag,
              /*value in ms=*/true);
  }
  
  void sdk_ets_timer_arm_us(ets_timer_t *timer, uint32_t useconds,
          bool repeat_flag)
  {
      sdk_ets_timer_arm_ms_us(timer, useconds, repeat_flag,
              /*value in ms=*/false);
  }
  
  /**
   * Function removes a timer from the pending timers list.
   */
  void sdk_ets_timer_disarm(ets_timer_t *timer)
  {
      vPortEnterCritical();
      ets_timer_t *curr = timer_list;
      ets_timer_t *prev = 0;
      while (curr) {
          if (curr == timer) {
              if (prev) {
                  prev->next = curr->next;
              } else {
                  timer_list = curr->next;
              }
              break;
          }
          prev = curr;
          curr = curr->next;
      }
      vPortExitCritical();
      timer->next = ETS_TIMER_NOT_ARMED;
      timer->period_ticks = 0;
  }
  
  /**
   * Check the list of pending timers for expired ones and process them.
   */
  static inline void process_pending_timers()
  {
      vPortEnterCritical();
      int32_t ticks = TIMER_FRC2.COUNT;
      while (timer_list) {
          if (((int32_t)timer_list->fire_ticks - ticks) < 1) {
              ets_timer_t *timer = timer_list;
              timer_list = timer->next;
              timer->next = ETS_TIMER_NOT_ARMED;
  
              vPortExitCritical();
              timer->callback(timer->timer_arg);
              vPortEnterCritical();
  
              if (timer->next == ETS_TIMER_NOT_ARMED) {
                  if (timer->period_ticks) {
                      timer->fire_ticks = timer->fire_ticks + timer->period_ticks;
                      add_pending_timer(timer->fire_ticks, timer);
                  }
              }
              ticks = TIMER_FRC2.COUNT;
          } else {
              if (timer_list) {
                  set_alarm(timer_list->fire_ticks);
              }
              break;
          }
      }
      vPortExitCritical();
  }
  
  /**
   * .Lfunc002
   */
  static void IRAM frc2_isr(void *arg)
  {
      BaseType_t task_woken = 0;
  
      BaseType_t result = xTaskNotifyFromISR(task_handle, 0, eNoAction, &task_woken);
      if (result != pdTRUE) {
          printf("TIMQ_FL:%d!!", (uint32_t)result);
      }
  
      portEND_SWITCHING_ISR(task_woken);
  }
  
  /**
   * .Lfunc007
   *
   * Timer task
   */
  static void timer_task(void* param)
  {
      while (true) {
          if (xTaskNotifyWait(0, 0, NULL, portMAX_DELAY) == pdTRUE) {
              process_pending_timers();
          }
      }
  }
  
  void sdk_ets_timer_init()
  {
      timer_list = 0;
  
      _xt_isr_attach(INUM_TIMER_FRC2, frc2_isr, NULL);
  
      /* Original code calls xTaskGenericCreate:
       * xTaskGenericCreate(task_handle, "rtc_timer_task", 200, 0, 12, &handle,
       *     NULL, NULL);
       */
      xTaskCreate(timer_task, "rtc_timer_task", 200, 0, 12, &task_handle);
      printf("frc2_timer_task_hdl:%p, prio:%d, stack:%d\n", task_handle, 12, 200);
  
      TIMER_FRC2.ALARM = 0;
      TIMER_FRC2.CTRL =  VAL2FIELD(TIMER_CTRL_CLKDIV, TIMER_CLKDIV_16)
          | TIMER_CTRL_RUN;
      TIMER_FRC2.LOAD = 0;
  
      DPORT.INT_ENABLE |= DPORT_INT_ENABLE_TIMER1;
  
     _xt_isr_unmask(BIT(INUM_TIMER_FRC2));
  }
  
  #endif
  
  #endif /* OPEN_LIBMAIN_ETS_TIMER */
  