/***************************************************************************
 *
 *  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/list.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)

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

static LIST_HEAD(gpioAdapterHead);

int gpioMajorNumber;
int firstMinorNumber = 0;
unsigned int numberPossibleDevices = 256;
int gpioDeviceCount = 0;

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


/*----------------------------------------------------------------------
 * 
 * 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
 *      struct pt_regs      - pointer to processor context (not used)
 *
 * Returns:
 *      irqreturn_t         - error
 *----------------------------------------------------------------------
 */
irqreturn_t gpio_interrupt(int irq, void * dev_id, struct pt_regs * regs)
{
    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 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_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 * gpio_setup_cdev(void )
{
    int err;
    int devno = MKDEV(gpioMajorNumber, gpioDeviceCount-1);
    GpioDevice * pGpioDevice = NULL;

    int gpioCount = 0;
    GpioDevice * pCurrentGpioDevice;
    struct list_head *tmp;
    
    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));

    // 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, devno, 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));

    // place the gpio device into linked list
    list_for_each(tmp, &gpioAdapterHead)
    {
        PATH_NOT_YET_TESTED();
        pCurrentGpioDevice = list_entry(tmp, GpioDevice, gpioDeviceEntry);
        
        if (pCurrentGpioDevice->index != gpioCount)
        {
            PATH_NOT_YET_TESTED();
            break;
        }
        gpioCount++;
    }
    
    pGpioDevice->index = gpioCount;
    list_add_tail(&pGpioDevice->gpioDeviceEntry, tmp);
    
    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 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(OS_INVALID_HANDLE); // Unused handle.

    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);
                 
    err = request_irq(pGpioDevice->osInterruptStruct.irq,
                      gpio_interrupt,
                      SA_SHIRQ,
                      DRIVER_NAME,
                      pGpioDevice);
    
    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
    kobject_init(&pGpioDevice->kobj);
    err = kobject_set_name(&pGpioDevice->kobj, "gpio");
    pGpioDevice->kobj.ktype = &gpio_kobj_type;

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

    return 0; 
}

/*-----------------------------------------------------------------------------
 *
 * gpio_pci_remove
 *
 * Remove a GPIO device
 *
 * Arguments:
 *      struct pci_dev  - pointer to a PCI device object
 *
 * Return:
 *      void
 *
 *-----------------------------------------------------------------------------
 */
static void gpio_pci_remove(struct pci_dev *dev)
{
    GpioDevice * pGpioDevice;
    struct list_head *tmp;
    
    PATH_TESTED();
    list_for_each(tmp, &gpioAdapterHead)
    {
        PATH_TESTED();
        pGpioDevice = list_entry(tmp, GpioDevice, gpioDeviceEntry);
        
        if (pGpioDevice->pci_dev == dev)
        {
            PATH_TESTED();
            PDEBUG("Have found the correct pci_dev to remove\n");
            PDEBUG("pci_remove major %d minor %d\n",
                MAJOR(pGpioDevice->cdev.dev),
                MINOR(pGpioDevice->cdev.dev));

            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;
    struct list_head *tmp;
    
    PATH_NOT_YET_TESTED();    
    PDEBUG("gpio_pci_suspend to state event %d\n", (int)state.event);
    
    list_for_each(tmp, &gpioAdapterHead)
    {
        PATH_NOT_YET_TESTED();
        
        pGpioDevice = list_entry(tmp, GpioDevice, gpioDeviceEntry);
        
        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;
    GpioDevice * pGpioDevice;
    struct list_head *tmp;

    PATH_NOT_YET_TESTED();    
    PDEBUG("gpio_pci_resume\n");
    
    list_for_each(tmp, &gpioAdapterHead)
    {
        PATH_NOT_YET_TESTED();
        
        pGpioDevice = list_entry(tmp, GpioDevice, gpioDeviceEntry);
        
        if (pGpioDevice->pci_dev == dev)
        {
            PATH_NOT_YET_TESTED();
            
            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;
            }
            
            PDEBUG("pci_enable_wake disable\n");
            /* Disable PME# generation */
            err = pci_enable_wake(dev, PCI_D0, 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("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("GpioCoreD0Entry %d\n", pGpioDevice->pci_dev->current_state);
            GpioCoreD0Entry(pGpioDevice->pGpioCore, 
                            pGpioDevice->pci_dev->current_state);

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

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

            PDEBUG("request_irq\n");
            err = request_irq(pGpioDevice->osInterruptStruct.irq,
                              gpio_interrupt,
                              SA_SHIRQ,
                              DRIVER_NAME,
                              pGpioDevice);

            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, numberPossibleDevices) ;

    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, 
                              numberPossibleDevices,
                              "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");
MODULE_DESCRIPTION("Driver for GPIO port of OxSemi Tornado device");
MODULE_VERSION("Version 1.00.0000.01");


