/*-----------------------------------------------------------------------------
 * atemsys.c
 * Copyright (c) 2009 - 2020 acontis technologies GmbH, Ravensburg, Germany
 * All rights reserved.
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 *
 * Response                  Paul Bussmann
 * Description               Provides usermode access to:
 *   - PCI configuration space
 *   - Device IO memory
 *   - Contiguous DMA memory
 *   - Single device interrupt
 *
 *
 * The driver should be used in the following way:
 *
 * - Make sure this driver's device node is present. I.e. call "mknod /dev/atemsys c 101 0"
 *
 * - open()
 *   Open driver (There can be more then one file descriptor active in parallel).
 *
 * - close()
 *   Close driver. Free resources, if any were allocated.
 *
 * - ioctl(ATEMSYS_IOCTL_PCI_FIND_DEVICE)
 *   Scan for PCI Devices.
 *   Input:  VendorID, DeviceID, InstanceNo
 *   Output: BusNo, DevNo, FuncNo
 *
 * - ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE)
 *   Configures PCI device. This ioctl pins the given PCI device to the current filedescriptor.
 *   Input:  BusNo, DevNo, FuncNo
 *   Output: Physical IO base address, IO area length, IRQ number
 *   The device must be released explicitly in order to configure the next device. The ioctl gets
 *   errno EBUSY if the device is in use by another device driver.
 *
 * - ioctl(ATEMSYS_IOCTL_PCI_RELEASE_DEVICE)
 *   Release PCI device and free resources assigned to PCI device (interrupt, DMA memory, ...).
 *
 * - mmap(0, dwSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, 0);
 *   Allocates and maps DMA memory of size dwSize. Note that the last parameter (offset) must be 0.
 *   Input:  Length in byte
 *   Output: Pointer to the allocated memory and DMA physical address. On success this address is
 *           written into the first 4 bytes of the allocated memory.
 *
 * - mmap(0, IOphysSize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED, fd, IOphysAddr);
 *   Maps IO memory of size IOphysSize.
 *   PCI device:
 *     First call ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE). The IOphysAddr and IOphysSize
 *     parameter must corespond with the base IO address and size returned by
 *     ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE), or the ioctl will fail.
 *   Non-PCI device:
 *     Don't call ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE) before and just pass
 *     IOphysAddr and IOphysSize. There are no checks done.
 *   Input:  Phys.IO base address, IO area length in byte
 *   Output: Pointer to the mapped IO memory.
 *   The user should call dev_munmap() if the requested DMA memory is not needed anymore. In any cases
 *   the allocated / mapped memory is released / unmapped if the module is unloaded.
 *
 * - ioctl(ATEMSYS_IOCTL_INT_CONNECT)
 *   Connect an ISR to the device's interrupt.
 *   If the parameter is USE_PCI_INT, then the IRQ is taken from the selected PCI device.
 *   So in this case you have to call ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE) first, or it will fail.
 *   Input:  IRQ-Number or USE_PCI_INT
 *   Output: none
 *   The device interrupt is active if this ioctl succeeds. The caller should do a read() on the file
 *   descriptor. The read call unblocks if an interrupt is received. If the read is unblocked, the
 *   interrupt is disabled on the (A)PIC and the caller must acknowledge the interrupt on the device
 *   (write to mmaped IO register). If the next read() is executed, the interrupt is enabled again
 *   on the (A)PIC. So a missing interrupt acknowledge will held the INT line active and interrupt
 *   trashing will happen (ISR is called again, read() unblocks, ...).
 *   Note that this ioctl will fail with errno EPERM if the interrupt line is shared.
 *   PCI device:
 *     The ioctl will try to use Message Signaled Interrupts (MSI) if supported
 *     by the PCI device. By definition, interrupts are never shared with MSI and MSI are mandatory
 *     for PCI-Express :).
 *
 * - ioctl(ATEMSYS_IOCTL_INT_DISCONNECT)
 *   Disconnect from device's interrupt.
 *
 * - ioctl(ATEMSYS_IOCTL_INT_INFO)
 *   Query used interrupt number.
 *
 * - read()
 *   see ioctl(ATEMSYS_IOCTL_INT_CONNECT)
 *
 *
 *  Changes see atemsys.h
 *
 *----------------------------------------------------------------------------*/

#define ATEMSYS_C

#include <linux/module.h>
#include "atemsys.h"
#include <linux/pci.h>
#include <linux/platform_device.h>

#if !(defined NO_IRQ) && (defined __aarch64__)
#define NO_IRQ   ((unsigned int)(-1))
#endif

#if (defined CONFIG_XENO_COBALT)
#include <rtdm/driver.h>
#else
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/semaphore.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/smp.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,00))
#include <linux/sched/signal.h>
#endif
#include <linux/irq.h>
#include <linux/list.h>
#if (defined CONFIG_OF)
#include <linux/of_device.h>
#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0))
#include <linux/uaccess.h>
#else
#include <asm/uaccess.h>
#endif

#include <asm/current.h>
#include <linux/compat.h>
#include <linux/slab.h>
#include <linux/device.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0))
#include <linux/dma-direct.h>
#endif

#if (defined CONFIG_DTC)
#include <linux/of.h>
#include <linux/of_irq.h>
#endif /* CONFIG_DTC */
#endif /* CONFIG_XENO_COBALT */

#if ((defined CONFIG_OF) \
       && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,6,0) /* not tested */))
#define INCLUDE_ATEMSYS_DT_DRIVER    1
#include <linux/etherdevice.h>
#include <linux/clk.h>
#include <linux/phy.h>
#include <linux/clk/clk-conf.h>
#include <linux/pinctrl/consumer.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/of_mdio.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_net.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <asm/param.h>
#include <linux/of_gpio.h>
#include <linux/reset.h>
#endif
#if ((defined CONFIG_PCI) \
       && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) /* not tested */))
#define INCLUDE_ATEMSYS_PCI_DRIVER    1
#include <linux/aer.h>
#endif

#if !(defined HAVE_IRQ_TO_DESC) && !(defined CONFIG_HAVE_DOVETAIL) && !(defined CONFIG_IRQ_PIPELINE)
 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,4,1))
  #define INCLUDE_IRQ_TO_DESC
 #endif
#else
 #if HAVE_IRQ_TO_DESC
  #define INCLUDE_IRQ_TO_DESC
 #endif
#endif

/* legacy support */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0))
#define wait_queue_entry_t wait_queue_t
#endif

#ifndef VM_RESERVED
#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP)
#endif


/* define this if IO memory should also be mapped into the kernel (for debugging only) */
#undef DEBUG_IOREMAP

MODULE_AUTHOR("acontis technologies GmbH <info@acontis.com>");
MODULE_DESCRIPTION("Generic usermode PCI driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(ATEMSYS_VERSION_STR);

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18))
#error "At least kernel version 2.6.18 is needed to compile!"
#endif

static char* AllowedPciDevices = "PCI_ANY_ID";
module_param(AllowedPciDevices, charp, 0000);
MODULE_PARM_DESC(AllowedPciDevices, "Bind only pci devices in semicolon separated list e.g. AllowedPciDevices=\"0000:01:00.0\", empty string will turn off atemsys_pci driver.");

/* Workaround for older kernels */
/* from 'linux/kern_levels.h' */
/* integer equivalents of KERN_<LEVEL> */
#ifndef LOGLEVEL_ERR
#define LOGLEVEL_ERR        3   /* error conditions */
#endif
#ifndef LOGLEVEL_WARNING
#define LOGLEVEL_WARNING    4   /* warning conditions */
#endif
#ifndef LOGLEVEL_INFO
#define LOGLEVEL_INFO       6   /* informational */
#endif
#ifndef LOGLEVEL_DEBUG
#define LOGLEVEL_DEBUG      7   /* debug-level messages */
#endif

static int loglevel = LOGLEVEL_INFO;
module_param(loglevel, int, 0);
MODULE_PARM_DESC(loglevel, "Set log level default LOGLEVEL_INFO, see /include/linux/kern_levels.h");

#if (defined CONFIG_XENO_COBALT)
#define PRINTK(prio, str, ...) rtdm_printk(prio ATEMSYS_DEVICE_NAME ": " str,  ##__VA_ARGS__)
#else
#define PRINTK(prio, str, ...) printk(prio ATEMSYS_DEVICE_NAME ": " str,  ##__VA_ARGS__)
#endif /* CONFIG_XENO_COBALT */

#define ERR(str, ...) (LOGLEVEL_ERR <= loglevel)?     PRINTK(KERN_ERR, str, ##__VA_ARGS__)     :0
#define WRN(str, ...) (LOGLEVEL_WARNING <= loglevel)? PRINTK(KERN_WARNING, str, ##__VA_ARGS__) :0
#define INF(str, ...) (LOGLEVEL_INFO <= loglevel)?    PRINTK(KERN_INFO, str, ##__VA_ARGS__)    :0
#define DBG(str, ...) (LOGLEVEL_DEBUG <= loglevel)?   PRINTK(KERN_INFO, str, ##__VA_ARGS__)    :0


#ifndef PAGE_UP
#define PAGE_UP(addr)   (((addr)+((PAGE_SIZE)-1))&(~((PAGE_SIZE)-1)))
#endif
#ifndef PAGE_DOWN
#define PAGE_DOWN(addr) ((addr)&(~((PAGE_SIZE)-1)))
#endif

/* Comments: for kernel 2.6.18 add DMA_BIT_MASK*/
#ifndef DMA_BIT_MASK
#define DMA_BIT_MASK(n) (((n) == 64) ? ~0ULL : ((1ULL<<(n))-1))
#endif

#ifndef HAVE_ACCESS_OK_TYPE
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0))
#define HAVE_ACCESS_OK_TYPE 0
#else
#define HAVE_ACCESS_OK_TYPE 1
#endif
#endif

#if HAVE_ACCESS_OK_TYPE
#define ACCESS_OK(type, addr, size)     access_ok(type, addr, size)
#else
#define ACCESS_OK(type, addr, size)     access_ok(addr, size)
#endif

#if ((defined CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,18,0)) && !(defined CONFIG_XENO_COBALT))
  #define OF_DMA_CONFIGURE(dev, of_node) of_dma_configure(dev, of_node, true)
#elif ((defined CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0)) && !(defined CONFIG_XENO_COBALT))
  #define OF_DMA_CONFIGURE(dev, of_node) of_dma_configure(dev, of_node)
#elif ((defined CONFIG_OF) && (LINUX_VERSION_CODE >= KERNEL_VERSION(3,16,0)) && !(defined CONFIG_XENO_COBALT))
 #define OF_DMA_CONFIGURE(dev, of_node) of_dma_configure(dev)
#else
 #define OF_DMA_CONFIGURE(dev, of_node)
#endif

typedef struct _ATEMSYS_T_IRQ_DESC
{
    u32               irq;
    atomic_t          count;
    atomic_t          totalCount;
#if (defined CONFIG_XENO_COBALT)
    rtdm_irq_t        irq_handle;
    rtdm_event_t      irq_event;
#else
    atomic_t          irqStatus;
    wait_queue_head_t q;
#endif /* CONFIG_XENO_COBALT */
#if (defined INCLUDE_IRQ_TO_DESC)
    bool              irq_is_level;
#endif
} ATEMSYS_T_IRQ_DESC;

struct _ATEMSYS_T_PCI_DRV_DESC_PRIVATE;
struct _ATEMSYS_T_DRV_DESC_PRIVATE;
typedef struct _ATEMSYS_T_DEVICE_DESC
{
    struct list_head list;
#if (defined CONFIG_PCI)
    struct pci_dev* pPcidev;
  #if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
    struct _ATEMSYS_T_PCI_DRV_DESC_PRIVATE* pPciDrvDesc;
  #endif
#endif
    struct platform_device* pPlatformDev;
  #if (defined INCLUDE_ATEMSYS_DT_DRIVER)
    struct _ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDesc;
  #endif

    ATEMSYS_T_IRQ_DESC  irqDesc;

    /* supported features */
    bool bSupport64BitDma;
} ATEMSYS_T_DEVICE_DESC;

typedef struct _ATEMSYS_T_MMAP_DESC
{
   struct list_head  list;
   ATEMSYS_T_DEVICE_DESC* pDevDesc;
   dma_addr_t        dmaAddr;
   void*             pVirtAddr;
   size_t            len;
} ATEMSYS_T_MMAP_DESC;

#if (defined CONFIG_OF)
#define ATEMSYS_DT_DRIVER_NAME "atemsys"
/* udev auto-loading support via DTB */
static const struct of_device_id atemsys_ids[] = {
    { .compatible = ATEMSYS_DT_DRIVER_NAME },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atemsys_ids);
#endif /* CONFIG_OF */


#define ATEMSYS_MAX_NUMBER_DRV_INSTANCES 10

#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
typedef struct _ATEMSYS_T_PCI_DRV_DESC_PRIVATE
{
    struct pci_dev*             pPciDev;

    int                         nPciDomain;
    int                         nPciBus;
    int                         nPciDev;
    int                         nPciFun;

    unsigned short              wVendorId;
    unsigned short              wDevice;
    unsigned short              wRevision;
    unsigned short              wSubsystem_vendor;
    unsigned short              wSubsystem_device;

    ATEMSYS_T_PCI_MEMBAR        aBars[ATEMSYS_PCI_MAXBAR];
    int                         nBarCnt;

    ATEMSYS_T_DEVICE_DESC*      pDevDesc;
    unsigned int                dwIndex;
} ATEMSYS_T_PCI_DRV_DESC_PRIVATE;

static ATEMSYS_T_PCI_DRV_DESC_PRIVATE*  S_apPciDrvDescPrivate[ATEMSYS_MAX_NUMBER_DRV_INSTANCES];
#endif

#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
#define ATEMSYS_MAX_NUMBER_OF_CLOCKS 10

typedef struct
{
    void __iomem*   pbyBase;
    __u64           qwPhys;
    __u32           dwSize;
} ATEMSYS_T_IOMEM;

typedef struct _ATEMSYS_T_DRV_DESC_PRIVATE
{
    int                         nDev_id;
    struct net_device*          netdev;
    struct platform_device*     pPDev;
    struct device_node*         pDevNode;

    /* storage and identification */
    ATEMSYS_T_MAC_INFO          MacInfo;

    /* powermanagement */
    struct reset_control*       pResetCtl;

    /* clocks */
    const char*                 clk_ids[ATEMSYS_MAX_NUMBER_OF_CLOCKS];
    struct clk*                 clks[ATEMSYS_MAX_NUMBER_OF_CLOCKS];
    int                         nCountClk;

    /* PHY */
    ATEMSYS_T_PHY_INFO          PhyInfo;
    phy_interface_t             PhyInterface;
    struct device_node*         pPhyNode;
    struct device_node*         pMdioNode;
    struct device_node*         pMdioDevNode; /* node for own mdio bus */
    struct phy_device*          pPhyDev;
    struct regulator*           pPhyRegulator;
    struct task_struct*         etx_thread_StartPhy;
    struct task_struct*         etx_thread_StopPhy;

    /* PHY reset*/
    int                         nPhyResetGpioPin;
    bool                        bPhyResetGpioActiveHigh;
    int                         nPhyResetDuration;
    int                         nPhyResetPostDelay;

    /* mdio */
    ATEMSYS_T_MDIO_ORDER        MdioOrder;
    struct mii_bus*             pMdioBus;
    struct mutex                mdio_order_mutex;
    struct mutex                mdio_mutex;
    wait_queue_head_t           mdio_wait_queue;
    int                         mdio_wait_queue_cnt;

#ifdef CONFIG_TI_K3_UDMA
    /* Ti CPSWG Channel, Flow & Ring */
#define ATEMSYS_UDMA_CHANNELS 10
    void*                       apvTxChan[ATEMSYS_UDMA_CHANNELS];
    int                         anTxIrq[ATEMSYS_UDMA_CHANNELS];
    void*                       apvRxChan[ATEMSYS_UDMA_CHANNELS];
    int                         anRxIrq[ATEMSYS_UDMA_CHANNELS];
#endif /*#ifdef CONFIG_TI_K3_UDMA*/

#define IOMEMLIST_LENGTH 20
    ATEMSYS_T_IOMEM             oIoMemList[IOMEMLIST_LENGTH];

    /* frame descriptor of the EcMaster connection */
    ATEMSYS_T_DEVICE_DESC*      pDevDesc;

} ATEMSYS_T_DRV_DESC_PRIVATE;

static ATEMSYS_T_DRV_DESC_PRIVATE*  S_apDrvDescPrivate[ATEMSYS_MAX_NUMBER_DRV_INSTANCES];

static int StartPhyThread(void* pvData);
static int StopPhyThread(void* pvData);
static int CleanUpEthernetDriverOnRelease(ATEMSYS_T_DEVICE_DESC* pDevDesc);
static int GetMacInfoIoctl(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam);
static int PhyStartStopIoctl( unsigned long ioctlParam);
static int GetMdioOrderIoctl(unsigned long ioctlParam);
static int ReturnMdioOrderIoctl(unsigned long ioctlParam);
static int GetPhyInfoIoctl(unsigned long ioctlParam);
static int PhyResetIoctl(unsigned long ioctlParam);
static int ResetPhyViaGpio(ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate);
static int EthernetDriverRemove(struct platform_device* pPDev);
static int EthernetDriverProbe(struct platform_device* pPDev);

#if (defined CONFIG_XENO_COBALT)
static int StartPhy(struct platform_device* pPDev);
static int StopPhy(struct platform_device* pPDev);
typedef struct _ATEMSYS_T_WORKER_THREAD_DESC
{
    struct task_struct*     etx_thread;
    int (* pfNextTask)(void*);
    void*                   pNextTaskData;
    struct mutex            WorkerTask_mutex;
    bool                    bWorkerTaskShutdown;
    bool                    bWorkerTaskRunning;
} ATEMSYS_T_WORKER_THREAD_DESC;
static ATEMSYS_T_WORKER_THREAD_DESC S_oAtemsysWorkerThreadDesc;

static int AtemsysWorkerThread(void* data)
{
    void* pWorkerTaskData = NULL;
    int (* pfWorkerTask)(void*);
    pfWorkerTask = NULL;

    S_oAtemsysWorkerThreadDesc.bWorkerTaskRunning = true;

    for (;;)
    {
        mutex_lock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
        if (S_oAtemsysWorkerThreadDesc.bWorkerTaskShutdown)
        {
            mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
            break;
        }
        pfWorkerTask = S_oAtemsysWorkerThreadDesc.pfNextTask;
        pWorkerTaskData = S_oAtemsysWorkerThreadDesc.pNextTaskData;
        S_oAtemsysWorkerThreadDesc.pfNextTask = NULL;
        S_oAtemsysWorkerThreadDesc.pNextTaskData = NULL;
        mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);

        if ((NULL != pfWorkerTask) && (NULL != pWorkerTaskData))
        {
            pfWorkerTask(pWorkerTaskData);
        }
        msleep(100);
    }

    S_oAtemsysWorkerThreadDesc.bWorkerTaskRunning = false;

    return 0;
}
#endif /* #if (defined CONFIG_XENO_COBALT) */

#endif /* INCLUDE_ATEMSYS_DT_DRIVER */


static void dev_munmap(struct vm_area_struct* vma);

#if (defined CONFIG_XENO_COBALT)
   static int dev_interrupt_handler(rtdm_irq_t* irq_handle);
#else
   static irqreturn_t dev_interrupt_handler(int nIrq, void* pParam);
#endif /* CONFIG_XENO_COBALT */

static struct vm_operations_struct mmap_vmop =
{
   .close = dev_munmap,
};

static DEFINE_MUTEX(S_mtx);
static ATEMSYS_T_DEVICE_DESC S_DevNode;
static struct class* S_pDevClass;
static struct device* S_pDev;
static struct platform_device* S_pPlatformDev = NULL;

#if !(defined CONFIG_XENO_COBALT)
static void dev_enable_irq(ATEMSYS_T_IRQ_DESC* pIrqDesc)
{
    /* enable/disable level type interrupts, not edge type interrupts */
#if (defined INCLUDE_IRQ_TO_DESC)
    if (pIrqDesc->irq_is_level)
#endif
    {
        atomic_inc(&pIrqDesc->irqStatus);
        enable_irq(pIrqDesc->irq);
    }
}

static void dev_disable_irq(ATEMSYS_T_IRQ_DESC* pIrqDesc)
{
    /* enable/disable level type interrupts, not edge type interrupts */
#if (defined INCLUDE_IRQ_TO_DESC)
    if (!pIrqDesc->irq_is_level) return;
#endif

    if (atomic_read(&pIrqDesc->irqStatus) > 0)
    {
        disable_irq_nosync(pIrqDesc->irq);
        atomic_dec(&pIrqDesc->irqStatus);
    }
}

static int dev_irq_disabled(ATEMSYS_T_IRQ_DESC* pIrqDesc)
{
    /* only level type interrupts get disabled */
#if (defined INCLUDE_IRQ_TO_DESC)
    if (!pIrqDesc->irq_is_level) return 0;
#endif

    if (atomic_read(&pIrqDesc->irqStatus) == 0)
    {
        return 1;
    }
    return 0;
}
#endif /* !CONFIG_XENO_COBALT */

#if (!defined __arm__) && (!defined __aarch64__)
static void* dev_dma_alloc(u32 dwLen, dma_addr_t* pDmaAddr)
{
   unsigned long virtAddr;
   unsigned long tmpAddr;
   u32 tmpSize;

   virtAddr =  __get_free_pages(GFP_KERNEL | GFP_DMA, get_order(dwLen));
   if (! virtAddr)
   {
      ERR("mmap: __get_free_pages failed\n");
      return NULL;
   }

   tmpAddr = virtAddr;
   tmpSize = dwLen;

   while (tmpSize > 0)
   {
     SetPageReserved( virt_to_page(tmpAddr) );
     tmpAddr += PAGE_SIZE;
     tmpSize -= PAGE_SIZE;
   }

   *pDmaAddr = virt_to_phys((void*) virtAddr);

   return (void*) virtAddr;
}

static void dev_dma_free(u32 dwLen, void* virtAddr)
{
   unsigned long tmpAddr = (unsigned long) virtAddr;
   u32 tmpSize = dwLen;

   while (tmpSize > 0)
   {
     ClearPageReserved( virt_to_page(tmpAddr) );
     tmpAddr += PAGE_SIZE;
     tmpSize -= PAGE_SIZE;
   }

   free_pages((unsigned long) virtAddr, get_order(dwLen));
}
#endif /* !__arm__ */

static void dev_munmap(struct vm_area_struct* vma)
{
   ATEMSYS_T_MMAP_DESC* pMmapDesc = (ATEMSYS_T_MMAP_DESC*) vma->vm_private_data;

   INF("dev_munmap: 0x%px -> 0x%px (%d)\n",
         (void*) pMmapDesc->pVirtAddr, (void*)(unsigned long)pMmapDesc->dmaAddr, (int) pMmapDesc->len);
    if (0 == pMmapDesc->dmaAddr) { INF("dev_munmap: 0 == pMmapDesc->dmaAddr!\n"); return; }
    if (NULL == pMmapDesc->pVirtAddr) { INF("dev_munmap: NULL == pMmapDesc->pVirtAddr!\n"); return; }

   /* free DMA memory */
#if (defined CONFIG_PCI)
   if (pMmapDesc->pDevDesc->pPcidev == NULL)
#endif
   {
#if (defined __arm__) || (defined __aarch64__)
      dmam_free_coherent(&pMmapDesc->pDevDesc->pPlatformDev->dev, pMmapDesc->len, pMmapDesc->pVirtAddr, pMmapDesc->dmaAddr);
#else
      dev_dma_free(pMmapDesc->len, pMmapDesc->pVirtAddr);
#endif
   }
#if (defined CONFIG_PCI)
   else
   {
#if ((defined __aarch64__) \
    || (LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)) \
    || ((defined __arm__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0))) \
    || ((defined __amd64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0))) )
      dma_free_coherent(&pMmapDesc->pDevDesc->pPcidev->dev, pMmapDesc->len, pMmapDesc->pVirtAddr, pMmapDesc->dmaAddr);
#else
      pci_free_consistent(pMmapDesc->pDevDesc->pPcidev, pMmapDesc->len, pMmapDesc->pVirtAddr, pMmapDesc->dmaAddr);
#endif /* __aarch64__ */
   }
#endif /* CONFIG_PCI */
   kfree(pMmapDesc);
}

#if (defined CONFIG_PCI)
/*
 * Lookup PCI device
 */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24))
struct pci_dev *pci_get_bus_and_slot(unsigned int bus, unsigned int devfn)
{
    struct pci_dev* dev = NULL;

    for_each_pci_dev(dev) {
        if (pci_domain_nr(dev->bus) == 0 &&
            (dev->bus->number == bus && dev->devfn == devfn))
            return dev;
    }
    return dev;
}
#endif

static int dev_pci_select_device(ATEMSYS_T_DEVICE_DESC* pDevDesc, ATEMSYS_T_PCI_SELECT_DESC* pPciDesc, size_t size)
{
    int nRetVal = -EFAULT;
    s32 nPciBus, nPciDev, nPciFun, nPciDomain;

    switch (size)
    {
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00):
    {
        ATEMSYS_T_PCI_SELECT_DESC_v1_0_00 oPciDesc_v1_0_00;
        nRetVal = copy_from_user(&oPciDesc_v1_0_00, (ATEMSYS_T_PCI_SELECT_DESC_v1_0_00*)pPciDesc, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00));
        if (0 != nRetVal)
        {
            ERR("dev_pci_select_device failed: %d\n", nRetVal);
            goto Exit;
        }
        nPciBus    = oPciDesc_v1_0_00.nPciBus;
        nPciDev    = oPciDesc_v1_0_00.nPciDev;
        nPciFun    = oPciDesc_v1_0_00.nPciFun;
        nPciDomain = 0;
    } break;
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05):
    {
        ATEMSYS_T_PCI_SELECT_DESC_v1_3_05 oPciDesc_v1_3_05;
        nRetVal = copy_from_user(&oPciDesc_v1_3_05, (ATEMSYS_T_PCI_SELECT_DESC_v1_3_05*)pPciDesc, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05));
        if (0 != nRetVal)
        {
            ERR("dev_pci_select_device failed: %d\n", nRetVal);
            goto Exit;
        }
        nPciBus    = oPciDesc_v1_3_05.nPciBus;
        nPciDev    = oPciDesc_v1_3_05.nPciDev;
        nPciFun    = oPciDesc_v1_3_05.nPciFun;
        nPciDomain = oPciDesc_v1_3_05.nPciDomain;
    } break;
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12):
    {
        ATEMSYS_T_PCI_SELECT_DESC_v1_4_12 oPciDesc_v1_4_12;
        nRetVal = copy_from_user(&oPciDesc_v1_4_12, (ATEMSYS_T_PCI_SELECT_DESC_v1_4_12*)pPciDesc, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12));
        if (0 != nRetVal)
        {
            ERR("dev_pci_select_device failed: %d\n", nRetVal);
            goto Exit;
        }
        nPciBus    = oPciDesc_v1_4_12.nPciBus;
        nPciDev    = oPciDesc_v1_4_12.nPciDev;
        nPciFun    = oPciDesc_v1_4_12.nPciFun;
        nPciDomain = oPciDesc_v1_4_12.nPciDomain;
    } break;
    default:
    {
        nRetVal = -EFAULT;
        ERR("pci_conf: EFAULT\n");
        goto Exit;
    }
    }

    /* Lookup for pci_dev object */
    pDevDesc->pPcidev       = NULL;
#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
    pDevDesc->pPciDrvDesc   = NULL;
    {
        unsigned int i = 0;

        for (i = 0; i < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; i++)
        {
            ATEMSYS_T_PCI_DRV_DESC_PRIVATE* pDrvInstance = S_apPciDrvDescPrivate[i];
            if (   (pDrvInstance                != NULL)
                && (pDrvInstance->nPciDomain    == nPciDomain)
                && (pDrvInstance->nPciBus       == nPciBus)
                && (pDrvInstance->nPciDev       == nPciDev)
                && (pDrvInstance->nPciFun       == nPciFun))
            {
                if (pDrvInstance->pDevDesc != NULL)
                {
                    ERR("dev_pci_select_device: device \"%s\" in use by another instance?\n", pci_name(pDrvInstance->pPciDev));
                    nRetVal = -EBUSY;
                    goto Exit;
                }
                pDevDesc->pPcidev        = pDrvInstance->pPciDev;
                pDevDesc->pPciDrvDesc    = pDrvInstance;
                pDrvInstance->pDevDesc   = pDevDesc;
                INF("pci_select: from pci driver %04x:%02x:%02x.%x\n", (u32)nPciDomain, (u32)nPciBus, (u32)nPciDev, (u32)nPciFun);
                break;
            }
        }
    }
    if (pDevDesc->pPcidev == NULL)
#endif
    {
        pDevDesc->pPcidev = pci_get_domain_bus_and_slot(nPciDomain, nPciBus, PCI_DEVFN(nPciDev, nPciFun));
        INF("pci_select: %04x:%02x:%02x.%x\n", (u32)nPciDomain, (u32)nPciBus, (u32)nPciDev, (u32)nPciFun);
    }
    if (pDevDesc->pPcidev == NULL)
    {
        WRN("pci_select: PCI-Device  %04x:%02x:%02x.%x not found\n",
            (unsigned) nPciDomain, (unsigned) nPciBus, (unsigned) nPciDev, (unsigned) nPciFun);
        goto Exit;
    }

    nRetVal = DRIVER_SUCCESS;

Exit:
    return nRetVal;
}

static int DefaultPciSettings(struct pci_dev* pPciDev)
{
    int nRetVal = -EIO;
    int nRes = -EIO;

    /* Turn on Memory-Write-Invalidate if it is supported by the device*/
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24))
    pci_set_mwi(pPciDev);
#else
    pci_try_set_mwi(pPciDev);
#endif

    /* remove wrong dma_coherent bit on ARM systems */
#if ((defined __aarch64__) || (defined __arm__))
 #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0))
  #if (defined CONFIG_PHYS_ADDR_T_64BIT)
    if (is_device_dma_coherent(&pPciDev->dev))
    {
        pPciDev->dev.archdata.dma_coherent = false;
        INF("%s: DefaultPciSettings: Clear device.archdata dma_coherent bit!\n", pci_name(pPciDev));
    }
  #endif
 #else
  #if ((defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)))
    if (0 != pPciDev->dev.dma_coherent)
    {
        pPciDev->dev.dma_coherent = 0;
        INF("%s: DefaultPciSettings: Clear device dma_coherent bit!\n", pci_name(pPciDev));
    }
  #endif
 #endif
#endif

#if ((LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)) || !(defined __aarch64__))
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,12,55))
    nRes = dma_set_coherent_mask(&pPciDev->dev, DMA_BIT_MASK(32));
#else
    nRes = dma_set_mask_and_coherent(&pPciDev->dev, DMA_BIT_MASK(32));
#endif
    if (nRes)
#endif
    {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,12,55))
        nRes = dma_set_coherent_mask(&pPciDev->dev, DMA_BIT_MASK(64));
#else
        nRes = dma_set_mask_and_coherent(&pPciDev->dev, DMA_BIT_MASK(64));
#endif
        if (nRes)
        {
            ERR("%s: DefaultPciSettings: dma_set_mask_and_coherent failed\n", pci_name(pPciDev));
            nRetVal = nRes;
            goto Exit;
        }
    }
    pci_set_master(pPciDev);

    /* Try to enable MSI (Message Signaled Interrupts). MSI's are non shared, so we can
    * use interrupt mode, also if we have a non exclusive interrupt line with legacy
    * interrupts.
    */
    if (pci_enable_msi(pPciDev))
    {
        INF("%s: DefaultPciSettings: legacy INT configured\n", pci_name(pPciDev));
    }
    else
    {
        INF("%s: DefaultPciSettings: MSI configured\n", pci_name(pPciDev));
    }

    nRetVal = 0;

Exit:
   return nRetVal;
}

/*
 * See also kernel/Documentation/PCI/pci.txt for the recommended PCI initialization sequence
 */
static int ioctl_pci_configure_device(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam, size_t size)
{
    int nRetVal = -EIO;
    int nRc;
    int i;
    unsigned long ioBase;
    s32 nBar = 0;
    u32 dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,0,0);
    ATEMSYS_T_PCI_SELECT_DESC_v1_4_12 oPciDesc;
    memset(&oPciDesc, 0, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12));
    switch (size)
    {
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00):
    {
        dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,0,0);
    } break;
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05):
    {
        dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,3,5);
    } break;
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12):
    {
        dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,4,12);
    } break;
    default:
    {
        nRetVal = -EIO;
        ERR("pci_conf: Invalid parameter\n");
        goto Exit;
    }
    }

    if (pDevDesc->pPcidev != NULL)
    {
        WRN("pci_conf: error call ioctl(ATEMSYS_IOCTL_PCI_RELEASE_DEVICE) first\n");
        goto Exit;
    }
    if (dev_pci_select_device(pDevDesc, (ATEMSYS_T_PCI_SELECT_DESC*)ioctlParam, size) != DRIVER_SUCCESS)
    {
        goto Exit;
    }

#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
    if (NULL != pDevDesc->pPciDrvDesc)
    {
        for (i = 0; i < pDevDesc->pPciDrvDesc->nBarCnt ; i++)
        {
            if ((EC_ATEMSYSVERSION(1,4,12) != dwAtemsysApiVersion) && (pDevDesc->pPciDrvDesc->aBars[i].qwIOMem > 0xFFFFFFFF))
            {
                ERR("pci_conf: 64-Bit IO address not supported\n");
                INF("pci_conf: Update LinkLayer for 64-Bit IO address support!\n");
                nRetVal = -ENODEV;
                goto Exit;
            }

            oPciDesc.aBar[i].qwIOMem = pDevDesc->pPciDrvDesc->aBars[i].qwIOMem;
            oPciDesc.aBar[i].dwIOLen = pDevDesc->pPciDrvDesc->aBars[i].dwIOLen;

        }

        oPciDesc.nBarCnt = pDevDesc->pPciDrvDesc->nBarCnt;
        oPciDesc.dwIrq   = (u32)pDevDesc->pPcidev->irq;
    }
    else
#endif
    {
        /* enable device */
        nRc = pci_enable_device(pDevDesc->pPcidev);
        if (nRc < 0)
        {
            ERR("pci_conf: pci_enable_device failed\n");
            pDevDesc->pPcidev = NULL;
            goto Exit;
        }

        /* Check if IO-memory is in use by another driver */
        nRc = pci_request_regions(pDevDesc->pPcidev, ATEMSYS_DEVICE_NAME);
        if (nRc < 0)
        {
            ERR("pci_conf: device \"%s\" in use by another driver?\n", pci_name(pDevDesc->pPcidev));
            pDevDesc->pPcidev = NULL;
            nRetVal = -EBUSY;
            goto Exit;
        }

        /* find the memory BAR */
        for (i = 0; i < ATEMSYS_PCI_MAXBAR ; i++)
        {
            if (pci_resource_flags(pDevDesc->pPcidev, i) & IORESOURCE_MEM)
            {
                /* IO area address */
                ioBase = pci_resource_start(pDevDesc->pPcidev, i);

                if ((EC_ATEMSYSVERSION(1,4,12) != dwAtemsysApiVersion) && (ioBase > 0xFFFFFFFF))
                {
                    ERR("pci_conf: 64-Bit IO address not supported\n");
                    pci_release_regions(pDevDesc->pPcidev);
                    pDevDesc->pPcidev = NULL;
                    nRetVal = -ENODEV;
                    goto Exit;
                }

                /* IO area length */
                oPciDesc.aBar[nBar].dwIOLen = pci_resource_len(pDevDesc->pPcidev, i);
                oPciDesc.aBar[nBar].qwIOMem = ioBase;

                nBar++;
            }
        }

        nRc = DefaultPciSettings(pDevDesc->pPcidev);
        if (nRc)
        {
            pci_release_regions(pDevDesc->pPcidev);
            pDevDesc->pPcidev = NULL;
            goto Exit;
        }

        /* number of memory BARs */
        /* assigned IRQ */
        oPciDesc.nBarCnt = nBar;
        oPciDesc.dwIrq   = pDevDesc->pPcidev->irq;
    }

#if defined(__arm__) && 0
   /*
    * This is required for TI's TMDXEVM8168 (Cortex A8) eval board
    * \sa TI "DM81xx AM38xx PCI Express Root Complex Driver User Guide"
    * "DM81xx RC supports maximum remote read request size (MRRQS) as 256 bytes"
    */
   pcie_set_readrq(pDevDesc->pPcidev, 256);
#endif

    switch (dwAtemsysApiVersion)
    {
    case EC_ATEMSYSVERSION(1,0,0):
    {
        ATEMSYS_T_PCI_SELECT_DESC_v1_0_00 oPciDesc_v1_0_00;
        memset(&oPciDesc_v1_0_00, 0, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00));
        if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_PCI_SELECT_DESC_v1_0_00*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00)))
        {
            nRetVal = -EFAULT;
            ERR("pci_conf: EFAULT\n");
            goto Exit;
        }
        oPciDesc_v1_0_00.nBarCnt = oPciDesc.nBarCnt;
        oPciDesc_v1_0_00.dwIrq   = oPciDesc.dwIrq;
        for (i = 0; i < oPciDesc_v1_0_00.nBarCnt ; i++)
        {
            oPciDesc_v1_0_00.aBar[i].dwIOLen = oPciDesc.aBar[i].dwIOLen;
            oPciDesc_v1_0_00.aBar[i].dwIOMem = (u32)oPciDesc.aBar[i].qwIOMem;
        }
        nRetVal = copy_to_user((ATEMSYS_T_PCI_SELECT_DESC_v1_0_00*)ioctlParam, &oPciDesc_v1_0_00, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_configure_device failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case EC_ATEMSYSVERSION(1,3,5):
    {
        ATEMSYS_T_PCI_SELECT_DESC_v1_3_05 oPciDesc_v1_3_05;
        memset(&oPciDesc_v1_3_05, 0, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05));
        if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_PCI_SELECT_DESC_v1_3_05*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05)))
        {
            nRetVal = -EFAULT;
            ERR("pci_conf: EFAULT\n");
            goto Exit;
        }
        oPciDesc_v1_3_05.nBarCnt = oPciDesc.nBarCnt;
        oPciDesc_v1_3_05.dwIrq   = oPciDesc.dwIrq;
        for (i = 0; i < oPciDesc_v1_3_05.nBarCnt ; i++)
        {
            oPciDesc_v1_3_05.aBar[i].dwIOLen = oPciDesc.aBar[i].dwIOLen;
            oPciDesc_v1_3_05.aBar[i].dwIOMem = (u32)oPciDesc.aBar[i].qwIOMem;
        }
        nRetVal = copy_to_user((ATEMSYS_T_PCI_SELECT_DESC_v1_3_05*)ioctlParam, &oPciDesc_v1_3_05, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_configure_device failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case EC_ATEMSYSVERSION(1,4,12):
    {
        if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_PCI_SELECT_DESC_v1_4_12*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12)))
        {
            nRetVal = -EFAULT;
            ERR("pci_conf: EFAULT\n");
            goto Exit;
        }
        nRetVal = copy_to_user((ATEMSYS_T_PCI_SELECT_DESC_v1_4_12*)ioctlParam, &oPciDesc, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_configure_device failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    default:
    {
        nRetVal = -EFAULT;
        goto Exit;
    }
    }

   nRetVal = 0;

Exit:
   return nRetVal;
}

static int ioctl_pci_finddevice(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam, size_t size)
{
    int nRetVal = -EIO;
    struct pci_dev* pPciDev = NULL;
    s32 nVendor, nDevice, nInstance, nInstanceId;
    u32 dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,0,0);
    ATEMSYS_T_PCI_SELECT_DESC_v1_0_00 oPciDesc_v1_0_00;
    ATEMSYS_T_PCI_SELECT_DESC_v1_3_05 oPciDesc_v1_3_05;
    ATEMSYS_T_PCI_SELECT_DESC_v1_4_12 oPciDesc_v1_4_12;




    switch (size)
    {
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00):
    {
        dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,0,0);
        memset(&oPciDesc_v1_0_00, 0, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00));
        if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_PCI_SELECT_DESC_v1_0_00*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00)))
        {
            nRetVal = -EFAULT;
        }
        nRetVal = copy_from_user(&oPciDesc_v1_0_00, (ATEMSYS_T_PCI_SELECT_DESC_v1_0_00*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_finddevice failed: %d\n", nRetVal);
            goto Exit;
        }
        nVendor   = oPciDesc_v1_0_00.nVendID;
        nDevice   = oPciDesc_v1_0_00.nDevID;
        nInstance = oPciDesc_v1_0_00.nInstance;
    } break;
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05):
    {
        dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,3,5);
        memset(&oPciDesc_v1_3_05, 0, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05));
        if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_PCI_SELECT_DESC_v1_3_05*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05)))
        {
            nRetVal = -EFAULT;
        }
        nRetVal = copy_from_user(&oPciDesc_v1_3_05, (ATEMSYS_T_PCI_SELECT_DESC_v1_3_05*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_finddevice failed: %d\n", nRetVal);
            goto Exit;
        }
        nVendor   = oPciDesc_v1_3_05.nVendID;
        nDevice   = oPciDesc_v1_3_05.nDevID;
        nInstance = oPciDesc_v1_3_05.nInstance;
    } break;
    case sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12):
    {
        dwAtemsysApiVersion = EC_ATEMSYSVERSION(1,4,12);
        memset(&oPciDesc_v1_4_12, 0, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12));
        if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_PCI_SELECT_DESC_v1_4_12*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12)))
        {
            nRetVal = -EFAULT;
        }
        nRetVal = copy_from_user(&oPciDesc_v1_4_12, (ATEMSYS_T_PCI_SELECT_DESC_v1_4_12*)ioctlParam, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_finddevice failed: %d\n", nRetVal);
            goto Exit;
        }
        nVendor   = oPciDesc_v1_4_12.nVendID;
        nDevice   = oPciDesc_v1_4_12.nDevID;
        nInstance = oPciDesc_v1_4_12.nInstance;
    } break;
    default:
    {
        nRetVal = -EIO;
        ERR("pci_conf: Invalid parameter\n");
        goto Exit;
    }
    }

    if (-EFAULT == nRetVal)
    {
        ERR("pci_find: EFAULT\n");
        nRetVal = -EFAULT;
        goto Exit;
    }

    INF("pci_find: ven 0x%x dev 0x%x nInstance %d\n", nVendor, nDevice, nInstance);

    for (nInstanceId = 0; nInstanceId <= nInstance; nInstanceId++ )
    {
        pPciDev = pci_get_device (nVendor, nDevice, pPciDev);
    }

    if (pPciDev == NULL)
    {
        WRN("pci_find: device 0x%x:0x%x:%d not found\n", nVendor, nDevice, nInstance);
        nRetVal = -ENODEV;
        goto Exit;
    }

    INF("pci_find: found 0x%x:0x%x:%d -> %s\n",
       nVendor, nDevice, nInstance, pci_name(pPciDev));

    switch (dwAtemsysApiVersion)
    {
    case EC_ATEMSYSVERSION(1,0,0):
    {
        oPciDesc_v1_0_00.nPciBus = (s32)pPciDev->bus->number;
        oPciDesc_v1_0_00.nPciDev = (s32)PCI_SLOT(pPciDev->devfn);
        oPciDesc_v1_0_00.nPciFun = (s32)PCI_FUNC(pPciDev->devfn);

        nRetVal = copy_to_user((ATEMSYS_T_PCI_SELECT_DESC_v1_0_00*)ioctlParam, &oPciDesc_v1_0_00, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_0_00));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_finddevice failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case EC_ATEMSYSVERSION(1,3,5):
    {
        oPciDesc_v1_3_05.nPciDomain = (s32)pci_domain_nr(pPciDev->bus);
        oPciDesc_v1_3_05.nPciBus    = (s32)pPciDev->bus->number;
        oPciDesc_v1_3_05.nPciDev    = (s32)PCI_SLOT(pPciDev->devfn);
        oPciDesc_v1_3_05.nPciFun    = (s32)PCI_FUNC(pPciDev->devfn);

        nRetVal = copy_to_user((ATEMSYS_T_PCI_SELECT_DESC_v1_3_05*)ioctlParam, &oPciDesc_v1_3_05, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_3_05));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_finddevice failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case EC_ATEMSYSVERSION(1,4,12):
    {
        oPciDesc_v1_4_12.nPciDomain = (s32)pci_domain_nr(pPciDev->bus);
        oPciDesc_v1_4_12.nPciBus    = (s32)pPciDev->bus->number;
        oPciDesc_v1_4_12.nPciDev    = (s32)PCI_SLOT(pPciDev->devfn);
        oPciDesc_v1_4_12.nPciFun    = (s32)PCI_FUNC(pPciDev->devfn);

        nRetVal = copy_to_user((ATEMSYS_T_PCI_SELECT_DESC_v1_4_12*)ioctlParam, &oPciDesc_v1_4_12, sizeof(ATEMSYS_T_PCI_SELECT_DESC_v1_4_12));
        if (0 != nRetVal)
        {
            ERR("ioctl_pci_finddevice failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    }

    nRetVal = 0;

Exit:
    return nRetVal;
}
#endif /* CONFIG_PCI */

#if (defined CONFIG_DTC)
/*
 * Lookup Nth (0: first) compatible device tree node with "interrupts" property present.
 */
static struct device_node * atemsys_of_lookup_intnode(const char* compatible, int deviceIdx)
{
   struct device_node* device = NULL;
   struct device_node* child = NULL;
   struct device_node* tmp = NULL;
   int devCnt;

   /* Lookup Nth device tree node */
   devCnt = 0;
   for_each_compatible_node(tmp, NULL, compatible)
   {
      if (devCnt == deviceIdx)
      {
         device = tmp;
         break;
      }
      ++devCnt;
   }

   if (device == NULL) return NULL;

   if (of_get_property(device, "interrupts", NULL)) return device;

   /* i.e. vETSEC has 2 groups. Search them */
   for_each_child_of_node(device, child)
   {
      if (of_get_property(child, "interrupts", NULL)) return child;
   }

   return NULL;
}

/*
 * Map interrupt number taken from the OF Device Tree (\sa .dts file) into
 * virtual interrupt number which can be passed to request_irq().
 * The usual (device driver) way is to use the irq_of_parse_and_map() function.
 *
 * We search all device tree nodes which have the "compatible" property
 * equal to compatible. Search until the Nth device is found. Then
 * map the Nth interrupt (given by intIdx) with irq_of_parse_and_map().
 */
static unsigned atemsys_of_map_irq_to_virq(const char* compatible, int deviceIdx, int intIdx)
{
   unsigned virq;
   struct device_node* device = NULL;

   /* Lookup Nth device */
   device = atemsys_of_lookup_intnode(compatible, deviceIdx);
   if (! device)
   {
      INF("atemsys_of_map_irq_to_virq: device tree node '%s':%d not found.\n",
         compatible, deviceIdx);
      return NO_IRQ;
   }

   virq = irq_of_parse_and_map(device, intIdx);
   if (virq == NO_IRQ)
   {
      ERR("atemsys_of_map_irq_to_virq: irq_of_parse_and_map failed for"
          " device tree node '%s':%d, IntIdx %d.\n",
         compatible, deviceIdx, intIdx);
   }

   return virq;
}
#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
static unsigned int atemsysDtDriver_of_map_irq_to_virq(ATEMSYS_T_DEVICE_DESC* pDevDesc, int nIdx)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    struct device_node*         device          = NULL;
    unsigned int                irq;
    unsigned int                i               = 0;

    /* get node from atemsys platform driver list */
    for (i = 0; i < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; i++)
    {

        pDrvDescPrivate = S_apDrvDescPrivate[i];
        if (NULL == pDrvDescPrivate)
        {
            continue;
        }

        if (pDrvDescPrivate->pDevDesc == pDevDesc)
        {
            device = pDrvDescPrivate->pDevNode;
            break;
        }
    }
    if ((NULL == device) || (NULL == pDrvDescPrivate))
    {
        INF("atemsysDtDriver_of_map_irq_to_virq: Cannot find connected device tree node\n");
        return NO_IRQ;
    }

    /* get interrupt from node */
    irq = irq_of_parse_and_map(device, nIdx);
    if (NO_IRQ == irq)
    {
        ERR("atemsysDtDriver_of_map_irq_to_virq: irq_of_parse_and_map failed for"
            " device tree node Interrupt index %d\n",
            nIdx);
    }

    return irq;
}
#endif /* INCLUDE_ATEMSYS_DT_DRIVER) */
#endif /* CONFIG_DTC */

#if (defined INCLUDE_IRQ_TO_DESC)
static bool atemsys_irq_is_level(unsigned int irq_id)
{
     bool irq_is_level = true;
     struct irq_data* irq_data = NULL;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,11,1))
    {
        irq_data = irq_get_irq_data(irq_id);
    }
#else
    {
        struct irq_desc* desc;
        desc = irq_to_desc(irq_id);
        if (desc)
        {
            irq_data = &desc->irq_data;
        }
    }
#endif
    if (irq_data)
    {
        irq_is_level = irqd_is_level_type(irq_data);
    }

    return irq_is_level;
}
#endif /* INCLUDE_IRQ_TO_DESC */

static int ioctl_int_connect(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam)
{
    int nRetVal = -EIO;
    int nRc;
    ATEMSYS_T_IRQ_DESC* pIrqDesc = NULL;
    unsigned int irq = 0;

#if (defined CONFIG_PCI)
    if (ioctlParam == ATEMSYS_USE_PCI_INT)
    {
        /* Use IRQ number from selected PCI device */

        if (pDevDesc->pPcidev == NULL)
        {
            WRN("intcon: error call ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE) first\n");
            goto Exit;
        }

        irq = pDevDesc->pPcidev->irq;
        INF("intcon: Use IRQ (%d) from PCI config\n", irq);
    }
    else
#endif /* CONFIG_PCI */
    {
#if (defined CONFIG_DTC)
        /* The ioctlParam is the Nth compatible device in the OF device tree (0: first, 1: second, ...)
         * TODO "compatible string" and "interrupt index" should be provided by usermode as IOCTL param
         */
        if ( /* Use interrupt number at idx 1 (Rx-Interrupt) for TSEC / eTSEC */
             ((irq = atemsys_of_map_irq_to_virq("fsl,etsec2", ioctlParam, 1)) == NO_IRQ) /* PPC, eTSEC */
          && ((irq = atemsys_of_map_irq_to_virq("gianfar", ioctlParam, 1)) == NO_IRQ) /* PPC, eTSEC */
          /* PRU-ICSS for am572x, am335x */
          && ((irq = atemsys_of_map_irq_to_virq("acontis,device", 0, ioctlParam)) == NO_IRQ)
          /* Use interrupt number at idx 0 (Catch-All-Interrupt) for GEM */
          && ((irq = atemsys_of_map_irq_to_virq("xlnx,ps7-ethernet-1.00.a", ioctlParam, 0)) == NO_IRQ) /* ARM, Xilinx Zynq */
           )
        {
#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
            /* Get Interrupt from binded device tree node */
            if ((irq = atemsysDtDriver_of_map_irq_to_virq(pDevDesc, ioctlParam)) == NO_IRQ)
#endif
            {
                nRetVal = -EPERM;
                goto Exit;
            }
        }

#else
        /* Use IRQ number passed as ioctl argument */
        irq = ioctlParam;
        INF("intcon: Use IRQ (%d) passed by user\n", irq);
#endif
    }

    pIrqDesc = &pDevDesc->irqDesc;
    if (pIrqDesc->irq)
    {
        WRN("intcon: error IRQ %u already connected. Call ioctl(ATEMSYS_IOCTL_INT_DISCONNECT) first\n",
            (unsigned) pIrqDesc->irq);
        goto Exit;
    }

    /* Setup some data which is needed during Interrupt handling */
    memset(pIrqDesc, 0, sizeof(ATEMSYS_T_IRQ_DESC));
    atomic_set(&pIrqDesc->count, 0);
    atomic_set(&pIrqDesc->totalCount, 0);

#if (defined CONFIG_XENO_COBALT)
    rtdm_event_init(&pIrqDesc->irq_event, 0);
    nRc = rtdm_irq_request(&pIrqDesc->irq_handle, irq, dev_interrupt_handler, 0, ATEMSYS_DEVICE_NAME, pDevDesc);
    if (nRc)
    {
        ERR("ioctl_int_connect: rtdm_irq_request() for IRQ %d returned error: %d\n", irq, nRc);
        nRetVal = nRc;
        goto Exit;
    }
    nRc = rtdm_irq_enable(&pIrqDesc->irq_handle);
    if (nRc)
    {
        ERR("ioctl_int_connect: rtdm_irq_enable() for IRQ %d returned error: %d\n", irq, nRc);
        nRetVal = nRc;
        goto Exit;
    }
#else
    init_waitqueue_head(&pIrqDesc->q);
    atomic_set(&pIrqDesc->irqStatus, 1); /* IRQ enabled */

    /* Setup non shared IRQ */
    nRc = request_irq(irq, dev_interrupt_handler, 0, ATEMSYS_DEVICE_NAME, pDevDesc);
    if (nRc)
    {
        ERR("ioctl_int_connect: request_irq (IRQ %d) failed. Err %d\n", irq, nRc);
        nRetVal = -EPERM;
        goto Exit;
    }
#endif /* CONFIG_XENO_COBALT */

    pIrqDesc->irq = irq;
#if (defined INCLUDE_IRQ_TO_DESC)
    pIrqDesc->irq_is_level = atemsys_irq_is_level(irq);
#endif

#if (defined INCLUDE_IRQ_TO_DESC)
    INF("intcon: IRQ %d connected, irq_is_level = %d\n", irq, pIrqDesc->irq_is_level);
#else
    INF("intcon: IRQ %d connected\n", irq);
#endif

    nRetVal = 0;
Exit:
    return nRetVal;
}

static int ioctl_intinfo(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam)
{
   int nRetVal = -EIO;
#if (defined CONFIG_XENO_COBALT)
   ATEMSYS_T_INT_INFO* pIntInfo = (ATEMSYS_T_INT_INFO*) ioctlParam;
   struct rtdm_fd* fd = rtdm_private_to_fd(pDevDesc);
   if (rtdm_fd_is_user(fd))
   {
      nRetVal = rtdm_safe_copy_to_user(fd, &pIntInfo->dwInterrupt, &pDevDesc->irqDesc.irq, sizeof(__u32));
      if (nRetVal)
      {
         ERR("ioctl_intinfo failed: %d\n", nRetVal);
         goto Exit;
      }
   }
#else
   ATEMSYS_T_INT_INFO oIntInfo;
   memset(&oIntInfo, 0, sizeof(ATEMSYS_T_INT_INFO));
   if (!ACCESS_OK(VERIFY_WRITE, (ATEMSYS_T_INT_INFO*)ioctlParam, sizeof(ATEMSYS_T_INT_INFO)))
   {
      ERR("ioctl_intinfo: EFAULT\n");
      nRetVal = -EFAULT;
      goto Exit;
   }
   oIntInfo.dwInterrupt = pDevDesc->irqDesc.irq;
   nRetVal = copy_to_user((ATEMSYS_T_INT_INFO*)ioctlParam, &oIntInfo, sizeof(ATEMSYS_T_INT_INFO));
   if (0 != nRetVal)
   {
      ERR("ioctl_intinfo failed: %d\n", nRetVal);
      goto Exit;
   }
#endif /* CONFIG_XENO_COBALT */

Exit:
    return nRetVal;
}


static int dev_int_disconnect(ATEMSYS_T_DEVICE_DESC* pDevDesc)
{
   int nRetVal = -EIO;
   int nCnt;
   ATEMSYS_T_IRQ_DESC* pIrqDesc = &(pDevDesc->irqDesc);

#if (defined CONFIG_XENO_COBALT)
      int nRc;
      if (pIrqDesc->irq)
      {
         nRc = rtdm_irq_disable(&pIrqDesc->irq_handle);
         if (nRc)
         {
            ERR("dev_int_disconnect: rtdm_irq_disable() for IRQ %d returned error: %d\n", (u32) pIrqDesc->irq, nRc);
            nRetVal = nRc;
            goto Exit;
         }

         nRc = rtdm_irq_free(&pIrqDesc->irq_handle);
         if (nRc)
         {
            ERR("dev_int_disconnect: rtdm_irq_free() for IRQ %d returned error: %d\n", (u32) pIrqDesc->irq, nRc);
            nRetVal = nRc;
            goto Exit;
         }

         nCnt = atomic_read(&pIrqDesc->totalCount);
         INF("pci_intdcon: IRQ %u disconnected. %d interrupts rcvd\n", (u32) pIrqDesc->irq, nCnt);

         pIrqDesc->irq = 0;
         rtdm_event_signal(&pIrqDesc->irq_event);
      }
#else
      if (pIrqDesc->irq)
      {
         /* Disable INT line. We can call this, because we only allow exclusive interrupts */
         disable_irq_nosync(pIrqDesc->irq);

         /* Unregister INT routine.This will block until all pending interrupts are handled */
         free_irq(pIrqDesc->irq, pDevDesc);

         nCnt = atomic_read(&pIrqDesc->totalCount);
         INF("pci_intdcon: IRQ %u disconnected. %d interrupts rcvd\n", (u32) pIrqDesc->irq, nCnt);

         pIrqDesc->irq = 0;

         /* Wakeup sleeping threads -> read() */
         wake_up(&pIrqDesc->q);
      }
#endif /* CONFIG_XENO_COBALT */
   nRetVal = 0;

#if (defined CONFIG_XENO_COBALT)
Exit:
#endif
   return nRetVal;
}

#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
#ifdef CONFIG_TI_K3_UDMA

#if (LINUX_VERSION_CODE > KERNEL_VERSION(5,10,0))
 #define CPSWG_STRUCT_VERSION_2 1
#endif

#include <linux/soc/ti/k3-ringacc.h>
#include <linux/soc/ti/ti_sci_protocol.h>
#include <linux/soc/ti/ti_sci_protocol.h>

/* from */
struct k3_ring_state {
    u32 free;
    u32 occ;
    u32 windex;
    u32 rindex;
#ifdef CPSWG_STRUCT_VERSION_2
    u32 tdown_complete:1;
#endif
};

struct k3_ring {
    struct k3_ring_rt_regs __iomem *rt;
    struct k3_ring_fifo_regs __iomem *fifos;
    struct k3_ringacc_proxy_target_regs  __iomem *proxy;
    dma_addr_t  ring_mem_dma;
    void        *ring_mem_virt;
    struct k3_ring_ops *ops;
    u32     size;
    enum k3_ring_size elm_size;
    enum k3_ring_mode mode;
    u32     flags;
#define K3_RING_FLAG_BUSY   BIT(1)
#define K3_RING_FLAG_SHARED BIT(2)
#ifdef CPSWG_STRUCT_VERSION_2
 #define K3_RING_FLAG_REVERSE BIT(3)
#endif
    struct k3_ring_state state;
    u32     ring_id;
    struct k3_ringacc   *parent;
    u32     use_count;
    int     proxy_id;
#ifdef CPSWG_STRUCT_VERSION_2
    struct device   *dma_dev;
    u32     asel;
#define K3_ADDRESS_ASEL_SHIFT   48
#endif
};

struct k3_udma_glue_common {
    struct device *dev;
#ifdef CPSWG_STRUCT_VERSION_2
    struct device chan_dev;
#endif
    struct udma_dev *udmax;
    const struct udma_tisci_rm *tisci_rm;
    struct k3_ringacc *ringacc;
    u32 src_thread;
    u32 dst_thread;

    u32  hdesc_size;
    bool epib;
    u32  psdata_size;
    u32  swdata_size;
    u32  atype;
#ifdef CPSWG_STRUCT_VERSION_2
    struct psil_endpoint_config *ep_config;
#endif
};

struct k3_udma_glue_tx_channel {
    struct k3_udma_glue_common common;

    struct udma_tchan *udma_tchanx;
    int udma_tchan_id;

    struct k3_ring *ringtx;
    struct k3_ring *ringtxcq;

    bool psil_paired;

    int virq;

    atomic_t free_pkts;
    bool tx_pause_on_err;
    bool tx_filt_einfo;
    bool tx_filt_pswords;
    bool tx_supr_tdpkt;
#ifdef CPSWG_STRUCT_VERSION_2
    int udma_tflow_id;
#endif
};



struct k3_udma_glue_rx_flow {
    struct udma_rflow *udma_rflow;
    int udma_rflow_id;
    struct k3_ring *ringrx;
    struct k3_ring *ringrxfdq;

    int virq;
};

struct k3_udma_glue_rx_channel {
    struct k3_udma_glue_common common;

    struct udma_rchan *udma_rchanx;
    int udma_rchan_id;
    bool remote;

    bool psil_paired;

    u32  swdata_size;
    int  flow_id_base;

    struct k3_udma_glue_rx_flow *flows;
    u32 flow_num;
    u32 flows_ready;
};


#define AM65_CPSW_NAV_SW_DATA_SIZE 16
#define AM65_CPSW_MAX_RX_FLOWS  1

#include "../drivers/dma/ti/k3-udma.h"

#include <linux/dma/k3-udma-glue.h>
void cleanup(void *data, dma_addr_t desc_dma)
{
    return;
}

static int CpswgCmd(void* arg,  ATEMSYS_T_CPSWG_CMD* pConfig)
{
    struct k3_udma_glue_tx_channel** ppTxChn = NULL;
    struct k3_udma_glue_rx_channel** ppRxChn = NULL;
    __u32* pnTxIrq;
    __u32* pnRxIrq;
    ATEMSYS_T_CPSWG_CMD oConfig;
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    memset(&oConfig, 0, sizeof(ATEMSYS_T_CPSWG_CMD));

    if (NULL == pConfig)
    {
        nRetVal = copy_from_user(&oConfig, (ATEMSYS_T_CPSWG_CMD *)arg, sizeof(ATEMSYS_T_CPSWG_CMD));
    }
    else
    {
        memcpy(&oConfig, pConfig, sizeof(ATEMSYS_T_CPSWG_CMD));
        nRetVal = 0;
    }
    if (0 != nRetVal)
    {
        ERR("CpswgCmd(): failed: %d\n", nRetVal);
        goto Exit;
    }
    if (oConfig.dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[oConfig.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        ERR("CpswgCmd(): cant find instance\n");
        nRetVal = -EBUSY;
        goto Exit;
    }

    DBG("CpswgCmd(): dwCmd: %d\n", oConfig.dwCmd);
    ppTxChn = (struct k3_udma_glue_tx_channel**)&pDrvDescPrivate->apvTxChan[oConfig.dwChannelIdx];
    ppRxChn = (struct k3_udma_glue_rx_channel**)&pDrvDescPrivate->apvRxChan[oConfig.dwChannelIdx];
    pnTxIrq = &pDrvDescPrivate->anTxIrq[oConfig.dwChannelIdx];
    pnRxIrq = &pDrvDescPrivate->anRxIrq[oConfig.dwChannelIdx];


    switch (oConfig.dwCmd)
    {
    case ATEMSYS_CPSWG_CMD_CONFIG_TX:
    {
        char tx_chn_name[128];
        struct k3_ring_cfg ring_cfg =
        {
            .elm_size = K3_RINGACC_RING_ELSIZE_8,
            .mode = K3_RINGACC_RING_MODE_RING,
            .flags = 0
        };
        struct k3_udma_glue_tx_channel_cfg tx_cfg = { 0 };

        tx_cfg.swdata_size = AM65_CPSW_NAV_SW_DATA_SIZE;
        tx_cfg.tx_cfg = ring_cfg;
        tx_cfg.txcq_cfg = ring_cfg;
        tx_cfg.tx_cfg.size = oConfig.dwRingSize;
        tx_cfg.txcq_cfg.size = oConfig.dwRingSize;
        snprintf(tx_chn_name, sizeof(tx_chn_name), "tx%d", 0);

        *ppTxChn = k3_udma_glue_request_tx_chn(&pDrvDescPrivate->pPDev->dev,
                            tx_chn_name,
                            &tx_cfg);
        if (IS_ERR(*ppTxChn))
        {
            ERR("CpswgCmd(): Failed to request tx dma channel %ld\n", PTR_ERR(*ppTxChn));
            *ppTxChn = NULL;
            goto Exit;
        }

        *pnTxIrq = k3_udma_glue_tx_get_irq(*ppTxChn);
        if (*pnTxIrq <= 0)
        {
            ERR("CpswgCmd(): Failed to get tx dma irq %d\n", *pnTxIrq);
            goto Exit;
        }

        {
            struct k3_udma_glue_tx_channel* pData = (struct k3_udma_glue_tx_channel*)*ppTxChn;
            DBG("CpswgCmd(): k3_udma_glue_request_tx_chn(): udma_tchan_id:0x%x, ringtx:0x%x::0x%px, ringtxcq:0x%x::0x%px\n",
            pData->udma_tchan_id,
            pData->ringtx->ring_id, (unsigned char*)NULL + pData->ringtx->ring_mem_dma,
            pData->ringtxcq->ring_id, (unsigned char*)NULL + pData->ringtxcq->ring_mem_dma);

            oConfig.dwChanId = pData->udma_tchan_id;
            oConfig.dwRingId = pData->ringtx->ring_id;
            oConfig.qwRingDma = pData->ringtx->ring_mem_dma;
            oConfig.dwRingSize = pData->ringtx->size;
            oConfig.dwRingFdqId = pData->ringtxcq->ring_id;
            oConfig.qwRingFdqDma = pData->ringtxcq->ring_mem_dma;
            oConfig.dwRingFdqSize = pData->ringtxcq->size;

            nRetVal = copy_to_user((ATEMSYS_T_CPSWG_CMD *)arg, &oConfig, sizeof(ATEMSYS_T_CPSWG_CMD));
            if (0 != nRetVal)
            {
                ERR("CpswgCmd(): copy_to_user() failed: %d\n", nRetVal);
            }
        }
    } break;
    case ATEMSYS_CPSWG_CMD_CONFIG_RX:
    {
        u32  rx_flow_id_base = -1;
        u32 fdqring_id;

        struct k3_udma_glue_rx_channel_cfg rx_cfg = { 0 };

        rx_cfg.swdata_size = AM65_CPSW_NAV_SW_DATA_SIZE;
        rx_cfg.flow_id_num = AM65_CPSW_MAX_RX_FLOWS;
        rx_cfg.flow_id_base = rx_flow_id_base;

        *ppRxChn = k3_udma_glue_request_rx_chn(&pDrvDescPrivate->pPDev->dev, "rx", &rx_cfg);
        if (IS_ERR(*ppRxChn)) {
            ERR("CpswgCmd(): Failed to request rx dma channel %ld\n", PTR_ERR(*ppRxChn));
           *ppRxChn = NULL;
            goto Exit;
        }

        rx_flow_id_base = k3_udma_glue_rx_get_flow_id_base(*ppRxChn);
        fdqring_id = K3_RINGACC_RING_ID_ANY;
        /*for*/
        {
            u32 i = 0;
            struct k3_ring_cfg rxring_cfg = {
                .elm_size = K3_RINGACC_RING_ELSIZE_8,
                .mode = K3_RINGACC_RING_MODE_RING,
                .flags = 0,
            };
            struct k3_ring_cfg fdqring_cfg = {
                .elm_size = K3_RINGACC_RING_ELSIZE_8,
                .mode = K3_RINGACC_RING_MODE_MESSAGE,
                .flags = K3_RINGACC_RING_SHARED,
            };
            struct k3_udma_glue_rx_flow_cfg rx_flow_cfg = {
                .rx_cfg = rxring_cfg,
                .rxfdq_cfg = fdqring_cfg,
                .ring_rxq_id = K3_RINGACC_RING_ID_ANY,
                .src_tag_lo_sel = K3_UDMA_GLUE_SRC_TAG_LO_USE_REMOTE_SRC_TAG,
            };

            rx_flow_cfg.ring_rxfdq0_id = fdqring_id;
            rx_flow_cfg.rx_cfg.size = oConfig.dwRingSize;
            rx_flow_cfg.rxfdq_cfg.size = oConfig.dwRingSize;

            nRetVal = k3_udma_glue_rx_flow_init(*ppRxChn, i, &rx_flow_cfg);
            if (nRetVal) {
                ERR("CpswgCmd(): Failed to init rx flow%d %d\n", i, nRetVal);
                goto Exit;
            }
            if (!i)
                fdqring_id = k3_udma_glue_rx_flow_get_fdq_id(*ppRxChn, i);

            *pnRxIrq = k3_udma_glue_rx_get_irq(*ppRxChn, i);

            if (*pnRxIrq <= 0) {
                ERR("CpswgCmd(): Failed to get rx dma irq %d\n", *pnRxIrq);
                goto Exit;
            }
        }
        {
            struct k3_udma_glue_rx_flow* pData = (struct k3_udma_glue_rx_flow*)(*ppRxChn)->flows;

            DBG("CpswgCmd(): k3_udma_glue_request_tx_chn(): udma_rflow_id:0x%x, rx_flow_id_base:0x%x, ringrx:0x%x::0x%px, ringrxfdq:0x%x::0x%px\n",
            pData->udma_rflow_id, rx_flow_id_base,
            pData->ringrx->ring_id, (unsigned char*)NULL + pData->ringrx->ring_mem_dma,
            pData->ringrxfdq->ring_id, (unsigned char*)NULL + pData->ringrxfdq->ring_mem_dma);

            oConfig.dwChanId = pData->udma_rflow_id;
            oConfig.dwRingId = pData->ringrx->ring_id;
            oConfig.qwRingDma = pData->ringrx->ring_mem_dma;
            oConfig.dwRingSize = pData->ringrx->size;
            oConfig.dwRingFdqId = pData->ringrxfdq->ring_id;
            oConfig.qwRingFdqDma = pData->ringrxfdq->ring_mem_dma;
            oConfig.dwRingFdqSize = pData->ringrxfdq->size;
            oConfig.dwFlowIdBase = rx_flow_id_base;

            nRetVal = copy_to_user((ATEMSYS_T_CPSWG_CMD *)arg, &oConfig, sizeof(ATEMSYS_T_CPSWG_CMD));
            if (0 != nRetVal)
            {
                ERR("CpswgCmd(): copy_to_user() failed: %d\n", nRetVal);
            }
        }
    } break;
    case ATEMSYS_CPSWG_CMD_ENABLE_TX:
    {
        if (NULL == *ppTxChn)
        {
            nRetVal = -1;
            ERR("CpswgCmd(): tx channel not ready %d\n", nRetVal);
            goto Exit;
        }
        nRetVal = k3_udma_glue_enable_tx_chn(*ppTxChn);
        if (nRetVal)
        {
            ERR("CpswgCmd(): k3_udma_glue_enable_tx_chn() failed %d\n", nRetVal);
            goto Exit;
        }

    } break;
    case ATEMSYS_CPSWG_CMD_ENABLE_RX:
    {
        if (NULL == *ppRxChn)
        {
            nRetVal = -1;
            ERR("CpswgCmd(): rx channel not ready %d\n", nRetVal);
            goto Exit;
        }
        nRetVal = k3_udma_glue_enable_rx_chn(*ppRxChn);
        if (nRetVal) {
            ERR("CpswgCmd(): k3_udma_glue_enable_rx_chn() failed %d\n", nRetVal);
            goto Exit;
        }

    } break;
    case ATEMSYS_CPSWG_CMD_DISABLE_TX:
    {
        if (NULL == *ppTxChn)
        {
            nRetVal = -1;
            ERR("CpswgCmd(): tx channel not ready %d\n", nRetVal);
            goto Exit;
        }
        //for (i = 0; i < tx_ch_num; i++)
            k3_udma_glue_tdown_tx_chn(*ppTxChn, false);

        //for (i = 0; i < tx_ch_num; i++)
        {
            k3_udma_glue_reset_tx_chn(*ppTxChn, NULL, cleanup);
            k3_udma_glue_disable_tx_chn(*ppTxChn);
        }
    } break;
    case ATEMSYS_CPSWG_CMD_DISABLE_RX:
    {
        int i = 0;
        if (NULL == *ppRxChn)
        {
            nRetVal = -1;
            ERR("CpswgCmd(): rx channel not ready %d\n", nRetVal);
            goto Exit;
        }
        k3_udma_glue_tdown_rx_chn(*ppRxChn, true);
        for (i = 0; i < AM65_CPSW_MAX_RX_FLOWS; i++)
            k3_udma_glue_reset_rx_chn(*ppRxChn, i, NULL, cleanup, !!i);

        k3_udma_glue_disable_rx_chn(*ppRxChn);
    } break;
    case ATEMSYS_CPSWG_CMD_RELEASE_TX:
    {
        if (NULL == *ppTxChn)
        {
            nRetVal = -1;
            ERR("CpswgCmd(): tx channel not ready %d\n", nRetVal);
            goto Exit;
        }
        k3_udma_glue_release_tx_chn(*ppTxChn);
        *ppTxChn = NULL;
    } break;
    case ATEMSYS_CPSWG_CMD_RELEASE_RX:
    {
        if (NULL == *ppRxChn)
        {
            nRetVal = -1;
            ERR("CpswgCmd(): rx channel not ready %d\n", nRetVal);
            goto Exit;
        }
        k3_udma_glue_release_rx_chn(*ppRxChn);
        *ppRxChn = NULL;
    } break;
    }



Exit:
    return nRetVal;
}



static void CleanCpswgCmd(ATEMSYS_T_DEVICE_DESC* pDevDesc)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    ATEMSYS_T_CPSWG_CMD oConfig;
    unsigned int dwChannelIdx = 0;
    unsigned int dwIndex = 0;
    if (pDevDesc == NULL)
    {
       return;
    }
    for (dwIndex = 0; dwIndex < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; dwIndex++)
    {
        if ((NULL != S_apDrvDescPrivate[dwIndex]) && pDevDesc == S_apDrvDescPrivate[dwIndex]->pDevDesc)
        {
            pDrvDescPrivate = S_apDrvDescPrivate[dwIndex];
            break;
        }
    }
    if (pDrvDescPrivate == NULL)
    {
        return;
    }
    for (dwChannelIdx = 0; ATEMSYS_UDMA_CHANNELS > dwChannelIdx; dwChannelIdx++)
    {
        void** ppvTxChn = &pDrvDescPrivate->apvTxChan[dwChannelIdx];
        void** ppvRxChn = &pDrvDescPrivate->apvRxChan[dwChannelIdx];

        if ((NULL != ppvTxChn) && (NULL != *ppvTxChn))
        {
            memset(&oConfig, 0, sizeof(ATEMSYS_T_CPSWG_CMD));
            oConfig.dwIndex = dwIndex;
            oConfig.dwChannelIdx = dwChannelIdx;
            oConfig.dwCmd = ATEMSYS_CPSWG_CMD_DISABLE_TX;
            CpswgCmd(NULL,  &oConfig);
            oConfig.dwCmd = ATEMSYS_CPSWG_CMD_RELEASE_TX;
            CpswgCmd(NULL,  &oConfig);
        }
        if ((NULL != ppvRxChn) && (NULL != *ppvRxChn))
        {
            memset(&oConfig, 0, sizeof(ATEMSYS_T_CPSWG_CMD));
            oConfig.dwIndex = dwIndex;
            oConfig.dwChannelIdx = dwChannelIdx;
            oConfig.dwCmd = ATEMSYS_CPSWG_CMD_DISABLE_RX;
            CpswgCmd(NULL,  &oConfig);
            oConfig.dwCmd = ATEMSYS_CPSWG_CMD_RELEASE_RX;
            CpswgCmd(NULL,  &oConfig);
        }
    }
}
#endif /*#ifdef CONFIG_TI_K3_UDMA*/


static int IoMemCmd(void* arg)
{
    ATEMSYS_T_IOMEM_CMD oIoMem;
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    unsigned int dwRetVal = 0;
    int nRetVal = -1;
    unsigned int dwIndex = 0;
    nRetVal = copy_from_user(&oIoMem, (unsigned long long *)arg, sizeof(ATEMSYS_T_IOMEM_CMD));
    if (0 != nRetVal)
    {
        goto Exit;
    }
    if (oIoMem.dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[oIoMem.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        ERR("IoMemCmd(): cant find instance\n");
        nRetVal = -EBUSY;
        goto Exit;
    }


    if (ATEMSYS_IOMEM_CMD_MAP_PERMANENT == oIoMem.dwCmd)
    {
        for (dwIndex = 0; IOMEMLIST_LENGTH>dwIndex; dwIndex++)
        {
            if (NULL == pDrvDescPrivate->oIoMemList[dwIndex].pbyBase)
            {
                break;
            }
        }
        if (IOMEMLIST_LENGTH < dwIndex)
        {
            nRetVal = -EFAULT;
            goto Exit;
        }
        pDrvDescPrivate->oIoMemList[dwIndex].pbyBase = devm_ioremap(&pDrvDescPrivate->pPDev->dev, oIoMem.qwPhys, oIoMem.dwSize);
        if (NULL == pDrvDescPrivate->oIoMemList[dwIndex].pbyBase )
        {
            pDrvDescPrivate->oIoMemList[dwIndex].pbyBase = NULL;
            nRetVal = -ENOMEM;;
            goto Exit;
        }
        pDrvDescPrivate->oIoMemList[dwIndex].qwPhys = oIoMem.qwPhys;
        pDrvDescPrivate->oIoMemList[dwIndex].dwSize = oIoMem.dwSize;
        DBG("IoMemCmd(): ATEMSYS_IOMEM_CMD_MAP_PERMANENT Virt:0x%px, Phys:0x%px, Size:0x%08x\n", pDrvDescPrivate->oIoMemList[dwIndex].pbyBase, (unsigned char*)NULL + oIoMem.qwPhys, oIoMem.dwSize);
    }
    else
    {
        for (dwIndex = 0; IOMEMLIST_LENGTH>dwIndex; dwIndex++)
        {
            if (pDrvDescPrivate->oIoMemList[dwIndex].qwPhys == oIoMem.qwPhys)
            {
                break;
            }
        }
        if (IOMEMLIST_LENGTH == dwIndex)
        {
            nRetVal = EFAULT;
            goto Exit;
        }

        if (ATEMSYS_IOMEM_CMD_UNMAP_PERMANENT == oIoMem.dwCmd)
        {
            devm_iounmap(&pDrvDescPrivate->pPDev->dev, pDrvDescPrivate->oIoMemList[dwIndex].pbyBase);
            pDrvDescPrivate->oIoMemList[dwIndex].pbyBase = NULL;
            pDrvDescPrivate->oIoMemList[dwIndex].qwPhys = 0;
            pDrvDescPrivate->oIoMemList[dwIndex].dwSize = 0;
        }
        else
        {
            if (ATEMSYS_IOMEM_CMD_WRITE == oIoMem.dwCmd)
            {
                if (sizeof(unsigned int)/* 4 */  == oIoMem.dwDataSize)
                    *(unsigned int*)(pDrvDescPrivate->oIoMemList[dwIndex].pbyBase + oIoMem.dwOffset) = oIoMem.dwData[0];
                else if (sizeof(unsigned long long)/* 8 */ == oIoMem.dwDataSize)
                {
                    *(unsigned long long*)(pDrvDescPrivate->oIoMemList[dwIndex].pbyBase + oIoMem.dwOffset) = *(unsigned long long*)&oIoMem.dwData[0];
                }
                else
                {
                    int i = 0;
                    for (i = 0; i < oIoMem.dwDataSize; i++)
                    {
                        ((unsigned char*)(pDrvDescPrivate->oIoMemList[dwIndex].pbyBase + oIoMem.dwOffset))[i] = ((unsigned char*)oIoMem.dwData)[i];
                    }
                }
            }
            else if (ATEMSYS_IOMEM_CMD_READ == oIoMem.dwCmd)
            {
                if (sizeof(unsigned int)/* 4 */ == oIoMem.dwDataSize)
                    oIoMem.dwData[0] = *(unsigned int*)(pDrvDescPrivate->oIoMemList[dwIndex].pbyBase + oIoMem.dwOffset);
                else
                {
                    int i = 0;
                    for (i = 0; i < oIoMem.dwDataSize; i++)
                    {
                        ((unsigned char*)oIoMem.dwData)[i] = ((unsigned char*)(pDrvDescPrivate->oIoMemList[dwIndex].pbyBase + oIoMem.dwOffset))[i];
                    }
                }
                nRetVal = copy_to_user((unsigned long long *)arg, &oIoMem, sizeof(ATEMSYS_T_IOMEM_CMD));
                if (0 != nRetVal)
                {
                    goto Exit;
                }
            }
        }
    }
    nRetVal = 0;
Exit:
        return nRetVal;
}

static void CleanIoMemCmd(ATEMSYS_T_DEVICE_DESC* pDevDesc)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    unsigned int dwIndex = 0;
    if (pDevDesc == NULL)
    {
        return;
    }
    for (dwIndex = 0; dwIndex < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; dwIndex++)
    {
        pDrvDescPrivate = S_apDrvDescPrivate[dwIndex];
        if (NULL == pDrvDescPrivate)
            continue;
        if (pDrvDescPrivate->pDevDesc == pDevDesc)
            break;
        pDrvDescPrivate = NULL;
    }
    if (NULL == pDrvDescPrivate)
    {
        return;
    }
    for (dwIndex = 0; IOMEMLIST_LENGTH>dwIndex; dwIndex++)
    {
        if (NULL != pDrvDescPrivate->oIoMemList[dwIndex].pbyBase )
        {
            devm_iounmap(&pDrvDescPrivate->pPDev->dev, pDrvDescPrivate->oIoMemList[dwIndex].pbyBase);
            pDrvDescPrivate->oIoMemList[dwIndex].pbyBase = NULL;
            pDrvDescPrivate->oIoMemList[dwIndex].qwPhys = 0;
            pDrvDescPrivate->oIoMemList[dwIndex].dwSize = 0;
        }
    }
}
#endif /*#ifdef INCLUDE_ATEMSYS_DT_DRIVER)*/


#if ((defined CONFIG_SMP) && (LINUX_VERSION_CODE > KERNEL_VERSION(5,14,0)))
static int SetIntCpuAffinityIoctl(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam, size_t size)
{
    int nRetVal = -EIO;
    ATEMSYS_T_IRQ_DESC* pIrqDesc = &(pDevDesc->irqDesc);
    struct cpumask* pCpuMask = 0;

    if (size > sizeof(struct cpumask))
    {
        ERR("SetIntCpuAffinityIoctl: cpu mask length mismatch\n");
        nRetVal = -EINVAL;
        goto Exit;
    }

    /* prepare cpu affinity mask*/
    pCpuMask = (struct cpumask*)kzalloc(sizeof(struct cpumask), GFP_KERNEL);
    if (NULL == pCpuMask)
    {
        ERR("SetIntCpuAffinityIoctl: no memory\n");
        nRetVal = -ENOMEM;
        goto Exit;
    }
    memset(pCpuMask, 0, sizeof(struct cpumask)>size? sizeof(struct cpumask): size);

    nRetVal = copy_from_user(pCpuMask, (struct cpumask *)ioctlParam, size);
    if (0 != nRetVal)
    {
        ERR("SetIntCpuAffinityIoctl failed: %d\n", nRetVal);
        goto Exit;
    }

    /* set cpu affinity mask*/
    if (pIrqDesc->irq)
    {
        nRetVal = irq_set_affinity(pIrqDesc->irq, pCpuMask);
        if (0 != nRetVal)
        {
            ERR("SetIntCpuAffinityIoctl: irq_set_affinity failed: %d\n", nRetVal);
            nRetVal = -EIO;
            goto Exit;
        }
    }

    nRetVal = 0;
Exit:
    if (NULL != pCpuMask)
        kfree(pCpuMask);

    return nRetVal;
}
#endif /* #if ((defined CONFIG_SMP) && (LINUX_VERSION_CODE > KERNEL_VERSION(5,14,0))) */

#if (defined CONFIG_PCI)
static void dev_pci_release(ATEMSYS_T_DEVICE_DESC* pDevDesc)
{
#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
    if (NULL != pDevDesc->pPciDrvDesc)
    {
        INF("pci_release: Disconnect from PCI device driver %s \n", pci_name(pDevDesc->pPcidev));
        pDevDesc->pPciDrvDesc->pDevDesc = NULL;
#if !(defined CONFIG_XENO_COBALT)
        pDevDesc->pPcidev               = NULL;
#endif
        pDevDesc->pPciDrvDesc           = NULL;
    }
    else
#endif

   if (pDevDesc->pPcidev)
   {
      pci_disable_device(pDevDesc->pPcidev);

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29))
      /* Make sure bus master DMA is disabled if the DMA buffers are finally released */
      pci_clear_master(pDevDesc->pPcidev);
#endif
      pci_release_regions(pDevDesc->pPcidev);

      pci_disable_msi(pDevDesc->pPcidev);

      INF("pci_release: PCI device %s released\n", pci_name(pDevDesc->pPcidev));

#if !(defined CONFIG_XENO_COBALT)
      pDevDesc->pPcidev = NULL;
#endif
   }
}
#endif /* CONFIG_PCI */

#if (defined CONFIG_XENO_COBALT)
static int dev_interrupt_handler(rtdm_irq_t* irq_handle)
{
    ATEMSYS_T_DEVICE_DESC* pDevDesc = rtdm_irq_get_arg(irq_handle, ATEMSYS_T_DEVICE_DESC);
    ATEMSYS_T_IRQ_DESC* pIrqDesc = NULL;

    if (pDevDesc != NULL)
    {
        pIrqDesc = &(pDevDesc->irqDesc);
        if (pIrqDesc != NULL)
        {
            atomic_inc(&pIrqDesc->count);
            atomic_inc(&pIrqDesc->totalCount);
            rtdm_event_signal(&pIrqDesc->irq_event);
        }
    }
    return RTDM_IRQ_HANDLED;
}
#else
static irqreturn_t dev_interrupt_handler(int nIrq, void* pParam)
{
   ATEMSYS_T_DEVICE_DESC* pDevDesc = (ATEMSYS_T_DEVICE_DESC*) pParam;
   ATEMSYS_T_IRQ_DESC* pIrqDesc = &(pDevDesc->irqDesc);

   /* Disable IRQ on (A)PIC to prevent interrupt trashing if the ISR is left.
    * In usermode the IRQ must be acknowledged on the device (IO register).
    * The IRQ is enabled again in the read() handler!
    * Just disabling the IRQ here doesn't work with shared IRQs!
    */
   dev_disable_irq(pIrqDesc);

   atomic_inc(&pIrqDesc->count);
   atomic_inc(&pIrqDesc->totalCount);

   /* Wakeup sleeping threads -> read() */
   wake_up(&pIrqDesc->q);

   return IRQ_HANDLED;
}
#endif /* CONFIG_XENO_COBALT */

/*
 * This is called whenever a process attempts to open the device file
 */
#if (defined CONFIG_XENO_COBALT)
static int device_open(struct rtdm_fd* fd, int oflags)
{
   ATEMSYS_T_DEVICE_DESC* pDevDesc = (ATEMSYS_T_DEVICE_DESC*) rtdm_fd_to_private(fd);
   memset(pDevDesc, 0, sizeof(ATEMSYS_T_DEVICE_DESC));
   rtdm_event_init(&pDevDesc->irqDesc.irq_event, 0);
   INF("device_open %s\n", rtdm_fd_device(fd)->label);
#else
static int device_open(struct inode* inode, struct file* file)
{
   ATEMSYS_T_DEVICE_DESC* pDevDesc;

   INF("device_open(0x%px)\n", file);

   /* create device descriptor */
   pDevDesc = (ATEMSYS_T_DEVICE_DESC*) kzalloc(sizeof(ATEMSYS_T_DEVICE_DESC), GFP_KERNEL);
   if (pDevDesc == NULL)
   {
      return -ENOMEM;
   }

   file->private_data = (void*) pDevDesc;

   /* Add descriptor to descriptor list */
   mutex_lock(&S_mtx);
   list_add(&pDevDesc->list, &S_DevNode.list);
   mutex_unlock(&S_mtx);
   try_module_get(THIS_MODULE);
#endif /* CONFIG_XENO_COBALT */

   /* use module's platform device for memory maping and allocation */
   pDevDesc->pPlatformDev = S_pPlatformDev;

   return DRIVER_SUCCESS;
}

#if (defined CONFIG_XENO_COBALT)
static void device_release(struct rtdm_fd* fd)
{
    ATEMSYS_T_DEVICE_DESC* pDevDesc = (ATEMSYS_T_DEVICE_DESC*) rtdm_fd_to_private(fd);
    ATEMSYS_T_IRQ_DESC* pIrqDesc = NULL;
#else
static int device_release(struct inode* inode, struct file* file)
{
   ATEMSYS_T_DEVICE_DESC* pDevDesc = file->private_data;
#endif /* CONFIG_XENO_COBALT */

   /* release device descriptor */
   if (pDevDesc != NULL )
   {
       INF("device_release, pDevDesc = 0x%px\n", pDevDesc);

       /* Try to tear down interrupts if they are on */
       dev_int_disconnect(pDevDesc);

#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
       CleanIoMemCmd(pDevDesc);

 #ifdef CONFIG_TI_K3_UDMA
       CleanCpswgCmd(pDevDesc);
 #endif

       CleanUpEthernetDriverOnRelease(pDevDesc);
#endif

#if (defined CONFIG_PCI)
       /* Try to release PCI resources */
       dev_pci_release(pDevDesc);
#endif

#if (defined CONFIG_XENO_COBALT)
       pIrqDesc = &(pDevDesc->irqDesc);

       if (pIrqDesc != NULL )
       {
          rtdm_event_clear(&pIrqDesc->irq_event);
          rtdm_event_destroy(&pIrqDesc->irq_event);
       }
    }
    return;
#else
       /* Remove descriptor from descriptor list */
       mutex_lock(&S_mtx);

       list_del(&pDevDesc->list);

       mutex_unlock(&S_mtx);

       kfree(pDevDesc);
   }

   module_put(THIS_MODULE);

   return DRIVER_SUCCESS;
#endif /* CONFIG_XENO_COBALT */
}

/*
 * This function is called whenever a process which has already opened the
 * device file attempts to read from it.
 */
 #if (defined CONFIG_XENO_COBALT)
static ssize_t device_read(struct rtdm_fd* fd, void* bufp, size_t len)
{
   ATEMSYS_T_DEVICE_DESC*   pDevDesc = (ATEMSYS_T_DEVICE_DESC*) rtdm_fd_to_private(fd);
   ATEMSYS_T_IRQ_DESC*      pIrqDesc = NULL;
   s32 nPending;
   int ret=0;

   if (! pDevDesc)
   {
      return -EINVAL;
   }

   pIrqDesc = &(pDevDesc->irqDesc);
   if (! pIrqDesc)
   {
      return -EINVAL;
   }

   if (len < sizeof(u32))
   {
      return -EINVAL;
   }

   if (rtdm_in_rt_context() == false)
   {
       return -EINVAL;
   }

   if (rtdm_fd_is_user(fd) == false)
   {
       return -EINVAL;
   }

   ret = rtdm_event_wait(&pIrqDesc->irq_event);
   if (ret)
   {
       return ret;
   }

   nPending = atomic_read(&pIrqDesc->count);

   ret = rtdm_safe_copy_to_user(fd, bufp, &nPending, sizeof(nPending));

   if (ret)
   {
       ERR("device_read: rtdm_safe_copy_to_user() returned error: %d\n", ret);
       return ret;
   }

   atomic_sub(nPending, &pIrqDesc->count);

   return sizeof(nPending);
}
#else
static ssize_t device_read(
      struct file* filp,   /* see include/linux/fs.h   */
      char __user* bufp,   /* buffer to be filled with data */
      size_t       len,    /* length of the buffer     */
      loff_t*      ppos)
{

   ATEMSYS_T_DEVICE_DESC*   pDevDesc = (ATEMSYS_T_DEVICE_DESC*) filp->private_data;
   ATEMSYS_T_IRQ_DESC*      pIrqDesc = NULL;
   s32 nPending;
   wait_queue_entry_t wait;

   if (! pDevDesc)
   {
      return -EINVAL;
   }

   pIrqDesc = &(pDevDesc->irqDesc);

   /* DBG("device_read...(0x%px,0x%px,%d)\n", filp, bufp, len); */

   init_wait(&wait);

   if (len < sizeof(u32))
   {
      return -EINVAL;
   }

   if (pIrqDesc->irq == 0) /* IRQ already disabled */
   {
      return -EINVAL;
   }

   nPending = atomic_read(&pIrqDesc->count);
   if (nPending == 0)
   {
      if (dev_irq_disabled(pIrqDesc))
      {
         dev_enable_irq(pIrqDesc);
      }
      if (filp->f_flags & O_NONBLOCK)
      {
         return -EWOULDBLOCK;
      }
   }

   while (nPending == 0)
   {
      prepare_to_wait(&pIrqDesc->q, &wait, TASK_INTERRUPTIBLE);
      nPending = atomic_read(&pIrqDesc->count);
      if (nPending == 0)
      {
         schedule();
      }
      finish_wait(&pIrqDesc->q, &wait);
      if (pIrqDesc->irq == 0) /* IRQ disabled while waiting for IRQ */
      {
         return -EINVAL;
      }
      if (signal_pending(current))
      {
         return -ERESTARTSYS;
      }
   }

   if (copy_to_user(bufp, &nPending, sizeof(nPending)))
   {
      return -EFAULT;
   }

   *ppos += sizeof(nPending);
   atomic_sub(nPending, &pIrqDesc->count);

   return sizeof(nPending);
}
#endif /* CONFIG_XENO_COBALT */

/*
 * character device mmap method
 */
#if (defined CONFIG_XENO_COBALT)
static int device_mmap(struct rtdm_fd* fd, struct vm_area_struct* vma)
{
   ATEMSYS_T_DEVICE_DESC*   pDevDesc = (ATEMSYS_T_DEVICE_DESC*) rtdm_fd_to_private(fd);
#else
static int device_mmap(struct file* filp, struct vm_area_struct* vma)
{
   ATEMSYS_T_DEVICE_DESC*   pDevDesc = filp->private_data;
#endif /* CONFIG_XENO_COBALT */

   int         nRet = -EIO;
   u32         dwLen;
   void*       pVa = NULL;
   dma_addr_t  dmaAddr;
   ATEMSYS_T_MMAP_DESC* pMmapNode;
#if (defined CONFIG_PCI)
   int         i;
   unsigned long ioBase;
   u32 dwIOLen, dwPageOffset;
#endif

   DBG("mmap: vm_pgoff 0x%px vm_start = 0x%px vm_end = 0x%px\n",
         (void*) vma->vm_pgoff, (void*) vma->vm_start, (void*) vma->vm_end);

   if (pDevDesc == NULL)
   {
      ERR("mmap: Invalid device dtor\n");
      goto Exit;
   }

   dwLen = PAGE_UP(vma->vm_end - vma->vm_start);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,3,0))
   vm_flags_set(vma, VM_RESERVED | VM_LOCKED | VM_DONTCOPY);
#else
   vma->vm_flags |= VM_RESERVED | VM_LOCKED | VM_DONTCOPY;
#endif 

   if (vma->vm_pgoff != 0)
   {
      /* map device IO memory */
#if (defined CONFIG_PCI)
      if (pDevDesc->pPcidev != NULL)
      {
         INF("mmap: Doing PCI device sanity check\n");

         /* sanity check. Make sure that the offset parameter of the mmap() call in userspace
          * corresponds with the PCI base IO address.
          * Make sure the user doesn't map more IO memory than the device provides.
          */
         for (i = 0; i < ATEMSYS_PCI_MAXBAR; i++)
         {
            if (pci_resource_flags(pDevDesc->pPcidev, i) & IORESOURCE_MEM)
            {
               /* IO area address */
               ioBase = PAGE_DOWN( pci_resource_start(pDevDesc->pPcidev, i) );

               dwPageOffset = pci_resource_start(pDevDesc->pPcidev, i) - ioBase;

               /* IO area length */
               dwIOLen = PAGE_UP( pci_resource_len(pDevDesc->pPcidev, i) + dwPageOffset );

               if (    ((vma->vm_pgoff << PAGE_SHIFT) >= ioBase)
                    && (((vma->vm_pgoff << PAGE_SHIFT) + dwLen) <= (ioBase + dwIOLen))
                  )
               {
                  /* for systems where physical address is in x64 space, high dword is not passes from user io
                   * use correct address from pci_resource_start */
                  resource_size_t res_start = pci_resource_start(pDevDesc->pPcidev, i);
                  unsigned long pgoff_new = (res_start>>PAGE_SHIFT);
                  if (pgoff_new != vma->vm_pgoff)
                  {
                      INF("mmap: Correcting page offset from 0x%lx to 0x%lx, for Phys address 0x%llx",
                              vma->vm_pgoff, pgoff_new, (u64)res_start);
                      vma->vm_pgoff =  pgoff_new;
                  }

                  break;
               }
            }
         }

         /* IO bar not found? */
         if (i == ATEMSYS_PCI_MAXBAR)
         {
            ERR("mmap: Invalid arguments\n");
            nRet = -EINVAL;
            goto Exit;
         }
      }
#endif /* CONFIG_PCI */

      /* avoid swapping, request IO memory */

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,3,0))
      vm_flags_set(vma, VM_IO);
#else
      vma->vm_flags |= VM_IO;
#endif 

      /*
       * avoid caching (this is at least needed for POWERPC,
       * or machine will lock on first IO access)
       */
      vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

      if ((nRet = remap_pfn_range(vma,
                                 vma->vm_start,
                                 vma->vm_pgoff,
                                 dwLen,
                                 vma->vm_page_prot)) < 0)
      {
         ERR("mmap: remap_pfn_range failed\n");
         goto Exit;
      }

      INF("mmap: mapped IO memory, Phys:0x%llx UVirt:0x%px Size:%u\n",
           (u64) (((u64)vma->vm_pgoff) << PAGE_SHIFT), (void*) vma->vm_start, dwLen);

#if (defined DEBUG_IOREMAP)
      {
        volatile unsigned char* ioaddr;
        unsigned long ioBase = vma->vm_pgoff << PAGE_SHIFT;
        INF("try to remap %p\n", (void*)ioBase);
        /* DEBUG Map device's IO memory into kernel space pagetables */
        ioaddr = (volatile unsigned char*) ioremap_nocache(ioBase, dwLen);
        if (ioaddr == NULL)
        {
          ERR("ioremap_nocache failed\n");
          goto Exit;
        }
        INF("io_base %p, *io_base[0]: %08x\n", ioaddr, readl(ioaddr));
      }
#endif /* DEBUG_IOREMAP */
   }
   else
   {
      /* allocated and map DMA memory */
#if (defined CONFIG_PCI)
      if (pDevDesc->pPcidev != NULL)
      {
#if ( (LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)) \
    || (defined __aarch64__) \
    || ((defined __arm__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0))) \
    || ((defined __i386__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))) \
    || ((defined __amd64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))) )
         pVa = dma_alloc_coherent(&pDevDesc->pPcidev->dev, dwLen, &dmaAddr, GFP_KERNEL);
         if (NULL == pVa)
         {
            ERR("mmap: dma_alloc_coherent failed\n");
            nRet = -ENOMEM;
            goto Exit;
         }
#else
         pVa = pci_alloc_consistent(pDevDesc->pPcidev, dwLen, &dmaAddr);
         if (NULL == pVa)
         {
            ERR("mmap: pci_alloc_consistent failed\n");
            nRet = -ENOMEM;
            goto Exit;
         }
#endif
      }
      else
#endif /* CONFIG_PCI */
      {
#if (defined __arm__) || (defined __aarch64__)
 #if (defined CONFIG_OF)
         OF_DMA_CONFIGURE(&pDevDesc->pPlatformDev->dev,pDevDesc->pPlatformDev->dev.of_node);
 #endif
         /* dma_alloc_coherent() is currently not tested on PPC.
          * TODO test this and remove legacy dev_dma_alloc()
          */
         pVa = dmam_alloc_coherent(&pDevDesc->pPlatformDev->dev, dwLen, &dmaAddr, GFP_KERNEL);
         if (NULL == pVa)
         {
            ERR("mmap: dmam_alloc_coherent failed\n");
            nRet = -ENOMEM;
            goto Exit;
         }
#else
         pVa = dev_dma_alloc(dwLen, &dmaAddr);
         if (NULL == pVa)
         {
            ERR("mmap: dev_dma_alloc failed\n");
            nRet = -ENOMEM;
            goto Exit;
         }
#endif
      }

      if ((dmaAddr > 0xFFFFFFFF) && !pDevDesc->bSupport64BitDma)
      {
         ERR("mmap: Can't handle 64-Bit DMA address\n");
         INF("mmap: Update LinkLayer for 64-Bit DMA support!\n");
         nRet = -ENOMEM;
         goto ExitAndFree;
      }

      /* zero memory for security reasons */
      memset(pVa, 0, dwLen);

      /* Always use noncached DMA memory for ARM. Otherwise cache invaliation/sync
       * would be necessary from usermode.
       * Can't do that without a kernel call because this OP's are privileged.
       */

      /* map the whole physically contiguous area in one piece */
#if (!(defined ATEMSYS_LEGACY_DMA) && (LINUX_VERSION_CODE < KERNEL_VERSION(5,4,0))) || ((defined ATEMSYS_LEGACY_DMA) && (0 != ATEMSYS_LEGACY_DMA))
      {
         unsigned int dwDmaPfn = 0;

#if (defined __arm__) || (defined __aarch64__)
         dwDmaPfn = (dmaAddr >> PAGE_SHIFT);
 #if (defined CONFIG_PCI)
  #if (LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0))
         if ((NULL != pDevDesc->pPcidev) && (0 != pDevDesc->pPcidev->dev.dma_pfn_offset))
         {
            dwDmaPfn = dwDmaPfn + pDevDesc->pPcidev->dev.dma_pfn_offset;
            INF("mmap: remap_pfn_range dma pfn 0x%x, offset pfn 0x%x\n",
                        dwDmaPfn, (u32)pDevDesc->pPcidev->dev.dma_pfn_offset);
         }
  #else
         if ((NULL != pDevDesc->pPcidev) && (NULL != pDevDesc->pPcidev->dev.dma_range_map))
         {
            const struct bus_dma_region* map = pDevDesc->pPcidev->dev.dma_range_map;
            unsigned long dma_pfn_offset = ((map->offset) >> PAGE_SHIFT);
            dwDmaPfn = dwDmaPfn + dma_pfn_offset;
            INF("mmap: remap_pfn_range dma pfn 0x%x, offset pfn 0x%x\n",
                        dwDmaPfn, (u32)dma_pfn_offset);
         }
  #endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0))*/
 #endif /* (defined CONFIG_PCI) */
#if (!defined ATEMSYS_DONT_SET_NONCACHED_DMA_PAGEPROTECTIONLFAG)
         vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#endif
#elif (defined __PPC__)
         dwDmaPfn = (dmaAddr >> PAGE_SHIFT);
#else /* x86 / x86_64 */
         dwDmaPfn = virt_to_phys((void*)pVa) >> PAGE_SHIFT;
#endif
         nRet = remap_pfn_range(vma,               /* user space mapping */
                                vma->vm_start,     /* User space virtual addr */
                                dwDmaPfn,          /* physical page frame number */
                                dwLen,             /* size in bytes */
                                vma->vm_page_prot);
         if (nRet < 0)
         {
            ERR("remap_pfn_range failed\n");
            goto ExitAndFree;
         }
      }
#else /* #if (defined ATEMSYS_LEGACY_DMA) */
      {
         struct device* pDmaDev = NULL;

 #if (defined CONFIG_PCI)
         if (NULL != pDevDesc->pPcidev)
         {
            pDmaDev = &pDevDesc->pPcidev->dev;
         }
         else
 #endif /* (defined CONFIG_PCI) */
         if (NULL != pDevDesc->pPlatformDev)
         {
            pDmaDev = &pDevDesc->pPlatformDev->dev;
         }

#if ((defined __arm__) || (defined __aarch64__)) && (!defined ATEMSYS_DONT_SET_NONCACHED_DMA_PAGEPROTECTIONLFAG)
         vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
#endif
            /* for Platform Device */
         nRet = dma_mmap_coherent(pDmaDev,
                                     vma,       /* user space mapping                   */
                                     pVa,       /* kernel virtual address               */
                                     dmaAddr,   /* Phys address                         */
                                     dwLen);    /* size         */
         if (nRet < 0)
         {
            ERR("dma_mmap_coherent failed\n");
            goto ExitAndFree;
         }
      }
#endif /* #if (defined ATEMSYS_LEGACY_DMA) */

      /* Write the physical DMA address into the first 4 bytes of allocated memory */
      /* If there is 64 bit DMA support write upper part into the the next 4 byte  */
      if (pDevDesc->bSupport64BitDma)
      {
         ((u32*) pVa)[0] = (u32)((u64)dmaAddr & 0xFFFFFFFF);
         ((u32*) pVa)[1] = (u32)(((u64)dmaAddr >> 32) & 0xFFFFFFFF);
      }
      else
      {
         *((u32*) pVa) = (u32) dmaAddr;
      }

      /* Some housekeeping to be able to cleanup the allocated memory later */
      pMmapNode = kzalloc(sizeof(ATEMSYS_T_MMAP_DESC), GFP_KERNEL);
      if (! pMmapNode)
      {
         ERR("mmap: kmalloc() failed\n");
         nRet = -ENOMEM;
         goto ExitAndFree;
      }

      pMmapNode->pDevDesc = pDevDesc;
      pMmapNode->dmaAddr = dmaAddr;
      pMmapNode->pVirtAddr = pVa;
      pMmapNode->len = dwLen;

      /* Setup close callback -> deallocates DMA memory if region is unmapped by the system */
      vma->vm_ops = &mmap_vmop;
      vma->vm_private_data = pMmapNode;

      INF("mmap: mapped DMA memory, Phys:0x%px KVirt:0x%px UVirt:0x%px Size:%u\n",
             (void*)(unsigned long)dmaAddr, (void*)pVa, (void*)vma->vm_start, dwLen);
   }

   nRet = 0;

   goto Exit;

ExitAndFree:

   if (pVa == NULL) goto Exit;

#if (defined CONFIG_PCI)
   if (pDevDesc->pPcidev != NULL)
   {
#if ( (LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0)) \
    || (defined __aarch64__) \
    || ((defined __arm__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0))) \
    || ((defined __i386__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))) \
    || ((defined __amd64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4,9,0))) )
      dma_free_coherent(&pDevDesc->pPcidev->dev, dwLen, pVa, dmaAddr);
#else
      pci_free_consistent(pDevDesc->pPcidev, dwLen, pVa, dmaAddr);
#endif
   }
   else
#endif
   {
#if (defined __arm__) || (defined __aarch64__)
      dmam_free_coherent(&pDevDesc->pPlatformDev->dev, dwLen, pVa, dmaAddr);
#else
      dev_dma_free(dwLen, pVa);
#endif
   }

Exit:
   return nRet;
}


/*
 * This function is called whenever a process tries to do an ioctl on our
 * device file.
 *
 * If the ioctl is write or read/write (meaning output is returned to the
 * calling process), the ioctl call returns the output of this function.
 *
 */
#if (defined CONFIG_XENO_COBALT)
static int atemsys_ioctl(struct rtdm_fd* fd, unsigned int cmd, void __user* user_arg)
{
   ATEMSYS_T_DEVICE_DESC*   pDevDesc = (ATEMSYS_T_DEVICE_DESC*) rtdm_fd_to_private(fd);
   unsigned long   arg = (unsigned long) user_arg;
#else
static long atemsys_ioctl(
      struct file* file,
      unsigned int cmd,
      unsigned long arg)
{
   ATEMSYS_T_DEVICE_DESC*   pDevDesc = file->private_data;
#endif /* CONFIG_XENO_COBALT */

   int nRetVal = -EFAULT;

   if (pDevDesc == NULL)
   {
      ERR("ioctl: Invalid device dtor\n");
      goto Exit;
   }

   /*
    * Switch according to the ioctl called
    */
   switch (cmd)
   {
#if (defined CONFIG_PCI)
      case ATEMSYS_IOCTL_PCI_FIND_DEVICE_v1_0_00:
      case ATEMSYS_IOCTL_PCI_FIND_DEVICE_v1_3_05:
      case ATEMSYS_IOCTL_PCI_FIND_DEVICE_v1_4_12:
      {
         nRetVal = ioctl_pci_finddevice(pDevDesc, arg, _IOC_SIZE(cmd)); /* size determines version */
         if (0 != nRetVal)
         {
           /* be quiet. ioctl may fail */
           goto Exit;
         }
      } break;
      case ATEMSYS_IOCTL_PCI_CONF_DEVICE_v1_0_00:
      case ATEMSYS_IOCTL_PCI_CONF_DEVICE_v1_3_05:
      case ATEMSYS_IOCTL_PCI_CONF_DEVICE_v1_4_12:
      {
         nRetVal = ioctl_pci_configure_device(pDevDesc, arg, _IOC_SIZE(cmd)); /* size determines version */
         if (0 != nRetVal)
         {
            ERR("ioctl ATEMSYS_IOCTL_PCI_CONF_DEVICE failed: %d\n", nRetVal);
            goto Exit;
         }
      } break;

      case ATEMSYS_IOCTL_PCI_RELEASE_DEVICE:
      {
         if (pDevDesc->pPcidev == NULL)
         {
            DBG("pci_release: No PCI device selected. Call ioctl(ATEMSYS_IOCTL_PCI_CONF_DEVICE) first\n");
            goto Exit;
         }
         /* do nothing */
         /* see device_release() -> dev_pci_release(pDevDesc)*/
      } break;
#endif
      case ATEMSYS_IOCTL_INT_CONNECT:
      {
         nRetVal = ioctl_int_connect(pDevDesc, arg);
         if (0 != nRetVal)
         {
            ERR("ioctl ATEMSYS_IOCTL_INT_CONNECT failed: %d\n", nRetVal);
            goto Exit;
         }
      } break;

      case ATEMSYS_IOCTL_INT_DISCONNECT:
      {
         nRetVal = dev_int_disconnect(pDevDesc);
         if (0 != nRetVal)
         {
            /* be quiet. ioctl may fail */
            goto Exit;
         }
      } break;

      case ATEMSYS_IOCTL_INT_INFO:
      {
         nRetVal = ioctl_intinfo(pDevDesc, arg);
         if (0 != nRetVal)
         {
            ERR("ioctl ATEMSYS_IOCTL_INT_INFO failed: %d\n", nRetVal);
            goto Exit;
         }
      } break;

      case ATEMSYS_IOCTL_MOD_GETVERSION:
      {
         __u32 dwVersion = USE_ATEMSYS_API_VERSION;

#if (defined CONFIG_XENO_COBALT)
         nRetVal = rtdm_safe_copy_to_user(fd, user_arg, &dwVersion, sizeof(__u32));
#else
         nRetVal = put_user(dwVersion, (__u32*)arg);
#endif /* CONFIG_XENO_COBALT */

         if (0 != nRetVal)
         {
            ERR("ioctl ATEMSYS_IOCTL_MOD_GETVERSION failed: %d\n", nRetVal);
            goto Exit;
         }
      } break;

      case ATEMSYS_IOCTL_MOD_SET_API_VERSION:
      {
         __u32 dwApiVersion = 0;

#if (defined CONFIG_XENO_COBALT)
         nRetVal = rtdm_safe_copy_from_user(fd, &dwApiVersion, user_arg, sizeof(__u32));
#else
         nRetVal = get_user(dwApiVersion, (__u32*)arg);
#endif

         /* activate supported features */
         if (EC_ATEMSYSVERSION(1,4,15) <= dwApiVersion)
         {
            pDevDesc->bSupport64BitDma = true;
         }

         if (0 != nRetVal)
         {
            ERR("ioctl ATEMSYS_IOCTL_MOD_SETVERSION failed: %d\n", nRetVal);
            goto Exit;
         }
      } break;
#if ((defined CONFIG_SMP) && (LINUX_VERSION_CODE > KERNEL_VERSION(5,14,0)))
      case ATEMSYS_IOCTL_INT_SET_CPU_AFFINITY:
      {
          nRetVal = SetIntCpuAffinityIoctl(pDevDesc, arg, _IOC_SIZE(cmd));
          if (0 != nRetVal)
          {
              ERR("ioctl ATEMSYS_IOCTL_INT_SET_CPU_AFFINITY failed: %d\n", nRetVal);
              goto Exit;
          }
      } break;
#endif

#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
    case ATEMSYS_IOCTL_IOMEM_CMD:
    {
        nRetVal = IoMemCmd((void*)arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_IOMEM_CMD failed: 0x%x\n", nRetVal);
            goto Exit;
        }
    } break;


#ifdef CONFIG_TI_K3_UDMA
    case ATEMSYS_IOCTL_CPSWG_CMD:
    {
        nRetVal = CpswgCmd((__u32*)arg, NULL);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_CPSWG_CMD failed: 0x%x\n", nRetVal);
            goto Exit;
        }
    } break;
#endif /*#ifdef CONFIG_TI_K3_UDMA*/

    case ATEMSYS_IOCTL_GET_MAC_INFO:
    {
        nRetVal = GetMacInfoIoctl(pDevDesc, arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_GET_MAC_INFO failed: 0x%x\n", nRetVal);
            goto Exit;
        }
    } break;
    case ATEMSYS_IOCTL_PHY_START_STOP:
    {
        nRetVal = PhyStartStopIoctl(arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_PHY_START_STOP failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case ATEMSYS_IOCTL_GET_MDIO_ORDER:
    {
        nRetVal = GetMdioOrderIoctl(arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_GET_MDIO_ORDER failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case ATEMSYS_IOCTL_RETURN_MDIO_ORDER:
    {
        nRetVal = ReturnMdioOrderIoctl(arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_RETURN_MDIO_ORDER failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
    case ATEMSYS_IOCTL_GET_PHY_INFO:
    {
        nRetVal = GetPhyInfoIoctl(arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_GET_PHY_INFO failed: %d\n", nRetVal);
            goto Exit;
        }
      } break;
    case ATEMSYS_IOCTL_PHY_RESET:
    {
        nRetVal = PhyResetIoctl(arg);
        if (0 != nRetVal)
        {
            ERR("ioctl ATEMSYS_IOCTL_PHY_RESET failed: %d\n", nRetVal);
            goto Exit;
        }
    } break;
#endif /* INCLUDE_ATEMSYS_DT_DRIVER */

      default:
      {
         nRetVal = -EOPNOTSUPP;
         goto Exit;
      } /* no break */
   }

   nRetVal = DRIVER_SUCCESS;

Exit:
   return nRetVal;
}

#if (defined CONFIG_COMPAT) && !(defined CONFIG_XENO_COBALT)
/*
 * ioctl processing for 32 bit process on 64 bit system
 */
static long atemsys_compat_ioctl(
      struct file*  file,
      unsigned int  cmd,
      unsigned long arg)
{
   return atemsys_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
}
#endif /* CONFIG_COMPAT && !CONFIG_XENO_COBALT */

/* Module Declarations */

/*
 * This structure will hold the functions to be called
 * when a process does something to the device we
 * created. Since a pointer to this structure is kept in
 * the devices table, it can't be local to
 * module_init. NULL is for unimplemented functions.
 */

#if (defined CONFIG_XENO_COBALT)
static struct rtdm_driver driver = {
        .profile_info = RTDM_PROFILE_INFO(atemsys, RTDM_CLASS_EXPERIMENTAL, MAJOR_NUM, 1),
        .device_flags = RTDM_NAMED_DEVICE,
        .device_count = 1,
        .context_size = sizeof(ATEMSYS_T_DEVICE_DESC),

        .ops = {
        .open = device_open,
        .close = device_release,
        .read_rt = device_read,
        .ioctl_rt = atemsys_ioctl,
        .ioctl_nrt = atemsys_ioctl,
        .mmap = device_mmap,
    },
};

static struct rtdm_device device = {
        .driver = &driver,
        .label = ATEMSYS_DEVICE_NAME,
};
#else /* !CONFIG_XENO_COBALT */
struct file_operations Fops = {
   .read = device_read,
   .unlocked_ioctl = atemsys_ioctl,
#if (defined CONFIG_COMPAT)
   .compat_ioctl = atemsys_compat_ioctl, /* ioctl processing for 32 bit process on 64 bit system */
#endif
   .open = device_open,
   .mmap = device_mmap,
   .release = device_release,   /* a.k.a. close */
};
#endif /* !CONFIG_XENO_COBALT */


#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
static int GetMacInfoIoctl(ATEMSYS_T_DEVICE_DESC* pDevDesc, unsigned long ioctlParam)
{
    ATEMSYS_T_MAC_INFO oInfo;
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    unsigned int i = 0;

    memset(&oInfo, 0, sizeof(ATEMSYS_T_MAC_INFO));
    nRetVal = copy_from_user(&oInfo, (ATEMSYS_T_MAC_INFO *)ioctlParam, sizeof(ATEMSYS_T_MAC_INFO));
    if (0 != nRetVal)
    {
        ERR("GetMacInfoIoctl failed: %d\n", nRetVal);
        goto Exit;
    }

    for (i = 0; i < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; i++)
    {
        if (NULL == S_apDrvDescPrivate[i])
        {
            continue;
        }
        if ((0 == strcmp(S_apDrvDescPrivate[i]->MacInfo.szIdent, oInfo.szIdent)) &&
            (S_apDrvDescPrivate[i]->MacInfo.dwInstance == oInfo.dwInstance))
        {
            pDrvDescPrivate = S_apDrvDescPrivate[i];
            break;
        }
    }

    if (NULL != pDrvDescPrivate)
    {
        if (pDrvDescPrivate->pDevDesc != NULL)
        {
            ERR("GetMacInfoIoctl: device \"%s\" in use by another instance?\n", pDrvDescPrivate->pPDev->name);
            nRetVal = -EBUSY;
            goto Exit;
        }

        oInfo.qwRegAddr            = pDrvDescPrivate->MacInfo.qwRegAddr;
        oInfo.dwRegSize            = pDrvDescPrivate->MacInfo.dwRegSize;
        oInfo.dwStatus             = pDrvDescPrivate->MacInfo.dwStatus;
        oInfo.ePhyMode             = pDrvDescPrivate->MacInfo.ePhyMode;
        oInfo.dwIndex              = pDrvDescPrivate->MacInfo.dwIndex;
        oInfo.bNoMdioBus           = pDrvDescPrivate->MacInfo.bNoMdioBus;
        oInfo.dwPhyAddr            = pDrvDescPrivate->MacInfo.dwPhyAddr;
        oInfo.bPhyResetSupported   = pDrvDescPrivate->MacInfo.bPhyResetSupported;

        /* save descriptor of callee for cleanup on device_release */
        pDrvDescPrivate->pDevDesc = pDevDesc;

        /* add driver's platfrom device to device descriptor of callee for memory mapping and allocation */
        pDevDesc->pPlatformDev    = pDrvDescPrivate->pPDev;
        pDevDesc->pDrvDesc        = pDrvDescPrivate;
        dwRetVal = 0; /* EC_E_NOERROR */
    }
    else
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND */
    }

    nRetVal = 0;
Exit:
    oInfo.dwErrorCode = dwRetVal;
    nRetVal = copy_to_user((ATEMSYS_T_MAC_INFO *)ioctlParam, &oInfo, sizeof(ATEMSYS_T_MAC_INFO));
    if (0 != nRetVal)
    {
        ERR("GetMacInfoIoctl failed: %d\n", nRetVal);
    }
    return nRetVal;
}

static int PhyStartStopIoctl( unsigned long ioctlParam)
{
    ATEMSYS_T_PHY_START_STOP_INFO oPhyStartStopInfo;
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    memset(&oPhyStartStopInfo, 0, sizeof(ATEMSYS_T_PHY_START_STOP_INFO));
    nRetVal = copy_from_user(&oPhyStartStopInfo, (ATEMSYS_T_PHY_START_STOP_INFO *)ioctlParam, sizeof(ATEMSYS_T_PHY_START_STOP_INFO));
    if (0 != nRetVal)
    {
        ERR("PhyStartStopIoctl failed: %d\n", nRetVal);
        goto Exit;
    }
    if (oPhyStartStopInfo.dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[oPhyStartStopInfo.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND*/
        nRetVal = 0;
        goto Exit;
    }
    if (oPhyStartStopInfo.bStart)
    {
#if (defined CONFIG_XENO_COBALT)
        mutex_lock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
        if (NULL == S_oAtemsysWorkerThreadDesc.pfNextTask)
        {
            S_oAtemsysWorkerThreadDesc.pfNextTask = StartPhyThread;
            S_oAtemsysWorkerThreadDesc.pNextTaskData = (void*)pDrvDescPrivate->pPDev;
        }
        else
        {
            mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
            ERR("PhyStartStopIoctl: StartPhy failed! WorkerThread is busy!\n");
            nRetVal = -EAGAIN;
            goto Exit;
        }
        mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
#else
        pDrvDescPrivate->etx_thread_StartPhy = kthread_create(StartPhyThread,(void*)pDrvDescPrivate->pPDev,"StartPhyThread");
        if(NULL == pDrvDescPrivate->etx_thread_StartPhy)
        {
            ERR("PhyStartStopIoctl: Cannot create kthread for StartPhyThread\n");
            nRetVal = -EAGAIN;
            goto Exit;
        }
        wake_up_process(pDrvDescPrivate->etx_thread_StartPhy);
#endif /*#if (defined CONFIG_XENO_COBALT)*/
    }
    else
    {
#if (defined CONFIG_XENO_COBALT)
        mutex_lock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
        if (NULL == S_oAtemsysWorkerThreadDesc.pfNextTask)
        {
            S_oAtemsysWorkerThreadDesc.pfNextTask = StopPhyThread;
            S_oAtemsysWorkerThreadDesc.pNextTaskData = (void*)pDrvDescPrivate->pPDev;
        }
        else
        {
            mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
            ERR("PhyStartStopIoctl: StopPhy failed! WorkerThread is busy!\n");
            nRetVal = -EAGAIN;
            goto Exit;
        }
        mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
#else
        pDrvDescPrivate->etx_thread_StopPhy = kthread_create(StopPhyThread,(void*)pDrvDescPrivate->pPDev,"StopPhyThread");
        if(NULL == pDrvDescPrivate->etx_thread_StopPhy)
        {
            ERR("PhyStartStopIoctl: Cannot create kthread for StopPhyThread\n");
            nRetVal = -EAGAIN;
            goto Exit;
        }
        wake_up_process(pDrvDescPrivate->etx_thread_StopPhy);
#endif /* #if (defined CONFIG_XENO_COBALT) */
    }
    nRetVal = 0;
    dwRetVal = 0; /* EC_E_NOERROR */
Exit:
    oPhyStartStopInfo.dwErrorCode = dwRetVal;

    nRetVal = copy_to_user((ATEMSYS_T_PHY_START_STOP_INFO *)ioctlParam, &oPhyStartStopInfo, sizeof(ATEMSYS_T_PHY_START_STOP_INFO));
    if (0 != nRetVal)
    {
        ERR("PhyStartStopIoctl failed: %d\n", nRetVal);
    }
    return nRetVal;
}


static int GetMdioOrderIoctl( unsigned long ioctlParam)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    ATEMSYS_T_MDIO_ORDER oOrder;
    bool bLocked = false;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    memset(&oOrder, 0, sizeof(ATEMSYS_T_MDIO_ORDER));
    nRetVal = copy_from_user(&oOrder, (ATEMSYS_T_MDIO_ORDER *)ioctlParam, sizeof(ATEMSYS_T_MDIO_ORDER));
    if (0 != nRetVal)
    {
        ERR("GetMdioOrderIoctl failed: %d\n", nRetVal);
        goto Exit;
    }
    if (oOrder.dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[oOrder.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND*/
        nRetVal = 0;
        goto Exit;
    }

    if (mutex_trylock(&pDrvDescPrivate->mdio_order_mutex))
    {
        bLocked = true;
        if ((pDrvDescPrivate->MdioOrder.bInUse) && (pDrvDescPrivate->MdioOrder.bInUseByIoctl))
        {
            oOrder.bInUse        = pDrvDescPrivate->MdioOrder.bInUse;
            oOrder.bInUseByIoctl = pDrvDescPrivate->MdioOrder.bInUseByIoctl;
            oOrder.bWriteOrder   = pDrvDescPrivate->MdioOrder.bWriteOrder;
            oOrder.wMdioAddr     = pDrvDescPrivate->MdioOrder.wMdioAddr;
            oOrder.wReg          = pDrvDescPrivate->MdioOrder.wReg;
            oOrder.wValue        = pDrvDescPrivate->MdioOrder.wValue;
        }
    }

    dwRetVal = 0; /* EC_E_NOERROR*/
    nRetVal = 0;
Exit:
    if (bLocked)
    {
        mutex_unlock(&pDrvDescPrivate->mdio_order_mutex);
    }
    oOrder.dwErrorCode = dwRetVal;
    nRetVal = copy_to_user((ATEMSYS_T_MDIO_ORDER *)ioctlParam, &oOrder, sizeof(ATEMSYS_T_MDIO_ORDER));
    if (0 != nRetVal)
    {
        ERR("GetMdioOrderIoctl failed: %d\n", nRetVal);
    }
    return nRetVal;
}

static int ReturnMdioOrderIoctl( unsigned long ioctlParam)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    ATEMSYS_T_MDIO_ORDER oOrder;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    memset(&oOrder, 0, sizeof(ATEMSYS_T_MDIO_ORDER));
    nRetVal = copy_from_user(&oOrder, (ATEMSYS_T_MDIO_ORDER *)ioctlParam, sizeof(ATEMSYS_T_MDIO_ORDER));
    if (0 != nRetVal)
    {
        ERR("ReturnMdioOrderIoctl failed: %d\n", nRetVal);
        goto Exit;
    }

    if (oOrder.dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[oOrder.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND*/
        nRetVal = 0;
        goto Exit;
    }

    pDrvDescPrivate = S_apDrvDescPrivate[oOrder.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND*/
        nRetVal = 0;
        goto Exit;
    }

    mutex_lock(&pDrvDescPrivate->mdio_order_mutex);
    pDrvDescPrivate->MdioOrder.wValue = oOrder.wValue;
    pDrvDescPrivate->MdioOrder.bInUseByIoctl = false;
    mutex_unlock(&pDrvDescPrivate->mdio_order_mutex);

    /* wake MdioRead or MdioWrite */
    pDrvDescPrivate->mdio_wait_queue_cnt = 1;
    wake_up_interruptible(&pDrvDescPrivate->mdio_wait_queue);

    dwRetVal = 0 /* EC_E_NOERROR*/;
    nRetVal = 0;

Exit:
    oOrder.dwErrorCode = dwRetVal;
    nRetVal = copy_to_user((ATEMSYS_T_MDIO_ORDER *)ioctlParam, &oOrder, sizeof(ATEMSYS_T_MDIO_ORDER));
    if (0 != nRetVal)
    {
        ERR("ReturnMdioOrderIoctl failed: %d\n", nRetVal);
    }
    return nRetVal;
}

static int GetPhyInfoIoctl(unsigned long ioctlParam)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate  = NULL;
    ATEMSYS_T_PHY_INFO oStatus;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    memset(&oStatus, 0, sizeof(ATEMSYS_T_PHY_INFO));
    nRetVal = copy_from_user(&oStatus, (ATEMSYS_T_PHY_INFO *)ioctlParam, sizeof(ATEMSYS_T_PHY_INFO));
    if (0 != nRetVal)
    {
        ERR("GetPhyInfoIoctl failed: %d\n", nRetVal);
        goto Exit;
    }

    if (oStatus.dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[oStatus.dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND*/
        nRetVal = 0;
        goto Exit;
    }

    oStatus.dwLink    = pDrvDescPrivate->PhyInfo.dwLink;
    oStatus.dwDuplex  = pDrvDescPrivate->PhyInfo.dwDuplex;
    oStatus.dwSpeed   = pDrvDescPrivate->PhyInfo.dwSpeed;
    oStatus.bPhyReady = pDrvDescPrivate->PhyInfo.bPhyReady;

    dwRetVal = 0; /* EC_E_NOERROR */
    nRetVal = 0;
Exit:
    oStatus.dwErrorCode = dwRetVal;
    nRetVal = copy_to_user((ATEMSYS_T_PHY_INFO *)ioctlParam, &oStatus, sizeof(ATEMSYS_T_PHY_INFO));
    if (0 != nRetVal)
    {
        ERR("GetPhyInfoIoctl failed: %d\n", nRetVal);
    }
    return nRetVal;
}

static int PhyResetIoctl(unsigned long ioctlParam)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate  = NULL;
    unsigned int* pdwIoctlData = (__u32*)ioctlParam;
    unsigned int dwIndex = 0;
    unsigned int dwRetVal = 0x98110000; /* EC_E_ERROR */
    int nRetVal = -1;
    int nRes = -1;

    nRes = get_user(dwIndex, pdwIoctlData);
    if (0 != nRes) { nRetVal = nRes; goto Exit; }

    if (dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        dwRetVal = 0x98110002; /* EC_E_INVALIDINDEX */
        nRetVal = 0;
        goto Exit;
    }
    pDrvDescPrivate = S_apDrvDescPrivate[dwIndex];
    if (NULL == pDrvDescPrivate)
    {
        dwRetVal = 0x9811000C; /* EC_E_NOTFOUND */
        nRetVal = 0;
        goto Exit;
    }

    if (!pDrvDescPrivate->MacInfo.bPhyResetSupported)
    {
        DBG("PhyResetIoctl: PhyReset not supported\n");
        dwRetVal = 0x98110001; /* EC_E_NOTSUPPORTED */
        nRetVal = 0;
        goto Exit;
    }

    nRes = ResetPhyViaGpio(pDrvDescPrivate);
    if (0 != nRes)
    {
        dwRetVal = 0x98110000; /* EC_E_ERROR */
        nRetVal = 0;
        goto Exit;
    }

    dwRetVal = 0; /* EC_E_NOERROR */
    nRetVal = 0;
Exit:
    put_user(dwRetVal, pdwIoctlData);

    return nRetVal;
}

static void UpdatePhyInfoByLinuxPhyDriver(struct net_device* ndev)
{
    struct phy_device* phy_dev = ndev->phydev;
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(ndev);

    if (LOGLEVEL_DEBUG <= loglevel)
    {
        phy_print_status(phy_dev);
    }

    pDrvDescPrivate->PhyInfo.dwLink = phy_dev->link;
    pDrvDescPrivate->PhyInfo.dwDuplex = phy_dev->duplex;
    pDrvDescPrivate->PhyInfo.dwSpeed = phy_dev->speed;
    pDrvDescPrivate->PhyInfo.bPhyReady = true;
}

static int MdioProbe(struct net_device* ndev)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(ndev);
    struct phy_device* pPhyDev = NULL;
    char mdio_bus_id[MII_BUS_ID_SIZE];
    char phy_name[MII_BUS_ID_SIZE + 3];
    int nPhy_id = 0;

    if (NULL != pDrvDescPrivate->pPhyNode)
    {
        pPhyDev = of_phy_connect(ndev, pDrvDescPrivate->pPhyNode,
                     &UpdatePhyInfoByLinuxPhyDriver, 0,
                     pDrvDescPrivate->PhyInterface);
    }
    else if (NULL != pDrvDescPrivate->pMdioNode)
    {
        struct platform_device* mdio;
        mdio = of_find_device_by_node(pDrvDescPrivate->pMdioNode);
        snprintf(phy_name, sizeof(phy_name), PHY_ID_FMT, mdio->name, pDrvDescPrivate->MacInfo.dwPhyAddr);
        pPhyDev = phy_connect(ndev, phy_name, &UpdatePhyInfoByLinuxPhyDriver, pDrvDescPrivate->PhyInterface);
    }
    else if (NULL != pDrvDescPrivate->pMdioBus)
    {
        int nDev_id = pDrvDescPrivate->nDev_id;
        /* check for attached phy */
        for (nPhy_id = 0; (nPhy_id < PHY_MAX_ADDR); nPhy_id++)
        {
            if (!mdiobus_is_registered_device(pDrvDescPrivate->pMdioBus, nPhy_id))
            {
                continue;
            }
            if (0 != nDev_id--)
            {
                continue;
            }
            strlcpy(mdio_bus_id, pDrvDescPrivate->pMdioBus->id, MII_BUS_ID_SIZE);
            break;
        }

        if (nPhy_id >= PHY_MAX_ADDR)
        {
            INF("%s: no PHY, assuming direct connection to switch\n", pDrvDescPrivate->pPDev->name);
            strlcpy(mdio_bus_id, "fixed-0", MII_BUS_ID_SIZE);
            nPhy_id = 0;
        }

        snprintf(phy_name, sizeof(phy_name), PHY_ID_FMT, mdio_bus_id, nPhy_id);
        pPhyDev = phy_connect(ndev, phy_name, &UpdatePhyInfoByLinuxPhyDriver, pDrvDescPrivate->PhyInterface);
    }

    if ((NULL == pPhyDev) || IS_ERR(pPhyDev))
    {
        ERR("%s: Could not attach to PHY (pPhyDev %p)\n", pDrvDescPrivate->pPDev->name, pPhyDev);
        return -ENODEV;
    }

    /* adjust maximal link speed */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,0,0))
    phy_set_max_speed(pPhyDev, 100);
#else
    pPhyDev->supported &= PHY_BASIC_FEATURES;
    pPhyDev->advertising = pPhyDev->supported;
#endif
    if (LOGLEVEL_INFO <= loglevel)
    {
        phy_attached_info(pPhyDev);
    }

    pDrvDescPrivate->pPhyDev = pPhyDev;
    pDrvDescPrivate->PhyInfo.dwLink = 0;
    pDrvDescPrivate->PhyInfo.dwDuplex = 0;
    pDrvDescPrivate->PhyInfo.dwSpeed = 0;

    return 0;
}

static int MdioRead(struct mii_bus* pBus, int mii_id, int regnum)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = pBus->priv;
    int nRetVal = -1;
    int nRes = -1;

    nRes = pm_runtime_get_sync(&pDrvDescPrivate->pPDev->dev);
    if (0 > nRes)
    {
        return nRes;
    }

    /* get lock for the Mdio bus only one MdioRead or MdioWrite*/
    mutex_lock(&pDrvDescPrivate->mdio_mutex);

    mutex_lock(&pDrvDescPrivate->mdio_order_mutex);
    memset(&pDrvDescPrivate->MdioOrder, 0, sizeof(ATEMSYS_T_MDIO_ORDER));
    pDrvDescPrivate->MdioOrder.bInUse = true;
    pDrvDescPrivate->MdioOrder.bInUseByIoctl = true;
    pDrvDescPrivate->MdioOrder.bWriteOrder = false;
    pDrvDescPrivate->MdioOrder.wMdioAddr = (__u16)mii_id;
    pDrvDescPrivate->MdioOrder.wReg = (__u16)regnum;
    mutex_unlock(&pDrvDescPrivate->mdio_order_mutex);

    /* wait for result */
    wait_event_interruptible(pDrvDescPrivate->mdio_wait_queue, pDrvDescPrivate->mdio_wait_queue_cnt != 0);
    pDrvDescPrivate->mdio_wait_queue_cnt = pDrvDescPrivate->mdio_wait_queue_cnt - 1;

    nRetVal = pDrvDescPrivate->MdioOrder.wValue;

    mutex_lock(&pDrvDescPrivate->mdio_order_mutex);
    pDrvDescPrivate->MdioOrder.bInUse = false;
    pDrvDescPrivate->MdioOrder.bInUseByIoctl = false;
    mutex_unlock(&pDrvDescPrivate->mdio_order_mutex);

    pm_runtime_mark_last_busy(&pDrvDescPrivate->pPDev->dev);
    pm_runtime_put_autosuspend(&pDrvDescPrivate->pPDev->dev);

    mutex_unlock(&pDrvDescPrivate->mdio_mutex);

    return nRetVal;
}

static int MdioWrite(struct mii_bus* pBus, int mii_id, int regnum, u16 value)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = pBus->priv;
    int nRetVal;

    nRetVal = pm_runtime_get_sync(&pDrvDescPrivate->pPDev->dev);
    if (0 > nRetVal)
    {
        return nRetVal;
    }

    /* get lock for the Mdio bus only one MdioRead or MdioWrite*/
    mutex_lock(&pDrvDescPrivate->mdio_mutex);

    mutex_lock(&pDrvDescPrivate->mdio_order_mutex);
    memset(&pDrvDescPrivate->MdioOrder, 0, sizeof(ATEMSYS_T_MDIO_ORDER));
    pDrvDescPrivate->MdioOrder.bInUse = true;
    pDrvDescPrivate->MdioOrder.bInUseByIoctl = true;
    pDrvDescPrivate->MdioOrder.bWriteOrder = true;
    pDrvDescPrivate->MdioOrder.wMdioAddr = (__u16)mii_id;
    pDrvDescPrivate->MdioOrder.wReg = (__u16)regnum;
    pDrvDescPrivate->MdioOrder.wValue = (__u16)value;
    mutex_unlock(&pDrvDescPrivate->mdio_order_mutex);

    /* wait for result */
    wait_event_interruptible(pDrvDescPrivate->mdio_wait_queue, pDrvDescPrivate->mdio_wait_queue_cnt != 0);
    pDrvDescPrivate->mdio_wait_queue_cnt = pDrvDescPrivate->mdio_wait_queue_cnt - 1;

    nRetVal = 0;

    mutex_lock(&pDrvDescPrivate->mdio_order_mutex);
    pDrvDescPrivate->MdioOrder.bInUse = false;
    pDrvDescPrivate->MdioOrder.bInUseByIoctl = false;
    mutex_unlock(&pDrvDescPrivate->mdio_order_mutex);

    pm_runtime_mark_last_busy(&pDrvDescPrivate->pPDev->dev);
    pm_runtime_put_autosuspend(&pDrvDescPrivate->pPDev->dev);

    mutex_unlock(&pDrvDescPrivate->mdio_mutex);

    return nRetVal;
}

static int MdioInit(struct platform_device* pPDev)
{
    struct net_device* pNDev = platform_get_drvdata(pPDev);
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(pNDev);
    int nRes = -ENXIO;

    if (pDrvDescPrivate->MacInfo.bNoMdioBus)
    {
        pDrvDescPrivate->pMdioBus = NULL;
        nRes = 0;
        goto Exit;
    }

    pDrvDescPrivate->pMdioBus = mdiobus_alloc();
    if (NULL == pDrvDescPrivate->pMdioBus)
    {
        nRes = -ENOMEM;
        goto Exit;
    }

    pDrvDescPrivate->pMdioBus->name = "atemsys_mdio_bus";
    pDrvDescPrivate->pMdioBus->read = &MdioRead;
    pDrvDescPrivate->pMdioBus->write = &MdioWrite;
    snprintf(pDrvDescPrivate->pMdioBus->id, MII_BUS_ID_SIZE, "%s-%x", pPDev->name, pDrvDescPrivate->nDev_id + 1);
    pDrvDescPrivate->pMdioBus->priv = pDrvDescPrivate;
    pDrvDescPrivate->pMdioBus->parent = &pPDev->dev;

    if (NULL != pDrvDescPrivate->pMdioDevNode)
    {
        nRes = of_mdiobus_register(pDrvDescPrivate->pMdioBus, pDrvDescPrivate->pMdioDevNode);
        of_node_put(pDrvDescPrivate->pMdioDevNode);
    }
    else
    {
        if (NULL == pDrvDescPrivate->pPhyNode)
        {
            nRes = mdiobus_register(pDrvDescPrivate->pMdioBus);
        }
        else
        {
            /* no Mdio sub-node use main node */
            nRes = of_mdiobus_register(pDrvDescPrivate->pMdioBus, pDrvDescPrivate->pDevNode);
        }
    }
    if (0 != nRes)
    {
        mdiobus_free(pDrvDescPrivate->pMdioBus);
    }

Exit:
    return nRes;
}


static int StopPhy(struct platform_device* pPDev)
{
    struct net_device* pNDev = platform_get_drvdata(pPDev);
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(pNDev);

    /* phy */
    if (NULL != pDrvDescPrivate->pPhyDev)
    {
        phy_stop(pDrvDescPrivate->pPhyDev);
        phy_disconnect(pDrvDescPrivate->pPhyDev);
        pDrvDescPrivate->pPhyDev = NULL;
    }

    /* mdio bus */
    if (NULL != pDrvDescPrivate->pMdioBus)
    {
        mdiobus_unregister(pDrvDescPrivate->pMdioBus);
        mdiobus_free(pDrvDescPrivate->pMdioBus);
        pDrvDescPrivate->pMdioBus = NULL;
    }

    pDrvDescPrivate->PhyInfo.bPhyReady = false;
    pDrvDescPrivate->mdio_wait_queue_cnt = 0;

    return 0;
}

static int StartPhy(struct platform_device* pPDev)
{
    struct net_device* pNDev = platform_get_drvdata(pPDev);
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(pNDev);
    int nRes = -1;

    if ((NULL != pDrvDescPrivate->pPhyDev) || (NULL != pDrvDescPrivate->pMdioBus))
    {
        StopPhy(pPDev);
    }

    /* mdio bus */
    nRes = MdioInit(pPDev);
    if (0 != nRes)
    {
        pDrvDescPrivate->pMdioBus = NULL;
    }
    nRes = MdioProbe(pNDev);
    if (0 != nRes)
    {
        return nRes;
    }
    /* phy */
    phy_start(pDrvDescPrivate->pPhyDev);
    phy_start_aneg(pDrvDescPrivate->pPhyDev);

    return 0;
}

static int StartPhyThread(void* data)
{
    struct platform_device* pPDev = (struct platform_device*)data;

    StartPhy(pPDev);

    return 0;
}

static int StopPhyThread(void* data)
{
    struct platform_device* pPDev = (struct platform_device*)data;

    StopPhy(pPDev);

    return 0;
}

static int StopPhyWithoutIoctlMdioHandling(struct platform_device* pPDev)
{
    struct net_device* pNDev = platform_get_drvdata(pPDev);
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(pNDev);

    /* start StopPhy as thread */
#if (defined CONFIG_XENO_COBALT)
    mutex_lock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
    if (NULL == S_oAtemsysWorkerThreadDesc.pfNextTask)
    {
        S_oAtemsysWorkerThreadDesc.pfNextTask = StopPhyThread;
        S_oAtemsysWorkerThreadDesc.pNextTaskData = (void*)pDrvDescPrivate->pPDev;
    }
    else
    {
        ERR("StopPhyWithoutIoctlMdioHandling failed! WorkerThread is busy!\n");
        return -EAGAIN;
    }
    mutex_unlock(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
#else
    pDrvDescPrivate->etx_thread_StopPhy = kthread_create(StopPhyThread,(void*)pDrvDescPrivate->pPDev,"StopPhyThread");
    if(NULL == pDrvDescPrivate->etx_thread_StopPhy)
    {
        ERR("Cannot create kthread for StopPhyThread\n");
        return -1;
    }
    wake_up_process(pDrvDescPrivate->etx_thread_StopPhy);
#endif /* #if (defined CONFIG_XENO_COBALT) */

    /* trigger event to continue MdioRead and MdioWrite */
    /* MdioRead returns always 0 */
    pDrvDescPrivate->mdio_wait_queue_cnt = 1000; // wait will be skipped 1000 times
    wake_up_interruptible(&pDrvDescPrivate->mdio_wait_queue);

    return 0;
}

static struct device_node* findDeviceTreeNode(struct platform_device* pPDev)
{
    int                    nTimeout;
    unsigned int           dwRegAddr32;
    long long unsigned int qwRegAddr64;
    char                   aBuff[32] = {0};
    struct device_node*    pDevNode;

    pDevNode = NULL;
    nTimeout = 100;
    while(0 < nTimeout)
    {
        pDevNode = of_find_node_by_name(pDevNode, "ethernet");
        if (NULL == pDevNode)
            break;

        of_property_read_u32(pDevNode, "reg", &dwRegAddr32);
        of_property_read_u64(pDevNode, "reg", &qwRegAddr64);

        sprintf(aBuff, "%x.ethernet", dwRegAddr32);
        if (strcmp(pPDev->name, aBuff) == 0) break;

        sprintf(aBuff, "%x.ethernet", (unsigned int)qwRegAddr64);
        if (strcmp(pPDev->name, aBuff) == 0) break;

        nTimeout--;
    }
    if (0 == nTimeout)
        pDevNode = NULL;

    return pDevNode;
}

static int ResetPhyViaGpio(ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate)
{
    int nRes = 0;

    nRes = devm_gpio_request_one(&pDrvDescPrivate->pPDev->dev, pDrvDescPrivate->nPhyResetGpioPin,
            pDrvDescPrivate->bPhyResetGpioActiveHigh ? GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
            "phy-reset");
    if (nRes)
    {
        ERR("%s: failed to get atemsys-phy-reset-gpios: %d \n", pDrvDescPrivate->pPDev->name, nRes);
        return nRes;
    }

    if (pDrvDescPrivate->nPhyResetDuration > 20)
        msleep(pDrvDescPrivate->nPhyResetDuration);
    else
        usleep_range(pDrvDescPrivate->nPhyResetDuration * 1000, pDrvDescPrivate->nPhyResetDuration * 1000 + 1000);

    gpio_set_value_cansleep(pDrvDescPrivate->nPhyResetGpioPin, !pDrvDescPrivate->bPhyResetGpioActiveHigh);

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(6,0,0))
    devm_gpio_free(&pDrvDescPrivate->pPDev->dev, pDrvDescPrivate->nPhyResetGpioPin);
#endif

    if (!pDrvDescPrivate->nPhyResetPostDelay)
        return 0;

    if (pDrvDescPrivate->nPhyResetPostDelay > 20)
        msleep(pDrvDescPrivate->nPhyResetPostDelay);
    else
        usleep_range(pDrvDescPrivate->nPhyResetPostDelay * 1000, pDrvDescPrivate->nPhyResetPostDelay * 1000 + 1000);

    return 0;
}

static int EthernetDriverProbe(struct platform_device* pPDev)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    struct net_device* pNDev = NULL;
    const struct of_device_id* pOf_id = NULL;
    static int nDev_id = 0;
    unsigned int dwIndex = 0;
    int nRes = 0;
    struct device_node* pDevNode = NULL;

    INF("Atemsys: Probe device: %s\n", pPDev->name);

    pDevNode = pPDev->dev.of_node;
    if (NULL == pDevNode)
    {
        struct device_node* pDevNodeNew = NULL;
        WRN("%s: Device node empty\n", pPDev->name);

        pDevNodeNew = findDeviceTreeNode(pPDev);
        if (NULL == pDevNodeNew)
        {
            ERR("%s: Device node not found\n", pPDev->name);
            return -ENODATA;
        }
        else
        {
            pDevNode = pDevNodeNew;
        }
    }

    /* Init network device */
    pNDev = alloc_etherdev_mqs(sizeof(ATEMSYS_T_DRV_DESC_PRIVATE), 1 , 1); /* No TX and RX queues requiered */
    if (NULL == pNDev)
    {
        return -ENOMEM;
    }
    SET_NETDEV_DEV(pNDev, &pPDev->dev);

    /* setup board info structure */
    pOf_id = of_match_device(atemsys_ids, &pPDev->dev);
    if (NULL != pOf_id)
    {
        pPDev->id_entry = pOf_id->data;
    }

    pDrvDescPrivate = netdev_priv(pNDev);
    memset(pDrvDescPrivate, 0, sizeof(ATEMSYS_T_DRV_DESC_PRIVATE));
    pDrvDescPrivate->pPDev = pPDev;
    pDrvDescPrivate->nDev_id  = nDev_id++;
    platform_set_drvdata(pPDev, pNDev);
    pDrvDescPrivate->netdev = pNDev;
    pDrvDescPrivate->pDevNode = pDevNode;

    /* Select default pin state */
    pinctrl_pm_select_default_state(&pPDev->dev);

    /* enable clock */
    pDrvDescPrivate->nCountClk = of_property_count_strings(pDevNode,"clock-names");
    if (0 > pDrvDescPrivate->nCountClk)
    {
        pDrvDescPrivate->nCountClk = 0;
    }
    DBG("%s: found %d Clocks\n", pPDev->name , pDrvDescPrivate->nCountClk);

    for (dwIndex = 0; dwIndex < pDrvDescPrivate->nCountClk; dwIndex++)
    {
        if(!of_property_read_string_index(pDevNode, "clock-names", dwIndex, &pDrvDescPrivate->clk_ids[dwIndex]))
        {
            pDrvDescPrivate->clks[dwIndex] = devm_clk_get(&pPDev->dev, pDrvDescPrivate->clk_ids[dwIndex]);
            if (!IS_ERR(pDrvDescPrivate->clks[dwIndex]))
            {
                clk_prepare_enable(pDrvDescPrivate->clks[dwIndex]);
                DBG("%s: Clock %s enabled\n", pPDev->name, pDrvDescPrivate->clk_ids[dwIndex]);
            }
            else
            {
                pDrvDescPrivate->clks[dwIndex] = NULL;
            }
        }
    }

    /* enable PHY regulator*/
    pDrvDescPrivate->pPhyRegulator = devm_regulator_get(&pPDev->dev, "phy");
    if (!IS_ERR(pDrvDescPrivate->pPhyRegulator))
    {
        if (regulator_enable(pDrvDescPrivate->pPhyRegulator))
        {
            WRN("%s: can't enable PHY regulator!\n", pPDev->name);
        }
    }
    else
    {
        pDrvDescPrivate->pPhyRegulator = NULL;
    }

    /* Device run-time power management */
    pm_runtime_dont_use_autosuspend(&pPDev->dev);
    pm_runtime_get_noresume(&pPDev->dev);
    pm_runtime_set_active(&pPDev->dev);
    pm_runtime_enable(&pPDev->dev);

    /* resets */
    {
        struct reset_control*   pResetCtl;
        const char*             szTempString = NULL;

        nRes = of_property_read_string(pDevNode, "reset-names", &szTempString);
        pResetCtl = devm_reset_control_get_optional(&pPDev->dev, szTempString);
        if (NULL != pResetCtl)
        {
            nRes = reset_control_assert(pResetCtl);
            reset_control_deassert(pResetCtl);

            /* Some reset controllers have only reset callback instead of
             * assert + deassert callbacks pair.
             */
            if (-ENOTSUPP == nRes)
            {
                reset_control_reset(pResetCtl);
                pDrvDescPrivate->pResetCtl = pResetCtl;
            }
        }
    }

    /* get prepare data for atemsys and print some data to kernel log */
    {
        unsigned int    dwTemp          = 0;
        const char*     szTempString    = NULL;
        unsigned int    adwTempValues[6];

        /* get identification */
        nRes = of_property_read_string(pDevNode, "atemsys-Ident", &szTempString);
        if ((0 == nRes) && (NULL != szTempString))
        {
            INF("%s: atemsys-Ident: %s\n", pPDev->name, szTempString);
            memcpy(pDrvDescPrivate->MacInfo.szIdent,szTempString, EC_LINKOS_IDENT_MAX_LEN);
        }
        else
        {
            INF("%s: Missing atemsys-Ident in the Device Tree\n", pPDev->name);
        }

        /* get instance number */
        nRes = of_property_read_u32(pDevNode, "atemsys-Instance", &dwTemp);
        if (0 == nRes)
        {
            INF("%s: atemsys-Instance: %d\n", pPDev->name , dwTemp);
            pDrvDescPrivate->MacInfo.dwInstance = dwTemp;
        }
        else
        {
            pDrvDescPrivate->MacInfo.dwInstance = 0;
            INF("%s: Missing atemsys-Instance in the Device Tree\n", pPDev->name);
        }

        /* status */
        szTempString = NULL;
        nRes = of_property_read_string(pDevNode, "status", &szTempString);
        if ((0 == nRes) && (NULL != szTempString))
        {
            DBG("%s: status: %s\n", pPDev->name , szTempString);
            pDrvDescPrivate->MacInfo.dwStatus = (strcmp(szTempString, "okay")==0)? 1:0;
        }

        /* interrupt-parent */
        nRes = of_property_read_u32(pDevNode, "interrupt-parent", &dwTemp);
        if (0 == nRes)
        {
            DBG("%s: interrupt-parent: %d\n", pPDev->name , dwTemp);
        }

        /* interrupts */
        nRes = of_property_read_u32_array(pDevNode, "interrupts", adwTempValues, 6);
        if (0 == nRes)
        {
            DBG("%s: interrupts: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", pPDev->name ,
                adwTempValues[0], adwTempValues[1], adwTempValues[2], adwTempValues[3], adwTempValues[4], adwTempValues[5]);
        }

        /* reg */
#if (defined __arm__)
        nRes = of_property_read_u32_array(pDevNode, "reg", adwTempValues, 2);
        if (0 == nRes)
        {
            DBG("%s: reg: 0x%x 0x%x\n", pPDev->name , adwTempValues[0], adwTempValues[1]);
            pDrvDescPrivate->MacInfo.qwRegAddr = adwTempValues[0];
            pDrvDescPrivate->MacInfo.dwRegSize = adwTempValues[1];
        }
#endif

        /* get phy-mode */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,5,0))
        nRes = of_get_phy_mode(pPDev->dev.of_node, &pDrvDescPrivate->PhyInterface);
        if ((strcmp(pDrvDescPrivate->MacInfo.szIdent, "CPSWG") == 0) && (0==pDrvDescPrivate->PhyInterface))
        {
            struct device_node* pDevNodeNew = pDevNode;
            pDevNodeNew = of_get_child_by_name(pDevNodeNew, "ethernet-ports");
            pDevNodeNew = of_get_child_by_name(pDevNodeNew, "port");
            nRes = of_get_phy_mode(pDevNodeNew, &pDrvDescPrivate->PhyInterface);
        }
#else
        pDrvDescPrivate->PhyInterface = of_get_phy_mode(pPDev->dev.of_node);
#endif
        switch (pDrvDescPrivate->PhyInterface)
        {
            case PHY_INTERFACE_MODE_MII:
            {
                INF("%s: phy-mode: MII\n", pPDev->name);
                pDrvDescPrivate->MacInfo.ePhyMode = eATEMSYS_PHY_MII;
            } break;
            case PHY_INTERFACE_MODE_RMII:
            {
                INF("%s: phy-mode: RMII\n", pPDev->name);
                pDrvDescPrivate->MacInfo.ePhyMode = eATEMSYS_PHY_RMII;
            } break;
            case PHY_INTERFACE_MODE_GMII:
            {
                INF("%s: phy-mode: GMII\n", pPDev->name);
                pDrvDescPrivate->MacInfo.ePhyMode = eATEMSYS_PHY_GMII;
            } break;
            case PHY_INTERFACE_MODE_SGMII:
            {
                INF("%s: phy-mode: SGMII\n", pPDev->name);
                pDrvDescPrivate->MacInfo.ePhyMode = eATEMSYS_PHY_SGMII;
            } break;
            case PHY_INTERFACE_MODE_RGMII_ID:
            case PHY_INTERFACE_MODE_RGMII_RXID:
            case PHY_INTERFACE_MODE_RGMII_TXID:
            case PHY_INTERFACE_MODE_RGMII:
            {
                INF("%s: phy-mode: RGMII\n", pPDev->name);
                pDrvDescPrivate->MacInfo.ePhyMode = eATEMSYS_PHY_RGMII;
            } break;
            default:
            {
                pDrvDescPrivate->MacInfo.ePhyMode = eATEMSYS_PHY_RGMII;
                pDrvDescPrivate->PhyInterface = PHY_INTERFACE_MODE_RGMII;
                WRN("%s: Missing phy-mode in the Device Tree, using RGMII\n", pPDev->name);
            }
        }

        /* pinctrl-names */
        szTempString = NULL;
        nRes = of_property_read_string(pDevNode, "pinctrl-names", &szTempString);
        if ((0 == nRes) && (NULL != szTempString))
        {
            DBG("%s: pinctrl-names: %s\n", pPDev->name , szTempString);
        }

        /* PHY address*/
        pDrvDescPrivate->MacInfo.dwPhyAddr = PHY_AUTO_ADDR;
        pDrvDescPrivate->pPhyNode = of_parse_phandle(pDevNode, "phy-handle", 0);
        if ((strcmp(pDrvDescPrivate->MacInfo.szIdent, "CPSWG") == 0) && (NULL == pDrvDescPrivate->pPhyNode))
        {
            struct device_node* pDevNodeNew = pDevNode;
            pDevNodeNew = of_get_child_by_name(pDevNodeNew, "ethernet-ports");
            pDevNodeNew = of_get_child_by_name(pDevNodeNew, "port");
            pDrvDescPrivate->pPhyNode = of_parse_phandle(pDevNodeNew, "phy-handle", 0);
        }
        if (NULL != pDrvDescPrivate->pPhyNode)
        {
            nRes = of_property_read_u32(pDrvDescPrivate->pPhyNode, "reg", &dwTemp);
            if (0 == nRes)
            {
                INF("%s: PHY mdio addr: %d\n", pPDev->name , dwTemp);
                pDrvDescPrivate->MacInfo.dwPhyAddr = dwTemp;
            }
        }
        else
        {
            int nLen;
            const __be32* pPhyId;
            pPhyId = of_get_property(pDevNode, "phy_id", &nLen);

            if (nLen == (sizeof(__be32) * 2))
            {
                pDrvDescPrivate->pMdioNode = of_find_node_by_phandle(be32_to_cpup(pPhyId));
                pDrvDescPrivate->MacInfo.dwPhyAddr = be32_to_cpup(pPhyId+1);
            }
            else
            {
                INF("%s: Missing phy-handle in the Device Tree\n", pPDev->name);
            }
        }

        /* check if mdio node is sub-node and mac has own mdio bus */
        {
            pDrvDescPrivate->pMdioDevNode = of_get_child_by_name(pDevNode, "mdio");
            if (NULL == pDrvDescPrivate->pMdioDevNode)
                pDrvDescPrivate->pMdioDevNode = of_get_child_by_name(pDevNode, "mdio0");
            if (NULL == pDrvDescPrivate->pMdioDevNode)
                pDrvDescPrivate->pMdioDevNode = of_get_child_by_name(pDevNode, "mdio1");
            if (NULL == pDrvDescPrivate->pMdioDevNode)
                pDrvDescPrivate->pMdioDevNode = of_get_child_by_name(pDevNode, "phy");
            if (NULL == pDrvDescPrivate->pMdioDevNode)
                pDrvDescPrivate->pMdioDevNode = of_get_child_by_name(pDevNode, "ethernet-phy");

            if ((NULL == pDrvDescPrivate->pMdioDevNode) && (NULL != pDrvDescPrivate->pPhyNode))
            {
                /* check if phy node is subnode and us first sub-node as node for mdio bus */
                struct device_node *pTempNode = of_get_parent(pDrvDescPrivate->pPhyNode);
                if ((NULL != pTempNode) && (pTempNode == pDevNode))
                {
                    pDrvDescPrivate->pMdioDevNode = pDrvDescPrivate->pPhyNode;
                }
                else if ((NULL != pTempNode) && (of_get_parent(pTempNode) == pDevNode))
                {
                    pDrvDescPrivate->pMdioDevNode = pTempNode;
                }
            }

            if (NULL != pDrvDescPrivate->pMdioDevNode)
            {
                /* mdio bus is owned by current mac instance */
                pDrvDescPrivate->MacInfo.bNoMdioBus = false;
                INF("%s: mac has mdio bus.\n", pPDev->name );
            }
            else if ((NULL != pDrvDescPrivate->pPhyNode) || (NULL != pDrvDescPrivate->pMdioNode))
            {
                /* mdio bus owned by another mac instance */
                pDrvDescPrivate->MacInfo.bNoMdioBus = true;
                INF("%s: mac has no mdio bus, uses mdio bus of other instance.\n", pPDev->name);
            }
            else
            {
                /* legacy mode: no node for mdio bus in device tree defined */
                pDrvDescPrivate->MacInfo.bNoMdioBus = false;
                INF("%s: handle mdio bus without device tree node.\n", pPDev->name );
            }
        }

        /* PHY reset data */
        nRes = of_property_read_u32(pDevNode, "atemsys-phy-reset-duration", &pDrvDescPrivate->nPhyResetDuration);
        if (nRes) pDrvDescPrivate->nPhyResetDuration = 0;
        pDrvDescPrivate->nPhyResetGpioPin = of_get_named_gpio(pDevNode, "atemsys-phy-reset-gpios", 0);
        nRes = of_property_read_u32(pDevNode, "atemsys-phy-reset-post-delay", &pDrvDescPrivate->nPhyResetPostDelay);
        if (nRes) pDrvDescPrivate->nPhyResetPostDelay = 0;
        pDrvDescPrivate->bPhyResetGpioActiveHigh = of_property_read_bool(pDevNode, "atemsys-phy-reset-active-high");

        if ((0 != pDrvDescPrivate->nPhyResetDuration) && (pDrvDescPrivate->nPhyResetGpioPin != -EPROBE_DEFER)
                && gpio_is_valid(pDrvDescPrivate->nPhyResetGpioPin))
        {
            pDrvDescPrivate->MacInfo.bPhyResetSupported = true;
            DBG("%s: PhyReset ready: GpioPin: %d; Duration %d, bActiveHigh %d, post delay %d\n", pPDev->name,
                pDrvDescPrivate->nPhyResetGpioPin, pDrvDescPrivate->nPhyResetDuration,
                pDrvDescPrivate->bPhyResetGpioActiveHigh, pDrvDescPrivate->nPhyResetPostDelay);
        }
    }

    /* insert device to array */
    for (dwIndex = 0; dwIndex < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; dwIndex++)
    {
        if (NULL == S_apDrvDescPrivate[dwIndex])
        {
            S_apDrvDescPrivate[dwIndex] = pDrvDescPrivate;
            pDrvDescPrivate->MacInfo.dwIndex =  dwIndex;
            break;
        }
    }
    if (dwIndex >= ATEMSYS_MAX_NUMBER_DRV_INSTANCES)
    {
        ERR("%s: Maximum number of instances exceeded!\n", pPDev->name);
        return EthernetDriverRemove(pPDev);
    }

    /* start drivers of sub-nodes */
    if (strcmp(pDrvDescPrivate->MacInfo.szIdent, "CPSW") == 0
       || strcmp(pDrvDescPrivate->MacInfo.szIdent, "ICSS") == 0)
    {
        of_platform_populate(pDevNode, NULL, NULL, &pPDev->dev);
        DBG("%s: start drivers of sub-nodes.\n", pPDev->name );
    }
    if (strcmp(pDrvDescPrivate->MacInfo.szIdent, "CPSWG") == 0)
    {
        /* in subnode "ethernet-ports" start driver for "port@2" */
        struct device_node* pDevNodeNew = pDevNode;
        pDevNodeNew = of_get_child_by_name(pDevNodeNew, "ethernet-ports");
        of_platform_populate(pDevNodeNew, NULL, NULL, &pPDev->dev);
        DBG("%s: start drivers of sub-nodes.\n", pPDev->name );
    }

    /* prepare mutex for mdio */
    mutex_init(&pDrvDescPrivate->mdio_mutex);
    mutex_init(&pDrvDescPrivate->mdio_order_mutex);
    init_waitqueue_head(&pDrvDescPrivate->mdio_wait_queue);
    pDrvDescPrivate->mdio_wait_queue_cnt = 0;

    return 0;
}


static int EthernetDriverRemove(struct platform_device* pPDev)
{
    struct net_device* pNDev = platform_get_drvdata(pPDev);
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = netdev_priv(pNDev);
    unsigned int i = 0;

    if ((NULL != pDrvDescPrivate->pPhyDev) || (NULL != pDrvDescPrivate->pMdioBus))
    {
        ERR("%s: EthernetDriverRemove: PHY driver is still active!\n", pPDev->name);
    }

    if (NULL != pDrvDescPrivate->pPhyRegulator)
    {
        regulator_disable(pDrvDescPrivate->pPhyRegulator);
    }

    /* Decrement refcount */
    of_node_put(pDrvDescPrivate->pPhyNode);

    pm_runtime_put(&pPDev->dev);
    pm_runtime_disable(&pPDev->dev);

    /* resets */
    if (NULL != pDrvDescPrivate->pResetCtl)
    {
        reset_control_assert(pDrvDescPrivate->pResetCtl);
    }
    for (i = 0; i < ATEMSYS_MAX_NUMBER_OF_CLOCKS; i++)
    {
        if (NULL != pDrvDescPrivate->clk_ids[i])
        {
            clk_disable_unprepare(pDrvDescPrivate->clks[i]);
            DBG("%s: Clock %s unprepared\n", pPDev->name, pDrvDescPrivate->clk_ids[i]);
        }
    }
    mutex_destroy(&pDrvDescPrivate->mdio_mutex);
    mutex_destroy(&pDrvDescPrivate->mdio_order_mutex);

    pinctrl_pm_select_sleep_state(&pPDev->dev);

    free_netdev(pNDev);

    INF("%s: atemsys driver removed: %s Instance %d\n", pPDev->name, pDrvDescPrivate->MacInfo.szIdent, pDrvDescPrivate->MacInfo.dwInstance);

    S_apDrvDescPrivate[pDrvDescPrivate->MacInfo.dwIndex] = NULL;

    if (NULL != pDrvDescPrivate->pDevDesc)
    {
        pDrvDescPrivate->pDevDesc->pPlatformDev = NULL;
        pDrvDescPrivate->pDevDesc->pDrvDesc     = NULL;
        pDrvDescPrivate->pDevDesc               = NULL;
    }

    return 0;
}

static int CleanUpEthernetDriverOnRelease(ATEMSYS_T_DEVICE_DESC* pDevDesc)
{
    ATEMSYS_T_DRV_DESC_PRIVATE* pDrvDescPrivate = NULL;
    int nRes = -1;
    unsigned int i = 0;

    if (pDevDesc == NULL)
    {
        return 0;
    }

    for (i = 0; i < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; i++)
    {

        pDrvDescPrivate = S_apDrvDescPrivate[i];
        if (NULL == pDrvDescPrivate)
        {
            continue;
        }

        if (pDrvDescPrivate->pDevDesc == pDevDesc)
        {
            INF("%s: Cleanup: pDevDesc = 0x%px\n", pDrvDescPrivate->pPDev->name, pDevDesc);

            /* ensure mdio bus and PHY are down */
            if ((NULL != pDrvDescPrivate->pPhyDev) || (NULL != pDrvDescPrivate->pMdioBus))
            {
                int timeout = 0;
                for (timeout = 50; timeout-- < 0; msleep(100))
                {
                    nRes = StopPhyWithoutIoctlMdioHandling(pDrvDescPrivate->pPDev);
                    if (-EAGAIN != nRes)
                        break;
                }
            }
            /* clean descriptor */
            pDrvDescPrivate->pDevDesc = NULL;
            pDevDesc->pPlatformDev    = NULL;
            pDevDesc->pDrvDesc        = NULL;
        }
    }

    return 0;
}

static struct platform_device_id mac_devtype[] = {
    {
        .name = ATEMSYS_DT_DRIVER_NAME,
        .driver_data = 0,
    }, {
        /* sentinel */
    }
};


MODULE_DEVICE_TABLE(platform, mac_devtype);

static struct platform_driver mac_driver = {
    .driver    = {
        .name           = ATEMSYS_DT_DRIVER_NAME,
        .of_match_table = atemsys_ids,
    },
    .id_table  = mac_devtype,
    .probe     = EthernetDriverProbe,
    .remove    = EthernetDriverRemove,
};
#endif /* INCLUDE_ATEMSYS_DT_DRIVER */


#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
#define ATEMSYS_PCI_DRIVER_NAME "atemsys_pci"
#define PCI_VENDOR_ID_BECKHOFF  0x15EC

static void PciDriverRemove(struct pci_dev* pPciDev)
{
    ATEMSYS_T_PCI_DRV_DESC_PRIVATE* pPciDrvDescPrivate = (ATEMSYS_T_PCI_DRV_DESC_PRIVATE*)pci_get_drvdata(pPciDev);

    if (NULL != pPciDrvDescPrivate)
    {
        /* remove references to the device */
        if (NULL != pPciDrvDescPrivate->pDevDesc)
        {
            pPciDrvDescPrivate->pDevDesc->pPcidev = NULL;
            pPciDrvDescPrivate->pDevDesc->pPciDrvDesc = NULL;
            pPciDrvDescPrivate->pDevDesc = NULL;
        }
        S_apPciDrvDescPrivate[pPciDrvDescPrivate->dwIndex] = NULL;

        kfree(pPciDrvDescPrivate);
    }

    /* disable device */
    pci_disable_msi(pPciDev);
    pci_release_regions(pPciDev);
    pci_disable_pcie_error_reporting(pPciDev);
    pci_disable_device(pPciDev);

    INF("%s: %s: disconnected\n", pci_name(pPciDev), ATEMSYS_PCI_DRIVER_NAME);
}

static int PciDriverProbe(struct pci_dev* pPciDev, const struct pci_device_id* id)
{
    ATEMSYS_T_PCI_DRV_DESC_PRIVATE* pPciDrvDescPrivate = NULL;
    int nRes = -ENODEV;
    int dwIndex = 0;

    /* check if is wanted pci device */
    if ((strcmp(AllowedPciDevices, "PCI_ANY_ID") != 0) && (strstr(AllowedPciDevices, pci_name(pPciDev)) == NULL))
    {
        /* don't attach driver */
        DBG("%s: PciDriverProbe: restricted by user parameters!\n", pci_name(pPciDev));

        return -ENODEV; /* error code doesn't create error message */
    }

    /* setup pci device */
    nRes = pci_enable_device_mem(pPciDev);
    if (nRes)
    {
        ERR("%s: PciDriverProbe: pci_enable_device_mem failed!\n", pci_name(pPciDev));
        goto Exit;
    }

    nRes = DefaultPciSettings(pPciDev);
    if (nRes)
    {
        ERR("%s: PciDriverProbe: DefaultPciSettings failed\n", pci_name(pPciDev));
        goto Exit;
    }
    pci_save_state(pPciDev);
    pci_enable_pcie_error_reporting(pPciDev);
    nRes = pci_request_regions(pPciDev, ATEMSYS_DEVICE_NAME);
    if (nRes < 0)
    {
        ERR("%s: PciDriverProbe: device in use by another driver?\n", pci_name(pPciDev));
        nRes = -EBUSY;
        goto Exit;
    }

    /* create private desc */
    pPciDrvDescPrivate = (ATEMSYS_T_PCI_DRV_DESC_PRIVATE*)kzalloc(sizeof(ATEMSYS_T_PCI_DRV_DESC_PRIVATE), GFP_KERNEL);
    if (NULL == pPciDrvDescPrivate)
    {
        nRes = -ENOMEM;
        goto Exit;
    }
    pPciDrvDescPrivate->pPciDev = pPciDev;

    /* get Pci Info */
    pPciDrvDescPrivate->wVendorId         = pPciDev->vendor;
    pPciDrvDescPrivate->wDevice           = pPciDev->device;
    pPciDrvDescPrivate->wRevision         = pPciDev->revision;
    pPciDrvDescPrivate->wSubsystem_vendor = pPciDev->subsystem_vendor;
    pPciDrvDescPrivate->wSubsystem_device = pPciDev->subsystem_device;
    pPciDrvDescPrivate->nPciBus           = pPciDev->bus->number;
    pPciDrvDescPrivate->nPciDomain        = pci_domain_nr(pPciDev->bus);
    pPciDrvDescPrivate->nPciDev           = PCI_SLOT(pPciDev->devfn);
    pPciDrvDescPrivate->nPciFun           = PCI_FUNC(pPciDev->devfn);

    INF("%s: %s: connected vendor:0x%04x device:0x%04x rev:0x%02x - sub_vendor:0x%04x sub_device:0x%04x\n", pci_name(pPciDev), ATEMSYS_PCI_DRIVER_NAME,
            pPciDev->vendor, pPciDev->device, pPciDev->revision,
            pPciDev->subsystem_vendor, pPciDev->subsystem_device);

    /* find the memory BAR */
    {
       unsigned long ioBase  = 0;
       unsigned int  dwIOLen = 0;
       int i    = 0;
       int nBar = 0;

       for (i = 0; i < ATEMSYS_PCI_MAXBAR ; i++)
       {
          if (pci_resource_flags(pPciDev, i) & IORESOURCE_MEM)
          {
             /* IO area address */
             ioBase = pci_resource_start(pPciDev, i);
             pPciDrvDescPrivate->aBars[nBar].qwIOMem = ioBase;

             /* IO area length */
             dwIOLen = pci_resource_len(pPciDev, i);
             pPciDrvDescPrivate->aBars[nBar].dwIOLen = dwIOLen;

             nBar++;
          }
       }

       if (nBar == 0)
       {
          WRN("%s: PciDriverProbe: No memory BAR found\n", pci_name(pPciDev));
       }

       pPciDrvDescPrivate->nBarCnt = nBar;
    }

    /* insert device to array */
    for (dwIndex = 0; dwIndex < ATEMSYS_MAX_NUMBER_DRV_INSTANCES; dwIndex++)
    {
        if (NULL == S_apPciDrvDescPrivate[dwIndex])
        {
            S_apPciDrvDescPrivate[dwIndex] = pPciDrvDescPrivate;
            pPciDrvDescPrivate->dwIndex =  dwIndex;
            break;
        }
    }
    if (ATEMSYS_MAX_NUMBER_DRV_INSTANCES <= dwIndex)
    {
        ERR("%s: PciDriverProbe: insert device to array failed\n", pci_name(pPciDev));
        nRes = -EBUSY;
        goto Exit;
    }

    pci_set_drvdata(pPciDev, pPciDrvDescPrivate);

    nRes = 0; /* OK */
Exit:
    if (nRes != 0 /* OK */)
    {
        if (NULL != pPciDrvDescPrivate)
        {
            kfree(pPciDrvDescPrivate);
        }
    }
    return nRes;
}

typedef struct _ATEMSYS_PCI_INFO {
} ATEMSYS_PCI_INFO;

static const struct _ATEMSYS_PCI_INFO oAtemsysPciInfo = {
};


static const struct pci_device_id pci_devtype[] = {
    {
    /* all devices of class PCI_CLASS_NETWORK_ETHERNET */
    .vendor      = PCI_ANY_ID,
    .device      = PCI_ANY_ID,
    .subvendor   = PCI_ANY_ID,
    .subdevice   = PCI_ANY_ID,
    .class       = (PCI_CLASS_NETWORK_ETHERNET << 8),
    .class_mask  = (0xFFFF00),
    .driver_data = (kernel_ulong_t)&oAtemsysPciInfo
    },
    {
     /* all devices with BECKHOFF vendor id */
    .vendor      = PCI_VENDOR_ID_BECKHOFF,
    .device      = PCI_ANY_ID,
    .subvendor   = PCI_ANY_ID,
    .subdevice   = PCI_ANY_ID,
    .driver_data = (kernel_ulong_t)&oAtemsysPciInfo
    },
    {}
};

MODULE_DEVICE_TABLE(pci, pci_devtype);
static struct pci_driver oPciDriver = {
    .name     = ATEMSYS_PCI_DRIVER_NAME,
    .id_table = pci_devtype,
    .probe    = PciDriverProbe,
    .remove   = PciDriverRemove,
};

#endif /* (defined INCLUDE_ATEMSYS_PCI_DRIVER) */


/*
 * Initialize the module - Register the character device
 */
int init_module(void)
{
#if (defined CONFIG_XENO_COBALT)

    int major = rtdm_dev_register(&device);
    if (major < 0)
    {
        INF("Failed to register %s (err: %d)\n", device.label, major);
        return major;
    }
#else

    /* Register the character device */
    int major = register_chrdev(MAJOR_NUM, ATEMSYS_DEVICE_NAME, &Fops);
    if (major < 0)
    {
        INF("Failed to register %s (err: %d)\n",
               ATEMSYS_DEVICE_NAME, major);
        return major;
    }
#endif /* CONFIG_XENO_COBALT */

    /* Register Pci and Platform Driver */
#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
    memset(S_apDrvDescPrivate ,0, ATEMSYS_MAX_NUMBER_DRV_INSTANCES * sizeof(ATEMSYS_T_DRV_DESC_PRIVATE*));
    platform_driver_register(&mac_driver);
#if (defined CONFIG_XENO_COBALT)
    memset(&S_oAtemsysWorkerThreadDesc, 0, sizeof(ATEMSYS_T_WORKER_THREAD_DESC));
    mutex_init(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
    S_oAtemsysWorkerThreadDesc.etx_thread = kthread_create(AtemsysWorkerThread,(void*)&S_oAtemsysWorkerThreadDesc,"Atemsys_WorkerThread");
    if(NULL == S_oAtemsysWorkerThreadDesc.etx_thread)
    {
        ERR("Cannot create kthread for AtemsysWorkerThread\n");
    }
    wake_up_process(S_oAtemsysWorkerThreadDesc.etx_thread);
#endif /*#if (defined CONFIG_XENO_COBALT)*/
#endif

#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
    memset(S_apPciDrvDescPrivate ,0, ATEMSYS_MAX_NUMBER_DRV_INSTANCES * sizeof(ATEMSYS_T_PCI_DRV_DESC_PRIVATE*));

    if (0 == strcmp(AllowedPciDevices, ""))
    {
        DBG("Atemsys PCI driver not registered\n");
    }
    else
    {
        if (0 != pci_register_driver(&oPciDriver))
        {
            INF("Register Atemsys PCI driver failed!\n");
        }
    }
#endif

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,4,0))
    S_pDevClass = class_create(ATEMSYS_DEVICE_NAME);
#else
    S_pDevClass = class_create(THIS_MODULE, ATEMSYS_DEVICE_NAME);
#endif
    if (IS_ERR(S_pDevClass))
    {
        INF("class_create failed\n");
        return -1;
    }

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24))
    S_pDev = class_device_create(S_pDevClass, NULL, MKDEV(MAJOR_NUM, 0), NULL, ATEMSYS_DEVICE_NAME);
#else
    S_pDev = device_create(S_pDevClass, NULL, MKDEV(MAJOR_NUM, 0), NULL, ATEMSYS_DEVICE_NAME);
#endif

#if (defined __arm__) || (defined __aarch64__)
    {
        int nRetVal = 0;
        S_pPlatformDev = platform_device_alloc("atemsys_PDev", MKDEV(MAJOR_NUM, 0));
        S_pPlatformDev->dev.parent = S_pDev;

        nRetVal = platform_device_add(S_pPlatformDev);
        if (nRetVal != 0) {
            ERR("platform_device_add failed. return=%d\n", nRetVal);
        }

 #if (defined __arm__) || (defined CONFIG_ZONE_DMA32)
        S_pPlatformDev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
        if (!S_pPlatformDev->dev.dma_mask)
        {
            S_pPlatformDev->dev.dma_mask = &S_pPlatformDev->dev.coherent_dma_mask;
        }
 #endif
    }
#else
    S_pPlatformDev = NULL;
#endif

    if (IS_ERR(S_pDev))
    {
        INF("device_create failed\n");
        return -1;
    }

    S_pDev->coherent_dma_mask = DMA_BIT_MASK(32);
    if (!S_pDev->dma_mask)
    {
        S_pDev->dma_mask = &S_pDev->coherent_dma_mask;
    }

#if (defined CONFIG_OF)
    OF_DMA_CONFIGURE(S_pDev,S_pDev->of_node);
#endif

    INIT_LIST_HEAD(&S_DevNode.list);

    INF("%s v%s loaded\n", ATEMSYS_DEVICE_NAME, ATEMSYS_VERSION_STR);
    return 0;
}

/*
 * Cleanup - unregister the appropriate file from /proc
 */
void cleanup_module(void)
{
   INF("%s v%s unloaded\n", ATEMSYS_DEVICE_NAME, ATEMSYS_VERSION_STR);

    /* Unregister Pci and Platform Driver */
#if (defined INCLUDE_ATEMSYS_DT_DRIVER)
    platform_driver_unregister(&mac_driver);
#if (defined CONFIG_XENO_COBALT)
    S_oAtemsysWorkerThreadDesc.bWorkerTaskShutdown = true;
    for (;;)
    {
        if (!S_oAtemsysWorkerThreadDesc.bWorkerTaskRunning)
        {
            break;
        }

        msleep(100);
    }
    mutex_destroy(&S_oAtemsysWorkerThreadDesc.WorkerTask_mutex);
#endif /*#if (defined CONFIG_XENO_COBALT)*/
#endif

#if (defined INCLUDE_ATEMSYS_PCI_DRIVER)
    if (0 != strcmp(AllowedPciDevices, ""))
    {
        pci_unregister_driver(&oPciDriver);
    }
#endif

#if (defined __arm__) || (defined __aarch64__)
    if (NULL != S_pPlatformDev)
    {
        platform_device_del(S_pPlatformDev);
        platform_device_put(S_pPlatformDev);
        S_pPlatformDev = NULL;
    }
#endif

#if (defined CONFIG_OF)
   device_release_driver(S_pDev); //see device_del() -> bus_remove_device()
#endif

   device_destroy(S_pDevClass, MKDEV(MAJOR_NUM, 0));
   class_destroy(S_pDevClass);

#if (defined CONFIG_XENO_COBALT)
   rtdm_dev_unregister(&device);
#else
   unregister_chrdev(MAJOR_NUM, ATEMSYS_DEVICE_NAME);
#endif /* CONFIG_XENO_COBALT */
}

