/*
 * All Rights Reserved (C) 2021-2025
 * Exoligent SARL (Colombes, France)
 * Website: www.exoligent.com
 * Author : Maxime Coroyer <maxime.coroyer@exoligent.com>
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/dma-mapping.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <asm/io.h>
#include <linux/list.h>
#include <linux/hrtimer.h>
#include <linux/sched.h>

#include "powerfip-pci.h"

#define DRV_NAME "powerfip"
#define BOARD_INFO "PowerFIP Device"

/* Module parameters */
static unsigned int max_devices = MAX_FA_BOARD;

/* Module globals */
static LIST_HEAD(pwrfip_list); /* sorted by ascending minor number */
static struct class *pwrfip_class;
static dev_t pwrfip_dev_first;

int pci_major = PCI_MAJOR;
static uint8_t minors[MAX_FA_BOARD] = {0};

int pwrfip_open(struct inode *inode, struct file *file);
int pwrfip_release(struct inode *inode, struct file *file);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
    int pwrfip_ioctl(struct inode *inode, struct file *file,
        unsigned int cmd, unsigned long arg);
#else
    long pwrfip_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
#endif
static int dma_xfer(struct pwrfip_dev *pwrfip, int direction, void *host_buffer,
    uint32_t target_addr, size_t bsz);

static int pwrfip_irq_global_enable(struct pwrfip_dev *pwrfip, int enable)
{
    uint32_t *reg_addr = (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + PWRFIP_GCTL_REG);
    uint32_t reg_val = ioread32(reg_addr);
    uint32_t reg_cmd = (pwrfip->has_msi_cap) ? INT_MSI_ENA : INT_LEG_ENA;

    if (enable)
        reg_val |= reg_cmd;
    else
        reg_val &= ~reg_cmd;

    if (pwrfip->has_msi_cap)
        reg_val &= ~INT_LEG_ENA;

    iowrite32(reg_val, reg_addr);
    return 0;
}

static int pwrfip_irq_mbox_enable(struct pwrfip_mbox *mbx, int enable)
{
    uint32_t reg_val, reg_cmd = 0x1;

    if (!mbx->has_it_cap)
        return -1;

    reg_val = ioread32(mbx->config);
    if (enable)
        reg_val |= reg_cmd;
    else
        reg_val &= ~reg_cmd;

    iowrite32(reg_val, mbx->config);
    return 0;
}

static irqreturn_t pwrfip_mbox_it_treat(struct pwrfip_dev *pwrfip, int mbox_num,
            uint32_t *irq_source)
{
    irqreturn_t ret = IRQ_HANDLED;
    uint32_t sta_val, sta_msk;

    if (mbox_num >= PWRFIP_MBOX_COUNT)
        return IRQ_NONE;

    if (!pwrfip->mbox[mbox_num].has_it_cap)
        return IRQ_NONE;

    sta_val = ioread32(pwrfip->mbox[mbox_num].status);
    sta_msk = (pwrfip->mbox[mbox_num].type == MBOX_IN) ? \
                        MBOX_RDY: MBOX_ACK;
    /* flag to test: inbox='rdy', outbox='ack' */
    if (sta_val & sta_msk) {

        spin_lock(&pwrfip->mbox[mbox_num].it_lock);

        switch (mbox_num) {
        case PWRFIP_MBOX_REQ:
        {
            *irq_source |= PWRFIP_MBOX_IRQ_REQ_ACK;
        }
        break;
        case PWRFIP_MBOX_REP:
        {
            *irq_source |= PWRFIP_MBOX_IRQ_REP_RDY;
        }
        break;
        case PWRFIP_MBOX_EVT:
        {   /**
             * specialized source events flags is contained
             * inside rdy_code field (pwrfip_mbox_irq_flags)
             */
            int i = 0;
            uint32_t *data = (uint32_t *) &pwrfip->irq.evt;
            uint32_t rdy_code = (sta_val >> MBOX_STATUS_RDY_CODE_SHIFT);
            rdy_code &= MBOX_STATUS_RDY_CODE_MASK;

            /* raz sync evt */
            memset(&pwrfip->irq.evt, 0, PWRFIP_EVENT_BSZ);

            *irq_source |= rdy_code;
            /**
             * Get synchronous event from mailbox:
             * Note: event content starts to mbox data area
             * (+1 word)
             * (see struct pwrfip_evt_sync_mboxcmd)
             */
            for (i = 0; i < PWRFIP_EVENT_WSZ; ++i) {
                *(data + i) = \
                    ioread32(pwrfip->mbox[mbox_num].data + \
                             1 /* mbox hdr wsz */ + i);
            }

            /**
             * Note: Synchronous event mailbox has to be
             * acknowledged immediately.
             */
            iowrite32(MBOX_ACK, pwrfip->mbox[mbox_num].status);

            /**
             * This event is transmitted to the user space
             * => we disable this irq for the time of processing
             */
            pwrfip_irq_mbox_enable(&pwrfip->mbox[mbox_num], 0);
            pwrfip->irq.cnt++;
            pwrfip->irq.src = rdy_code;

        } break;
        default: ret = IRQ_NONE;
        }

        pwrfip->mbox[mbox_num].it_flag = 1;
        spin_unlock(&pwrfip->mbox[mbox_num].it_lock);
        wake_up(&pwrfip->mbox[mbox_num].it_mutex);
    }

    return ret;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
    static irqreturn_t pwrfip_irq_handler(int irq, void *dev_id)
#else
    static irqreturn_t pwrfip_irq_handler(int irq, void *dev_id,
                            struct pt_regs *regs)
#endif
{
    struct pwrfip_dev *pwrfip = (struct pwrfip_dev *) dev_id;
    irqreturn_t irq_ret = 0;
    uint32_t irq_source = 0;
    int irq_number = irq - pwrfip->infos.irq_number;
    int i = 0;

    if (!pwrfip->has_msi_cap && irq_number) {
        /**
         * For a Line-Based interrupt, irq_number should be always equals to 0
         * => irq not produced by this device
         */
        return IRQ_NONE;
    }

    switch (irq_number)
    {
    case 0: /* global it: check all mboxes */
        for (i = 0; i < PWRFIP_MBOX_COUNT; ++i)
            irq_ret |= pwrfip_mbox_it_treat(pwrfip, i, &irq_source);
    break;
    case 1: /* msi from mbox 0 */
        irq_ret |= pwrfip_mbox_it_treat(pwrfip, PWRFIP_MBOX_REQ, &irq_source);
    break;
    case 2: /* msi from mbox 1 */
        irq_ret |= pwrfip_mbox_it_treat(pwrfip, PWRFIP_MBOX_REP, &irq_source);
    break;
    case 3: /* msi from mbox 2 */
        irq_ret |= pwrfip_mbox_it_treat(pwrfip, PWRFIP_MBOX_EVT, &irq_source);
    break;
    default: break;
    }

    return IRQ_RETVAL(irq_ret);
}

static int pwrfip_board_init(struct pwrfip_dev *pwrfip)
{
    uint32_t reg_val = 0;
    uint8_t magic_num = 0;

    /* private infos */
    /* => dma capability ? */
    reg_val = ioread32(pwrfip->remap[BAR_BOARD] + RES_REG);
    magic_num = (uint8_t) ((reg_val >> RES_MAG_SHIFT) & RES_MAG_MASK);
    if (magic_num != RES_MAG_NUM)
        goto public_part;

    pwrfip->has_dma_cap = (reg_val & RES_DMA) ? true : false;
    if (pwrfip->has_dma_cap) {

        /* dma mode 32-bit mode */
        if (dma_set_mask(&pwrfip->pdev->dev, DMA_BIT_MASK(32)))
            pwrfip->has_dma_cap = false;
        else {
            /* allocate a dma buffer (1MB) */
            pwrfip->dma_buffer = kcalloc(1, DMA_BUFFER_BSZ, GFP_DMA);
            if (!pwrfip->dma_buffer) {
                pwrfip->has_dma_cap = false;
                goto public_part;
            }
        }
    }

public_part:
    /* public infos */
    pwrfip->infos.index = pwrfip->minor + 1;
    pwrfip->infos.fsn = ioread32(pwrfip->remap[BAR_BOARD] + FSN_HI_REG);
    pwrfip->infos.fsn <<= 32;
    pwrfip->infos.fsn |= ioread32(pwrfip->remap[BAR_BOARD] + FSN_LO_REG);
    pwrfip->infos.fsn >>= 16; // the two LSB bytes are removed (not necessary because fixed)
    pwrfip->infos.vid = pwrfip->pdev->vendor;
    pwrfip->infos.did = pwrfip->pdev->device;
    pwrfip->infos.ssvid = pwrfip->pdev->subsystem_vendor;
    pwrfip->infos.ssdid = pwrfip->pdev->subsystem_device;
    pwrfip->infos.irq_number = (int) pwrfip->pdev->irq;
    pwrfip->infos.drv_version = POWERFIP_DRV_MAJOR << 16;
    pwrfip->infos.drv_version += POWERFIP_DRV_MINOR << 8;
    pwrfip->infos.drv_version += POWERFIP_DRV_PATCH;
    pwrfip->infos.drv_date = POWERFIP_DRV_BUILD_YEAR << 16;
    pwrfip->infos.drv_date += POWERFIP_DRV_BUILD_MONTH << 8;
    pwrfip->infos.drv_date += POWERFIP_DRV_BUILD_DAY;
    return 0;
}

static void pwrfip_register(struct pwrfip_dev *pwrfip, int index)
{
    char dev_name[64];
    int err, devno = MKDEV(pci_major, index);

    /* add class to device - ls /dev/ */
    sprintf(dev_name, "%s%d", DRV_NAME, index);
    pwrfip->dev = device_create(pwrfip_class, NULL, devno, NULL, dev_name);
    if (IS_ERR(pwrfip->dev))
        printk(KERN_ERR DRV_NAME
            ": Error when adding class to device (index %d)", index);

    cdev_init(&pwrfip->cdev, &pwrfip_fops);
    pwrfip->cdev.owner = THIS_MODULE;
    pwrfip->cdev.ops = &pwrfip_fops;
    err = cdev_add(&pwrfip->cdev, devno, max_devices);
    if (err)
        printk(KERN_ERR DRV_NAME
            ": Error %d when adding device (index %d)", err, index);
}

static void pwrfip_unregister(struct pwrfip_dev* pwrfip)
{
    /* device destroying */
    if (pwrfip_class) {
        int devno = MKDEV(pci_major, pwrfip->minor);
        device_destroy(pwrfip_class, devno);
    }
    cdev_del(&pwrfip->cdev);
}

static int pwrfip_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct pwrfip_dev *pwrfip;
    uint8_t irq_pin;
    uint8_t bar_mem_cnt;
    uint8_t free_minor_dectected = 0;
    unsigned long flag;
    int err, i, nb_vecs = 0, msi_pos = 0;

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 10, 0))
    unsigned int flags = PCI_IRQ_INTX | PCI_IRQ_MSI;
#else
    unsigned int flags = PCI_IRQ_LEGACY | PCI_IRQ_MSI;
#endif

    spinlock_t to_lock = __SPIN_LOCK_UNLOCKED(to_lock);

    printk(KERN_DEBUG DRV_NAME ": ==> Device probing\n");

    pwrfip = kmalloc(sizeof(struct pwrfip_dev), GFP_KERNEL);
    if (!pwrfip)
        return -ENOMEM;

    err = pci_enable_device(pdev);
    if (err)
        return err;

    /* Enable: MSI + DMA support */
    pci_set_master(pdev);

    /* MSI capabilities ? */
    if ((msi_pos = pci_find_capability(pdev, PCI_CAP_ID_MSI)) > 0)
        pwrfip->has_msi_cap = true;

    pwrfip->pdev = pdev;

    spin_lock_init(&to_lock);

    spin_lock(&to_lock);
    for (i = 0; i < MAX_FA_BOARD; i++) {
        if (minors[i] == 0) {
            minors[i] = 1 ;
            pwrfip->minor = i;
            free_minor_dectected = 1;
            break;
        }
    }

    if (free_minor_dectected == 0)
        return -EFAULT;

    spin_unlock(&to_lock);
    pci_set_drvdata(pdev, pwrfip);
    pwrfip_register(pwrfip, pwrfip->minor);

    /* Reserve PCI I/O and memory resources */
    err = pci_request_regions(pdev, DRV_NAME);
    if (err)
        return -ENODEV;

    bar_mem_cnt = 0;
    for (i = 0; i < DEVICE_COUNT_RESOURCE; i++)  {

        if (pci_resource_start(pdev, i) != 0) {
            printk(KERN_DEBUG DRV_NAME
                ": BAR %d (%#08x - %#08x), len = %d, flags = %#08x\n", i,
                (uint32_t) pci_resource_start(pdev, i),
                (uint32_t) pci_resource_end(pdev, i),
                (uint32_t) pci_resource_len(pdev, i),
                (uint32_t) pci_resource_flags(pdev, i));
        }

        flag = pci_resource_flags(pdev, i);

        if ((flag & IORESOURCE_MEM) == IORESOURCE_MEM) {
            pwrfip->infos.bar_bsz[bar_mem_cnt] = pci_resource_len(pdev, i);
            pwrfip->infos.bar_base[bar_mem_cnt] = pci_resource_start(pdev, i);
            pwrfip->remap[bar_mem_cnt] = ioremap(pci_resource_start(pdev, i),
                                            pci_resource_len(pdev, i));
            if (pwrfip->remap[bar_mem_cnt] == NULL)
                return -ENOMEM;

            bar_mem_cnt++;
        }
    }

    /* mbox infos */
    for (i = 0; i < PWRFIP_MBOX_COUNT; ++i) {
        pwrfip->mbox[i].index = i;
        /* mbox registers settings */
        pwrfip->mbox[i].config = (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + \
                            MBOX_AREA_PCI_OFFSET + MBOX_CONFIG_ADDR(i));
        pwrfip->mbox[i].status = (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + \
                            MBOX_AREA_PCI_OFFSET + MBOX_STATUS_ADDR(i));
        pwrfip->mbox[i].data_wsz = (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + \
                            MBOX_AREA_PCI_OFFSET + MBOX_DATA_WSZ_ADDR(i));
        pwrfip->mbox[i].data = (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + \
                            MBOX_AREA_PCI_OFFSET + MBOX_DATA_ADDR(i));
        /* mbox interrupts settings */
        pwrfip->mbox[i].it_flag = 0;
        init_waitqueue_head(&pwrfip->mbox[i].it_mutex);
        spin_lock_init(&pwrfip->mbox[i].it_lock);
    }

    err = pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &irq_pin);
    if (err)
        return err;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0)
    nb_vecs = pci_alloc_irq_vectors(pdev, 1, IRQ_VECS_MAX, flags);
#else
    nb_vecs = pci_enable_msi_range(pdev, 1, IRQ_VECS_MAX);
#endif

    if (nb_vecs < 0)
        return -ENOSPC;

    for (i = 0; i < nb_vecs; i++)  {
        err = request_irq(pdev->irq+i, &pwrfip_irq_handler,
                IRQF_SHARED, DRV_NAME, pwrfip);
        if (err)
            return err;
    }

    pwrfip->nb_irq_vecs = nb_vecs;
    pwrfip->infos.bar_cnt = bar_mem_cnt;
    err = pwrfip_board_init(pwrfip);
    if (err)
        return err;

    /* MSI capabilities registration */
    if (pwrfip->has_msi_cap) {
        uint32_t msi_addr_lo = 0;
        uint32_t msi_addr_hi = 0;
        pci_read_config_dword(pdev, msi_pos + PCI_MSI_ADDRESS_LO, &msi_addr_lo);
        pci_read_config_dword(pdev, msi_pos + PCI_MSI_ADDRESS_HI, &msi_addr_hi);
        iowrite32(msi_addr_lo, pwrfip->remap[BAR_BOARD] + MSI_ADDR_LO_REG);
        iowrite32(msi_addr_hi, pwrfip->remap[BAR_BOARD] + MSI_ADDR_HI_REG);
        /* TODO: case of msi vectors > 4 (not supported here) */
    }

    printk(KERN_DEBUG DRV_NAME ": DMA Capability = %s\n",
        (pwrfip->has_dma_cap) ? "YES" : "NO");
    printk(KERN_DEBUG DRV_NAME ": MSI Capability = %s\n",
        (pwrfip->has_msi_cap) ? "YES" : "NO");
    printk(KERN_DEBUG DRV_NAME
        ": IRQ pin  = #%d (0=none, 1=INTA#...4=INTD#)\n", irq_pin);
    printk(KERN_DEBUG DRV_NAME ": IRQ line = #%d\n", pdev->irq);
    printk(KERN_DEBUG DRV_NAME ": IRQ vectors = %d\n", nb_vecs);

    spin_lock(&to_lock);
    list_add_tail(&pwrfip->link, &pwrfip_list);
    spin_unlock(&to_lock);

    return 0;
}

static void pwrfip_remove(struct pci_dev *pdev)
{
    spinlock_t to_lock = __SPIN_LOCK_UNLOCKED(to_lock);
    struct pwrfip_dev *pwrfip = pci_get_drvdata(pdev);
    int i = 0;

    printk(KERN_DEBUG DRV_NAME ": ==> Device removing (idx=%d)\n",
           pwrfip->minor);

    pci_clear_master(pdev);

    /* free IRQ */
    if (pwrfip->infos.irq_number >= 0) {
        printk(KERN_DEBUG DRV_NAME
            ": Freeing IRQ #%d\n", pwrfip->infos.irq_number);
        /* disable irq */
        for (i = 0; i < pwrfip->nb_irq_vecs; i++)
            free_irq(pdev->irq+i, (void *) pwrfip);
    }

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0)
    pci_free_irq_vectors(pdev);
#else
    pci_disable_msi(pdev);
#endif

    /* iounmap safe for NULL */
    for (i = 0; i < pwrfip->infos.bar_cnt; ++i)
        iounmap(pwrfip->remap[i]);

    spin_lock_init(&to_lock);

    spin_lock(&to_lock);
    minors[pwrfip->minor] = 0;
    spin_unlock(&to_lock);

    pci_disable_device(pdev);
    pci_release_regions(pdev);

    pwrfip_unregister(pwrfip);

    spin_lock(&to_lock);
    list_del(&pwrfip->link);
    spin_unlock(&to_lock);

    kfree(pwrfip->dma_buffer);
    kfree(pwrfip);
}

int pwrfip_open(struct inode *inode, struct file *file)
{
    int minor = MINOR(inode->i_rdev);
    struct list_head *cur;
    struct pwrfip_dev *pwrfip;

    list_for_each(cur, &pwrfip_list) {

        pwrfip = list_entry(cur, struct pwrfip_dev, link);
        if (pwrfip->minor == minor) {

            if (pwrfip->is_open) {
                printk(KERN_WARNING DRV_NAME
                    ": Device is already open (index %d)\n",
                    pwrfip->minor);
                return -EBUSY;
            }
            file->private_data = pwrfip;
            pwrfip->is_open = 1;
            return 0;
        }
    }

    return -ENODEV;
}

int pwrfip_release(struct inode *inode, struct file *file)
{
    struct pwrfip_dev *pwrfip;

    if (file == NULL)
        return 0;

    if (file->private_data == NULL)
        return 0;

    pwrfip = file->private_data;
    pwrfip->is_open = 0;
    file->private_data = 0;

    return 0;
}

static int wait_for_reg32(uint32_t *reg, uint32_t flags, uint32_t timeout_us,
        uint8_t trigger_level)
{
    ktime_t end = ktime_add_us(ktime_get(), timeout_us);
    uint32_t cmp_flags = (trigger_level) ? flags : 0;
    /* polling method */
    for (;;)
    {
        if ((ioread32(reg) & flags) == cmp_flags)
            break;

        if (ktime_after(ktime_get() /* now */, end))
            return -EBUSY;
    }
    return 0;
}

static int pwrfip_mbox_status_wait(struct pwrfip_mbox *mbx, uint32_t flags,
                uint32_t tmo_ms, uint32_t trigger_level,
                int wait_it /* 0: polling, 1: interrupt */)
{
    int err = 0;
    if (wait_it) {
        /* interrupt method */
        if (!wait_event_interruptible_timeout(mbx->it_mutex, mbx->it_flag,
                    msecs_to_jiffies(tmo_ms)))
            return -EBUSY;

        spin_lock(&mbx->it_lock);
        mbx->it_flag = 0;
        spin_unlock(&mbx->it_lock);
    } else {
        /* polling method */
        err = wait_for_reg32(mbx->status, flags, tmo_ms * 1000, trigger_level);
    }
    return err;
}

static int dma_xfer_wait_for_end(struct pwrfip_dev *pwrfip, uint32_t timeout_us)
{
    int err;
    void *dma_base = pwrfip->remap[BAR_BOARD] + DMA_AREA_PCI_OFFSET;
    uint32_t reg_val;

    /* polling mode */
    err = wait_for_reg32(dma_base + DMA_STA_REG(0), DMA_STA_COMPL_OK,
            timeout_us, 1);
    if (!err)
        return 0;

    /* dma error trace */
    reg_val = ioread32(dma_base + DMA_STA_REG(0));
    printk(KERN_ERR DRV_NAME
        ": dma_xfer_wait_for_end => dma_status_reg=0x%08x (minor=%d)\n",
        reg_val, pwrfip->minor);
    return err;
}

static int dma_xfer(struct pwrfip_dev *pwrfip, int direction, void *host_buffer,
        uint32_t target_addr, size_t bsz)
{
    int err = 0;
    void *dma_base = pwrfip->remap[BAR_BOARD] + DMA_AREA_PCI_OFFSET;
    uint32_t data;
    dma_addr_t bus_addr;

    if (!host_buffer)
        return -EACCES;

    if (bsz > DMA_BUFFER_BSZ)
        return -EACCES;

    if (pwrfip->dma_xfer_in_progress)
        return -EBUSY;

    /* map the buffer for dma */
    pwrfip->dma_dir = ((direction == DMA_OP_W) ? \
                            DMA_TO_DEVICE : DMA_FROM_DEVICE);
    pwrfip->dma_bsz = bsz;
    bus_addr = dma_map_single(&pwrfip->pdev->dev, pwrfip->dma_buffer,
                pwrfip->dma_bsz, pwrfip->dma_dir);
    if (dma_mapping_error(&pwrfip->pdev->dev, bus_addr))
        return -EACCES;

    pwrfip->dma_addr = bus_addr;

    /* set up the device */
    /* => set source parameter */
    data = (direction == DMA_OP_R) ? DMA_SRC_PARAM_OP_R : 0;
    iowrite32(data, dma_base + DMA_SRC_PARAM_REG(0));
    /* => set destination parameter */
    iowrite32(0, dma_base + DMA_DST_PARAM_REG(0));
    /* => set source address */
    data = (uint32_t) ((direction == DMA_OP_W) ? bus_addr : target_addr);
    iowrite32(data, dma_base + DMA_SRC_ADDR_LO_REG(0));
    iowrite32(0, dma_base + DMA_SRC_ADDR_HI_REG(0));
    /* => set destination address */
    data = (uint32_t) ((direction == DMA_OP_W) ? target_addr : bus_addr);
    iowrite32(data, dma_base + DMA_DST_ADDR_LO_REG(0));
    iowrite32(0, dma_base + DMA_DST_ADDR_HI_REG(0));
    /* => set length */
    iowrite32(pwrfip->dma_bsz, dma_base + DMA_LEN_REG(0));

    /* prepare dma buffer (for write)*/
    if (direction == DMA_OP_W)
        memcpy(pwrfip->dma_buffer, host_buffer, pwrfip->dma_bsz);

    /* start the operation */
    pwrfip->dma_xfer_in_progress = true;
    data = DMA_CTRL_OP_STRT_ABRT;
    iowrite32(data, dma_base + DMA_CTRL_REG(0));

    err = dma_xfer_wait_for_end(pwrfip, DMA_TMO_US);
    pwrfip->dma_xfer_in_progress = false;

    /* retrieve dma buffer (for read) */
    if ((!err) && (direction == DMA_OP_R))
        memcpy(host_buffer, pwrfip->dma_buffer, pwrfip->dma_bsz);

    dma_unmap_single(&pwrfip->pdev->dev, bus_addr, pwrfip->dma_bsz,
        pwrfip->dma_dir);

    return err;
}

static int pwrfip_info_get(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    return (copy_to_user(uarg, &pwrfip->infos, sizeof(struct pwrfip_dev_infos))) ? \
            -EFAULT : 0;
}

static int pwrfip_sensors_get(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct pwrfip_dev_sensors snsrs;
    uint32_t *reg_addr = (uint32_t *) (pwrfip->remap[BAR_BOARD] + SNSR_REG);
    memset(&snsrs, 0, sizeof(snsrs));
    snsrs.value[0] = ioread32(reg_addr);
    return (copy_to_user(uarg, &snsrs, sizeof(struct pwrfip_dev_sensors))) ? \
            -EFAULT : 0;
}

static int pwrfip_firmware_load(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct arg_io io;
    uint32_t *gctl_addr = \
        (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + PWRFIP_GCTL_REG);
    struct pwrfip_dev_rw_data rw_data;
    uint32_t i, word, *fw_base_ptr, *fw_data_ptr;
    uint32_t gctl_val, written_words = 0;

    if (copy_from_user(&io, uarg, sizeof(io)))
        return -EFAULT;

    if (!io.i_ptr || !io.o_ptr)
        return -EINVAL;

    if ((io.i_bsz < PWRFIP_DEV_RW_DATA_BSZ) ||
        (io.o_bsz < sizeof(uint32_t)))
        return -EINVAL;

    if (copy_from_user(&rw_data, (void *) (uintptr_t) io.i_ptr, PWRFIP_DEV_RW_DATA_BSZ))
        return -EFAULT;

    if (!rw_data.data_ptr ||
        (rw_data.data_wsz > rw_data.data_max_wsz) ||
        (rw_data.bar_num >= pwrfip->infos.bar_cnt))
        return -EINVAL;

    /* BAR overflow */
    if ((rw_data.bar_off + (4 * rw_data.data_wsz)) >
        pwrfip->infos.bar_bsz[rw_data.bar_num])
        return -EINVAL;

    fw_base_ptr = (uint32_t *) (((uint8_t *) pwrfip->remap[rw_data.bar_num]) + \
                                            rw_data.bar_off);
    fw_data_ptr = (uint32_t *) (uintptr_t) rw_data.data_ptr;

    /* keep cpu reset during loading */
    /* cpu reset: on */
    gctl_val = ioread32(gctl_addr);
    gctl_val &= CPU_RST;
    iowrite32(gctl_val, gctl_addr);

    /* first we clear all firmware area */
    for (i = 0; i < rw_data.data_max_wsz; ++i)
        iowrite32(0, (fw_base_ptr + i));

    /* firmware loading */
    for (i = 0; i < rw_data.data_wsz; ++i) {
        if (copy_from_user(&word, fw_data_ptr + i, sizeof(word)))
            return -EFAULT;
        iowrite32(word, (fw_base_ptr + i));
        written_words++;
    }

    /* cpu reset: off */
    gctl_val = ioread32(gctl_addr);
    gctl_val |= ~CPU_RST;
    iowrite32(gctl_val, gctl_addr);

    /* enable irq + reset counter */
    memset(&pwrfip->irq, 0, sizeof(pwrfip->irq));
    pwrfip_irq_global_enable(pwrfip, 1);

    /* copy rep to user space */
    if (copy_to_user((void *) (uintptr_t) io.o_ptr, &written_words, sizeof(uint32_t)))
        return -EFAULT;

    /* update arg_io */
    io.status = sizeof(uint32_t);
    return (copy_to_user(uarg, &io, sizeof(io))) ? -EFAULT : 0;
}

static int pwrfip_mbox_init(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct arg_io io;
    struct pwrfip_dev_mbox_info mbx_info;
    uint32_t mbox_status = 0;

    if (copy_from_user(&io, uarg, sizeof(io)))
        return -EFAULT;

    if (!io.i_ptr || !io.o_ptr)
        return -EINVAL;

    if ((io.i_bsz != PWRFIP_DEV_MBOX_INFO_BSZ) ||
        (io.o_bsz != sizeof(uint32_t)))
        return -EINVAL;

    if (copy_from_user(&mbx_info, (void *) (uintptr_t) io.i_ptr,
            PWRFIP_DEV_MBOX_INFO_BSZ))
        return -EFAULT;

    if (mbx_info.num >= PWRFIP_MBOX_COUNT)
        return -EINVAL;

    /* init mbox config register */
    iowrite32(mbx_info.config_init, pwrfip->mbox[mbx_info.num].config);
    pwrfip->mbox[mbx_info.num].has_it_cap = \
        (mbx_info.config_init & MBOX_HOST_IT_ENA) ? 1 : 0;
    pwrfip->mbox[mbx_info.num].type = mbx_info.type;

    /* clear mbox status for outbox */
    if (mbx_info.type == MBOX_OUT)
        iowrite32(0, pwrfip->mbox[mbx_info.num].status);

    /* read mbox status */
    mbox_status = ioread32(pwrfip->mbox[mbx_info.num].status);

    /* copy rep to user space */
    if (copy_to_user((void *) (uintptr_t) io.o_ptr, &mbox_status, sizeof(uint32_t)))
        return -EFAULT;

    /* update arg_io */
    io.status = sizeof(uint32_t);
    return (copy_to_user(uarg, &io, sizeof(io))) ? -EFAULT : 0;
}

static int pwrfip_mbox_exit(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct arg_io io;
    struct pwrfip_dev_mbox_info mbx_info;
    uint32_t mbox_status = 0;

    if (copy_from_user(&io, uarg, sizeof(io)))
        return -EFAULT;

    if (!io.i_ptr || !io.o_ptr)
        return -EINVAL;

    if ((io.i_bsz != PWRFIP_DEV_MBOX_INFO_BSZ) ||
        (io.o_bsz != sizeof(uint32_t)))
        return -EINVAL;

    if (copy_from_user(&mbx_info, (void *) (uintptr_t) io.i_ptr,
            PWRFIP_DEV_MBOX_INFO_BSZ))
        return -EFAULT;

    if (mbx_info.num >= PWRFIP_MBOX_COUNT)
        return -EINVAL;

    if ((mbx_info.type != MBOX_OUT) &&
        (mbx_info.type != MBOX_IN))
        return -EINVAL;

    if (mbx_info.type == MBOX_OUT) {
        /* cleanup outbox context */
        iowrite32(0, pwrfip->mbox[mbx_info.num].status);
    } else {
        /* cleanup inbox context */
        /* note: if an inbox packet is currently ready to be treated,
         * we cancel it (ack with err code busy)
         */
        uint32_t reg_val = (MBOX_ERR_BUSY & MBOX_STATUS_ACK_CODE_MASK);
        reg_val <<= MBOX_STATUS_ACK_CODE_SHIFT;
        reg_val |= MBOX_ACK;
        iowrite32(reg_val, pwrfip->mbox[mbx_info.num].status);
    }

    /* read mbox status */
    mbox_status = ioread32(pwrfip->mbox[mbx_info.num].status);

    /* copy rep to user space */
    if (copy_to_user((void *) (uintptr_t) io.o_ptr, &mbox_status, sizeof(uint32_t)))
        return -EFAULT;

    /* update arg_io */
    io.status = sizeof(uint32_t);
    return (copy_to_user(uarg, &io, sizeof(io))) ? -EFAULT : 0;
}

static int pwrfip_post(struct pwrfip_mbox *outbx, void *data, uint32_t bsz,
            uint32_t tmo_ms)
{
    int err = 0;
    uint32_t i, wsz = 0;

    /* check if mbox is ready to write */
    /* wait for ack & rdy bits to 0 */
    err = pwrfip_mbox_status_wait(outbx, MBOX_RDY | MBOX_ACK, tmo_ms, 0, 0);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post => wait_!ack_!rdy error (err=%d)\n", err);
        return err;
    }

    /* packet length */
    wsz = (bsz / 4) + (!!(bsz % 4));
    if ((wsz > ((MBOX_WSZ) - 3) /* PWRFIP_MBOXPKT_MAX_WSZ */) ||
        (wsz < 1 /* PWRFIP_MBOXPKT_MIN_WSZ */)) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post  => invalid input pkt wsz length (%d) (min=%d/max=%d)\n",
            wsz, 1, ((MBOX_WSZ) - 3));
        return -EINVAL;
    }

    /* write packet */
    iowrite32(wsz, outbx->data_wsz);
    for (i = 0; i < wsz; ++i)
        iowrite32(*(((uint32_t*) data) + i), outbx->data + i);

    /* set ready flag */
    iowrite32(MBOX_RDY, outbx->status);
    return 0;
}

static int pwrfip_pend(struct pwrfip_dev *pwrfip, struct pwrfip_mbox *inbx,
            void *data, uint32_t *data_bsz, uint32_t tmo_ms)
{
    int err = 0;
    uint32_t i, target_addr, bsz, wsz = 0;

    /* wait for rdy */
    err = pwrfip_mbox_status_wait(inbx, MBOX_RDY, tmo_ms, 1, inbx->has_it_cap);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_pend => wait_rdy error (err=%d)\n", err);
        return err;
    }

    /* check packet length */
    wsz = ioread32(inbx->data_wsz);
    bsz = wsz * 4;

    if ((wsz > ((MBOX_WSZ) - 3) /* PWRFIP_MBOXPKT_MAX_WSZ */) ||
        (wsz < 1 /* PWRFIP_MBOXPKT_MIN_WSZ */)) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_pend => invalid output pkt wsz length (%d) (min=%d/max=%d)\n",
            wsz, 1, ((MBOX_WSZ) - 3));
        return -EINVAL;
    }

    /* read packet */
    *data_bsz = bsz;

    if (pwrfip->has_dma_cap)
    {
        /* dma reading */
        target_addr = PWRFIP_DPM_AREA_PCI_OFFSET + MBOX_DATA_ADDR(inbx->index);
        err = dma_xfer(pwrfip, DMA_OP_R, data, target_addr, bsz);
    }
    else
    {
        /* mmio reading */
        for (i = 0; i < wsz; ++i)
            *(((uint32_t*) data) + i) = ioread32(inbx->data + i);
    }

    /* set ack flag */
    iowrite32(MBOX_ACK, inbx->status);

    return err;
}

static int pwrfip_post_n_wait_ack(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct arg_io io;
    struct pwrfip_dev_mbox_req *req = &pwrfip->req;
    uint32_t mbox_status = 0;
    int err = 0;

    if (copy_from_user(&io, uarg, sizeof(io)))
        return -EFAULT;

    if (!io.i_ptr || !io.o_ptr)
        return -EINVAL;

    if ((io.i_bsz < PWRFIP_DEV_MBOX_REQ_HDR_BSZ) ||
        (io.o_bsz < sizeof(uint32_t)))
        return -EINVAL;

    if (copy_from_user(&req->hdr,
            &(((struct pwrfip_dev_mbox_req *) (uintptr_t) io.i_ptr)->hdr),
            PWRFIP_DEV_MBOX_REQ_HDR_BSZ))
        return -EFAULT;

    if (io.i_bsz != (PWRFIP_DEV_MBOX_REQ_HDR_BSZ + req->hdr.data_bsz))
        return -EINVAL;

    if (copy_from_user(req->data,
            ((struct pwrfip_dev_mbox_req *) (uintptr_t) io.i_ptr)->data,
            req->hdr.data_bsz))
        return -EFAULT;

    /* post outbox pkt */
    err = pwrfip_post(&pwrfip->mbox[PWRFIP_MBOX_REQ], req->data,
            req->hdr.data_bsz, req->hdr.tmo_ms);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post_n_wait_ack => post error (err=%d)\n", err);
        goto end;
    }

    /* wait for outbox ack */
    err = pwrfip_mbox_status_wait(&pwrfip->mbox[PWRFIP_MBOX_REQ], MBOX_ACK,
            req->hdr.tmo_ms, 1, pwrfip->mbox[PWRFIP_MBOX_REQ].has_it_cap);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post_n_wait_ack => wait_ack error (err=%d)\n", err);
        goto end;
    }

    /* read mbox status */
    mbox_status = ioread32(pwrfip->mbox[PWRFIP_MBOX_REQ].status);
end:
    /* clear mbox status */
    iowrite32(0, pwrfip->mbox[PWRFIP_MBOX_REQ].status);

    if (!err) {
        /* copy rep to user space */
        if (copy_to_user((void *) (uintptr_t) io.o_ptr, &mbox_status, sizeof(uint32_t)))
            return -EFAULT;

        /* update arg_io */
        io.status = sizeof(uint32_t);
        if (copy_to_user(uarg, &io, sizeof(io)))
            return -EFAULT;
    }
    return err;
}

static int pwrfip_post_n_wait_res(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct arg_io io;
    struct pwrfip_dev_mbox_req *req = &pwrfip->req;
    struct pwrfip_dev_mbox_rep *rep = &pwrfip->rep;
    uint32_t rep_bsz = PWRFIP_DEV_MBOX_REP_HDR_BSZ;
    int err = 0;

    if (copy_from_user(&io, uarg, sizeof(io)))
        return -EFAULT;

    if (!io.i_ptr || !io.o_ptr)
        return -EINVAL;

    if ((io.i_bsz < PWRFIP_DEV_MBOX_REQ_HDR_BSZ) ||
        (io.o_bsz < PWRFIP_DEV_MBOX_REP_HDR_BSZ))
        return -EINVAL;

    if (copy_from_user(&req->hdr,
            &(((struct pwrfip_dev_mbox_req *) (uintptr_t) io.i_ptr)->hdr),
            PWRFIP_DEV_MBOX_REQ_HDR_BSZ))
        return -EFAULT;

    if (io.i_bsz != (PWRFIP_DEV_MBOX_REQ_HDR_BSZ + req->hdr.data_bsz))
        return -EINVAL;

    if (copy_from_user(req->data,
            ((struct pwrfip_dev_mbox_req *) (uintptr_t) io.i_ptr)->data,
            req->hdr.data_bsz))
        return -EFAULT;

    /* post outbox pkt */
    err = pwrfip_post(&pwrfip->mbox[PWRFIP_MBOX_REQ], req->data,
            req->hdr.data_bsz, req->hdr.tmo_ms);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post_n_wait_res => post error (err=%d)\n", err);
        goto end;
    }

    /* wait inbox pkt */
    err = pwrfip_pend(pwrfip, &pwrfip->mbox[PWRFIP_MBOX_REP], rep->data,
            &rep->hdr.data_bsz, req->hdr.tmo_ms);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post_n_wait_res => pend error (err=%d)\n", err);
        goto end;
    }
    rep_bsz += rep->hdr.data_bsz;

    /* wait for outbox ack */
    err = pwrfip_mbox_status_wait(&pwrfip->mbox[PWRFIP_MBOX_REQ], MBOX_ACK,
            req->hdr.tmo_ms, 1, pwrfip->mbox[PWRFIP_MBOX_REQ].has_it_cap);
    if (err) {
        printk(KERN_ERR DRV_NAME
            ": pwrfip_post_n_wait_res => wait_ack error (err=%d)\n", err);
        goto end;
    }

    /* read mbox status */
    rep->hdr.status = ioread32(pwrfip->mbox[PWRFIP_MBOX_REQ].status);

end:
    /* clear mbox status */
    iowrite32(0, pwrfip->mbox[PWRFIP_MBOX_REQ].status);

    if (!err) {
        if (io.o_bsz < rep_bsz)
            return -EINVAL;

        /* copy rep to user space */
        if (copy_to_user((void *) (uintptr_t) io.o_ptr, rep, rep_bsz))
            return -EFAULT;

        /* update arg_io */
        io.status = rep_bsz;
        if (copy_to_user(uarg, &io, sizeof(io)))
            return -EFAULT;
    }
    return err;
}

static int pwrfip_reset(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    uint32_t i, gctl_val, *gctl_addr, *sram_base_addr;

    gctl_addr = (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + PWRFIP_GCTL_REG);
    sram_base_addr = \
        (uint32_t*) (pwrfip->remap[BAR_PWRFIP] + PWRFIP_SRAM_AREA_PCI_OFFSET);

    /* clean all sram memory */
    for (i = 0; i < PWRFIP_SRAM_WSZ; ++i)
        iowrite32(0, sram_base_addr + i);

    /* reset cpu + logic */
    gctl_val = ioread32(gctl_addr);
    gctl_val &= ~ALL_RST;
    iowrite32(gctl_val, gctl_addr);

    /* disable irq */
    pwrfip_irq_global_enable(pwrfip, 0);
    return 0;
}

static int pwrfip_irq_wait_for(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    struct pwrfip_dev_irq irq;
    /* enable irq */
    pwrfip_irq_mbox_enable(&(pwrfip->mbox[PWRFIP_MBOX_EVT]), 1);

    if (!wait_event_interruptible_timeout( \
            pwrfip->mbox[PWRFIP_MBOX_EVT].it_mutex,
            pwrfip->mbox[PWRFIP_MBOX_EVT].it_flag,
            msecs_to_jiffies(IRQ_TIMEOUT_MS)))
    {
        printk(KERN_WARNING DRV_NAME
            ": the interruption is too long to be triggered\n");
        return -ETIME;
    }
    spin_lock(&pwrfip->mbox[PWRFIP_MBOX_EVT].it_lock);
    pwrfip->mbox[PWRFIP_MBOX_EVT].it_flag = 0;
    memcpy(&irq, &pwrfip->irq, sizeof(irq));
    spin_unlock(&pwrfip->mbox[PWRFIP_MBOX_EVT].it_lock);
    return (copy_to_user(uarg, &irq, sizeof(irq))) ? -EFAULT : 0;
}

static int pwrfip_irq_resume(struct pwrfip_dev *pwrfip, void __user *uarg)
{
    /**
     * Wake up 'manually' the mutex
     * Note: Useful to unlock a wait on an event that won't come
     */
    spin_lock(&pwrfip->mbox[PWRFIP_MBOX_EVT].it_lock);
    /* disable irq */
    pwrfip_irq_mbox_enable(&pwrfip->mbox[PWRFIP_MBOX_EVT], 0);
    pwrfip->mbox[PWRFIP_MBOX_EVT].it_flag = 1;
    spin_unlock(&pwrfip->mbox[PWRFIP_MBOX_EVT].it_lock);
    wake_up(&pwrfip->mbox[PWRFIP_MBOX_EVT].it_mutex);
    return 0;
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
    int pwrfip_ioctl(struct inode *inode, struct file *file,
        unsigned int cmd, unsigned long arg)
#else
    long pwrfip_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
#endif
{
    struct pwrfip_dev *pwrfip = file->private_data;
    void __user *uarg = (void __user *) arg;
    int err = 0;

    /* check command */
    if (_IOC_TYPE(cmd) != PWRFIP_IOCTL_MAGIC)
        return -ENOTTY;

    if (_IOC_DIR(cmd) & _IOC_READ) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0))
        err = !access_ok(uarg, _IOC_SIZE(cmd));
#else
        err = !access_ok(VERIFY_WRITE, uarg, _IOC_SIZE(cmd));
#endif
    }

    if (_IOC_DIR(cmd) & _IOC_WRITE) {
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0))
        err = !access_ok(uarg, _IOC_SIZE(cmd));
#else
        err = !access_ok(VERIFY_READ, uarg, _IOC_SIZE(cmd));
#endif
    }
    if (err)
        return -EFAULT;

    switch (cmd) {
    case PWRFIP_IOCTL_INFO_GET:
        err = pwrfip_info_get(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_SNSRS_GET:
        err = pwrfip_sensors_get(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_FW_LOAD:
        err = pwrfip_firmware_load(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_MBOX_INIT:
        err = pwrfip_mbox_init(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_MBOX_EXIT:
        err = pwrfip_mbox_exit(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_POST_N_WAIT_ACK:
        err = pwrfip_post_n_wait_ack(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_POST_N_WAIT_RES:
        err = pwrfip_post_n_wait_res(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_RESET:
        err = pwrfip_reset(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_IRQ_WAIT_FOR:
        err = pwrfip_irq_wait_for(pwrfip, uarg);
        break;
    case PWRFIP_IOCTL_IRQ_RESUME:
        err = pwrfip_irq_resume(pwrfip, uarg);
        break;
    default:
        printk(KERN_ERR DRV_NAME ": Unsupported IOCTL command (0x%x)\n", cmd);
        return -EINVAL;
    }
    return err;
}

struct file_operations pwrfip_fops = {
    .owner = THIS_MODULE,
    .open  = pwrfip_open,
    .release = pwrfip_release,
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35))
    .ioctl = pwrfip_ioctl,
#else
    .unlocked_ioctl = pwrfip_ioctl,
    .compat_ioctl = pwrfip_ioctl,
#endif
};

/* Microsemi */
#define ACTEL_VID 0x11aa
#define ACTEL_DID 0x1556

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,8,0)
static const struct pci_device_id pwrfip_idtable[] = {
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCI_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCIE_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCI104_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCIE104_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PXI_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PXIE_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_MPCIE_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_CPCI_COP) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCI_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCIE_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCI104_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PCIE104_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PXI_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_PXIE_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_MPCIE_OPT) },
    { PCI_DEVICE_SUB(ACTEL_VID, ACTEL_DID, PCI_ANY_ID, PWRFIP_SSDID_CPCI_OPT) },
    { 0, },
};
#else
static const struct pci_device_id pwrfip_idtable[] = {
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCI_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCIE_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCI104_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCIE104_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PXI_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PXIE_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_MPCIE_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_CPCI_COP, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCI_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCIE_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCI104_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PCIE104_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PXI_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_PXIE_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_MPCIE_OPT, },
    { .vendor = ACTEL_VID, .device = ACTEL_DID, .subvendor = PCI_ANY_ID, .subdevice = PWRFIP_SSDID_CPCI_OPT, },
    { 0, },
};
#endif
MODULE_DEVICE_TABLE(pci, pwrfip_idtable);

static struct pci_driver pwrfip_pcidrv = {
    .name = DRV_NAME,
    .id_table = pwrfip_idtable,
    .probe = pwrfip_probe,
    .remove = pwrfip_remove,
};

static int __init pwrfip_init(void)
{
    int err = 0;

    printk(KERN_DEBUG DRV_NAME ": ==> Init\n");

    /* create device class - ls /sys/class */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(6,4,0))
    pwrfip_class = class_create(THIS_MODULE, DRV_NAME);
#else
    pwrfip_class = class_create(DRV_NAME);
#endif
    if (IS_ERR(pwrfip_class)) {
        err = PTR_ERR(pwrfip_class);
        goto fail;
    }

    /* allocate device numbers - cat /proc/devices */
    if (alloc_chrdev_region(&pwrfip_dev_first, 0, max_devices, DRV_NAME) < 0) {
        err = -EIO;
        goto fail_class;
    }

    /* get major */
    pci_major = MAJOR(pwrfip_dev_first);

    /* register driver */
    return pci_register_driver(&pwrfip_pcidrv);

fail_class:
    class_destroy(pwrfip_class);
fail:
    return err;
}

static void __exit pwrfip_exit(void)
{
    printk(KERN_DEBUG DRV_NAME ": ==> Exit\n");
    /* unregister driver */
    pci_unregister_driver(&pwrfip_pcidrv);
    /* destroy device class */
    class_destroy(pwrfip_class);
    /* free devices numbers */
    unregister_chrdev_region(pwrfip_dev_first, max_devices);
}

module_init(pwrfip_init);
module_exit(pwrfip_exit);

MODULE_AUTHOR("Exoligent");
MODULE_DESCRIPTION("FIP/WorldFIP Linux Driver [PowerFIP]");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(POWERFIP_DRV_VERSION);
