#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
#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0)
#include <asm/uaccess.h>
#else
    #include <linux/uaccess.h>
#endif
#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 <linux/time.h>

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

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

EXPORT_NO_SYMBOLS;

static const char modulename[] = "sample_watchdog";


static int sample_watchdog_kernel (struct pci_dev *pdev)
{ 
	int ret = 0;
	uint8_t stop = 0;
	uint8_t b_TimerMode = APCI3120_WATCHDOG;
	uint32_t ul_DelayTime = 200000; /* Watchdog delay from 70 us to 4587400 us, set on 200 ms */
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,5,19)
	time_t sec = 10; /* Duration of the test loop */
#else
	signed long sec = 10; // it's define as this in nolibc.h (which is an alternative) it's at least 32bits long as time_t
#endif

	uint8_t b_InterruptFlag = APCI3120_DISABLE; /* Interruption is not implemented */
	uint8_t b_watchdogStatus = 0;
	
	uint8_t b_ChannelNumber = 0;
	uint8_t b_Polarity = APCI3120_UNIPOLAR;
	uint32_t ui_ValueToWrite = 819;

#if LINUX_VERSION_CODE < KERNEL_VERSION(4,20,17) // because do_gettimeofday isn't supported anymore after this kernel version
	struct timeval now;
	struct timeval before; 
#else
	struct timespec64 now;
	struct timespec64 before;
#endif

		printk("i_APCI3120_InitAnalogOutput: ");	
	
		ret = i_APCI3120_InitAnalogOutput (pdev, b_ChannelNumber, b_Polarity);
		
		switch (ret)
		{
			case 0: printk("OK\n"); 
			break;
			
			case 1: printk("The handle parameter of the board is wrong.\n");         
			break;
			
			case 2: printk("Number of the analog output is wrong.\n");                
			break;
						
			case 3: printk("The selected output voltage range is wrong.\n");                         
			break;           			
			
			default: printk("Unknown error.\n");
			break;			
		}		

		printk("i_APCI3120_InitTimerWatchdog: ");	
			
		ret = i_APCI3120_InitTimerWatchdog (pdev, b_TimerMode, ul_DelayTime, b_InterruptFlag);
		
		switch (ret)
		{
			case 0: printk("OK\n"); 
			break;
			
			case 1: printk("The handle parameter of the board is wrong.\n");         
			break;
			
			case 2: printk("Timer mode error.\n");                
			break;
						
			case 3: printk("The user interrupt routine is not installed.\n");                         
			break;
						
			case 4: printk("The interrupt parameter is wrong.\n");          
			break;
						
			case 5: printk("The time interval selected for timer is wrong.\n");
			break;			              			
			
			default: printk("Unknown error.\n");
			break;			
		}

		printk("i_APCI3120_StartTimerWatchdog: ");	
										  
    	ret = i_APCI3120_StartTimerWatchdog (pdev);

		switch (ret)
		{
			case 0: printk("OK\n"); 
			break;
			
			case 1: printk("The handle parameter of the board is wrong.\n");         
			break;
			
			case 2: printk("Timer or watchdog not initialized.\n");                
			break;	              			
			
			default: printk("Unknown error.\n");
			break;			
		}
						 
		/* Get the current time */
		#if LINUX_VERSION_CODE < KERNEL_VERSION(4,20,17)
			do_gettimeofday (&before);
		#else
			// ktime_get_real_ts64 // Returns the time of day in a timespec64 (WARN if suspended).
			ktime_get_real_ts64(&before);
		#endif
		
		/* Start a test loop for sec seconds:
		 * Write on the first analog output to trigger the watchdog.
		 * After 5 seconds stop to write on the output.
		 * The watchdog shoud occured and reset outputs.
		 */	
		while (!stop)
		{		
			/* Get the current time */
			#if LINUX_VERSION_CODE < KERNEL_VERSION(4,20,17)
				do_gettimeofday (&now);	
			#else
				// ktime_get_real_ts64 // Returns the time of day in a timespec64 (WARN if suspended).
				ktime_get_real_ts64(&now);
			#endif

			/* Wait that the DAC is ready to set a new analog output value and write only */		
			if ((i_APCI3120_ReadAnalogOutputBit (pdev) == 0) && ((before.tv_sec + 2) >= now.tv_sec))
			{
				ret = i_APCI3120_Write1AnalogOutput (pdev, b_ChannelNumber, ui_ValueToWrite);
				
				switch (ret)
				{
					case 0:
						printk("i_APCI3120_Write1AnalogOutput: Write %u on channel 0.\n", ui_ValueToWrite);
						ui_ValueToWrite += 819;
						if (ui_ValueToWrite >= 8192)
							ui_ValueToWrite = 819;
					break;
					
					case 1: printk("i_APCI3120_Write1AnalogOutput: The handle parameter of the board is wrong.\n");         
							stop = 1;	
					break;
					
					case 2: printk("i_APCI3120_Write1AnalogOutput: Wrong output value for the output channel.\n");
					        stop = 1;	
					break;	            	
					
					default: printk("i_APCI3120_Write1AnalogOutput: Unknown error.\n");
							stop = 1;					
					break;			
				}
			}
			
			ret = i_APCI3120_ReadWatchdogStatus (pdev, &b_watchdogStatus);
			
			switch (ret)
			{
				case 0:
					if (b_watchdogStatus != 0)
					{ 				
						printk("Watchdog status: Watchdog runs down\n"); 											
						stop = 1;
					}
				break;
				
				case 1: printk("i_APCI3120_ReadWatchdogStatus: The handle parameter of the board is wrong.\n");
						stop = 1;         
				break;
				
				case 2: printk("i_APCI3120_ReadWatchdogStatus: Watchdog not initialized.\n");                
						stop = 1;
				break;	            
				
				case 3: printk("i_APCI3120_ReadWatchdogStatus: Timer was initialized, not a Watchdog.\n");
						stop = 1;				
				break;  			
				
				default: printk("i_APCI3120_ReadWatchdogStatus: Unknown error.\n");
						stop = 1;				
				break;			
			}			
							
			if ((before.tv_sec + sec) < now.tv_sec)
				stop = 1;	
				
			/* Wait 100 ms */
			mdelay (100);
		}
		
		printk("i_APCI3120_StopTimerWatchdog: ");	
										  
    	ret = i_APCI3120_StopTimerWatchdog (pdev);

		switch (ret)
		{
			case 0: printk("OK\n"); 
			break;
			
			case 1: printk("The handle parameter of the board is wrong.\n");         
			break;
			
			case 2: printk("Timer or watchdog not initialized.\n");                
			break;	              			
			
			case 3: printk("Timer or watchdog has not been started.\n");
			break;
			
			default: printk("Unknown error.\n");
			break;			
		}
		
		return 0;
}


/** Called when module loads. */
static int __init apci3120Sample_init(void)
{
	struct pci_dev *pdev;
	int board_index = 0;
	printk("%s: looking for board %u\n",modulename,board_index);
	pdev=apci3120_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_watchdog_kernel (pdev);

	return 0;
}
//-------------------------------------------------------------------
/** Called when module is unloaded. */
static void __exit apci3120Sample_exit(void)
{
 	
}
//------------------------------------------------------------------------------
module_exit(apci3120Sample_exit);
module_init(apci3120Sample_init);
