staging: kpc2000: add initial set of Daktronics drivers
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 19 Apr 2019 18:42:05 +0000 (20:42 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 20 Apr 2019 21:04:58 +0000 (23:04 +0200)
These drivers have been outside of the kernel tree since the 2.x days,
and it's time to bring them into the tree so they can get properly
cleaned up.

This first dump of drivers is based on a tarball Matt gave to me, minus
an odd "dma" driver that I could not get to build at all.  I renamed a
few files, added the proper SPDX lines to it, added Kconfig entries and
tied it into the kernel build.  I also fixed up a number of initial
obvious kernel build warnings, but left the odd bitfield warning that
gcc is spitting out, as I'm not quite sure what to do about that.

There's loads of low-hanging coding style cleanups in here for people to
start attacking, as well as the more obvious logic and api cleanups as
well.

Cc: Matt Sickler <Matt.Sickler@daktronics.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
20 files changed:
drivers/staging/Kconfig
drivers/staging/Makefile
drivers/staging/kpc2000/Kconfig [new file with mode: 0644]
drivers/staging/kpc2000/Makefile [new file with mode: 0644]
drivers/staging/kpc2000/TODO [new file with mode: 0644]
drivers/staging/kpc2000/kpc.h [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/Makefile [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/cell_probe.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/core.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/dma_common_defs.h [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/fileops.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/kp2000_module.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/pcie.h [new file with mode: 0644]
drivers/staging/kpc2000/kpc2000/uapi.h [new file with mode: 0644]
drivers/staging/kpc2000/kpc_i2c/Makefile [new file with mode: 0644]
drivers/staging/kpc2000/kpc_i2c/fileops.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc_i2c/i2c_driver.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc_spi/Makefile [new file with mode: 0644]
drivers/staging/kpc2000/kpc_spi/spi_driver.c [new file with mode: 0644]
drivers/staging/kpc2000/kpc_spi/spi_parts.h [new file with mode: 0644]

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