text: /**
  ********************************************************************************
  \file   pciedrv.c
  
  \brief  Linux kernel driver for openPOWERLINK PCIe interface card
  
  This module handles initialization and hardware specific operations of the
  openPOWERLINK PCIe interface card.
  
  It initializes and cleans up the PCIe hardware and maps the bus memory into
  kernel space. It also enables and handles the PCIe interrupts from the
  openPOWERLINK driver.
  
  \ingroup module_driver_linux_kernel_pcie
  *******************************************************************************/
  
  /*------------------------------------------------------------------------------
  Copyright (c) 2015, Kalycito Infotech Private Limited
  Copyright (c) 2016, Bernecker+Rainer Industrie-Elektronik Ges.m.b.H. (B&R)
  All rights reserved.
  
  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:
      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in the
        documentation and/or other materials provided with the distribution.
      * Neither the name of the copyright holders nor the
        names of its contributors may be used to endorse or promote products
        derived from this software without specific prior written permission.
  
  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS BE LIABLE FOR ANY
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  ------------------------------------------------------------------------------*/
  
  //------------------------------------------------------------------------------
  // includes
  //------------------------------------------------------------------------------
  #include "pciedrv.h"
  
  #include <common/driver.h>
  
  #include <linux/module.h>
  #include <linux/kernel.h>
  #include <linux/pci.h>
  #include <linux/interrupt.h>
  #include <linux/init.h>
  #include <linux/errno.h>
  #include <linux/major.h>
  #include <linux/version.h>
  #include <asm/io.h>
  #include <asm/uaccess.h>
  #include <asm/atomic.h>
  #include <asm/irq.h>
  #include <linux/sched.h>
  #include <linux/delay.h>
  #include <linux/gfp.h>
  #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26))
  #include <linux/semaphore.h>
  #endif
  
  
  //============================================================================//
  //            G L O B A L   D E F I N I T I O N S                             //
  //============================================================================//
  
  //------------------------------------------------------------------------------
  // const defines
  //------------------------------------------------------------------------------
  #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19))
  #error "Linux Kernel versions older 2.6.19 are not supported by this driver!"
  #endif
  
  #define OPLK_MAX_BAR_COUNT      6                   // Maximum BARs polled in the PCIe
  
  //------------------------------------------------------------------------------
  // module global vars
  //------------------------------------------------------------------------------
  
  //------------------------------------------------------------------------------
  // global function prototypes
  //------------------------------------------------------------------------------
  
  //============================================================================//
  //            P R I V A T E   D E F I N I T I O N S                           //
  //============================================================================//
  
  //------------------------------------------------------------------------------
  // const defines
  //------------------------------------------------------------------------------
  
  //------------------------------------------------------------------------------
  // local types
  //------------------------------------------------------------------------------
  /**
  
  \brief PCIe BAR information
  
  The structure holds the information of the PCIe BAR mapped by this driver.
  
  */
  typedef struct
  {
      ULONG           busAddr;                        ///< Physical address of the BAR.
      ULONG           virtualAddr;                    ///< Virtual address of the BAR in kernel memory.
      ULONG           length;                         ///< Length of the BAR.
  } tBarInfo;
  
  /**
  
  \brief PCIe Driver instance information
  
  The structure holds the information of this PCIe driver instance.
  
  */
  typedef struct
  {
      struct pci_dev* pPciDev;                        ///< Pointer to PCI device structure.
      tBarInfo        aBarInfo[OPLK_MAX_BAR_COUNT];   ///< Bar instances of the PCIe interface.
      tIrqCallback    pfnCbSync;                      ///< Sync IRQ callback function of the upper user layer.
      BOOL            fSyncEnabled;                   ///< Flag to check if sync IRQ for user has been enabled.
  } tPcieDrvInstance;
  
  //------------------------------------------------------------------------------
  // local function prototypes
  //------------------------------------------------------------------------------
  static int          initOnePciDev(struct pci_dev* pPciDev_p,
                                    const struct pci_device_id* pId_p);
  static void         removeOnePciDev(struct pci_dev* pPciDev_p);
  static irqreturn_t  pcieDrvIrqHandler(int irqNum_p,
                                        void* ppDevInstData_p);
  
  //------------------------------------------------------------------------------
  // local vars
  //------------------------------------------------------------------------------
  
  static struct pci_device_id aDriverPciTbl_l[] =
  {
      {0x1677, 0xe53f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},  // APC2100
      {0x1677, 0xe809, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},  // APC2100
      {0x1172, 0xe001, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},  // DE2i150
      {0, }
  };
  
  MODULE_DEVICE_TABLE(pci, aDriverPciTbl_l);
  
  static struct pci_driver    oplkPcieDriver_l;
  static tPcieDrvInstance     pcieDrvInstance_l;
  
  //============================================================================//
  //            P U B L I C   F U N C T I O N S                                 //
  //============================================================================//
  
  //------------------------------------------------------------------------------
  /**
  \brief  openPOWERLINK PCIe driver initialization
  
  This function initializes the openPOWERLINK PCIe driver.
  
  \return The function returns a tOplkError error code.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  tOplkError pciedrv_init(void)
  {
      tOplkError  ret = kErrorOk;
      INT         result;
  
      // Clear instance structure
      OPLK_MEMSET(&pcieDrvInstance_l, 0, sizeof(pcieDrvInstance_l));
  
      // Clear driver structure
      OPLK_MEMSET(&oplkPcieDriver_l, 0, sizeof(oplkPcieDriver_l));
      oplkPcieDriver_l.name = PLK_DRV_NAME;
      oplkPcieDriver_l.id_table = aDriverPciTbl_l;
      oplkPcieDriver_l.probe = initOnePciDev;
      oplkPcieDriver_l.remove = removeOnePciDev;
  
      // Register PCI driver
      result = pci_register_driver(&oplkPcieDriver_l);
      if (result != 0)
      {
          DEBUG_LVL_ERROR_TRACE("%s pci_register_driver failed with %d\n",
                                __func__,
                                result);
          ret = kErrorNoResource;
          goto Exit;
      }
  
      if (pcieDrvInstance_l.pPciDev == NULL)
      {
          DEBUG_LVL_ERROR_TRACE("%s pPciDev=NULL\n", __func__);
          ret = pciedrv_shutdown();
          ret = kErrorNoResource;
          goto Exit;
      }
  
  Exit:
      return ret;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  openPOWERLINK PCIe driver shutdown
  
  This function shuts down the openPOWERLINK PCIe driver.
  
  \return The function returns a tOplkError error code.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  tOplkError pciedrv_shutdown(void)
  {
      // Unregister PCI driver
      DEBUG_LVL_DRVINTF_TRACE("%s calling pci_unregister_driver()\n", __func__);
      pci_unregister_driver(&oplkPcieDriver_l);
  
      return kErrorOk;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Get PCIe BAR virtual address
  
  This routine fetches the BAR address of the requested BAR.
  
  \param[in]      barCount_p          ID of the requested BAR.
  
  \return Returns the address of requested PCIe BAR.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  ULONG pciedrv_getBarAddr(UINT8 barCount_p)
  {
      if (barCount_p >= OPLK_MAX_BAR_COUNT)
          return 0;
  
      return pcieDrvInstance_l.aBarInfo[barCount_p].virtualAddr;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Get PCIe BAR physical address
  
  This routine fetches the physical BAR address of the requested BAR.
  
  \param[in]      barCount_p          ID of the requested BAR.
  
  \return Returns the physical address of requested PCIe BAR.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  ULONG pciedrv_getBarPhyAddr(UINT8 barCount_p)
  {
      if (barCount_p >= OPLK_MAX_BAR_COUNT)
          return 0;
  
      return pcieDrvInstance_l.aBarInfo[barCount_p].busAddr;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Get BAR Length
  
  \param[in]      barCount_p          ID of the requested BAR.
  
  \return Returns the length of requested PCIe BAR.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  ULONG pciedrv_getBarLength(ULONG barCount_p)
  {
      if (barCount_p >= OPLK_MAX_BAR_COUNT)
          return 0;
  
      return pcieDrvInstance_l.aBarInfo[barCount_p].length;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Register user sync interrupt callback
  
  This function stores the user sync event callback function tobe called from
  the PCIe sync ISR.
  
  \param[in]      cbSync_p            Pinter to the user sync callback function.
  
  \return The function returns a tOplkError error code.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  tOplkError pciedrv_regSyncHandler(tIrqCallback cbSync_p)
  {
      pcieDrvInstance_l.pfnCbSync = cbSync_p;
  
      return kErrorOk;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Enable/Disable user sync interrupt
  
  Enables or disable the forwarding of sync interrupt to user.
  
  \param[in]      fEnable_p           Boolean value indicating whether or not to
                                      forward sync IRQ to user.
  
  \return The function returns a tOplkError error code.
  
  \ingroup module_driver_linux_kernel_pcie
  */
  //------------------------------------------------------------------------------
  tOplkError pciedrv_enableSync(BOOL fEnable_p)
  {
      pcieDrvInstance_l.fSyncEnabled = fEnable_p;
  
      return kErrorOk;
  }
  
  //============================================================================//
  //            P R I V A T E   F U N C T I O N S                               //
  //============================================================================//
  /// \name Private Functions
  /// \{
  
  //------------------------------------------------------------------------------
  /**
  \brief  openPOWERLINK PCIe driver interrupt handler
  
  This function is the interrupt service routine for the openPOWERLINK PCIe driver.
  
  \param[in]      irqNum_p            IRQ number
  \param[in]      ppDevInstData_p     Pointer to private data provided by request_irq
  
  \return The function returns an IRQ handled code.
  */
  //------------------------------------------------------------------------------
  static irqreturn_t pcieDrvIrqHandler(int irqNum_p,
                                       void* ppDevInstData_p)
  {
      irqreturn_t ret = IRQ_HANDLED;
  
      UNUSED_PARAMETER(irqNum_p);
      UNUSED_PARAMETER(ppDevInstData_p);
  
      // Currently only sync interrupt is produced by the PCIe, check if the user
      // wants the irq to be forwarded
      if ((pcieDrvInstance_l.pfnCbSync != NULL) &&
          (pcieDrvInstance_l.fSyncEnabled == TRUE))
      {
          // User wants the interrupt, forward it without any argument
          pcieDrvInstance_l.pfnCbSync();
      }
  
      return ret;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Initialize one PCI device
  
  This function initializes one PCI device.
  
  \param[in,out]  pPciDev_p           Pointer to corresponding PCI device structure
  \param[in]      pId_p               PCI device ID
  
  \return The function returns an integer error code.
  \retval 0           Successful
  \retval Otherwise   Error
  */
  //------------------------------------------------------------------------------
  static int initOnePciDev(struct pci_dev* pPciDev_p,
                           const struct pci_device_id* pId_p)
  {
      int         result = 0;
      UINT8       barCount = 0;
      tBarInfo*   pBarInfo = NULL;
  
      UNUSED_PARAMETER(pId_p);
  
      if (pcieDrvInstance_l.pPciDev != NULL)
      {
          // This driver is already connected to a PCIe device
          DEBUG_LVL_DRVINTF_TRACE("%s device %s discarded\n",
                                  __func__,
                                  pci_name(pPciDev_p));
          result = -ENODEV;
          goto Exit;
      }
  
      pcieDrvInstance_l.pPciDev = pPciDev_p;
  
      // Enable the PCIe device
      DEBUG_LVL_DRVINTF_TRACE("%s enable device\n", __func__);
      result = pci_enable_device(pPciDev_p);
      if (result != 0)
          goto Exit;
  
      DEBUG_LVL_DRVINTF_TRACE("%s request PCIe regions\n", __func__);
      result = pci_request_regions(pPciDev_p, PLK_DRV_NAME);
      if (result != 0)
          goto ExitFail;
  
      // Ignoring whether or not any BAR is accessible
      for (barCount = 0; barCount < OPLK_MAX_BAR_COUNT; barCount++)
      {
          pBarInfo = &pcieDrvInstance_l.aBarInfo[barCount];
  
          if (pBarInfo->virtualAddr != (ULONG)NULL)
          {
              // The instance is already present
              result = -EIO;
              goto ExitFail;
          }
  
          // Look for the MMIO BARs
          if ((pci_resource_flags(pPciDev_p, barCount) & IORESOURCE_MEM) == 0)
              continue;
  
          // get the size of this field
          pBarInfo->length = pci_resource_len(pPciDev_p, barCount);
  
          // $$: Add check for weird broken IO regions
  
          pBarInfo->virtualAddr = (ULONG)ioremap_nocache(pci_resource_start(pPciDev_p, barCount),
                                                         pBarInfo->length);
          if (pBarInfo->virtualAddr == 0UL)
          {
              // Remap of controller's register space failed
              result = -EIO;
              goto ExitFail;
          }
  
          pBarInfo->busAddr = (ULONG)pci_resource_start(pPciDev_p, barCount);
  
          DEBUG_LVL_DRVINTF_TRACE("%s() --> ioremap\n", __func__);
          DEBUG_LVL_DRVINTF_TRACE("\tbar#\t%u\n", barCount);
          DEBUG_LVL_DRVINTF_TRACE("\tbarLen\t%lu\n", pBarInfo->length);
          DEBUG_LVL_DRVINTF_TRACE("\tbarMap\t0x%lX\n", pBarInfo->virtualAddr);
          DEBUG_LVL_DRVINTF_TRACE("\tbarPhy\t0x%lX\n", pBarInfo->busAddr);
      }
  
      // Enable PCI busmaster
      DEBUG_LVL_DRVINTF_TRACE("%s enable busmaster\n", __func__);
      pci_set_master(pPciDev_p);
  
      // Enable msi
      DEBUG_LVL_DRVINTF_TRACE("Enable MSI\n");
      result = pci_enable_msi(pPciDev_p);
      if (result != 0)
      {
          DEBUG_LVL_DRVINTF_TRACE("%s Could not enable MSI\n", __func__);
      }
  
      // Install interrupt handler
      DEBUG_LVL_DRVINTF_TRACE("%s install interrupt handler\n", __func__);
      result = request_irq(pPciDev_p->irq,
                           pcieDrvIrqHandler,
                           IRQF_SHARED,
                           PLK_DRV_NAME, /* pPciDev_p->dev.name */
                           pPciDev_p);
      if (result != 0)
          goto ExitFail;
  
      goto Exit;
  
  ExitFail:
      removeOnePciDev(pPciDev_p);
  
  Exit:
      DEBUG_LVL_DRVINTF_TRACE("%s finished with %d\n", __func__, result);
      return result;
  }
  
  //------------------------------------------------------------------------------
  /**
  \brief  Remove one PCI device
  
  This function removes one PCI device.
  
  \param[in,out]  pPciDev_p           Pointer to corresponding PCI device structure
  */
  //------------------------------------------------------------------------------
  static void removeOnePciDev(struct pci_dev* pPciDev_p)
  {
      UINT8       barCount = 0;
      tBarInfo*   pBarInfo = NULL;
  
      if (pcieDrvInstance_l.pPciDev != pPciDev_p)
      {
          // Trying to remove unknown device
          BUG_ON(pcieDrvInstance_l.pPciDev != pPciDev_p);
          goto Exit;
      }
  
      // Remove interrupt handler
      if (pPciDev_p->irq != 0)
          free_irq(pPciDev_p->irq, pPciDev_p);
  
      // Disable Message Signaled Interrupt
      DEBUG_LVL_DRVINTF_TRACE("%s Disable MSI\n", __func__);
      pci_disable_msi(pPciDev_p);
  
      // unmap controller's register space
      for (barCount = 0; barCount < OPLK_MAX_BAR_COUNT; barCount++)
      {
          pBarInfo = &pcieDrvInstance_l.aBarInfo[barCount];
  
          if (pBarInfo->virtualAddr != (ULONG)NULL)
          {
              iounmap((void __iomem*)pBarInfo->virtualAddr);
              pBarInfo->virtualAddr = (ULONG)NULL;
          }
      }
  
      // disable the PCI device
      pci_disable_device(pPciDev_p);
  
      // release memory regions
      pci_release_regions(pPciDev_p);
  
      pcieDrvInstance_l.pPciDev = NULL;
  
  Exit:
      return;
  }
  
  /// \}
  