/* PCI-E ECAT series device driver.

   Author: Golden Wang

   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, 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 should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

/* File level history (record changes for this file here.)

  v 0.0.0 20 Dec 2016 by Golden Wang
    Create. */

/* *INDENT-OFF* */
#define IXECAT_RESERVED 0		/* 1 do compilation with reserved codes */

/* Mandatory */
#include <linux/kernel.h>		/* ta, kernel work */
#include <linux/module.h>		/* is a module */
#include "ixecat.h"

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

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
        #include <linux/devfs_fs_kernel.h>
        #include <linux/init.h>
#endif

/* Deal with CONFIG_MODVERSIONS that is defined in
   /usr/include/linux/config.h (config.h is included by module.h) */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
/* Deal with CONFIG_MODVERSIONS that is defined in
   /usr/include/linux/config.h (config.h is included by module.h) */
        #if CONFIG_MODVERSIONS==1
        #define MODVERSIONS
        #include <linux/modversions.h>
        #endif
/* Additional */
// #include <linux/fs.h> /* file system here */
        #include <linux/wrapper.h>
#endif

/* using I/O ports */
#include <asm/io.h>
#include <linux/ioport.h>

/* need kmalloc */
#include <linux/slab.h>

#include <linux/proc_fs.h>

/* Local matter */

#define MODULE_NAME "ixecat"

#ifdef MODULE_LICENSE
MODULE_AUTHOR("Golden Wang <golden_wang@icpdas.com>");
MODULE_DESCRIPTION("ICPDAS PCI-E ECAT series driver, common Interface");
MODULE_LICENSE(ICPDAS_LICENSE);
#endif

// EXPORT_NO_SYMBOLS; /* do not export symbols */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
EXPORT_SYMBOL_GPL(ixecat_dev);
EXPORT_SYMBOL_GPL(ixecat_major);
EXPORT_SYMBOL_GPL(ixecat_copy_devinfo);
#else
EXPORT_SYMBOL_NOVERS(ixecat_dev);
EXPORT_SYMBOL_NOVERS(ixecat_major);
EXPORT_SYMBOL_NOVERS(ixecat_copy_devinfo);
#endif


/* functions are declared here first */
static int ixecat_open(struct inode *, struct file *);
int ixecat_release(struct inode *, struct file *);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
long ixecat_ioctl(struct file *, unsigned int , unsigned long );
#else
int ixecat_ioctl(struct inode *, struct file *, unsigned int , unsigned long );
#endif

/* variables are declared here first */
//int ixecat_total_boards = 0;
/* (export) dynamic major number */
int ixecat_major = DEVICE_MAJOR;
int ixecat_minor = DEVICE_MINOR;
/* pointer to the PIO cards' list */
ixecat_devinfo_t *ixecat_dev;

/* device file operaitons information */
static struct file_operations fops = {
        /* kernel 2.6 prevent the module from unloading while there is a open file(kernel 2.4 use the funciton MOD_INC_USE_COUNT and MOD_DEC_USE_COUNT to protect module from unloading when someone is opening file), so driver writer must set the file_operation's field owner a value 'THIS_MODULE' */
# if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        owner:THIS_MODULE,
#endif
        open:ixecat_open,
        release:ixecat_release,

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
        unlocked_ioctl:ixecat_ioctl,
#else
        ioctl:ixecat_ioctl,
#endif
};

struct ixecat_carddef ixecat_card[] = {
	/* composed_id present module name */
	{PCI_ECAT_R3600, 0,   "ixpciecat", "PCI-ECAT-R3600"},
	{PCI_ECAT_GENERIC, 0, "ixpciecat", "PCI-ECAT-GENERIC"},
	{0, 0, "", "UNKNOW"},
};

void *_cardname(__u64 id, int add_present)
{
	/* Get card name by id
	 * 
	 * Arguments:
	 *     id     card id (see ixecat.h)
	 *     new    flag for a new card just be found
	 * Returned:
	 *     Pointer to the name string that coresponds the given id.
	 *     If there is no card name has been found, return 0. */

	int i = 0;

	while (ixecat_card[i].id) {
		if (ixecat_card[i].id == id) {
			if (add_present)
				++(ixecat_card[i].present);
			/* yeh, present, check in */
			return ixecat_card[i].name;
		}
		++i;
	}
	return 0;
}

__inline__ void *_pci_cardname(__u64 id)
{
	return _cardname(id, 0);
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0)
static int ixecat_board_init (ixecat_devinfo_t *dev, int index)
#else
static int __devinit ixecat_board_init (ixecat_devinfo_t *dev, int index)
#endif
{
  int err, devno;
  devno = MKDEV(ixecat_major, ixecat_minor + index);
  dev->cdev = kmalloc(sizeof(struct cdev), GFP_KERNEL);
  memset(dev->cdev, 0, sizeof(struct cdev));
  cdev_init(dev->cdev, &fops);

  dev->cdev->owner = THIS_MODULE;
  dev->cdev->ops = &fops;

  err = cdev_add(dev->cdev, devno, 1);

  if (err)
  {
    printk("Error %d adding ixecat\n", err);
    return FAILURE;
  }

  return SUCCESS;
}
#endif

void _del_dev(void)
{
	/* Release memory from card list
	 *
	 * Arguments: none
	 *
	 * Returned:  void */

	ixecat_devinfo_t *dev, *prev;

	dev = ixecat_dev;
	while (dev) {
		prev = dev;
		dev = dev->next;
		kfree(prev);
	}
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
ixecat_devinfo_t *_add_dev(unsigned int no, unsigned int cno, __u64 id, char *name,
						  struct pci_dev *sdev)
#else
ixecat_devinfo_t *_add_dev(unsigned int no, __u64 id, char *name,
                                                  struct pci_dev *sdev)
#endif
{
	/* Add the found card to list
	 *
	 * Arguments:
	 *     no      device number in list
	 *     id      card id (see ixecat.h)
	 *     name    card name
	 *     sdev    pointer to the device information from system
	 *
	 * Returned:   The added device */

	int i;
	ixecat_devinfo_t *dev, *prev, *prev_f;

	if (ixecat_dev) {			/* ixecat device list is already followed */
		if (ixecat_dev->id == id)
			prev_f = ixecat_dev;
		else
			prev_f = 0;
		prev = ixecat_dev;
		dev = prev->next;
		while (dev) {			/* seek the tail of list */
			if (dev->id == id) {	/* last device in family */
				prev_f = dev;
			}
			prev = dev;
			dev = dev->next;
		}
		dev = prev->next = kmalloc(sizeof(ixecat_devinfo_t), GFP_KERNEL);
		//ixecat_memset(dev, 0, sizeof(ixecat_devinfo_t));
		memset(dev, 0, sizeof(ixecat_devinfo_t));
		dev->prev = prev;		/* member the previous address */
		if (prev_f) {
			dev->prev_f = prev_f;	/* member the previous family
									   device */
			prev_f->next_f = dev;	/* member the next family device */
		}
	} else {					/* ixecat device list is empty, initiate */
		ixecat_dev = kmalloc(sizeof(ixecat_devinfo_t), GFP_KERNEL);
		//ixecat_memset(ixecat_dev, 0, sizeof(ixecat_devinfo_t));
		memset(ixecat_dev, 0, sizeof(ixecat_devinfo_t));
		dev = ixecat_dev;
	}
	dev->cno = cno;
	dev->no = no;
	dev->id = id;
	dev->irq = sdev->irq;

	for (i = 0; i < PBAN; i++) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
		dev->base[i] = pci_resource_start(sdev, i);

/*
		if(pci_resource_flags(sdev, i) & IORESOURCE_MEM)
		{
			printk("BAR %d --> 0x%x\n", i, dev->base[i]);
		}
		else
		{
			printk("no memory map\n");
		}
*/
#else
		dev->base[i] = sdev->base_address[i] & PCI_BASE_ADDRESS_IO_MASK;
#endif							/* LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0) */
		dev->range[i] = pci_resource_len(sdev, i);
	}

	strncpy(dev->name, name, CNL);
	return dev;
}

int _find_dev(void)
{
	/* Find all devices (cards) in this system.
	 *
	 * Arguments: none
	 *
	 * Returned: The number of found devices */

	unsigned int i = 0;
	unsigned int dev_no = 0, card_no = 0;
	unsigned int j;
	__u64 id;
	unsigned int vid, did, svid, sdid;
	char *name;
	struct pci_dev *sdev;
	ixecat_devinfo_t *dev;

	dev = NULL;
	sdev = NULL;

	for (; (id = ixecat_card[i].id) != 0; ++i)
	{
		vid = IXECAT_VENDOR(id);
		did = IXECAT_DEVICE(id);
		svid = IXECAT_SUBVENDOR(id);
		sdid = IXECAT_SUBDEVICE(id);
	
		#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
		while ((sdev = pci_find_subsys(vid, did, PCI_ANY_ID, PCI_ANY_ID, sdev)))
		#else
		while ((sdev = pci_get_subsys(vid, did, PCI_ANY_ID, PCI_ANY_ID, sdev)))
		#endif
		{
			if (svid && svid != sdev->subsystem_vendor) continue;
			if (sdid && sdid != sdev->subsystem_device) continue;
		
			#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
			if (pci_enable_device(sdev))
			{
				printk("Error : enable pci fail\n");
				return FAILURE;
			}
			#endif
		 
			++(ixecat_card[i].present);
			name = ixecat_card[i].name;
			++dev_no;

			if ( card_no )
			{
				card_no++;
			}
			else
			{
				card_no = dev_no;
			}	

			dev = _add_dev(dev_no, card_no, id, name, sdev);
	
			if (dev_no == 1)
			{
				KMSG("NO  PCI_ID____________  IRQ  BASE______  NAME...\n");
			}
	 
			KMSG("%2d  0x%04x%04x%04x%04x  %3d  0x%08lx  %s\n", dev->cno, vid, did, svid, sdid, dev->irq, dev->base[0], name);
 		  
			for (j = 1; (j < PBAN) && (dev->base[j] != 0); ++j)
			{
				KMSG("                             0x%08lx\n", dev->base[j]);
			}
		  
			#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
			ixecat_board_init(dev, dev_no);
			#endif
		}
	}

	//set how many ixecat board in PC

	return dev_no;
}

void ixecat_copy_devinfo(ixecat_devinfo_t * dst, ixecat_devinfo_t * src)
{
	int i;

	dst->no = src->no;
	dst->id = src->id;
	for (i = 0; i < PBAN; i++) {
		dst->base[i] = src->base[i];
	}
	strncpy(dst->name, src->name, CNL);
}

ixecat_devinfo_t *_find_minor(int minor)
{
	ixecat_devinfo_t *dp;
	for (dp = ixecat_dev; dp && dp->no != minor; dp = dp->next);
	return dp;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,36)
long ixecat_ioctl(struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
#else
int ixecat_ioctl(struct inode *inode, struct file *file, unsigned int ioctl_num, unsigned long ioctl_param)
#endif
{
	/* This function is called whenever a process tries to do and IO
	 * control on IXECAT device file
	 *
	 * Arguments: read <linux/fs.h> for (*ioctl) of struct file_operations
	 *
	 * Returned:  error code */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        return SUCCESS;
#else
	ixecat_devinfo_t *dp;

	dp = _find_minor(MINOR(inode->i_rdev));

	if (!dp || !dp->fops || !dp->fops->ioctl) return -EINVAL;

	return (dp->fops->ioctl) (inode, file, ioctl_num, ioctl_param);
#endif
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
int ixecat_release(struct inode *inode, struct file *file)
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static int ixecat_release(struct inode *inode, struct file *file)
#else
static void ixecat_release(struct inode *inode, struct file *file)
#endif
{
	/* This function is called whenever a process attempts to closes the
	 * device file. It doesn't have a return value in kernel version 2.0.x
	 * because it can't fail (you must always be able to close a device).
	 * In version 2.2.x it is allowed to fail.
	 *
	 * Arguments: read <linux/fs.h> for (*release) of struct file_operations
	 *
	 * Returned:  version dependence */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        return SUCCESS;
#else
	ixecat_devinfo_t *dp;

	dp = _find_minor(MINOR(inode->i_rdev));

	if (!dp || !dp->fops || !dp->fops->release)
	{
	  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
	  return -EINVAL;
	  #endif
	}
	else
	{
	  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
	  return (dp->fops->release) (inode, file);
	  #else
	  (dp->fops->release) (inode, file);
	  #endif
	}
#endif
}

static int ixecat_open(struct inode *inode, struct file *file)
{
	/* This function is called whenever a process attempts to open the
	 * device file
	 *
	 * Arguments: read <linux/fs.h> for (*open) of struct file_operations
	 *
	 * Returned:  error code */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        return SUCCESS;
#else
	ixecat_devinfo_t *dp;

	dp = _find_minor(MINOR(inode->i_rdev));

	if (!dp || !dp->fops || !dp->fops->open) return -EINVAL;

	return (dp->fops->open) (inode, file);
#endif
}

void ixecat_cleanup(void)
{
	/* cleanup this module */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        dev_t devno = MKDEV(ixecat_major, ixecat_minor);
#else
        int unr;
#endif

	/* remove /proc/ixecat */
	ixecat_proc_exit();

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        unregister_chrdev_region(devno, DEVICE_NR_DEVS);
#else
	/* remove device file operations */
	unr = devfs_unregister_chrdev(ixecat_major, DEVICE_NAME);
	if (unr < 0) KMSG("%s devfs(module)_unregister_chrdev() error: %d\n", MODULE_NAME, unr);
#endif
	_del_dev();					/* release allocated memory */

	KMSG("%s has been removed\n", MODULE_NAME);
}

static int __init ixecat_init(void)
{
	/* initialize this module
	 *
	 * Arguments:
	 *
	 * Returned:  error code, 0 ok */

	unsigned found;
	char *comp;
	int err;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        dev_t devt = 0;

        err = alloc_chrdev_region(&devt, DEVICE_MINOR, DEVICE_NR_DEVS, DEVICE_NAME);
        if (err < 0)
        {
          printk(KERN_WARNING "ixecat: can't get major %d\n", ixecat_major);
        }
        else
        {
          ixecat_major = MAJOR(devt);
        }
#endif

	KMSG("Linux Driver %s(build at %s).", MODULE_NAME, __DATE__);

	found = _find_dev();
	if (found == FAILURE) {
		KMSG("_find_dev() failed!\n");
		return FAILURE;
	}
	if (found == 0) {
		KMSG("No device (card) found.\n");
		return FAILURE;
	}
	if (found == 1)
		comp = "";
	else
		comp = "s";
	KMSG("Total %d device%s (card%s) found.\n", found, comp, comp);

	/* register device file operations  */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
        ixecat_major = devfs_register_chrdev(DEVICE_MAJOR, DEVICE_NAME, &fops);
#endif
	if (ixecat_major < 0) {
		KMSG("major %d failed!\n", ixecat_major);
		_del_dev();				/* release allocated memory */
		return ixecat_major;
	}
	KMSG("major %d.\n", ixecat_major);

	/* setup proc entry */
	if ((err = ixecat_proc_init())) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
                KMSG("/proc/%s/%s %d failed!\n", FAMILY, DEVICE_NAME, err);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
		KMSG("%s/%s/%s %d failed!\n", proc_root.name, FAMILY,
			 DEVICE_NAME, err);
#else
		KMSG("%s/%s %d failed!\n", proc_root.name, DEVICE_NAME, err);
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
                unregister_chrdev_region(devt, DEVICE_NR_DEVS);
#else
                devfs_unregister_chrdev(ixecat_major, DEVICE_NAME);
#endif
		_del_dev();
		return err;
	}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
        KMSG("/proc/%s/%s %d.\n", FAMILY, DEVICE_NAME, err);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
	KMSG("%s/%s/%s %d.\n", proc_root.name, FAMILY, DEVICE_NAME, err);
#else
	KMSG("%s/%s %d.\n", proc_root.name, DEVICE_NAME, err);
#endif

	return SUCCESS;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
module_init(ixecat_init);
module_exit(ixecat_cleanup);
#endif  /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0) */
/* *INDENT-ON * */
