root/drivers/misc/dw-xdata-pcie.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
 * Synopsys DesignWare xData driver
 *
 * Author: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
 */

#include <linux/miscdevice.h>
#include <linux/bitfield.h>
#include <linux/pci-epf.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/string_choices.h>

#define DW_XDATA_DRIVER_NAME            "dw-xdata-pcie"

#define DW_XDATA_EP_MEM_OFFSET          0x8000000

static DEFINE_IDA(xdata_ida);

#define STATUS_DONE                     BIT(0)

#define CONTROL_DOORBELL                BIT(0)
#define CONTROL_IS_WRITE                BIT(1)
#define CONTROL_LENGTH(a)               FIELD_PREP(GENMASK(13, 2), a)
#define CONTROL_PATTERN_INC             BIT(16)
#define CONTROL_NO_ADDR_INC             BIT(18)

#define XPERF_CONTROL_ENABLE            BIT(5)

#define BURST_REPEAT                    BIT(31)
#define BURST_VALUE                     0x1001

#define PATTERN_VALUE                   0x0

struct dw_xdata_regs {
        u32 addr_lsb;                                   /* 0x000 */
        u32 addr_msb;                                   /* 0x004 */
        u32 burst_cnt;                                  /* 0x008 */
        u32 control;                                    /* 0x00c */
        u32 pattern;                                    /* 0x010 */
        u32 status;                                     /* 0x014 */
        u32 RAM_addr;                                   /* 0x018 */
        u32 RAM_port;                                   /* 0x01c */
        u32 _reserved0[14];                             /* 0x020..0x054 */
        u32 perf_control;                               /* 0x058 */
        u32 _reserved1[41];                             /* 0x05c..0x0fc */
        u32 wr_cnt_lsb;                                 /* 0x100 */
        u32 wr_cnt_msb;                                 /* 0x104 */
        u32 rd_cnt_lsb;                                 /* 0x108 */
        u32 rd_cnt_msb;                                 /* 0x10c */
} __packed;

struct dw_xdata_region {
        phys_addr_t paddr;                              /* physical address */
        void __iomem *vaddr;                            /* virtual address */
};

struct dw_xdata {
        struct dw_xdata_region rg_region;               /* registers */
        size_t max_wr_len;                              /* max wr xfer len */
        size_t max_rd_len;                              /* max rd xfer len */
        struct mutex mutex;
        struct pci_dev *pdev;
        struct miscdevice misc_dev;
};

static inline struct dw_xdata_regs __iomem *__dw_regs(struct dw_xdata *dw)
{
        return dw->rg_region.vaddr;
}

static void dw_xdata_stop(struct dw_xdata *dw)
{
        u32 burst;

        mutex_lock(&dw->mutex);

        burst = readl(&(__dw_regs(dw)->burst_cnt));

        if (burst & BURST_REPEAT) {
                burst &= ~(u32)BURST_REPEAT;
                writel(burst, &(__dw_regs(dw)->burst_cnt));
        }

        mutex_unlock(&dw->mutex);
}

static void dw_xdata_start(struct dw_xdata *dw, bool write)
{
        struct device *dev = &dw->pdev->dev;
        u32 control, status;

        /* Stop first if xfer in progress */
        dw_xdata_stop(dw);

        mutex_lock(&dw->mutex);

        /* Clear status register */
        writel(0x0, &(__dw_regs(dw)->status));

        /* Burst count register set for continuous until stopped */
        writel(BURST_REPEAT | BURST_VALUE, &(__dw_regs(dw)->burst_cnt));

        /* Pattern register */
        writel(PATTERN_VALUE, &(__dw_regs(dw)->pattern));

        /* Control register */
        control = CONTROL_DOORBELL | CONTROL_PATTERN_INC | CONTROL_NO_ADDR_INC;
        if (write) {
                control |= CONTROL_IS_WRITE;
                control |= CONTROL_LENGTH(dw->max_wr_len);
        } else {
                control |= CONTROL_LENGTH(dw->max_rd_len);
        }
        writel(control, &(__dw_regs(dw)->control));

        /*
         * The xData HW block needs about 100 ms to initiate the traffic
         * generation according this HW block datasheet.
         */
        usleep_range(100, 150);

        status = readl(&(__dw_regs(dw)->status));

        mutex_unlock(&dw->mutex);

        if (!(status & STATUS_DONE))
                dev_dbg(dev, "xData: started %s direction\n",
                        str_write_read(write));
}

static void dw_xdata_perf_meas(struct dw_xdata *dw, u64 *data, bool write)
{
        if (write) {
                *data = readl(&(__dw_regs(dw)->wr_cnt_msb));
                *data <<= 32;
                *data |= readl(&(__dw_regs(dw)->wr_cnt_lsb));
        } else {
                *data = readl(&(__dw_regs(dw)->rd_cnt_msb));
                *data <<= 32;
                *data |= readl(&(__dw_regs(dw)->rd_cnt_lsb));
        }
}

static u64 dw_xdata_perf_diff(u64 *m1, u64 *m2, u64 time)
{
        u64 rate = (*m1 - *m2);

        rate *= (1000 * 1000 * 1000);
        rate >>= 20;
        rate = DIV_ROUND_CLOSEST_ULL(rate, time);

        return rate;
}

static void dw_xdata_perf(struct dw_xdata *dw, u64 *rate, bool write)
{
        struct device *dev = &dw->pdev->dev;
        u64 data[2], time[2], diff;

        mutex_lock(&dw->mutex);

        /* First acquisition of current count frames */
        writel(0x0, &(__dw_regs(dw)->perf_control));
        dw_xdata_perf_meas(dw, &data[0], write);
        time[0] = jiffies;
        writel((u32)XPERF_CONTROL_ENABLE, &(__dw_regs(dw)->perf_control));

        /*
         * Wait 100ms between the 1st count frame acquisition and the 2nd
         * count frame acquisition, in order to calculate the speed later
         */
        mdelay(100);

        /* Second acquisition of current count frames */
        writel(0x0, &(__dw_regs(dw)->perf_control));
        dw_xdata_perf_meas(dw, &data[1], write);
        time[1] = jiffies;
        writel((u32)XPERF_CONTROL_ENABLE, &(__dw_regs(dw)->perf_control));

        /*
         * Speed calculation
         *
         * rate = (2nd count frames - 1st count frames) / (time elapsed)
         */
        diff = jiffies_to_nsecs(time[1] - time[0]);
        *rate = dw_xdata_perf_diff(&data[1], &data[0], diff);

        mutex_unlock(&dw->mutex);

        dev_dbg(dev, "xData: time=%llu us, %s=%llu MB/s\n",
                diff, str_write_read(write), *rate);
}

static struct dw_xdata *misc_dev_to_dw(struct miscdevice *misc_dev)
{
        return container_of(misc_dev, struct dw_xdata, misc_dev);
}

static ssize_t write_show(struct device *dev, struct device_attribute *attr,
                          char *buf)
{
        struct miscdevice *misc_dev = dev_get_drvdata(dev);
        struct dw_xdata *dw = misc_dev_to_dw(misc_dev);
        u64 rate;

        dw_xdata_perf(dw, &rate, true);

        return sysfs_emit(buf, "%llu\n", rate);
}

static ssize_t write_store(struct device *dev, struct device_attribute *attr,
                           const char *buf, size_t size)
{
        struct miscdevice *misc_dev = dev_get_drvdata(dev);
        struct dw_xdata *dw = misc_dev_to_dw(misc_dev);
        bool enabled;
        int ret;

        ret = kstrtobool(buf, &enabled);
        if (ret < 0)
                return ret;

        if (enabled) {
                dev_dbg(dev, "xData: requested write transfer\n");
                dw_xdata_start(dw, true);
        } else {
                dev_dbg(dev, "xData: requested stop transfer\n");
                dw_xdata_stop(dw);
        }

        return size;
}

static DEVICE_ATTR_RW(write);

static ssize_t read_show(struct device *dev, struct device_attribute *attr,
                         char *buf)
{
        struct miscdevice *misc_dev = dev_get_drvdata(dev);
        struct dw_xdata *dw = misc_dev_to_dw(misc_dev);
        u64 rate;

        dw_xdata_perf(dw, &rate, false);

        return sysfs_emit(buf, "%llu\n", rate);
}

static ssize_t read_store(struct device *dev, struct device_attribute *attr,
                          const char *buf, size_t size)
{
        struct miscdevice *misc_dev = dev_get_drvdata(dev);
        struct dw_xdata *dw = misc_dev_to_dw(misc_dev);
        bool enabled;
        int ret;

        ret = kstrtobool(buf, &enabled);
        if (ret < 0)
                return ret;

        if (enabled) {
                dev_dbg(dev, "xData: requested read transfer\n");
                dw_xdata_start(dw, false);
        } else {
                dev_dbg(dev, "xData: requested stop transfer\n");
                dw_xdata_stop(dw);
        }

        return size;
}

static DEVICE_ATTR_RW(read);

static struct attribute *xdata_attrs[] = {
        &dev_attr_write.attr,
        &dev_attr_read.attr,
        NULL,
};

ATTRIBUTE_GROUPS(xdata);

static int dw_xdata_pcie_probe(struct pci_dev *pdev,
                               const struct pci_device_id *pid)
{
        struct device *dev = &pdev->dev;
        struct dw_xdata *dw;
        char name[24];
        u64 addr;
        int err;
        int id;

        /* Enable PCI device */
        err = pcim_enable_device(pdev);
        if (err) {
                dev_err(dev, "enabling device failed\n");
                return err;
        }

        /* Mapping PCI BAR regions */
        err = pcim_iomap_regions(pdev, BIT(BAR_0), pci_name(pdev));
        if (err) {
                dev_err(dev, "xData BAR I/O remapping failed\n");
                return err;
        }

        pci_set_master(pdev);

        /* Allocate memory */
        dw = devm_kzalloc(dev, sizeof(*dw), GFP_KERNEL);
        if (!dw)
                return -ENOMEM;

        /* Data structure initialization */
        mutex_init(&dw->mutex);

        dw->rg_region.vaddr = pcim_iomap_table(pdev)[BAR_0];
        if (!dw->rg_region.vaddr)
                return -ENOMEM;

        dw->rg_region.paddr = pdev->resource[BAR_0].start;

        dw->max_wr_len = pcie_get_mps(pdev);
        dw->max_wr_len >>= 2;

        dw->max_rd_len = pcie_get_readrq(pdev);
        dw->max_rd_len >>= 2;

        dw->pdev = pdev;

        id = ida_alloc(&xdata_ida, GFP_KERNEL);
        if (id < 0) {
                dev_err(dev, "xData: unable to get id\n");
                return id;
        }

        snprintf(name, sizeof(name), DW_XDATA_DRIVER_NAME ".%d", id);
        dw->misc_dev.name = kstrdup(name, GFP_KERNEL);
        if (!dw->misc_dev.name) {
                err = -ENOMEM;
                goto err_ida_remove;
        }

        dw->misc_dev.minor = MISC_DYNAMIC_MINOR;
        dw->misc_dev.parent = dev;
        dw->misc_dev.groups = xdata_groups;

        writel(0x0, &(__dw_regs(dw)->RAM_addr));
        writel(0x0, &(__dw_regs(dw)->RAM_port));

        addr = dw->rg_region.paddr + DW_XDATA_EP_MEM_OFFSET;
        writel(lower_32_bits(addr), &(__dw_regs(dw)->addr_lsb));
        writel(upper_32_bits(addr), &(__dw_regs(dw)->addr_msb));
        dev_dbg(dev, "xData: target address = 0x%.16llx\n", addr);

        dev_dbg(dev, "xData: wr_len = %zu, rd_len = %zu\n",
                dw->max_wr_len * 4, dw->max_rd_len * 4);

        /* Saving data structure reference */
        pci_set_drvdata(pdev, dw);

        /* Register misc device */
        err = misc_register(&dw->misc_dev);
        if (err) {
                dev_err(dev, "xData: failed to register device\n");
                goto err_kfree_name;
        }

        return 0;

err_kfree_name:
        kfree(dw->misc_dev.name);

err_ida_remove:
        ida_free(&xdata_ida, id);

        return err;
}

static void dw_xdata_pcie_remove(struct pci_dev *pdev)
{
        struct dw_xdata *dw = pci_get_drvdata(pdev);
        int id;

        if (sscanf(dw->misc_dev.name, DW_XDATA_DRIVER_NAME ".%d", &id) != 1)
                return;

        if (id < 0)
                return;

        dw_xdata_stop(dw);
        misc_deregister(&dw->misc_dev);
        kfree(dw->misc_dev.name);
        ida_free(&xdata_ida, id);
}

static const struct pci_device_id dw_xdata_pcie_id_table[] = {
        { PCI_DEVICE_DATA(SYNOPSYS, EDDA, NULL) },
        { }
};
MODULE_DEVICE_TABLE(pci, dw_xdata_pcie_id_table);

static struct pci_driver dw_xdata_pcie_driver = {
        .name           = DW_XDATA_DRIVER_NAME,
        .id_table       = dw_xdata_pcie_id_table,
        .probe          = dw_xdata_pcie_probe,
        .remove         = dw_xdata_pcie_remove,
};

module_pci_driver(dw_xdata_pcie_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Synopsys DesignWare xData PCIe driver");
MODULE_AUTHOR("Gustavo Pimentel <gustavo.pimentel@synopsys.com>");