/** @file sample_chronos_02.c
*
* @author Krauth Julien
*
*  Initialise chronometer with the selected mode.
*  Read chronometer with interrupt.
*/

/** @par LICENCE
* @verbatim
    Copyright (C) 2009  ADDI-DATA GmbH for the source code of this module.

    ADDI-DATA GmbH
    Airpark Business Center
    Airport Boulevard B210
    77836 Rheinmuenster
    Germany
    Tel: +49(0)7229/1847-0
    Fax: +49(0)7229/1847-200
    http://www.addi-data-com
    info@addi-data.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You shoud find the complete GPL in the COPYING file accompanying
    this source code.
* @endverbatim
*/

#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
    #include <linux/config.h>
#else
    #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
        #include <linux/autoconf.h>
    #else
        #include <generated/autoconf.h>
    #endif
#endif

#include <linux/spinlock.h>
#include <linux/ioctl.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pci.h> // struct pci_dev
#include <asm/io.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,4,0)
    #include <asm/system.h>
#endif
#include <asm/uaccess.h>
#include <asm/bitops.h>
#include <linux/sched.h>

#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/proc_fs.h>

#include <apci1710.h>
#include <apci1710-kapi.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ADDI-DATA GmbH <info@addi-data.com>");
MODULE_DESCRIPTION("APCI-1710");

EXPORT_NO_SYMBOLS;

static const char modulename[] = "sample_chronos_02";


static void apci1710_interrupt (struct pci_dev * pdev)
{
    uint8_t b_ModuleMask = 0;
    uint32_t ul_InterruptMask = 0;
    uint32_t ul_CounterLatchValue = 0;

    i_APCI1710_TestInterrupt (pdev, &b_ModuleMask, &ul_InterruptMask, &ul_CounterLatchValue);

    printk ("Module = %d Interrupt mask = %d Chronometer value = %u\n",
            b_ModuleMask,
            ul_InterruptMask,
            ul_CounterLatchValue);
}


static int sample_chronos_kernel (struct pci_dev *pdev)
{

    int i_ReturnValue = 0;
    int i = 0;
    unsigned long irqstate;

    uint8_t b_ModuleNbr = 0;                                 // The module to use (0 to 3).
    uint8_t b_PCIInputClock = APCI1710_40MHZ;                // Clock

    /*
     * Timing unity:
     *    0 for ns
     *    1 for u
     *    2 for ms
     *    3 for s
     *    4 for mn
     */
    uint8_t b_TimingUnity = 1;

    /*
     * APCI1710_40MHZ
     *
     * +-----------+--------------------+--------------------+
     * | Time Unit | Min. Time Interval | Max. Time Interval |
     * +-----------+--------------------+--------------------+
     * | ns (0)    | 60                 | 429467295          |
     * +-----------+--------------------+--------------------+
     * | us (1)    |  1                 | 107374182          |
     * +-----------+--------------------+--------------------+
     * | ms (2)    |  1                 |    107374          |
     * +-----------+--------------------+--------------------+
     * | s  (3)    |  1                 |       107          |
     * +-----------+--------------------+--------------------+
     * | mn (4)    |  1                 |         2          |
     * +-----------+--------------------+--------------------+
     */
    uint32_t ul_TimingInterval = 1;

    /*
     * +------+---------------------------------------------+----------------------------------------+
     * | Mode | Signal C                                    | Signal D                               |
     * +------+---------------------------------------------+----------------------------------------+
     * |   0  | The high signal starts the time measure     |                                        |
     * |      | The low signal stops the time measure       |                                        |
     * +------+---------------------------------------------+----------------------------------------+
     * |   1  | The low signal starts the time measure      |                                        |
     * |      | The high signal stops the time measure      |                                        |
     * +------+---------------------------------------------+----------------------------------------+
     * |   2  | The high signal starts the time measure     |                                        |
     * |      | The next high signal stops the time measure |                                        |
     * +------+---------------------------------------------+----------------------------------------+
     * |   3  | The low signal starts the time measure      |                                        |
     * |      | The next low signal stops the time measure  |                                        |
     * +------+---------------------------------------------+----------------------------------------+
     * |   4  | The high signal starts the time measure     | The high signal stops the time measure |
     * +------+---------------------------------------------+----------------------------------------+
     * |   5  | The low signal starts the time measure      | The low signal stops the time measure  |
     * +------+---------------------------------------------+----------------------------------------+
     * |   6  | The high signal starts the time measure     | The low signal stops the time measure  |
     * +------+---------------------------------------------+----------------------------------------+
     * |   7  | The low signal starts the time measure      | The high signal stops the time measure |
     * +------+---------------------------------------------+----------------------------------------+
     */
    uint8_t b_ChronoMode = 0;

    /* APCI1710_SINGLE / APCI1710_CONTINUOUS */
    uint8_t b_CycleMode = APCI1710_CONTINUOUS;

    /* APCI1710_DISABLE / APCI1710_ENABLE */
    uint8_t b_Interrupt = APCI1710_ENABLE;

    /* Set the interrupt routine */
    i_ReturnValue = i_APCI1710_SetBoardIntRoutine (pdev, apci1710_interrupt);

    switch (i_ReturnValue)
    {
        case 1:
            printk ("\ni_APCI1710_SetBoardIntRoutine error");
            printk ("\nError = %d. The handle parameter of the board is wrong", i_ReturnValue);
            return 0;
        break;

        case 2:
            printk ("\ni_APCI1710_SetBoardIntRoutine error");
            printk ("\nError = %d. The interrupt callback is NULL", i_ReturnValue);
            return 0;
        break;
    }

    /* Lock the function to avoid parallel configurations */
    apci1710_lock(pdev,&irqstate);

    /* Initialise the chronometer */
    /* The following code in the comment show you the computation of the ul_TimerValue
     * The // Real part is not done beacause of kernel mode and use of float values.
     * You can use it to compute yourself the real value that the board will use
     * to measure the time.
     *
     * The code to convert the raw measured time in float value is available under ../samples/chronos_utils.c v_APCI1710_ConvertChronoValue

        switch (b_TimingUnit)
        {
            // ns
            case 0:
            ul_TimerValue = (ul_TimingInterval * (b_PCIInputClock/1000));

            // Real
            ul_RealTimingInterval = (unsigned long) (ul_TimerValue / (0.001 * (double) b_PCIInputClock));
            d_RealTimingInterval = (double) ul_TimerValue / (0.001 * (double) b_PCIInputClock);
            break;

            // us
            case 1:
            ul_TimerValue = (ul_TimingInterval * b_PCIInputClock);

            // Real
            ul_RealTimingInterval = (unsigned long) (ul_TimerValue / (1.0 * (double) b_PCIInputClock));
            d_RealTimingInterval = (double) ul_TimerValue / ((double) 1.0 * (double) b_PCIInputClock);
            break;

            //  ms
            case 2:
            ul_TimerValue = (ul_TimingInterval * 1000 * b_PCIInputClock);

            // Real
            ul_RealTimingInterval = (unsigned long) (ul_TimerValue / (1000.0 * (double) b_PCIInputClock));
            d_RealTimingInterval = (double) ul_TimerValue / (1000.0 * (double) b_PCIInputClock);
            break;

            //  s
            case 3:
            ul_TimerValue = (ul_TimingInterval * 1000000 * b_PCIInputClock);

            // Real
            ul_RealTimingInterval = (unsigned long) (ul_TimerValue / (1000000.0 * (double) b_PCIInputClock));
            d_RealTimingInterval = (double) ul_TimerValue / (1000000.0 * (double) b_PCIInputClock);
            break;

            //  mn
            case 4:
            ul_TimerValue = (ul_TimingInterval * 60 * 1000000 * b_PCIInputClock);

            // Real
            ul_RealTimingInterval = (unsigned long) (ul_TimerValue / (1000000.0 * (double) b_PCIInputClock)) / 60;
            d_RealTimingInterval = ((double) ul_TimerValue / (0.001 * (double) b_PCIInputClock)) / 60.0;
            break;
        }
     */
    i_ReturnValue = i_APCI1710_InitChrono (pdev,
                                           b_ModuleNbr,
                                           b_ChronoMode,
                                           b_PCIInputClock,
                                           b_TimingUnity,
                                           ul_TimingInterval);

    /* Unlock the function so that other applications can call it */
    apci1710_unlock(pdev,irqstate);

    switch (i_ReturnValue)
    {
        case 0:
        {
            printk ("\ni_APCI1710_InitChrono OK");

            /* Lock the function to avoid parallel configurations */
            apci1710_lock(pdev,&irqstate);

            i_ReturnValue = i_APCI1710_EnableChrono (pdev,
                                                     b_ModuleNbr,
                                                     b_CycleMode,
                                                     b_Interrupt);

            /* Unlock the function so that other applications can call it */
            apci1710_unlock(pdev,irqstate);

            switch (i_ReturnValue)
            {
                case 0:
                    printk ("\ni_APCI1710_EnableChrono OK");
                break;

                case 1:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. The handle parameter of the board is wrong", i_ReturnValue);
                    return 0;
                break;

                case 2:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. The selected module number parameter is wrong", i_ReturnValue);
                    return 0;
                break;

                case 3:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. The module is not a chronos module", i_ReturnValue);
                    return 0;
                break;

                case 4:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. Chronometer not initialised", i_ReturnValue);
                    return 0;
                break;

                case 5:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. Chronometer acquisition mode cycle is wrong", i_ReturnValue);
                    return 0;
                break;

                case 6:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. Interrupt parameter is wrong", i_ReturnValue);
                    return 0;
                break;

                case 7:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d. Interrupt function not initialised", i_ReturnValue);
                    return 0;
                break;

                default:
                    printk ("\ni_APCI1710_EnableChrono error");
                    printk ("\nError = %d.", i_ReturnValue);
                    return 0;
                break;
            }

            printk ("\n\n");

            do
            {
                /* Wait 1 Second
                 * If a signal is detected an reach
                 * all states from the selected mode
                 * an interrupt should occured.
                 */
                udelay(1);
            }
            while (i++ < 1000000);

            /* Lock the function to avoid parallel configurations */
            apci1710_lock(pdev,&irqstate);

            i_ReturnValue = i_APCI1710_DisableChrono (pdev, b_ModuleNbr);

            /* Unlock the function so that other applications can call it */
            apci1710_unlock(pdev,irqstate);

            switch (i_ReturnValue)
            {
                case 0:
                    printk ("\ni_APCI1710_DisableChrono OK");
                break;

                case 1:
                    printk ("\ni_APCI1710_DisableChrono error");
                    printk ("\nError = %d. The handle parameter of the board is wrong", i_ReturnValue);
                    return 0;
                break;

                case 2:
                    printk ("\ni_APCI1710_DisableChrono error");
                    printk ("\nError = %d. The selected module number parameter is wrong", i_ReturnValue);
                    return 0;
                break;

                case 3:
                    printk ("\ni_APCI1710_DisableChrono error");
                    printk ("\nError = %d. The module is not a chronos module", i_ReturnValue);
                    return 0;
                break;

                case 4:
                    printk ("\ni_APCI1710_DisableChrono error");
                    printk ("\nError = %d. Chronometer not initialised", i_ReturnValue);
                    return 0;
                break;

                default:
                    printk ("\ni_APCI1710_DisableChrono error");
                    printk ("\nError = %d.", i_ReturnValue);
                    return 0;
                break;
            }

            /* Release the interrupt function */
            i_ReturnValue = i_APCI1710_ResetBoardIntRoutine (pdev);

            switch (i_ReturnValue)
            {
                case 0:
                    printk ("\ni_APCI1710_ResetBoardIntRoutine OK");
                break;

                case 1:
                    printk ("\ni_APCI1710_ResetBoardIntRoutine error");
                    printk ("\nError = %d. The handle parameter of the board is wrong", i_ReturnValue);
                break;

                case 2:
                    printk ("\ni_APCI1710_ResetBoardIntRoutine error");
                    printk ("\nError = %d. Interrupt was not previously initialised", i_ReturnValue);
                break;

                default:
                    printk ("\ni_APCI1710_ResetBoardIntRoutine error");
                    printk ("\nError = %d.", i_ReturnValue);
                break;
            }

        }
        break;

        case 1:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. The handle parameter of the board is wrong", i_ReturnValue);
        break;

        case 2:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. The module is not a counter module", i_ReturnValue);
        break;

        case 3:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. The module is not a chronos module", i_ReturnValue);
        break;

        case 4:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. Chronometer mode selection is wrong", i_ReturnValue);
        break;

        case 5:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. The selected PCI input clock is wrong", i_ReturnValue);
        break;

        case 6:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. Timing unity selection is wrong", i_ReturnValue);
        break;

        case 7:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d. Base timing selection is wrong", i_ReturnValue);
        break;

        default:
            printk ("\ni_APCI1710_InitChrono error");
            printk ("\nError = %d", i_ReturnValue);
        break;
    }

    return 0;
}


/** Called when module loads. */
static int __init apci1710Sample_init(void)
{
    struct pci_dev *pdev;
    int board_index = 0;

    printk("%s: looking for board %u\n",modulename,board_index);
    pdev=apci1710_lookup_board_by_index(0);
    if( !pdev )
    {
        printk("%s: board %u not found\n",modulename,board_index);
        return -ENODEV;
    }
    printk("%s: beginning test\n",modulename);
    sample_chronos_kernel (pdev);

    return 0;
}
//-------------------------------------------------------------------
/** Called when module is unloaded. */
static void __exit apci1710Sample_exit(void)
{


}
//------------------------------------------------------------------------------
module_exit(apci1710Sample_exit);
module_init(apci1710Sample_init);
