/** 82C54-kapi.c
 *
 *  Author: P. Ferkous 
 *  Creation: 27.06.2019
 *  $LastChangedDate: 2017-04-28 15:16:10 +0100 (Fri, 28 Apr 2017) $
 *  $LastChangedRevision: 1495 $
 *
 *  Copyright (C) 2014 ADDI-DATA GmbH
 *
 *  ADDI-DATA GmbH
 *  Airpark Business Center
 *  Airport Boulevard B210
 *  77836 Rheinmünster
 *  Germany
 *  Tel: +49(0)7229/1847-0    |  Fax: +49(0)7229/1847-200
 *  http://www.addi-data-com  |  info@addi-data.com
 **/

/***********************************************/

#include "apci1710-private.h"

/***********************************************/

#ifndef DISABLE_TIMER_TESTS
    #define ASSERT_TIMER_PDEV(returnValue, pdev) \
        if (unlikely(pdev == NULL)) \
            return returnValue;
    #define ASSERT_TIMER_PARAM(returnValue, param, max) \
        if (unlikely(param > max)) \
            return returnValue;
    #define ASSERT_TIMER_CONFIG(returnValue, pdev, moduleNbr) \
        if (unlikely(APCI1710_MODULE_FUNCTIONALITY(pdev,moduleNbr) != APCI1710_82X54_TIMER)) \
            return returnValue;
    #define ASSERT_TIMER_INITIALIZED(returnValue, pdev, moduleNbr, timerNbr) \
        if (unlikely(APCI1710_PRIVDATA (pdev)-> s_ModuleInfo[(int) moduleNbr]. s_82X54ModuleInfo. s_82X54TimerInfo[timerNbr].b_82X54Init == 0))\
            return returnValue;
#else
    #define ASSERT_TIMER_PDEV(returnValue, pdev)
    #define ASSERT_TIMER_PARAM(returnValue, param, max)
    #define ASSERT_TIMER_CONFIG(returnValue, pdev, moduleNbr)
    #define ASSERT_TIMER_INITIALIZED(returnValue, pdev, moduleNbr, timerNbr)
#endif
/***********************************************/

EXPORT_SYMBOL(i_APCI1710_ReadInterruptMask);
int i_APCI1710_ReadInterruptMask(struct pci_dev *pdev, uint8_t moduleNbr,uint32_t *interruptMask)
{
    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)

    *interruptMask = inl(pdev->resource[2].start + (moduleNbr << 6) + 12) & 0x7;

    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_InitTimer);
int i_APCI1710_InitTimer(struct pci_dev *pdev,
                         uint8_t  moduleNbr,
                         uint8_t  timerNbr,
                         uint8_t  timerMode,
                         uint8_t  inputClk,
                         uint8_t  inputClkLevel,
                         uint8_t  outputLevel,
                         uint8_t  hardGate,
                         uint32_t reloadValue)
{
    uint32_t *configRegister;

    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_PARAM(5, timerMode, 5)
    ASSERT_TIMER_PARAM(6, inputClk, 2)
    ASSERT_TIMER_PARAM(7, inputClkLevel, 1)
    ASSERT_TIMER_PARAM(8, outputLevel, 1)
    ASSERT_TIMER_PARAM(9, hardGate, 1)

    /* Write the mode */
    outl(timerMode, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 16);

    /* Write the configuration */
    configRegister = &APCI1710_PRIVDATA (pdev)-> s_ModuleInfo[(int) moduleNbr].s_82X54ModuleInfo.s_82X54TimerInfo[timerNbr].dw_ConfigurationWord;
    *configRegister = (inputClk << 4) | (outputLevel << 2) | (inputClkLevel << 1) | hardGate;
    outl(*configRegister, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 32);

    /* Write the reload value */
    outl(reloadValue, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2));
    APCI1710_PRIVDATA (pdev)-> s_ModuleInfo[(int) moduleNbr].s_82X54ModuleInfo.s_82X54TimerInfo[timerNbr].b_82X54Init=1;
    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_EnableTimer);
int i_APCI1710_EnableTimer(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr, uint8_t interruptFlag)
{
    uint32_t *configRegister;

    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_INITIALIZED(4, pdev, moduleNbr, timerNbr)
    ASSERT_TIMER_PARAM(5, interruptFlag, 1)

    /* Set the interrupt bit */
    configRegister = &APCI1710_PRIVDATA (pdev)-> s_ModuleInfo[(int) moduleNbr]. s_82X54ModuleInfo. s_82X54TimerInfo[timerNbr].dw_ConfigurationWord;

    *configRegister &= ~(1 << 3);
    *configRegister |= (interruptFlag << 3);

    /* Write the new configuration */
    outl(*configRegister, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 32);

    /* Enable the software gate */
    outl(1, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 44);

    return 0;
}

EXPORT_SYMBOL(i_APCI1710_DisableTimer);
int i_APCI1710_DisableTimer(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr)
{
    uint32_t *configRegister;

    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, timerNbr)

    /* Unset the interrupt bit */
    configRegister = &APCI1710_PRIVDATA (pdev)-> s_ModuleInfo[(int) moduleNbr]. s_82X54ModuleInfo. s_82X54TimerInfo[timerNbr].dw_ConfigurationWord;

    *configRegister &= ~(1 << 3);

    /* Write the new configuration */
    outl(*configRegister, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 32);

    /* Disable the software gate */
    outl(0, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 44);

    APCI1710_PRIVDATA (pdev)-> s_ModuleInfo[(int) moduleNbr].s_82X54ModuleInfo.s_82X54TimerInfo[timerNbr].b_82X54Init=1;

    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_ReadTimerValue);
int i_APCI1710_ReadTimerValue(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr, uint32_t *value)
{
    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, timerNbr)

    /* Latch the selected timer */
    outl((2 << timerNbr) | 0xC0, pdev->resource[2].start + (moduleNbr << 6) + 12);

    /* Read the latched value */
    *value = inl(pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2));

    return 0;
}

EXPORT_SYMBOL(i_APCI1710_ReadAllTimerValues);
int i_APCI1710_ReadAllTimerValues(struct pci_dev *pdev, uint8_t moduleNbr, uint32_t values[3])
{
    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_INITIALIZED(4, pdev, moduleNbr, 0)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, 1)
    ASSERT_TIMER_INITIALIZED(6, pdev, moduleNbr, 2)

    /* Latch all timers */
    outl(0xCE, pdev->resource[2].start + (moduleNbr << 6) + 12);

    /* Read the latched values */
    values[0] = inl(pdev->resource[2].start + (moduleNbr << 6));
    values[1] = inl(pdev->resource[2].start + (moduleNbr << 6) + 4);
    values[2] = inl(pdev->resource[2].start + (moduleNbr << 6) + 8);

    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_GetTimerOutputLevel);
int i_APCI1710_GetTimerOutputLevel(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr, uint8_t *outputLevel)
{
    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, timerNbr)

    /* Latch the selected timer */
    outl((2 << timerNbr) | 0xC0, pdev->resource[2].start + (moduleNbr << 6) + 12);

    /* Read the output level */
    *outputLevel = (uint8_t)((inl(pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2) + 16) >> 7) & 1);

    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_WriteTimerValue);
int i_APCI1710_WriteTimerValue(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr, uint32_t value)
{
    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, timerNbr)

    outl(value, pdev->resource[2].start + (moduleNbr << 6) + (timerNbr << 2));

    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_GetTimerProgressStatus);
int i_APCI1710_GetTimerProgressStatus(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr, uint8_t *status)
{
    uint32_t config,offset,reg_value;

    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, timerNbr)

    /*Latch -> DQ6 and DQ7 are to be set to 1
    -> DQ(TimerNbr+1) is to be set to 1 */
    config = (3 << 6) | (1 << (timerNbr + 1));
    offset = (uint32_t)((moduleNbr << 6) + 12);

    /*Latch the timer value*/
    outl(config, pdev->resource[2].start + offset);

    /*Read the progress*/
    offset = (uint32_t)((moduleNbr << 6) + (timerNbr << 2) + 16);
    reg_value = (uint32_t)(inl(pdev->resource[2].start + offset));

    /*Apply mask, shift and return optput value*/
    *status = (uint8_t)((reg_value >> 6) & 0x1);
    return 0;
}

/***********************************************/

EXPORT_SYMBOL(i_APCI1710_GetTimerProgressStatusEx);
int i_APCI1710_GetTimerProgressStatusEx(struct pci_dev *pdev, uint8_t moduleNbr, uint8_t timerNbr, uint8_t *status)
{
    uint32_t config,offset,reg_value;

    ASSERT_TIMER_PDEV(1, pdev)
    ASSERT_TIMER_PARAM(2, moduleNbr, 3)
    ASSERT_TIMER_CONFIG(3, pdev, moduleNbr)
    ASSERT_TIMER_PARAM(4, timerNbr, 2)
    ASSERT_TIMER_INITIALIZED(5, pdev, moduleNbr, timerNbr)

    /*Latch -> DQ6 and DQ7 are to be set to 1
    -> DQ(TimerNbr+1) is to be set to 1 */
    config = (7 << 5) | (1 << (timerNbr + 1));
    offset = (uint32_t)((moduleNbr << 6) + 12);

    /*Latch the timer value*/
    outl(config, pdev->resource[2].start + offset);

    /*Read the progress*/
    offset = (uint32_t)((moduleNbr << 6) + (timerNbr << 2) + 16);
    reg_value = (uint32_t)(inl(pdev->resource[2].start + offset));

    /*Apply mask, shift and return optput value*/
    *status = (uint8_t)((reg_value >> 8) & 0x1);
    return 0;
}

/***********************************************/
