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

#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/module.h>
#include <linux/suspend.h>
#include <net/rtnetlink.h>

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

MODULE_DESCRIPTION("IOSM Driver");
MODULE_LICENSE("GPL v2");

/* WWAN GUID */
static guid_t wwan_acpi_guid = GUID_INIT(0xbad01b75, 0x22a8, 0x4f48, 0x87, 0x92,
                                       0xbd, 0xde, 0x94, 0x67, 0x74, 0x7d);
static bool pci_registered;

static void ipc_pcie_resources_release(struct iosm_pcie *ipc_pcie)
{
        /* Free the MSI resources. */
        ipc_release_irq(ipc_pcie);

        /* Free mapped doorbell scratchpad bus memory into CPU space. */
        iounmap(ipc_pcie->scratchpad);

        /* Free mapped IPC_REGS bus memory into CPU space. */
        iounmap(ipc_pcie->ipc_regs);

        /* Releases all PCI I/O and memory resources previously reserved by a
         * successful call to pci_request_regions.  Call this function only
         * after all use of the PCI regions has ceased.
         */
        pci_release_regions(ipc_pcie->pci);
}

static void ipc_pcie_cleanup(struct iosm_pcie *ipc_pcie)
{
        /* Free the shared memory resources. */
        ipc_imem_cleanup(ipc_pcie->imem);

        ipc_pcie_resources_release(ipc_pcie);

        /* Signal to the system that the PCI device is not in use. */
        pci_disable_device(ipc_pcie->pci);
}

static void ipc_pcie_deinit(struct iosm_pcie *ipc_pcie)
{
        kfree(ipc_pcie->imem);
        kfree(ipc_pcie);
}

static void ipc_pcie_remove(struct pci_dev *pci)
{
        struct iosm_pcie *ipc_pcie = pci_get_drvdata(pci);

        ipc_pcie_cleanup(ipc_pcie);

        ipc_pcie_deinit(ipc_pcie);
}

static int ipc_pcie_resources_request(struct iosm_pcie *ipc_pcie)
{
        struct pci_dev *pci = ipc_pcie->pci;
        u32 cap = 0;
        int ret;

        /* Reserved PCI I/O and memory resources.
         * Mark all PCI regions associated with PCI device pci as
         * being reserved by owner IOSM_IPC.
         */
        ret = pci_request_regions(pci, "IOSM_IPC");
        if (ret) {
                dev_err(ipc_pcie->dev, "failed pci request regions");
                goto pci_request_region_fail;
        }

        /* Reserve the doorbell IPC REGS memory resources.
         * Remap the memory into CPU space. Arrange for the physical address
         * (BAR) to be visible from this driver.
         * pci_ioremap_bar() ensures that the memory is marked uncachable.
         */
        ipc_pcie->ipc_regs = pci_ioremap_bar(pci, ipc_pcie->ipc_regs_bar_nr);

        if (!ipc_pcie->ipc_regs) {
                dev_err(ipc_pcie->dev, "IPC REGS ioremap error");
                ret = -EBUSY;
                goto ipc_regs_remap_fail;
        }

        /* Reserve the MMIO scratchpad memory resources.
         * Remap the memory into CPU space. Arrange for the physical address
         * (BAR) to be visible from this driver.
         * pci_ioremap_bar() ensures that the memory is marked uncachable.
         */
        ipc_pcie->scratchpad =
                pci_ioremap_bar(pci, ipc_pcie->scratchpad_bar_nr);

        if (!ipc_pcie->scratchpad) {
                dev_err(ipc_pcie->dev, "doorbell scratchpad ioremap error");
                ret = -EBUSY;
                goto scratch_remap_fail;
        }

        /* Install the irq handler triggered by CP. */
        ret = ipc_acquire_irq(ipc_pcie);
        if (ret) {
                dev_err(ipc_pcie->dev, "acquiring MSI irq failed!");
                goto irq_acquire_fail;
        }

        /* Enable bus-mastering for the IOSM IPC device. */
        pci_set_master(pci);

        /* Enable LTR if possible
         * This is needed for L1.2!
         */
        pcie_capability_read_dword(ipc_pcie->pci, PCI_EXP_DEVCAP2, &cap);
        if (cap & PCI_EXP_DEVCAP2_LTR)
                pcie_capability_set_word(ipc_pcie->pci, PCI_EXP_DEVCTL2,
                                         PCI_EXP_DEVCTL2_LTR_EN);

        dev_dbg(ipc_pcie->dev, "link between AP and CP is fully on");

        return ret;

irq_acquire_fail:
        iounmap(ipc_pcie->scratchpad);
scratch_remap_fail:
        iounmap(ipc_pcie->ipc_regs);
ipc_regs_remap_fail:
        pci_release_regions(pci);
pci_request_region_fail:
        return ret;
}

bool ipc_pcie_check_aspm_enabled(struct iosm_pcie *ipc_pcie,
                                 bool parent)
{
        struct pci_dev *pdev;
        u16 value = 0;
        u32 enabled;

        if (parent)
                pdev = ipc_pcie->pci->bus->self;
        else
                pdev = ipc_pcie->pci;

        pcie_capability_read_word(pdev, PCI_EXP_LNKCTL, &value);
        enabled = value & PCI_EXP_LNKCTL_ASPMC;
        dev_dbg(ipc_pcie->dev, "ASPM L1: 0x%04X 0x%03X", pdev->device, value);

        return (enabled == PCI_EXP_LNKCTL_ASPM_L1 ||
                enabled == PCI_EXP_LNKCTL_ASPMC);
}

bool ipc_pcie_check_data_link_active(struct iosm_pcie *ipc_pcie)
{
        struct pci_dev *parent;
        u16 link_status = 0;

        if (!ipc_pcie->pci->bus || !ipc_pcie->pci->bus->self) {
                dev_err(ipc_pcie->dev, "root port not found");
                return false;
        }

        parent = ipc_pcie->pci->bus->self;

        pcie_capability_read_word(parent, PCI_EXP_LNKSTA, &link_status);
        dev_dbg(ipc_pcie->dev, "Link status: 0x%04X", link_status);

        return link_status & PCI_EXP_LNKSTA_DLLLA;
}

static bool ipc_pcie_check_aspm_supported(struct iosm_pcie *ipc_pcie,
                                          bool parent)
{
        struct pci_dev *pdev;
        u32 support;
        u32 cap = 0;

        if (parent)
                pdev = ipc_pcie->pci->bus->self;
        else
                pdev = ipc_pcie->pci;
        pcie_capability_read_dword(pdev, PCI_EXP_LNKCAP, &cap);
        support = u32_get_bits(cap, PCI_EXP_LNKCAP_ASPMS);
        if (support < PCI_EXP_LNKCTL_ASPM_L1) {
                dev_dbg(ipc_pcie->dev, "ASPM L1 not supported: 0x%04X",
                        pdev->device);
                return false;
        }
        return true;
}

void ipc_pcie_config_aspm(struct iosm_pcie *ipc_pcie)
{
        bool parent_aspm_enabled, dev_aspm_enabled;

        /* check if both root port and child supports ASPM L1 */
        if (!ipc_pcie_check_aspm_supported(ipc_pcie, true) ||
            !ipc_pcie_check_aspm_supported(ipc_pcie, false))
                return;

        parent_aspm_enabled = ipc_pcie_check_aspm_enabled(ipc_pcie, true);
        dev_aspm_enabled = ipc_pcie_check_aspm_enabled(ipc_pcie, false);

        dev_dbg(ipc_pcie->dev, "ASPM parent: %s device: %s",
                parent_aspm_enabled ? "Enabled" : "Disabled",
                dev_aspm_enabled ? "Enabled" : "Disabled");
}

/* Initializes PCIe endpoint configuration */
static void ipc_pcie_config_init(struct iosm_pcie *ipc_pcie)
{
        /* BAR0 is used for doorbell */
        ipc_pcie->ipc_regs_bar_nr = IPC_DOORBELL_BAR0;

        /* update HW configuration */
        ipc_pcie->scratchpad_bar_nr = IPC_SCRATCHPAD_BAR2;
        ipc_pcie->doorbell_reg_offset = IPC_DOORBELL_CH_OFFSET;
        ipc_pcie->doorbell_write = IPC_WRITE_PTR_REG_0;
        ipc_pcie->doorbell_capture = IPC_CAPTURE_PTR_REG_0;
}

/* This will read the BIOS WWAN RTD3 settings:
 * D0L1.2/D3L2/Disabled
 */
static enum ipc_pcie_sleep_state ipc_pcie_read_bios_cfg(struct device *dev)
{
        enum ipc_pcie_sleep_state sleep_state = IPC_PCIE_D0L12;
        union acpi_object *object;
        acpi_handle handle_acpi;

        handle_acpi = ACPI_HANDLE(dev);
        if (!handle_acpi) {
                pr_debug("pci device is NOT ACPI supporting device\n");
                goto default_ret;
        }

        object = acpi_evaluate_dsm(handle_acpi, &wwan_acpi_guid, 0, 3, NULL);
        if (!object)
                goto default_ret;

        if (object->integer.value == 3)
                sleep_state = IPC_PCIE_D3L2;

        ACPI_FREE(object);

default_ret:
        return sleep_state;
}

static int ipc_pcie_probe(struct pci_dev *pci,
                          const struct pci_device_id *pci_id)
{
        struct iosm_pcie *ipc_pcie = kzalloc_obj(*ipc_pcie);
        int ret;

        pr_debug("Probing device 0x%X from the vendor 0x%X", pci_id->device,
                 pci_id->vendor);

        if (!ipc_pcie)
                goto ret_fail;

        /* Initialize ipc dbg component for the PCIe device */
        ipc_pcie->dev = &pci->dev;

        /* Set the driver specific data. */
        pci_set_drvdata(pci, ipc_pcie);

        /* Save the address of the PCI device configuration. */
        ipc_pcie->pci = pci;

        /* Update platform configuration */
        ipc_pcie_config_init(ipc_pcie);

        /* Initialize the device before it is used. Ask low-level code
         * to enable I/O and memory. Wake up the device if it was suspended.
         */
        if (pci_enable_device(pci)) {
                dev_err(ipc_pcie->dev, "failed to enable the AP PCIe device");
                /* If enable of PCIe device has failed then calling
                 * ipc_pcie_cleanup will panic the system. More over
                 * ipc_pcie_cleanup() is required to be called after
                 * ipc_imem_mount()
                 */
                goto pci_enable_fail;
        }

        ret = dma_set_mask(ipc_pcie->dev, DMA_BIT_MASK(64));
        if (ret) {
                dev_err(ipc_pcie->dev, "Could not set PCI DMA mask: %d", ret);
                goto set_mask_fail;
        }

        ipc_pcie_config_aspm(ipc_pcie);
        dev_dbg(ipc_pcie->dev, "PCIe device enabled.");

        /* Read WWAN RTD3 BIOS Setting
         */
        ipc_pcie->d3l2_support = ipc_pcie_read_bios_cfg(&pci->dev);

        ipc_pcie->suspend = 0;

        if (ipc_pcie_resources_request(ipc_pcie))
                goto resources_req_fail;

        /* Establish the link to the imem layer. */
        ipc_pcie->imem = ipc_imem_init(ipc_pcie, pci->device,
                                       ipc_pcie->scratchpad, ipc_pcie->dev);
        if (!ipc_pcie->imem) {
                dev_err(ipc_pcie->dev, "failed to init imem");
                goto imem_init_fail;
        }

        return 0;

imem_init_fail:
        ipc_pcie_resources_release(ipc_pcie);
resources_req_fail:
set_mask_fail:
        pci_disable_device(pci);
pci_enable_fail:
        kfree(ipc_pcie);
ret_fail:
        return -EIO;
}

static const struct pci_device_id iosm_ipc_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7560_ID) },
        { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CP_DEVICE_7360_ID) },
        {}
};
MODULE_DEVICE_TABLE(pci, iosm_ipc_ids);

/* Enter sleep in s2idle case
 */
static int __maybe_unused ipc_pcie_suspend_s2idle(struct iosm_pcie *ipc_pcie)
{
        ipc_cp_irq_sleep_control(ipc_pcie, IPC_MEM_DEV_PM_FORCE_SLEEP);

        /* Complete all memory stores before setting bit */
        smp_mb__before_atomic();

        set_bit(0, &ipc_pcie->suspend);

        /* Complete all memory stores after setting bit */
        smp_mb__after_atomic();

        ipc_imem_pm_s2idle_sleep(ipc_pcie->imem, true);

        return 0;
}

/* Resume from sleep in s2idle case
 */
static int __maybe_unused ipc_pcie_resume_s2idle(struct iosm_pcie *ipc_pcie)
{
        ipc_cp_irq_sleep_control(ipc_pcie, IPC_MEM_DEV_PM_FORCE_ACTIVE);

        ipc_imem_pm_s2idle_sleep(ipc_pcie->imem, false);

        /* Complete all memory stores before clearing bit. */
        smp_mb__before_atomic();

        clear_bit(0, &ipc_pcie->suspend);

        /* Complete all memory stores after clearing bit. */
        smp_mb__after_atomic();
        return 0;
}

int __maybe_unused ipc_pcie_suspend(struct iosm_pcie *ipc_pcie)
{
        /* The HAL shall ask the shared memory layer whether D3 is allowed. */
        ipc_imem_pm_suspend(ipc_pcie->imem);

        dev_dbg(ipc_pcie->dev, "SUSPEND done");
        return 0;
}

int __maybe_unused ipc_pcie_resume(struct iosm_pcie *ipc_pcie)
{
        /* The HAL shall inform the shared memory layer that the device is
         * active.
         */
        ipc_imem_pm_resume(ipc_pcie->imem);

        dev_dbg(ipc_pcie->dev, "RESUME done");
        return 0;
}

static int __maybe_unused ipc_pcie_suspend_cb(struct device *dev)
{
        struct iosm_pcie *ipc_pcie;
        struct pci_dev *pdev;

        pdev = to_pci_dev(dev);

        ipc_pcie = pci_get_drvdata(pdev);

        switch (ipc_pcie->d3l2_support) {
        case IPC_PCIE_D0L12:
                ipc_pcie_suspend_s2idle(ipc_pcie);
                break;
        case IPC_PCIE_D3L2:
                ipc_pcie_suspend(ipc_pcie);
                break;
        }

        return 0;
}

static int __maybe_unused ipc_pcie_resume_cb(struct device *dev)
{
        struct iosm_pcie *ipc_pcie;
        struct pci_dev *pdev;

        pdev = to_pci_dev(dev);

        ipc_pcie = pci_get_drvdata(pdev);

        switch (ipc_pcie->d3l2_support) {
        case IPC_PCIE_D0L12:
                ipc_pcie_resume_s2idle(ipc_pcie);
                break;
        case IPC_PCIE_D3L2:
                ipc_pcie_resume(ipc_pcie);
                break;
        }

        return 0;
}

static SIMPLE_DEV_PM_OPS(iosm_ipc_pm, ipc_pcie_suspend_cb, ipc_pcie_resume_cb);

static struct pci_driver iosm_ipc_driver = {
        .name = KBUILD_MODNAME,
        .probe = ipc_pcie_probe,
        .remove = ipc_pcie_remove,
        .driver = {
                .pm = &iosm_ipc_pm,
        },
        .id_table = iosm_ipc_ids,
};

int ipc_pcie_addr_map(struct iosm_pcie *ipc_pcie, unsigned char *data,
                      size_t size, dma_addr_t *mapping, int direction)
{
        if (ipc_pcie->pci) {
                *mapping = dma_map_single(&ipc_pcie->pci->dev, data, size,
                                          direction);
                if (dma_mapping_error(&ipc_pcie->pci->dev, *mapping)) {
                        dev_err(ipc_pcie->dev, "dma mapping failed");
                        return -EINVAL;
                }
        }
        return 0;
}

void ipc_pcie_addr_unmap(struct iosm_pcie *ipc_pcie, size_t size,
                         dma_addr_t mapping, int direction)
{
        if (!mapping)
                return;
        if (ipc_pcie->pci)
                dma_unmap_single(&ipc_pcie->pci->dev, mapping, size, direction);
}

struct sk_buff *ipc_pcie_alloc_local_skb(struct iosm_pcie *ipc_pcie,
                                         gfp_t flags, size_t size)
{
        struct sk_buff *skb;

        if (!ipc_pcie || !size) {
                pr_err("invalid pcie object or size");
                return NULL;
        }

        skb = __netdev_alloc_skb(NULL, size, flags);
        if (!skb)
                return NULL;

        IPC_CB(skb)->op_type = (u8)UL_DEFAULT;
        IPC_CB(skb)->mapping = 0;

        return skb;
}

struct sk_buff *ipc_pcie_alloc_skb(struct iosm_pcie *ipc_pcie, size_t size,
                                   gfp_t flags, dma_addr_t *mapping,
                                   int direction, size_t headroom)
{
        struct sk_buff *skb = ipc_pcie_alloc_local_skb(ipc_pcie, flags,
                                                       size + headroom);
        if (!skb)
                return NULL;

        if (headroom)
                skb_reserve(skb, headroom);

        if (ipc_pcie_addr_map(ipc_pcie, skb->data, size, mapping, direction)) {
                dev_kfree_skb(skb);
                return NULL;
        }

        BUILD_BUG_ON(sizeof(*IPC_CB(skb)) > sizeof(skb->cb));

        /* Store the mapping address in skb scratch pad for later usage */
        IPC_CB(skb)->mapping = *mapping;
        IPC_CB(skb)->direction = direction;
        IPC_CB(skb)->len = size;

        return skb;
}

void ipc_pcie_kfree_skb(struct iosm_pcie *ipc_pcie, struct sk_buff *skb)
{
        if (!skb)
                return;

        ipc_pcie_addr_unmap(ipc_pcie, IPC_CB(skb)->len, IPC_CB(skb)->mapping,
                            IPC_CB(skb)->direction);
        IPC_CB(skb)->mapping = 0;
        dev_kfree_skb(skb);
}

static int pm_notify(struct notifier_block *nb, unsigned long mode, void *_unused)
{
        if (mode == PM_HIBERNATION_PREPARE || mode == PM_RESTORE_PREPARE) {
                if (pci_registered) {
                        pci_unregister_driver(&iosm_ipc_driver);
                        pci_registered = false;
                }
        } else if (mode == PM_POST_HIBERNATION || mode == PM_POST_RESTORE) {
                if (!pci_registered) {
                        int ret;

                        ret = pci_register_driver(&iosm_ipc_driver);
                        if (ret) {
                                pr_err(KBUILD_MODNAME ": unable to re-register PCI driver: %d\n",
                                       ret);
                        } else {
                                pci_registered = true;
                        }
                }
        }

        return 0;
}

static struct notifier_block pm_notifier = {
        .notifier_call = pm_notify,
};

static int __init iosm_ipc_driver_init(void)
{
        int ret;

        ret = pci_register_driver(&iosm_ipc_driver);
        if (ret)
                return ret;

        pci_registered = true;

        register_pm_notifier(&pm_notifier);

        return 0;
}
module_init(iosm_ipc_driver_init);

static void __exit iosm_ipc_driver_exit(void)
{
        unregister_pm_notifier(&pm_notifier);

        if (pci_registered)
                pci_unregister_driver(&iosm_ipc_driver);
}
module_exit(iosm_ipc_driver_exit);