// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2016 CERN * Author: Federico Vaga <federico.vaga@cern.ch> * * Driver for the HT-VIC IRQ controller */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/version.h> #include <linux/irqdomain.h> #include <linux/irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/io.h> #include "htvic.h" static int htvic_dbg_info(struct seq_file *s, void *offset) { struct htvic_device *htvic = s->private; int i; seq_printf(s, "%s:\n",dev_name(&htvic->pdev->dev)); seq_printf(s, " redirect: %d\n", platform_get_irq(htvic->pdev, 0)); seq_printf(s, " irq-mapping:\n"); for (i = 0; i < VIC_MAX_VECTORS; ++i) { seq_printf(s, " - hardware: %d\n", i); seq_printf(s, " linux: %d\n", irq_find_mapping(htvic->domain, i)); } return 0; } static int htvic_dbg_info_open(struct inode *inode, struct file *file) { struct htvic_device *htvic = inode->i_private; return single_open(file, htvic_dbg_info, htvic); } static const struct file_operations htvic_dbg_info_ops = { .owner = THIS_MODULE, .open = htvic_dbg_info_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int htvic_dbg_reg(struct seq_file *s, void *offset) { struct htvic_device *htvic = s->private; uint32_t val; void *addr; int i; #define VIC_REG_N 8 for (i = 0, addr = htvic->kernel_va; i < VIC_REG_N ; ++i, addr +=4) { val = htvic_ioread(htvic, addr); seq_printf(s, "%p = 0x%08x\n", addr, val); } for (i = 0, addr = htvic->kernel_va + VIC_IVT_RAM_BASE; i < VIC_MAX_VECTORS; ++i, addr +=4) { val = htvic_ioread(htvic, addr); seq_printf(s, "%p = 0x%08x\n", addr, val); } return 0; } static int htvic_dbg_reg_open(struct inode *inode, struct file *file) { struct htvic_device *htvic = inode->i_private; return single_open(file, htvic_dbg_reg, htvic); } static const struct file_operations htvic_dbg_reg_ops = { .owner = THIS_MODULE, .open = htvic_dbg_reg_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static ssize_t htvic_dbg_swirq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct htvic_device *vic = file->private_data; htvic_iowrite(vic, 1, vic->kernel_va + VIC_REG_SWIR); return count; } static int htvic_dbg_swirq_open(struct inode *inode, struct file *file) { struct htvic_device *htvic = inode->i_private; file->private_data = htvic; return 0; } static const struct file_operations htvic_dbg_swirq_ops = { .owner = THIS_MODULE, .open = htvic_dbg_swirq_open, .write = htvic_dbg_swirq_write, }; /** * It initializes the debugfs interface * @htvic: IRQ controler instance * * Return: 0 on success, otherwise a negative error number */ static int htvic_debug_init(struct htvic_device *htvic) { htvic->dbg_dir = debugfs_create_dir(dev_name(&htvic->pdev->dev), NULL); if (IS_ERR_OR_NULL(htvic->dbg_dir)) { dev_err(&htvic->pdev->dev, "Cannot create debugfs directory (%ld)\n", PTR_ERR(htvic->dbg_dir)); return PTR_ERR(htvic->dbg_dir); } htvic->dbg_info = debugfs_create_file(HTVIC_DBG_INFO_NAME, 0444, htvic->dbg_dir, htvic, &htvic_dbg_info_ops); if (IS_ERR_OR_NULL(htvic->dbg_info)) { dev_err(&htvic->pdev->dev, "Cannot create debugfs file \"%s\" (%ld)\n", HTVIC_DBG_INFO_NAME, PTR_ERR(htvic->dbg_info)); return PTR_ERR(htvic->dbg_info); } htvic->dbg_reg = debugfs_create_file(HTVIC_DBG_REG_NAME, 0444, htvic->dbg_dir, htvic, &htvic_dbg_reg_ops); if (IS_ERR_OR_NULL(htvic->dbg_reg)) { dev_err(&htvic->pdev->dev, "Cannot create debugfs file \"%s\" (%ld)\n", HTVIC_DBG_REG_NAME, PTR_ERR(htvic->dbg_reg)); return PTR_ERR(htvic->dbg_reg); } htvic->dbg_swirq = debugfs_create_file(HTVIC_DBG_SWIRQ_NAME, 0200, htvic->dbg_dir, htvic, &htvic_dbg_swirq_ops); if (IS_ERR_OR_NULL(htvic->dbg_swirq)) { dev_err(&htvic->pdev->dev, "Cannot create debugfs file \"%s\" (%ld)\n", HTVIC_DBG_SWIRQ_NAME, PTR_ERR(htvic->dbg_swirq)); return PTR_ERR(htvic->dbg_reg); } return 0; } /** * It removes the debugfs interface * @htvic: IRQ controler instance */ static void htvic_debug_exit(struct htvic_device *htvic) { if (htvic->dbg_dir) debugfs_remove_recursive(htvic->dbg_dir); } /** * End of interrupt for the VIC. In case of level interrupt, * there is no way to delegate this to the kernel */ static void htvic_eoi(struct irq_data *d) { struct htvic_device *vic = irq_data_get_irq_chip_data(d); /* * Any write operation acknowledges the pending interrupt. * Then, VIC advances to another pending interrupt(s) or * releases the master interrupt output. */ htvic_iowrite(vic, 1, vic->kernel_va + VIC_REG_EOIR); } static void htvic_mask_disable_reg(struct irq_data *d) { struct htvic_device *htvic = irq_data_get_irq_chip_data(d); htvic_iowrite(htvic, 1 << d->hwirq, htvic->kernel_va + VIC_REG_IDR); } static void htvic_unmask_enable_reg(struct irq_data *d) { struct htvic_device *htvic = irq_data_get_irq_chip_data(d); htvic_iowrite(htvic, 1 << d->hwirq, htvic->kernel_va + VIC_REG_IER); } static void htvic_irq_ack(struct irq_data *d) { } /** * */ static unsigned int htvic_irq_startup(struct irq_data *d) { struct htvic_device *vic = irq_data_get_irq_chip_data(d); int ret; ret = try_module_get(vic->pdev->dev.driver->owner); if (ret == 0) { /* 0 fail, 1 success */ dev_err(&vic->pdev->dev, "Cannot pin the \"%s\" driver. Something really wrong is going on\n", vic->pdev->dev.driver->name); return 1; } htvic_unmask_enable_reg(d); return 0; } /** * Executed when a driver does `free_irq()`. */ static void htvic_irq_shutdown(struct irq_data *d) { struct htvic_device *vic = irq_data_get_irq_chip_data(d); htvic_mask_disable_reg(d); module_put(vic->pdev->dev.driver->owner); } static int htvic_irq_set_type(struct irq_data *d, unsigned int flow_type) { struct htvic_device *vic = irq_data_get_irq_chip_data(d); /* We support only levels */ if (!(flow_type & IRQ_TYPE_LEVEL_MASK)) { dev_err(&vic->pdev->dev, "%s: unsopported type 0x%x\n", __func__, flow_type); return -EINVAL; } return IRQ_SET_MASK_OK; } static struct irq_chip htvic_chip = { .name = "HT-VIC", .irq_startup = htvic_irq_startup, .irq_shutdown = htvic_irq_shutdown, .irq_ack = htvic_irq_ack, .irq_eoi = htvic_eoi, .irq_mask_ack = htvic_mask_disable_reg, .irq_mask = htvic_mask_disable_reg, .irq_unmask = htvic_unmask_enable_reg, .irq_set_type = htvic_irq_set_type, }; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) static int htvic_irq_domain_select(struct irq_domain *d, struct irq_fwspec *fwspec, enum irq_domain_bus_token bus_token) { struct htvic_device *htvic = d->host_data; /* * FIXME this should point to htvic->pdev->dev.parent. Today it is not * a problem for CERN-like installations, so we leave it like this * so that the WR-stating kit works. */ struct device *dev = &htvic->pdev->dev; struct device *req_dev; if(fwspec->param_count != 2) return 0; req_dev = (struct device *) ((((unsigned long) fwspec->param[0]) << 32) | (((unsigned long) fwspec->param[1]) & 0xFFFFFFFF)); return (dev == req_dev); } #endif /** * Given the hardware IRQ and the Linux IRQ number (virtirq), configure the * Linux IRQ number in order to handle properly the incoming interrupts * on the hardware IRQ line. */ static int htvic_irq_domain_map(struct irq_domain *h, unsigned int virtirq, irq_hw_number_t hwirq) { struct htvic_device *htvic = h->host_data; irq_set_chip_data(virtirq, htvic); irq_set_chip(virtirq, &htvic_chip); irq_set_handler(virtirq, handle_level_irq); /* not really used now */ /* all handlers are directly nested */ irq_set_nested_thread(virtirq, 1); /* * It MUST be no-thread because the VIC EOI must occur AFTER * the device handler ack its signal. Any way the interrupt from * the carrier is already threaded (most likely, if not we will * see problems) */ return 0; } static struct irq_domain_ops htvic_irq_domain_ops = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) .select = htvic_irq_domain_select, #endif .map = htvic_irq_domain_map, }; /** * Mapping of HTVIC irqs to Linux irqs using linear IRQ domain */ static int htvic_irq_mapping(struct htvic_device *htvic) { int i, irq; htvic->domain = irq_domain_add_linear((void *)&htvic->pdev->dev, VIC_MAX_VECTORS, &htvic_irq_domain_ops, htvic); if (!htvic->domain) return -ENOMEM; #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0) htvic->domain->name = kasprintf(GFP_KERNEL, "%s", dev_name(&htvic->pdev->dev)); #endif /* Create the mapping between HW irq and virtual IRQ number */ for (i = 0; i < VIC_MAX_VECTORS; ++i) { htvic->hwid[i] = htvic_ioread(htvic, htvic->kernel_va + VIC_IVT_RAM_BASE + 4 * i); htvic_iowrite(htvic, i, htvic->kernel_va + VIC_IVT_RAM_BASE + 4 * i); irq = irq_create_mapping(htvic->domain, i); if (irq <= 0) goto out; } return 0; out: irq_domain_remove(htvic->domain); return -EPERM; } /** * Check if the platform is providing all the necessary information * for the HTVIC to work properly. * * The HTVIC needs the following informations: * - a Linux IRQ number where it should attach itself * - a virtual address where to find the component */ static int htvic_validation(struct platform_device *pdev) { struct resource *r; r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!r) { dev_err(&pdev->dev, "Carrier IRQ number is missing\n"); return -EINVAL; } if (!(r->flags & (IORESOURCE_IRQ_HIGHEDGE | IORESOURCE_IRQ_LOWEDGE | IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_LOWLEVEL))) { dev_err(&pdev->dev, "Edge/Level High/Low information missing\n"); return -EINVAL; } r = platform_get_resource(pdev, IORESOURCE_MEM, HTVIC_MEM_BASE); if (!r) { dev_err(&pdev->dev, "VIC base address is missing\n"); return -EINVAL; } return 0; } /** * It acks any pending interrupt in order to avoid to bring the HTVIC * to a stable status (possibly) */ static inline void htvic_ack_pending(struct htvic_device *htvic) { while (htvic_ioread(htvic, htvic->kernel_va + VIC_REG_RISR)) htvic_iowrite(htvic, 1, htvic->kernel_va + VIC_REG_EOIR); } /** * This is the place to re-route interrupts to the proper handler */ static irqreturn_t htvic_handler(int irq, void *arg) { struct htvic_device *htvic = arg; u32 risr; risr = htvic_ioread(htvic, htvic->kernel_va + VIC_REG_RISR); if (!risr) /* Nothing to do - not for us */ return IRQ_NONE; do { unsigned int cascade_irq; uint32_t vect; vect = htvic_ioread(htvic, htvic->kernel_va + VIC_REG_VAR) & 0xFF; if (WARN(vect >= VIC_MAX_VECTORS, "Invalid vector number %d\n", vect)) return IRQ_HANDLED; cascade_irq = irq_find_mapping(htvic->domain, vect); dev_dbg(&htvic->pdev->dev, "Raw: 0x%x Vect: 0x%x, IRQ: %d\n", risr, vect, cascade_irq); risr &= ~(1 << vect); /* * Ok, now we execute the handler for the given IRQ. Please * note that this is not the action requested by the device driver * but it is the handler defined during the IRQ mapping */ handle_nested_irq(cascade_irq); /** * ATTENTION here the ack is actually an EOI.The kernel * does not export the handle_edge_eoi_irq() handler which * is the one we need here. The kernel offers us the * handle_edge_irq() which use only the ack() function. * So what actually we need to to is to call the ack * function but not the eoi function. */ htvic_eoi(irq_get_irq_data(cascade_irq)); /* * Read the RISR register again (it could be any other * register) to introduce a delay equivalent to the time * necessary for the VIC to propagate the IRQ status line * to the processor. */ htvic_ioread(htvic, htvic->kernel_va + VIC_REG_RISR); } while(risr); return IRQ_HANDLED; } /** * Create a new instance for this driver. */ static int htvic_probe(struct platform_device *pdev) { struct htvic_device *htvic; const struct resource *r; unsigned long irq_flags = 0; uint32_t ctl; int ret; ret = htvic_validation(pdev); if (ret) return ret; htvic = kzalloc(sizeof(struct htvic_device), GFP_KERNEL); if (!htvic) return -ENOMEM; dev_set_drvdata(&pdev->dev, htvic); htvic->pdev = pdev; /* * TODO theoretically speaking all the confguration should come * from a platform_data structure. Since we do not have it yet, * we proceed this way */ switch(pdev->id_entry->driver_data) { case HTVIC_VER_SPEC: case HTVIC_VER_WRSWI: htvic->memop.read = __htvic_ioread32; htvic->memop.write = __htvic_iowrite32; break; case HTVIC_VER_SVEC: htvic->memop.read = __htvic_ioread32be; htvic->memop.write = __htvic_iowrite32be; break; default: dev_err(&pdev->dev, "Can't identify memory operations\n"); ret = -EINVAL; goto out_memop; } r = platform_get_resource(pdev, IORESOURCE_MEM, HTVIC_MEM_BASE); htvic->kernel_va = ioremap(r->start, resource_size(r)); /* Disable the VIC during the configuration */ htvic_iowrite(htvic, 0, htvic->kernel_va + VIC_REG_CTL); /* Disable also all interrupt lines */ htvic_iowrite(htvic, ~0, htvic->kernel_va + VIC_REG_IDR); /* Ack any pending interrupt */ htvic_ack_pending(htvic); ret = htvic_irq_mapping(htvic); if (ret) goto out_map; /* VIC configuration */ ctl = 0; ctl |= VIC_CTL_ENABLE; irq_flags |= IRQF_SHARED; switch (pdev->id_entry->driver_data) { case HTVIC_VER_SPEC: ctl |= VIC_CTL_POL; ctl |= VIC_CTL_EMU_EDGE; ctl |= VIC_CTL_EMU_LEN_W(250); irq_flags |= IRQF_TRIGGER_HIGH; break; case HTVIC_VER_SVEC: ctl |= VIC_CTL_POL; irq_flags |= IRQF_TRIGGER_HIGH; /* TODO what if we want and edge using the edge emulator? */ break; case HTVIC_VER_WRSWI: break; default: goto out_ctl; } /* * It depends on the platform and on the IRQ on which we are connecting * but most likely our interrupt handler will be a thread */ htvic->irq = platform_get_irq(htvic->pdev, 0); ret = request_any_context_irq(htvic->irq, htvic_handler, irq_flags, dev_name(&pdev->dev), htvic); if (ret < 0) { dev_err(&pdev->dev, "Can't request IRQ %d (%d)\n", platform_get_irq(htvic->pdev, 0), ret); goto out_req; } htvic_debug_init(htvic); htvic_iowrite(htvic, ctl, htvic->kernel_va + VIC_REG_CTL); return 0; out_req: out_ctl: out_map: out_memop: dev_set_drvdata(&pdev->dev, NULL); kfree(htvic); return ret; } /** * Unload the htvic driver from the platform */ static int htvic_remove(struct platform_device *pdev) { struct htvic_device *htvic = dev_get_drvdata(&pdev->dev); /* struct irq_desc *desc = irq_to_desc(platform_get_irq(htvic->pdev, 0)); */ int i; if (!htvic) return 0; htvic_debug_exit(htvic); /* * Disable all interrupts to prevent spurious interrupt * Disable also the HTVIC component for the very same reason, * but this way on next instance even if we enable the VIC * no interrupt will come unless configured. */ htvic_iowrite(htvic, ~0, htvic->kernel_va + VIC_REG_IDR); htvic_iowrite(htvic, 0, htvic->kernel_va + VIC_REG_CTL); /* * Restore HTVIC vector table with it's original content * Release Linux IRQ number */ for (i = 0; i < VIC_MAX_VECTORS; i++) { htvic_iowrite(htvic, htvic->hwid[i], htvic->kernel_va + VIC_IVT_RAM_BASE + 4 * i); irq_dispose_mapping(irq_find_mapping(htvic->domain, i)); } free_irq(htvic->irq, htvic); /* * Clear the memory and restore flags when needed */ irq_domain_remove(htvic->domain); kfree(htvic); dev_set_drvdata(&pdev->dev, NULL); return 0; } /** * List of supported platform */ static const struct platform_device_id htvic_id_table[] = { { /* SPEC compatible */ .name = "htvic-spec", .driver_data = HTVIC_VER_SPEC, }, { /* SVEC compatible */ .name = "htvic-svec", .driver_data = HTVIC_VER_SVEC, }, { .name = "htvic-wr-swi", .driver_data = HTVIC_VER_WRSWI, }, {}, }; static struct platform_driver htvic_driver = { .driver = { .name = "htvic", .owner = THIS_MODULE, }, .id_table = htvic_id_table, .probe = htvic_probe, .remove = htvic_remove, }; module_platform_driver(htvic_driver); MODULE_AUTHOR("Federico Vaga <federico.vaga@cern.ch>"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("CERN BECOHT VHDL Vector Interrupt Controller - HTVIC"); MODULE_DEVICE_TABLE(platform, htvic_id_table); ADDITIONAL_VERSIONS;