| // 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 <linux/io.h> |
| #include <linux/io-64-nonatomic-lo-hi.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 & 0xFFF0000000000000UL) >> 52); |
| cte->offset = ((read_val & 0x00000000FFFF0000UL) >> 16) * 4096; |
| cte->length = ((read_val & 0x0000FFFF00000000UL) >> 32) * 8; |
| cte->s2c_dma_present = ((read_val & 0x0008000000000000UL) >> 51); |
| cte->s2c_dma_channel_num = ((read_val & 0x0007000000000000UL) >> 48); |
| cte->c2s_dma_present = ((read_val & 0x0000000000008000UL) >> 15); |
| cte->c2s_dma_channel_num = ((read_val & 0x0000000000007000UL) >> 12); |
| cte->irq_count = ((read_val & 0x0000000000000C00UL) >> 10); |
| cte->irq_base_num = ((read_val & 0x00000000000003F8UL) >> 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; |
| } |
| } |
| |
| static int probe_core_basic(unsigned int core_num, struct kp2000_device *pcard, |
| char *name, const struct core_table_entry cte) |
| { |
| struct mfd_cell cell = { .id = core_num, .name = name }; |
| struct resource resources[2]; |
| |
| 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.num_resources = 2; |
| |
| memset(&resources, 0, sizeof(resources)); |
| |
| 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 offset_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%u\n", kudev->cte.offset); |
| } |
| static DEVICE_ATTR_RO(offset); |
| |
| static ssize_t size_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%u\n", kudev->cte.length); |
| } |
| static DEVICE_ATTR_RO(size); |
| |
| static ssize_t type_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%u\n", kudev->cte.type); |
| } |
| static DEVICE_ATTR_RO(type); |
| |
| static ssize_t s2c_dma_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| if (!kudev->cte.s2c_dma_present) |
| return sprintf(buf, "%s", "not present\n"); |
| |
| return sprintf(buf, "%u\n", kudev->cte.s2c_dma_channel_num); |
| } |
| static DEVICE_ATTR_RO(s2c_dma); |
| |
| static ssize_t c2s_dma_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| if (!kudev->cte.c2s_dma_present) |
| return sprintf(buf, "%s", "not present\n"); |
| |
| return sprintf(buf, "%u\n", kudev->cte.c2s_dma_channel_num); |
| } |
| static DEVICE_ATTR_RO(c2s_dma); |
| |
| static ssize_t irq_count_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%u\n", kudev->cte.irq_count); |
| } |
| static DEVICE_ATTR_RO(irq_count); |
| |
| static ssize_t irq_base_num_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%u\n", kudev->cte.irq_base_num); |
| } |
| static DEVICE_ATTR_RO(irq_base_num); |
| |
| static ssize_t core_num_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct kpc_uio_device *kudev = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%u\n", kudev->core_num); |
| } |
| static DEVICE_ATTR_RO(core_num); |
| |
| struct attribute *kpc_uio_class_attrs[] = { |
| &dev_attr_offset.attr, |
| &dev_attr_size.attr, |
| &dev_attr_type.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 = BIT_ULL(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)) { |
| /* Clear the active flag */ |
| writeq(BIT_ULL(kudev->cte.irq_base_num), |
| kudev->pcard->sysinfo_regs_base + REG_INTERRUPT_ACTIVE); |
| 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; |
| |
| mutex_lock(&pcard->sem); |
| mask = readq(pcard->sysinfo_regs_base + REG_INTERRUPT_MASK); |
| if (irq_on) |
| mask &= ~(BIT_ULL(kudev->cte.irq_base_num)); |
| else |
| mask |= BIT_ULL(kudev->cte.irq_base_num); |
| writeq(mask, pcard->sysinfo_regs_base + REG_INTERRUPT_MASK); |
| mutex_unlock(&pcard->sem); |
| |
| return 0; |
| } |
| |
| static 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(*kudev), GFP_KERNEL); |
| if (!kudev) |
| 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, "%s: device_create failed!\n", |
| __func__); |
| kfree(kudev); |
| return -ENODEV; |
| } |
| dev_set_drvdata(kudev->dev, kudev); |
| |
| rv = uio_register_device(kudev->dev, &kudev->uioinfo); |
| if (rv) { |
| dev_err(&pcard->pdev->dev, "%s: failed uio_register_device: %d\n", |
| __func__, rv); |
| put_device(kudev->dev); |
| kfree(kudev); |
| 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 = { .id = engine_num }; |
| struct resource resources[2]; |
| |
| cell.platform_data = NULL; |
| cell.pdata_size = 0; |
| cell.name = KP_DRIVER_NAME_DMA_CONTROLLER; |
| cell.num_resources = 2; |
| |
| memset(&resources, 0, sizeof(resources)); |
| |
| 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, "%s: failed to add a DMA Engine: %d\n", |
| __func__, 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; |
| |
| 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) |
| continue; |
| |
| 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, |
| "%s: failed to add core %d: %d\n", |
| __func__, i, err); |
| goto error; |
| } |
| 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, "%s: failed to add board_info core: %d\n", |
| __func__, err); |
| goto error; |
| } |
| |
| return 0; |
| |
| error: |
| kp2000_remove_cores(pcard); |
| mfd_remove_devices(PCARD_TO_DEV(pcard)); |
| return err; |
| } |
| |
| 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); |
| } |
| } |
| |