aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/kpc2000/kpc2000
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-04-19 20:42:05 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2019-04-20 23:04:58 +0200
commit7dc7967fc39af81191558f63eeaf3d2b83899b1c (patch)
treeed3a6b846d8fb6e914dcec329f2afc02568f3617 /drivers/staging/kpc2000/kpc2000
parentStaging: rtlwifi: Replace return type (diff)
downloadlinux-dev-7dc7967fc39af81191558f63eeaf3d2b83899b1c.tar.xz
linux-dev-7dc7967fc39af81191558f63eeaf3d2b83899b1c.zip
staging: kpc2000: add initial set of Daktronics drivers
These drivers have been outside of the kernel tree since the 2.x days, and it's time to bring them into the tree so they can get properly cleaned up. This first dump of drivers is based on a tarball Matt gave to me, minus an odd "dma" driver that I could not get to build at all. I renamed a few files, added the proper SPDX lines to it, added Kconfig entries and tied it into the kernel build. I also fixed up a number of initial obvious kernel build warnings, but left the odd bitfield warning that gcc is spitting out, as I'm not quite sure what to do about that. There's loads of low-hanging coding style cleanups in here for people to start attacking, as well as the more obvious logic and api cleanups as well. Cc: Matt Sickler <Matt.Sickler@daktronics.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging/kpc2000/kpc2000')
-rw-r--r--drivers/staging/kpc2000/kpc2000/Makefile4
-rw-r--r--drivers/staging/kpc2000/kpc2000/cell_probe.c466
-rw-r--r--drivers/staging/kpc2000/kpc2000/core.c436
-rw-r--r--drivers/staging/kpc2000/kpc2000/dma_common_defs.h43
-rw-r--r--drivers/staging/kpc2000/kpc2000/fileops.c128
-rw-r--r--drivers/staging/kpc2000/kpc2000/kp2000_module.c55
-rw-r--r--drivers/staging/kpc2000/kpc2000/pcie.h112
-rw-r--r--drivers/staging/kpc2000/kpc2000/uapi.h22
8 files changed, 1266 insertions, 0 deletions
diff --git a/drivers/staging/kpc2000/kpc2000/Makefile b/drivers/staging/kpc2000/kpc2000/Makefile
new file mode 100644
index 000000000000..28ab1e185f9f
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-m := kpc2000.o
+kpc2000-objs += kp2000_module.o core.o cell_probe.o fileops.o
diff --git a/drivers/staging/kpc2000/kpc2000/cell_probe.c b/drivers/staging/kpc2000/kpc2000/cell_probe.c
new file mode 100644
index 000000000000..ad2cc0a3bfa1
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/cell_probe.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/uio_driver.h>
+#include "pcie.h"
+
+/* Core (Resource) Table Layout:
+ * one Resource per record (8 bytes)
+ * 6 5 4 3 2 1 0
+ * 3210987654321098765432109876543210987654321098765432109876543210
+ * IIIIIIIIIIII Core Type [up to 4095 types]
+ * D S2C DMA Present
+ * DDD S2C DMA Channel Number [up to 8 channels]
+ * LLLLLLLLLLLLLLLL Register Count (64-bit registers) [up to 65535 registers]
+ * OOOOOOOOOOOOOOOO Core Offset (in 4kB blocks) [up to 65535 cores]
+ * D C2S DMA Present
+ * DDD C2S DMA Channel Number [up to 8 channels]
+ * II IRQ Count [0 to 3 IRQs per core]
+ 1111111000
+ * IIIIIII IRQ Base Number [up to 128 IRQs per card]
+ * ___ Spare
+ *
+ */
+
+#define KPC_OLD_DMA_CH_NUM(present, channel) ((present) ? (0x8 | ((channel) & 0x7)) : 0)
+#define KPC_OLD_S2C_DMA_CH_NUM(cte) KPC_OLD_DMA_CH_NUM(cte.s2c_dma_present, cte.s2c_dma_channel_num)
+#define KPC_OLD_C2S_DMA_CH_NUM(cte) KPC_OLD_DMA_CH_NUM(cte.c2s_dma_present, cte.c2s_dma_channel_num)
+
+#define KP_CORE_ID_INVALID 0
+#define KP_CORE_ID_I2C 3
+#define KP_CORE_ID_SPI 5
+
+struct core_table_entry {
+ u16 type;
+ u32 offset;
+ u32 length;
+ bool s2c_dma_present;
+ u8 s2c_dma_channel_num;
+ bool c2s_dma_present;
+ u8 c2s_dma_channel_num;
+ u8 irq_count;
+ u8 irq_base_num;
+};
+
+static
+void parse_core_table_entry_v0(struct core_table_entry *cte, const u64 read_val)
+{
+ cte->type = ((read_val & 0xFFF0000000000000) >> 52);
+ cte->offset = ((read_val & 0x00000000FFFF0000) >> 16) * 4096;
+ cte->length = ((read_val & 0x0000FFFF00000000) >> 32) * 8;
+ cte->s2c_dma_present = ((read_val & 0x0008000000000000) >> 51);
+ cte->s2c_dma_channel_num = ((read_val & 0x0007000000000000) >> 48);
+ cte->c2s_dma_present = ((read_val & 0x0000000000008000) >> 15);
+ cte->c2s_dma_channel_num = ((read_val & 0x0000000000007000) >> 12);
+ cte->irq_count = ((read_val & 0x0000000000000C00) >> 10);
+ cte->irq_base_num = ((read_val & 0x00000000000003F8) >> 3);
+}
+
+static
+void dbg_cte(struct kp2000_device *pcard, struct core_table_entry *cte)
+{
+ dev_dbg(&pcard->pdev->dev, "CTE: type:%3d offset:%3d (%3d) length:%3d (%3d) s2c:%d c2s:%d irq_count:%d base_irq:%d\n",
+ cte->type,
+ cte->offset,
+ cte->offset / 4096,
+ cte->length,
+ cte->length / 8,
+ (cte->s2c_dma_present ? cte->s2c_dma_channel_num : -1),
+ (cte->c2s_dma_present ? cte->c2s_dma_channel_num : -1),
+ cte->irq_count,
+ cte->irq_base_num
+ );
+}
+
+static
+void parse_core_table_entry(struct core_table_entry *cte, const u64 read_val, const u8 entry_rev)
+{
+ switch (entry_rev) {
+ case 0: parse_core_table_entry_v0(cte, read_val); break;
+ default: cte->type = 0; break;
+ }
+}
+
+
+int probe_core_basic(unsigned int core_num, struct kp2000_device *pcard, char *name, const struct core_table_entry cte)
+{
+ struct mfd_cell cell = {0};
+ struct resource resources[2] = {0};
+
+ struct kpc_core_device_platdata core_pdata = {
+ .card_id = pcard->card_id,
+ .build_version = pcard->build_version,
+ .hardware_revision = pcard->hardware_revision,
+ .ssid = pcard->ssid,
+ .ddna = pcard->ddna,
+ };
+
+ dev_dbg(&pcard->pdev->dev, "Found Basic core: type = %02d dma = %02x / %02x offset = 0x%x length = 0x%x (%d regs)\n", cte.type, KPC_OLD_S2C_DMA_CH_NUM(cte), KPC_OLD_C2S_DMA_CH_NUM(cte), cte.offset, cte.length, cte.length / 8);
+
+
+ cell.platform_data = &core_pdata;
+ cell.pdata_size = sizeof(struct kpc_core_device_platdata);
+ cell.name = name;
+ cell.id = core_num;
+ cell.num_resources = 2;
+
+ resources[0].start = cte.offset;
+ resources[0].end = cte.offset + (cte.length - 1);
+ resources[0].flags = IORESOURCE_MEM;
+
+ resources[1].start = pcard->pdev->irq;
+ resources[1].end = pcard->pdev->irq;
+ resources[1].flags = IORESOURCE_IRQ;
+
+ cell.resources = resources;
+
+ return mfd_add_devices(
+ PCARD_TO_DEV(pcard), // parent
+ pcard->card_num * 100, // id
+ &cell, // struct mfd_cell *
+ 1, // ndevs
+ &pcard->regs_base_resource,
+ 0, // irq_base
+ NULL // struct irq_domain *
+ );
+}
+
+
+struct kpc_uio_device {
+ struct list_head list;
+ struct kp2000_device *pcard;
+ struct device *dev;
+ struct uio_info uioinfo;
+ struct core_table_entry cte;
+ u16 core_num;
+};
+
+static ssize_t show_attr(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct kpc_uio_device *kudev = dev_get_drvdata(dev);
+
+ #define ATTR_NAME_CMP(v) (strcmp(v, attr->attr.name) == 0)
+ if ATTR_NAME_CMP("offset"){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.offset);
+ } else if ATTR_NAME_CMP("size"){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.length);
+ } else if ATTR_NAME_CMP("type"){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.type);
+ }
+ else if ATTR_NAME_CMP("s2c_dma"){
+ if (kudev->cte.s2c_dma_present){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.s2c_dma_channel_num);
+ } else {
+ return scnprintf(buf, PAGE_SIZE, "not present\n");
+ }
+ } else if ATTR_NAME_CMP("c2s_dma"){
+ if (kudev->cte.c2s_dma_present){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.c2s_dma_channel_num);
+ } else {
+ return scnprintf(buf, PAGE_SIZE, "not present\n");
+ }
+ }
+ else if ATTR_NAME_CMP("irq_count"){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.irq_count);
+ } else if ATTR_NAME_CMP("irq_base_num"){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->cte.irq_base_num);
+ } else if ATTR_NAME_CMP("core_num"){
+ return scnprintf(buf, PAGE_SIZE, "%u\n", kudev->core_num);
+ } else {
+ return 0;
+ }
+ #undef ATTR_NAME_CMP
+}
+
+
+DEVICE_ATTR(offset, 0444, show_attr, NULL);
+DEVICE_ATTR(size, 0444, show_attr, NULL);
+DEVICE_ATTR(type, 0444, show_attr, NULL);
+DEVICE_ATTR(s2c_dma_ch, 0444, show_attr, NULL);
+DEVICE_ATTR(c2s_dma_ch, 0444, show_attr, NULL);
+DEVICE_ATTR(s2c_dma, 0444, show_attr, NULL);
+DEVICE_ATTR(c2s_dma, 0444, show_attr, NULL);
+DEVICE_ATTR(irq_count, 0444, show_attr, NULL);
+DEVICE_ATTR(irq_base_num, 0444, show_attr, NULL);
+DEVICE_ATTR(core_num, 0444, show_attr, NULL);
+struct attribute * kpc_uio_class_attrs[] = {
+ &dev_attr_offset.attr,
+ &dev_attr_size.attr,
+ &dev_attr_type.attr,
+ &dev_attr_s2c_dma_ch.attr,
+ &dev_attr_c2s_dma_ch.attr,
+ &dev_attr_s2c_dma.attr,
+ &dev_attr_c2s_dma.attr,
+ &dev_attr_irq_count.attr,
+ &dev_attr_irq_base_num.attr,
+ &dev_attr_core_num.attr,
+ NULL,
+};
+
+
+static
+int kp2000_check_uio_irq(struct kp2000_device *pcard, u32 irq_num)
+{
+ u64 interrupt_active = readq(pcard->sysinfo_regs_base + REG_INTERRUPT_ACTIVE);
+ u64 interrupt_mask_inv = ~readq(pcard->sysinfo_regs_base + REG_INTERRUPT_MASK);
+ u64 irq_check_mask = (1 << irq_num);
+ if (interrupt_active & irq_check_mask){ // if it's active (interrupt pending)
+ if (interrupt_mask_inv & irq_check_mask){ // and if it's not masked off
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static
+irqreturn_t kuio_handler(int irq, struct uio_info *uioinfo)
+{
+ struct kpc_uio_device *kudev = uioinfo->priv;
+ if (irq != kudev->pcard->pdev->irq)
+ return IRQ_NONE;
+
+ if (kp2000_check_uio_irq(kudev->pcard, kudev->cte.irq_base_num)){
+ writeq((1 << kudev->cte.irq_base_num), kudev->pcard->sysinfo_regs_base + REG_INTERRUPT_ACTIVE); // Clear the active flag
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static
+int kuio_irqcontrol(struct uio_info *uioinfo, s32 irq_on)
+{
+ struct kpc_uio_device *kudev = uioinfo->priv;
+ struct kp2000_device *pcard = kudev->pcard;
+ u64 mask;
+
+ lock_card(pcard);
+ mask = readq(pcard->sysinfo_regs_base + REG_INTERRUPT_MASK);
+ if (irq_on){
+ mask &= ~(1 << (kudev->cte.irq_base_num));
+ } else {
+ mask |= (1 << (kudev->cte.irq_base_num));
+ }
+ writeq(mask, pcard->sysinfo_regs_base + REG_INTERRUPT_MASK);
+ unlock_card(pcard);
+
+ return 0;
+}
+
+int probe_core_uio(unsigned int core_num, struct kp2000_device *pcard, char *name, const struct core_table_entry cte)
+{
+ struct kpc_uio_device *kudev;
+ int rv;
+
+ dev_dbg(&pcard->pdev->dev, "Found UIO core: type = %02d dma = %02x / %02x offset = 0x%x length = 0x%x (%d regs)\n", cte.type, KPC_OLD_S2C_DMA_CH_NUM(cte), KPC_OLD_C2S_DMA_CH_NUM(cte), cte.offset, cte.length, cte.length / 8);
+
+ kudev = kzalloc(sizeof(struct kpc_uio_device), GFP_KERNEL);
+ if (!kudev){
+ dev_err(&pcard->pdev->dev, "probe_core_uio: failed to kzalloc kpc_uio_device\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&kudev->list);
+ kudev->pcard = pcard;
+ kudev->cte = cte;
+ kudev->core_num = core_num;
+
+ kudev->uioinfo.priv = kudev;
+ kudev->uioinfo.name = name;
+ kudev->uioinfo.version = "0.0";
+ if (cte.irq_count > 0){
+ kudev->uioinfo.irq_flags = IRQF_SHARED;
+ kudev->uioinfo.irq = pcard->pdev->irq;
+ kudev->uioinfo.handler = kuio_handler;
+ kudev->uioinfo.irqcontrol = kuio_irqcontrol;
+ } else {
+ kudev->uioinfo.irq = 0;
+ }
+
+ kudev->uioinfo.mem[0].name = "uiomap";
+ kudev->uioinfo.mem[0].addr = pci_resource_start(pcard->pdev, REG_BAR) + cte.offset;
+ kudev->uioinfo.mem[0].size = (cte.length + PAGE_SIZE-1) & ~(PAGE_SIZE-1); // Round up to nearest PAGE_SIZE boundary
+ kudev->uioinfo.mem[0].memtype = UIO_MEM_PHYS;
+
+ kudev->dev = device_create(kpc_uio_class, &pcard->pdev->dev, MKDEV(0,0), kudev, "%s.%d.%d.%d", kudev->uioinfo.name, pcard->card_num, cte.type, kudev->core_num);
+ if (IS_ERR(kudev->dev)) {
+ dev_err(&pcard->pdev->dev, "probe_core_uio device_create failed!\n");
+ return -ENODEV;
+ }
+ dev_set_drvdata(kudev->dev, kudev);
+
+ rv = uio_register_device(kudev->dev, &kudev->uioinfo);
+ if (rv){
+ dev_err(&pcard->pdev->dev, "probe_core_uio failed uio_register_device: %d\n", rv);
+ return rv;
+ }
+
+ list_add_tail(&kudev->list, &pcard->uio_devices_list);
+
+ return 0;
+}
+
+
+static int create_dma_engine_core(struct kp2000_device *pcard, size_t engine_regs_offset, int engine_num, int irq_num)
+{
+ struct mfd_cell cell = {0};
+ struct resource resources[2] = {0};
+
+ dev_dbg(&pcard->pdev->dev, "create_dma_core(pcard = [%p], engine_regs_offset = %zx, engine_num = %d)\n", pcard, engine_regs_offset, engine_num);
+
+ cell.platform_data = NULL;
+ cell.pdata_size = 0;
+ cell.id = engine_num;
+ cell.name = KP_DRIVER_NAME_DMA_CONTROLLER;
+ cell.num_resources = 2;
+
+ resources[0].start = engine_regs_offset;
+ resources[0].end = engine_regs_offset + (KPC_DMA_ENGINE_SIZE - 1);
+ resources[0].flags = IORESOURCE_MEM;
+
+ resources[1].start = irq_num;
+ resources[1].end = irq_num;
+ resources[1].flags = IORESOURCE_IRQ;
+
+ cell.resources = resources;
+
+ return mfd_add_devices(
+ PCARD_TO_DEV(pcard), // parent
+ pcard->card_num * 100, // id
+ &cell, // struct mfd_cell *
+ 1, // ndevs
+ &pcard->dma_base_resource,
+ 0, // irq_base
+ NULL // struct irq_domain *
+ );
+}
+
+static int kp2000_setup_dma_controller(struct kp2000_device *pcard)
+{
+ int err;
+ unsigned int i;
+ u64 capabilities_reg;
+
+ // S2C Engines
+ for (i = 0 ; i < 32 ; i++){
+ capabilities_reg = readq( pcard->dma_bar_base + KPC_DMA_S2C_BASE_OFFSET + (KPC_DMA_ENGINE_SIZE * i) );
+ if (capabilities_reg & ENGINE_CAP_PRESENT_MASK){
+ err = create_dma_engine_core(pcard, (KPC_DMA_S2C_BASE_OFFSET + (KPC_DMA_ENGINE_SIZE * i)), i, pcard->pdev->irq);
+ if (err) goto err_out;
+ }
+ }
+ // C2S Engines
+ for (i = 0 ; i < 32 ; i++){
+ capabilities_reg = readq( pcard->dma_bar_base + KPC_DMA_C2S_BASE_OFFSET + (KPC_DMA_ENGINE_SIZE * i) );
+ if (capabilities_reg & ENGINE_CAP_PRESENT_MASK){
+ err = create_dma_engine_core(pcard, (KPC_DMA_C2S_BASE_OFFSET + (KPC_DMA_ENGINE_SIZE * i)), 32+i, pcard->pdev->irq);
+ if (err) goto err_out;
+ }
+ }
+
+ return 0;
+
+err_out:
+ dev_err(&pcard->pdev->dev, "kp2000_setup_dma_controller: failed to add a DMA Engine: %d\n", err);
+ return err;
+}
+
+int kp2000_probe_cores(struct kp2000_device *pcard)
+{
+ int err = 0;
+ int i;
+ int current_type_id;
+ u64 read_val;
+ unsigned int highest_core_id = 0;
+ struct core_table_entry cte;
+
+ dev_dbg(&pcard->pdev->dev, "kp2000_probe_cores(pcard = %p / %d)\n", pcard, pcard->card_num);
+
+ err = kp2000_setup_dma_controller(pcard);
+ if (err) return err;
+
+ INIT_LIST_HEAD(&pcard->uio_devices_list);
+
+ // First, iterate the core table looking for the highest CORE_ID
+ for (i = 0 ; i < pcard->core_table_length ; i++){
+ read_val = readq(pcard->sysinfo_regs_base + ((pcard->core_table_offset + i) * 8));
+ parse_core_table_entry(&cte, read_val, pcard->core_table_rev);
+ dbg_cte(pcard, &cte);
+ if (cte.type > highest_core_id){
+ highest_core_id = cte.type;
+ }
+ if (cte.type == KP_CORE_ID_INVALID){
+ dev_info(&pcard->pdev->dev, "Found Invalid core: %016llx\n", read_val);
+ }
+ }
+ // Then, iterate over the possible core types.
+ for (current_type_id = 1 ; current_type_id <= highest_core_id ; current_type_id++){
+ unsigned int core_num = 0;
+ // Foreach core type, iterate the whole table and instantiate subdevices for each core.
+ // Yes, this is O(n*m) but the actual runtime is small enough that it's an acceptable tradeoff.
+ for (i = 0 ; i < pcard->core_table_length ; i++){
+ read_val = readq(pcard->sysinfo_regs_base + ((pcard->core_table_offset + i) * 8));
+ parse_core_table_entry(&cte, read_val, pcard->core_table_rev);
+
+ if (cte.type == current_type_id){
+ switch (cte.type){
+ case KP_CORE_ID_I2C:
+ err = probe_core_basic(core_num, pcard, KP_DRIVER_NAME_I2C, cte);
+ break;
+
+ case KP_CORE_ID_SPI:
+ err = probe_core_basic(core_num, pcard, KP_DRIVER_NAME_SPI, cte);
+ break;
+
+ default:
+ err = probe_core_uio(core_num, pcard, "kpc_uio", cte);
+ break;
+ }
+ if (err){
+ dev_err(&pcard->pdev->dev, "kp2000_probe_cores: failed to add core %d: %d\n", i, err);
+ return err;
+ }
+ core_num++;
+ }
+ }
+ }
+
+ // Finally, instantiate a UIO device for the core_table.
+ cte.type = 0; // CORE_ID_BOARD_INFO
+ cte.offset = 0; // board info is always at the beginning
+ cte.length = 512*8;
+ cte.s2c_dma_present = false;
+ cte.s2c_dma_channel_num = 0;
+ cte.c2s_dma_present = false;
+ cte.c2s_dma_channel_num = 0;
+ cte.irq_count = 0;
+ cte.irq_base_num = 0;
+ err = probe_core_uio(0, pcard, "kpc_uio", cte);
+ if (err){
+ dev_err(&pcard->pdev->dev, "kp2000_probe_cores: failed to add board_info core: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+void kp2000_remove_cores(struct kp2000_device *pcard)
+{
+ struct list_head *ptr;
+ struct list_head *next;
+ list_for_each_safe(ptr, next, &pcard->uio_devices_list){
+ struct kpc_uio_device *kudev = list_entry(ptr, struct kpc_uio_device, list);
+ uio_unregister_device(&kudev->uioinfo);
+ device_unregister(kudev->dev);
+ list_del(&kudev->list);
+ kfree(kudev);
+ }
+}
+
diff --git a/drivers/staging/kpc2000/kpc2000/core.c b/drivers/staging/kpc2000/kpc2000/core.c
new file mode 100644
index 000000000000..35b87cdc4fb0
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/core.c
@@ -0,0 +1,436 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <asm/io.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/jiffies.h>
+#include "pcie.h"
+
+
+/*******************************************************
+ * SysFS Attributes
+ ******************************************************/
+static ssize_t show_attr(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct kp2000_device *pcard;
+
+ if (!pdev) return -ENXIO;
+ pcard = pci_get_drvdata(pdev);
+ if (!pcard) return -ENXIO;
+
+ if (strcmp("ssid", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%016llx\n", pcard->ssid); } else
+ if (strcmp("ddna", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%016llx\n", pcard->ddna); } else
+ if (strcmp("card_id", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%08x\n", pcard->card_id); } else
+ if (strcmp("hw_rev", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%08x\n", pcard->hardware_revision); } else
+ if (strcmp("build", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%08x\n", pcard->build_version); } else
+ if (strcmp("build_date", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%08x\n", pcard->build_datestamp); } else
+ if (strcmp("build_time", attr->attr.name) == 0){ return scnprintf(buf, PAGE_SIZE, "%08x\n", pcard->build_timestamp); } else
+ { return -ENXIO; }
+}
+
+static ssize_t show_cpld_config_reg(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct kp2000_device *pcard;
+ u64 val;
+
+ if (!pdev)
+ return -ENXIO;
+
+ pcard = pci_get_drvdata(pdev);
+ if (!pcard)
+ return -ENXIO;
+
+ val = readq(pcard->sysinfo_regs_base + REG_CPLD_CONFIG);
+ return scnprintf(buf, PAGE_SIZE, "%016llx\n", val);
+}
+static ssize_t cpld_reconfigure(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ long wr_val;
+ struct kp2000_device *pcard;
+ int rv;
+
+ if (!pdev) return -ENXIO;
+ pcard = pci_get_drvdata(pdev);
+ if (!pcard) return -ENXIO;
+
+ rv = kstrtol(buf, 0, &wr_val);
+ if (rv < 0) return rv;
+ if (wr_val > 7) return -EINVAL;
+
+ wr_val = wr_val << 8;
+ wr_val |= 0x1; // Set the "Configure Go" bit
+ writeq(wr_val, pcard->sysinfo_regs_base + REG_CPLD_CONFIG);
+ return count;
+}
+
+
+DEVICE_ATTR(ssid, 0444, show_attr, NULL);
+DEVICE_ATTR(ddna, 0444, show_attr, NULL);
+DEVICE_ATTR(card_id, 0444, show_attr, NULL);
+DEVICE_ATTR(hw_rev, 0444, show_attr, NULL);
+DEVICE_ATTR(build, 0444, show_attr, NULL);
+DEVICE_ATTR(build_date, 0444, show_attr, NULL);
+DEVICE_ATTR(build_time, 0444, show_attr, NULL);
+DEVICE_ATTR(cpld_reg, 0444, show_cpld_config_reg, NULL);
+DEVICE_ATTR(cpld_reconfigure, 0220, NULL, cpld_reconfigure);
+
+static const struct attribute * kp_attr_list[] = {
+ &dev_attr_ssid.attr,
+ &dev_attr_ddna.attr,
+ &dev_attr_card_id.attr,
+ &dev_attr_hw_rev.attr,
+ &dev_attr_build.attr,
+ &dev_attr_build_date.attr,
+ &dev_attr_build_time.attr,
+ &dev_attr_cpld_reg.attr,
+ &dev_attr_cpld_reconfigure.attr,
+ NULL,
+};
+
+
+/*******************************************************
+ * Functions
+ ******************************************************/
+
+static void wait_and_read_ssid(struct kp2000_device *pcard)
+{
+ u64 read_val = readq(pcard->sysinfo_regs_base + REG_FPGA_SSID);
+ unsigned long timeout;
+
+ if (read_val & 0x8000000000000000){
+ pcard->ssid = read_val;
+ return;
+ }
+
+ timeout = jiffies + (HZ * 2);
+ do {
+ read_val = readq(pcard->sysinfo_regs_base + REG_FPGA_SSID);
+ if (read_val & 0x8000000000000000){
+ pcard->ssid = read_val;
+ return;
+ }
+ cpu_relax();
+ //schedule();
+ } while (time_before(jiffies, timeout));
+
+ dev_notice(&pcard->pdev->dev, "SSID didn't show up!\n");
+
+ #if 0
+ // Timed out waiting for the SSID to show up, just use the DDNA instead?
+ read_val = readq(pcard->sysinfo_regs_base + REG_FPGA_DDNA);
+ pcard->ssid = read_val;
+ #else
+ // Timed out waiting for the SSID to show up, stick all zeros in the value
+ pcard->ssid = 0;
+ #endif
+}
+
+static int read_system_regs(struct kp2000_device *pcard)
+{
+ u64 read_val;
+
+ read_val = readq(pcard->sysinfo_regs_base + REG_MAGIC_NUMBER);
+ if (read_val != KP2000_MAGIC_VALUE){
+ dev_err(&pcard->pdev->dev, "Invalid magic! Got: 0x%016llx Want: 0x%016lx\n", read_val, KP2000_MAGIC_VALUE);
+ return -EILSEQ;
+ }
+
+ read_val = readq(pcard->sysinfo_regs_base + REG_CARD_ID_AND_BUILD);
+ pcard->card_id = (read_val & 0xFFFFFFFF00000000) >> 32;
+ pcard->build_version = (read_val & 0x00000000FFFFFFFF) >> 0;
+
+ read_val = readq(pcard->sysinfo_regs_base + REG_DATE_AND_TIME_STAMPS);
+ pcard->build_datestamp = (read_val & 0xFFFFFFFF00000000) >> 32;
+ pcard->build_timestamp = (read_val & 0x00000000FFFFFFFF) >> 0;
+
+ read_val = readq(pcard->sysinfo_regs_base + REG_CORE_TABLE_OFFSET);
+ pcard->core_table_length = (read_val & 0xFFFFFFFF00000000) >> 32;
+ pcard->core_table_offset = (read_val & 0x00000000FFFFFFFF) >> 0;
+
+ wait_and_read_ssid(pcard);
+
+ read_val = readq(pcard->sysinfo_regs_base + REG_FPGA_HW_ID);
+ pcard->core_table_rev = (read_val & 0x0000000000000F00) >> 8;
+ pcard->hardware_revision = (read_val & 0x000000000000001F);
+
+ read_val = readq(pcard->sysinfo_regs_base + REG_FPGA_DDNA);
+ pcard->ddna = read_val;
+
+ dev_info(&pcard->pdev->dev, "system_regs: %08x %08x %08x %08x %02x %d %d %016llx %016llx\n",
+ pcard->card_id,
+ pcard->build_version,
+ pcard->build_datestamp,
+ pcard->build_timestamp,
+ pcard->hardware_revision,
+ pcard->core_table_rev,
+ pcard->core_table_length,
+ pcard->ssid,
+ pcard->ddna
+ );
+
+ if (pcard->core_table_rev > 1){
+ dev_err(&pcard->pdev->dev, "core table entry revision is higher than we can deal with, cannot continue with this card!\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+irqreturn_t kp2000_irq_handler(int irq, void *dev_id)
+{
+ struct kp2000_device *pcard = (struct kp2000_device*)dev_id;
+ SetBackEndControl(pcard->dma_common_regs, KPC_DMA_CARD_IRQ_ENABLE | KPC_DMA_CARD_USER_INTERRUPT_MODE | KPC_DMA_CARD_USER_INTERRUPT_ACTIVE);
+ return IRQ_HANDLED;
+}
+
+int kp2000_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ int err = 0;
+ struct kp2000_device *pcard;
+ static int card_count = 1;
+ int rv;
+ unsigned long reg_bar_phys_addr;
+ unsigned long reg_bar_phys_len;
+ unsigned long dma_bar_phys_addr;
+ unsigned long dma_bar_phys_len;
+ u16 regval;
+
+ dev_dbg(&pdev->dev, "kp2000_pcie_probe(pdev = [%p], id = [%p])\n", pdev, id);
+
+ //{ Step 1: Allocate a struct for the pcard
+ pcard = kzalloc(sizeof(struct kp2000_device), GFP_KERNEL);
+ if (NULL == pcard){
+ dev_err(&pdev->dev, "probe: failed to allocate private card data\n");
+ return -ENOMEM;
+ }
+ dev_dbg(&pdev->dev, "probe: allocated struct kp2000_device @ %p\n", pcard);
+ //}
+
+ //{ Step 2: Initialize trivial pcard elements
+ pcard->card_num = card_count;
+ card_count++;
+ scnprintf(pcard->name, 16, "kpcard%d", pcard->card_num);
+
+ mutex_init(&pcard->sem);
+ lock_card(pcard);
+
+ pcard->pdev = pdev;
+ pci_set_drvdata(pdev, pcard);
+ //}
+
+ //{ Step 3: Enable PCI device
+ err = pci_enable_device(pcard->pdev);
+ if (err){
+ dev_err(&pcard->pdev->dev, "probe: failed to enable PCIE2000 PCIe device (%d)\n", err);
+ goto out3;
+ }
+ //}
+
+ //{ Step 4: Setup the Register BAR
+ reg_bar_phys_addr = pci_resource_start(pcard->pdev, REG_BAR);
+ reg_bar_phys_len = pci_resource_len(pcard->pdev, REG_BAR);
+
+ pcard->regs_bar_base = ioremap_nocache(reg_bar_phys_addr, PAGE_SIZE);
+ if (NULL == pcard->regs_bar_base){
+ dev_err(&pcard->pdev->dev, "probe: REG_BAR could not remap memory to virtual space\n");
+ err = -ENODEV;
+ goto out4;
+ }
+ dev_dbg(&pcard->pdev->dev, "probe: REG_BAR virt hardware address start [%p]\n", pcard->regs_bar_base);
+
+ err = pci_request_region(pcard->pdev, REG_BAR, KP_DRIVER_NAME_KP2000);
+ if (err){
+ iounmap(pcard->regs_bar_base);
+ dev_err(&pcard->pdev->dev, "probe: failed to acquire PCI region (%d)\n", err);
+ err = -ENODEV;
+ goto out4;
+ }
+
+ pcard->regs_base_resource.start = reg_bar_phys_addr;
+ pcard->regs_base_resource.end = reg_bar_phys_addr + reg_bar_phys_len - 1;
+ pcard->regs_base_resource.flags = IORESOURCE_MEM;
+ //}
+
+ //{ Step 5: Setup the DMA BAR
+ dma_bar_phys_addr = pci_resource_start(pcard->pdev, DMA_BAR);
+ dma_bar_phys_len = pci_resource_len(pcard->pdev, DMA_BAR);
+
+ pcard->dma_bar_base = ioremap_nocache(dma_bar_phys_addr, dma_bar_phys_len);
+ if (NULL == pcard->dma_bar_base){
+ dev_err(&pcard->pdev->dev, "probe: DMA_BAR could not remap memory to virtual space\n");
+ err = -ENODEV;
+ goto out5;
+ }
+ dev_dbg(&pcard->pdev->dev, "probe: DMA_BAR virt hardware address start [%p]\n", pcard->dma_bar_base);
+
+ pcard->dma_common_regs = pcard->dma_bar_base + KPC_DMA_COMMON_OFFSET;
+
+ err = pci_request_region(pcard->pdev, DMA_BAR, "kp2000_pcie");
+ if (err){
+ iounmap(pcard->dma_bar_base);
+ dev_err(&pcard->pdev->dev, "probe: failed to acquire PCI region (%d)\n", err);
+ err = -ENODEV;
+ goto out5;
+ }
+
+ pcard->dma_base_resource.start = dma_bar_phys_addr;
+ pcard->dma_base_resource.end = dma_bar_phys_addr + dma_bar_phys_len - 1;
+ pcard->dma_base_resource.flags = IORESOURCE_MEM;
+ //}
+
+ //{ Step 6: System Regs
+ pcard->sysinfo_regs_base = pcard->regs_bar_base;
+ err = read_system_regs(pcard);
+ if (err)
+ goto out6;
+
+ // Disable all "user" interrupts because they're not used yet.
+ writeq(0xFFFFFFFFFFFFFFFF, pcard->sysinfo_regs_base + REG_INTERRUPT_MASK);
+ //}
+
+ //{ Step 7: Configure PCI thingies
+ // let the card master PCIe
+ pci_set_master(pcard->pdev);
+ // enable IO and mem if not already done
+ pci_read_config_word(pcard->pdev, PCI_COMMAND, &regval);
+ regval |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
+ pci_write_config_word(pcard->pdev, PCI_COMMAND, regval);
+
+ // Clear relaxed ordering bit
+ pcie_capability_clear_and_set_word(pcard->pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN, 0);
+
+ // Set Max_Payload_Size and Max_Read_Request_Size
+ regval = (0x0) << 5; // Max_Payload_Size = 128 B
+ pcie_capability_clear_and_set_word(pcard->pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_PAYLOAD, regval);
+ regval = (0x0) << 12; // Max_Read_Request_Size = 128 B
+ pcie_capability_clear_and_set_word(pcard->pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_READRQ, regval);
+
+ // Enable error reporting for: Correctable Errors, Non-Fatal Errors, Fatal Errors, Unsupported Requests
+ pcie_capability_clear_and_set_word(pcard->pdev, PCI_EXP_DEVCTL, 0, PCI_EXP_DEVCTL_CERE | PCI_EXP_DEVCTL_NFERE | PCI_EXP_DEVCTL_FERE | PCI_EXP_DEVCTL_URRE);
+
+ err = dma_set_mask(PCARD_TO_DEV(pcard), DMA_BIT_MASK(64));
+ if (err){
+ dev_err(&pcard->pdev->dev, "CANNOT use DMA mask %0llx\n", DMA_BIT_MASK(64));
+ goto out7;
+ }
+ dev_dbg(&pcard->pdev->dev, "Using DMA mask %0llx\n", dma_get_mask(PCARD_TO_DEV(pcard)));
+ //}
+
+ //{ Step 8: Configure IRQs
+ err = pci_enable_msi(pcard->pdev);
+ if (err < 0)
+ goto out8a;
+
+ rv = request_irq(pcard->pdev->irq, kp2000_irq_handler, IRQF_SHARED, pcard->name, pcard);
+ if (rv){
+ dev_err(&pcard->pdev->dev, "kp2000_pcie_probe: failed to request_irq: %d\n", rv);
+ goto out8b;
+ }
+ //}
+
+ //{ Step 9: Setup sysfs attributes
+ err = sysfs_create_files(&(pdev->dev.kobj), kp_attr_list);
+ if (err){
+ dev_err(&pdev->dev, "Failed to add sysfs files: %d\n", err);
+ goto out9;
+ }
+ //}
+
+ //{ Step 10: Setup misc device
+ pcard->miscdev.minor = MISC_DYNAMIC_MINOR;
+ pcard->miscdev.fops = &kp2000_fops;
+ pcard->miscdev.parent = &pcard->pdev->dev;
+ pcard->miscdev.name = pcard->name;
+
+ err = misc_register(&pcard->miscdev);
+ if (err){
+ dev_err(&pcard->pdev->dev, "kp2000_pcie_probe: misc_register failed: %d\n", err);
+ goto out10;
+ }
+ //}
+
+ //{ Step 11: Probe cores
+ err = kp2000_probe_cores(pcard);
+ if (err)
+ goto out11;
+ //}
+
+ //{ Step 12: Enable IRQs in HW
+ SetBackEndControl(pcard->dma_common_regs, KPC_DMA_CARD_IRQ_ENABLE | KPC_DMA_CARD_USER_INTERRUPT_MODE);
+ //}
+
+ dev_dbg(&pcard->pdev->dev, "kp2000_pcie_probe() complete!\n");
+ unlock_card(pcard);
+ return 0;
+
+ out11:
+ misc_deregister(&pcard->miscdev);
+ out10:
+ sysfs_remove_files(&(pdev->dev.kobj), kp_attr_list);
+ out9:
+ free_irq(pcard->pdev->irq, pcard);
+ out8b:
+ pci_disable_msi(pcard->pdev);
+ out8a:
+ out7:
+ out6:
+ iounmap(pcard->dma_bar_base);
+ pci_release_region(pdev, DMA_BAR);
+ pcard->dma_bar_base = NULL;
+ out5:
+ iounmap(pcard->regs_bar_base);
+ pci_release_region(pdev, REG_BAR);
+ pcard->regs_bar_base = NULL;
+ out4:
+ pci_disable_device(pcard->pdev);
+ out3:
+ unlock_card(pcard);
+ kfree(pcard);
+ return err;
+}
+
+
+void kp2000_pcie_remove(struct pci_dev *pdev)
+{
+ struct kp2000_device *pcard = pci_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "kp2000_pcie_remove(pdev=%p)\n", pdev);
+
+ if (pcard == NULL) return;
+
+ lock_card(pcard);
+ kp2000_remove_cores(pcard);
+ mfd_remove_devices(PCARD_TO_DEV(pcard));
+ misc_deregister(&pcard->miscdev);
+ sysfs_remove_files(&(pdev->dev.kobj), kp_attr_list);
+ free_irq(pcard->pdev->irq, pcard);
+ pci_disable_msi(pcard->pdev);
+ if (pcard->dma_bar_base != NULL){
+ iounmap(pcard->dma_bar_base);
+ pci_release_region(pdev, DMA_BAR);
+ pcard->dma_bar_base = NULL;
+ }
+ if (pcard->regs_bar_base != NULL){
+ iounmap(pcard->regs_bar_base);
+ pci_release_region(pdev, REG_BAR);
+ pcard->regs_bar_base = NULL;
+ }
+ pci_disable_device(pcard->pdev);
+ pci_set_drvdata(pdev, NULL);
+ unlock_card(pcard);
+ kfree(pcard);
+}
diff --git a/drivers/staging/kpc2000/kpc2000/dma_common_defs.h b/drivers/staging/kpc2000/kpc2000/dma_common_defs.h
new file mode 100644
index 000000000000..f35e636b1fb7
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/dma_common_defs.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef KPC_DMA_COMMON_DEFS_H_
+#define KPC_DMA_COMMON_DEFS_H_
+
+#define KPC_DMA_COMMON_OFFSET 0x4000
+#define KPC_DMA_S2C_BASE_OFFSET 0x0000
+#define KPC_DMA_C2S_BASE_OFFSET 0x2000
+#define KPC_DMA_ENGINE_SIZE 0x0100
+#define ENGINE_CAP_PRESENT_MASK 0x1
+
+
+#define KPC_DMA_CARD_IRQ_ENABLE (1 << 0)
+#define KPC_DMA_CARD_IRQ_ACTIVE (1 << 1)
+#define KPC_DMA_CARD_IRQ_PENDING (1 << 2)
+#define KPC_DMA_CARD_IRQ_MSI (1 << 3)
+#define KPC_DMA_CARD_USER_INTERRUPT_MODE (1 << 4)
+#define KPC_DMA_CARD_USER_INTERRUPT_ACTIVE (1 << 5)
+#define KPC_DMA_CARD_IRQ_MSIX_MODE (1 << 6)
+#define KPC_DMA_CARD_MAX_PAYLOAD_SIZE_MASK 0x0700
+#define KPC_DMA_CARD_MAX_READ_REQUEST_SIZE_MASK 0x7000
+#define KPC_DMA_CARD_S2C_INTERRUPT_STATUS_MASK 0x00FF0000
+#define KPC_DMA_CARD_C2S_INTERRUPT_STATUS_MASK 0xFF000000
+
+static inline void SetBackEndControl(void __iomem *regs, u32 value)
+{
+ writel(value, regs + 0);
+}
+static inline u32 GetBackEndStatus(void __iomem *regs)
+{
+ return readl(regs + 0);
+}
+
+static inline u32 BackEndControlSetClear(void __iomem *regs, u32 set_bits, u32 clear_bits)
+{
+ u32 start_val = GetBackEndStatus(regs);
+ u32 new_val = start_val;
+ new_val &= ~clear_bits;
+ new_val |= set_bits;
+ SetBackEndControl(regs, new_val);
+ return start_val;
+}
+
+#endif /* KPC_DMA_COMMON_DEFS_H_ */
diff --git a/drivers/staging/kpc2000/kpc2000/fileops.c b/drivers/staging/kpc2000/kpc2000/fileops.c
new file mode 100644
index 000000000000..4bfba59570e6
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/fileops.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h> /* printk() */
+#include <linux/slab.h> /* kmalloc() */
+#include <linux/fs.h> /* everything... */
+#include <linux/errno.h> /* error codes */
+#include <linux/types.h> /* size_t */
+#include <linux/cdev.h>
+#include <linux/uaccess.h> /* copy_*_user */
+#include <linux/rwsem.h>
+#include <linux/idr.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include "pcie.h"
+#include "uapi.h"
+
+int kp2000_cdev_open(struct inode *inode, struct file *filp)
+{
+ struct kp2000_device *pcard = container_of(filp->private_data, struct kp2000_device, miscdev);
+
+ dev_dbg(&pcard->pdev->dev, "kp2000_cdev_open(filp = [%p], pcard = [%p])\n", filp, pcard);
+
+ filp->private_data = pcard; /* so other methods can access it */
+
+ return 0;
+}
+
+int kp2000_cdev_close(struct inode *inode, struct file *filp)
+{
+ struct kp2000_device *pcard = filp->private_data;
+
+ dev_dbg(&pcard->pdev->dev, "kp2000_cdev_close(filp = [%p], pcard = [%p])\n", filp, pcard);
+ return 0;
+}
+
+
+ssize_t kp2000_cdev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+ struct kp2000_device *pcard = filp->private_data;
+ int cnt = 0;
+ int ret;
+#define BUFF_CNT 1024
+ char buff[BUFF_CNT] = {0}; //NOTE: Increase this so it is at least as large as all the scnprintfs. And don't use unbounded strings. "%s"
+ //NOTE: also, this is a really shitty way to implement the read() call, but it will work for any size 'count'.
+
+ if (WARN(NULL == buf, "kp2000_cdev_read: buf is a NULL pointer!\n"))
+ return -EINVAL;
+
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Card ID : 0x%08x\n", pcard->card_id);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Build Version : 0x%08x\n", pcard->build_version);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Build Date : 0x%08x\n", pcard->build_datestamp);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Build Time : 0x%08x\n", pcard->build_timestamp);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Core Table Offset : 0x%08x\n", pcard->core_table_offset);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Core Table Length : 0x%08x\n", pcard->core_table_length);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "Hardware Revision : 0x%08x\n", pcard->hardware_revision);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "SSID : 0x%016llx\n", pcard->ssid);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "DDNA : 0x%016llx\n", pcard->ddna);
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "IRQ Mask : 0x%016llx\n", readq(pcard->sysinfo_regs_base + REG_INTERRUPT_MASK));
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "IRQ Active : 0x%016llx\n", readq(pcard->sysinfo_regs_base + REG_INTERRUPT_ACTIVE));
+ cnt += scnprintf(buff+cnt, BUFF_CNT-cnt, "CPLD : 0x%016llx\n", readq(pcard->sysinfo_regs_base + REG_CPLD_CONFIG));
+
+ if (*f_pos >= cnt)
+ return 0;
+
+ if (count > cnt)
+ count = cnt;
+
+ ret = copy_to_user(buf, buff + *f_pos, count);
+ if (ret)
+ return -EFAULT;
+ *f_pos += count;
+ return count;
+}
+
+ssize_t kp2000_cdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+ return -EINVAL;
+}
+
+long kp2000_cdev_ioctl(struct file *filp, unsigned int ioctl_num, unsigned long ioctl_param)
+{
+ struct kp2000_device *pcard = filp->private_data;
+
+ dev_dbg(&pcard->pdev->dev, "kp2000_cdev_ioctl(filp = [%p], ioctl_num = 0x%08x, ioctl_param = 0x%016lx) pcard = [%p]\n", filp, ioctl_num, ioctl_param, pcard);
+
+ switch (ioctl_num){
+ case KP2000_IOCTL_GET_CPLD_REG: return readq(pcard->sysinfo_regs_base + REG_CPLD_CONFIG);
+ case KP2000_IOCTL_GET_PCIE_ERROR_REG: return readq(pcard->sysinfo_regs_base + REG_PCIE_ERROR_COUNT);
+
+ case KP2000_IOCTL_GET_EVERYTHING: {
+ struct kp2000_regs temp;
+ int ret;
+ temp.card_id = pcard->card_id;
+ temp.build_version = pcard->build_version;
+ temp.build_datestamp = pcard->build_datestamp;
+ temp.build_timestamp = pcard->build_timestamp;
+ temp.hw_rev = pcard->hardware_revision;
+ temp.ssid = pcard->ssid;
+ temp.ddna = pcard->ddna;
+ temp.cpld_reg = readq(pcard->sysinfo_regs_base + REG_CPLD_CONFIG);
+
+ ret = copy_to_user((void*)ioctl_param, (void*)&temp, sizeof(temp));
+ if (ret)
+ return -EFAULT;
+
+ return sizeof(temp);
+ }
+
+ default:
+ return -ENOTTY;
+ }
+ return -ENOTTY;
+}
+
+
+struct file_operations kp2000_fops = {
+ .owner = THIS_MODULE,
+ .open = kp2000_cdev_open,
+ .release = kp2000_cdev_close,
+ .read = kp2000_cdev_read,
+ //.write = kp2000_cdev_write,
+ //.poll = kp2000_cdev_poll,
+ //.fasync = kp2000_cdev_fasync,
+ .llseek = noop_llseek,
+ .unlocked_ioctl = kp2000_cdev_ioctl,
+};
+
diff --git a/drivers/staging/kpc2000/kpc2000/kp2000_module.c b/drivers/staging/kpc2000/kpc2000/kp2000_module.c
new file mode 100644
index 000000000000..661b0b74ed66
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/kp2000_module.c
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <linux/io.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include "pcie.h"
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lee.Brooke@Daktronics.com, Matt.Sickler@Daktronics.com");
+MODULE_SOFTDEP("pre: uio post: kpc_nwl_dma kpc_i2c kpc_spi");
+
+struct class *kpc_uio_class;
+ATTRIBUTE_GROUPS(kpc_uio_class);
+
+static const struct pci_device_id kp2000_pci_device_ids[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_DAKTRONICS, PCI_DEVICE_ID_DAKTRONICS) },
+ { PCI_DEVICE(PCI_VENDOR_ID_DAKTRONICS, PCI_DEVICE_ID_DAKTRONICS_KADOKA_P2KR0) },
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, kp2000_pci_device_ids);
+
+static struct pci_driver kp2000_driver_inst = {
+ .name = "kp2000_pcie",
+ .id_table = kp2000_pci_device_ids,
+ .probe = kp2000_pcie_probe,
+ .remove = kp2000_pcie_remove
+};
+
+
+static int __init kp2000_pcie_init(void)
+{
+ kpc_uio_class = class_create(THIS_MODULE, "kpc_uio");
+ if (IS_ERR(kpc_uio_class))
+ return PTR_ERR(kpc_uio_class);
+
+ kpc_uio_class->dev_groups = kpc_uio_class_groups;
+ return pci_register_driver(&kp2000_driver_inst);
+}
+module_init(kp2000_pcie_init);
+
+static void __exit kp2000_pcie_exit(void)
+{
+ pci_unregister_driver(&kp2000_driver_inst);
+ class_destroy(kpc_uio_class);
+}
+module_exit(kp2000_pcie_exit);
diff --git a/drivers/staging/kpc2000/kpc2000/pcie.h b/drivers/staging/kpc2000/kpc2000/pcie.h
new file mode 100644
index 000000000000..893aebfd1449
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/pcie.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef KP2000_PCIE_H
+#define KP2000_PCIE_H
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include "../kpc.h"
+#include "dma_common_defs.h"
+
+
+/* System Register Map (BAR 1, Start Addr 0)
+ *
+ * BAR Size:
+ * 1048576 (0x100000) bytes = 131072 (0x20000) registers = 256 pages (4K)
+ *
+ * 6 5 4 3 2 1 0
+ * 3210987654321098765432109876543210987654321098765432109876543210
+ * 0 <--------------------------- MAGIC ---------------------------->
+ * 1 <----------- Card ID ---------><----------- Revision ---------->
+ * 2 <--------- Date Stamp --------><--------- Time Stamp ---------->
+ * 3 <-------- Core Tbl Len -------><-------- Core Tbl Offset ------>
+ * 4 <---------------------------- SSID ---------------------------->
+ * 5 < HWID >
+ * 6 <------------------------- FPGA DDNA -------------------------->
+ * 7 <------------------------ CPLD Config ------------------------->
+ * 8 <----------------------- IRQ Mask Flags ----------------------->
+ * 9 <---------------------- IRQ Active Flags ---------------------->
+ */
+
+#define REG_WIDTH 8
+#define REG_MAGIC_NUMBER (0 * REG_WIDTH)
+#define REG_CARD_ID_AND_BUILD (1 * REG_WIDTH)
+#define REG_DATE_AND_TIME_STAMPS (2 * REG_WIDTH)
+#define REG_CORE_TABLE_OFFSET (3 * REG_WIDTH)
+#define REG_FPGA_SSID (4 * REG_WIDTH)
+#define REG_FPGA_HW_ID (5 * REG_WIDTH)
+#define REG_FPGA_DDNA (6 * REG_WIDTH)
+#define REG_CPLD_CONFIG (7 * REG_WIDTH)
+#define REG_INTERRUPT_MASK (8 * REG_WIDTH)
+#define REG_INTERRUPT_ACTIVE (9 * REG_WIDTH)
+#define REG_PCIE_ERROR_COUNT (10 * REG_WIDTH)
+
+#define KP2000_MAGIC_VALUE 0x196C61482231894D
+
+#define PCI_VENDOR_ID_DAKTRONICS 0x1c33
+#define PCI_DEVICE_ID_DAKTRONICS 0x6021
+
+#define DMA_BAR 0
+#define REG_BAR 1
+
+struct kp2000_device {
+ struct pci_dev *pdev;
+ struct miscdevice miscdev;
+ char name[16];
+
+ unsigned int card_num;
+ struct mutex sem;
+
+ void __iomem *sysinfo_regs_base;
+ void __iomem *regs_bar_base;
+ struct resource regs_base_resource;
+ void __iomem *dma_bar_base;
+ void __iomem *dma_common_regs;
+ struct resource dma_base_resource;
+
+ // "System Registers"
+ u32 card_id;
+ u32 build_version;
+ u32 build_datestamp;
+ u32 build_timestamp;
+ u32 core_table_offset;
+ u32 core_table_length;
+ u8 core_table_rev;
+ u8 hardware_revision;
+ u64 ssid;
+ u64 ddna;
+
+ // IRQ stuff
+ unsigned int irq;
+
+ struct list_head uio_devices_list;
+};
+
+extern struct class *kpc_uio_class;
+extern struct attribute *kpc_uio_class_attrs[];
+
+int kp2000_pcie_probe(struct pci_dev *dev, const struct pci_device_id *id);
+void kp2000_pcie_remove(struct pci_dev *pdev);
+int kp2000_probe_cores(struct kp2000_device *pcard);
+void kp2000_remove_cores(struct kp2000_device *pcard);
+
+extern struct file_operations kp2000_fops;
+
+
+// Define this quick little macro because the expression is used frequently
+#define PCARD_TO_DEV(pcard) (&(pcard->pdev->dev))
+
+static inline void
+lock_card(struct kp2000_device *pcard)
+{
+ BUG_ON(pcard == NULL);
+ mutex_lock(&pcard->sem);
+}
+static inline void
+unlock_card(struct kp2000_device *pcard)
+{
+ BUG_ON(pcard == NULL);
+ mutex_unlock(&pcard->sem);
+}
+
+
+#endif /* KP2000_PCIE_H */
diff --git a/drivers/staging/kpc2000/kpc2000/uapi.h b/drivers/staging/kpc2000/kpc2000/uapi.h
new file mode 100644
index 000000000000..ef8008bcd33d
--- /dev/null
+++ b/drivers/staging/kpc2000/kpc2000/uapi.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef KP2000_CDEV_UAPI_H_
+#define KP2000_CDEV_UAPI_H_
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+struct kp2000_regs {
+ __u32 card_id;
+ __u32 build_version;
+ __u32 build_datestamp;
+ __u32 build_timestamp;
+ __u32 hw_rev;
+ __u64 ssid;
+ __u64 ddna;
+ __u64 cpld_reg;
+};
+
+#define KP2000_IOCTL_GET_CPLD_REG _IOR('k', 9, __u32)
+#define KP2000_IOCTL_GET_PCIE_ERROR_REG _IOR('k', 11, __u32)
+#define KP2000_IOCTL_GET_EVERYTHING _IOR('k', 8, struct kp2000_regs*)
+
+#endif /* KP2000_CDEV_UAPI_H_ */