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

EXPORT_NO_SYMBOLS;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,27)
#define __user 
#endif

/** set diagnostic interrupt enable/disable */
static __inline__ void WRITE_INTERRUPT_CONTROL(struct pci_dev * pdev, uint32_t val )
{
	outl(  val , GET_BAR1(pdev) + APCI2032_INTERRUPT_CONTROL_REG);
}

static __inline__ uint32_t READ_INTERRUPT_CONTROL(struct pci_dev * pdev)
{
	return inl((GET_BAR1(pdev) + APCI2032_INTERRUPT_CONTROL_REG));
}

/** return the flags of the diagnostic interruptions */
static __inline__ int GET_INTERRUPT_STATUS(struct pci_dev * pdev )
{
	return ( inl( GET_BAR1(pdev) + APCI2032_INTERRUPT_STATUS_REG) & 0x03 );
}

/** has the card actually generated an interrupt ? */
static __inline__ int HAS_INTERRUPTED(struct pci_dev * pdev )
{
	return ( inl( GET_BAR1(pdev) + APCI2032_IRQ_STATUS_REG) & 0x01 );
}

/** acknowledge interrupt by unsetting the corresponding bit */
static __inline__ void ACK_INTERRUPT(struct pci_dev * pdev)
{
	outl( 0x0, GET_BAR1(pdev) + APCI2032_IRQ_STATUS_REG);
}

//------------------------------------------------------------------------------
/** 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 apci2032_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 apci2032_do_interrupt(int irq, void * dev_id, struct pt_regs *regs)
#else /* 2.6 */
	static irqreturn_t apci2032_do_interrupt(int irq, void * dev_id)
#endif
{	
	/* board management */
	{
		unsigned long irqstate;
		APCI2032_LOCK(VOID_TO_PCIDEV(dev_id), &irqstate);
		{
			
			/* 1) is the interrupt generated by the card ? (remember, this is shared IRQ) */		
			if ( !HAS_INTERRUPTED(VOID_TO_PCIDEV(dev_id)) )
			{
				APCI2032_UNLOCK(VOID_TO_PCIDEV(dev_id), irqstate);
				RETURN_NONE;
			}

			{
				volatile uint32_t statreg;
				
				/* 2) get status of interrupt */
				statreg = GET_INTERRUPT_STATUS(VOID_TO_PCIDEV(dev_id));
				
				/* if status is zero (no cause given), it may that the board 
				 * interrupts has been cleared between the interrupt has been raised 
				 * and this point in the handler. In any case, warn and give up */
				if (statreg)
				{
					printk("%s: spurious interrupt (board gives no cause)\n",__DRIVER_NAME);
					ACK_INTERRUPT(VOID_TO_PCIDEV(dev_id));
					APCI2032_UNLOCK(VOID_TO_PCIDEV(dev_id), irqstate);
					RETURN_HANDLED;
				}
				//printk("%s: interrupt from %d\n",__DRIVER_NAME,statreg);				
				/* 3) memorize status */
				APCI2032_PRIVDATA(VOID_TO_PCIDEV(dev_id))->DigitalOutputInterruptStatus |= statreg;
	
				/* 4) clear the bits of the source of the interrupt */
				{
					volatile uint32_t ctrlreg = READ_INTERRUPT_CONTROL(VOID_TO_PCIDEV(dev_id));
					ctrlreg = ctrlreg & ( (~statreg) & 0x3);
					WRITE_INTERRUPT_CONTROL(VOID_TO_PCIDEV(dev_id),ctrlreg);
				}			
			}
	
			/* 5) acknowledge interrupt */
			ACK_INTERRUPT(VOID_TO_PCIDEV(dev_id));
	
		}
		APCI2032_UNLOCK(VOID_TO_PCIDEV(dev_id), irqstate);
	}
	/* Asynchronous IO signal */
	{
		if (APCI2032_PRIVDATA(VOID_TO_PCIDEV(dev_id))->async_queue)
		{		
			kill_fasync( &(APCI2032_PRIVDATA(VOID_TO_PCIDEV(dev_id))->async_queue), SIGIO, POLL_IN);
		}		
	}
	RETURN_HANDLED;
}

//------------------------------------------------------------------------------
int do_CMD_APCI2032_GetDigitalOutputInterruptStatus( struct pci_dev * pdev, unsigned int __user * arg)
{
	if (copy_to_user(arg, (const unsigned int *) & (APCI2032_PRIVDATA(pdev)->DigitalOutputInterruptStatus ), sizeof(unsigned int)  ) ) 
		return -EFAULT;
	return 0;
}
//------------------------------------------------------------------------------
int do_CMD_APCI2032_ReleaseDigitalOutputInterrupt(struct pci_dev * pdev)
{
	unsigned long irqstate;
	APCI2032_LOCK(pdev, &irqstate);
	{
		/* clear all interrupt control bit */
		WRITE_INTERRUPT_CONTROL(pdev,0x0000);
		/* acknowledge any pending interrupt */
		ACK_INTERRUPT(pdev);
	}
	APCI2032_UNLOCK(pdev, irqstate);
	return 0;
}
//------------------------------------------------------------------------------
int do_CMD_APCI2032_EnableDisableDigitalOutputInterrupt(struct pci_dev * pdev, unsigned int arg)
{
	if (!arg)
	{
		return -EINVAL;
	}
	if (arg > 0x3)
	{
		return -EINVAL;
	}
	{
		unsigned long irqstate;
		APCI2032_LOCK(pdev, &irqstate);
		{
			/* clear correspondig bit in DigitalOutputInterruptStatus */
			APCI2032_PRIVDATA(pdev)->DigitalOutputInterruptStatus &= (~arg);
			
			/* set new status */
			WRITE_INTERRUPT_CONTROL(pdev,arg);
		}
		APCI2032_UNLOCK(pdev,irqstate);	
	}
	return 0;
}
//------------------------------------------------------------------------------	        	
//------------------------------------------------------------------------------
int apci2032_register_interrupt(struct pci_dev * pdev)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
	if ( request_irq( pdev->irq, apci2032_do_interrupt, SA_SHIRQ, __DRIVER_NAME, pdev) )
#else
	if ( request_irq( pdev->irq, apci2032_do_interrupt, IRQF_SHARED, __DRIVER_NAME, pdev) )
#endif
	{
		printk(KERN_ERR "%s: can't register interrupt handler\n", __DRIVER_NAME );
		return -EBUSY;
	}
	return 0;
}
//------------------------------------------------------------------------------
int apci2032_deregister_interrupt(struct pci_dev * pdev)
{
	free_irq( pdev->irq , pdev);
	return 0;
}
