/* ----------------------------------------------------------------------------
 *         SAM Software Package License 
 * ----------------------------------------------------------------------------
 * Copyright (c) 2013, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */
 
/** \file */

/*----------------------------------------------------------------------------
 *         Headers
 *----------------------------------------------------------------------------*/

#include <board.h>

#include <string.h>

#ifdef REG_ADC_TSMR
/** \addtogroup tsd_module
 *@{
 */

/*----------------------------------------------------------------------------
 *         Local definitions
 *----------------------------------------------------------------------------*/

/** SWAP X & Y */
#define TS_XY_SWAP

/** Status that used for touchscreen */
#define TS_STATUSES ( ADC_ISR_PENS | ADC_ISR_PEN | ADC_ISR_NOPEN \
                    | ADC_ISR_XRDY | ADC_ISR_YRDY | ADC_ISR_PRDY )

/*----------------------------------------------------------------------------
 *         Local types
 *----------------------------------------------------------------------------*/

/** X value is ready */
#define TS_X_RDY        (1 << 0)
/** Y value is ready */
#define TS_Y_RDY        (1 << 1)
/** Pressure value is ready */
#define TS_P_RDY        (1 << 2)
/** Pen status */
#define TS_PEN_STAT     (1 << 7)
/** All data is ready (X,Y & P) */
#define TS_DATA_RDY     (TS_X_RDY|TS_Y_RDY|TS_P_RDY)

/*----------------------------------------------------------------------------
 *         Local variables
 *----------------------------------------------------------------------------*/

/** Raw register value */
static uint32_t dwRaw[3];
/** Touchscreen data sampling results */
static uint32_t dwTsData[3];
/** Last Touchscreen sampling results */
static uint32_t dwLastTsData[3];
/** Touchscreen data ready */
static uint8_t  bTsFlags = 0;

/*----------------------------------------------------------------------------
 *         External functions
 *----------------------------------------------------------------------------*/

extern uint32_t TSD_GetRaw(uint32_t i);

/**
 * Return raw register value.
 */
uint32_t TSD_GetRaw(uint32_t i)
{
    return dwRaw[i];
}

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

/**
 * Interrupt handler for the TouchScreen.
 * Handles pen press, pen move and pen release events
 * by invoking three callback functions.
 */
void TSD_Handler(uint32_t dwAdcStatus)
{
    Adc *pAdc = ADC;

    uint32_t status;

    /* TSADC status */
    status  = dwAdcStatus;
    status &= /*ADC_GetItMask(pAdc) &*/ TS_STATUSES;
    if (status == 0)    return;

    /* Pen released */
    if (status & ADC_ISR_NOPEN)
    {
        if ((bTsFlags & TS_PEN_STAT) == 0)
        {
            /* Register last data */
            memcpy(dwLastTsData, dwTsData, sizeof(dwTsData));
            /* Invoke PenReleased callback */
            if (TSDCom_IsCalibrationOk())
                TSD_PenReleased(dwTsData[0], dwTsData[1]);
        }
        bTsFlags = 0;
        /* Stop periodic trigger & enable pen */
        ADC_SetTsAverage(pAdc, ADC_TSMR_TSAV_NO_FILTER);
        ADC_SetTsDebounce(pAdc, BOARD_TOUCHSCREEN_DEBOUNCE);
        ADC_SetTriggerMode(pAdc, ADC_TRGR_TRGMOD_PEN_TRIG);
        /* Disable pen release detect */
        ADC_DisableIt(pAdc, ADC_IDR_NOPEN);
        /* Enable pen press detect */
        ADC_EnableIt(pAdc, ADC_IER_PEN);
    }
    /* Pen pressed */
    else if (status & ADC_ISR_PEN)
    {
        bTsFlags |= TS_PEN_STAT;
        /* Configure for peripdic trigger */
        ADC_SetTsAverage(pAdc, ADC_TSMR_TSAV_AVG8CONV);
        ADC_SetTsDebounce(pAdc, 300);         /* 300ns */
        ADC_SetTriggerMode(pAdc, ADC_TRGR_TRGMOD_PERIOD_TRIG);
        /* Disable pen press detect */
        ADC_DisableIt(pAdc, ADC_IDR_PEN);
        /* Enable pen release detect */
        ADC_EnableIt(pAdc, ADC_IER_NOPEN|ADC_IER_XRDY|ADC_IER_YRDY|ADC_IER_PRDY);
    }
    else if (status & ADC_ISR_PENS)
    {
        /* X */
        if (status & ADC_ISR_XRDY)
        {
            bTsFlags |= TS_X_RDY;
        }
        /* Y */
        if (status & ADC_ISR_YRDY)
        {
            bTsFlags |= TS_Y_RDY;
        }
        /* P: (X/1024)*[(Z2/Z1)-1] */
        if (status & ADC_ISR_PRDY)
        {
            bTsFlags |= TS_P_RDY;
        }
    }
    /* X,Y,P are ready */
    if ((bTsFlags & TS_DATA_RDY) == TS_DATA_RDY)
    {
        uint32_t xpos, z2, z1;
        bTsFlags &= ~TS_DATA_RDY;

        /* Get X,Y */
        TSD_GetRawMeasurement(dwRaw);

        /* Interprate X,Y */
        TSDCom_InterpolateMeasurement(dwRaw, dwTsData);

        /* Get P: Rp = Rxp*(Xpos/1024)*[(Z2/Z1)-1] */
        dwRaw[2] = ADC_GetTsPressure(pAdc);
        #ifdef TS_XY_SWAP
        xpos = (dwRaw[1]);
        #else
        xpos = (dwRaw[0]);
        #endif
        xpos = (xpos & ADC_XPOSR_XPOS_Msk) >> ADC_XPOSR_XPOS_Pos;
        z2 = (dwRaw[2] & ADC_PRESSR_Z2_Msk) >> ADC_PRESSR_Z2_Pos;
        z1 = (dwRaw[2] & ADC_PRESSR_Z1_Msk) >> ADC_PRESSR_Z1_Pos;
        dwTsData[2] = (xpos) * (z2 - z1) / z1;

        /* PenPress */
        if (bTsFlags & TS_PEN_STAT)
        {
            bTsFlags &= ~TS_PEN_STAT;
            /* Invoke PenPress callback */
            if (TSDCom_IsCalibrationOk())
                TSD_PenPressed(dwTsData[0], dwTsData[1], dwTsData[2]);
        }
        /* Periodic if data change invoke callback */
        if (dwTsData[0] != dwLastTsData[0]
            ||   dwTsData[1] != dwLastTsData[1]
            ||   dwTsData[2] != dwLastTsData[2] )
        {
            /* Register last data */
            memcpy(dwLastTsData, dwTsData, sizeof(dwTsData));
            /* Invoke PenMoved callback */
            if (TSDCom_IsCalibrationOk())
                TSD_PenMoved(dwTsData[0], dwTsData[1], dwTsData[2]);
        }
        
    }
}

/*----------------------------------------------------------------------------
 *         Global functions
 *----------------------------------------------------------------------------*/

/**
 * Reads and store a touchscreen measurement in the provided array.
 * The value stored are:
 *  - data[0] = XPOS * 1024 / XSCALE
 *  - data[1] = YPOS * 1024 / YSCALE
 * \param pData  Array where the measurements will be stored
 */
void TSD_GetRawMeasurement(uint32_t *pData)
{
    Adc *pAdc = ADC;
    uint32_t xr, yr;
    #ifdef TS_XY_SWAP
    yr = ADC_GetTsXPosition(pAdc);
    xr = ADC_GetTsYPosition(pAdc);
    #else
    xr = ADC_GetTsXPosition(pAdc);
    yr = ADC_GetTsYPosition(pAdc);
    #endif
    pData[0]  = ((xr & ADC_XPOSR_XPOS_Msk) >> ADC_XPOSR_XPOS_Pos) * 1024 * 4;
    pData[0] /= ((xr & ADC_XPOSR_XSCALE_Msk) >> ADC_XPOSR_XSCALE_Pos);
    pData[1]  = ((yr & ADC_YPOSR_YPOS_Msk) >> ADC_YPOSR_YPOS_Pos) * 1024 * 4;
    pData[1] /= ((yr & ADC_YPOSR_YSCALE_Msk) >> ADC_YPOSR_YSCALE_Pos);
}

/**
 * Wait pen pressed
 */
void TSD_WaitPenPressed(void)
{
    Adc *pAdc = ADC;
    uint8_t bFlags = 0;
    uint32_t dwStatus;
    /* Wait for touch & end of conversion */
    while (1)
    {
        dwStatus = ADC_GetStatus(pAdc);
        if (dwStatus & ADC_ISR_PEN) bFlags |= 8;
        if (dwStatus & ADC_ISR_XRDY)bFlags |= 1;
        if (dwStatus & ADC_ISR_YRDY)bFlags |= 2;
        if (dwStatus & ADC_ISR_PRDY)bFlags |= 4;
        if (bFlags == 0xF) break;
    }
}

/**
 * Wait pen released
 */
void TSD_WaitPenReleased(void)
{
    Adc *pAdc = ADC;

    /* Wait for contact loss */
    while((ADC_GetStatus(pAdc) & ADC_ISR_NOPEN) == 0);
}

/**
 * Initializes the touchscreen driver and starts the calibration process. When
 * finished, the touchscreen is operational.
 * The configuration is taken from the board.h of the device being compiled.
 * Important: the LCD driver must have been initialized prior to calling this
 * function.
 */
void TSD_Initialize(void)
{
    Adc *pAdc = ADC;

    bTsFlags = 0;

    /* Configuration */
    PMC_EnablePeripheral(ID_ADC);

    ADC_SetClock(pAdc, BOARD_TOUCHSCREEN_ADCCLK, BOARD_MCK);
    ADC_SetStartupTime(pAdc, BOARD_TOUCHSCREEN_STARTUP);
    ADC_SetTrackingTime(pAdc, BOARD_TOUCHSCREEN_SHTIM);

    ADC_SetTriggerPeriod(pAdc, 20000000); /*  20ms */

    ADC_SetTsMode(pAdc, ADC_TSMR_TSMODE_4_WIRE);
    ADC_SetTsAverage(pAdc, ADC_TSMR_TSAV_NO_FILTER);

    ADC_SetTsPenDetect(pAdc, 1);
    ADC_SetTsDebounce(pAdc, BOARD_TOUCHSCREEN_DEBOUNCE);

    pAdc->ADC_MR &= ~(3<<28);
    pAdc->ADC_ACR = 0x102;    
}

/**
 * Enable/Disable TSD capturing
 */
void TSD_Enable(uint8_t bEnDis)
{
    Adc *pAdc = ADC;
    if (bEnDis)
    {
        ADC_SetTsAverage(pAdc, ADC_TSMR_TSAV_NO_FILTER);
        ADC_TsCalibration(pAdc);
        ADC_SetTriggerMode(pAdc, ADC_TRGR_TRGMOD_PEN_TRIG);
        ADC_EnableIt(pAdc, ADC_IER_PEN);
    }
    else
    {
        ADC_SetTriggerMode(pAdc, ADC_TRGR_TRGMOD_NO_TRIGGER);
        ADC_DisableIt(pAdc, TS_STATUSES);
        ADC_GetStatus(pAdc);
        ADC_GetTsXPosition(pAdc);
        ADC_GetTsYPosition(pAdc);
        ADC_GetTsPressure(pAdc);
    }
}

/**
 * Do touchscreen calibration
 * \param pLcdBuffer  LCD buffer to use for displaying the calibration info.
 * \return 1 if calibration is Ok, 0 else
 */
uint8_t TSD_Calibrate(void)
{
    Adc *pAdc = ADC;
    uint8_t ret = 0;

    /* Calibration is done only once */
    if(TSDCom_IsCalibrationOk())    return 1;

    /* Disable touch */
    TSD_Enable(0);

    /* Enable capturing */
    ADC_SetTriggerMode(pAdc, ADC_TRGR_TRGMOD_PEN_TRIG);

    /* Do calibration */
    ret = TSDCom_Calibrate();

    /* Configure interrupt generation
       Do it only if the calibration is Ok. */
    //TSD_Enable(ret);

    return ret;
}

/**
 * Reset/stop the touchscreen
 */
void TSD_DeInitialize(void)
{
    Adc *pAdc = ADC;
    /* Disable TS related interrupts */
    ADC_DisableIt(pAdc, TS_STATUSES);
    /* Disable Trigger */
    ADC_SetTriggerMode(pAdc, ADC_TRGR_TRGMOD_NO_TRIGGER);
    /* Disable TS mode */
    ADC_SetTsMode(pAdc, ADC_TSMR_TSMODE_NONE);
    bTsFlags = 0;
}

/**@}*/
#endif /* #ifdef REG_ADC_TSMR */

