
#include "drex.h"
#include "drexchar.h"

#define DRIVER_NAME "drexpcie"

/* This sample driver supports device with VID = 0x010F, and PID = 0x0F0E*/
static struct pci_device_id drexpcie_driver_id_table[] = {
   { PCI_DEVICE(0x10EE, 0x903F) },
   {0,}
};

MODULE_DEVICE_TABLE(pci, drexpcie_driver_id_table);

static int drexpcie_driver_probe(struct pci_dev *pdev, const struct pci_device_id *ent);
static void drexpcie_driver_remove(struct pci_dev *pdev);

/* Driver registration structure */
static struct pci_driver drexpcie_driver = {
   .name = DRIVER_NAME,
   .id_table = drexpcie_driver_id_table,
   .probe = drexpcie_driver_probe,
   .remove = drexpcie_driver_remove
};


static int __init drexpcie_driver_init(void)
{
   /* Register new PCI driver */
   int result = pci_register_driver(&drexpcie_driver);
   if (result < 0) {
      // This print will tell you exactly why it failed
      pr_err("pci_register_driver failed with error: %d\n", result);
      return result; 
   }

   pr_info("Xilinx custom driver registered successfully!\n");   

   return result;
}

static void __exit drexpcie_driver_exit(void)
{
   /* Unregister */
   pci_unregister_driver(&drexpcie_driver);
}

void release_device(struct pci_dev *pdev)
{
   /* Disable IRQ #42*/
//    free_irq(42, pdev);
   /* Free memory region */
   pci_release_region(pdev, pci_select_bars(pdev, IORESOURCE_MEM));
   /* And disable device */
   pci_disable_device(pdev);
}

/* */

static irqreturn_t irq_handler(int irq, void *cookie)
{
   (void) cookie;
   printk("Handle IRQ #%d\n", irq);
   return IRQ_HANDLED;
}

/* Reqest interrupt and setup handler */
int set_interrupts(struct pci_dev *pdev)
{
   /* We want MSI interrupt, 3 lines (just an example) */
   int ret = pci_alloc_irq_vectors(pdev, 3, 3, PCI_IRQ_MSI);

   if (ret < 0) {
      return ret;
   }

   /* Request IRQ #42 */
   return request_threaded_irq(42, irq_handler, NULL, 0, "TEST IRQ", pdev);
}



/* This function is called by the kernel */
static int drexpcie_driver_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
   int bar, err;
   u16 vendor, device;
//    unsigned long mmio_start,mmio_len;
   struct drexpcie_driver_priv *drv_priv;

   printk("drexpcie probe\n");

   /* Let's read data from the PCI device configuration registers */
   pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor);
   pci_read_config_word(pdev, PCI_DEVICE_ID, &device);

   printk(KERN_INFO "Device vid: 0x%X pid: 0x%X\n", vendor, device);

   /* Request IO BAR */
   bar = pci_select_bars(pdev, IORESOURCE_MEM);

   /* Enable device memory */
   err = pci_enable_device_mem(pdev);

   if (err) {
      return err;
   }

   /* Request memory region for the BAR */
   err = pci_request_region(pdev, bar, DRIVER_NAME);

   if (err) {
      pci_disable_device(pdev);
      return err;
   }
   
   pci_set_master(pdev);

   if (dma_set_mask_and_coherent(&(pdev->dev), DMA_BIT_MASK(40))) {
      printk("dma_set_mask_and_coherent error\n");
   }

   /* Allocate memory for the driver private data */
   drv_priv = kzalloc(sizeof(struct drexpcie_driver_priv), GFP_KERNEL);

   if (!drv_priv) {
      release_device(pdev);
      return -ENOMEM;
   }

   drv_priv->pdev = pdev;

   /* Get start and stop memory offsets */
   drv_priv->mem_start = pci_resource_start(pdev, 0);
   drv_priv->mem_len = pci_resource_len(pdev, 0);

   printk(KERN_INFO "Resource Start: 0x%lX length: 0x%lX\n", drv_priv->mem_start, drv_priv->mem_len);

   /* Remap BAR to the local pointer */
   drv_priv->bar0_mem = ioremap(drv_priv->mem_start, drv_priv->mem_len);

   if (!drv_priv->bar0_mem) {
      release_device(pdev);
      return -EIO;
   }

   drexpcie_chardev_create(drv_priv);

   /* Set driver private data */
   /* Now we can access mapped "bar0_mem" from the any driver's function */
   pci_set_drvdata(pdev, drv_priv);

   return 0;
    
//    return set_interrupts(pdev);
}

/* Clean up */
static void drexpcie_driver_remove(struct pci_dev *pdev)
{
   struct drexpcie_driver_priv *drv_priv = pci_get_drvdata(pdev);

   drexpcie_chardev_destroy();

   if (drv_priv) {
      if (drv_priv->bar0_mem) {
         iounmap(drv_priv->bar0_mem);
      }

      pci_free_irq_vectors(pdev);

      kfree(drv_priv);
      pci_set_drvdata(pdev, NULL); 
   }

   // release_device(pdev);
   pci_release_region(pdev, 0); 
   pci_disable_device(pdev);    

}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("");
MODULE_DESCRIPTION("DREX PCIe driver");
MODULE_VERSION("0.1");

module_init(drexpcie_driver_init);
module_exit(drexpcie_driver_exit);

