root/drivers/usb/cdns3/cdnsp-pci.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Cadence PCI Glue driver.
 *
 * Copyright (C) 2019 Cadence.
 *
 * Author: Pawel Laszczak <pawell@cadence.com>
 *
 */

#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci.h>

#include "core.h"
#include "gadget-export.h"

#define PCI_BAR_HOST            0
#define PCI_BAR_OTG             0
#define PCI_BAR_DEV             2

#define PCI_DEV_FN_HOST_DEVICE  0
#define PCI_DEV_FN_OTG          1

#define PCI_DRIVER_NAME         "cdns-pci-usbssp"
#define PLAT_DRIVER_NAME        "cdns-usbssp"

#define CHICKEN_APB_TIMEOUT_VALUE       0x1C20

static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
{
        /*
         * Gets the second function.
         * Platform has two function. The fist keeps resources for
         * Host/Device while the secon keeps resources for DRD/OTG.
         */
        if (pdev->device == PCI_DEVICE_ID_CDNS_USBSSP)
                return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSS, NULL);
        if (pdev->device == PCI_DEVICE_ID_CDNS_USBSS)
                return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USBSSP, NULL);

        return NULL;
}

static int cdnsp_pci_probe(struct pci_dev *pdev,
                           const struct pci_device_id *id)
{
        struct device *dev = &pdev->dev;
        struct pci_dev *func;
        struct resource *res;
        struct cdns *cdnsp;
        int ret;

        /*
         * For GADGET/HOST PCI (devfn) function number is 0,
         * for OTG PCI (devfn) function number is 1.
         */
        if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE &&
                    pdev->devfn != PCI_DEV_FN_OTG))
                return -EINVAL;

        func = cdnsp_get_second_fun(pdev);
        if (!func)
                return -EINVAL;

        if (func->class == PCI_CLASS_SERIAL_USB_XHCI ||
            pdev->class == PCI_CLASS_SERIAL_USB_XHCI) {
                ret = -EINVAL;
                goto put_pci;
        }

        ret = pcim_enable_device(pdev);
        if (ret) {
                dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret);
                goto put_pci;
        }

        pci_set_master(pdev);
        if (pci_is_enabled(func)) {
                cdnsp = pci_get_drvdata(func);
        } else {
                cdnsp = kzalloc_obj(*cdnsp);
                if (!cdnsp) {
                        ret = -ENOMEM;
                        goto put_pci;
                }
        }

        /* For GADGET device function number is 0. */
        if (pdev->devfn == 0) {
                resource_size_t rsrc_start, rsrc_len;

                /* Function 0: host(BAR_0) + device(BAR_1).*/
                dev_dbg(dev, "Initialize resources\n");
                rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV);
                rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV);
                res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev");
                if (!res) {
                        dev_dbg(dev, "controller already in use\n");
                        ret = -EBUSY;
                        goto free_cdnsp;
                }

                cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len);
                if (!cdnsp->dev_regs) {
                        dev_dbg(dev, "error mapping memory\n");
                        ret = -EFAULT;
                        goto free_cdnsp;
                }

                cdnsp->dev_irq = pdev->irq;
                dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n",
                        &rsrc_start);

                res = &cdnsp->xhci_res[0];
                res->start = pci_resource_start(pdev, PCI_BAR_HOST);
                res->end = pci_resource_end(pdev, PCI_BAR_HOST);
                res->name = "xhci";
                res->flags = IORESOURCE_MEM;
                dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n",
                        &res->start);

                /* Interrupt for XHCI, */
                res = &cdnsp->xhci_res[1];
                res->start = pdev->irq;
                res->name = "host";
                res->flags = IORESOURCE_IRQ;
        } else {
                res = &cdnsp->otg_res;
                res->start = pci_resource_start(pdev, PCI_BAR_OTG);
                res->end =   pci_resource_end(pdev, PCI_BAR_OTG);
                res->name = "otg";
                res->flags = IORESOURCE_MEM;
                dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n",
                        &res->start);

                /* Interrupt for OTG/DRD. */
                cdnsp->otg_irq = pdev->irq;
        }

        /*
         * Cadence PCI based platform require some longer timeout for APB
         * to fixes domain clock synchronization issue after resuming
         * controller from L1 state.
         */
        cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
        pci_set_drvdata(pdev, cdnsp);

        if (pci_is_enabled(func)) {
                cdnsp->dev = dev;
                cdnsp->gadget_init = cdnsp_gadget_init;

                ret = cdns_init(cdnsp);
                if (ret)
                        goto free_cdnsp;
        }

        device_wakeup_enable(&pdev->dev);
        if (pci_dev_run_wake(pdev))
                pm_runtime_put_noidle(&pdev->dev);

        return 0;

free_cdnsp:
        if (!pci_is_enabled(func))
                kfree(cdnsp);

put_pci:
        pci_dev_put(func);

        return ret;
}

static void cdnsp_pci_remove(struct pci_dev *pdev)
{
        struct cdns *cdnsp;
        struct pci_dev *func;

        func = cdnsp_get_second_fun(pdev);
        cdnsp = (struct cdns *)pci_get_drvdata(pdev);

        if (pci_dev_run_wake(pdev))
                pm_runtime_get_noresume(&pdev->dev);

        if (pci_is_enabled(func)) {
                cdns_remove(cdnsp);
        } else {
                kfree(cdnsp);
        }

        pci_dev_put(func);
}

static int __maybe_unused cdnsp_pci_suspend(struct device *dev)
{
        struct cdns *cdns = dev_get_drvdata(dev);

        return cdns_suspend(cdns);
}

static int __maybe_unused cdnsp_pci_resume(struct device *dev)
{
        struct cdns *cdns = dev_get_drvdata(dev);
        unsigned long flags;
        int ret;

        spin_lock_irqsave(&cdns->lock, flags);
        ret = cdns_resume(cdns);
        spin_unlock_irqrestore(&cdns->lock, flags);
        cdns_set_active(cdns, 1);

        return ret;
}

static const struct dev_pm_ops cdnsp_pci_pm_ops = {
        SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume)
};

static const struct pci_device_id cdnsp_pci_ids[] = {
        { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
          .class = PCI_CLASS_SERIAL_USB_DEVICE },
        { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSSP),
          .class = PCI_CLASS_SERIAL_USB_CDNS },
        { PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USBSS),
          .class = PCI_CLASS_SERIAL_USB_CDNS },
        { 0, }
};

static struct pci_driver cdnsp_pci_driver = {
        .name = "cdnsp-pci",
        .id_table = cdnsp_pci_ids,
        .probe = cdnsp_pci_probe,
        .remove = cdnsp_pci_remove,
        .driver = {
                .pm = &cdnsp_pci_pm_ops,
        }
};

module_pci_driver(cdnsp_pci_driver);
MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids);

MODULE_ALIAS("pci:cdnsp");
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Cadence CDNSP PCI driver");