/*
  module.c
  --------
  Module code
  Provides a timer facility, mainly for the Midi Support Driver Modules

  7/6/23 created
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <kernel.h>
#include <swis.h>
#include "modhdr.h"

#ifndef NULL
#define NULL 0
#endif

#ifndef FALSE
#define FALSE 0
#define TRUE !FALSE
#endif

typedef struct mod_s
{
  FILE *log;
  unsigned int debug;
} mod_t;

#define DBG_TIMER 0

mod_t mod;

enum
{
  ERR_NO_TIMER = 1,
  ERR_NO_HAL,
  ERR_CLAIM_FAIL,
  ERR_PERIOD_FAIL,
  ERR_INTR_FAIL,
  ERR_NO_MEMORY,
  ERR_NO_INTERRUPT
};

static const char * const errstr[] =
{
  "Cannot allocate timer",
  "No HAL found",
  "Unable to claim vector",
  "Unable to set timer period",
  "Cannot enable timer interrupt",
  "Cannot allocate memory",
  "No interrupts from timer"
};

/* DEBUG_TIMING:
 This was so I could actually see what was going on in true real time.
 Scope trigger on gpio 16, 500Hz square wave produced within the interrupt.
 gpio 19,20,21,26 provide pulses for the first 4 clients, hi when executing. */
//#define DEBUG_TIMING

#define HAL_IRQEnable           1
#define HAL_IRQDisable          2
#define HAL_IRQClear            3
#define HAL_Timers             12
#define HAL_TimerDevice        13
#define HAL_TimerGranularity   14
#define HAL_TimerMaxPeriod     15
#define HAL_TimerSetPeriod     16
#define HAL_TimerPeriod        17
#define HAL_TimerReadCountdown 18
#define HAL_TimerIRQClear     113
#define HAL_TimerIRQStatus    114

#define GPIO_ReadData     0x58f80
#define GPIO_WriteData    0x58f81
#define GPIO_ReadOE       0x58f82
#define GPIO_WriteOE      0x58f83
#define GPIO_ReadMode     0x58f8f
#define GPIO_WriteMode    0x58f90
#define GPIO_ReadEvent    0x58fab
#define GPIO_WriteEvent   0x58fac
#define GPIO_Info         0x58fb6
#define GPIO_Features     0x58faf

#define TickerV 0x1c

#define BASE_RATE   1000 // base timing frequency in Hz

void module_call(int fn, _kernel_swi_regs *regs, int pw); // veneer.s

// client entry in linked list of clients
typedef struct client_s client_t;
struct client_s
{
  client_t *next;     // pointer to next client in list, 0 if last
  client_t *prev;     // pointer to previous client in list, 0 if first
  char *name;         // pointer to name string, NULL if none
  int client;         // client call address
  int pw;             // client r12 value
  int period;         // number of ticks between each call
  int count;          // period counter for above
  unsigned int old_ticks; // tick counter value when client last called
};

typedef struct timer_s
{
  int err;            // timer setup errors
  int callbacks;      // count of ticks before callback executes
  int device;         // device number
  int id;             // id number
  int num;            // number of available timers
  int clock;          // clock frequency of timer
  unsigned int ticks; // tick counter, ticks since initialisation
  int count;          // incremented on every ticker interrupt
  int incr;           // added to ticks every interrupt, normally 1, but 2 for RPCEmu
  int systicks;       // used to measure the period against the system ticker
  int interrupt;      // incremented on every timer interrupt
  client_t *first;    // pointer to first client
  client_t *last;     // pointer to last client
} timer_t;

typedef struct ticker_s
{
  unsigned int ticks;
  int callbacks;
} ticker_t;

static ticker_t ticker;
static timer_t timer;

#ifdef DEBUG_TIMING
static const int gpio_pins[5] = {16,19,20,21,26};
#endif


/*
 * get_oserror
 * -----------
 */
_kernel_oserror *get_oserror(int err)
{
  static _kernel_oserror oserr;

  oserr.errnum = ERROR_BASE + err;
  strcpy(oserr.errmess, errstr[err]);

  return &oserr;
}


/*
 * module_init
 * -----------
 */
_kernel_oserror *module_init(const char *tail, int podule_base, void *pw)
{
  _kernel_swi_regs regs;

  timer.err = 0;

  // get number of timers available
  regs.r[0] = 0;
  regs.r[8] = 0;
  regs.r[9] = HAL_Timers;
  if(_kernel_swi(OS_Hardware, &regs, &regs))
    timer.err = ERR_NO_HAL;
  else
  {
    timer.num = regs.r[0];

    if(timer.num < 2) // need ticker plus at least 1 more
      timer.err = ERR_NO_TIMER;
    else
    {
      timer.id = 1;

      // get device number
      regs.r[0] = timer.id;
      regs.r[8] = 0;
      regs.r[9] = HAL_TimerDevice;
      _kernel_swi(OS_Hardware, &regs, &regs);
      timer.device = regs.r[0];

      // get the clock speed
      regs.r[0] = timer.id;
      regs.r[8] = 0;
      regs.r[9] = HAL_TimerGranularity;
      _kernel_swi(OS_Hardware, &regs, &regs);
      timer.clock = regs.r[0];

      // set the period
      int period = timer.clock / BASE_RATE;
      regs.r[0] = timer.id;
      regs.r[1] = period;
      regs.r[8] = 0;
      regs.r[9] = HAL_TimerSetPeriod;
      _kernel_swi(OS_Hardware, &regs, &regs);

      // claim the vector
      regs.r[0] = timer.device;
      regs.r[1] = (int)timer_irq;
      regs.r[2] = (int)pw;
      if(_kernel_swi(OS_ClaimDeviceVector, &regs, &regs))
        timer.err = ERR_CLAIM_FAIL;
      else
      {
        // enable the interrupt
        regs.r[0] = timer.device;
        regs.r[8] = 0;
        regs.r[9] = HAL_IRQEnable;
        _kernel_swi(OS_Hardware, &regs, &regs);
        // note. if r0 != 0 then the interrupt was already enabled
      }
    }
  }

  timer.first = NULL;
  timer.last = NULL;
  timer.callbacks = 0;
  timer.ticks = 0;
  timer.incr = (timer.err) ? 10 : 1; // if timer 1 fails, use the ticker

  // claim the ticker to keep a check on timer 1, or to use instead of timer 1
  regs.r[0] = TickerV;
  regs.r[1] = (int)tickerv;
  regs.r[2] = (int)pw;
  _kernel_swi(OS_Claim,&regs,&regs);

#ifdef DEBUG_TIMING
  // testing, setup gpio pins to provide outputs to display timing
  int i;
  for(i=0; i<5; i++)
  {
    regs.r[0] = gpio_pins[i];
    regs.r[1] = 0; // output
    _kernel_swi(GPIO_WriteOE, &regs, &regs);
  }
#endif

  return NULL;
}


/*
 * module_term
 * -----------
 */
_kernel_oserror *module_term(int fatal, int podule_base, void *pw)
{
  _kernel_swi_regs regs;

  // release ticker
  regs.r[0] = TickerV;
  regs.r[1] = (int)tickerv;
  regs.r[2] = (int)pw;
  _kernel_swi(OS_Release,&regs,&regs);

  if(ticker.callbacks)
  {
    regs.r[0] = (int)ticker_callback;
    regs.r[1] = (int)pw;
    _kernel_swi(OS_RemoveCallBack, &regs, &regs);
  }

  if((timer.err == 0) || (timer.err == ERR_NO_INTERRUPT))
  {
    // Disable the timer's interrupts
    regs.r[0] = timer.device;
    regs.r[8] = 0;
    regs.r[9] = HAL_IRQDisable;
    _kernel_swi(OS_Hardware, &regs, &regs);
    // Release the device
    regs.r[0] = timer.device;
    regs.r[1] = (int)timer_irq;
    regs.r[2] = (int)pw;
    _kernel_swi(OS_ReleaseDeviceVector, &regs, &regs);
    timer.device = 0;
  }

  if(timer.callbacks)
  {
    regs.r[0] = (int)timer_callback;
    regs.r[1] = (int)pw;
    _kernel_swi(OS_RemoveCallBack, &regs, &regs);
  }

  // free linked list memory
  client_t *cur = timer.first, *next;
  while(cur)
  {
    next = cur->next;
    free(cur);
    cur = next;
  }

  return NULL;
}


/*
 * module_swi
 * ----------
 */
_kernel_oserror *module_swi(int number, _kernel_swi_regs *r, void *pw)
{
  client_t *cur;

  switch(number + MTimer_00)
  {
    case MTimer_Register:
      if((cur = malloc(sizeof(client_t))) == 0)
        return get_oserror(ERR_NO_MEMORY-1);
      cur->client = r->r[0]; // call address
      cur->pw     = r->r[1]; // r12 (pw) value
      cur->period = r->r[2]; // initial timing period
      cur->name   = (char *)r->r[3]; // name string
      cur->count = 1;
      cur->old_ticks = timer.ticks;
      if(timer.first == 0) // first item
      {
        cur->prev = 0;
        timer.first = cur;
      }
      else // not first item
      {
        cur->prev = timer.last;
        timer.last->next = cur;
      }
      cur->next = 0;
      timer.last = cur;

      if(mod.debug & (1<<DBG_TIMER))
        if(mod.log)
        {
          fprintf(mod.log, "registered:\n");
          fprintf(mod.log, "client %08X, period %d\n", r->r[0], r->r[2]);
          fprintf(mod.log, "first %08X, last %08X\n",
             (int)timer.first, (int)timer.last);
          fprintf(mod.log, "prev %08X, next %08X\n",
             (int)cur->prev, (int)cur->next);
        }
      break;

    case MTimer_Remove:
      // r0 = call address
      cur = timer.first;
      while(cur)
      {
        if(cur->client == r->r[0])
          break;
        cur = cur->next;
      }
      if(!cur)
        break; // client not registered, ignore

      if(cur->prev)
        cur->prev->next = cur->next;
      else
        timer.first = cur->next;

      if(cur->next)
        cur->next->prev = cur->prev;
      else
        timer.last = cur->prev;
      free(cur);
      break;

    case MTimer_ReadTime:
      r->r[0] = timer.ticks;
      break;
  }
  return NULL;
}


/*
 * module_command
 * --------------
 */
_kernel_oserror *module_command(const char *arg_string, int argc, int number, void *pw)
{
  client_t *cur;
  int i;

  switch(number)
  {
    case CMD_MTimerReport:
      printf("ticks %d\n", timer.ticks);
      i = 1;
      cur = timer.first;
      while(cur)
      {
        if(cur->name)
          printf("client %d, period %dms, call &%X, %s\n", i++, cur->period, cur->client, cur->name);
        else
          printf("client %d, period %dms, call &%X\n", i++, cur->period, cur->client);
//        printf("prev %X, cur %X, next %X\n\n", (int)cur->prev, (int)cur, (int)cur->next);
        cur = cur->next;
      }
      break;

    case CMD_MTimerTest:
      printf("Timer count = %d\n", timer.count);
      printf("Timer increment = %d\n", timer.incr);
      if(timer.err > 0)
        printf("Error: %s, using system ticker\n", errstr[timer.err-1]);
      break;
  }

  return NULL;
}


/*
 * call_clients
 * ------------
 * Calls each client in turn, when their period expires
 */
static void call_clients(void)
{
  _kernel_swi_regs regs;
  client_t *cur = timer.first;
  unsigned int ticks = timer.ticks;

#ifdef DEBUG_TIMING
  int pin = 1; // testing
#endif

  while(cur)
  {
    if(!--cur->count)
    {
#ifdef DEBUG_TIMING
      if(pin < 5)
      {
        regs.r[0] = gpio_pins[pin];
        regs.r[1] = 1;
        _kernel_swi(GPIO_WriteData, &regs, &regs);
      }
#endif

      regs.r[0] = cur->period;
      regs.r[1] = ticks;
      regs.r[2] = ticks - cur->old_ticks; //timer.callbacks;
      cur->old_ticks = ticks;
      regs.r[3] = timer.incr;
      module_call(cur->client, &regs, cur->pw);
      if(regs.r[0] > 0)
        cur->period = regs.r[0];
      cur->count = cur->period;

#ifdef DEBUG_TIMING
      if(pin < 5)
      {
        regs.r[0] = gpio_pins[pin];
        regs.r[1] = 0;
        _kernel_swi(GPIO_WriteData, &regs, &regs);
      }
#endif
    }

#ifdef DEBUG_TIMING
    pin++;
#endif
    cur = cur->next;
  }
}


/*
 * timer_irq_handler
 * -----------------
 * Clears the interrupt flags, increments ticks, adds a callback
 */
int timer_irq_handler(_kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;


  if(r->r[0] != timer.device)
    return VECTOR_PASSON;

#ifdef DEBUG_TIMING
  static int x;
  regs.r[0] = gpio_pins[0];
  regs.r[1] = x ^= 1;
  _kernel_swi(GPIO_WriteData, &regs, &regs);
#endif

  regs.r[0] = timer.id;
  regs.r[8] = 0;
  regs.r[9] = HAL_TimerIRQClear;
  _kernel_swi (OS_Hardware, &regs, &regs);
  regs.r[0] = timer.device;
  regs.r[8] = 0;
  regs.r[9] = HAL_IRQClear;
  _kernel_swi (OS_Hardware, &regs, &regs);

  timer.interrupt++;

  if(timer.incr < 10) // check for ticker taken over
  {
    timer.ticks += timer.incr;

    if(!timer.callbacks++)
    {
      regs.r[0] = (int)timer_callback;
      regs.r[1] = (int)pw;
      _kernel_swi(OS_AddCallBack, &regs, &regs);
    }
  }

  return VECTOR_CLAIM;
}


/*
 * timer_callback_handler
 * ----------------------
 * Calls each client in turn, when their period expires
 */
_kernel_oserror *timer_callback_handler(_kernel_swi_regs *r, void *pw)
{
  call_clients();
  timer.callbacks = 0;
  return NULL;
}


/*
 * tickerv_handler
 * ---------------
 * Increments tick, adds a callback
 */
int tickerv_handler(_kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;

  if(timer.err)
  {
#ifdef DEBUG_TIMING
    static int x;
    regs.r[0] = gpio_pins[0];
    regs.r[1] = x ^= 1;
    _kernel_swi(GPIO_WriteData, &regs, &regs);
#endif

    timer.ticks += timer.incr;
  }

  if(!ticker.callbacks++)
  {
    regs.r[0] = (int)ticker_callback;
    regs.r[1] = (int)pw;
    _kernel_swi(OS_AddCallBack, &regs, &regs);
  }

  return VECTOR_PASSON;
}


/*
 * ticker_callback_handler
 * ----------------------
 * if timer 1 is not being used, calls each client in turn, when their period expires
 * else checks timer 1 operation and may take over if required
 */
_kernel_oserror *ticker_callback_handler(_kernel_swi_regs *r, void *pw)
{
//  _kernel_swi_regs regs;

  if(timer.err) // there was an error with the timer so use ticker
    call_clients();

  else // timer 1 is being used for timing, keep an eye on it !
  {    // if another program claims the timer, this will pick it up and revert to using the ticker
    if(++timer.systicks >= 10) // 100ms counter
    {
      timer.systicks = 0;
      // check number of timer interrupts since previous check.
      int t = timer.interrupt; // for a 1ms interrupt this should be 100 give or take a bit
      timer.interrupt = 0;
      timer.count = t; // for reporting
      if(t <= 0) // timer 1 stopped or not working, use system ticker
      {
        timer.err = ERR_NO_INTERRUPT; // for reporting
        timer.incr = 10;
      }
      else if(t < 55) // RPCEmu check (seems it can't do less than a 2ms period)
        timer.incr = 2; // compensate for running at half speed
   // else
   //   timer 1 running correctly
    }
  }

  ticker.callbacks = 0;

  return NULL;
}



