/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support
 * ----------------------------------------------------------------------------
 * Copyright (c) 2009, 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
 *
 * Implementation of ADS7843 driver.
 */

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

#include "board.h"

#ifdef BOARD_TSC_ADS7843

#include <stdint.h>
#include <string.h>

/*----------------------------------------------------------------------------
 *        Definitions
 *----------------------------------------------------------------------------*/

/** Delay for pushbutton debouncing (the time-base is 10 ms). */
#define DEBOUNCE_TIME       6               /* 10 * 6 = 60 ms */

/** Color of calibration points. */
#define POINTS_COLOR        0x0000FF

/** Size in pixels of calibration points. */
#define POINTS_SIZE         4

/** Maximum difference in pixels between the test point and the measured point. */
#define POINTS_MAX_ERROR    5

/*----------------------------------------------------------------------------
 *        Types
 *----------------------------------------------------------------------------*/

/** pen state */
typedef enum {
    STATE_PEN_RELEASED  = 0,
    STATE_PEN_PRESSED   = 1,
    STATE_PEN_DEBOUNCE  = 2
} e_pen_state;

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

/** Pins used by Interrupt Signal for Touch Screen Controller */
static const Pin pinPenIRQ  = PIN_TSC_IRQ;

/** Global timestamp in milliseconds since start of application. */
static volatile uint32_t timestamp = 0;

/** last time when the pen is pressed on the touchscreen */
static volatile uint32_t timePress = 0;

/** last time when the pen is released */
static volatile uint32_t timeRelease = 0;

/** pen state */
static volatile e_pen_state penState = STATE_PEN_RELEASED;

/** Touch screen initiallized flag */
static uint32_t tsInitFlag = 0;

/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Timer handler for touch screen. Increments the timestamp counter.
 * Determine the state "Pen Pressed" or "Pen Released". To change state,
 * the penIRQ has to keep the same value during DEBOUNCE_TIME.
 *
 * \note External timer interrupt should call it per 10ms.
 */
void TSD_TimerHandler( void )
{
    uint32_t data[2];
    uint32_t timeKeep;
    static uint32_t point[2];

    if (!tsInitFlag) return;

    timestamp++;
    /* Get the current position of the pen if penIRQ has low value (pen pressed) */
    if ( PIO_Get(&pinPenIRQ) == 0 )
    {
        /* Get the current position of the pressed pen */
        if ( TSDCom_IsCalibrationOk() )
        {
            TSD_GetRawMeasurement(data);
            TSDCom_InterpolateMeasurement(data, point);
        }

        /* call the callback function */
        if ( penState == STATE_PEN_PRESSED )
        {
            if(TSDCom_IsCalibrationOk())
            {
                TSD_PenMoved(point[0], point[1]);
            }
        }
    }

    /* Determine the pen state */
    if ( PIO_Get( &pinPenIRQ ) == 0 )
    {
        /* reinit the last time when release */
        timeRelease = timestamp;

        if ( penState == STATE_PEN_DEBOUNCE )
        {
            timeKeep = timestamp;
            timeKeep -= timePress;
            if(timeKeep > DEBOUNCE_TIME)
            {
                /* pen is pressed during an enough time : the state change */
                penState = STATE_PEN_PRESSED;
                /* call the callback function */
                if ( TSDCom_IsCalibrationOk() )
                {
                    TSD_PenPressed(point[0], point[1]);
                }
            }
        }
    }
    else
    {
        /* reinit the last time when release */
        timePress = timestamp;

        if ( penState == STATE_PEN_DEBOUNCE )
        {
            timeKeep = timestamp;
            timeKeep -= timeRelease;
            if ( timeKeep > DEBOUNCE_TIME )
            {
                /* pen is released during an enough time : the state change */
                penState = STATE_PEN_RELEASED;
                /* call the callback function */
                if ( TSDCom_IsCalibrationOk() )
                {
                    TSD_PenReleased(point[0], point[1]);
                }
            }
        }
    }
}

/**
 * \breif Interrupt handler for Touchscreen.
 */
static void ISR_PenIRQ( void )
{
    /* Check if the pen has been pressed */
    if ( !PIO_Get( &pinPenIRQ ) )
    {
        if ( penState == STATE_PEN_RELEASED )
        {
            timePress = timestamp;
            penState = STATE_PEN_DEBOUNCE;
        }
    }
    else
    {
        if ( penState == STATE_PEN_PRESSED )
        {
            timeRelease = timestamp;
            penState = STATE_PEN_DEBOUNCE;
        }
    }
}

/**
 * \brief Configure PENIRQ for interrupt.
 *
 * \note Be sure the PIO interrupt management has been initialized by
 * PIO_InitializeInterrupts() before call this function.
 */
static void ConfigurePenIRQ( void )
{
    /* Configure pios */
    PIO_Configure(&pinPenIRQ, PIO_LISTSIZE(pinPenIRQ));

    /* Initialize interrupts */
    PIO_ConfigureIt(&pinPenIRQ, (void (*)(const Pin *)) ISR_PenIRQ);

    /* Enable the interrupt */
    PIO_EnableIt(&pinPenIRQ);
}

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Reads and store a touchscreen measurement in the provided array.
 *
 * \param pData  Array where the measurements will be stored
 */
extern void TSD_GetRawMeasurement( uint32_t* pdwData )
{
    /* Get the current position of the pressed pen */
    PIO_DisableIt( &pinPenIRQ ) ;
    ADS7843_GetPosition( &pdwData[0], &pdwData[1] ) ;
    PIO_EnableIt( &pinPenIRQ ) ;
}

/**
 * \brief Wait pen pressed.
 */
extern void TSD_WaitPenPressed( void )
{
    /* Wait for touch & end of conversion */
    while ( penState != STATE_PEN_RELEASED ) ;

    /* while (penState != STATE_PEN_PRESSED); */
    while ( penState != STATE_PEN_PRESSED )
    {
    }
}

/**
 * \brief Wait pen released.
 */
extern void TSD_WaitPenReleased( void )
{
    /* Wait for contact loss */
    while (penState != STATE_PEN_PRESSED);
    while (penState != STATE_PEN_RELEASED);
}

/**
 * \brief Do calibration.
 *
 * \return 1 if calibration is Ok, 0 else.
 */
extern uint8_t TSD_Calibrate( void )
{
    uint8_t ret = 0 ;

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

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

    return ret;
}

/**
 * \brief Initializes the touchscreen driver and starts the calibration process. When
 * finished, the touchscreen is operational.
 *
 * \note Important: the LCD driver must have been initialized prior to calling this
 * function.
 */
extern void TSD_Initialize( int8_t calEn )
{
    ADS7843_Initialize();
    ConfigurePenIRQ();

    tsInitFlag = 1;

    /* Calibration */
    if(calEn) {
       while (!TSD_Calibrate());
    }
}

/**
 * \brief Stop the Touchscreen, disable interrupt.
 */
extern void TSD_Reset( void )
{
    /* Disable SPI 0 */
    ADS7843_Reset() ;

    /* Disable the interrupt */
    PIO_DisableIt( &pinPenIRQ ) ;
}

#endif /* #ifdef BOARD_TSC_ADS7843 */
