root/drivers/net/can/ctucanfd/ctucanfd_pci.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*******************************************************************************
 *
 * CTU CAN FD IP Core
 *
 * Copyright (C) 2015-2018 Ondrej Ille <ondrej.ille@gmail.com> FEE CTU
 * Copyright (C) 2018-2021 Ondrej Ille <ondrej.ille@gmail.com> self-funded
 * Copyright (C) 2018-2019 Martin Jerabek <martin.jerabek01@gmail.com> FEE CTU
 * Copyright (C) 2018-2022 Pavel Pisa <pisa@cmp.felk.cvut.cz> FEE CTU/self-funded
 *
 * Project advisors:
 *     Jiri Novak <jnovak@fel.cvut.cz>
 *     Pavel Pisa <pisa@cmp.felk.cvut.cz>
 *
 * Department of Measurement         (http://meas.fel.cvut.cz/)
 * Faculty of Electrical Engineering (http://www.fel.cvut.cz)
 * Czech Technical University        (http://www.cvut.cz/)
 ******************************************************************************/

#include <linux/module.h>
#include <linux/pci.h>

#include "ctucanfd.h"

#ifndef PCI_DEVICE_DATA
#define PCI_DEVICE_DATA(vend, dev, data) \
.vendor = PCI_VENDOR_ID_##vend, \
.device = PCI_DEVICE_ID_##vend##_##dev, \
.subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, 0, 0, \
.driver_data = (kernel_ulong_t)(data)
#endif

#ifndef PCI_VENDOR_ID_TEDIA
#define PCI_VENDOR_ID_TEDIA 0x1760
#endif

#ifndef PCI_DEVICE_ID_TEDIA_CTUCAN_VER21
#define PCI_DEVICE_ID_TEDIA_CTUCAN_VER21 0xff00
#endif

#define CTUCAN_BAR0_CTUCAN_ID 0x0000
#define CTUCAN_BAR0_CRA_BASE  0x4000
#define CYCLONE_IV_CRA_A2P_IE (0x0050)

#define CTUCAN_WITHOUT_CTUCAN_ID  0
#define CTUCAN_WITH_CTUCAN_ID     1

struct ctucan_pci_board_data {
        void __iomem *bar0_base;
        void __iomem *cra_base;
        void __iomem *bar1_base;
        struct list_head ndev_list_head;
        int use_msi;
};

static struct ctucan_pci_board_data *ctucan_pci_get_bdata(struct pci_dev *pdev)
{
        return (struct ctucan_pci_board_data *)pci_get_drvdata(pdev);
}

static void ctucan_pci_set_drvdata(struct device *dev,
                                   struct net_device *ndev)
{
        struct pci_dev *pdev = container_of(dev, struct pci_dev, dev);
        struct ctucan_priv *priv = netdev_priv(ndev);
        struct ctucan_pci_board_data *bdata = ctucan_pci_get_bdata(pdev);

        list_add(&priv->peers_on_pdev, &bdata->ndev_list_head);
        priv->irq_flags = IRQF_SHARED;
}

/**
 * ctucan_pci_probe - PCI registration call
 * @pdev:       Handle to the pci device structure
 * @ent:        Pointer to the entry from ctucan_pci_tbl
 *
 * This function does all the memory allocation and registration for the CAN
 * device.
 *
 * Return: 0 on success and failure value on error
 */
static int ctucan_pci_probe(struct pci_dev *pdev,
                            const struct pci_device_id *ent)
{
        struct device   *dev = &pdev->dev;
        unsigned long driver_data = ent->driver_data;
        struct ctucan_pci_board_data *bdata;
        void __iomem *addr;
        void __iomem *cra_addr;
        void __iomem *bar0_base;
        u32 cra_a2p_ie;
        u32 ctucan_id = 0;
        int ret;
        unsigned int ntxbufs;
        unsigned int num_cores = 1;
        unsigned int core_i = 0;
        int irq;
        int msi_ok = 0;

        ret = pci_enable_device(pdev);
        if (ret) {
                dev_err(dev, "pci_enable_device FAILED\n");
                goto err;
        }

        ret = pci_request_regions(pdev, KBUILD_MODNAME);
        if (ret) {
                dev_err(dev, "pci_request_regions FAILED\n");
                goto err_disable_device;
        }

        ret = pci_enable_msi(pdev);
        if (!ret) {
                dev_info(dev, "MSI enabled\n");
                pci_set_master(pdev);
                msi_ok = 1;
        }

        dev_info(dev, "ctucan BAR0 0x%08llx 0x%08llx\n",
                 (long long)pci_resource_start(pdev, 0),
                 (long long)pci_resource_len(pdev, 0));

        dev_info(dev, "ctucan BAR1 0x%08llx 0x%08llx\n",
                 (long long)pci_resource_start(pdev, 1),
                 (long long)pci_resource_len(pdev, 1));

        addr = pci_iomap(pdev, 1, pci_resource_len(pdev, 1));
        if (!addr) {
                dev_err(dev, "PCI BAR 1 cannot be mapped\n");
                ret = -ENOMEM;
                goto err_release_regions;
        }

        /* Cyclone IV PCI Express Control Registers Area */
        bar0_base = pci_iomap(pdev, 0, pci_resource_len(pdev, 0));
        if (!bar0_base) {
                dev_err(dev, "PCI BAR 0 cannot be mapped\n");
                ret = -EIO;
                goto err_pci_iounmap_bar1;
        }

        if (driver_data == CTUCAN_WITHOUT_CTUCAN_ID) {
                cra_addr = bar0_base;
                num_cores = 2;
        } else {
                cra_addr = bar0_base + CTUCAN_BAR0_CRA_BASE;
                ctucan_id = ioread32(bar0_base + CTUCAN_BAR0_CTUCAN_ID);
                dev_info(dev, "ctucan_id 0x%08lx\n", (unsigned long)ctucan_id);
                num_cores = ctucan_id & 0xf;
        }

        irq = pdev->irq;

        ntxbufs = 4;

        bdata = kzalloc_obj(*bdata);
        if (!bdata) {
                ret = -ENOMEM;
                goto err_pci_iounmap_bar0;
        }

        INIT_LIST_HEAD(&bdata->ndev_list_head);
        bdata->bar0_base = bar0_base;
        bdata->cra_base = cra_addr;
        bdata->bar1_base = addr;
        bdata->use_msi = msi_ok;

        pci_set_drvdata(pdev, bdata);

        ret = ctucan_probe_common(dev, addr, irq, ntxbufs, 100000000,
                                  0, ctucan_pci_set_drvdata);
        if (ret < 0)
                goto err_free_board;

        core_i++;

        while (core_i < num_cores) {
                addr += 0x4000;
                ret = ctucan_probe_common(dev, addr, irq, ntxbufs, 100000000,
                                          0, ctucan_pci_set_drvdata);
                if (ret < 0) {
                        dev_info(dev, "CTU CAN FD core %d initialization failed\n",
                                 core_i);
                        break;
                }
                core_i++;
        }

        /* enable interrupt in
         * Avalon-MM to PCI Express Interrupt Enable Register
         */
        cra_a2p_ie = ioread32(cra_addr + CYCLONE_IV_CRA_A2P_IE);
        dev_info(dev, "cra_a2p_ie 0x%08x\n", cra_a2p_ie);
        cra_a2p_ie |= 1;
        iowrite32(cra_a2p_ie, cra_addr + CYCLONE_IV_CRA_A2P_IE);
        cra_a2p_ie = ioread32(cra_addr + CYCLONE_IV_CRA_A2P_IE);
        dev_info(dev, "cra_a2p_ie 0x%08x\n", cra_a2p_ie);

        return 0;

err_free_board:
        pci_set_drvdata(pdev, NULL);
        kfree(bdata);
err_pci_iounmap_bar0:
        pci_iounmap(pdev, cra_addr);
err_pci_iounmap_bar1:
        pci_iounmap(pdev, addr);
err_release_regions:
        if (msi_ok)
                pci_disable_msi(pdev);
        pci_release_regions(pdev);
err_disable_device:
        pci_disable_device(pdev);
err:
        return ret;
}

/**
 * ctucan_pci_remove - Unregister the device after releasing the resources
 * @pdev:       Handle to the pci device structure
 *
 * This function frees all the resources allocated to the device.
 * Return: 0 always
 */
static void ctucan_pci_remove(struct pci_dev *pdev)
{
        struct net_device *ndev;
        struct ctucan_priv *priv = NULL;
        struct ctucan_pci_board_data *bdata = ctucan_pci_get_bdata(pdev);

        dev_dbg(&pdev->dev, "ctucan_remove");

        if (!bdata) {
                dev_err(&pdev->dev, "%s: no list of devices\n", __func__);
                return;
        }

        /* disable interrupt in
         * Avalon-MM to PCI Express Interrupt Enable Register
         */
        if (bdata->cra_base)
                iowrite32(0, bdata->cra_base + CYCLONE_IV_CRA_A2P_IE);

        while ((priv = list_first_entry_or_null(&bdata->ndev_list_head, struct ctucan_priv,
                                                peers_on_pdev)) != NULL) {
                ndev = priv->can.dev;

                unregister_candev(ndev);

                netif_napi_del(&priv->napi);

                list_del_init(&priv->peers_on_pdev);
                free_candev(ndev);
        }

        pci_iounmap(pdev, bdata->bar1_base);

        if (bdata->use_msi)
                pci_disable_msi(pdev);

        pci_release_regions(pdev);
        pci_disable_device(pdev);

        pci_iounmap(pdev, bdata->bar0_base);

        pci_set_drvdata(pdev, NULL);
        kfree(bdata);
}

static SIMPLE_DEV_PM_OPS(ctucan_pci_pm_ops, ctucan_suspend, ctucan_resume);

static const struct pci_device_id ctucan_pci_tbl[] = {
        {PCI_DEVICE_DATA(TEDIA, CTUCAN_VER21,
                CTUCAN_WITH_CTUCAN_ID)},
        {},
};

static struct pci_driver ctucan_pci_driver = {
        .name = KBUILD_MODNAME,
        .id_table = ctucan_pci_tbl,
        .probe = ctucan_pci_probe,
        .remove = ctucan_pci_remove,
        .driver.pm = &ctucan_pci_pm_ops,
};

module_pci_driver(ctucan_pci_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pavel Pisa <pisa@cmp.felk.cvut.cz>");
MODULE_DESCRIPTION("CTU CAN FD for PCI bus");