root/drivers/misc/mrvl_cn10k_dpi.c
// SPDX-License-Identifier: GPL-2.0
/* Marvell Octeon CN10K DPI driver
 *
 * Copyright (C) 2024 Marvell.
 *
 */

#include <linux/bitfield.h>
#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/irq.h>
#include <linux/interrupt.h>

#include <uapi/misc/mrvl_cn10k_dpi.h>

/* PCI device IDs */
#define PCI_DEVID_MRVL_CN10K_DPI_PF     0xA080
#define PCI_SUBDEVID_MRVL_CN10K_DPI_PF  0xB900

/* PCI BAR Number */
#define PCI_DPI_CFG_BAR 0

/* MSI-X interrupts */
#define DPI_MAX_REQQ_INT        0x20
#define DPI_MAX_CC_INT          0x40

/* MBOX MSI-X interrupt vector index */
#define DPI_MBOX_PF_VF_INT_IDX  0x75

#define DPI_MAX_IRQS (DPI_MBOX_PF_VF_INT_IDX + 1)

#define DPI_MAX_VFS     0x20

#define DPI_MAX_ENG_FIFO_SZ     0x20
#define DPI_MAX_ENG_MOLR        0x400

#define DPI_DMA_IDS_DMA_NPA_PF_FUNC(x)  FIELD_PREP(GENMASK_ULL(31, 16), x)
#define DPI_DMA_IDS_INST_STRM(x)        FIELD_PREP(GENMASK_ULL(47, 40), x)
#define DPI_DMA_IDS_DMA_STRM(x)         FIELD_PREP(GENMASK_ULL(39, 32), x)
#define DPI_DMA_ENG_EN_MOLR(x)          FIELD_PREP(GENMASK_ULL(41, 32), x)
#define DPI_EBUS_PORTX_CFG_MPS(x)       FIELD_PREP(GENMASK(6, 4), x)
#define DPI_DMA_IDS_DMA_SSO_PF_FUNC(x)  FIELD_PREP(GENMASK(15, 0), x)
#define DPI_DMA_IDS2_INST_AURA(x)       FIELD_PREP(GENMASK(19, 0), x)
#define DPI_DMA_IBUFF_CSIZE_CSIZE(x)    FIELD_PREP(GENMASK(13, 0), x)
#define DPI_EBUS_PORTX_CFG_MRRS(x)      FIELD_PREP(GENMASK(2, 0), x)
#define DPI_ENG_BUF_BLKS(x)             FIELD_PREP(GENMASK(5, 0), x)
#define DPI_DMA_CONTROL_DMA_ENB         GENMASK_ULL(53, 48)

#define DPI_DMA_CONTROL_O_MODE          BIT_ULL(14)
#define DPI_DMA_CONTROL_LDWB            BIT_ULL(32)
#define DPI_DMA_CONTROL_WQECSMODE1      BIT_ULL(37)
#define DPI_DMA_CONTROL_ZBWCSEN         BIT_ULL(39)
#define DPI_DMA_CONTROL_WQECSOFF(ofst)  (((u64)ofst) << 40)
#define DPI_DMA_CONTROL_WQECSDIS        BIT_ULL(47)
#define DPI_DMA_CONTROL_PKT_EN          BIT_ULL(56)
#define DPI_DMA_IBUFF_CSIZE_NPA_FREE    BIT(16)

#define DPI_CTL_EN                      BIT_ULL(0)
#define DPI_DMA_CC_INT                  BIT_ULL(0)
#define DPI_DMA_QRST                    BIT_ULL(0)

#define DPI_REQQ_INT_INSTRFLT           BIT_ULL(0)
#define DPI_REQQ_INT_RDFLT              BIT_ULL(1)
#define DPI_REQQ_INT_WRFLT              BIT_ULL(2)
#define DPI_REQQ_INT_CSFLT              BIT_ULL(3)
#define DPI_REQQ_INT_INST_DBO           BIT_ULL(4)
#define DPI_REQQ_INT_INST_ADDR_NULL     BIT_ULL(5)
#define DPI_REQQ_INT_INST_FILL_INVAL    BIT_ULL(6)
#define DPI_REQQ_INT_INSTR_PSN          BIT_ULL(7)

#define DPI_REQQ_INT \
        (DPI_REQQ_INT_INSTRFLT          | \
        DPI_REQQ_INT_RDFLT              | \
        DPI_REQQ_INT_WRFLT              | \
        DPI_REQQ_INT_CSFLT              | \
        DPI_REQQ_INT_INST_DBO           | \
        DPI_REQQ_INT_INST_ADDR_NULL     | \
        DPI_REQQ_INT_INST_FILL_INVAL    | \
        DPI_REQQ_INT_INSTR_PSN)

#define DPI_PF_RAS_EBI_DAT_PSN  BIT_ULL(0)
#define DPI_PF_RAS_NCB_DAT_PSN  BIT_ULL(1)
#define DPI_PF_RAS_NCB_CMD_PSN  BIT_ULL(2)

#define DPI_PF_RAS_INT \
        (DPI_PF_RAS_EBI_DAT_PSN  | \
         DPI_PF_RAS_NCB_DAT_PSN  | \
         DPI_PF_RAS_NCB_CMD_PSN)

/* Message fields in word_l of DPI mailbox structure */
#define DPI_MBOX_VFID(msg)              FIELD_GET(GENMASK_ULL(7, 0), msg)
#define DPI_MBOX_CMD(msg)               FIELD_GET(GENMASK_ULL(11, 8), msg)
#define DPI_MBOX_CBUF_SIZE(msg)         FIELD_GET(GENMASK_ULL(27, 12), msg)
#define DPI_MBOX_CBUF_AURA(msg)         FIELD_GET(GENMASK_ULL(47, 28), msg)
#define DPI_MBOX_SSO_PFFUNC(msg)        FIELD_GET(GENMASK_ULL(63, 48), msg)

/* Message fields in word_h of DPI mailbox structure */
#define DPI_MBOX_NPA_PFFUNC(msg)        FIELD_GET(GENMASK_ULL(15, 0), msg)
#define DPI_MBOX_WQES_COMPL(msg)        FIELD_GET(GENMASK_ULL(16, 16), msg)
#define DPI_MBOX_WQES_OFFSET(msg)       FIELD_GET(GENMASK_ULL(23, 17), msg)

#define DPI_DMAX_IBUFF_CSIZE(x) (0x0ULL | ((x) << 11))
#define DPI_DMAX_IDS(x)         (0x18ULL | ((x) << 11))
#define DPI_DMAX_IDS2(x)        (0x20ULL | ((x) << 11))
#define DPI_DMAX_QRST(x)        (0x30ULL | ((x) << 11))

#define DPI_CTL                         0x10010ULL
#define DPI_DMA_CONTROL                 0x10018ULL
#define DPI_PF_RAS                      0x10308ULL
#define DPI_PF_RAS_ENA_W1C              0x10318ULL
#define DPI_MBOX_VF_PF_INT              0x16300ULL
#define DPI_MBOX_VF_PF_INT_W1S          0x16308ULL
#define DPI_MBOX_VF_PF_INT_ENA_W1C      0x16310ULL
#define DPI_MBOX_VF_PF_INT_ENA_W1S      0x16318ULL

#define DPI_DMA_ENGX_EN(x)              (0x10040ULL | ((x) << 3))
#define DPI_ENGX_BUF(x)                 (0x100C0ULL | ((x) << 3))
#define DPI_EBUS_PORTX_CFG(x)           (0x10100ULL | ((x) << 3))
#define DPI_DMA_CCX_INT(x)              (0x11000ULL | ((x) << 3))
#define DPI_DMA_CCX_INT_ENA_W1C(x)      (0x11800ULL | ((x) << 3))
#define DPI_REQQX_INT(x)                (0x12C00ULL | ((x) << 5))
#define DPI_REQQX_INT_ENA_W1C(x)        (0x13800ULL | ((x) << 5))
#define DPI_MBOX_PF_VF_DATA0(x)         (0x16000ULL | ((x) << 4))
#define DPI_MBOX_PF_VF_DATA1(x)         (0x16008ULL | ((x) << 4))

#define DPI_WCTL_FIF_THR        0x17008ULL

#define DPI_EBUS_MAX_PORTS      2

#define DPI_EBUS_MRRS_MIN       128
#define DPI_EBUS_MRRS_MAX       1024
#define DPI_EBUS_MPS_MIN        128
#define DPI_EBUS_MPS_MAX        1024
#define DPI_WCTL_FIFO_THRESHOLD 0x30

#define DPI_QUEUE_OPEN          0x1
#define DPI_QUEUE_CLOSE         0x2
#define DPI_REG_DUMP            0x3
#define DPI_GET_REG_CFG         0x4
#define DPI_QUEUE_OPEN_V2       0x5

enum dpi_mbox_rsp_type {
        DPI_MBOX_TYPE_CMD,
        DPI_MBOX_TYPE_RSP_ACK,
        DPI_MBOX_TYPE_RSP_NACK,
};

struct dpivf_config {
        u32 aura;
        u16 csize;
        u16 sso_pf_func;
        u16 npa_pf_func;
};

struct dpipf_vf {
        struct dpivf_config vf_config;
        bool setup_done;
        u8 this_vfid;
};

/* DPI device mailbox */
struct dpi_mbox {
        struct work_struct work;
        /* lock to serialize mbox requests */
        struct mutex lock;
        struct dpipf *pf;
        u8 __iomem *pf_vf_data_reg;
        u8 __iomem *vf_pf_data_reg;
};

struct dpipf {
        struct miscdevice miscdev;
        void __iomem *reg_base;
        struct pci_dev *pdev;
        struct dpipf_vf vf[DPI_MAX_VFS];
        /* Mailbox to talk to VFs */
        struct dpi_mbox *mbox[DPI_MAX_VFS];
};

struct dpi_mbox_message {
        uint64_t word_l;
        uint64_t word_h;
};

static inline void dpi_reg_write(struct dpipf *dpi, u64 offset, u64 val)
{
        writeq(val, dpi->reg_base + offset);
}

static inline u64 dpi_reg_read(struct dpipf *dpi, u64 offset)
{
        return readq(dpi->reg_base + offset);
}

static void dpi_wqe_cs_offset(struct dpipf *dpi, u8 offset)
{
        u64 reg;

        reg = dpi_reg_read(dpi, DPI_DMA_CONTROL);
        reg &= ~DPI_DMA_CONTROL_WQECSDIS;
        reg |= DPI_DMA_CONTROL_ZBWCSEN | DPI_DMA_CONTROL_WQECSMODE1;
        reg |= DPI_DMA_CONTROL_WQECSOFF(offset);
        dpi_reg_write(dpi, DPI_DMA_CONTROL, reg);
}

static int dpi_queue_init(struct dpipf *dpi, struct dpipf_vf *dpivf, u8 vf)
{
        u16 sso_pf_func = dpivf->vf_config.sso_pf_func;
        u16 npa_pf_func = dpivf->vf_config.npa_pf_func;
        u16 csize = dpivf->vf_config.csize;
        u32 aura = dpivf->vf_config.aura;
        unsigned long timeout;
        u64 reg;

        dpi_reg_write(dpi, DPI_DMAX_QRST(vf), DPI_DMA_QRST);

        /* Wait for a maximum of 3 sec */
        timeout = jiffies + msecs_to_jiffies(3000);
        while (!time_after(jiffies, timeout)) {
                reg = dpi_reg_read(dpi, DPI_DMAX_QRST(vf));
                if (!(reg & DPI_DMA_QRST))
                        break;

                /* Reset would take time for the request cache to drain */
                usleep_range(500, 1000);
        }

        if (reg & DPI_DMA_QRST) {
                dev_err(&dpi->pdev->dev, "Queue reset failed\n");
                return -EBUSY;
        }

        dpi_reg_write(dpi, DPI_DMAX_IDS2(vf), 0);
        dpi_reg_write(dpi, DPI_DMAX_IDS(vf), 0);

        reg = DPI_DMA_IBUFF_CSIZE_CSIZE(csize) | DPI_DMA_IBUFF_CSIZE_NPA_FREE;
        dpi_reg_write(dpi, DPI_DMAX_IBUFF_CSIZE(vf), reg);

        reg = dpi_reg_read(dpi, DPI_DMAX_IDS2(vf));
        reg |= DPI_DMA_IDS2_INST_AURA(aura);
        dpi_reg_write(dpi, DPI_DMAX_IDS2(vf), reg);

        reg = dpi_reg_read(dpi, DPI_DMAX_IDS(vf));
        reg |= DPI_DMA_IDS_DMA_NPA_PF_FUNC(npa_pf_func);
        reg |= DPI_DMA_IDS_DMA_SSO_PF_FUNC(sso_pf_func);
        reg |= DPI_DMA_IDS_DMA_STRM(vf + 1);
        reg |= DPI_DMA_IDS_INST_STRM(vf + 1);
        dpi_reg_write(dpi, DPI_DMAX_IDS(vf), reg);

        return 0;
}

static void dpi_queue_fini(struct dpipf *dpi, u8 vf)
{
        dpi_reg_write(dpi, DPI_DMAX_QRST(vf), DPI_DMA_QRST);

        /* Reset IDS and IDS2 registers */
        dpi_reg_write(dpi, DPI_DMAX_IDS2(vf), 0);
        dpi_reg_write(dpi, DPI_DMAX_IDS(vf), 0);
}

static irqreturn_t dpi_mbox_intr_handler(int irq, void *data)
{
        struct dpipf *dpi = data;
        u64 reg;
        u32 vf;

        reg = dpi_reg_read(dpi, DPI_MBOX_VF_PF_INT);
        if (reg) {
                for (vf = 0; vf < pci_num_vf(dpi->pdev); vf++) {
                        if (reg & BIT_ULL(vf))
                                schedule_work(&dpi->mbox[vf]->work);
                }
                dpi_reg_write(dpi, DPI_MBOX_VF_PF_INT, reg);
        }

        return IRQ_HANDLED;
}

static int queue_config(struct dpipf *dpi, struct dpipf_vf *dpivf, struct dpi_mbox_message *msg)
{
        int ret = 0;

        switch (DPI_MBOX_CMD(msg->word_l)) {
        case DPI_QUEUE_OPEN:
        case DPI_QUEUE_OPEN_V2:
                dpivf->vf_config.aura = DPI_MBOX_CBUF_AURA(msg->word_l);
                dpivf->vf_config.csize = DPI_MBOX_CMD(msg->word_l) == DPI_QUEUE_OPEN ?
                                         DPI_MBOX_CBUF_SIZE(msg->word_l) >> 3 :
                                         DPI_MBOX_CBUF_SIZE(msg->word_l);
                dpivf->vf_config.sso_pf_func = DPI_MBOX_SSO_PFFUNC(msg->word_l);
                dpivf->vf_config.npa_pf_func = DPI_MBOX_NPA_PFFUNC(msg->word_h);
                ret = dpi_queue_init(dpi, dpivf, DPI_MBOX_VFID(msg->word_l));
                if (!ret) {
                        if (DPI_MBOX_WQES_COMPL(msg->word_h))
                                dpi_wqe_cs_offset(dpi, DPI_MBOX_WQES_OFFSET(msg->word_h));
                        dpivf->setup_done = true;
                }
                break;
        case DPI_QUEUE_CLOSE:
                memset(&dpivf->vf_config, 0, sizeof(struct dpivf_config));
                dpi_queue_fini(dpi, DPI_MBOX_VFID(msg->word_l));
                dpivf->setup_done = false;
                break;
        default:
                return -EINVAL;
        }

        return ret;
}

static void dpi_pfvf_mbox_work(struct work_struct *work)
{
        struct dpi_mbox *mbox = container_of(work, struct dpi_mbox, work);
        struct dpi_mbox_message msg;
        struct dpipf_vf *dpivf;
        struct dpipf *dpi;
        int vfid, ret;

        dpi = mbox->pf;
        memset(&msg, 0, sizeof(msg));

        mutex_lock(&mbox->lock);
        msg.word_l = readq(mbox->vf_pf_data_reg);
        if (msg.word_l == (u64)-1)
                goto exit;

        vfid = DPI_MBOX_VFID(msg.word_l);
        if (vfid >= pci_num_vf(dpi->pdev))
                goto exit;

        dpivf = &dpi->vf[vfid];
        msg.word_h = readq(mbox->pf_vf_data_reg);

        ret = queue_config(dpi, dpivf, &msg);
        if (ret < 0)
                writeq(DPI_MBOX_TYPE_RSP_NACK, mbox->pf_vf_data_reg);
        else
                writeq(DPI_MBOX_TYPE_RSP_ACK, mbox->pf_vf_data_reg);
exit:
        mutex_unlock(&mbox->lock);
}

/* Setup registers for a PF mailbox */
static void dpi_setup_mbox_regs(struct dpipf *dpi, int vf)
{
        struct dpi_mbox *mbox = dpi->mbox[vf];

        mbox->pf_vf_data_reg = dpi->reg_base + DPI_MBOX_PF_VF_DATA0(vf);
        mbox->vf_pf_data_reg = dpi->reg_base + DPI_MBOX_PF_VF_DATA1(vf);
}

static int dpi_pfvf_mbox_setup(struct dpipf *dpi)
{
        int vf;

        for (vf = 0; vf < DPI_MAX_VFS; vf++) {
                dpi->mbox[vf] = devm_kzalloc(&dpi->pdev->dev, sizeof(*dpi->mbox[vf]), GFP_KERNEL);

                if (!dpi->mbox[vf])
                        return -ENOMEM;

                mutex_init(&dpi->mbox[vf]->lock);
                INIT_WORK(&dpi->mbox[vf]->work, dpi_pfvf_mbox_work);
                dpi->mbox[vf]->pf = dpi;
                dpi_setup_mbox_regs(dpi, vf);
        }

        return 0;
}

static void dpi_pfvf_mbox_destroy(struct dpipf *dpi)
{
        unsigned int vf;

        for (vf = 0; vf < DPI_MAX_VFS; vf++) {
                if (work_pending(&dpi->mbox[vf]->work))
                        cancel_work_sync(&dpi->mbox[vf]->work);

                dpi->mbox[vf] = NULL;
        }
}

static void dpi_init(struct dpipf *dpi)
{
        unsigned int engine, port;
        u8 mrrs_val, mps_val;
        u64 reg;

        for (engine = 0; engine < DPI_MAX_ENGINES; engine++) {
                if (engine == 4 || engine == 5)
                        reg = DPI_ENG_BUF_BLKS(16);
                else
                        reg = DPI_ENG_BUF_BLKS(8);

                dpi_reg_write(dpi, DPI_ENGX_BUF(engine), reg);
        }

        reg = DPI_DMA_CONTROL_ZBWCSEN | DPI_DMA_CONTROL_PKT_EN | DPI_DMA_CONTROL_LDWB |
              DPI_DMA_CONTROL_O_MODE | DPI_DMA_CONTROL_DMA_ENB;

        dpi_reg_write(dpi, DPI_DMA_CONTROL, reg);
        dpi_reg_write(dpi, DPI_CTL, DPI_CTL_EN);

        mrrs_val = 2; /* 512B */
        mps_val = 1; /* 256B */

        for (port = 0; port < DPI_EBUS_MAX_PORTS; port++) {
                reg = dpi_reg_read(dpi, DPI_EBUS_PORTX_CFG(port));
                reg &= ~(DPI_EBUS_PORTX_CFG_MRRS(7) | DPI_EBUS_PORTX_CFG_MPS(7));
                reg |= DPI_EBUS_PORTX_CFG_MPS(mps_val) | DPI_EBUS_PORTX_CFG_MRRS(mrrs_val);
                dpi_reg_write(dpi, DPI_EBUS_PORTX_CFG(port), reg);
        }

        dpi_reg_write(dpi, DPI_WCTL_FIF_THR, DPI_WCTL_FIFO_THRESHOLD);
}

static void dpi_fini(struct dpipf *dpi)
{
        unsigned int engine;

        for (engine = 0; engine < DPI_MAX_ENGINES; engine++)
                dpi_reg_write(dpi, DPI_ENGX_BUF(engine), 0);

        dpi_reg_write(dpi, DPI_DMA_CONTROL, 0);
        dpi_reg_write(dpi, DPI_CTL, 0);
}

static void dpi_free_irq_vectors(void *pdev)
{
        pci_free_irq_vectors((struct pci_dev *)pdev);
}

static int dpi_irq_init(struct dpipf *dpi)
{
        struct pci_dev *pdev = dpi->pdev;
        struct device *dev = &pdev->dev;
        int i, ret;

        /* Clear all RAS interrupts */
        dpi_reg_write(dpi, DPI_PF_RAS, DPI_PF_RAS_INT);

        /* Clear all RAS interrupt enable bits */
        dpi_reg_write(dpi, DPI_PF_RAS_ENA_W1C, DPI_PF_RAS_INT);

        for (i = 0; i < DPI_MAX_REQQ_INT; i++) {
                dpi_reg_write(dpi, DPI_REQQX_INT(i), DPI_REQQ_INT);
                dpi_reg_write(dpi, DPI_REQQX_INT_ENA_W1C(i), DPI_REQQ_INT);
        }

        for (i = 0; i < DPI_MAX_CC_INT; i++) {
                dpi_reg_write(dpi, DPI_DMA_CCX_INT(i), DPI_DMA_CC_INT);
                dpi_reg_write(dpi, DPI_DMA_CCX_INT_ENA_W1C(i), DPI_DMA_CC_INT);
        }

        ret = pci_alloc_irq_vectors(pdev, DPI_MAX_IRQS, DPI_MAX_IRQS, PCI_IRQ_MSIX);
        if (ret != DPI_MAX_IRQS) {
                dev_err(dev, "DPI: Failed to alloc %d msix irqs\n", DPI_MAX_IRQS);
                return ret;
        }

        ret = devm_add_action_or_reset(dev, dpi_free_irq_vectors, pdev);
        if (ret) {
                dev_err(dev, "DPI: Failed to add irq free action\n");
                return ret;
        }

        ret = devm_request_irq(dev, pci_irq_vector(pdev, DPI_MBOX_PF_VF_INT_IDX),
                               dpi_mbox_intr_handler, 0, "dpi-mbox", dpi);
        if (ret) {
                dev_err(dev, "DPI: request_irq failed for mbox; err=%d\n", ret);
                return ret;
        }

        dpi_reg_write(dpi, DPI_MBOX_VF_PF_INT_ENA_W1S, GENMASK_ULL(31, 0));

        return 0;
}

static int dpi_mps_mrrs_config(struct dpipf *dpi, void __user *arg)
{
        struct dpi_mps_mrrs_cfg cfg;
        u8 mrrs_val, mps_val;
        u64 reg;

        if (copy_from_user(&cfg, arg, sizeof(struct dpi_mps_mrrs_cfg)))
                return -EFAULT;

        if (cfg.max_read_req_sz < DPI_EBUS_MRRS_MIN || cfg.max_read_req_sz > DPI_EBUS_MRRS_MAX ||
            !is_power_of_2(cfg.max_read_req_sz))
                return -EINVAL;

        if (cfg.max_payload_sz < DPI_EBUS_MPS_MIN || cfg.max_payload_sz > DPI_EBUS_MPS_MAX ||
            !is_power_of_2(cfg.max_payload_sz))
                return -EINVAL;

        if (cfg.port >= DPI_EBUS_MAX_PORTS)
                return -EINVAL;

         /* Make sure reserved fields are set to 0 */
        if (cfg.reserved)
                return -EINVAL;

        mrrs_val = fls(cfg.max_read_req_sz >> 8);
        mps_val = fls(cfg.max_payload_sz >> 8);

        reg = dpi_reg_read(dpi, DPI_EBUS_PORTX_CFG(cfg.port));
        reg &= ~(DPI_EBUS_PORTX_CFG_MRRS(0x7) | DPI_EBUS_PORTX_CFG_MPS(0x7));
        reg |= DPI_EBUS_PORTX_CFG_MPS(mps_val) | DPI_EBUS_PORTX_CFG_MRRS(mrrs_val);
        dpi_reg_write(dpi, DPI_EBUS_PORTX_CFG(cfg.port), reg);

        return 0;
}

static int dpi_engine_config(struct dpipf *dpi, void __user *arg)
{
        struct dpi_engine_cfg cfg;
        unsigned int engine;
        u8 *eng_buf;
        u64 reg;

        if (copy_from_user(&cfg, arg, sizeof(struct dpi_engine_cfg)))
                return -EFAULT;

         /* Make sure reserved fields are set to 0 */
        if (cfg.reserved)
                return -EINVAL;

        eng_buf = (u8 *)&cfg.fifo_mask;

        for (engine = 0; engine < DPI_MAX_ENGINES; engine++) {
                if (eng_buf[engine] > DPI_MAX_ENG_FIFO_SZ)
                        return -EINVAL;
                dpi_reg_write(dpi, DPI_ENGX_BUF(engine), eng_buf[engine]);

                if (cfg.update_molr) {
                        if (cfg.molr[engine] > DPI_MAX_ENG_MOLR)
                                return -EINVAL;
                        reg = DPI_DMA_ENG_EN_MOLR(cfg.molr[engine]);
                        dpi_reg_write(dpi, DPI_DMA_ENGX_EN(engine), reg);
                } else {
                        /* Make sure unused fields are set to 0 */
                        if (cfg.molr[engine])
                                return -EINVAL;
                }
        }

        return 0;
}

static long dpi_dev_ioctl(struct file *fptr, unsigned int cmd, unsigned long data)
{
        void __user *arg = (void __user *)data;
        struct dpipf *dpi;
        int ret;

        dpi = container_of(fptr->private_data, struct dpipf, miscdev);

        switch (cmd) {
        case DPI_MPS_MRRS_CFG:
                ret = dpi_mps_mrrs_config(dpi, arg);
                break;
        case DPI_ENGINE_CFG:
                ret = dpi_engine_config(dpi, arg);
                break;
        default:
                ret = -ENOTTY;
                break;
        }

        return ret;
}

static const struct file_operations dpi_device_fops = {
        .owner = THIS_MODULE,
        .unlocked_ioctl = dpi_dev_ioctl,
        .compat_ioctl = compat_ptr_ioctl,
};

static int dpi_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
        struct device *dev = &pdev->dev;
        struct dpipf *dpi;
        int ret;

        dpi = devm_kzalloc(dev, sizeof(*dpi), GFP_KERNEL);
        if (!dpi)
                return -ENOMEM;

        dpi->pdev = pdev;

        ret = pcim_enable_device(pdev);
        if (ret) {
                dev_err(dev, "DPI: Failed to enable PCI device\n");
                return ret;
        }

        ret = pcim_iomap_regions(pdev, BIT(0) | BIT(4), KBUILD_MODNAME);
        if (ret) {
                dev_err(dev, "DPI: Failed to request MMIO region\n");
                return ret;
        }

        dpi->reg_base = pcim_iomap_table(pdev)[PCI_DPI_CFG_BAR];

        /* Initialize global PF registers */
        dpi_init(dpi);

        /* Setup PF-VF mailbox */
        ret = dpi_pfvf_mbox_setup(dpi);
        if (ret) {
                dev_err(dev, "DPI: Failed to setup pf-vf mbox\n");
                goto err_dpi_fini;
        }

        /* Register interrupts */
        ret = dpi_irq_init(dpi);
        if (ret) {
                dev_err(dev, "DPI: Failed to initialize irq vectors\n");
                goto err_dpi_mbox_free;
        }

        pci_set_drvdata(pdev, dpi);
        dpi->miscdev.minor = MISC_DYNAMIC_MINOR;
        dpi->miscdev.name = KBUILD_MODNAME;
        dpi->miscdev.fops = &dpi_device_fops;
        dpi->miscdev.parent = dev;

        ret = misc_register(&dpi->miscdev);
        if (ret) {
                dev_err(dev, "DPI: Failed to register misc device\n");
                goto err_dpi_mbox_free;
        }

        return 0;

err_dpi_mbox_free:
        dpi_pfvf_mbox_destroy(dpi);
err_dpi_fini:
        dpi_fini(dpi);
        return ret;
}

static void dpi_remove(struct pci_dev *pdev)
{
        struct dpipf *dpi = pci_get_drvdata(pdev);

        misc_deregister(&dpi->miscdev);
        pci_sriov_configure_simple(pdev, 0);
        dpi_pfvf_mbox_destroy(dpi);
        dpi_fini(dpi);
        pci_set_drvdata(pdev, NULL);
}

static const struct pci_device_id dpi_id_table[] = {
        { PCI_DEVICE_SUB(PCI_VENDOR_ID_CAVIUM, PCI_DEVID_MRVL_CN10K_DPI_PF,
                         PCI_VENDOR_ID_CAVIUM, PCI_SUBDEVID_MRVL_CN10K_DPI_PF) },
        { 0, }  /* end of table */
};

static struct pci_driver dpi_driver = {
        .name = KBUILD_MODNAME,
        .id_table = dpi_id_table,
        .probe = dpi_probe,
        .remove = dpi_remove,
        .sriov_configure = pci_sriov_configure_simple,
};

module_pci_driver(dpi_driver);
MODULE_DEVICE_TABLE(pci, dpi_id_table);
MODULE_AUTHOR("Marvell.");
MODULE_DESCRIPTION("Marvell Octeon CN10K DPI Driver");
MODULE_LICENSE("GPL");