source "drivers/staging/fieldbus/Kconfig"
 
+source "drivers/staging/kpc2000/Kconfig"
+
 endif # STAGING
 
 obj-$(CONFIG_XIL_AXIS_FIFO)    += axis-fifo/
 obj-$(CONFIG_EROFS_FS)         += erofs/
 obj-$(CONFIG_FIELDBUS_DEV)     += fieldbus/
+obj-$(CONFIG_KPC2000)          += kpc2000/
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+config KPC2000
+       bool "Daktronics KPC Device support"
+       depends on PCI
+       help
+         Select this if you wish to use the Daktronics KPC PCI devices
+
+         If unsure, say N.
+
+config KPC2000_CORE
+       tristate "Daktronics KPC PCI UIO device"
+       depends on KPC2000
+       help
+         Say Y here if you wish to support the Daktronics KPC PCI
+         device in UIO mode.
+
+         To compile this driver as a module, choose M here: the module
+         will be called kpc2000
+
+         If unsure, say N.
+
+config KPC2000_SPI
+       tristate "Kaktronics KPC SPI device"
+       depends on KPC2000 && SPI
+       help
+         Say Y here if you wish to support the Daktronics KPC PCI
+         device in SPI mode.
+
+         To compile this driver as a module, choose M here: the module
+         will be called kpc2000_spi
+
+         If unsure, say N.
+
+config KPC2000_I2C
+       tristate "Kaktronics KPC I2C device"
+       depends on KPC2000 && I2C
+       help
+         Say Y here if you wish to support the Daktronics KPC PCI
+         device in I2C mode.
+
+         To compile this driver as a module, choose M here: the module
+         will be called kpc2000_i2c
+
+         If unsure, say N.
+
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_KPC2000) += kpc2000/
+obj-$(CONFIG_KPC2000_I2C) += kpc_i2c/
+obj-$(CONFIG_KPC2000_SPI) += kpc_spi/
 
--- /dev/null
+- the kpc_spi driver doesn't seem to let multiple transactions (to different instances of the core) happen in parallel...
+- The kpc_i2c driver is a hot mess, it should probably be cleaned up a ton.  It functions against current hardware though.
+- pcard->card_num in kp2000_pcie_probe() is a global variable and needs atomic / locking / something better.
+- probe_core_uio() probably needs error handling
+- the loop in kp2000_probe_cores() that uses probe_core_uio() also probably needs error handling
+- would be nice if the AIO fileops in kpc_dma could be made to work
+    - probably want to add a CONFIG_ option to control compilation of the AIO functions
+- if the AIO fileops in kpc_dma start working, next would be making iov_count > 1 work too
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef KPC_H_
+#define KPC_H_
+
+/* *****  Driver Names  ***** */
+#define KP_DRIVER_NAME_KP2000           "kp2000"
+#define KP_DRIVER_NAME_INVALID          "kpc_invalid"
+#define KP_DRIVER_NAME_DMA_CONTROLLER   "kpc_nwl_dma"
+#define KP_DRIVER_NAME_UIO              "uio_pdrv_genirq"
+#define KP_DRIVER_NAME_I2C              "kpc_i2c"
+#define KP_DRIVER_NAME_SPI              "kpc_spi"
+
+struct kpc_core_device_platdata {
+       u32 card_id;
+       u32 build_version;
+       u32 hardware_revision;
+       u64 ssid;
+       u64 ddna;
+};
+
+#define PCI_DEVICE_ID_DAKTRONICS_KADOKA_P2KR0           0x4b03
+
+#endif /* KPC_H_ */
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+obj-m := kpc2000.o
+kpc2000-objs += kp2000_module.o  core.o  cell_probe.o  fileops.o
 
--- /dev/null
+// 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);
+    }
+}
+
 
--- /dev/null
+// 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, ®val);
+    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);
+}
 
--- /dev/null
+/* 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_ */
 
--- /dev/null
+// 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,
+};
+
 
--- /dev/null
+// 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);
 
--- /dev/null
+/* 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 */
 
--- /dev/null
+/* 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_ */
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+obj-m := kpc2000_i2c.o
+kpc2000_i2c-objs := i2c_driver.o fileops.o
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+#if 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 <asm/uaccess.h>       /* copy_*_user */
+
+#include "i2c_driver.h"
+
+int i2c_cdev_open(struct inode *inode, struct file *filp)
+{
+  struct i2c_device *lddev;
+  
+  if(NULL == inode) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_open: inode is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_open: inode is a NULL pointer\n");
+    return -EINVAL;
+  }
+  if(NULL == filp) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_open: filp is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_open: filp is a NULL pointer\n");
+    return -EINVAL;
+  }
+  
+  lddev = container_of(inode->i_cdev, struct i2c_device, cdev);
+  //printk(KERN_DEBUG "<pl_i2c> i2c_cdev_open(filp = [%p], lddev = [%p])\n", filp, lddev);
+  DBG_PRINT(KERN_DEBUG, "i2c_cdev_open(filp = [%p], lddev = [%p])\n", filp, lddev);
+  
+  filp->private_data = lddev; /* so other methods can access it */
+  
+  return 0;    /* success */
+}
+
+int i2c_cdev_close(struct inode *inode, struct file *filp)
+{
+  struct i2c_device *lddev;
+  
+  if(NULL == inode) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_close: inode is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_close: inode is a NULL pointer\n");
+    return -EINVAL;
+  }
+  if(NULL == filp) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_close: filp is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_close: filp is a NULL pointer\n");
+    return -EINVAL;
+  }
+  
+  lddev = filp->private_data;
+  //printk(KERN_DEBUG "<pl_i2c> i2c_cdev_close(filp = [%p], lddev = [%p])\n", filp, lddev);
+  DBG_PRINT(KERN_DEBUG, "i2c_cdev_close(filp = [%p], lddev = [%p])\n", filp, lddev);
+  
+  return 0;
+}
+
+ssize_t i2c_cdev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+  size_t copy;
+  ssize_t ret = 0;
+  int err = 0;
+  u64 read_val;
+  char tmp_buf[48] = { 0 };
+  struct i2c_device *lddev = filp->private_data;
+
+  if(NULL == filp) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_read: filp is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_read: filp is a NULL pointer\n");
+    return -EINVAL;
+  }
+  if(NULL == buf) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_read: buf is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_read: buf is a NULL pointer\n");
+    return -EINVAL;
+  }
+  if(NULL == f_pos) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_read: f_pos is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_read: f_pos is a NULL pointer\n");
+    return -EINVAL;
+  }
+
+  if(count < sizeof(tmp_buf)) {
+    //printk(KERN_INFO "<pl_i2c> i2c_cdev_read: buffer is too small (count = %d, should be at least %d bytes)\n", (int)count, (int)sizeof(tmp_buf));
+    DBG_PRINT(KERN_INFO, "i2c_cdev_read: buffer is too small (count = %d, should be at least %d bytes)\n", (int)count, (int)sizeof(tmp_buf));
+    return -EINVAL;
+  }
+  if(((*f_pos * 8) + lddev->pldev->resource[0].start) > lddev->pldev->resource[0].end) {
+    //printk(KERN_INFO "<pl_i2c> i2c_cdev_read: bad read addr %016llx\n", (*f_pos * 8) + lddev->pldev->resource[0].start);
+    DBG_PRINT(KERN_INFO, "i2c_cdev_read: bad read addr %016llx\n", (*f_pos * 8) + lddev->pldev->resource[0].start);
+    //printk(KERN_INFO "<pl_i2c> i2c_cdev_read: addr end %016llx\n", lddev->pldev->resource[0].end);
+    DBG_PRINT(KERN_INFO, "i2c_cdev_read: addr end %016llx\n", lddev->pldev->resource[0].end);
+    //printk(KERN_INFO "<pl_i2c> i2c_cdev_read: EOF reached\n");
+    DBG_PRINT(KERN_INFO, "i2c_cdev_read: EOF reached\n");
+    return 0;
+  }
+
+  down_read(&lddev->rw_sem);
+  
+  read_val = *(lddev->regs + *f_pos);
+  copy = clamp_t(size_t, count, 1, sizeof(tmp_buf));
+  copy = scnprintf(tmp_buf, copy, "reg: 0x%x val: 0x%llx\n", (unsigned int)*f_pos, read_val);
+  err = copy_to_user(buf, tmp_buf, copy);
+  if(err) {
+    //printk(KERN_INFO "<pl_i2c> i2c_cdev_read: could not copy to user (err = %d)\n", err);
+    DBG_PRINT(KERN_INFO, "i2c_cdev_read: could not copy to user (err = %d)\n", err);
+    return -EINVAL;
+  }
+
+  ret = (ssize_t)copy;
+  (*f_pos)++;
+  
+  up_read(&lddev->rw_sem);
+  
+  return ret;
+}
+
+ssize_t i2c_cdev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
+{
+  u8 reg;
+  u8 val;
+  char tmp[8] = { 0 };
+  struct i2c_device *lddev = filp->private_data;
+
+  if(NULL == filp) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_write: filp is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_write: filp is a NULL pointer\n");
+    return -EINVAL;
+  }
+  if(NULL == buf) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_write: buf is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_write: buf is a NULL pointer\n");
+    return -EINVAL;
+  }
+  if(NULL == f_pos) {
+    //printk(KERN_WARNING "<pl_i2c> i2c_cdev_write: f_pos is a NULL pointer\n");
+    DBG_PRINT(KERN_WARNING, "i2c_cdev_write: f_pos is a NULL pointer\n");
+    return -EINVAL;
+  }
+
+  //printk(KERN_DEBUG "<pl_i2c> i2c_cdev_write(filp = [%p], lddev = [%p])\n", filp, lddev);
+  DBG_PRINT(KERN_DEBUG, "i2c_cdev_write(filp = [%p], lddev = [%p])\n", filp, lddev);
+
+  down_write(&lddev->rw_sem);
+
+  if(count >= 2) {
+    if(copy_from_user(tmp, buf, 2)) {
+      return -EFAULT;
+    }
+    
+    reg = tmp[0] - '0';
+    val = tmp[1] - '0';
+
+    //printk(KERN_DEBUG "  reg = %d  val = %d\n", reg, val);
+    DBG_PRINT(KERN_DEBUG, "  reg = %d  val = %d\n", reg, val);
+
+    if(reg >= 0 && reg < 16) {
+      //printk(KERN_DEBUG "  Writing 0x%x to %p\n", val, lddev->regs + reg);
+      DBG_PRINT(KERN_DEBUG, "  Writing 0x%x to %p\n", val, lddev->regs + reg);
+      *(lddev->regs + reg) = val;
+    }
+  }
+
+  (*f_pos)++;
+
+  up_write(&lddev->rw_sem);
+
+  return count;
+}
+
+struct file_operations i2c_fops = {
+  .owner               = THIS_MODULE,
+  .open                = i2c_cdev_open,
+  .release     = i2c_cdev_close,
+  .read                = i2c_cdev_read,
+  .write               = i2c_cdev_write,
+};
+#endif
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*  Copyright (c) 2014-2018  Daktronics,
+                             Matt Sickler <matt.sickler@daktronics.com>,
+                             Jordon Hofer <jordon.hofer@daktronics.com>
+    Adapted i2c-i801.c for use with Kadoka hardware.
+    Copyright (c) 1998 - 2002  Frodo Looijaard <frodol@dds.nl>,
+    Philip Edelbrock <phil@netroedge.com>, and Mark D. Studebaker
+    <mdsxyz123@yahoo.com>
+    Copyright (C) 2007 - 2012  Jean Delvare <khali@linux-fr.org>
+    Copyright (C) 2010         Intel Corporation,
+                               David Woodhouse <dwmw2@infradead.org>
+*/
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <asm/io.h>
+#include <linux/export.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/fs.h>
+#include <linux/rwsem.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include "../kpc.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matt.Sickler@Daktronics.com");
+MODULE_SOFTDEP("pre: i2c-dev");
+
+struct i2c_device {
+    unsigned long           smba;
+    struct i2c_adapter      adapter;
+    struct platform_device *pldev;
+    struct rw_semaphore     rw_sem;
+    unsigned int            features;
+};
+
+/*****************************
+ *** Part 1 - i2c Handlers ***
+ *****************************/
+
+#define REG_SIZE 8
+
+/* I801 SMBus address offsets */
+#define SMBHSTSTS(p)    ((0  * REG_SIZE) + (p)->smba)
+#define SMBHSTCNT(p)    ((2  * REG_SIZE) + (p)->smba)
+#define SMBHSTCMD(p)    ((3  * REG_SIZE) + (p)->smba)
+#define SMBHSTADD(p)    ((4  * REG_SIZE) + (p)->smba)
+#define SMBHSTDAT0(p)   ((5  * REG_SIZE) + (p)->smba)
+#define SMBHSTDAT1(p)   ((6  * REG_SIZE) + (p)->smba)
+#define SMBBLKDAT(p)    ((7  * REG_SIZE) + (p)->smba)
+#define SMBPEC(p)       ((8  * REG_SIZE) + (p)->smba)   /* ICH3 and later */
+#define SMBAUXSTS(p)    ((12 * REG_SIZE) + (p)->smba)   /* ICH4 and later */
+#define SMBAUXCTL(p)    ((13 * REG_SIZE) + (p)->smba)   /* ICH4 and later */
+
+/* PCI Address Constants */
+#define SMBBAR      4
+#define SMBHSTCFG   0x040
+
+/* Host configuration bits for SMBHSTCFG */
+#define SMBHSTCFG_HST_EN        1
+#define SMBHSTCFG_SMB_SMI_EN    2
+#define SMBHSTCFG_I2C_EN        4
+
+/* Auxiliary control register bits, ICH4+ only */
+#define SMBAUXCTL_CRC       1
+#define SMBAUXCTL_E32B      2
+
+/* kill bit for SMBHSTCNT */
+#define SMBHSTCNT_KILL      2
+
+/* Other settings */
+#define MAX_RETRIES         400
+#define ENABLE_INT9         0       /* set to 0x01 to enable - untested */
+
+/* I801 command constants */
+#define I801_QUICK              0x00
+#define I801_BYTE               0x04
+#define I801_BYTE_DATA          0x08
+#define I801_WORD_DATA          0x0C
+#define I801_PROC_CALL          0x10    /* unimplemented */
+#define I801_BLOCK_DATA         0x14
+#define I801_I2C_BLOCK_DATA     0x18    /* ICH5 and later */
+#define I801_BLOCK_LAST         0x34
+#define I801_I2C_BLOCK_LAST     0x38    /* ICH5 and later */
+#define I801_START              0x40
+#define I801_PEC_EN             0x80    /* ICH3 and later */
+
+/* I801 Hosts Status register bits */
+#define SMBHSTSTS_BYTE_DONE     0x80
+#define SMBHSTSTS_INUSE_STS     0x40
+#define SMBHSTSTS_SMBALERT_STS  0x20
+#define SMBHSTSTS_FAILED        0x10
+#define SMBHSTSTS_BUS_ERR       0x08
+#define SMBHSTSTS_DEV_ERR       0x04
+#define SMBHSTSTS_INTR          0x02
+#define SMBHSTSTS_HOST_BUSY     0x01
+
+#define STATUS_FLAGS        (SMBHSTSTS_BYTE_DONE | SMBHSTSTS_FAILED | SMBHSTSTS_BUS_ERR | SMBHSTSTS_DEV_ERR | SMBHSTSTS_INTR)
+
+/* Older devices have their ID defined in <linux/pci_ids.h> */
+#define PCI_DEVICE_ID_INTEL_COUGARPOINT_SMBUS       0x1c22
+#define PCI_DEVICE_ID_INTEL_PATSBURG_SMBUS          0x1d22
+/* Patsburg also has three 'Integrated Device Function' SMBus controllers */
+#define PCI_DEVICE_ID_INTEL_PATSBURG_SMBUS_IDF0     0x1d70
+#define PCI_DEVICE_ID_INTEL_PATSBURG_SMBUS_IDF1     0x1d71
+#define PCI_DEVICE_ID_INTEL_PATSBURG_SMBUS_IDF2     0x1d72
+#define PCI_DEVICE_ID_INTEL_PANTHERPOINT_SMBUS      0x1e22
+#define PCI_DEVICE_ID_INTEL_DH89XXCC_SMBUS          0x2330
+#define PCI_DEVICE_ID_INTEL_5_3400_SERIES_SMBUS     0x3b30
+#define PCI_DEVICE_ID_INTEL_LYNXPOINT_SMBUS         0x8c22
+#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_SMBUS      0x9c22
+
+
+#define FEATURE_SMBUS_PEC       (1 << 0)
+#define FEATURE_BLOCK_BUFFER    (1 << 1)
+#define FEATURE_BLOCK_PROC      (1 << 2)
+#define FEATURE_I2C_BLOCK_READ  (1 << 3)
+/* Not really a feature, but it's convenient to handle it as such */
+#define FEATURE_IDF             (1 << 15)
+
+static unsigned int disable_features;
+module_param(disable_features, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(disable_features, "Disable selected driver features");
+
+// FIXME!
+#undef inb_p
+#define inb_p(a) readq((void*)a)
+#undef outb_p
+#define outb_p(d,a) writeq(d,(void*)a)
+
+/* Make sure the SMBus host is ready to start transmitting.
+   Return 0 if it is, -EBUSY if it is not. */
+static int i801_check_pre(struct i2c_device *priv)
+{
+    int status;
+    
+    dev_dbg(&priv->adapter.dev, "i801_check_pre\n");
+    
+    status = inb_p(SMBHSTSTS(priv));
+    if (status & SMBHSTSTS_HOST_BUSY) {
+        dev_err(&priv->adapter.dev, "SMBus is busy, can't use it! (status=%x)\n", status);
+        return -EBUSY;
+    }
+    
+    status &= STATUS_FLAGS;
+    if (status) {
+        //dev_dbg(&priv->adapter.dev, "Clearing status flags (%02x)\n", status);
+        outb_p(status, SMBHSTSTS(priv));
+        status = inb_p(SMBHSTSTS(priv)) & STATUS_FLAGS;
+        if (status) {
+          dev_err(&priv->adapter.dev, "Failed clearing status flags (%02x)\n", status);
+          return -EBUSY;
+        }
+    }
+    return 0;
+}
+
+/* Convert the status register to an error code, and clear it. */
+static int i801_check_post(struct i2c_device *priv, int status, int timeout)
+{
+    int result = 0;
+    
+    dev_dbg(&priv->adapter.dev, "i801_check_post\n");
+    
+    /* If the SMBus is still busy, we give up */
+    if (timeout) {
+        dev_err(&priv->adapter.dev, "Transaction timeout\n");
+        /* try to stop the current command */
+        dev_dbg(&priv->adapter.dev, "Terminating the current operation\n");
+        outb_p(inb_p(SMBHSTCNT(priv)) | SMBHSTCNT_KILL, SMBHSTCNT(priv));
+        usleep_range(1000, 2000);
+        outb_p(inb_p(SMBHSTCNT(priv)) & (~SMBHSTCNT_KILL), SMBHSTCNT(priv));
+        
+        /* Check if it worked */
+        status = inb_p(SMBHSTSTS(priv));
+        if ((status & SMBHSTSTS_HOST_BUSY) || !(status & SMBHSTSTS_FAILED)) {
+            dev_err(&priv->adapter.dev, "Failed terminating the transaction\n");
+        }
+        outb_p(STATUS_FLAGS, SMBHSTSTS(priv));
+        return -ETIMEDOUT;
+    }
+    
+    if (status & SMBHSTSTS_FAILED) {
+        result = -EIO;
+        dev_err(&priv->adapter.dev, "Transaction failed\n");
+    }
+    if (status & SMBHSTSTS_DEV_ERR) {
+        result = -ENXIO;
+        dev_dbg(&priv->adapter.dev, "No response\n");
+    }
+    if (status & SMBHSTSTS_BUS_ERR) {
+        result = -EAGAIN;
+        dev_dbg(&priv->adapter.dev, "Lost arbitration\n");
+    }
+    
+    if (result) {
+        /* Clear error flags */
+        outb_p(status & STATUS_FLAGS, SMBHSTSTS(priv));
+        status = inb_p(SMBHSTSTS(priv)) & STATUS_FLAGS;
+        if (status) {
+            dev_warn(&priv->adapter.dev, "Failed clearing status flags at end of transaction (%02x)\n", status);
+        }
+    }
+    
+    return result;
+}
+
+static int i801_transaction(struct i2c_device *priv, int xact)
+{
+    int status;
+    int result;
+    int timeout = 0;
+    
+    dev_dbg(&priv->adapter.dev, "i801_transaction\n");
+    
+    result = i801_check_pre(priv);
+    if (result < 0) {
+        return result;
+    }
+    /* the current contents of SMBHSTCNT can be overwritten, since PEC,
+    * INTREN, SMBSCMD are passed in xact */
+    outb_p(xact | I801_START, SMBHSTCNT(priv));
+    
+    /* We will always wait for a fraction of a second! */
+    do {
+        usleep_range(250, 500);
+        status = inb_p(SMBHSTSTS(priv));
+    } while ((status & SMBHSTSTS_HOST_BUSY) && (timeout++ < MAX_RETRIES));
+    
+    result = i801_check_post(priv, status, timeout > MAX_RETRIES);
+    if (result < 0) {
+        return result;
+    }
+    
+    outb_p(SMBHSTSTS_INTR, SMBHSTSTS(priv));
+    return 0;
+}
+
+/* wait for INTR bit as advised by Intel */
+static void i801_wait_hwpec(struct i2c_device *priv)
+{
+    int timeout = 0;
+    int status;
+    
+    dev_dbg(&priv->adapter.dev, "i801_wait_hwpec\n");
+    
+    do {
+        usleep_range(250, 500);
+        status = inb_p(SMBHSTSTS(priv));
+    } while ((!(status & SMBHSTSTS_INTR)) && (timeout++ < MAX_RETRIES));
+    
+    if (timeout > MAX_RETRIES) {
+        dev_dbg(&priv->adapter.dev, "PEC Timeout!\n");
+    }
+    
+    outb_p(status, SMBHSTSTS(priv));
+}
+
+static int i801_block_transaction_by_block(struct i2c_device *priv, union i2c_smbus_data *data, char read_write, int hwpec)
+{
+    int i, len;
+    int status;
+    
+    dev_dbg(&priv->adapter.dev, "i801_block_transaction_by_block\n");
+    
+    inb_p(SMBHSTCNT(priv)); /* reset the data buffer index */
+    
+    /* Use 32-byte buffer to process this transaction */
+    if (read_write == I2C_SMBUS_WRITE) {
+        len = data->block[0];
+        outb_p(len, SMBHSTDAT0(priv));
+        for (i = 0; i < len; i++) {
+            outb_p(data->block[i+1], SMBBLKDAT(priv));
+        }
+    }
+    
+    status = i801_transaction(priv, I801_BLOCK_DATA | ENABLE_INT9 | I801_PEC_EN * hwpec);
+    if (status) {
+        return status;
+    }
+
+    if (read_write == I2C_SMBUS_READ) {
+        len = inb_p(SMBHSTDAT0(priv));
+        if (len < 1 || len > I2C_SMBUS_BLOCK_MAX) {
+            return -EPROTO;
+        }
+        
+        data->block[0] = len;
+        for (i = 0; i < len; i++) {
+            data->block[i + 1] = inb_p(SMBBLKDAT(priv));
+        }
+    }
+    return 0;
+}
+
+static int i801_block_transaction_byte_by_byte(struct i2c_device *priv, union i2c_smbus_data *data, char read_write, int command, int hwpec)
+{
+    int i, len;
+    int smbcmd;
+    int status;
+    int result;
+    int timeout;
+    
+    dev_dbg(&priv->adapter.dev, "i801_block_transaction_byte_by_byte\n");
+    
+    result = i801_check_pre(priv);
+    if (result < 0) {
+        return result;
+    }
+    
+    len = data->block[0];
+    
+    if (read_write == I2C_SMBUS_WRITE) {
+        outb_p(len, SMBHSTDAT0(priv));
+        outb_p(data->block[1], SMBBLKDAT(priv));
+    }
+    
+    for (i = 1; i <= len; i++) {
+        if (i == len && read_write == I2C_SMBUS_READ) {
+            if (command == I2C_SMBUS_I2C_BLOCK_DATA) {
+                smbcmd = I801_I2C_BLOCK_LAST;
+            } else {
+                smbcmd = I801_BLOCK_LAST;
+            }
+        } else {
+            if (command == I2C_SMBUS_I2C_BLOCK_DATA && read_write == I2C_SMBUS_READ) {
+                smbcmd = I801_I2C_BLOCK_DATA;
+            } else {
+                smbcmd = I801_BLOCK_DATA;
+            }
+        }
+        outb_p(smbcmd | ENABLE_INT9, SMBHSTCNT(priv));
+        
+        if (i == 1) {
+            outb_p(inb(SMBHSTCNT(priv)) | I801_START, SMBHSTCNT(priv));
+        }
+        /* We will always wait for a fraction of a second! */
+        timeout = 0;
+        do {
+            usleep_range(250, 500);
+            status = inb_p(SMBHSTSTS(priv));
+        } while ((!(status & SMBHSTSTS_BYTE_DONE)) && (timeout++ < MAX_RETRIES));
+        
+        result = i801_check_post(priv, status, timeout > MAX_RETRIES);
+        if (result < 0) {
+            return result;
+        }
+        if (i == 1 && read_write == I2C_SMBUS_READ && command != I2C_SMBUS_I2C_BLOCK_DATA) {
+            len = inb_p(SMBHSTDAT0(priv));
+            if (len < 1 || len > I2C_SMBUS_BLOCK_MAX) {
+                dev_err(&priv->adapter.dev, "Illegal SMBus block read size %d\n", len);
+                /* Recover */
+                while (inb_p(SMBHSTSTS(priv)) & SMBHSTSTS_HOST_BUSY) {
+                    outb_p(SMBHSTSTS_BYTE_DONE, SMBHSTSTS(priv));
+                }
+                outb_p(SMBHSTSTS_INTR, SMBHSTSTS(priv));
+                return -EPROTO;
+            }
+            data->block[0] = len;
+        }
+        
+        /* Retrieve/store value in SMBBLKDAT */
+        if (read_write == I2C_SMBUS_READ) {
+            data->block[i] = inb_p(SMBBLKDAT(priv));
+        }
+        if (read_write == I2C_SMBUS_WRITE && i+1 <= len) {
+            outb_p(data->block[i+1], SMBBLKDAT(priv));
+        }
+        /* signals SMBBLKDAT ready */
+        outb_p(SMBHSTSTS_BYTE_DONE | SMBHSTSTS_INTR, SMBHSTSTS(priv));
+    }
+    
+    return 0;
+}
+
+static int i801_set_block_buffer_mode(struct i2c_device *priv)
+{
+    dev_dbg(&priv->adapter.dev, "i801_set_block_buffer_mode\n");
+    
+    outb_p(inb_p(SMBAUXCTL(priv)) | SMBAUXCTL_E32B, SMBAUXCTL(priv));
+    if ((inb_p(SMBAUXCTL(priv)) & SMBAUXCTL_E32B) == 0) {
+        return -EIO;
+    }
+    return 0;
+}
+
+/* Block transaction function */
+static int i801_block_transaction(struct i2c_device *priv, union i2c_smbus_data *data, char read_write, int command, int hwpec)
+{
+    int result = 0;
+    //unsigned char hostc;
+    
+    dev_dbg(&priv->adapter.dev, "i801_block_transaction\n");
+    
+    if (command == I2C_SMBUS_I2C_BLOCK_DATA) {
+        if (read_write == I2C_SMBUS_WRITE) {
+            /* set I2C_EN bit in configuration register */
+            //TODO: Figure out the right thing to do here...
+            //pci_read_config_byte(priv->pci_dev, SMBHSTCFG, &hostc);
+            //pci_write_config_byte(priv->pci_dev, SMBHSTCFG, hostc | SMBHSTCFG_I2C_EN);
+        } else if (!(priv->features & FEATURE_I2C_BLOCK_READ)) {
+            dev_err(&priv->adapter.dev, "I2C block read is unsupported!\n");
+            return -EOPNOTSUPP;
+        }
+    }
+    
+    if (read_write == I2C_SMBUS_WRITE || command == I2C_SMBUS_I2C_BLOCK_DATA) {
+        if (data->block[0] < 1) {
+            data->block[0] = 1;
+        }
+        if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
+            data->block[0] = I2C_SMBUS_BLOCK_MAX;
+        }
+    } else {
+        data->block[0] = 32;   /* max for SMBus block reads */
+    }
+    
+    /* Experience has shown that the block buffer can only be used for
+        SMBus (not I2C) block transactions, even though the datasheet
+        doesn't mention this limitation. */
+    if ((priv->features & FEATURE_BLOCK_BUFFER) && command != I2C_SMBUS_I2C_BLOCK_DATA && i801_set_block_buffer_mode(priv) == 0) {
+        result = i801_block_transaction_by_block(priv, data, read_write, hwpec);
+    } else {
+        result = i801_block_transaction_byte_by_byte(priv, data, read_write, command, hwpec);
+    }  
+    if (result == 0 && hwpec) {
+        i801_wait_hwpec(priv);
+    }
+    if (command == I2C_SMBUS_I2C_BLOCK_DATA && read_write == I2C_SMBUS_WRITE) {
+        /* restore saved configuration register value */
+        //TODO: Figure out the right thing to do here...
+        //pci_write_config_byte(priv->pci_dev, SMBHSTCFG, hostc);
+    }
+    return result;
+}
+
+/* Return negative errno on error. */
+static s32 i801_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data)
+{
+    int hwpec;
+    int block = 0;
+    int ret, xact = 0;
+    struct i2c_device *priv = i2c_get_adapdata(adap);
+    
+    dev_dbg(&priv->adapter.dev, "i801_access (addr=%0d)  flags=%x  read_write=%x  command=%x  size=%x",
+      addr, flags, read_write, command, size );
+    
+    hwpec = (priv->features & FEATURE_SMBUS_PEC) && (flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK && size != I2C_SMBUS_I2C_BLOCK_DATA;
+    
+    switch (size) {
+        case I2C_SMBUS_QUICK:
+            dev_dbg(&priv->adapter.dev, "  [acc] SMBUS_QUICK\n");
+            outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD(priv));
+            xact = I801_QUICK;
+            break;
+        case I2C_SMBUS_BYTE:
+            dev_dbg(&priv->adapter.dev, "  [acc] SMBUS_BYTE\n");
+            
+            outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD(priv));
+            if (read_write == I2C_SMBUS_WRITE) {
+                outb_p(command, SMBHSTCMD(priv));
+            }
+            xact = I801_BYTE;
+            break;
+        case I2C_SMBUS_BYTE_DATA:
+            dev_dbg(&priv->adapter.dev, "  [acc] SMBUS_BYTE_DATA\n");
+            outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD(priv));
+            outb_p(command, SMBHSTCMD(priv));
+            if (read_write == I2C_SMBUS_WRITE) {
+                outb_p(data->byte, SMBHSTDAT0(priv));
+            }
+            xact = I801_BYTE_DATA;
+            break;
+        case I2C_SMBUS_WORD_DATA:
+            dev_dbg(&priv->adapter.dev, "  [acc] SMBUS_WORD_DATA\n");
+            outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD(priv));
+            outb_p(command, SMBHSTCMD(priv));
+            if (read_write == I2C_SMBUS_WRITE) {
+                outb_p(data->word & 0xff, SMBHSTDAT0(priv));
+                outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1(priv));
+            }
+            xact = I801_WORD_DATA;
+            break;
+        case I2C_SMBUS_BLOCK_DATA:
+            dev_dbg(&priv->adapter.dev, "  [acc] SMBUS_BLOCK_DATA\n");
+            outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), SMBHSTADD(priv));
+            outb_p(command, SMBHSTCMD(priv));
+            block = 1;
+            break;
+        case I2C_SMBUS_I2C_BLOCK_DATA:
+            dev_dbg(&priv->adapter.dev, "  [acc] SMBUS_I2C_BLOCK_DATA\n");
+            /* NB: page 240 of ICH5 datasheet shows that the R/#W
+             * bit should be cleared here, even when reading */
+            outb_p((addr & 0x7f) << 1, SMBHSTADD(priv));
+            if (read_write == I2C_SMBUS_READ) {
+                /* NB: page 240 of ICH5 datasheet also shows
+                 * that DATA1 is the cmd field when reading */
+                outb_p(command, SMBHSTDAT1(priv));
+            } else {
+                outb_p(command, SMBHSTCMD(priv));
+            }
+            block = 1;
+            break;
+        default:
+            dev_dbg(&priv->adapter.dev, "  [acc] Unsupported transaction %d\n", size);
+            return -EOPNOTSUPP;
+    }
+    
+    if (hwpec) { /* enable/disable hardware PEC */
+        dev_dbg(&priv->adapter.dev, "  [acc] hwpec: yes\n");
+        outb_p(inb_p(SMBAUXCTL(priv)) | SMBAUXCTL_CRC, SMBAUXCTL(priv));
+    } else {
+        dev_dbg(&priv->adapter.dev, "  [acc] hwpec: no\n");
+        outb_p(inb_p(SMBAUXCTL(priv)) & (~SMBAUXCTL_CRC), SMBAUXCTL(priv));
+    }
+    
+    if (block) {
+        //ret = 0;
+        dev_dbg(&priv->adapter.dev, "  [acc] block: yes\n");
+        ret = i801_block_transaction(priv, data, read_write, size, hwpec);
+    } else {
+        dev_dbg(&priv->adapter.dev, "  [acc] block: no\n");
+        ret = i801_transaction(priv, xact | ENABLE_INT9);
+    }
+    
+    /* Some BIOSes don't like it when PEC is enabled at reboot or resume
+       time, so we forcibly disable it after every transaction. Turn off
+       E32B for the same reason. */
+    if (hwpec || block) {
+        dev_dbg(&priv->adapter.dev, "  [acc] hwpec || block\n");
+        outb_p(inb_p(SMBAUXCTL(priv)) & ~(SMBAUXCTL_CRC | SMBAUXCTL_E32B), SMBAUXCTL(priv));
+    }
+    if (block) {
+        dev_dbg(&priv->adapter.dev, "  [acc] block\n");
+        return ret;
+    }
+    if (ret) {
+        dev_dbg(&priv->adapter.dev, "  [acc] ret %d\n", ret);
+        return ret;
+    }
+    if ((read_write == I2C_SMBUS_WRITE) || (xact == I801_QUICK)) {
+        dev_dbg(&priv->adapter.dev, "  [acc] I2C_SMBUS_WRITE || I801_QUICK  -> ret 0\n");
+        return 0;
+    }
+    
+    switch (xact & 0x7f) {
+        case I801_BYTE:  /* Result put in SMBHSTDAT0 */
+        case I801_BYTE_DATA:
+            dev_dbg(&priv->adapter.dev, "  [acc] I801_BYTE or I801_BYTE_DATA\n");
+            data->byte = inb_p(SMBHSTDAT0(priv));
+            break;
+        case I801_WORD_DATA:
+            dev_dbg(&priv->adapter.dev, "  [acc] I801_WORD_DATA\n");
+            data->word = inb_p(SMBHSTDAT0(priv)) + (inb_p(SMBHSTDAT1(priv)) << 8);
+            break;
+    }
+    return 0;
+}
+
+
+
+static u32 i801_func(struct i2c_adapter *adapter)
+{
+    struct i2c_device *priv = i2c_get_adapdata(adapter);
+    
+    /* original settings
+    u32 f = I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
+      I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
+      I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_WRITE_I2C_BLOCK |
+      ((priv->features & FEATURE_SMBUS_PEC) ? I2C_FUNC_SMBUS_PEC : 0) |
+      ((priv->features & FEATURE_I2C_BLOCK_READ) ?
+       I2C_FUNC_SMBUS_READ_I2C_BLOCK : 0);
+     */
+    
+    // http://lxr.free-electrons.com/source/include/uapi/linux/i2c.h#L85
+    
+    u32 f = 
+        I2C_FUNC_I2C                     | /* 0x00000001 (I enabled this one) */
+        !I2C_FUNC_10BIT_ADDR             | /* 0x00000002 */
+        !I2C_FUNC_PROTOCOL_MANGLING      | /* 0x00000004 */
+        ((priv->features & FEATURE_SMBUS_PEC) ? I2C_FUNC_SMBUS_PEC : 0) | /* 0x00000008 */
+        !I2C_FUNC_SMBUS_BLOCK_PROC_CALL  | /* 0x00008000 */
+        I2C_FUNC_SMBUS_QUICK             | /* 0x00010000 */
+        !I2C_FUNC_SMBUS_READ_BYTE        | /* 0x00020000 */
+        !I2C_FUNC_SMBUS_WRITE_BYTE       | /* 0x00040000 */
+        !I2C_FUNC_SMBUS_READ_BYTE_DATA   | /* 0x00080000 */
+        !I2C_FUNC_SMBUS_WRITE_BYTE_DATA  | /* 0x00100000 */
+        !I2C_FUNC_SMBUS_READ_WORD_DATA   | /* 0x00200000 */
+        !I2C_FUNC_SMBUS_WRITE_WORD_DATA  | /* 0x00400000 */
+        !I2C_FUNC_SMBUS_PROC_CALL        | /* 0x00800000 */
+        !I2C_FUNC_SMBUS_READ_BLOCK_DATA  | /* 0x01000000 */
+        !I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | /* 0x02000000 */
+        ((priv->features & FEATURE_I2C_BLOCK_READ) ? I2C_FUNC_SMBUS_READ_I2C_BLOCK : 0) | /* 0x04000000 */
+        I2C_FUNC_SMBUS_WRITE_I2C_BLOCK   | /* 0x08000000 */
+        
+        I2C_FUNC_SMBUS_BYTE              | /* _READ_BYTE  _WRITE_BYTE */
+        I2C_FUNC_SMBUS_BYTE_DATA         | /* _READ_BYTE_DATA  _WRITE_BYTE_DATA */
+        I2C_FUNC_SMBUS_WORD_DATA         | /* _READ_WORD_DATA  _WRITE_WORD_DATA */
+        I2C_FUNC_SMBUS_BLOCK_DATA        | /* _READ_BLOCK_DATA  _WRITE_BLOCK_DATA */
+        !I2C_FUNC_SMBUS_I2C_BLOCK        | /* _READ_I2C_BLOCK  _WRITE_I2C_BLOCK */
+        !I2C_FUNC_SMBUS_EMUL;              /* _QUICK  _BYTE  _BYTE_DATA  _WORD_DATA  _PROC_CALL  _WRITE_BLOCK_DATA  _I2C_BLOCK _PEC */
+    return f;
+}
+
+static const struct i2c_algorithm smbus_algorithm = {
+    .smbus_xfer     = i801_access,
+    .functionality  = i801_func,
+};
+
+
+
+/********************************
+ *** Part 2 - Driver Handlers ***
+ ********************************/
+int pi2c_probe(struct platform_device *pldev)
+{
+    int err;
+    struct i2c_device *priv;
+    struct resource *res;
+    
+    dev_dbg(&pldev->dev, "pi2c_probe(pldev = %p '%s')\n", pldev, pldev->name);
+    
+    priv = kzalloc(sizeof(struct i2c_device), GFP_KERNEL);
+    if (!priv) {
+        return -ENOMEM;
+    }
+    
+    i2c_set_adapdata(&priv->adapter, priv);
+    priv->adapter.owner = THIS_MODULE;
+    priv->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+    priv->adapter.algo = &smbus_algorithm;
+    
+    res = platform_get_resource(pldev, IORESOURCE_MEM, 0);
+    priv->smba = (unsigned long)ioremap_nocache(res->start, res->end - res->start);
+    
+    priv->pldev = pldev;
+    pldev->dev.platform_data = priv;
+    
+    priv->features |= FEATURE_IDF;
+    priv->features |= FEATURE_I2C_BLOCK_READ;
+    priv->features |= FEATURE_SMBUS_PEC;
+    priv->features |= FEATURE_BLOCK_BUFFER;
+    
+    //init_MUTEX(&lddata->sem);
+    init_rwsem(&priv->rw_sem);
+    
+    /* set up the sysfs linkage to our parent device */
+    priv->adapter.dev.parent = &pldev->dev;
+    
+    /* Retry up to 3 times on lost arbitration */
+    priv->adapter.retries = 3;
+    
+    //snprintf(priv->adapter.name, sizeof(priv->adapter.name), "Fake SMBus I801 adapter at %04lx", priv->smba);
+    snprintf(priv->adapter.name, sizeof(priv->adapter.name), "Fake SMBus I801 adapter");
+    
+    err = i2c_add_adapter(&priv->adapter);
+    if (err) {
+        dev_err(&priv->adapter.dev, "Failed to add SMBus adapter\n");
+        return err;
+    }
+    
+    return 0;
+}
+
+int pi2c_remove(struct platform_device *pldev)
+{
+    struct i2c_device *lddev;
+    dev_dbg(&pldev->dev, "pi2c_remove(pldev = %p '%s')\n", pldev, pldev->name);
+    
+    lddev = (struct i2c_device *)pldev->dev.platform_data;
+    
+    i2c_del_adapter(&lddev->adapter);
+    
+    //TODO: Figure out the right thing to do here...
+    //pci_write_config_byte(dev, SMBHSTCFG, priv->original_hstcfg);
+    //pci_release_region(dev, SMBBAR);
+    //pci_set_drvdata(dev, NULL);
+    
+    //cdev_del(&lddev->cdev);
+    if(lddev != 0) {
+        kfree(lddev);
+        pldev->dev.platform_data = 0;
+    }
+    
+    return 0;
+}
+
+struct platform_driver i2c_plat_driver_i = {
+    .probe      = pi2c_probe,
+    .remove     = pi2c_remove,
+    .driver     = {
+        .name   = KP_DRIVER_NAME_I2C,
+        .owner  = THIS_MODULE,
+    },
+};
+
+module_platform_driver(i2c_plat_driver_i);
 
--- /dev/null
+# SPDX-License-Identifier: GPL-2.0
+
+obj-m += kpc2000_spi.o
+kpc2000_spi-objs := spi_driver.o
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * KP2000 SPI controller driver
+ *
+ * Copyright (C) 2014-2018 Daktronics
+ * Author: Matt Sickler <matt.sickler@daktronics.com>
+ * Very loosely based on spi-omap2-mcspi.c
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/gcd.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/flash.h>
+#include <linux/mtd/partitions.h>
+
+#include "../kpc.h"
+#include "spi_parts.h"
+
+
+/***************
+ * SPI Defines *
+ ***************/
+#define KP_SPI_REG_CONFIG 0x0 /* 0x00 */
+#define KP_SPI_REG_STATUS 0x1 /* 0x08 */
+#define KP_SPI_REG_FFCTRL 0x2 /* 0x10 */
+#define KP_SPI_REG_TXDATA 0x3 /* 0x18 */
+#define KP_SPI_REG_RXDATA 0x4 /* 0x20 */
+
+#define KP_SPI_CLK           48000000
+#define KP_SPI_MAX_FIFODEPTH 64
+#define KP_SPI_MAX_FIFOWCNT  0xFFFF
+
+#define KP_SPI_REG_CONFIG_TRM_TXRX 0
+#define KP_SPI_REG_CONFIG_TRM_RX   1
+#define KP_SPI_REG_CONFIG_TRM_TX   2
+
+#define KP_SPI_REG_STATUS_RXS   0x01
+#define KP_SPI_REG_STATUS_TXS   0x02
+#define KP_SPI_REG_STATUS_EOT   0x04
+#define KP_SPI_REG_STATUS_TXFFE 0x10
+#define KP_SPI_REG_STATUS_TXFFF 0x20
+#define KP_SPI_REG_STATUS_RXFFE 0x40
+#define KP_SPI_REG_STATUS_RXFFF 0x80
+
+
+
+/******************
+ * SPI Structures *
+ ******************/
+struct kp_spi {
+       struct spi_master  *master;
+       u64 __iomem        *base;
+       unsigned long       phys;
+       struct device      *dev;
+       int                 fifo_depth;
+       unsigned int        pin_dir:1;
+};
+
+
+struct kp_spi_controller_state {
+    void __iomem   *base;
+    unsigned long   phys;
+    unsigned char   chip_select;
+    int             word_len;
+    s64             conf_cache;
+};
+
+
+union kp_spi_config {
+    /* use this to access individual elements */
+    struct __attribute__((packed)) spi_config_bitfield {
+        unsigned char pha       : 1; /* spim_clk Phase      */
+        unsigned char pol       : 1; /* spim_clk Polarity   */
+        unsigned char epol      : 1; /* spim_csx Polarity   */
+        unsigned char dpe       : 1; /* Transmission Enable */
+        unsigned char wl        : 5; /* Word Length         */
+        unsigned char           : 3;
+        unsigned char trm       : 2; /* TxRx Mode           */
+        unsigned char cs        : 4; /* Chip Select         */
+        unsigned char wcnt      : 7; /* Word Count          */
+        unsigned char ffen      : 1; /* FIFO Enable         */
+        unsigned char spi_en    : 1; /* SPI Enable          */
+        unsigned char           : 5;
+    } bitfield;
+    /* use this to grab the whole register */
+    u32 reg;
+};
+
+
+
+union kp_spi_status {
+    struct __attribute__((packed)) spi_status_bitfield {
+        unsigned char rx    :  1; /* Rx Status       */
+        unsigned char tx    :  1; /* Tx Status       */
+        unsigned char eo    :  1; /* End of Transfer */
+        unsigned char       :  1;
+        unsigned char txffe :  1; /* Tx FIFO Empty   */
+        unsigned char txfff :  1; /* Tx FIFO Full    */
+        unsigned char rxffe :  1; /* Rx FIFO Empty   */
+        unsigned char rxfff :  1; /* Rx FIFO Full    */
+        unsigned int        : 24;
+    } bitfield;
+    u32 reg;
+};
+
+
+
+union kp_spi_ffctrl {
+    struct __attribute__((packed)) spi_ffctrl_bitfield {
+        unsigned char ffstart :  1; /* FIFO Start */
+        unsigned int          : 31;
+    } bitfield;
+    u32 reg;
+};
+
+
+
+/***************
+ * SPI Helpers *
+ ***************/
+static inline int
+kp_spi_bytes_per_word(int word_len)
+{
+    if (word_len <= 8){
+        return 1;
+    }
+    else if (word_len <= 16) {
+        return 2;
+    }
+    else { /* word_len <= 32 */
+        return 4;
+    }
+}
+
+static inline u64
+kp_spi_read_reg(struct kp_spi_controller_state *cs, int idx)
+{
+    u64 __iomem *addr = cs->base;
+    u64 val;
+
+    addr += idx;
+    if ((idx == KP_SPI_REG_CONFIG) && (cs->conf_cache >= 0)){
+        return cs->conf_cache;
+    }
+    val = readq((void*)addr);
+    return val;
+}
+
+static inline void
+kp_spi_write_reg(struct kp_spi_controller_state *cs, int idx, u64 val)
+{
+    u64 __iomem *addr = cs->base;
+    addr += idx;
+    writeq(val, (void*)addr);
+    if (idx == KP_SPI_REG_CONFIG)
+        cs->conf_cache = val;
+}
+
+static int
+kp_spi_wait_for_reg_bit(struct kp_spi_controller_state *cs, int idx, unsigned long bit)
+{
+    unsigned long timeout;
+    timeout = jiffies + msecs_to_jiffies(1000);
+    while (!(kp_spi_read_reg(cs, idx) & bit)) {
+        if (time_after(jiffies, timeout)) {
+            if (!(kp_spi_read_reg(cs, idx) & bit)) {
+                return -ETIMEDOUT;
+            } else {
+                return 0;
+            }
+        }
+        cpu_relax();
+    }
+    return 0;
+}
+
+static unsigned
+kp_spi_txrx_pio(struct spi_device *spidev, struct spi_transfer *transfer)
+{
+    struct kp_spi_controller_state *cs = spidev->controller_state;
+    unsigned int count = transfer->len;
+    unsigned int c = count;
+    
+    int i;
+    u8 *rx       = transfer->rx_buf;
+    const u8 *tx = transfer->tx_buf;
+    int processed = 0;
+    
+    if (tx) {
+        for (i = 0 ; i < c ; i++) {
+            char val = *tx++;
+            
+            if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_TXS) < 0) {
+                goto out;
+            }
+            
+            kp_spi_write_reg(cs, KP_SPI_REG_TXDATA, val);
+            processed++;
+        }
+    }
+    else if(rx) {
+        for (i = 0 ; i < c ; i++) {
+            char test=0;
+            
+            kp_spi_write_reg(cs, KP_SPI_REG_TXDATA, 0x00);
+            
+            if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_RXS) < 0) {
+                goto out;
+            }
+            
+            test = kp_spi_read_reg(cs, KP_SPI_REG_RXDATA);
+            *rx++ = test;
+            processed++;
+        }
+    }
+    
+    if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_EOT) < 0) {
+        //TODO: Figure out how to abort transaction??  This has never happened in practice though...
+    }
+    
+ out:
+    return processed;
+}
+
+/*****************
+ * SPI Functions *
+ *****************/
+static int
+kp_spi_setup(struct spi_device *spidev)
+{
+    union kp_spi_config sc;
+    struct kp_spi *kpspi = spi_master_get_devdata(spidev->master);
+    struct kp_spi_controller_state *cs;
+    
+    /* setup controller state */
+    cs = spidev->controller_state;
+    if (!cs) {
+        cs = kzalloc(sizeof(*cs), GFP_KERNEL);
+        if(!cs) {
+            return -ENOMEM;
+        }
+        cs->base = kpspi->base;
+        cs->phys = kpspi->phys;
+        cs->chip_select = spidev->chip_select;
+        cs->word_len = spidev->bits_per_word;
+        cs->conf_cache = -1;
+        spidev->controller_state = cs;
+    }
+    
+    /* set config register */
+    sc.bitfield.wl = spidev->bits_per_word - 1;
+    sc.bitfield.cs = spidev->chip_select;
+    sc.bitfield.spi_en = 0;
+    sc.bitfield.trm = 0;
+    sc.bitfield.ffen = 0;
+    kp_spi_write_reg(spidev->controller_state, KP_SPI_REG_CONFIG, sc.reg);
+    return 0;
+}
+
+static int
+kp_spi_transfer_one_message(struct spi_master *master, struct spi_message *m)
+{
+    struct kp_spi_controller_state *cs;
+    struct spi_device   *spidev;
+    struct kp_spi       *kpspi;
+    struct spi_transfer *transfer;
+    union kp_spi_config sc;
+    int status = 0;
+    
+    spidev = m->spi;
+    kpspi = spi_master_get_devdata(master);
+    m->actual_length = 0;
+    m->status = 0;
+    
+    cs = spidev->controller_state;
+    
+    /* reject invalid messages and transfers */
+    if (list_empty(&m->transfers)) {
+        return -EINVAL;
+    }
+    
+    /* validate input */
+    list_for_each_entry(transfer, &m->transfers, transfer_list) {
+        const void *tx_buf = transfer->tx_buf;
+        void       *rx_buf = transfer->rx_buf;
+        unsigned    len = transfer->len;
+        
+        if (transfer->speed_hz > KP_SPI_CLK || (len && !(rx_buf || tx_buf))) {
+            dev_dbg(kpspi->dev, "  transfer: %d Hz, %d %s%s, %d bpw\n",
+                    transfer->speed_hz,
+                    len,
+                    tx_buf ? "tx" : "",
+                    rx_buf ? "rx" : "",
+                    transfer->bits_per_word);
+            dev_dbg(kpspi->dev, "  transfer -EINVAL\n");
+            return -EINVAL;
+        }
+        if (transfer->speed_hz && (transfer->speed_hz < (KP_SPI_CLK >> 15))) {
+            dev_dbg(kpspi->dev, "speed_hz %d below minimum %d Hz\n",
+                    transfer->speed_hz,
+                    KP_SPI_CLK >> 15);
+            dev_dbg(kpspi->dev, "  speed_hz -EINVAL\n");
+            return -EINVAL;
+        }
+    }
+    
+    /* assert chip select to start the sequence*/
+    sc.reg = kp_spi_read_reg(cs, KP_SPI_REG_CONFIG);
+    sc.bitfield.spi_en = 1;
+    kp_spi_write_reg(cs, KP_SPI_REG_CONFIG, sc.reg);
+    
+    /* work */
+    if (kp_spi_wait_for_reg_bit(cs, KP_SPI_REG_STATUS, KP_SPI_REG_STATUS_EOT) < 0) {
+        dev_info(kpspi->dev, "EOT timed out\n");
+        goto out;
+    }
+    
+    /* do the transfers for this message */
+    list_for_each_entry(transfer, &m->transfers, transfer_list) {
+        if (transfer->tx_buf == NULL && transfer->rx_buf == NULL && transfer->len) {
+            status = -EINVAL;
+            break;
+        }
+        
+        /* transfer */
+        if (transfer->len) {
+            unsigned int word_len = spidev->bits_per_word;
+            unsigned count;
+            
+            /* set up the transfer... */
+            sc.reg = kp_spi_read_reg(cs, KP_SPI_REG_CONFIG);
+            
+            /* ...direction */
+            if (transfer->tx_buf) {
+                sc.bitfield.trm = KP_SPI_REG_CONFIG_TRM_TX;
+            }
+            else if (transfer->rx_buf) {
+                sc.bitfield.trm = KP_SPI_REG_CONFIG_TRM_RX;
+            }
+            
+            /* ...word length */
+            if (transfer->bits_per_word) {
+                word_len = transfer->bits_per_word;
+            }
+            cs->word_len = word_len;
+            sc.bitfield.wl = word_len-1;
+            
+            /* ...chip select */
+            sc.bitfield.cs = spidev->chip_select;
+            
+            /* ...and write the new settings */
+            kp_spi_write_reg(cs, KP_SPI_REG_CONFIG, sc.reg);
+            
+            /* do the transfer */
+            count = kp_spi_txrx_pio(spidev, transfer);
+            m->actual_length += count;
+            
+            if (count != transfer->len) {
+                status = -EIO;
+                break;
+            }
+        }
+        
+        if (transfer->delay_usecs) {
+            udelay(transfer->delay_usecs);
+        }
+    }
+    
+    /* de-assert chip select to end the sequence */
+    sc.reg = kp_spi_read_reg(cs, KP_SPI_REG_CONFIG);
+    sc.bitfield.spi_en = 0;
+    kp_spi_write_reg(cs, KP_SPI_REG_CONFIG, sc.reg);
+    
+ out:
+    /* done work */
+    spi_finalize_current_message(master);
+    return 0;
+}
+
+static void
+kp_spi_cleanup(struct spi_device *spidev)
+{
+    struct kp_spi_controller_state *cs = spidev->controller_state;
+    if (cs) {
+        kfree(cs);
+    }
+}
+
+
+
+/******************
+ * Probe / Remove *
+ ******************/
+static int
+kp_spi_probe(struct platform_device *pldev)
+{
+    struct kpc_core_device_platdata *drvdata = (struct kpc_core_device_platdata *)pldev->dev.platform_data;
+    struct spi_master *master = spi_alloc_master(&pldev->dev, sizeof(struct kp_spi));
+    struct kp_spi *kpspi;
+    struct resource *r;
+    int status = 0;
+    int i;
+
+    drvdata = (struct kpc_core_device_platdata *)pldev->dev.platform_data;
+    if (!drvdata){
+        dev_err(&pldev->dev, "kp_spi_probe: platform_data is NULL!\n");
+        return -ENODEV;
+    }
+    
+    master = spi_alloc_master(&pldev->dev, sizeof(struct kp_spi));
+    if (master == NULL) {
+        dev_err(&pldev->dev, "kp_spi_probe: master allocation failed\n");
+        return -ENOMEM;
+    }
+    
+    /* set up the spi functions */
+    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
+    master->bits_per_word_mask = (unsigned int)SPI_BPW_RANGE_MASK(4, 32);
+    master->setup = kp_spi_setup;
+    master->transfer_one_message = kp_spi_transfer_one_message;
+    master->cleanup = kp_spi_cleanup;
+    
+    platform_set_drvdata(pldev, master);
+    
+    kpspi = spi_master_get_devdata(master);
+    kpspi->master = master;
+    kpspi->dev = &pldev->dev;
+    
+    master->num_chipselect = 4;
+    if (pldev->id != -1) {
+        master->bus_num = pldev->id;
+    }
+    kpspi->pin_dir = 0;
+    
+    r = platform_get_resource(pldev, IORESOURCE_MEM, 0);
+    if (r == NULL) {
+        dev_err(&pldev->dev, "kp_spi_probe: Unable to get platform resources\n");
+        status = -ENODEV;
+        goto free_master;
+    }
+    
+    kpspi->phys = (unsigned long)ioremap_nocache(r->start, r->end - r->start);
+    kpspi->base = (u64 __iomem *)kpspi->phys;
+    
+    status = spi_register_master(master);
+    if (status < 0) {
+        dev_err(&pldev->dev, "Unable to register SPI device\n");
+        goto free_master;
+    }
+    
+    /* register the slave boards */
+    #define NEW_SPI_DEVICE_FROM_BOARD_INFO_TABLE(table) \
+        for (i = 0 ; i < ARRAY_SIZE(table) ; i++) { \
+            spi_new_device(master, &(table[i])); \
+        }
+    
+    switch ((drvdata->card_id & 0xFFFF0000) >> 16){
+        case PCI_DEVICE_ID_DAKTRONICS_KADOKA_P2KR0:
+            NEW_SPI_DEVICE_FROM_BOARD_INFO_TABLE(p2kr0_board_info);
+            break;
+        default:
+            dev_err(&pldev->dev, "Unknown hardware, cant know what partition table to use!\n");
+            goto free_master;
+            break;
+    }
+    
+    return status;
+
+ free_master:
+    spi_master_put(master);
+    return status;
+}
+
+static int
+kp_spi_remove(struct platform_device *pldev)
+{
+    struct spi_master * master = platform_get_drvdata(pldev);
+    spi_unregister_master(master);
+    return 0;
+}
+
+
+static struct platform_driver kp_spi_driver = {
+    .driver = {
+        .name =     KP_DRIVER_NAME_SPI,
+        .owner =    THIS_MODULE,
+    },
+    .probe =    kp_spi_probe,
+    .remove =   kp_spi_remove,
+};
+
+module_platform_driver(kp_spi_driver);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:kp_spi");
 
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef __KPC_SPI_SPI_PARTS_H__
+#define __KPC_SPI_SPI_PARTS_H__
+
+static struct mtd_partition p2kr0_spi0_parts[] = {
+    { .name = "SLOT_0",    .size = 7798784,          .offset = 0,                 },
+    { .name = "SLOT_1",    .size = 7798784,          .offset = MTDPART_OFS_NXTBLK },
+    { .name = "SLOT_2",    .size = 7798784,          .offset = MTDPART_OFS_NXTBLK },
+    { .name = "SLOT_3",    .size = 7798784,          .offset = MTDPART_OFS_NXTBLK },
+    { .name = "CS0_EXTRA", .size = MTDPART_SIZ_FULL, .offset = MTDPART_OFS_NXTBLK }
+};
+static struct mtd_partition p2kr0_spi1_parts[] = {
+    { .name = "SLOT_4",    .size   = 7798784,          .offset = 0,                 },
+    { .name = "SLOT_5",    .size   = 7798784,          .offset = MTDPART_OFS_NXTBLK },
+    { .name = "SLOT_6",    .size   = 7798784,          .offset = MTDPART_OFS_NXTBLK },
+    { .name = "SLOT_7",    .size   = 7798784,          .offset = MTDPART_OFS_NXTBLK },
+    { .name = "CS1_EXTRA", .size   = MTDPART_SIZ_FULL, .offset = MTDPART_OFS_NXTBLK }
+};
+
+static struct flash_platform_data p2kr0_spi0_pdata = {
+    .name = "SPI0",
+    .nr_parts = ARRAY_SIZE(p2kr0_spi0_parts),
+    .parts = p2kr0_spi0_parts,
+};
+static struct flash_platform_data p2kr0_spi1_pdata = {
+    .name = "SPI1",
+    .nr_parts = ARRAY_SIZE(p2kr0_spi1_parts),
+    .parts = p2kr0_spi1_parts,
+};
+
+static struct spi_board_info p2kr0_board_info[] = {
+    {
+        .modalias = "n25q256a11",
+        .bus_num = 1,
+        .chip_select = 0,
+        .mode = SPI_MODE_0,
+        .platform_data = &p2kr0_spi0_pdata
+    },
+    {
+        .modalias = "n25q256a11",
+        .bus_num = 1,
+        .chip_select = 1,
+        .mode = SPI_MODE_0,
+        .platform_data = &p2kr0_spi1_pdata
+    },
+};
+
+#endif