root/drivers/net/wwan/iosm/iosm_ipc_irq.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2020-21 Intel Corporation.
 */

#include "iosm_ipc_pcie.h"
#include "iosm_ipc_protocol.h"

static void ipc_write_dbell_reg(struct iosm_pcie *ipc_pcie, int irq_n, u32 data)
{
        void __iomem *write_reg;

        /* Select the first doorbell register, which is only currently needed
         * by CP.
         */
        write_reg = (void __iomem *)((u8 __iomem *)ipc_pcie->ipc_regs +
                                     ipc_pcie->doorbell_write +
                                     (irq_n * ipc_pcie->doorbell_reg_offset));

        /* Fire the doorbell irq by writing data on the doorbell write pointer
         * register.
         */
        iowrite32(data, write_reg);
}

void ipc_doorbell_fire(struct iosm_pcie *ipc_pcie, int irq_n, u32 data)
{
        ipc_write_dbell_reg(ipc_pcie, irq_n, data);
}

/* Threaded Interrupt handler for MSI interrupts */
static irqreturn_t ipc_msi_interrupt(int irq, void *dev_id)
{
        struct iosm_pcie *ipc_pcie = dev_id;
        int instance = irq - ipc_pcie->pci->irq;

        /* Shift the MSI irq actions to the IPC tasklet. IRQ_NONE means the
         * irq was not from the IPC device or could not be served.
         */
        if (instance >= ipc_pcie->nvec)
                return IRQ_NONE;

        if (!test_bit(0, &ipc_pcie->suspend))
                ipc_imem_irq_process(ipc_pcie->imem, instance);

        return IRQ_HANDLED;
}

void ipc_release_irq(struct iosm_pcie *ipc_pcie)
{
        struct pci_dev *pdev = ipc_pcie->pci;

        if (pdev->msi_enabled) {
                while (--ipc_pcie->nvec >= 0)
                        free_irq(pdev->irq + ipc_pcie->nvec, ipc_pcie);
        }
        pci_free_irq_vectors(pdev);
}

int ipc_acquire_irq(struct iosm_pcie *ipc_pcie)
{
        struct pci_dev *pdev = ipc_pcie->pci;
        int i, rc = -EINVAL;

        ipc_pcie->nvec = pci_alloc_irq_vectors(pdev, IPC_MSI_VECTORS,
                                               IPC_MSI_VECTORS, PCI_IRQ_MSI);

        if (ipc_pcie->nvec < 0) {
                rc = ipc_pcie->nvec;
                goto error;
        }

        if (!pdev->msi_enabled)
                goto error;

        for (i = 0; i < ipc_pcie->nvec; ++i) {
                rc = request_threaded_irq(pdev->irq + i, NULL,
                                          ipc_msi_interrupt, IRQF_ONESHOT,
                                          KBUILD_MODNAME, ipc_pcie);
                if (rc) {
                        dev_err(ipc_pcie->dev, "unable to grab IRQ, rc=%d", rc);
                        ipc_pcie->nvec = i;
                        ipc_release_irq(ipc_pcie);
                        goto error;
                }
        }

error:
        return rc;
}