/***************************************************************************
 *
 *  Oxford Semiconductor Proprietary and Confidential Information
 *
 *  Copyright:      Oxford Semiconductor Ltd, 2006
 *
 *  Description:    GPIO test code.
 *
 ***************************************************************************/

#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/kobject.h>
#include <linux/version.h>

//MODULE_LICENSE("Dual BSD/GPL");

#include "Common.h"
#include "GpioIoctls.h"
#include "GpioCore.h"

#define GPIO_DEBUG 1
#include "gpio.h"

#define to_GpioDevice(d) container_of(d, GpioDevice, kobj)

//define a dummy valid handle
#define DUMMY_VALID_HANDLE 1

/**
 *  Driver name.
 */
char const DRIVER_NAME[] = "gpio";

int gpioMajorNumber;
int firstMinorNumber = 0;
int gpioDeviceCount = 0;

#define MAX_GPIO_DEVICES	256


//Dummy function
void
GpioRequestCancel(
    void ** blah)
{
};


unsigned char gpio_devices[MAX_GPIO_DEVICES];

/*----------------------------------------------------------------------
 * 
 * gpio_do_tasklet
 *
 * Tasklet for deferring work
 *
 * Arguments:
 *      unsigned long   - data
 *
 * Returns:
 *      void
 *----------------------------------------------------------------------
 */
void gpio_do_tasklet(unsigned long data)
{
    GpioDevice *    pGpioDevice = (GpioDevice *)data;
    
    PATH_TESTED();
    
    PRECONDITION(pGpioDevice->pGpioCore != NULL);

    GpioCoreDpcFunc(pGpioDevice->pGpioCore);
}
 /*----------------------------------------------------------------------
 * 
 * gpio_interrupt
 *
 * Interrupt handler for the device.
 *
 * Arguments:
 *      int                 - IRQ number
 *      void                - pointer to client data
 *
 *
 * Returns:
 *      irqreturn_t         - error
 *----------------------------------------------------------------------
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19)
// handler now takes only 2 arguments from 2.6.19 ....

irqreturn_t gpio_interrupt(int irq, void * dev_id)

#else
irqreturn_t gpio_interrupt(int irq, void * dev_id, struct pt_regs *regs)

#endif

{
    BOOLEAN bInterruptRecognized;
    GpioDevice * pGpioDevice = dev_id;
    
    PATH_TESTED();
    
    PRECONDITION(pGpioDevice->pGpioCore != NULL);
    
    bInterruptRecognized = GpioCoreIsr(pGpioDevice->pGpioCore);
    
    if (bInterruptRecognized)
    {
        PATH_TESTED();

        tasklet_schedule(&(pGpioDevice->osInterruptStruct.tasklet));
        return IRQ_HANDLED;
    }

    return IRQ_NONE;

}

/*-----------------------------------------------------------------------------
 * GPIO file_operations methods
 *-----------------------------------------------------------------------------
 */

/*-----------------------------------------------------------------------------
 *
 * gpio_open
 *
 * Check for device specific errors, initialise the device if it is being
 * opened for the first time, update the f_op pointer is necessary, and
 * allocate and fill any data structure to be put in filp->private_data.
 *
 * Arguments:
 *      struct inode    - pointer to inode
 *      struct file     - pointer to file
 *
 * Return:
 *      int             - error
 *
 *-----------------------------------------------------------------------------
 */
int gpio_open(struct inode *inode, struct file *filp)
{
    // device information
    GpioDevice * pGpioDevice;

    PATH_TESTED();
    PDEBUG( "Opening GPIO device\n" );

    pGpioDevice = container_of( inode->i_cdev, GpioDevice, cdev );

    // to make easier access for other methods
    filp->private_data = pGpioDevice;

    // success
    return 0;
}

/*-----------------------------------------------------------------------------
 *
 * gpio_close
 *
 * Deallocate anything that "gpio_open" allocated in filp->private_data. Shut
 * down the device on last close.
 *
 * Arguments:
 *      struct inode    - pointer to inode
 *      struct file     - pointer to file
 *
 * Return:
 *      int             - error
 *
 *-----------------------------------------------------------------------------
 */
int gpio_release(struct inode *inode, struct file *filp)
{
    PATH_TESTED();
    PDEBUG( "Closing GPIO device\n" );

    return 0;
}

/*-----------------------------------------------------------------------------
 *
 * gpio_ioctl
 *
 * Handles IO Control requests
 *
 * Arguments:
 *      struct inode    - pointer to inode
 *      struct file     - pointer to file
 *      unsigned int    - command
 *      unsigned long   - argument
 *
 * Return:
 *      int             - error
 *
 *-----------------------------------------------------------------------------
 */
int gpio_ioctl(struct inode *inode, 
               struct file *filp,
               unsigned int cmd, 
               unsigned long arg)
{
    int err = 0;
    int retval = 0;
    size_t returnedBytes = 0;
    unsigned long function;

    OsIoctlHandle       hIoctl;
    GpioDevice *        pGpioDevice;
    
    // initialise the Ioctl object
    hIoctl = kmalloc(sizeof(hIoctl), GFP_KERNEL);
    if (hIoctl == NULL)
    {
        PATH_NOT_YET_TESTED();
        PDEBUG("kmalloc has returned a NULL pointer\n");
        return -ENOMEM;
    }
    memset(hIoctl, 0, sizeof(hIoctl));

    hIoctl->pIoctlHandle = (IoctlHandle *)arg;
 
    
    PATH_TESTED();
    pGpioDevice = filp->private_data;
    
    // point to Ioctl waitqueue
    hIoctl->pWqIoctl = &(pGpioDevice->wqIoctl);
    
    PDEBUG( "Oxford GPIO driver IOCTL\n");
    PDEBUG( "input buffer length %ud, output buffer length %ud\n", 
            (unsigned int)hIoctl->pIoctlHandle->inputLength,
            (unsigned int)hIoctl->pIoctlHandle->outputLength);

    // Extract the type and number bitfields, and don't decode wrong cmds:
    // return ENOTTY (inappropriate ioctl) before access_ok ()
    if (_IOC_TYPE(cmd) != GPIO_IOC_MAGIC) 
    {
        PATH_NOT_YET_TESTED();
        return -ENOTTY;
    }

	// the direction is a bitmask, and VERIFY_WRITE catches R/W transfers.
    // `Type' is user-oriented, while access_ok is kernel-oriented, so the
    // concept of "read" and "write" is reversed
    if (_IOC_DIR(cmd) & _IOC_READ)
    {
        PATH_TESTED();
        err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)) ;
    }
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
	{
        PATH_TESTED();
        err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)) ;
    }

	if (err)
    {
        PATH_NOT_YET_TESTED();
        return -EFAULT;
    }

    function = _IOC_NR(cmd);
    
    // initialise input and output buffers for ioctl 
    hIoctl->pInputBuffer = NULL;
    hIoctl->pOutputBuffer = NULL;
    
    if (hIoctl->pIoctlHandle->inputLength != 0)
    {
        PATH_TESTED();
        
        if (hIoctl->pIoctlHandle->inputLength > 1024)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("input length greater than 1k\n");
            hIoctl->pIoctlHandle->inputLength = 1024;
        }
        
        hIoctl->pInputBuffer = kmalloc(hIoctl->pIoctlHandle->inputLength, GFP_KERNEL);
        if (hIoctl->pInputBuffer == NULL)
        {
            PATH_NOT_YET_TESTED();
            
            PDEBUG("kmalloc has returned a NULL pointer\n");
            // free all mem
            kfree(hIoctl);
            
            return -ENOMEM;
        }
        memset(hIoctl->pInputBuffer, 0, hIoctl->pIoctlHandle->inputLength);
        
        // copy user data to kernel space
        err = copy_from_user(hIoctl->pInputBuffer, 
                             hIoctl->pIoctlHandle->pInputBuffer,
                             hIoctl->pIoctlHandle->inputLength);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            // Couldn't copy all data from use space
            PDEBUG("Couldn't copy %d bytes from user space\n",
                   err);
            // free all memory
            kfree(hIoctl->pInputBuffer);
            kfree(hIoctl);
            return -EFAULT;
        }
    }
    
    if (hIoctl->pIoctlHandle->outputLength != 0)
    {
        PATH_TESTED();
        
        if (hIoctl->pIoctlHandle->outputLength > 1024)
        {
            PATH_NOT_YET_TESTED();
            
            PDEBUG("output length greater than 1k\n");
            hIoctl->pIoctlHandle->outputLength = 1024;
        }
        
        hIoctl->pOutputBuffer = kmalloc(hIoctl->pIoctlHandle->outputLength, GFP_KERNEL);
        if (hIoctl->pOutputBuffer == NULL)
        {
            PATH_NOT_YET_TESTED();
            
            PDEBUG("kmalloc has returned a NULL pointer\n");
            
            // free all memory
            if (hIoctl->pIoctlHandle->inputLength != 0)
            {
                kfree(hIoctl->pInputBuffer);
            }
            kfree(hIoctl);

            return -ENOMEM;
        }
        memset(hIoctl->pOutputBuffer, 0, hIoctl->pIoctlHandle->outputLength);
    }
    
    if (   (function >= 0)
        && (function < NUMBER_OF_IO_CONTROL_CODES))
    {
        PATH_TESTED();
        
        err = GpioCoreIoctlDispatch(
                pGpioDevice->pGpioCore,
                hIoctl,
                function,
                hIoctl->pInputBuffer,
                hIoctl->pIoctlHandle->inputLength,
                hIoctl->pOutputBuffer,
                hIoctl->pIoctlHandle->outputLength,
                &returnedBytes);

	if (err == OS_STATUS__INVALID_PARAMETER)
	{
		//an invalid parameter passed to dispatch function
		//for example an invalid pin number...
		return -EINVAL;
	}
                
        // If ioctl is pending then block
        if (hIoctl->osStatus == OS_STATUS__PENDING)
        {
            PATH_TESTED();
            if(wait_event_interruptible(*(hIoctl->pWqIoctl), 
                                      hIoctl->osStatus != OS_STATUS__PENDING))
            {
                // got a signal while blocking on a event (e.g. CTRL C)                
                return -ERESTARTSYS;
            }
        }
    }

    // free input and output buffers from ioctl if possible
    if (hIoctl->pIoctlHandle->outputLength != 0)
    {
        PATH_TESTED();
        
        // copy data to user space
        err = copy_to_user(hIoctl->pIoctlHandle->pOutputBuffer, 
                           hIoctl->pOutputBuffer,
                           hIoctl->pIoctlHandle->outputLength);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            
            // Couldn't copy all data from use space
            PDEBUG("Couldn't copy %d bytes to user space\n",
                   err);
                   
            if (hIoctl->pIoctlHandle->inputLength != 0)
            {
                kfree(hIoctl->pInputBuffer);
            }
            kfree(hIoctl->pOutputBuffer);
            kfree(hIoctl);
            
            return -EFAULT;
        }
        kfree(hIoctl->pOutputBuffer);                               
    }
    
    if (hIoctl->pIoctlHandle->inputLength != 0)
    {
        PATH_TESTED();
        
        kfree(hIoctl->pInputBuffer);
    }
    
    kfree(hIoctl);

	return retval;
}


/*-----------------------------------------------------------------------------
 * Set the GPIO file operations
 *-----------------------------------------------------------------------------
 */
struct file_operations gpio_operations = {
    .owner =        THIS_MODULE,
    .open =         gpio_open,
    .release =      gpio_release,
    .ioctl =        gpio_ioctl,
};

/*-----------------------------------------------------------------------------
 *
 * gpio_remove_one_device
 *
 * Releases all of the resources of a single GPIO device
 *
 * Arguments:
 *      struct gpio_device      - pointer to a GpioDevice object
 *
 * Return:
 *      void
 *
 *-----------------------------------------------------------------------------
 */
static void gpio_remove_one_device(GpioDevice * pGpioDevice)
{
    PATH_TESTED();
    cdev_del(&pGpioDevice->cdev);
    
    GpioCoreInterruptDisable(pGpioDevice->pGpioCore);

    // Free IRQ and tasklet
    free_irq(pGpioDevice->osInterruptStruct.irq,
             pGpioDevice);
    tasklet_disable(&(pGpioDevice->osInterruptStruct.tasklet));
    
    GpioCoreReleaseHwResources(pGpioDevice->pGpioCore);
    GpioCoreDestruct(pGpioDevice->pGpioCore);
    
    kfree( pGpioDevice ) ;
}

/*-----------------------------------------------------------------------------
 *
 * gpio_kobj_release
 *
 * Releases the GPIO kobject, and any resources associated with the device
 *
 * Arguments:
 *      struct kobject  - pointer to the GPIO's kobject
 *
 * Return:
 *      int             - error
 *
 *-----------------------------------------------------------------------------
 */
static void gpio_kobj_release(struct kobject *kobj)
{
    GpioDevice *    pGpioDevice;

    PATH_TESTED();
    PDEBUG("gpio_kobj_release\n");    
    pGpioDevice = container_of(kobj, GpioDevice, kobj);
    
    // perform any additional cleanup on this object
    gpio_remove_one_device(pGpioDevice);
}

/*-----------------------------------------------------------------------------
 * Set the kobject function pointed
 *-----------------------------------------------------------------------------
 */
static struct kobj_type gpio_kobj_type = {
    .release    = gpio_kobj_release,
};




/*-----------------------------------------------------------------------------
*
* gpio_get_free_minor
*
* Get a free minor number for the gpio device
*
*
*------------------------------------------------------------------------------
*/
static unsigned int __devinit gpio_get_free_minor(void)
{
	unsigned int i;

	for (i = 0; i < MAX_GPIO_DEVICES; i++)
	{
		if( gpio_devices[i] == 0)
			break;
	}   

	return i;
}








/*-----------------------------------------------------------------------------
 *
 * gpio_setup_cdev
 *
 * Initialise the gpio_device, and in turn the cdev that interfaces our GPIO
 * device with the kernel. Then add to the system.
 *
 * Arguments:
 *      struct gpio_device       - pointer to our device
 *
 * Return:
 *      void
 *
 *-----------------------------------------------------------------------------
 */ 
GpioDevice* __devinit gpio_setup_cdev(void )
{
    int err;
//    int devno = MKDEV(gpioMajorNumber, gpioDeviceCount-1);
    GpioDevice * pGpioDevice = NULL;

    unsigned int minor;
   
    PATH_TESTED();

    // Allocate some memory for the device
    pGpioDevice = kmalloc(sizeof(GpioDevice), GFP_KERNEL);
   
    if (pGpioDevice == NULL)
    {
        PATH_NOT_YET_TESTED();
        PDEBUG("kmalloc has returned a NULL pointer\n");
        err = -ENOMEM;
        kfree(pGpioDevice);
        return NULL;
    }
    
    memset(pGpioDevice, 0, sizeof(GpioDevice));

    minor  = gpio_get_free_minor();

    gpio_devices[minor] = 1;

    // initialise the cdev structure
    cdev_init(&pGpioDevice->cdev, &gpio_operations);
    pGpioDevice->cdev.owner = THIS_MODULE;
    pGpioDevice->cdev.ops = &gpio_operations;

    // add cdev to the system
    err = cdev_add(&pGpioDevice->cdev, MKDEV(gpioMajorNumber, minor), 1);
    
    if (err != 0)
    {
        PATH_NOT_YET_TESTED();
        printk(KERN_ALERT "gpio: Error %d adding GPIO", err);
        kfree(pGpioDevice);
        return NULL;
    }

    PDEBUG("cdev_add with major %d minor %d\n",
           MAJOR(pGpioDevice->cdev.dev),
           MINOR(pGpioDevice->cdev.dev));

    return pGpioDevice;
}

/*-----------------------------------------------------------------------------
 * PCI device registration
 *-----------------------------------------------------------------------------
 */
static struct pci_device_id gpio_ids[] = {
	//PCIe840, mode 0: function 2, 0UART, +GPIO, -PMI
    { PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc006), },
    //PCIe952, mode 0: function 2, 1UART (legacy), +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc106), },
    //PCIe952, mode 1: function 2, 0UART, +GPIO, -PMI        
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc116), },
    //PCIe952, mode 1: function 2, 1UART, +GPIO, -PMI        
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc11e), },
    //PCIe952, mode 2: function 2, 1UART (legacy), +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc126), },
    //PCIe952, mode 3: function 0, 0UART, +GPIO, -PMI        
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc134), },
    //PCIe952, mode 3: function 0, 1UART, +GPIO, -PMI        
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc13c), },
    //PCIe952, mode 4: function 2, 2UART (legacy), +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc146), },
    //PCIe952, mode 5: function 0, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc154), },
    //PCIe952, mode 5: function 0, 2UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc15c), },
    //PCIe954, mode 0: function 0, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc204), },
    //PCIe954, mode 0: function 0, 4UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc20c), },
    //PCIe958, mode 0: function 0, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc304), },
    //PCIe958, mode 0: function 0, 8UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc30c), },
    //PCIe200, mode 0; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc406), },
    //PCIe200, mode 0; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc40e), },
    //PCIe200, mode 1; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc416), },
    //PCIe200, mode 1; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc41e), },
    //PCIe200, mode 2; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc426), },
    //PCIe200, mode 2; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc42e), },
    //PCIe200, mode 3; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc436), },
    //PCIe200, mode 3; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc43e), },
    //PCIe200, mode 4; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc446), },
    //PCIe200, mode 4; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc44e), },
    //PCIe200, mode 5; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc456), },
    //PCIe200, mode 5; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc45e), },
    //PCIe200, mode 6; function 2, 0UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc466), },
    //PCIe200, mode 6; function 2, 1UART, +GPIO, -PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc46e), },
    //PCIe200, mode 7; function 2, 0UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc476), },
    //PCIe200, mode 7; function 2, 1UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc47e), },
    //PCIe200, mode 8; function 2, 0UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc486), },
    //PCIe200, mode 8; function 2, 1UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc48e), },
    //PCIe200, mode 9; function 2, 0UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc496), },
    //PCIe200, mode 9; function 2, 1UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc49e), },
    //PCIe200, mode A; function 2, 0UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc4a6), },
    //PCIe200, mode A; function 2, 1UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc4ae), },
    //PCIe200, mode B; function 2, 0UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc4b6), },
    //PCIe200, mode B; function 2, 1UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc4be), },
    //PCIe200, mode C; function 2, 0UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc4c6), },
    //PCIe200, mode C; function 2, 1UART, +GPIO, +PMI
	{ PCI_DEVICE(PCI_VENDOR_ID_OXSEMI, 0xc4ce), },
	{ 0, }
};
MODULE_DEVICE_TABLE(pci, gpio_ids);

/*-----------------------------------------------------------------------------
 *
 * gpio_pci_probe
 *
 * Handles IO Control requests
 *
 * Arguments:
 *      struct pci_dev          - pointer to a PCI device object
 *      struct pci_device_id    - pointer to a PCI ID object
 *
 * Return:
 *      int                     - error
 *
 *-----------------------------------------------------------------------------
 */
static int __devinit gpio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
    OsHwResource        hwResource;
    OsRegBaseDescriptor regBaseDescr;
    OsStatus            osStatus;
    GpioDevice *        pGpioDevice = NULL;
    
    int err = 0;
    int i;
    int numberBars = 6;
    unsigned long length; 
    
    PATH_TESTED();
    
    gpioDeviceCount++;    
    
    pGpioDevice = gpio_setup_cdev();
    
    if (err != 0)
    {
        PATH_NOT_YET_TESTED();
        gpioDeviceCount--;
        PDEBUG("Error in cdev_setup\n");
        return err;
    }

    pGpioDevice->pGpioCore = GpioCoreConstruct(&pGpioDevice->cdev, 
                                               GpioRequestCancel) ;

    // initialise the ioctl waitqueue
    init_waitqueue_head(&(pGpioDevice->wqIoctl));
    
    err = pci_enable_device(dev);
    if (err)
    {
        PATH_NOT_YET_TESTED();
        PDEBUG("pci_enable_device failed\n");
        return err;
    }
    PDEBUG("pci_enable_device\n");
    
    pGpioDevice->pci_dev = dev;
    
    pGpioDevice->osInterruptStruct.irq = dev->irq;
    pGpioDevice->osInterruptStruct.flags = 0;
    pGpioDevice->osInterruptStruct.hLock = OsSpinLockConstruct((OsObjectHandle)DUMMY_VALID_HANDLE); // dummy valid handle used .

    for(i=0; i<numberBars; i++)
    {
        PATH_TESTED();
        PDEBUG("BAR %d address = %08lx\n", 
               i,
               (unsigned long int)pci_resource_start(dev, i));
        PDEBUG("BAR %d length = %08lx\n",
               i,
               (unsigned long int)pci_resource_len(dev, i));
        PDEBUG("PCI flags %d = %08lx\n", 
               i, 
               (unsigned long int)pci_resource_flags(dev,i));
    }

    hwResource.flags = pci_resource_flags(dev,0);
    length = pci_resource_len(dev, 0);
    if (length == 1024)
    {
        PATH_TESTED();
        hwResource.length = length;
        hwResource.base = pci_resource_start(dev, 0);
        PDEBUG("hwResource.membase = %08lx\n", hwResource.base);
    }
    else
    {
        // base address isn't the expected size?
        PDEBUG("base address isn't the size expected\n");
        return -EFAULT;
    }
    
    // register tasklet
    tasklet_init(&(pGpioDevice->osInterruptStruct.tasklet),
                 gpio_do_tasklet,
                 (unsigned long)pGpioDevice);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
                 
    err = request_irq(pGpioDevice->osInterruptStruct.irq,
                      gpio_interrupt,
                      IRQF_SHARED,
                      DRIVER_NAME,
                      pGpioDevice);
#else
    err = request_irq(pGpioDevice->osInterruptStruct.irq,
                      gpio_interrupt,
                      SA_SHIRQ,
                      DRIVER_NAME,
                      pGpioDevice);

#endif

    
    if (err != 0)
    {
        PATH_NOT_YET_TESTED();
        PDEBUG("gpio_pci_probe couldn't get IRQ\n");
        pci_disable_device(dev);
        return -EBUSY;
    }
    
    OsRegBaseInit(
        &regBaseDescr,
        &hwResource);

    osStatus = GpioCoreAssignHwResources(
        pGpioDevice->pGpioCore,
        &regBaseDescr,
        &(pGpioDevice->osInterruptStruct));

    if (osStatus != OS_STATUS__OK)
    {
        PATH_NOT_YET_TESTED();
        PDEBUG("gpio_pci_probe failed: osStatus %d\n", osStatus);
        pci_disable_device(dev);
        return osStatus;
    }
    
   // kobject initialization
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
// kobject functions have been changed since 2.6.25 ...

    kobject_init(&pGpioDevice->kobj, &gpio_kobj_type);
    err = kobject_add(&pGpioDevice->kobj, NULL, "gpio");

#else

    kobject_init(&pGpioDevice->kobj);
    err = kobject_set_name(&pGpioDevice->kobj, "gpio");
    pGpioDevice->kobj.ktype = &gpio_kobj_type;

#endif

    // Enable interrupts on board device.
    GpioCoreInterruptEnable(pGpioDevice->pGpioCore);

    PDEBUG("OXFORD SEMICONDUCTOR PCIe Gpio device added: Major = %d   Minor = %d\n",
           MAJOR(pGpioDevice->cdev.dev),
           MINOR(pGpioDevice->cdev.dev));


    pci_set_drvdata(dev,pGpioDevice); 

    return 0; 
}

/*-----------------------------------------------------------------------------
 *
 * gpio_pci_remove
 *
 * Remove a GPIO device
 *
 * Arguments:
 *      struct pci_dev  - pointer to a PCI device object
 *
 * Return:
 *      void
 *
 *-----------------------------------------------------------------------------
 */
static void __devinit gpio_pci_remove(struct pci_dev *dev)
{
    GpioDevice * pGpioDevice;

    unsigned int minor;
    
    PATH_TESTED();

    pGpioDevice = pci_get_drvdata(dev);
        
    if (pGpioDevice->pci_dev == dev)
    {
    	PATH_TESTED();
     
        minor = MINOR(pGpioDevice->cdev.dev);

        PDEBUG("Have found the correct pci_dev to remove\n");
        PDEBUG("OXFORD SEMICONDUCTOR PCIe Gpio device removed: Major = %d   Minor = %d\n",
                MAJOR(pGpioDevice->cdev.dev),
                MINOR(pGpioDevice->cdev.dev));

	gpio_devices[minor] = 0;

        kobject_put(&pGpioDevice->kobj);
        return;
    }

    PATH_NOT_YET_TESTED();
    printk(KERN_ALERT "Unable to find device to remove\n");

}


/*-----------------------------------------------------------------------------
 *
 * gpio_pci_suspend
 *
 *  Prepare GPIO for transition of power state
 *
 * Arguments:
 *      struct pci_dev      - pointer to PCI device object
 *      pm_message_t        - power management state
 *
 * Return:
 *      int                 - error
 *
 *-----------------------------------------------------------------------------
 */
int gpio_pci_suspend(struct pci_dev *dev, pm_message_t state)
{
    int err;
    GpioDevice * pGpioDevice;
    
    PATH_NOT_YET_TESTED();    
    PDEBUG("gpio_pci_suspend to state event %d\n", (int)state.event);

    pGpioDevice = pci_get_drvdata(dev);
        
    if (pGpioDevice->pci_dev == dev)
    {
    	PATH_NOT_YET_TESTED();
            
        // driver specific operations
        PDEBUG("GpioCoreArmWakeFromSx\n");
        GpioCoreArmWakeFromSx(pGpioDevice->pGpioCore);
                        
        PDEBUG("GpioCoreD0Exit\n");
        GpioCoreD0Exit(pGpioDevice->pGpioCore,
                       state.event);
         
        GpioCoreInterruptDisable(pGpioDevice->pGpioCore);
     
	PDEBUG("free_irq\n");
        free_irq(pGpioDevice->osInterruptStruct.irq,
                 pGpioDevice);
        // If using MSI
        // pci_disable_msi();
            
        PDEBUG("pci_save_state\n");
        err = pci_save_state(dev);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_save_state failed %d", err);
//          return err;
        }
            
        PDEBUG("pci_enable_wake\n");
            
        // Enable PME# generation
        
	err = pci_enable_wake(dev,
                              pci_choose_state(dev, state),
                              1);

        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_enable_wake failed %d", err);
//          return err;
        }
         
        // Disable IO/bus master/irq router
        PDEBUG("pci_disable_device\n");
        pci_disable_device(dev);

        PDEBUG("pci_set_power_state\n");
        err = pci_set_power_state(dev, pci_choose_state(dev, state));
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_set_power_state failed %d", err);
//            return err;
        }
            
        return 0;           
  
    }
    PATH_NOT_YET_TESTED();
    PDEBUG("gpio_pci_suspend couldn't find device");
    
    return -EFAULT;
}

/*-----------------------------------------------------------------------------
 *
 * gpio_resume
 *
 *  Restore GPIO state after a power transition to D0
 *
 * Arguments:
 *      struct pci_dev      - pointer to a PCI device object
 *
 * Return:
 *      int                 - error
 *
 *-----------------------------------------------------------------------------
 */
int gpio_pci_resume(struct pci_dev *dev)
{
    int err,state_before_resume;
    GpioDevice * pGpioDevice;

    PATH_NOT_YET_TESTED();    
    PDEBUG("gpio_pci_resume\n");

    pGpioDevice = pci_get_drvdata(dev);

        
    if (pGpioDevice->pci_dev == dev)
    {
        PATH_NOT_YET_TESTED();
	state_before_resume= pGpioDevice->pci_dev->current_state;
        
	PDEBUG("Gpio device coming from state: %d\n", state_before_resume);
	
        PDEBUG("pci_set_power_state\n");
        err = pci_set_power_state(dev, PCI_D0);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_set_power_state failed %d", err);
 //           return err;
        }
     
        PDEBUG("pci_restore_state\n");
        err = pci_restore_state(dev);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_restore_state failed %d", err);
//            return err;
        }

        /* Disable PME# generation */
        PDEBUG("pci_enable_wake disable\n");
	err = pci_enable_wake(dev, state_before_resume, 0);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_enable_wake failed\n");
//            return err;
        }

        
         /* device's irq may have changed, driver should take care */
        PDEBUG("pci_enable_device\n");
        err = pci_enable_device(dev);
        if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("pci_enable_device failed\n");
            return err;
        }

	PDEBUG("GpioCoreD0Entry from state: %d\n",state_before_resume);
        GpioCoreD0Entry(pGpioDevice->pGpioCore, 
                        state_before_resume);

	PDEBUG("GpioCoreDisarmWakeFromSx\n");
        GpioCoreDisarmWakeFromSx(pGpioDevice->pGpioCore);

	PDEBUG("GpioCoreWakeFromSxTriggered\n");
        GpioCoreWakeFromSxTriggered(pGpioDevice->pGpioCore);

        
        PDEBUG("check to see if interrupt has changed\n");
        if (pGpioDevice->osInterruptStruct.irq != dev->irq)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("Interrupt has changed\n");
            
            //Our interrupt has changed
            pGpioDevice->osInterruptStruct.irq = dev->irq;
            pGpioDevice->osInterruptStruct.flags = 0;
            pGpioDevice->osInterruptStruct.hLock = OsSpinLockConstruct(OS_INVALID_HANDLE); // Unused handle.
        }
        
//      pci_set_master();
             /* If using MSI, device's vector may have changed */
//      pci_enable_msi();
             /* driver specific operations */
            // This is done before interrupts are enabled, therefore on 
            // interrupt locks needed.
        
        PDEBUG("request_irq\n");

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
// SA_* flags have been deprecated from 2.6.18.
// Use IRQH_* flags
                 
        err = request_irq(pGpioDevice->osInterruptStruct.irq,
                          gpio_interrupt,
                          IRQF_SHARED,
                          DRIVER_NAME,
                          pGpioDevice);
#else
        err = request_irq(pGpioDevice->osInterruptStruct.irq,
                          gpio_interrupt,
                          SA_SHIRQ,
                          DRIVER_NAME,
                          pGpioDevice);

#endif

         if (err != 0)
        {
            PATH_NOT_YET_TESTED();
            PDEBUG("gpio_pci_resume couldn't get IRQ\n");
        }

        // Enable interrupts onboard device.
        GpioCoreInterruptEnable(pGpioDevice->pGpioCore); 
                 return 0;
    }

    PATH_NOT_YET_TESTED();    
    PDEBUG("gpio_pci_resume couldn't find device\n");

    return -EFAULT; 
}

/*-----------------------------------------------------------------------------
 * Set function pointers for PCI driver
 *-----------------------------------------------------------------------------
 */
static struct pci_driver gpio_pci_driver = {
     .name      = "pci_gpio",
     .id_table  = gpio_ids,
     .probe     = gpio_pci_probe,
     .remove    = gpio_pci_remove,
     .suspend   = gpio_pci_suspend,
     .resume    = gpio_pci_resume,
 };


/*-----------------------------------------------------------------------------
 *
 * gpio_exit
 *
 *  Unregister GPIO driver
 *
 * Arguments:
 *      void
 *
 * Return:
 *      void
 *
 *-----------------------------------------------------------------------------
 */
static void __devexit gpio_exit(void) 
{
    dev_t devno = MKDEV(gpioMajorNumber, firstMinorNumber);
    
    PATH_TESTED();
    unregister_chrdev_region(devno, MAX_GPIO_DEVICES) ;

    pci_unregister_driver(&gpio_pci_driver);
    PDEBUG("pci_unregister_driver\n");

    PDEBUG("Oxford GPIO driver exit\n\n");
}

/*----------------------------------------------------------------------
 * 
 * gpio_init
 *
 * Initialisation code, registers any functionality offered by module
 *
 * Arguments:
 *      void
 *
 * Returns:
 *      static int      - error
 *----------------------------------------------------------------------
 */
static int __devinit gpio_init(void)
{
    int err = 0;
    dev_t dev = 0;

    PATH_TESTED();
    PDEBUG("\n");
    PDEBUG("Oxford GPIO driver\n");

    err = alloc_chrdev_region(&dev,
                              firstMinorNumber, 
                              MAX_GPIO_DEVICES,
                              "gpio");

    if (err < 0) {
        PATH_NOT_YET_TESTED();
		printk(KERN_WARNING "gpio: can't get major %d\n", gpioMajorNumber);
		return err;
	}

    gpioMajorNumber = MAJOR(dev);
    firstMinorNumber = MINOR(dev);
    PDEBUG("major %d, minor %d\n", gpioMajorNumber, firstMinorNumber);

    // register with the pci core
    err = pci_register_driver(&gpio_pci_driver);

	if (err < 0)
    {
        PATH_NOT_YET_TESTED();
        PDEBUG("pci_register_driver failed\n");
        return err;
    }
    PDEBUG("pci_register_driver\n");

    return 0;
}

module_init(gpio_init);
module_exit(gpio_exit);

MODULE_AUTHOR("Oxford Semiconductor Ltd.");
MODULE_DESCRIPTION("GPIO Driver for Oxford Semiconductor's Expresso Family of devices");
MODULE_VERSION("1.01.0000.05");


