/* ---------------------------------------------------------------------------
 * dio.c - v0.1                                         (c) 2007 Micro-key bv
 * ---------------------------------------------------------------------------
 * Micro-key bv
 * Industrieweg 28, 9804 TG Noordhorn
 * Postbus 92, 9800 AB Zuidhorn
 * The Netherlands
 * Tel: +31 594 503020
 * Fax: +31 594 505825
 * Email: support@microkey.nl  
 * Web: www.microkey.nl
 * ---------------------------------------------------------------------------
 * Description: Digital inputs/outputs interface.
 * ---------------------------------------------------------------------------
 * Version(s):  0.1, 28-11-2007, fvds.
 *                   Creation.
 * ---------------------------------------------------------------------------
 */

/* ---------------------------------------------------------------------------
 * System include files
 * ---------------------------------------------------------------------------
 */
/* Compiler includes */
#include <stdio.h>
#include <stdlib.h>

/* Hardware Includes */
#include "LPC23xx.h"
#include "types.h"

/* FreeRTOS includes */
#include "FreeRTOS.h"
#include "Task.h"
#include "queue.h"
/* ---------------------------------------------------------------------------
 * Application include files
 * ---------------------------------------------------------------------------
 */
#include "dio.h"
#include "dioISR.h"
#include "armVIC.h"
#include "ElecStatusCache.h"

/* ---------------------------------------------------------------------------
 * Local constant and macro definitions
 * ---------------------------------------------------------------------------
 */
#define PORT0_BASE_ADDR     FIO_BASE_ADDR
#define PORT1_BASE_ADDR     (FIO_BASE_ADDR + 0x20)
#define PORT2_BASE_ADDR     (FIO_BASE_ADDR + 0x40)
#define PORT3_BASE_ADDR     (FIO_BASE_ADDR + 0x60)
#define PORT4_BASE_ADDR     (FIO_BASE_ADDR + 0x80)

#define DIR_OFFSET          (0x00)
#define MASK_OFFSET         (0x10)
#define PIN_OFFSET          (0x14)
#define SET_OFFSET          (0x18)
#define CLR_OFFSET          (0x1C)

// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
#define DIO_MUX_DIR         FIO3DIR
#define DIO_MUX_SET         FIO3SET         
#define DIO_MUX_CLR         FIO3CLR

#define DIN_MUX_BIT         BIT(25)
#define DIN_MUX_SET         (DIO_MUX_SET = DIN_MUX_BIT)
#define DIN_MUX_CLR         (DIO_MUX_CLR = DIN_MUX_BIT)


#define DOUT_MUX_BIT        BIT(26)
#define DOUT_MUX_SET        (DIO_MUX_SET = DOUT_MUX_BIT)
#define DOUT_MUX_CLR        (DIO_MUX_CLR = DOUT_MUX_BIT)
#endif

#define DISPATCH_TASK_PRIO  (tskIDLE_PRIORITY + 5)
#define DISPATCH_QUEUE_SIZE 10

#define Int_queue_time      3         /* In Miliseconds                   */

/* ---------------------------------------------------------------------------
 * Global variable definitions
 * ---------------------------------------------------------------------------
 */


/* ---------------------------------------------------------------------------
 * Local variable definitions
 * ---------------------------------------------------------------------------
 */
portTickType dechatterStopTime[maxDI_Channels]; /* Contains tickcount Value, where*/
                                          /* dechattering timeout occours   */

UINT8 dechatterLockFlag[maxDI_Channels] = /* Channel Lock Flag              */
    {
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
    };

UINT32 dechatterTime[maxDI_Channels] =  /* TimeDelay until dechatter timeout*/
    {
    10,
    10,
    10,
    10,
    10,
    10,
    10,
    10,
    10,
    10,
    10
    };

// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
t_output outputPins[maxDO_Channels] =
    {
        { PORT4_BASE_ADDR, BIT(8) }, // DOUT0 
        { PORT4_BASE_ADDR, BIT(9) }, // DOUT1 
        { PORT4_BASE_ADDR, BIT(10) }, // DOUT2 
        { PORT4_BASE_ADDR, BIT(11) }, // DOUT3 
        { PORT4_BASE_ADDR, BIT(12) }, // DOUT4 
        { PORT4_BASE_ADDR, BIT(13) }, // DOUT5 
        { PORT4_BASE_ADDR, BIT(14) }, // DOUT6 
        { PORT4_BASE_ADDR, BIT(15) } // DOUT7 
    };
#else
t_output outputPins[maxDO_Channels] =
    {
        { PORT1_BASE_ADDR, BIT(0) }, // DOUT0 
        { PORT1_BASE_ADDR, BIT(1) }, // DOUT1 
        { PORT1_BASE_ADDR, BIT(4) }, // DOUT2 
        { PORT1_BASE_ADDR, BIT(8) }, // DOUT3 
        { PORT1_BASE_ADDR, BIT(9) }, // DOUT4 
        { PORT1_BASE_ADDR, BIT(10) }, // DOUT5 
        { PORT1_BASE_ADDR, BIT(14) }, // DOUT6 
        { PORT1_BASE_ADDR, BIT(15) } // DOUT7 
    };
#endif

t_input inputPins[maxDI_Channels] =
    {
        { PORT0_BASE_ADDR, BIT(15), NULL }, // DIN0 
        { PORT0_BASE_ADDR, BIT(16), NULL }, // DIN1 
        { PORT0_BASE_ADDR, BIT(17), NULL }, // DIN2 
        { PORT0_BASE_ADDR, BIT(18), NULL }, // DIN3 
        { PORT0_BASE_ADDR, BIT(24), NULL }, // DIN4 
        { PORT0_BASE_ADDR, BIT(25), NULL }, // DIN5 
        { PORT0_BASE_ADDR, BIT(26), NULL }, // DIN6 
        { PORT0_BASE_ADDR, BIT(27), NULL }, // DIN7 
        { PORT0_BASE_ADDR, BIT(28), NULL }, // DIN8 
        { PORT0_BASE_ADDR, BIT(29), NULL }, // DIN9 
        { PORT0_BASE_ADDR, BIT(30), NULL } // DIN10 
    };

BOOLEAN dioActive = FALSE;
xQueueHandle dispatchedIsrQueue= 0;

/* ---------------------------------------------------------------------------
 * Local function definitions
 * ---------------------------------------------------------------------------
 */
static void setMaskDirRegisters(void);
static void enableChannelInterrupt(UINT8 channel);
static void disableChannelInterrupt(UINT8 channel);
static void isrDispatchTask(void *pvParameters);
static BOOLEAN hasTimeoutPast( UINT32 endTick );
static void startDechatterInput (UINT8 ChannelNumber, t_di_mode Edge);
static void endDechatterInput (void);
static void callAllCallbacks(t_callbackListItem *pCallbackList, t_di_mode detectedEdge);



void dioInit(void)
{
    SCS |= (1UL<<0);                        /* GPIOM in SCS to fast IO  */   

    setMaskDirRegisters();                  /* Init channel Registers   */
    dioActive= TRUE;                        /* Set global dioAcitve     */

    /* Create queue and Task for ISR-displatching                       */
    dispatchedIsrQueue = xQueueCreate( DISPATCH_QUEUE_SIZE, sizeof(t_IsrDispatchQueueItem));
    xTaskCreate(isrDispatchTask, ( signed portCHAR * ) "dioIsrDispatcher", 
    configMINIMAL_STACK_SIZE, NULL, DISPATCH_TASK_PRIO, NULL);

    /* Enable GPIO interrupt                                            */
    portENTER_CRITICAL();
        {
        VICIntSelect &= ~(VIC_CHAN_TO_MASK(VIC_CHAN_NUM_EINT3));
        VICIntEnClr = VIC_CHAN_TO_MASK(VIC_CHAN_NUM_EINT3);
        VICVectAddr17 = (void *)gpioISR;
        VICVectCntl7 = 0x01;
        VICIntEnable = VIC_CHAN_TO_MASK(VIC_CHAN_NUM_EINT3);
        }
    portEXIT_CRITICAL();
    
// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
    /* Init both MUX lines as Outputs                                       */
    DIO_MUX_DIR |= (DIN_MUX_BIT | DOUT_MUX_BIT);
#endif
    
    
}


void dio_inMuxEn (BOOLEAN mode)
{
// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
    if (mode == TRUE)
        {
        DIN_MUX_SET;
        }
    else
        {
        DIN_MUX_CLR;
        }
#endif
}

void dio_outMuxEn (BOOLEAN mode)
{
// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
    if (mode == TRUE)
        {
        DOUT_MUX_SET;
        }
    else
        {
        DOUT_MUX_CLR;
        }
#endif
}

BOOLEAN dio_inMuxRB (void)
{
// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
    if (DIO_MUX_SET & DIN_MUX_BIT)
        {
        /* MUX of digital input is switched to Extension Board              */
        return (TRUE);
        }
    else
        {
        /* MUX of digital input is switched to Main Board                   */
        return (FALSE);
        }
#endif
}

BOOLEAN dio_outMuxRB (void)
{
// \MARK NEW PINSETTINGS FOR TESTER (2)
#if (PINSET_TESTER == 2)
    if (DIO_MUX_SET & DOUT_MUX_BIT)
        {
        /* MUX of digital input is switched to Extension Board              */
        return (TRUE);
        }
    else
        {
        /* MUX of digital input is switched to Main Board                   */
        return (FALSE);
        }
#endif
}



RESULT dioRegisterCallback (t_dio_callbackfunc pFunc, UINT8 channel, t_di_mode mode)
{
    RESULT result = OK;

    disableChannelInterrupt(channel);

    // Create item
    t_callbackListItem *newCallbackListItem =
            (t_callbackListItem *) pvPortMalloc(sizeof(t_callbackListItem));
    if (newCallbackListItem == NULL)
        {
        result = ERROR;
        } else
        {
        if (inputPins[channel].pCallbackList == NULL)
            {
            enableChannelInterrupt(channel);
            }

        // Fill list item
        newCallbackListItem->pCallback = pFunc;
        newCallbackListItem->mode = mode;

        // Add to front of list
        newCallbackListItem->next = inputPins[channel].pCallbackList;
        inputPins[channel].pCallbackList = newCallbackListItem;
        }

    enableChannelInterrupt(channel);

    return result;
}

/** \brief Remove register callback function for digital input
 * \retval OK Callback was removed succesfully
 * \retval ERROR Failed to remove Callback-function from channel 
 */
RESULT dioRemoveCallback(t_dio_callbackfunc pFunc, UINT8 channel)
{
    RESULT result = OK;

    disableChannelInterrupt(channel);

    // Check if channel has callback list
    if (inputPins[channel].pCallbackList == NULL)
        {
        result = ERROR;
        } else
        {
        BOOLEAN callbackFound = FALSE;
        t_callbackListItem * previousItem= NULL;
        t_callbackListItem * currentItem = inputPins[channel].pCallbackList;

        // Find callbackfunc
        while ((!callbackFound) && (currentItem != NULL))
            {
            callbackFound = (currentItem->pCallback == pFunc);

            if (!callbackFound)
                {
                previousItem = currentItem;
                currentItem = currentItem->next;
                }
            }

        if (callbackFound)
            {
            if (previousItem == NULL)
                {
                // Remove first item from list
                inputPins[channel].pCallbackList = currentItem->next;
                } else
                {
                // Remove list item
                previousItem->next = currentItem->next;
                }
            vPortFree(currentItem); // Release resources of listitem
            } else
            {
            result = FALSE;
            }
        }

    if (inputPins[channel].pCallbackList != NULL)
        {
        enableChannelInterrupt(channel);
        }

    return result;
}

/** \brief Read digital input.*/
BOOLEAN dioRead(UINT8 device, /**< 0 = Self, 1..32 = Remote device */
        UINT8 channel /**< 0..10 = valid, 11..255 = future use */
)
    {
    BOOLEAN Result;
    volatile UINT32 *gpioRegister;

    if (device == 0)
        {
        gpioRegister = (UINT32 *)(inputPins[channel].portBaseAddr + PIN_OFFSET);

        // device is now ignored, future implementation

        // Get result
        if (*gpioRegister & inputPins[channel].pinMask)
            {
            Result = FALSE;
            } else
            {
            Result = TRUE;
            }
        }
    else
        {
        Result = bpecDioRead( device, channel );
        }

    return Result;
    }

/** \brief Readback digital outputs.*/
BOOLEAN dioReadBack(UINT8 device, /**< 0 = Self, 1..32 = Remote device */
        UINT8 channel /**< 0..7 = valid, 8..255 = future use */
)
    {
    BOOLEAN Result;
    volatile UINT32 *gpioRegister;

    if (device == 0)
        {
        gpioRegister = (UINT32 *)(outputPins[channel].portBaseAddr + PIN_OFFSET);

        // device is now ignored, future implementation

        // Get result 
        // NOTE: Input signal is inverted
        if (*gpioRegister & outputPins[channel].pinMask)
            {
            Result = TRUE;
            } else
            {
            Result = FALSE;
            }
        }
    else
        {
        Result = bpecDioReadBack( device, channel );
        }

    return Result;
    }

/** \brief Write digital outputs.*/
void dioWrite(UINT8 device, /**< 0 = Self, 1..32 = Remote device */
UINT8 channel, /**< 0..7 = valid, 8..255 = future use */
BOOLEAN data)
{
    volatile UINT32 *gpioRegister;

    if (device == 0)
        {
        if (data == TRUE)
            {
            gpioRegister = (UINT32 *)(outputPins[channel].portBaseAddr
                    + SET_OFFSET);
            *gpioRegister = outputPins[channel].pinMask;
            } 
        else
            {
            gpioRegister = (UINT32 *)(outputPins[channel].portBaseAddr
                    + CLR_OFFSET);
            *gpioRegister = outputPins[channel].pinMask;
            }
        } else
        {
        bpecWriteDioValue(device, channel, data);
        }
}

void setMaskDirRegisters()
{
    int i;
    volatile UINT32 *gpioRegister;

    for (i=0; i< maxDO_Channels; i++)
        {
        // Clear bit in mask Register
        gpioRegister = (UINT32 *)(outputPins[i].portBaseAddr + MASK_OFFSET);
        *gpioRegister &= ~(outputPins[i].pinMask);

        // Set direction bit to output
        gpioRegister = (UINT32 *)(outputPins[i].portBaseAddr + DIR_OFFSET);
        *gpioRegister |= outputPins[i].pinMask;
        }

    for (i=0; i< maxDI_Channels; i++)
        {
        // Clear bit in maskRegister
        gpioRegister = (UINT32 *)(inputPins[i].portBaseAddr + MASK_OFFSET);
        *gpioRegister &= ~(inputPins[i].pinMask);

        // Set direction bit to input
        gpioRegister = (UINT32 *)(inputPins[i].portBaseAddr + DIR_OFFSET);
        *gpioRegister &= ~(inputPins[i].pinMask);

        // Reset callback functions
        inputPins[i].pCallbackList = NULL;
        }
}

void enableChannelInterrupt(UINT8 channel)
{
    if (inputPins[channel].portBaseAddr == PORT0_BASE_ADDR)
        {
        IO0_INT_CLR = inputPins[channel].pinMask;
        IO0_INT_EN_R |= inputPins[channel].pinMask;
        IO0_INT_EN_F |= inputPins[channel].pinMask;
        } 
    else
        {
        IO2_INT_CLR = inputPins[channel].pinMask;
        IO2_INT_EN_R |= inputPins[channel].pinMask;
        IO2_INT_EN_F |= inputPins[channel].pinMask;
        }
}

void disableChannelInterrupt(UINT8 channel)
{
    if (inputPins[channel].portBaseAddr == PORT0_BASE_ADDR)
        {
        IO0_INT_EN_R &= ~BIT(inputPins[channel].pinMask);
        IO0_INT_EN_F &= ~BIT(inputPins[channel].pinMask);
        } 
    else
        {
        IO2_INT_EN_R &= ~BIT(inputPins[channel].pinMask);
        IO2_INT_EN_F &= ~BIT(inputPins[channel].pinMask);
        }
}

void isrDispatchTask(void *pvParameters)
{
    t_IsrDispatchQueueItem dispatchItem;
    UINT32 channelcnt;

    do                                  /* As long as active                */
        {
        /* If an Input Interrupt occures, enter if Statement                */
        if (xQueueReceive(dispatchedIsrQueue, &dispatchItem, Int_queue_time))
            {
            /* Check for each single Channel, which one is active           */
            for (channelcnt=0; channelcnt < maxDI_Channels; channelcnt++)
                {
                /* Go further if CallBack exists for corresponding Channel  */
                if (inputPins[channelcnt].pCallbackList != NULL)
                    {
                    /* Check if this bit is active                          */
                    int portIndex = (inputPins[channelcnt].portBaseAddr
                            == PORT0_BASE_ADDR ? 0 : 1);

                    /* Check if Interrupt occured from a RISING EDGE        */
                    if (dispatchItem.riseInterrupts[ portIndex ]
                            & inputPins[channelcnt].pinMask)
                        {               /* Rising Edge Interrupt detected   */
                        if (dechatterTime[channelcnt] == 0)
                            {           /* If dechatterTime is set to ZERO  */
                                        /* call Callback directly           */
                            callAllCallbacks(
                                    inputPins[channelcnt].pCallbackList,
                                    RISING_EDGE);
                            } 
                        else            /* If there is a dechattering Time  */
                            {           /* Start dechattering the Input     */
                            startDechatterInput(channelcnt, RISING_EDGE);
                            }
                        }

                    /* Check if Interrupt occured from a FALLING EDGE       */
                    if (dispatchItem.fallInterrupts[ portIndex ]
                            & inputPins[channelcnt].pinMask)
                        {               /* Falling Edge Interrupt detected  */
                        if (dechatterTime[channelcnt] == 0)
                            {           /* If dechatterTime is set to ZERO  */
                                        /* call Callback directly           */
                            callAllCallbacks(
                                    inputPins[channelcnt].pCallbackList,
                                    FALLING_EDGE);
                            }
                        else            /* if there is a dechattering Time  */
                            {           /* Start dechattering the Input     */
                            startDechatterInput(channelcnt, FALLING_EDGE);
                            }
                        }
                    }                   /* EndIf CallBack exists Check      */
                }                       /* End of FOR Loop                  */
            }                           /* End Check for occuring Interrupt */
        

        endDechatterInput();        /* Call dechatter monitor Function  */

        } while (dioActive == TRUE);
}


void startDechatterInput(UINT8 ChannelNumber, t_di_mode Edge)
{

    /* If corresponding Channel is currently not monitored                  */
    if (dechatterLockFlag[ChannelNumber] == 0)
        {
        /* Set dechatter Timer                                              */
        dechatterStopTime[ChannelNumber] = xTaskGetTickCount()
                + dechatterTime[ChannelNumber];
        /* Handle type of detected Edge in channel Lock Flag                */
        switch (Edge)
            {
            case RISING_EDGE:
                dechatterLockFlag[ChannelNumber] = 1;
                break;
            case FALLING_EDGE:
                dechatterLockFlag[ChannelNumber] = 2;
                break;
//            case BOTH_EDGES:
            default:
                break;
            }
        }

    /* If come here, an already monitored Channel causes a new Interrupt
     * (which means, within the Timer a new Edge occured)
     * This is a chatter!
     * Reload the Timer!
     */
    else
        {
        dechatterStopTime[ChannelNumber] = xTaskGetTickCount()
                + dechatterTime[ChannelNumber];
        }

}
void endDechatterInput(void)
{
    UINT32 channelcnt;

    /* Check every single Channel                                           */
    for (channelcnt = 0; channelcnt < maxDI_Channels; channelcnt++)
        {
        /* If Timeout for corresponding channel occured                     */
        if (hasTimeoutPast( dechatterStopTime[channelcnt]) )
            {
            /* Switch corresponding to the former detected Edge             */
            switch (dechatterLockFlag[channelcnt])
                {
                case 1:                 /* a Rising Edge was dechattered    */
                    /* Is Input still High? If Yes, channel is dechattered  */
                    if (dioRead(0, channelcnt) == TRUE)
                        {               /* Call Callback Functions          */
                        callAllCallbacks(inputPins[channelcnt].pCallbackList,
                                RISING_EDGE);
                        }
                    dechatterLockFlag[channelcnt] = 0; /* release Lock Flag */
                    break;
                
                case 2:                 /* a Falling Edge was dechattered   */
                    /* Is Input still Low? If Yes, channel is dechattered   */
                    if (dioRead(0, channelcnt) == FALSE)
                        {               /* Call Callback Functions          */
                        callAllCallbacks(inputPins[channelcnt].pCallbackList,
                                FALLING_EDGE);
                        }
                    dechatterLockFlag[channelcnt] = 0;
                    break;
                }
            }
        }
}

void callAllCallbacks(t_callbackListItem *pCallbackList, t_di_mode detectedEdge)
{
    while (pCallbackList != NULL)
        {
        if ( (pCallbackList->mode == detectedEdge) || (pCallbackList->mode
                == BOTH_EDGES))
            {
            // Call the callback
            pCallbackList->pCallback();
            }

        // Check next item
        pCallbackList = pCallbackList->next;
        }
}

void dioSetDechatterTime(UINT8 channel, UINT32 time)
{
    dechatterTime[channel] = time;
}


BOOLEAN hasTimeoutPast( UINT32 endTick )
{
    UINT32 nowTick = xTaskGetTickCount();
    
    if (nowTick >= endTick)
    {
        if ((nowTick - endTick) > 0x0000FFFF)
        {
            // the endTick has gone through 0 point, nowTick is at end of range 
            return FALSE;
        }
        else
        {
            // nowTick passed endTick.
            return TRUE;
        }
    }
    else
    {
        if ((endTick - nowTick) > 0x0000FFFF)
        {
            
            // the endTick was at end of range, nowTick has gone through 0 point 
            return TRUE;
        }
        else
        {
            // nowTick still has to pass endTick
            return FALSE;
        }
    }
}
