/// \file touch.c Busyloop implementation for reading resistive touchpad coordinates

#include <vstypes.h>
#include <stdlib.h> //abs
#include <vs1005h.h>
#include <clockspeed.h>
#include <timers.h>
#include <string.h>
#include <hwLocks.h>
#include <audio.h>
#include <parseFileParam.h>
#include "touch_int.h"
#include <vo_gpio.h> //GpioSetPin
#include <devboard.h> //Delay
#include <lcd.h> //lcd0 width and height
#include <vsos.h>
#include <vo_stdio.h>
#include <volink.h>
#include <imem.h>


struct TouchInfo touchInfo = {
  //minX minY maxX maxY calMode      persistance   lastTimeCnt oldX oldY
  124, 138, 1635, 1512,  0,     TICKS_PER_SEC/20,   0x40000000,   0,   0
};

struct TouchInfo __mem_y touchInfoG = {
  //minX minY maxX maxY calMode      persistance   lastTimeCnt oldX oldY
  33,  33,  440,  440,  0,     TICKS_PER_SEC/20,   0x40000000,   0,   0
};

u_int16 sarScale = 4;



#define SETTINGS_LINE_MAX_SIZE 40

void ReadTouchSettings(void) {
  FILE *f;
	
  f = fopen("S:VSOS.INI", "r");

  if (f) {
    char s[SETTINGS_LINE_MAX_SIZE];
    char *s2;
    while ((s2 = fgets(s, SETTINGS_LINE_MAX_SIZE, f))) {
      u_int16 t;
      if ((t = FileParamInt(s, "touchHMinX", 0xFFFFU)) != 0xFFFFU) {
	pTouchInfo->minX = t*sarScale;
      }
      if ((t = FileParamInt(s, "touchHMinY", 0xFFFFU)) != 0xFFFFU) {
	pTouchInfo->minY = t*sarScale;
      }
      if ((t = FileParamInt(s, "touchHMaxX", 0xFFFFU)) != 0xFFFFU) {
	pTouchInfo->maxX = t*sarScale;
      }
      if ((t = FileParamInt(s, "touchHMaxY", 0xFFFFU)) != 0xFFFFU) {
	pTouchInfo->maxY = t*sarScale;
      }
    }
    fclose(f);
  }	
}



#define INTS_PER_SEC 10000 /* How many times a second to make interrupt */
/* How many points to measure for each direction (X, Y) */
#define MEAS_POINTS 5
// MEAS_DELAY measures how many interrupts the whole measurement cycle is
// (including both actual measurement and delay)
// The active measurement takes 2*MEAS_POINTS+1 interrupts.
// So, with MEAS_POINTS=5, MEAS_DELAY=100, and INTS_PER_SEC=10000,
// utilization is about 10%, and we get a new measurement sample
// 100 times per second.
// Note, however, that this interrupt routine doesn't Obtain the hardware
// resources it needs (because interrupts cannot do that), it Attempts
// them. So, if bus utilization is very high (continuous queue), actual
// measuring may happen less often.
#define MEAS_DELAY 100

struct TouchInt {
  s_int16 phase;
  s_int16 allocatedResources;
  s_int16 res[3][2*MEAS_POINTS];
} __mem_y touchInt;





void TouchIntAsm(void);

/* Touch interrupt using the new LCC interrupt pragma (131015) */

/* touchInt.phase value at start of function:
	< -1: don't do conversion.
	-1: Start initial conversion if possible (if can allocate hardware)
	0...2*MEAS_POINTS-1 (0...9): convert.
	2*MEAS_POINTS (10): final result, free HW */
//#pragma interrupt y TouchIntVector
auto void TouchIntC(void) {
  if (touchInt.phase >= 0 && touchInt.phase < 2*MEAS_POINTS) {
    touchInt.res[0][touchInt.phase] = PERIP(SAR_DAT);
  }
  
  if (++touchInt.phase >= 2*MEAS_POINTS) {
    touchInt.phase = -MEAS_DELAY;
    memcpyYY(touchInt.res[1], touchInt.res[0], sizeof(touchInt.res[0]));
    ReleaseHwLocksBIP(HLB_NONE, HLIO_0_14|HLIO_0_15, HLP_SAR);
    touchInt.allocatedResources = 0;
  }

  if (!touchInt.phase) {
    if (AttemptHwLocksBIP(HLB_NONE, HLIO_0_14|HLIO_0_15, HLP_SAR)) {
      // If attempt fails, return phase to previous stage,
      // thus attempting to activate touch every interrupt
      // from now on until success.
      touchInt.phase--;
    } else {
      touchInt.allocatedResources = 1;
    }
  }

  if (touchInt.phase >= 0) {
    if (!touchInt.phase) {
      /* Make it X */
      GpioSetPin(TOUCH_ENA, 1);
      GpioSetPin(TOUCH_XYS, 1); // Horizontal
      PERIP(SAR_CF) = 0x7*SAR_CF_SEL | 8*SAR_CF_CK;
    } else if (touchInt.phase==MEAS_POINTS) {
      /* Make it Y */
      GpioSetPin(TOUCH_XYS, 0); // Vertical
      PERIP(SAR_CF) = 0xC*SAR_CF_SEL | 8*SAR_CF_CK;
    }
    /* Start the SAR */
    PERIP(SAR_CF) |= SAR_CF_ENA;
  }
}


DLLIMPORT(clockSpeed)


s_int16 InitTouch(void) {
  u_int32 t;

  pTouchInfo = &touchInfo;
  /* VS1005g has different SAR converters from VS1005h and later, so
     make a conversion between formats. */
  if (ReadIMem(0xFFFFU) == ROM_ID_VS1005G) {
    memcpyYX(&touchInfo, &touchInfoG, sizeof(touchInfo));
    sarScale = 1;
  }
  ReadTouchSettings();

  touchInt.phase = -MEAS_DELAY;
  memsetY(&touchInt, 0, sizeof(touchInt));
  WriteIMem(0x20+INTV_TIMER1, 0x2a00000e+((u_int32)TouchIntAsm<<6));

  t = clockSpeed.peripClkHz / (PERIP(TIMER_CF)+1) / (INTS_PER_SEC) - 1;

  PERIP(TIMER_T1L) = (u_int16)t;
  PERIP(TIMER_T1H) = (u_int16)(t>>16);
  PERIP(TIMER_ENA) |= TIMER_ENA_T1;

  PERIP(INT_ENABLE0_LP) |= INTF_TIMER1;
  PERIP(INT_ENABLE0_HP) &= ~INTF_TIMER1;

  PERIP(ANA_CF2) |= ANA_CF2_REF_ENA; //Analog reference
  PERIP(ANA_CF1) |= ANA_CF1_SAR_ENA; //SAR power and enable

  SetHandler(GetTouchLocation, MyGetTouchLocation);

  return 0;
}



void FiniTouch(void) {
  /* Turn off the interrupts. */
  PERIP(INT_ENABLE0_LP) &= ~INTF_TIMER1;
  /* Wait a couple of clock cycles */
  PERIP(0);
  PERIP(0);
  if (touchInt.allocatedResources) {
    ReleaseHwLocksBIP(HLB_NONE, HLIO_0_14|HLIO_0_15, HLP_SAR);
  }
}





s_int16 MyGetTouchLocation(s_int16 *x, s_int16 *y) {
  s_int16 i, tx, ty;
  u_int32 currTimeCount = ReadTimeCount();
  u_int16 landscapeMode = (lcd0.width >= lcd0.height);

  Disable();
  memcpyYY(touchInt.res[2], touchInt.res[1], sizeof(touchInt.res[1]));
  Enable();
	
  if (abs(touchInt.res[2][1] - touchInt.res[2][4]) < 16*sarScale &&
      abs(touchInt.res[2][MEAS_POINTS+1] - touchInt.res[2][MEAS_POINTS+4])
      < 16*sarScale) {
    tx = (touchInt.res[2][1] + touchInt.res[2][2] +
	  touchInt.res[2][3] + touchInt.res[2][4]);
    ty = (touchInt.res[2][MEAS_POINTS+1] + touchInt.res[2][MEAS_POINTS+2] +
	  touchInt.res[2][MEAS_POINTS+3] + touchInt.res[2][MEAS_POINTS+4]);
    if (!landscapeMode) {
      s_int16 t = tx;
      tx = ty;
      ty = t;
    }
    tx /= 4;
    ty /= 4;
    if (!touchInfo.calibrationMode) {
      tx = (s_int16)((s_int32)(tx-touchInfo.minX)*lcd0.width/
		  (touchInfo.maxX-touchInfo.minX) );
      ty = (s_int16)((s_int32)(ty-touchInfo.minY)*lcd0.height/
		  (touchInfo.maxY-touchInfo.minY) );
      if (landscapeMode) {
        tx = lcd0.width-tx;
      }
      touchInfo.lastPressureTimeCount = currTimeCount;
    }
    *x = touchInfo.oldX = tx;
    *y = touchInfo.oldY = ty;
    return 20;
  } else if (currTimeCount-touchInfo.lastPressureTimeCount <
	     touchInfo.persistance &&
	     !touchInfo.calibrationMode) {
    *x = touchInfo.oldX;
    *y = touchInfo.oldY;
    return 10;
  }

  return 0;
}
