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

/** @mainpage Information about the APCI-3200 Linux driver.
*
* @section intro_sec Introduction
*
* This documentation is about the APCI-3200 Linux driver.
* With this driver it is possible to:
* - Read 1 analog input channel.
* - Read 1 temperature input channel (Thermocouple).
* - Read all digital inputs.
* - Set all digital outputs.
* - Get eeprom calibration values.
* - Convert the analog temperature value into degree Celsius.
*
*/



#include "apci3200-private.h"
#include "vtable.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#include <linux/sysfs.h>
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
static struct class * apci3200_class = NULL;
#endif

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ADDI-DATA GmbH <info@addi-data.com>");
MODULE_DESCRIPTION("APCI-3200 IOCTL driver");

/**@def EXPORT_SYMBOL
* Export apci3200_do_ioctl function.
*/
EXPORT_SYMBOL(apci3200_do_ioctl);

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

#ifndef __devinit
#define __devinit
#define __devexit
#define __devinitdata
#define __devexit_p
#endif

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

atomic_t apci3200_count = ATOMIC_INIT(0);
unsigned int apci3200_majornumber = 0;

//------------------------------------------------------------------------------
static int __devinit apci3200_probe_one(struct pci_dev *dev,const struct pci_device_id *ent);
static void __devexit apci3200_remove_one(struct pci_dev *dev);

//------------------------------------------------------------------------------
/** The ID table is an array of struct pci_device_id ending with a all-zero entry.
Each entry consists of:

- vendor, device	Vendor and device ID to match (or PCI_ANY_ID)
- subvendor,	Subsystem vendor and device ID to match (or PCI_ANY_ID)
- subdevice class,		Device class to match. The class_mask tells which bits
- class_mask	of the class are honored during the comparison.
- driver_data	Data private to the driver.
*/

static struct pci_device_id apci3200_pci_tbl[] __devinitdata = {
		{
				APCI3200_BOARD_VENDOR_ID,
				APCI3200_BOARD_DEVICE_ID,
				PCI_ANY_ID,
				PCI_ANY_ID,
				0,
				0,
				0
		},

		{ 0 },	/* terminate list */
};

MODULE_DEVICE_TABLE (pci, apci3200_pci_tbl);

//----------------------------------------------------------------------------------------------------------------------
static struct pci_driver apci3200_pci_driver = {
		.name		= __DRIVER_NAME,
		.probe		= apci3200_probe_one,
		.remove		= __devexit_p(apci3200_remove_one),
		.id_table	= apci3200_pci_tbl,
};
//------------------------------------------------------------------------------
static struct file_operations apci3200_fops =
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36)
        .ioctl                          = apci3200_ioctl_lookup,
#else
        .unlocked_ioctl         = apci3200_ioctl_lookup,
#endif
		.open		= apci3200_open_lookup,
		.release	= apci3200_release_lookup,
};

//------------------------------------------------------------------------------
/** when module is unloaded, stop all board activities (cf interrupt)*/
static void apci3200_stop_board(struct pci_dev * pdev)
{
	// FILL THE BLANK
}

//-------------------------------------------------------------------
/** event: a new card is detected.
*
* Historically - in former ADDI-DATA drivers - data about a board has been stored
* in a static structure.
* This led to huge duplication of information since most of these information
* are already present in the pci_dev struct.
*
* Now we manipulate this standard structure provided by the OS and we attach
* private data using the field driver_data, if necessary.
*
*  */
static int __devinit apci3200_probe_one(struct pci_dev *dev, const struct pci_device_id *ent)
{
	int ret = 0;
	{
		ret = pci_enable_device(dev);
		if (ret)
		{
			printk(KERN_ERR "%s: pci_enable_device failed\n",__DRIVER_NAME);
			return ret;
		}
	}

	/* 2.4 only : check_region is deprecated in 2.6 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	/* check if BAR 0 is free */
	if ( check_region(dev->resource[0].start, pci_resource_len(dev,0)) )
	{
		printk(KERN_WARNING "%s: BAR 0 (%lX) is already in use\n",__DRIVER_NAME,dev->resource[0].start);
		ret = -EBUSY;
		goto error_disable_device;
	}
	/* check if BAR 1 is free */
	if ( check_region(dev->resource[1].start, pci_resource_len(dev,1)) )
	{
		printk(KERN_WARNING "%s: BAR 1 (%lX) is already in use\n",__DRIVER_NAME,dev->resource[1].start);
		ret = -EBUSY;
		goto error_disable_device;
	}
#endif // 2.4

	/* allocate a new data structure containing board private data */
	{
		struct apci3200_str_BoardInformations * newboard_data = NULL;
		newboard_data = kmalloc( sizeof( struct apci3200_str_BoardInformations) , GFP_ATOMIC);
		if (!newboard_data)
		{
			printk(KERN_CRIT "Can't allocate memory for new board %s\n",pci_name(dev));
			ret = -ENOMEM;
			goto error_disable_device;
		}
		/* link standard data structure to the board's private one */
		pci_set_drvdata(dev,newboard_data);
		apci3200_init_priv_data(newboard_data);
	}

	/* lock BAR IO ports ressources */
	{
		if (pci_request_regions(dev,__DRIVER_NAME))
		{
			printk(KERN_ERR "%s: pci_request_regions failed\n",__DRIVER_NAME);
			ret = -EBUSY;
			/* free all allocated ressources here*/
			goto error_free_privdata;
		}
	}

	/* register interrupt */
	if ( apci3200_register_interrupt(dev) )
	{
		/* failed, clean previously allocated resources */
		ret = -EBUSY;
		goto error_free_regions;
	}

	{
		/* increase the global board count */
		atomic_inc(&apci3200_count);
		APCI3200_PRIVDATA(dev)->minor = (atomic_read(&apci3200_count)-1);
		printk(KERN_INFO "%s: board %s managed (minor number is %d)\n",__DRIVER_NAME, pci_name(dev), APCI3200_PRIVDATA(dev)->minor);
	}

	/* udev */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
		struct class_device *cdev;
#else
		struct device *cdev;
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
		cdev = class_device_create (apci3200_class, NULL, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor), NULL, "%s_%d", __DRIVER_NAME, APCI3200_PRIVDATA(dev)->minor);
#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
		cdev = device_create (apci3200_class, NULL, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor), "%s_%d", __DRIVER_NAME, APCI3200_PRIVDATA(dev)->minor);
#else
		cdev = device_create (apci3200_class, NULL, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor), NULL, "%s_%d", __DRIVER_NAME, APCI3200_PRIVDATA(dev)->minor);
#endif

		if (IS_ERR(cdev))
		{
			printk (KERN_WARNING "%s: class_device_create error\n", __DRIVER_NAME);
			goto error_deregister_interrupt;
		}
	}
#endif

	/* create /proc entry */
	apci3200_proc_create_device(dev, atomic_read(&apci3200_count)-1);

	/* Initialize the APCI-3200
	 * - Wait state (very important!)
	 */
	{
		switch(i_APCI3200_InitAndReadHeader(dev, __DRIVER_NAME, APCI3200_PRIVDATA(dev)->minor))
		{
		case -1:
		ret = -EIO;
		goto error_free_proc;
		case -2:
		printk(KERN_CRIT "%s: board %d: header is incorrect\n",__DRIVER_NAME, APCI3200_PRIVDATA(dev)->minor);
		ret = -ENODEV;
		goto error_free_proc;
		case 0:
			break;
		}
	}

	return 0;

	error_free_proc:
	apci3200_proc_release_device(dev);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
	class_device_destroy (apci3200_class, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor));
#else
	device_destroy (apci3200_class, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor));
#endif
	error_deregister_interrupt:
#endif
	apci3200_deregister_interrupt(dev);
	error_free_regions:
	pci_release_regions(dev);
	error_free_privdata:
	kfree(APCI3200_PRIVDATA(dev));
	error_disable_device:
	pci_disable_device(dev);
	return ret;
}


//------------------------------------------------------------------------------
/** This function registers the driver with register_chrdev.
*
* @todo For kernel 2.6, use udev
*  */

static int apci3200_register(void)
{
	//Registration of driver
	apci3200_majornumber = register_chrdev(0, __DRIVER_NAME, &apci3200_fops);

	if (apci3200_majornumber < 0)
	{
		printk (KERN_ERR "%s: register_chrdev returned %d ... aborting\n",__DRIVER_NAME,apci3200_majornumber);
		return -ENODEV;
	}
	return 0;
}
//------------------------------------------------------------------------------
/** This function unregisters the driver with unregister_chrdev.
*
* @todo For kernel 2.6, use udev
*  */

static void apci3200_unregister(void)
{
	unregister_chrdev(apci3200_majornumber, __DRIVER_NAME);
}
//------------------------------------------------------------------------------
/** Called when module loads. */
static int __init apci3200_init(void)
{
	int ret = 0;
	/* 2.4 only : check_region is deprecated in 2.6 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	/* Detection if an PCI Bus is present */
	if(!pci_present())
	{
		printk(KERN_WARNING "%s: NO PCI BUS.\n", __DRIVER_NAME);
		return -ENODEV;
	}
#endif // 2.4

	apci3200_init_vtable(apci3200_vtable);

	if( apci3200_register() )
	{
		return -ENODEV;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	apci3200_class = ADDI_CLASS_CREATE(THIS_MODULE, __DRIVER_NAME);
	if (IS_ERR(apci3200_class))
	{
		printk (KERN_WARNING "%s: class_create error\n",__DRIVER_NAME );
		ret = -EIO;
		goto error_unregister;
	}
#endif

	/* registred, now create root /proc entry */
	apci3200_proc_init();

	printk(KERN_INFO "%s: loaded\n",__DRIVER_NAME);

	/* now, subscribe to PCI bus subsystem  */
	/** @note The module will load anyway even if the call to pci_module_init() fails */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
	if ( pci_module_init (&apci3200_pci_driver) )
#else
	if ( pci_register_driver (&apci3200_pci_driver) )
#endif
		{
			printk("%s: no device found\n",__DRIVER_NAME);
			ret = -ENODEV;
			goto error_proc_release;
		}

	return 0;

	error_proc_release:
	apci3200_proc_release();
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	error_unregister:
#endif
	apci3200_unregister();
	return ret;
}

//-------------------------------------------------------------------
/** event: a card is removed (also called when module is unloaded) */
static void __devexit apci3200_remove_one(struct pci_dev *dev)
{

	/* stop board activities */
	apci3200_stop_board(dev);

	/* register interrupt */
	apci3200_deregister_interrupt(dev);

	/* do OS-dependant thing we don't really want to know of :) */
	pci_disable_device(dev);

	/* deallocate BAR IO Ports ressources */
	pci_release_regions(dev);

	/* free private device data (uncomment) */
	/* kfree(APCI3200_PRIVDATA(dev)); */

	/* delete associated /proc entry */
	apci3200_proc_release_device(dev);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
	class_device_destroy (apci3200_class, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor));
#else
	device_destroy (apci3200_class, MKDEV(apci3200_majornumber, APCI3200_PRIVDATA(dev)->minor));
#endif

#endif

	/* decrement global board count*/
	atomic_dec(&apci3200_count);

	printk("%s: board %s unloaded\n",__DRIVER_NAME,pci_name(dev));

}

//-------------------------------------------------------------------
/** Called when module is unloaded. */
static void __exit apci3200_exit(void)
{

	/* unsubscribe to PCI bus subsystem */
	if (apci3200_pci_driver.name[0])
	{
		pci_unregister_driver (&apci3200_pci_driver);
	}

	/* unsubscribe to /dev VFS */
	apci3200_unregister();

	/* delete /proc root */
	apci3200_proc_release();

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	/* don't execute if class not exists */
	if ( !IS_ERR(apci3200_class) )
		class_destroy (apci3200_class);
#endif
}
//------------------------------------------------------------------------------
module_exit(apci3200_exit);
module_init(apci3200_init);
