text: /*
   * linux/drivers/misc/xillybus_pcie.c
   *
   * Copyright 2011 Xillybus Ltd, http://xillybus.com
   *
   * Driver for the Xillybus FPGA/host framework using PCI Express.
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the smems of the GNU General Public License as published by
   * the Free Software Foundation; version 2 of the License.
   */
  
  #include <linux/module.h>
  #include <linux/pci.h>
  #include <linux/pci-aspm.h>
  #include <linux/slab.h>
  #include "xillybus.h"
  
  MODULE_DESCRIPTION("Xillybus driver for PCIe");
  MODULE_AUTHOR("Eli Billauer, Xillybus Ltd.");
  MODULE_VERSION("1.10");
  MODULE_ALIAS("xillybus_pcie");
  MODULE_LICENSE("GPL v2");
  
  #define PCI_DEVICE_ID_XILLYBUS		0xebeb
  
  #define PCI_VENDOR_ID_ALTERA		0x1172
  #define PCI_VENDOR_ID_ACTEL		0x11aa
  #define PCI_VENDOR_ID_LATTICE		0x1204
  
  static const char xillyname[] = "xillybus_pcie";
  
  static const struct pci_device_id xillyids[] = {
  	{PCI_DEVICE(PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_XILLYBUS)},
  	{PCI_DEVICE(PCI_VENDOR_ID_ALTERA, PCI_DEVICE_ID_XILLYBUS)},
  	{PCI_DEVICE(PCI_VENDOR_ID_ACTEL, PCI_DEVICE_ID_XILLYBUS)},
  	{PCI_DEVICE(PCI_VENDOR_ID_LATTICE, PCI_DEVICE_ID_XILLYBUS)},
  	{ /* End: all zeroes */ }
  };
  
  static int xilly_pci_direction(int direction)
  {
  	switch (direction) {
  	case DMA_TO_DEVICE:
  		return PCI_DMA_TODEVICE;
  	case DMA_FROM_DEVICE:
  		return PCI_DMA_FROMDEVICE;
  	default:
  		return PCI_DMA_BIDIRECTIONAL;
  	}
  }
  
  static void xilly_dma_sync_single_for_cpu_pci(struct xilly_endpoint *ep,
  					      dma_addr_t dma_handle,
  					      size_t size,
  					      int direction)
  {
  	pci_dma_sync_single_for_cpu(ep->pdev,
  				    dma_handle,
  				    size,
  				    xilly_pci_direction(direction));
  }
  
  static void xilly_dma_sync_single_for_device_pci(struct xilly_endpoint *ep,
  						 dma_addr_t dma_handle,
  						 size_t size,
  						 int direction)
  {
  	pci_dma_sync_single_for_device(ep->pdev,
  				       dma_handle,
  				       size,
  				       xilly_pci_direction(direction));
  }
  
  /*
   * Map either through the PCI DMA mapper or the non_PCI one. Behind the
   * scenes exactly the same functions are called with the same parameters,
   * but that can change.
   */
  
  static dma_addr_t xilly_map_single_pci(struct xilly_cleanup *mem,
  				       struct xilly_endpoint *ep,
  				       void *ptr,
  				       size_t size,
  				       int direction
  	)
  {
  
  	dma_addr_t addr = 0;
  	struct xilly_dma *this;
  	int pci_direction;
  
  	this = kmalloc(sizeof(struct xilly_dma), GFP_KERNEL);
  	if (!this)
  		return 0;
  
  	pci_direction = xilly_pci_direction(direction);
  	addr = pci_map_single(ep->pdev, ptr, size, pci_direction);
  	this->direction = pci_direction;
  
  	if (pci_dma_mapping_error(ep->pdev, addr)) {
  		kfree(this);
  		return 0;
  	}
  
  	this->dma_addr = addr;
  	this->pdev = ep->pdev;
  	this->size = size;
  
  	list_add_tail(&this->node, &mem->to_unmap);
  
  	return addr;
  }
  
  static void xilly_unmap_single_pci(struct xilly_dma *entry)
  {
  	pci_unmap_single(entry->pdev,
  			 entry->dma_addr,
  			 entry->size,
  			 entry->direction);
  }
  
  static struct xilly_endpoint_hardware pci_hw = {
  	.owner = THIS_MODULE,
  	.hw_sync_sgl_for_cpu = xilly_dma_sync_single_for_cpu_pci,
  	.hw_sync_sgl_for_device = xilly_dma_sync_single_for_device_pci,
  	.map_single = xilly_map_single_pci,
  	.unmap_single = xilly_unmap_single_pci
  };
  
  static int xilly_probe(struct pci_dev *pdev,
  				 const struct pci_device_id *ent)
  {
  	struct xilly_endpoint *endpoint;
  	int rc = 0;
  
  	endpoint = xillybus_init_endpoint(pdev, &pdev->dev, &pci_hw);
  
  	if (!endpoint)
  		return -ENOMEM;
  
  	pci_set_drvdata(pdev, endpoint);
  
  	rc = pci_enable_device(pdev);
  
  	/* L0s has caused packet drops. No power saving, thank you. */
  
  	pci_disable_link_state(pdev, PCIE_LINK_STATE_L0S);
  
  	if (rc) {
  		dev_err(endpoint->dev,
  			"pci_enable_device() failed. Aborting.\n");
  		goto no_enable;
  	}
  
  	if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {
  		dev_err(endpoint->dev,
  			"Incorrect BAR configuration. Aborting.\n");
  		rc = -ENODEV;
  		goto bad_bar;
  	}
  
  	rc = pci_request_regions(pdev, xillyname);
  	if (rc) {
  		dev_err(endpoint->dev,
  			"pci_request_regions() failed. Aborting.\n");
  		goto failed_request_regions;
  	}
  
  	endpoint->registers = pci_iomap(pdev, 0, 128);
  	if (!endpoint->registers) {
  		dev_err(endpoint->dev, "Failed to map BAR 0. Aborting.\n");
  		rc = -EIO;
  		goto failed_iomap0;
  	}
  
  	pci_set_master(pdev);
  
  	/* Set up a single MSI interrupt */
  	if (pci_enable_msi(pdev)) {
  		dev_err(endpoint->dev,
  			"Failed to enable MSI interrupts. Aborting.\n");
  		rc = -ENODEV;
  		goto failed_enable_msi;
  	}
  	rc = request_irq(pdev->irq, xillybus_isr, 0, xillyname, endpoint);
  
  	if (rc) {
  		dev_err(endpoint->dev,
  			"Failed to register MSI handler. Aborting.\n");
  		rc = -ENODEV;
  		goto failed_register_msi;
  	}
  
  	/*
  	 * Some (old and buggy?) hardware drops 64-bit addressed PCIe packets,
  	 * even when the PCIe driver claims that a 64-bit mask is OK. On the
  	 * other hand, on some architectures, 64-bit addressing is mandatory.
  	 * So go for the 64-bit mask only when failing is the other option.
  	 */
  
  	if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(32)))
  		endpoint->dma_using_dac = 0;
  	else if (!pci_set_dma_mask(pdev, DMA_BIT_MASK(64)))
  		endpoint->dma_using_dac = 1;
  	else {
  		dev_err(endpoint->dev, "Failed to set DMA mask. Aborting.\n");
  		rc = -ENODEV;
  		goto failed_dmamask;
  	}
  
  	rc = xillybus_endpoint_discovery(endpoint);
  
  	if (!rc)
  		return 0;
  
  failed_dmamask:
  	free_irq(pdev->irq, endpoint);
  failed_register_msi:
  	pci_disable_msi(pdev);
  failed_enable_msi:
  	/* pci_clear_master(pdev); Nobody else seems to do this */
  	pci_iounmap(pdev, endpoint->registers);
  failed_iomap0:
  	pci_release_regions(pdev);
  failed_request_regions:
  bad_bar:
  	pci_disable_device(pdev);
  no_enable:
  	xillybus_do_cleanup(&endpoint->cleanup, endpoint);
  
  	kfree(endpoint);
  	return rc;
  }
  
  static void xilly_remove(struct pci_dev *pdev)
  {
  	struct xilly_endpoint *endpoint = pci_get_drvdata(pdev);
  
  	xillybus_endpoint_remove(endpoint);
  
  	free_irq(pdev->irq, endpoint);
  
  	pci_disable_msi(pdev);
  	pci_iounmap(pdev, endpoint->registers);
  	pci_release_regions(pdev);
  	pci_disable_device(pdev);
  
  	xillybus_do_cleanup(&endpoint->cleanup, endpoint);
  
  	kfree(endpoint);
  }
  
  MODULE_DEVICE_TABLE(pci, xillyids);
  
  static struct pci_driver xillybus_driver = {
  	.name = xillyname,
  	.id_table = xillyids,
  	.probe = xilly_probe,
  	.remove = xilly_remove,
  };
  
  static int __init xillybus_pcie_init(void)
  {
  	return pci_register_driver(&xillybus_driver);
  }
  
  static void __exit xillybus_pcie_exit(void)
  {
  	pci_unregister_driver(&xillybus_driver);
  }
  
  module_init(xillybus_pcie_init);
  module_exit(xillybus_pcie_exit);
  