/** @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
        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 <apcie1564-events.h>
#include "apcie1564-private.h"
#include "tcw.h"

EXPORT_NO_SYMBOLS;



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

/* return 1 if an input generated an interrupt, 0 otherwise */
static int do_digital_input_interrupt(struct pci_dev * pdev)
{
	/* did input generated int. ? check the IRQ logic bit - reading it acknowledges interrupt */
	{
		uint32_t irq_logic = inl(GET_BAR3(pdev)+APCIE1564_DIGITAL_INPUT_INTERRUPT_LOGIC_REG);
		if (! (irq_logic & 0x1) )
			return 0;
	}
	/* read input status - save as event */
	{
		apcie1564_event_t evt;
		evt.source = APCIE1564_EVT_FROM_DIGITAL_INPUT;
		evt.param1 = (inl(GET_BAR3(pdev)+APCIE1564_DIGITAL_INPUT_INTERRUPT_STATUS_REG));
				
		/* save event in FIFO */
		{
			unsigned long irqstate;
			
			APCIE1564_LOCK(pdev,&irqstate);
			{
				/* call user callback */
				if (APCIE1564_PRIVDATA(pdev)->user_interrupt_handler)
					APCIE1564_PRIVDATA(pdev)->user_interrupt_handler(pdev,&evt);
				apcie1564_evt_add(pdev,&evt,1);
			}
			APCIE1564_UNLOCK(pdev,irqstate);

		}
	}
	return 1;
} 

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

/* return 1 if the counter generated an interrupt, 0 otherwise */
static inline int DO_COUNTER_INTERRUPT(struct pci_dev * pdev, long __counter)
{
	/* did input generated int. ? check the IRQ logic bit - reading it acknowledges interrupt */
	if( ! tcw_get_interrupt_status( COUNTER_BASE_ADDR(pdev,__counter) ) )
	{
		return 0;
	}
	/* generate event - call user interrupt call back */
	{
		apcie1564_event_t evt;
		evt.source = APCIE1564_EVT_FROM_COUNTER;
		evt.param1 = __counter; /* save the index of the counter */

		/* save event in FIFO */
		{
			unsigned long irqstate;
			APCIE1564_LOCK(pdev,&irqstate);
			{
				if (APCIE1564_PRIVDATA(pdev)->user_interrupt_handler)
					APCIE1564_PRIVDATA(pdev)->user_interrupt_handler(pdev,&evt);
				
				apcie1564_evt_add(pdev,&evt,1);
			}
			APCIE1564_UNLOCK(pdev,irqstate);

		}
		
	}
	return 1;

}

//------------------------------------------------------------------------------
/* return 1 if one counter has generated an interrupt, 0 otherwise */
static int do_counter_interrupt(struct pci_dev * pdev)
{
	/* for each counter, get the interrupt status and generate an event if needed */
	return DO_COUNTER_INTERRUPT(pdev,0) | DO_COUNTER_INTERRUPT(pdev,1) | DO_COUNTER_INTERRUPT(pdev,2); 
}

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

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	/* For kernel 2.4 */
	static void apcie1564_do_interrupt(int irq, void * dev_id, struct pt_regs *regs)
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
	static irqreturn_t apcie1564_do_interrupt(int irq, void * dev_id, struct pt_regs *regs)
#else
	
	/* For kernel >= 2.6.19 */	
	static irqreturn_t apcie1564_do_interrupt(int irq, void * dev_id)
#endif
{	
	/* The board may have several interrupt sources (digital input, counter, ... )
	 * This code calls interrupt handler for each source 
	 */
	register struct pci_dev * pdev = dev_id;
	register unsigned int has_interrupted = do_digital_input_interrupt( pdev ) | do_counter_interrupt(pdev);
	
	if (has_interrupted)
	{
		/* the driver generated an interruption, wakeup processes waiting for an event */
		apcie1564_evt_wakeup(pdev);
		RETURN_HANDLED;
	}
	else
		RETURN_NONE;
}

//------------------------------------------------------------------------------
int apcie1564_register_interrupt(struct pci_dev * pdev)
{
	uint32_t tmp = 0;

	/* Enable the interrupt on the PCI-Express controller */
	tmp = inl(GET_BAR1(pdev) + 0x68);
	outl((tmp | (1<<11) | (1 << 8)), GET_BAR1(pdev) + 0x68);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
	if ( request_irq( pdev->irq, apcie1564_do_interrupt, SA_SHIRQ, __DRIVER_NAME, pdev) )
#else
	if ( request_irq( pdev->irq, apcie1564_do_interrupt, IRQF_SHARED, __DRIVER_NAME, pdev) )
#endif
	{
		printk(KERN_ERR "%s: can't register interrupt handler\n", __DRIVER_NAME );
		return -EBUSY;
	}
	
	/*board specific interruption initialisation*/
	{
		//FILLME
	}
	
	return 0;
}
//------------------------------------------------------------------------------
int apcie1564_deregister_interrupt(struct pci_dev * pdev)
{
	free_irq( pdev->irq , pdev);
	return 0;
}
//------------------------------------------------------------------------------
int APCIE1564_SetInterruptCallback(struct pci_dev *pdev, APCIE1564_user_interrupt_t user_interrupt_handler  )
{
	if ( !pdev)
		return -EINVAL;

	if ( !user_interrupt_handler )
		return -EINVAL;

	/* is an interrupt already registered */
	if ( APCIE1564_PRIVDATA(pdev)->user_interrupt_handler )
		return -EBUSY;

	APCIE1564_PRIVDATA(pdev)->user_interrupt_handler = user_interrupt_handler;
	return 0;
}
//------------------------------------------------------------------------------
int APCIE1564_ClearInterruptCallback(struct pci_dev *pdev)
{
	if ( !pdev)
		return -EINVAL;

	APCIE1564_PRIVDATA(pdev)->user_interrupt_handler = NULL;
	return 0;
}

