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

/** @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 "apci3120-private.h"

EXPORT_NO_SYMBOLS;

//------------------------------------------------------------------------------
/** 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 )
{
	//FILLME
	return 0;
}
//------------------------------------------------------------------------------
/** acknowledge interrupt -
*
* @note This is board specific - usually setting a bit in a register
*/
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


#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)	/* 2.4  */
static void apci3120_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 apci3120_do_interrupt(int irq, void * dev_id, struct pt_regs *regs)
#else /* >= 2.6.20 */
static irqreturn_t apci3120_do_interrupt(int irq, void * dev_id)
#endif
{
	unsigned short handled = 0;
	unsigned short  status_value;
	unsigned long irqstate;
	unsigned char dummy_read;
	unsigned char interupt_mask;
	struct pci_dev * pdev = VOID_TO_PCIDEV(dev_id);
	unsigned short* pData;
	unsigned long dma_buff_addr;
	apci3120_event_t evt; // Event to send to the user space
	int i = 0;
	int j = 0;
	int number_of_event = 0;
	int return_value = 0;

	APCI3120_LOCK(pdev, &irqstate);
	{
		status_value = inw(GET_BAR1(pdev) + APCI3120_RD_STATUS);	/* get the interrupt status of the board */
		status_value = (status_value >> 12) & 0xF;					/* mask the possible interrupt reasons */
		/****************************************************************************************************/
		/**** Test if DMA interrupt ****/
		// FIXME this code just doesn't work for big acquisition that exceed the size of the allocated DMA, which is way smaller than
		// the theorical value we could use with the board. This code really only work with acquisition that doesn't
		// exceed the APCI3120_PRIVDATA(pdev)->dma_buffer size, which is quite small: 4 pages maximum so 4*PAGE_SIZE, in most systems it's 16kio
		// Which is far less than the board theorical maxium of (sequence_array_size * sequence_data.number_of_acquisition * 2).
		if ((status_value & 0x4) && (APCI3120_PRIVDATA(pdev)->intMode == APCI3120_DMA_INT_MODE))
		{
			evt.source = APCI3120_EVT_DMA_INTERRUPT; // Set the status of the event
			handled = 1;

			/* Clear Timer Write TC INT */
			outl(APCI3120_CLEAR_WRITE_TC_INT, GET_BAR0(pdev) + APCI3120_AMCC_OP_REG_INTCSR);

			/* Clear Timer int */
			dummy_read = inb(GET_BAR1(pdev) + APCI3120_TIMER_STATUS_REGISTER);

			if (APCI3120_PRIVDATA(pdev)->dma_state == APCI3120_ENABLE)
			{
				interupt_mask = interupt_mask | 0x8;

				if (APCI3120_PRIVDATA(pdev)->sequence_data.acquisition_cycle == APCI3120_SINGLE)
				{
					/* Stops the Timer */
					outw(APCI3120_PRIVDATA(pdev)->nWrAddr & APCI3120_DISABLE_TIMER0 & APCI3120_DISABLE_TIMER1, GET_BAR1(pdev) + APCI3120_WR_ADDRESS);

					APCI3120_PRIVDATA(pdev)->nWrAddr  = APCI3120_PRIVDATA(pdev)->nWrAddr & APCI3120_DISABLE_TIMER0 & APCI3120_DISABLE_TIMER1;
					pData = (unsigned short *) APCI3120_PRIVDATA(pdev)->dma_buffer;
					APCI3120_PRIVDATA(pdev)->dma_buf_index = 0;

					/* Resets the FIFO */
					dummy_read = inb(GET_BAR1(pdev) + APCI3120_RESET_FIFO);
				}
				else /* if (ps_APCI3120Variable ... AcquisitionCycle == APCI3120_SINGLE)*/
				{
					/* Continuous DMA mode */
					/* Initializes the DMA    */
					/* ENABLE BUS MASTER */
					outw(APCI3120_ADD_ON_AGCSTS_LOW, GET_BAR2(pdev));
					outw(APCI3120_ENABLE_TRANSFER_ADD_ON_LOW, GET_BAR2(pdev) + 2);

					outw(APCI3120_ADD_ON_AGCSTS_HIGH, GET_BAR2(pdev));
					outw(0x1000, GET_BAR2(pdev) + 2);

					/* set active DMA buffer */
					if (APCI3120_PRIVDATA(pdev)->dma_buf_index)
						dma_buff_addr = (unsigned long)APCI3120_PRIVDATA(pdev)->dma_old_buffer_hw;
					else
						dma_buff_addr = (unsigned long)APCI3120_PRIVDATA(pdev)->dma_buffer_hw;

					/* DMA Start Adress Low */
					outw(APCI3120_ADD_ON_MWAR_LOW, GET_BAR2(pdev));
					outw((dma_buff_addr & 0x0000FFFFUL),  GET_BAR2(pdev) + 2);

					/* DMA Start Adress High */
					outw(APCI3120_ADD_ON_MWAR_HIGH, GET_BAR2(pdev));
					outw(((dma_buff_addr & 0xFFFF0000UL)>>16),  GET_BAR2(pdev) + 2);

					APCI3120_PRIVDATA(pdev)->dma_buf_index = (APCI3120_PRIVDATA(pdev)->dma_buf_index ^ 1);	/* toggle index 0 <-> 1 */

					/* Nbr of acquisition LOW */
					outw(APCI3120_ADD_ON_MWTC_LOW, GET_BAR2(pdev));
					outw((unsigned short) (APCI3120_PRIVDATA(pdev)->sequence_data.number_of_acquisition * 2) & 0xFFFF, GET_BAR2(pdev) + 2);

					/* Nbr of acquisition HIGH */
					outw(APCI3120_ADD_ON_MWTC_HIGH, GET_BAR2(pdev));
					outw((unsigned short)((APCI3120_PRIVDATA(pdev)->sequence_data.number_of_acquisition * 2) >> 16), GET_BAR2(pdev) + 2);

					/* A2P FIFO CONFIGURATE, END OF DMA INTERRUPT INIT */
					outl((APCI3120_FIFO_ADVANCE_ON_BYTE_2 | APCI3120_ENABLE_WRITE_TC_INT),GET_BAR0(pdev) + APCI3120_AMCC_OP_REG_INTCSR);

					/* ENABLE A2P FIFO WRITE AND ENABLE AMWEN */
					outw(3, GET_BAR2(pdev) + 4);
				}  /* else if (ps_APCI3120Variable ... AcquisitionCycle == APCI3120_SINGLE)*/
			}     /* if (ps_APCI3120Variable ...b_InputAcquisitionEnable == APCI3120_ENABLE)*/
			else
			{
				/* Stops the Timer */
				outw(APCI3120_PRIVDATA(pdev)->nWrAddr & APCI3120_DISABLE_TIMER0 & APCI3120_DISABLE_TIMER1, GET_BAR1(pdev) + APCI3120_WR_ADDRESS);
				APCI3120_PRIVDATA(pdev)->nWrAddr  = APCI3120_PRIVDATA(pdev)->nWrAddr & APCI3120_DISABLE_TIMER0 & APCI3120_DISABLE_TIMER1;
			}

			if (APCI3120_PRIVDATA(pdev)->dma_buf_index == 0)
			{
				pData = (unsigned short *)APCI3120_PRIVDATA(pdev)->dma_buffer;
			}
			else
			{
				pData = (unsigned short *)APCI3120_PRIVDATA(pdev)->dma_old_buffer; //FIXME This is really a bad name for what is simply the second buffer, nothing to do with "old"
			}

			// Fill the event fifo with DMA data.
			// FIXME In the future use a data fifo for data and an event fifo for event info, with data size in info etc. It will be 10 times better to handle.
			// One event will contain one "sequence" that contain data for each input that was selected for recording.
			// FIXME remember that this code is wrong if the DMA buffer size < (sequence_array_size * sequence_data.number_of_acquisition * 2)
			number_of_event = APCI3120_PRIVDATA(pdev)->sequence_data.number_of_acquisition / APCI3120_PRIVDATA(pdev)->sequence_array_size;
			for ( i = 0; i < number_of_event; i++ )
			{
				for ( j = 0; j < APCI3120_PRIVDATA(pdev)->sequence_array_size; j++ )
				{
					evt.param1[j] = pData[ (i*APCI3120_PRIVDATA(pdev)->sequence_array_size) + j ];
				}
				// Send the event
				return_value = apci3120_evt_add(pdev, &evt, 1 );
				if(return_value) {
					pr_err("APCI3120 DMA interrupt: software FIFO was full before adding event.\n"
							"This means the software Fifo is not emptied fast enough and we are losing data.\n");
					break;
				}
			}
			// Wake up the waiting processes
			apci3120_evt_wakeup(pdev);
		}     /* if ((ui_StatusValue & 0x4) && (ps_APCI3120Variable ... b_InterruptMode == APCI3120_DMA_INT_MODE))*/
	}
	APCI3120_UNLOCK(VOID_TO_PCIDEV(dev_id), irqstate);

	if ( handled == 1)
		RETURN_HANDLED;

	RETURN_NONE;
}

//------------------------------------------------------------------------------
/* enable interrupts (PEX8311 controller) in INTCSR register (=BAR1+0x68)
 *
 * bit 1 : internal PCI wire interrupt enable
 * bit 11: enable nLINTi pin
 */
static void apcie3120_enable_interrupt(struct pci_dev * pdev)
{
	/* set bit 8 and 11 of INTCSR */
	uint32_t tmp = XPCI3120_INW(pdev, GET_BAR1(pdev)+0x68)|(1<<11)|(1<<8);
	outw(tmp,GET_BAR1(pdev)+0x68);
}
//------------------------------------------------------------------------------
/* disable interrupts (PEX8311 controller) in INTCSR register (=BAR1+0x68) */
static void apcie3120_disable_interrupt(struct pci_dev * pdev)
{
	/* clear bit 8 and 11 of INTCSR */
	uint32_t tmp = XPCI3120_INW(pdev, GET_BAR1(pdev)+0x68) & ~((1<<11)|(1<<8));
	outw(tmp,GET_BAR1(pdev)+0x68);
}
//------------------------------------------------------------------------------
int apci3120_register_interrupt(struct pci_dev * pdev)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
	/*
	 * Threaded IRQ — required for PREEMPT_RT compatibility.
	 *
	 * The bottom-half (apci3120_do_interrupt) acquires a spinlock_t which
	 * becomes a sleeping mutex under PREEMPT_RT. Running it as a threaded
	 * handler ensures it executes in process context where sleeping locks
	 * are safe to acquire.
	 *
	 * Passing NULL as the primary handler tells the kernel to use the
	 * default primary, which simply schedules the thread (combined with
	 * IRQF_ONESHOT this disables the IRQ line until the thread completes).
	 */
	if ( request_threaded_irq( pdev->irq, NULL, apci3120_do_interrupt,
				   IRQF_SHARED | IRQF_ONESHOT, __DRIVER_NAME, pdev) )
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
	if ( request_irq( pdev->irq, apci3120_do_interrupt, IRQF_SHARED, __DRIVER_NAME, pdev) )
#else
	if ( request_irq( pdev->irq, apci3120_do_interrupt, SA_SHIRQ, __DRIVER_NAME, pdev) )
#endif
		{
			return -EBUSY;
		}

	/* board specific interruption initialization*/
	{
		// If the card is an APCIE
		if(pdev->device==APCIE3120_BOARD_DEVICE_ID)
			apcie3120_enable_interrupt(pdev);

		APCI3120_PRIVDATA(pdev)->interruptInitialized = APCI3120_ENABLE;
	}

	return 0;
}
//------------------------------------------------------------------------------
int apci3120_deregister_interrupt(struct pci_dev * pdev)
{
	{
		if(pdev->device==APCIE3120_BOARD_DEVICE_ID)
			apcie3120_disable_interrupt(pdev);
	}
	free_irq( pdev->irq , pdev);
	APCI3120_PRIVDATA(pdev)->interruptInitialized = APCI3120_DISABLE;
	return 0;
}

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

/*
  +----------------------------------------------------------------------------+
  | Function name   : INT        i_APCI3120_SetBoardInterruptRoutine           |
  |                                              (BYTE     b_BoardHandle,      |
  |                 VOID (*usrHdl) (BYTE handle, BYTE intMask, USHORT *aiv))   |
  +----------------------------------------------------------------------------+
  | Task              : Initialises the interruptroutine for the APCI-3120     |
  +----------------------------------------------------------------------------+
  | Input parameters  : BYTE   b_BoardHandle :Handle of the APCI-3120          |
  |                     VOID (*usrHdl): User interrupt function                |
  +----------------------------------------------------------------------------+
  | Output Parameters : ---                                                    |
  +----------------------------------------------------------------------------+
  | Return Value      :  0 : No error                                          |
  |                      1 : The handle parameter of the board is wrong        |
  |                      2 : Interrupt routine already installed               |
  |                      3 : Interrupt routine not installed                   |
  +----------------------------------------------------------------------------+
*/

int do_CMD_APCI3120_SetBoardInterruptRoutine (struct pci_dev *pdev, unsigned int cmd, unsigned long arg)
{
	if(!pdev)
	{
		return 1;
	}

	if(APCI3120_PRIVDATA(pdev)->interruptInitialized == APCI3120_ENABLE)
	{
		return 2;
	}

	if ( apci3120_register_interrupt(pdev) )
	{
		// failed, clean previously allocated resources
		printk(KERN_ERR "Failed to register the interruption \n");
		kfree(APCI3120_PRIVDATA(pdev));
		pci_release_regions(pdev);
		return 3;
	}
	printk(KERN_ERR "Interruption set ! \n");
	return 0;
}

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

/*
  +----------------------------------------------------------------------------+
  | Function Name     : _INT_ i_APCI3120_ResetBoardIntRoutine                  |
  |                                                      (BYTE_ b_BoardHandle) |
  +----------------------------------------------------------------------------+
  | Task              : Stops the interrupt management of board APCI-3120.     |
  |                     Deinstalls the user interrupt routine if the management|
  |                     of interrupts of all boards APCI-3120 is stopped.      |
  +----------------------------------------------------------------------------+
  | Input Parameters  : BYTE_ b_BoardHandle : Handle of board APCI-3120        |
  +----------------------------------------------------------------------------+
  | Output Parameters : -                                                      |
  +----------------------------------------------------------------------------+
  | Return Value      :  0: No error                                           |
  |                      1: The handle parameter of the board is wrong         |
  |                      2: No Interrupt function initialized with function    |
  |                         "i_APCI3120_SetBoardIntRoutine"                    |
  +----------------------------------------------------------------------------+
*/
int do_CMD_APCI3120_ResetBoardInterruptRoutine (struct pci_dev *pdev, unsigned int cmd, unsigned long arg)
{
	if(!pdev)
	{
		return 1;
	}
	if(APCI3120_PRIVDATA(pdev)->interruptInitialized == APCI3120_DISABLE)
	{
		return 2;
	}
	apci3120_deregister_interrupt(pdev);
	return 0;
}


