/*******************************************************************************
 *
 *       A L T R O N I C S    Z 7 0 8 0    L C D    T O U C H S C R E E N
 *
 *                              D R I V E R
 *
 *      Copyright A Levido 2013 - All Rights Reserved
 *
 *******************************************************************************
 *  This module uses an ILI9340 driver chip (not ST77881 as stated by Altronics)
 *  We set up for 240 x 320 pixels, 16bpp colour UINT16 5:6:5 RGB format.
 *  Dislay is in portrait mode. Origin is top left. Uses PMP, ADC Timer1,
 *  Timer2 and OC3
 */

#include "touchscreen.h"
#include "system.h"
#include <plib.h>

UINT32 battV;

/* Hardware */
#define XYPOS_SETUP                     PORTSetBits(IOPORT_B, BIT_0 | BIT_1)
#define XYNEG_SETUP                     PORTClearBits(IOPORT_B, BIT_2 | BIT_3)
#define XPOS_ADC                        PORTSetPinsAnalogIn(IOPORT_B, BIT_0); AD1CHS = 0x00000000
#define XPOS_HIZ                        PORTSetPinsAnalogIn(IOPORT_B, BIT_0)
#define XPOS_3V3                        PORTSetPinsDigitalOut(IOPORT_B, BIT_0)
#define YPOS_ADC                        PORTSetPinsAnalogIn(IOPORT_B, BIT_1); AD1CHS = 0x00010000
#define YPOS_HIZ                        PORTSetPinsAnalogIn(IOPORT_B, BIT_1)
#define YPOS_3V3                        PORTSetPinsDigitalOut(IOPORT_B, BIT_1)
#define XNEG_HIZ                        PORTSetPinsAnalogIn(IOPORT_B, BIT_2)
#define XNEG_GND                        PORTSetPinsDigitalOut(IOPORT_B, BIT_2)
#define YNEG_HIZ                        PORTSetPinsAnalogIn(IOPORT_B, BIT_3)
#define YNEG_GND                        PORTSetPinsDigitalOut(IOPORT_B, BIT_3)
#define BATT_ADC                        AD1CHS = 0x00050000
#define B_HIGH                          616 // 3.7V * 1024 / 2.05 x 3
#define B_LOW                           566 // 3.4V * 1024 / 2.05 x 3
#define STAT1                           PORTBbits.RB8
#define STAT2                           PORTBbits.RB9
#define PGOOD                           PORTBbits.RB10

/* Dimensions */
#define MAX_X                           239
#define MAX_Y                           319

void tchInitialise(void);

/*** Touchscreen Driver *******************************************************/

//Touchscreen configuration
#define TOUCH_PRESS_THRESHOLD   	512
#define TOUCH_SCALE_FACTOR              8
#define TICK_FREQ                       100 // Hz
#define TICK_PERIOD                     ((FPB / (256 * TICK_FREQ)) - 1)
#define EVENT_RATE                      20   //Hz
#define DELAY_PERIOD                    2/*5*//*10*/    // in units of event period
#define REPEAT_PERIOD                   4/*4*/    // in units of event period
#define TICK_COUNT                      (TICK_FREQ / EVENT_RATE)
#define RawX                            adcX
#define RawY                            adcY

#define SAMPLE_POINTS                   4

TOUCH_CAL_T caldata;
volatile INT16  xRawTouch[SAMPLE_POINTS];
volatile INT16  yRawTouch[SAMPLE_POINTS];
volatile INT16  adcX;
volatile INT16  adcY;
volatile INT16 touchstate;
volatile INT16 tickcount;
volatile INT16 repeatcount;
INT16    oldX;
INT16    oldY;
INT16    state;
INT16 tchGetX(void);
INT16 tchGetY(void);
void tchDetectPosition(void);
 void tchGetEvents(void);
enum {TEST, MEASURE_Y, MEASURE_X, BATTERY};

void tchInitialise(void)
{
    /* Initialize ADC for auto sampling mode (Tad = 6.4us, Tsamp 275 us) */
    AD1CON1 = 0;            // reset
    AD1CON2 = 0;            // AVdd, AVss, int every conversion, MUXA only
    AD1CON3 = 0x1FFF;       // 31 Tad auto-sample, Tad = 256*Tcy
    AD1CSSL = 0;            // No scanned inputs
    AD1CON1 = 0x80E4;       // Turn on A/D module, use auto-convert

    /* Set up pins */
    XYPOS_SETUP;
    XYNEG_SETUP;

    /* Set up for TEST state */
    XPOS_ADC;
    YPOS_HIZ;
    XNEG_HIZ;
    YNEG_GND;
    touchstate = TEST;

    // Initial calibration values
    caldata.xscale = 80;
    caldata.xoffset = -9040;
    caldata.yscale = 96;
    caldata.yoffset = -7574;

    // Initialise private variables
    adcX = -1;
    adcY = -1;
    tickcount = 0;
    repeatcount = 0;
    oldX = -1;
    oldY = -1;
    state = INVALID;

    //Initialise Tick timer for polling touchscreen
    OpenTimer1(T1_ON | T1_SOURCE_INT | T1_PS_1_256, TICK_PERIOD);
    ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_7);
    INTSetVectorPriority(INT_TIMER_1_VECTOR,INT_PRIORITY_LEVEL_3);
    INTEnable(INT_T1, INT_ENABLED);
}

void tchSleepWake(void)
{
    UINT32 temp;
    // Close timer and disable timer interrupts
    INTEnable(INT_T1, INT_DISABLED);
    CloseTimer1();
    AD1CON1bits.ADON = 0;

    YPOS_HIZ;
    YNEG_GND;
    XNEG_HIZ;

    PORTSetPinsDigitalIn(IOPORT_B, BIT_0);
    CNCONbits.SIDL = 0;
    CNCONbits.ON = 1;
    CNENbits.CNEN2 = 1;
    temp = PORTB;
    INTSetVectorPriority(INT_CHANGE_NOTICE_VECTOR, INT_PRIORITY_LEVEL_5);
    INTClearFlag(INT_CN);
    INTEnable(INT_CN, INT_ENABLED);

    /* go to sleep */
    PowerSaveSleep();

    /* woken from sleep */
    CNCONbits.ON = 0;
    tchInitialise();
}
void __ISR(_CHANGE_NOTICE_VECTOR, IPL5AUTO) ChangeNoticeHandler(void)
{
    INTClearFlag(INT_CN);
    INTEnable(INT_CN, INT_DISABLED);
}

extern UINT32 mode;
enum { IDLE, RECORD, PLAY, USB};

void __ISR(_TIMER_1_VECTOR, IPL3AUTO) Timer1Handler(void)
{
    mT1ClearIntFlag();

    tchDetectPosition();
    //sdDetectStatus();
    if(++tickcount == TICK_COUNT || mode == RECORD) {
        tickcount = 0;
        tchGetEvents();
    }
}

/* No idea why this is necessary but if it's a non-volatile local within
 * the tchDetectPosition function, when optimisation is enabled, ADC1BUF0
 * is not read properly and yTemp remains 0. */

volatile unsigned int yTemp;

void tchDetectPosition(void)
{
    switch(touchstate){
        case TEST:
            if(ADC1BUF0 < TOUCH_PRESS_THRESHOLD){
                YPOS_3V3;
                touchstate = MEASURE_Y;
            }
            else {
                //touchstate = TEST;
                touchstate = BATTERY;
                BATT_ADC;
                adcX = -1;
                adcY = -1;
            }
            break;
        case BATTERY:
            battV = ADC1BUF0;
            touchstate = TEST;
            adcX = -1;
            adcY = -1;
            XPOS_ADC;
            break;

        case MEASURE_Y:
            yTemp = ADC1BUF0;
            XPOS_3V3;
            XNEG_GND;
            YPOS_ADC;
            YNEG_HIZ;
            touchstate = MEASURE_X;
            break;

        case MEASURE_X:
            adcX = ADC1BUF0;
            adcY = yTemp;
            XPOS_ADC;
            YPOS_HIZ;
            XNEG_HIZ;
            YNEG_GND;
            touchstate = TEST;
            break;


    }
}
UINT32 tchGetVUSB(void)
{
    if((STAT1 == 1) && (STAT2 == 1) ) { return FALSE; }
    else return TRUE;
}
UINT8 tchGetPgood(void)
{
    return PGOOD;
}
UINT8 * tchGetBatt(void)
{
    if((STAT1 == 1) && (STAT2 == 1) ) { // Discharging
        if(battV > B_HIGH) return "HIGH";
        if(battV > B_LOW) return "FAIR";
        else return "LOW ";
    }
    if((STAT1 == 0) && (STAT2 == 1)){ return "CHRG"; }
    if((STAT1 == 1) && (STAT2 == 0)){ return "FULL"; }
    else return "ERR ";
}

 void tchGetEvents(void)
{
    INT16           x, y;
    INT16           validtouch = TRUE;

    // Get touch values
    x = tchGetX();
    y = tchGetY();
    if((x == -1) || (y == -1))validtouch = FALSE;

    switch(state){
        case INVALID:
            if(validtouch == TRUE) {
                state = PRESS;
                enqueueEvent(TOUCH, PRESS, x, y);
                repeatcount = 0;
            }
            break;

        case PRESS:
            if(validtouch == TRUE){
                if(++repeatcount >= DELAY_PERIOD || mode == RECORD){
                    repeatcount = 0;
                    enqueueEvent(TOUCH, STILL_PRESS, x, y);
                    state = STILL_PRESS;
                }
            }
            else {
                state = RELEASE;
                enqueueEvent(TOUCH_RELEASE, 0, 0, 0);
            }
            break;
        case STILL_PRESS:
            if(validtouch == TRUE){
                if(++repeatcount >= REPEAT_PERIOD){
                    repeatcount = 0;
                    enqueueEvent(TOUCH, STILL_PRESS, x, y);
                }
            }
            else {
                state = RELEASE;
                enqueueEvent(TOUCH_RELEASE, 0, 0, 0);
            }
            break;
        case RELEASE:
            state = INVALID;
            break;
    }

    oldX = x;
    oldY = y;
}

INT16 tchGetX(void)
{
    LONG    x;

    x = RawX;
    if(x >= 0) {
        x = (x * caldata.xscale + caldata.xoffset) >> TOUCH_SCALE_FACTOR;
	}
    return ((INT16)x);
}

INT16 tchGetY(void)
{
    LONG y;

    y = RawY;
    if(y >= 0) {
        y = (y * caldata.yscale + caldata.yoffset) >> TOUCH_SCALE_FACTOR;
	}
    return ((INT16)y);
}