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

/** @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>
#include <asm/system.h>
#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 <xpci3009.h>
#include <xpci3009-kapi.h>

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

EXPORT_NO_SYMBOLS;

static const char modulename[] = "sample_chronos_01";


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 = XPCI3009_40MHZ;				// Clock

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

	/*
	 * XPCI3009_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;

	/* XPCI3009_SINGLE / XPCI3009_CONTINUOUS */
	uint8_t b_CycleMode = XPCI3009_CONTINUOUS;

	/* XPCI3009_DISABLE / XPCI3009_ENABLE */
	uint8_t b_Interrupt = XPCI3009_DISABLE;

	uint8_t b_ChronoStatus = 0;
	uint32_t ul_ChronoValue = 0;

	/* Display management */
	uint8_t nostartflag = 0;
	uint8_t stopflag = 0;
	uint8_t startflag = 0;

	/* Lock the function to avoid parallel configurations */
	xpci3009_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_XPCI3009_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_XPCI3009_InitChrono (pdev,
										   b_ModuleNbr,
										   b_ChronoMode,
										   b_PCIInputClock,
										   b_TimingUnity,
										   ul_TimingInterval);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			printk ("\n\n");

			do
			{
				/* Read the chronometer */
				i_ReturnValue = i_XPCI3009_ReadChronoValue (pdev,
															b_ModuleNbr,
															0,
															&b_ChronoStatus,
															&ul_ChronoValue);

				/* Test the return value */
				switch (i_ReturnValue)
				{
					case 0:
						switch (b_ChronoStatus)
						{
							case 0:
							/* Display only once */
							if (!nostartflag)
							{
								printk ("\nNo start signal occur");
								nostartflag = 1;
								stopflag = 0;
								startflag = 0;
							}
							break;

							case 1:
							/* Display only once */
							if (!startflag)
							{
								printk ("\nStart signal occur");
								nostartflag = 0;
								stopflag = 0;
								startflag = 1;
							}
							break;

							case 2:
							/* Display only once */
							if (!stopflag)
							{
								printk ("\nStop signal occur");
								printk ("\nChronometer value from module %d = %u", b_ModuleNbr, ul_ChronoValue);
								nostartflag = 0;
								stopflag = 1;
								startflag = 0;
							}
							break;

							case 3:
							printk ("\nOverflow occur");
							break;
						}
					break;

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

					case 2:
						printk ("\ni_XPCI3009_ReadChronoValue for the first counter error");
						printk ("\nError = %d. The selected module is wrong", i_ReturnValue);
						return 0;
					break;

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

					case 4:
						printk ("\ni_XPCI3009_ReadChronoValue for the first counter error");
						printk ("\nError = %d. Chronometer not initialised", i_ReturnValue);
						return 0;
					break;

					case 5:
						printk ("\ni_XPCI3009_ReadChronoValue for the first counter error");
						printk ("\nError = %d. Timeout parameter is wrong (0 to 65535)", i_ReturnValue);
						return 0;
					break;

					case 6:
						printk ("\ni_XPCI3009_ReadChronoValue for the first counter error");
						printk ("\nError = %d. Interrupt routine installed, you can not read directly the chronometer measured timing", i_ReturnValue);
						return 0;
					break;

					default:
						printk ("\ni_XPCI3009_ReadChronoValue for the first counter error");
						printk ("\nError = %d.", i_ReturnValue);
						return 0;
					break;
				}
				udelay(1);
			}
			while (i++ < 10000000UL);

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

			i_ReturnValue = i_XPCI3009_DisableChrono (pdev, b_ModuleNbr);

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

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

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

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

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

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

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

		}
		break;

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

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

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

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

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

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

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

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

	return 0;
}


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

	printk("%s: looking for board %u\n",modulename,board_index);
	pdev=xpci3009_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 xpci3009Sample_exit(void)
{


}
//------------------------------------------------------------------------------
module_exit(xpci3009Sample_exit);
module_init(xpci3009Sample_init);
