/** @file ksample02.c
*
* @author J. Krauth
* @author Hilpert Rodolphe
*
* Simple kernel sample with DMA interrupt.
*/

/** @par LICENCE
* @verbatim
 Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code and the documentation.
        
        ADDI-DATA GmbH
        Dieselstrasse 3 
        D-77833 Ottersweier
        Tel: +19(0)7223/9493-0
        Fax: +49(0)7223/9493-92
        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/pci.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");
MODULE_DESCRIPTION("XPCI-3009 Analog input DMA sample");

#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)
MODULE_SUPPORTED_DEVICE("XPCI-3009");
#endif

EXPORT_NO_SYMBOLS;

/** The name of the module. */
#define MODULE_NAME "ksample02"

#define NUMBER_OF_BOARDS	1

#define NBR_OF_DMA_BUFFER 	(2 + ADDIDATA_MAX_AI)
#define NBR_OF_VALUE		(3 + ADDIDATA_MAX_AI)
#define FISRT_VALUE_ADDRESS	(4 + ADDIDATA_MAX_AI)


#define NUMBER_OF_CHANNELS					16
#define NUMBER_OF_INTERRUPT_PRO_SEQUENCE	10
#define NUMBER_OF_ACQUISITION_PRO_INTERRUPT	(NUMBER_OF_CHANNELS*NUMBER_OF_INTERRUPT_PRO_SEQUENCE)
#define TOTAL_NUMBER_OF_INTERRUPT			50

/* Global pointer on the board structure. Global because it is used in module init and cleanup */
static struct pci_dev * _pdev[NUMBER_OF_BOARDS];
static int interrupt_counter;

static uint16_t *value;
static uint32_t *info;


//--------------------------------------------------------------------------------
/* The user callback interrupt */
static void interrupt_callback (struct pci_dev * pdev, uint32_t dw_Dummy)
{
	static uint32_t DeviceMinorNumber;
	static uint8_t b_ValueCounter;
	static uint16_t *pw_Value ;

		/* Get shared memory pointer (pointer on DMA value) */
		i_xpci3009_TestInterrupt(pdev, &DeviceMinorNumber, info, &pw_Value);

		/* Test if FIFO Empty */
		if (0xfffffffe != info[0])
		{
			/* Analog input with DMA */
			if (info[0] & 0x1) 
			{
				/* Test if there is just one buffer. (The Linux driver managed one buffer) */
				if ((info[NBR_OF_DMA_BUFFER] == 1) && (interrupt_counter < TOTAL_NUMBER_OF_INTERRUPT))
				{
					for (b_ValueCounter = 0 ; b_ValueCounter < info[NBR_OF_VALUE] ; b_ValueCounter ++)
						value[(interrupt_counter * NUMBER_OF_ACQUISITION_PRO_INTERRUPT) + b_ValueCounter] = 
						*(pw_Value + b_ValueCounter + info[FISRT_VALUE_ADDRESS]);
				}
				else
					interrupt_counter = -1;
			}

			if (info[0] & 0x4)
			{
				printk ("Analog input FIFO Overflow\n");	
				interrupt_counter = TOTAL_NUMBER_OF_INTERRUPT;
			}
		}
		else
		{
			/* Test if FIFO Full */
			if (0xffffffff != info[0])
			{
				printk("dw_InterruptSource : %x\n", info[0]); 
				interrupt_counter = TOTAL_NUMBER_OF_INTERRUPT;
			}
		}

		interrupt_counter++;
}

//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------

static int ksample02_init(struct pci_dev * pdev)
{
	int error = 0;
	unsigned long irqstate;
	
    uint8_t  pb_ChannelList[NUMBER_OF_CHANNELS] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    uint8_t  pb_Gain[NUMBER_OF_CHANNELS] = {ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN,
										ADDIDATA_1_GAIN};

    uint8_t  pb_Polarity[NUMBER_OF_CHANNELS] = {ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR,
											ADDIDATA_BIPOLAR};

		printk ("Analog input sequence (DMA and interrupt)\n");

		xpci3009_lock(pdev,&irqstate);
		{
			error = i_xpci3009_InitAnalogInputSequence (pdev,
														NUMBER_OF_CHANNELS,							// Number of channels
														pb_ChannelList,
														pb_Gain, 
														pb_Polarity,
														ADDIDATA_DIFFERENTIAL,							// Single Diff
														0,											// Number of sequence (0: Continuous mode)
														ADDIDATA_ENABLE,							// Use DMA
														NUMBER_OF_INTERRUPT_PRO_SEQUENCE,			// Number of sequence for each interrupt
														ADDIDATA_DISABLE,							// Delay mode
														0,											// Delay time unit
														0);											// Delay time
		}
		xpci3009_unlock(pdev,irqstate);
	
		printk ("i_xpci3009_InitAnalogInputSequence :");
		switch (error)
		{
			case 0: printk ("OK\n");
			break;
			case 1: printk ("The handle parameter of the board is wrong\n");
			break;
			case 2: printk ("Wrong number of channels\n");
			break;
			case 3: printk ("Wrong channel number\n");
			break;
			case 4: printk ("Wrong gain factor\n");
			break;
			case 5: printk ("Wrong polarity\n");
			break;
			case 6: printk ("Wrong single diff parameter\n");
			break;
			case 7: printk ("Wrong use DMA parameter\n");
			break;
			case 8: printk ("Wrong number of sequence for each interrupt parameter\n");
			break;
			case 9: printk ("Wrong delay mode parameter\n");
			break;
			case 10: printk ("Wrong delay time unit parameter\n");
			break;
			case 11: printk ("Wrong delay time parameter\n");
			break;
			case 12: printk ("A conversion is already started\n");
			break;
			case 13: printk ("DMA mode not supported\n");
			break;	
			case 14: printk ("No memory space available\n");
			break;	
			case 15: printk ("Error by initialising the DMA\n");
			break;	
		}

		if (error)
			return -1;

		xpci3009_lock(pdev,&irqstate);
		{
			error = i_xpci3009_StartAnalogInputSequence (pdev,
														ADDIDATA_MICRO_SECOND,	// Convert time unit
														10);					// Convert time
		}
		xpci3009_unlock(pdev,irqstate);

		printk ("i_xpci3009_StartAnalogInputSequence :");
		switch (error) 
		{
			case 0: printk ("OK\n");
			break;
			case 1: printk ("The handle parameter of the board is wrong\n");
			break;
			case 2: printk ("Sequence not initialised\n");
			break;
			case 3: printk ("Wrong convert time unit\n");
			break;
			case 4: printk ("Wrong convert time\n");
			break;
			case 5: printk ("A conversion is already started\n");
			break;
			case 6: printk ("Error by starting DMA\n");
			break;
		}
	
	return error;
}

//--------------------------------------------------------------------------------

static void ksample02_release(struct pci_dev * pdev)
{
	int error = 0;
	unsigned long irqstate;

		/*
		* Remark: It is possible that error 3 occurred. 
		* That depends of the initialization type.
		* If e.g. only 2 DMA with interrupt acquisitions have
		* to be done, at the end of the second acquisition,
		* the "Sequence Started" flag is reset. The work
		* of i_xpci3009_StopAnalogInputSequence is done
		* in the interrupt function. Don't care about this
		* error.
		*/
		xpci3009_lock(pdev,&irqstate);
		{
			error = i_xpci3009_StopAnalogInputSequence (pdev);
		}
		xpci3009_unlock(pdev,irqstate);
	
		printk ("i_xpci3009_StopAnalogInputSequence :");
		switch (error) 
		{
			case 0: printk ("OK\n");
			break;
			case 1: printk ("The handle parameter of the board is wrong\n");
			break;
			case 2: printk ("Sequence not initialised\n");
			break;
			case 3: printk ("Sequence not started\n");
			break;
			case 4: printk ("Error by stoping DMA\n");
			break;
		}

		xpci3009_lock(pdev,&irqstate);
		{
			error = i_xpci3009_ReleaseAnalogInputSequence (pdev);
		}
		xpci3009_unlock(pdev,irqstate);
	
		printk ("i_xpci3009_ReleaseAnalogInputSequence :");
		switch (error) 
		{
			case 0: printk ("OK\n");
			break;
			case 1: printk ("The handle parameter of the board is wrong\n");
			break;
			case 2: printk ("Sequence not initialised\n");
			break;
			case 3: printk ("A conversion is already started\n");
			break;
			case 4: printk ("Error by releasing DMA\n");
			break;
		}
}

//--------------------------------------------------------------------------------

/** Called when the driver is loaded.*/
static int __init _init_module(void)
{
	int index = 0; /* Index of the board to use (here the fisrt board) */
	unsigned long irqstate;
	
		info = kmalloc (sizeof(uint32_t) * (NBR_OF_DMA_BUFFER + ADDIDATA_MAX_INTERRUPT_ANALOG_INPUT_VALUE), GFP_KERNEL);
		if (info == NULL)
			return 1;
				
		value = kmalloc (sizeof(uint16_t) * TOTAL_NUMBER_OF_INTERRUPT * NUMBER_OF_ACQUISITION_PRO_INTERRUPT, GFP_KERNEL);
		if (value == NULL)
			return 1;
	
		interrupt_counter = 0;

		/* Get a pointer on the board to use */
		for (index=0; index < NUMBER_OF_BOARDS; index++)
		{
			if ((_pdev[index] = xpci3009_lookup_board_by_index(index)) == NULL)
			{
				printk ( KERN_DEBUG "Board index %i not found\n", index);
				return -ENODEV;
			}
			printk ( KERN_DEBUG "Board index %i found (slot: %s)\n", index,pci_name(_pdev[index]));
		}
		
		/* Set the user interrupt callback */
		xpci3009_SetIntCallback (interrupt_callback);

		/* Test the first board found */
		for (index=0; index<NUMBER_OF_BOARDS; index++)
		{
			if ( ksample02_init (_pdev[index]) )
			{
				/* Reset the user interrupt callback */
				xpci3009_lock(_pdev[index],&irqstate);
				{
					xpci3009_ResetIntCallback ();
				}
				xpci3009_unlock(_pdev[index],irqstate);	
				return -EIO;
			}
		}
		
		return 0;
}

//--------------------------------------------------------------------------------

/** Called when the driver is unloaded.*/
static void __exit _cleanup_module(void)
{
	int NUMBER_OF_CHANNELS_Counter = 0;
	int TOTAL_NUMBER_OF_INTERRUPT_Counter = 0;
	int index = 0;
	
		for (index=0; index<NUMBER_OF_BOARDS; index++)
			ksample02_release (_pdev[index]);
			
		/* Reset the user interrupt callback */
		xpci3009_ResetIntCallback ();
			
		printk ("interrupt_counter=%i\n", interrupt_counter);
	
		for (TOTAL_NUMBER_OF_INTERRUPT_Counter=0; TOTAL_NUMBER_OF_INTERRUPT_Counter < TOTAL_NUMBER_OF_INTERRUPT; TOTAL_NUMBER_OF_INTERRUPT_Counter++)
		{
			printk ("\n%hu - ", TOTAL_NUMBER_OF_INTERRUPT_Counter);
			// display first sequence for each group of sequence
			for (NUMBER_OF_CHANNELS_Counter=0; NUMBER_OF_CHANNELS_Counter<NUMBER_OF_CHANNELS; NUMBER_OF_CHANNELS_Counter++) 
				printk ("%4hx ",value[TOTAL_NUMBER_OF_INTERRUPT_Counter * NUMBER_OF_ACQUISITION_PRO_INTERRUPT + NUMBER_OF_CHANNELS_Counter]);
			mdelay (3);
		}
			
		printk ("\nvalue address %x\n", (uint32_t)value);
		
		if (value!=NULL)
			kfree(value);

		if (info!=NULL)
			kfree(info);
}

//--------------------------------------------------------------------------------

module_init(_init_module);
module_exit(_cleanup_module);
