/** @file interrupt.c
*
* @author Sylvain Nahas
*
* This module implements the interrupt related functions.
*/

/** @par LICENCE
* @verbatim
Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code and the documentation.

ADDI-DATA GmbH
Airpark Business Center
Airport Boulevard B210
77836 Rheinmünster
Germany
Tel: +49(0)7229/1847-0 2
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 "apci3200-private.h"
#include "iomap.h"

#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

EXPORT_SYMBOL(i_APCI3200_SetBoardIntRoutine);
EXPORT_SYMBOL(i_APCI3200_ResetBoardIntRoutine);

/**@def EXPORT_NO_SYMBOLS
* Function in this file are not exported.
*/
EXPORT_NO_SYMBOLS;

/** Enable and set the interrupt routine.
*
* @param [in] pdev              : The device to initialize.
* @param [in] InterruptCallback : The user interrupt callback.
*
* @retval 0: No error.
* @retval 1: The handle parameter of the board is wrong.
* @retval 2: The interrupt callback is NULL.
*/
INT   i_APCI3200_SetBoardIntRoutine  (struct pci_dev * pdev, void (*InterruptCallback) (DWORD minor, DWORD mask, DWORD *value))
{
	if (!pdev) return 1;

	/* If the global interrupt is not set */
	if (APCI3200_PRIVDATA(pdev)->UserInterruptCallback == NULL)
	{
		/* If the user don't specify the callback function */
		if (InterruptCallback == NULL) return 2;

		/* Set the user callback function */
		APCI3200_PRIVDATA(pdev)->UserInterruptCallback = InterruptCallback;
	}

	return 0;
}

/** Disable and reset the interrupt routine.
*
* @param [in] pdev              : The device to initialize.
*
* @retval 0: No error.
* @retval 1: The handle parameter of the board is wrong.
*/
INT   i_APCI3200_ResetBoardIntRoutine     (struct pci_dev * pdev)
{
	if (!pdev) return 1;

	APCI3200_PRIVDATA(pdev)->UserInterruptCallback = NULL;

	return 0;
}


//------------------------------------------------------------------------------
/** has the card actually generated an interrupt ?
*
* @return 1 if the board has generated an interrupt, 0 otherwise
*
* */
static __inline__ int HAS_INTERRUPTED(struct pci_dev * pdev )
{
	return 0;
}
//------------------------------------------------------------------------------
/** acknowledge interrupt by unsetting the corresponding bit */
static __inline__ void ACK_INTERRUPT(struct pci_dev * pdev)
{
	//FILLME
}

//------------------------------------------------------------------------------
/** IRQ handler prototype has changed between 2.4 and 2.6.
* in 2.4 the handler has not return value, in 2.6 it is of type irqreturn_t
* IRQ_HANDLED means that the device did indeed need attention from the driver
* IRQ_NONE means that the device didn't actually need attention
*
* NOTE: the change between 2.4 and 2.6 was not so important that it needed
* two version of the function. BUT if in the course of the implementation you
* notice the changes are so important that maintaining a code for both version
* in one function is just a hassle, DON'T HESITATE and create two versions
* of the same function.
*
*/

/** IRQ Handler */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	/* 2.4  */
#define RETURN_HANDLED return
#define RETURN_NONE return
#else
#define RETURN_HANDLED return IRQ_HANDLED
#define RETURN_NONE return IRQ_NONE
#endif

/* interrupts come in SCAN mode
 * there is one interrupt per data to read
 *
 */


/** @retval 0 no
 * @retval 1 yes
 */
static inline int has_module_generated_eos_interrupt(struct pci_dev *pdev, int module)
{
	uint32_t dw_StatusRegister = inl(module_to_address(pdev, module) + APCI3200_REG_EOSIRQ);
	return (dw_StatusRegister & 0x2);
}

static int handle_scan_interrupt(struct pci_dev *pdev, uint8_t module)
{
	/* Read the raw Analog Input value */
	GET_SCAN_DATA(pdev,module)->dw_DigitalAnalogInputValue[GET_SCAN_DATA(pdev,module)->counter] = inl(module_to_address(pdev,module) + APCI3200_REG_CHANNEL_VALUE);

	GET_SCAN_DATA(pdev,module)->counter++;

	/* Test if all data of the scan are read and we can call user handler */
	if (GET_SCAN_DATA(pdev,module)->counter == GET_MODULE_DATA(pdev,module)->ui_NbDataToRead)
	{
		/* Reset the counter value */
		GET_SCAN_DATA(pdev,module)->counter = 0;

		/* Put the values in the FIFO */
		{
			apci3200_event_t event;
			int ret = 0;

			/* Header */
			event.size = GET_MODULE_DATA(pdev,module)->ui_NbDataToRead;
			event.mask = (module | 0x10);

			/* Data */
			memcpy (event.values, GET_SCAN_DATA(pdev,module)->dw_DigitalAnalogInputValue, (sizeof (uint32_t) * GET_MODULE_DATA(pdev,module)->ui_NbDataToRead));

			/* The fifo is full, the next element overwrites the first one! */
			if (apci3200_evt_full (pdev))
				event.mask |= 0x80000000;

			if ((ret = apci3200_evt_add (pdev, &event, 1)) != 0)
			{
				printk ("apci3200_evt_add: No space left to add values (ret = %i)\n", ret);
			}

			// Wake up the waiting processes
			apci3200_evt_wakeup(pdev);
		}

		/* Call the user kernel interrupt handler */
		if (APCI3200_PRIVDATA(pdev)->UserInterruptCallback != NULL)
		{
			APCI3200_PRIVDATA(pdev)->UserInterruptCallback ((DWORD)(APCI3200_PRIVDATA(pdev)->minor),
					(DWORD)(module | 0x10),
					(DWORD *) GET_SCAN_DATA(pdev,module)->dw_DigitalAnalogInputValue);
		}

		/* set buffer to zero */
		memset(GET_SCAN_DATA(pdev,module)->dw_DigitalAnalogInputValue, 0 , sizeof(GET_SCAN_DATA(pdev,module)->dw_DigitalAnalogInputValue));

	}

	return 0;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	/* 2.4  */
static void apci3200_do_interrupt(int irq, void * dev_id, struct pt_regs *regs)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)       /* 2.6.0 - 2.6.19  */
static irqreturn_t apci3200_do_interrupt(int irq, void * dev_id, struct pt_regs *regs)
#else /* >= 2.6.19 */
static irqreturn_t apci3200_do_interrupt(int irq, void * dev_id)
#endif
{
	uint8_t module=0;
	unsigned int b_InterruptFound = 0;
	struct pci_dev * pdev = VOID_TO_PCIDEV(dev_id);
	{
		unsigned long irqstate;
		APCI3200_LOCK(pdev, &irqstate);
		{
			/* Search the module which has generated an interrupt EOS IRQ */
			/* loop is done for all modules as long b_InterruptFound is 0  */
			/* loop stops by first module having generated the interrupt */
			for (module=0; module<(APCI3200_PRIVDATA(pdev)->w_NbHardwareComponent) ; module++)
			{
				if (!has_module_generated_eos_interrupt(pdev,module))
					continue;
				b_InterruptFound = 1;
				handle_scan_interrupt(pdev,module);
			}
		}
		APCI3200_UNLOCK(pdev,irqstate);
	}
	/* Asynchronous IO signal */
	{
		/*
		if (APCI3200_PRIVDATA(VOID_TO_PCIDEV(dev_id))->async_queue)
		{
			kill_fasync( &(APCI3200_PRIVDATA(VOID_TO_PCIDEV(dev_id))->async_queue), SIGIO, POLL_IN);
		}
		 */
	}

	if (b_InterruptFound)
		RETURN_HANDLED;
	else
		RETURN_NONE;
}

//------------------------------------------------------------------------------
static void enable_pci_interrupt(struct pci_dev * pdev)
{
	register uint32_t tmp =  inl (GET_BAR0(pdev) + APCI3200_INTCSR);
	outl (tmp | 0x2000, GET_BAR0(pdev) + 0x38);
}
//------------------------------------------------------------------------------
static void disable_pci_interrupt(struct pci_dev * pdev)
{
	uint32_t tmp =  inl (GET_BAR0(pdev) + APCI3200_INTCSR);
	outl (tmp & (~0x2000), GET_BAR0(pdev) + 0x38);
}
//------------------------------------------------------------------------------
int apci3200_register_interrupt(struct pci_dev * pdev)
{


#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
	if ( request_irq( pdev->irq, apci3200_do_interrupt, SA_SHIRQ, __DRIVER_NAME, pdev) )
#else
	if ( request_irq( pdev->irq, apci3200_do_interrupt, IRQF_SHARED, __DRIVER_NAME, pdev) )
#endif
	{
		printk(KERN_ERR "%s: can't register interrupt handler\n", __DRIVER_NAME );
		return -EBUSY;
	}

	enable_pci_interrupt(pdev);

	return 0;
}
//------------------------------------------------------------------------------
int apci3200_deregister_interrupt(struct pci_dev * pdev)
{
	disable_pci_interrupt(pdev);
	free_irq( pdev->irq , pdev);
	return 0;
}

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