diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-18 15:24:18 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-18 15:24:18 -0700 |
| commit | 8b5d31123f41957fe265deeb03ee87fe62f155a8 (patch) | |
| tree | 55c7871554aee125124bd9beaface3f327706775 | |
| parent | 762fffa407d8d24288513538cf5d17c2c5425258 (diff) | |
| parent | 7f328162a98ee4e10aed42b51a7f8125c6e73384 (diff) | |
Merge tag 'irq-drivers-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull interrupt chip driver updates from Thomas Gleixner:
- Update the realtek-rtl driver to support multiple interrupt domains
and remove hardcoded assumptions about the underlying demultiplex
interrupt
- Remove the unused IMGPDC driver, which got orphaned when the metag
architecture was removed long ago.
- Update the Qualcom PDC interrupt chip driver to support differnent
versions of the IP block, to properly differentiable between direct
and GPIO based interrupts and to utilize pass through mode to the
underlying GIC interrupt.
- Remove redundant dev_err() and dev_err_probe() invocations in various
drivers as that error logging has been moved to devm_request_*_irq().
- Support dynamic MSI-X allocations in the GIC-V3-ITS MSI parent domain
driver. That's important for VFIO pass through devices to avoid
reinitialization of the MSI-X space when extending the vector range.
Such a reinitialization can result is lost interrupts and stale
devices.
- Fix a bunch of resource leaks in various driver error paths
- The usual small improvements all over the place
* tag 'irq-drivers-2026-08-17' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (21 commits)
irqchip/gic-v3-its: Fix grammar and replace a bit number with its symbol
irqchip/gic-v3-its: Prevent leak in its_vpe_irq_domain_alloc()
irqchip/renesas-irqc: Fix generic interrupt chip leak on remove
irqchip/gic-v3-its: Enable dynamic MSI-X allocation
irqchip: Remove redundant dev_err()/dev_err_probe()
irqchip/gic-v5/iwb: Fix stray verb in comment
irqchip/qcom-pdc: Fix kernel doc for qcom_pdc_gic_secondary_set_type()
irqchip/gic-v3-its: Fix its node leak in gic_acpi_parse_madt_its()
irqchip/gic-v3-its: Fix memleak in its_probe_one()
irqchip/qcom-pdc: Configure PDC to pass through mode
irqchip/qcom-pdc: Differentiate between direct SPI and GPIO as SPI
irqchip/qcom-pdc: Move all static variables to struct pdc_desc
irqchip/qcom-pdc: Restructure version support
irqchip/irq-imgpdc: Remove unused driver
irqchip/irq-realtek-rtl: Activate multiple parents
irqchip/irq-realtek-rtl: Allow shuffled interrupt order
irqchip/irq-realtek-rtl: Add a select function
irqchip/irq-realtek-rtl: Add mask for interrupt handling
irqchip/irq-realtek-rtl: Add interrupt data structure
irqchip/irq-realtek-rtl: Split out parent setup code
...
| -rw-r--r-- | Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml | 5 | ||||
| -rw-r--r-- | drivers/irqchip/Kconfig | 5 | ||||
| -rw-r--r-- | drivers/irqchip/Makefile | 1 | ||||
| -rw-r--r-- | drivers/irqchip/irq-gic-its-msi-parent.c | 3 | ||||
| -rw-r--r-- | drivers/irqchip/irq-gic-v3-its.c | 30 | ||||
| -rw-r--r-- | drivers/irqchip/irq-gic-v5-iwb.c | 2 | ||||
| -rw-r--r-- | drivers/irqchip/irq-imgpdc.c | 495 | ||||
| -rw-r--r-- | drivers/irqchip/irq-qcom-mpm.c | 4 | ||||
| -rw-r--r-- | drivers/irqchip/irq-realtek-rtl.c | 152 | ||||
| -rw-r--r-- | drivers/irqchip/irq-renesas-irqc.c | 1 | ||||
| -rw-r--r-- | drivers/irqchip/irq-renesas-rzt2h.c | 2 | ||||
| -rw-r--r-- | drivers/irqchip/irq-renesas-rzv2h.c | 4 | ||||
| -rw-r--r-- | drivers/irqchip/qcom-pdc.c | 540 |
13 files changed, 568 insertions, 676 deletions
diff --git a/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml index 833a01cdd1b1..5c52e57c6647 100644 --- a/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml +++ b/Documentation/devicetree/bindings/interrupt-controller/realtek,rtl-intc.yaml @@ -32,8 +32,9 @@ properties: "#interrupt-cells": description: - SoC interrupt line index. - const: 1 + First cell is the SoC interrupt line index. Optional second cell + specifies the parent interrupt index to route to. + enum: [1, 2] reg: minItems: 1 diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig index 42f2278a702d..20b77fbc51ee 100644 --- a/drivers/irqchip/Kconfig +++ b/drivers/irqchip/Kconfig @@ -212,11 +212,6 @@ config HISILICON_IRQ_MBIGEN select ARM_GIC_V3 select ARM_GIC_V3_ITS -config IMGPDC_IRQ - bool - select GENERIC_IRQ_CHIP - select IRQ_DOMAIN - config IXP4XX_IRQ bool select IRQ_DOMAIN diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 74912429c59f..ab33cccd8471 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -44,7 +44,6 @@ obj-$(CONFIG_ARMADA_370_XP_IRQ) += irq-armada-370-xp.o obj-$(CONFIG_ATMEL_AIC_IRQ) += irq-atmel-aic-common.o irq-atmel-aic.o obj-$(CONFIG_ATMEL_AIC5_IRQ) += irq-atmel-aic-common.o irq-atmel-aic5.o obj-$(CONFIG_I8259) += irq-i8259.o -obj-$(CONFIG_IMGPDC_IRQ) += irq-imgpdc.o obj-$(CONFIG_IRQ_MIPS_CPU) += irq-mips-cpu.o obj-$(CONFIG_IXP4XX_IRQ) += irq-ixp4xx.o obj-$(CONFIG_JCORE_AIC) += irq-jcore-aic.o diff --git a/drivers/irqchip/irq-gic-its-msi-parent.c b/drivers/irqchip/irq-gic-its-msi-parent.c index b9257103a999..b2b9d2068bb1 100644 --- a/drivers/irqchip/irq-gic-its-msi-parent.c +++ b/drivers/irqchip/irq-gic-its-msi-parent.c @@ -18,7 +18,8 @@ #define ITS_MSI_FLAGS_SUPPORTED (MSI_GENERIC_FLAGS_MASK | \ MSI_FLAG_PCI_MSIX | \ - MSI_FLAG_MULTI_PCI_MSI) + MSI_FLAG_MULTI_PCI_MSI | \ + MSI_FLAG_PCI_MSIX_ALLOC_DYN) static int its_translate_frame_address(struct fwnode_handle *msi_node, phys_addr_t *pa) { diff --git a/drivers/irqchip/irq-gic-v3-its.c b/drivers/irqchip/irq-gic-v3-its.c index 6f5811aae59c..ac697e60a2f9 100644 --- a/drivers/irqchip/irq-gic-v3-its.c +++ b/drivers/irqchip/irq-gic-v3-its.c @@ -163,7 +163,7 @@ struct event_lpi_map { /* * The ITS view of a device - belongs to an ITS, owns an interrupt - * translation table, and a list of interrupts. If it some of its + * translation table, and a list of interrupts. If some of its * LPIs are injected into a guest (GICv4), the event_map.vm field * indicates which one. */ @@ -2501,10 +2501,7 @@ static bool its_parse_indirect_baser(struct its_node *its, /* No need to enable Indirection if memory requirement < (psz*2)bytes */ if ((esz << ids) > (psz * 2)) { - /* - * Find out whether hw supports a single or two-level table by - * table by reading bit at offset '62' after writing '1' to it. - */ + /* Find out whether the hardware supports a single or two-level table */ its_write_baser(its, baser, val | GITS_BASER_INDIRECT); indirect = !!(baser->val & GITS_BASER_INDIRECT); @@ -4592,6 +4589,13 @@ static int its_vpe_init(struct its_vpe *vpe) static void its_vpe_teardown(struct its_vpe *vpe) { + /* + * If vpt_page is NULL, then its_vpe_init() has failed, and + * there is nothing to do as no resource has been allocated. + */ + if (vpe->vpt_page == NULL) + return; + its_vpe_db_proxy_unmap(vpe); its_vpe_id_free(vpe->vpe_id); its_free_pending_table(vpe->vpt_page); @@ -4672,8 +4676,10 @@ static int its_vpe_irq_domain_alloc(struct irq_domain *domain, unsigned int virq irqd_set_resend_when_in_progress(irq_get_irq_data(virq + i)); } - if (err) + if (err) { + its_vpe_teardown(vm->vpes[i]); its_vpe_irq_domain_free(domain, virq, i); + } return err; } @@ -5320,7 +5326,7 @@ static int __init its_probe_one(struct its_node *its) err = its_init_domain(its); if (err) - goto out_free_tables; + goto out_free_collection; raw_spin_lock(&its_lock); list_add(&its->entry, &its_nodes); @@ -5328,6 +5334,8 @@ static int __init its_probe_one(struct its_node *its) return 0; +out_free_collection: + kfree(its->collections); out_free_tables: its_free_tables(its); out_free_cmd: @@ -5739,9 +5747,13 @@ static int __init gic_acpi_parse_madt_its(union acpi_subtable_headers *header, its->flags |= ITS_FLAGS_FORCE_NON_SHAREABLE; err = its_probe_one(its); - if (!err) - return 0; + if (err) + goto probe_err; + + return 0; +probe_err: + its_node_destroy(its); node_err: iort_deregister_domain_token(its_entry->translation_id); dom_err: diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c index 6b02b90d9cce..442c20614dbe 100644 --- a/drivers/irqchip/irq-gic-v5-iwb.c +++ b/drivers/irqchip/irq-gic-v5-iwb.c @@ -143,7 +143,7 @@ static int gicv5_iwb_irq_domain_translate(struct irq_domain *d, struct irq_fwspe return -EINVAL; /* - * param[0] is be the wire + * param[0] is the wire interrupt number * param[1] is the interrupt type */ *hwirq = fwspec->param[0]; diff --git a/drivers/irqchip/irq-imgpdc.c b/drivers/irqchip/irq-imgpdc.c deleted file mode 100644 index 4feef4ab5fec..000000000000 --- a/drivers/irqchip/irq-imgpdc.c +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IMG PowerDown Controller (PDC) - * - * Copyright 2010-2013 Imagination Technologies Ltd. - * - * Exposes the syswake and PDC peripheral wake interrupts to the system. - * - */ - -#include <linux/bitops.h> -#include <linux/interrupt.h> -#include <linux/irqdomain.h> -#include <linux/io.h> -#include <linux/kernel.h> -#include <linux/of.h> -#include <linux/platform_device.h> -#include <linux/spinlock.h> - -/* PDC interrupt register numbers */ - -#define PDC_IRQ_STATUS 0x310 -#define PDC_IRQ_ENABLE 0x314 -#define PDC_IRQ_CLEAR 0x318 -#define PDC_IRQ_ROUTE 0x31c -#define PDC_SYS_WAKE_BASE 0x330 -#define PDC_SYS_WAKE_STRIDE 0x8 -#define PDC_SYS_WAKE_CONFIG_BASE 0x334 -#define PDC_SYS_WAKE_CONFIG_STRIDE 0x8 - -/* PDC interrupt register field masks */ - -#define PDC_IRQ_SYS3 0x08 -#define PDC_IRQ_SYS2 0x04 -#define PDC_IRQ_SYS1 0x02 -#define PDC_IRQ_SYS0 0x01 -#define PDC_IRQ_ROUTE_WU_EN_SYS3 0x08000000 -#define PDC_IRQ_ROUTE_WU_EN_SYS2 0x04000000 -#define PDC_IRQ_ROUTE_WU_EN_SYS1 0x02000000 -#define PDC_IRQ_ROUTE_WU_EN_SYS0 0x01000000 -#define PDC_IRQ_ROUTE_WU_EN_WD 0x00040000 -#define PDC_IRQ_ROUTE_WU_EN_IR 0x00020000 -#define PDC_IRQ_ROUTE_WU_EN_RTC 0x00010000 -#define PDC_IRQ_ROUTE_EXT_EN_SYS3 0x00000800 -#define PDC_IRQ_ROUTE_EXT_EN_SYS2 0x00000400 -#define PDC_IRQ_ROUTE_EXT_EN_SYS1 0x00000200 -#define PDC_IRQ_ROUTE_EXT_EN_SYS0 0x00000100 -#define PDC_IRQ_ROUTE_EXT_EN_WD 0x00000004 -#define PDC_IRQ_ROUTE_EXT_EN_IR 0x00000002 -#define PDC_IRQ_ROUTE_EXT_EN_RTC 0x00000001 -#define PDC_SYS_WAKE_RESET 0x00000010 -#define PDC_SYS_WAKE_INT_MODE 0x0000000e -#define PDC_SYS_WAKE_INT_MODE_SHIFT 1 -#define PDC_SYS_WAKE_PIN_VAL 0x00000001 - -/* PDC interrupt constants */ - -#define PDC_SYS_WAKE_INT_LOW 0x0 -#define PDC_SYS_WAKE_INT_HIGH 0x1 -#define PDC_SYS_WAKE_INT_DOWN 0x2 -#define PDC_SYS_WAKE_INT_UP 0x3 -#define PDC_SYS_WAKE_INT_CHANGE 0x6 -#define PDC_SYS_WAKE_INT_NONE 0x4 - -/** - * struct pdc_intc_priv - private pdc interrupt data. - * @nr_perips: Number of peripheral interrupt signals. - * @nr_syswakes: Number of syswake signals. - * @perip_irqs: List of peripheral IRQ numbers handled. - * @syswake_irq: Shared PDC syswake IRQ number. - * @domain: IRQ domain for PDC peripheral and syswake IRQs. - * @pdc_base: Base of PDC registers. - * @irq_route: Cached version of PDC_IRQ_ROUTE register. - * @lock: Lock to protect the PDC syswake registers and the cached - * values of those registers in this struct. - */ -struct pdc_intc_priv { - unsigned int nr_perips; - unsigned int nr_syswakes; - unsigned int *perip_irqs; - unsigned int syswake_irq; - struct irq_domain *domain; - void __iomem *pdc_base; - - u32 irq_route; - raw_spinlock_t lock; -}; - -static void pdc_write(struct pdc_intc_priv *priv, unsigned int reg_offs, - unsigned int data) -{ - iowrite32(data, priv->pdc_base + reg_offs); -} - -static unsigned int pdc_read(struct pdc_intc_priv *priv, - unsigned int reg_offs) -{ - return ioread32(priv->pdc_base + reg_offs); -} - -/* Generic IRQ callbacks */ - -#define SYS0_HWIRQ 8 - -static unsigned int hwirq_is_syswake(irq_hw_number_t hw) -{ - return hw >= SYS0_HWIRQ; -} - -static unsigned int hwirq_to_syswake(irq_hw_number_t hw) -{ - return hw - SYS0_HWIRQ; -} - -static irq_hw_number_t syswake_to_hwirq(unsigned int syswake) -{ - return SYS0_HWIRQ + syswake; -} - -static struct pdc_intc_priv *irqd_to_priv(struct irq_data *data) -{ - return (struct pdc_intc_priv *)data->domain->host_data; -} - -/* - * perip_irq_mask() and perip_irq_unmask() use IRQ_ROUTE which also contains - * wake bits, therefore we cannot use the generic irqchip mask callbacks as they - * cache the mask. - */ - -static void perip_irq_mask(struct irq_data *data) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - - raw_spin_lock(&priv->lock); - priv->irq_route &= ~data->mask; - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - raw_spin_unlock(&priv->lock); -} - -static void perip_irq_unmask(struct irq_data *data) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - - raw_spin_lock(&priv->lock); - priv->irq_route |= data->mask; - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - raw_spin_unlock(&priv->lock); -} - -static int syswake_irq_set_type(struct irq_data *data, unsigned int flow_type) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - unsigned int syswake = hwirq_to_syswake(data->hwirq); - unsigned int irq_mode; - unsigned int soc_sys_wake_regoff, soc_sys_wake; - - /* translate to syswake IRQ mode */ - switch (flow_type) { - case IRQ_TYPE_EDGE_BOTH: - irq_mode = PDC_SYS_WAKE_INT_CHANGE; - break; - case IRQ_TYPE_EDGE_RISING: - irq_mode = PDC_SYS_WAKE_INT_UP; - break; - case IRQ_TYPE_EDGE_FALLING: - irq_mode = PDC_SYS_WAKE_INT_DOWN; - break; - case IRQ_TYPE_LEVEL_HIGH: - irq_mode = PDC_SYS_WAKE_INT_HIGH; - break; - case IRQ_TYPE_LEVEL_LOW: - irq_mode = PDC_SYS_WAKE_INT_LOW; - break; - default: - return -EINVAL; - } - - raw_spin_lock(&priv->lock); - - /* set the IRQ mode */ - soc_sys_wake_regoff = PDC_SYS_WAKE_BASE + syswake*PDC_SYS_WAKE_STRIDE; - soc_sys_wake = pdc_read(priv, soc_sys_wake_regoff); - soc_sys_wake &= ~PDC_SYS_WAKE_INT_MODE; - soc_sys_wake |= irq_mode << PDC_SYS_WAKE_INT_MODE_SHIFT; - pdc_write(priv, soc_sys_wake_regoff, soc_sys_wake); - - /* and update the handler */ - irq_setup_alt_chip(data, flow_type); - - raw_spin_unlock(&priv->lock); - - return 0; -} - -/* applies to both peripheral and syswake interrupts */ -static int pdc_irq_set_wake(struct irq_data *data, unsigned int on) -{ - struct pdc_intc_priv *priv = irqd_to_priv(data); - irq_hw_number_t hw = data->hwirq; - unsigned int mask = (1 << 16) << hw; - unsigned int dst_irq; - - raw_spin_lock(&priv->lock); - if (on) - priv->irq_route |= mask; - else - priv->irq_route &= ~mask; - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - raw_spin_unlock(&priv->lock); - - /* control the destination IRQ wakeup too for standby mode */ - if (hwirq_is_syswake(hw)) - dst_irq = priv->syswake_irq; - else - dst_irq = priv->perip_irqs[hw]; - irq_set_irq_wake(dst_irq, on); - - return 0; -} - -static void pdc_intc_perip_isr(struct irq_desc *desc) -{ - unsigned int irq = irq_desc_get_irq(desc); - struct pdc_intc_priv *priv; - unsigned int i; - - priv = (struct pdc_intc_priv *)irq_desc_get_handler_data(desc); - - /* find the peripheral number */ - for (i = 0; i < priv->nr_perips; ++i) - if (irq == priv->perip_irqs[i]) - goto found; - - /* should never get here */ - return; -found: - - /* pass on the interrupt */ - generic_handle_domain_irq(priv->domain, i); -} - -static void pdc_intc_syswake_isr(struct irq_desc *desc) -{ - struct pdc_intc_priv *priv; - unsigned int syswake; - unsigned int status; - - priv = (struct pdc_intc_priv *)irq_desc_get_handler_data(desc); - - status = pdc_read(priv, PDC_IRQ_STATUS) & - pdc_read(priv, PDC_IRQ_ENABLE); - status &= (1 << priv->nr_syswakes) - 1; - - for (syswake = 0; status; status >>= 1, ++syswake) { - /* Has this sys_wake triggered? */ - if (!(status & 1)) - continue; - - generic_handle_domain_irq(priv->domain, syswake_to_hwirq(syswake)); - } -} - -static void pdc_intc_setup(struct pdc_intc_priv *priv) -{ - int i; - unsigned int soc_sys_wake_regoff; - unsigned int soc_sys_wake; - - /* - * Mask all syswake interrupts before routing, or we could receive an - * interrupt before we're ready to handle it. - */ - pdc_write(priv, PDC_IRQ_ENABLE, 0); - - /* - * Enable routing of all syswakes - * Disable all wake sources - */ - priv->irq_route = ((PDC_IRQ_ROUTE_EXT_EN_SYS0 << priv->nr_syswakes) - - PDC_IRQ_ROUTE_EXT_EN_SYS0); - pdc_write(priv, PDC_IRQ_ROUTE, priv->irq_route); - - /* Initialise syswake IRQ */ - for (i = 0; i < priv->nr_syswakes; ++i) { - /* set the IRQ mode to none */ - soc_sys_wake_regoff = PDC_SYS_WAKE_BASE + i*PDC_SYS_WAKE_STRIDE; - soc_sys_wake = PDC_SYS_WAKE_INT_NONE - << PDC_SYS_WAKE_INT_MODE_SHIFT; - pdc_write(priv, soc_sys_wake_regoff, soc_sys_wake); - } -} - -static int pdc_intc_probe(struct platform_device *pdev) -{ - struct pdc_intc_priv *priv; - struct device_node *node = pdev->dev.of_node; - struct resource *res_regs; - struct irq_chip_generic *gc; - unsigned int i; - int irq, ret; - u32 val; - - if (!node) - return -ENOENT; - - /* Get registers */ - res_regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res_regs == NULL) { - dev_err(&pdev->dev, "cannot find registers resource\n"); - return -ENOENT; - } - - /* Allocate driver data */ - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - raw_spin_lock_init(&priv->lock); - platform_set_drvdata(pdev, priv); - - /* Ioremap the registers */ - priv->pdc_base = devm_ioremap(&pdev->dev, res_regs->start, - resource_size(res_regs)); - if (!priv->pdc_base) - return -EIO; - - /* Get number of peripherals */ - ret = of_property_read_u32(node, "num-perips", &val); - if (ret) { - dev_err(&pdev->dev, "No num-perips node property found\n"); - return -EINVAL; - } - if (val > SYS0_HWIRQ) { - dev_err(&pdev->dev, "num-perips (%u) out of range\n", val); - return -EINVAL; - } - priv->nr_perips = val; - - /* Get number of syswakes */ - ret = of_property_read_u32(node, "num-syswakes", &val); - if (ret) { - dev_err(&pdev->dev, "No num-syswakes node property found\n"); - return -EINVAL; - } - if (val > SYS0_HWIRQ) { - dev_err(&pdev->dev, "num-syswakes (%u) out of range\n", val); - return -EINVAL; - } - priv->nr_syswakes = val; - - /* Get peripheral IRQ numbers */ - priv->perip_irqs = devm_kcalloc(&pdev->dev, 4, priv->nr_perips, - GFP_KERNEL); - if (!priv->perip_irqs) - return -ENOMEM; - for (i = 0; i < priv->nr_perips; ++i) { - irq = platform_get_irq(pdev, 1 + i); - if (irq < 0) - return irq; - priv->perip_irqs[i] = irq; - } - /* check if too many were provided */ - if (platform_get_irq(pdev, 1 + i) >= 0) { - dev_err(&pdev->dev, "surplus perip IRQs detected\n"); - return -EINVAL; - } - - /* Get syswake IRQ number */ - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - priv->syswake_irq = irq; - - /* Set up an IRQ domain */ - priv->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), 16, &irq_generic_chip_ops, - priv); - if (unlikely(!priv->domain)) { - dev_err(&pdev->dev, "cannot add IRQ domain\n"); - return -ENOMEM; - } - priv->domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; - - /* - * Set up 2 generic irq chips with 2 chip types. - * The first one for peripheral irqs (only 1 chip type used) - * The second one for syswake irqs (edge and level chip types) - */ - ret = irq_alloc_domain_generic_chips(priv->domain, 8, 2, "pdc", - handle_level_irq, 0, 0, - IRQ_GC_INIT_NESTED_LOCK); - if (ret) - goto err_generic; - - /* peripheral interrupt chip */ - - gc = irq_get_domain_generic_chip(priv->domain, 0); - gc->unused = ~(BIT(priv->nr_perips) - 1); - gc->reg_base = priv->pdc_base; - /* - * IRQ_ROUTE contains wake bits, so we can't use the generic versions as - * they cache the mask - */ - gc->chip_types[0].regs.mask = PDC_IRQ_ROUTE; - gc->chip_types[0].chip.irq_mask = perip_irq_mask; - gc->chip_types[0].chip.irq_unmask = perip_irq_unmask; - gc->chip_types[0].chip.irq_set_wake = pdc_irq_set_wake; - - /* syswake interrupt chip */ - - gc = irq_get_domain_generic_chip(priv->domain, 8); - gc->unused = ~(BIT(priv->nr_syswakes) - 1); - gc->reg_base = priv->pdc_base; - - /* edge interrupts */ - gc->chip_types[0].type = IRQ_TYPE_EDGE_BOTH; - gc->chip_types[0].handler = handle_edge_irq; - gc->chip_types[0].regs.ack = PDC_IRQ_CLEAR; - gc->chip_types[0].regs.mask = PDC_IRQ_ENABLE; - gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit; - gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; - gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; - gc->chip_types[0].chip.irq_set_type = syswake_irq_set_type; - gc->chip_types[0].chip.irq_set_wake = pdc_irq_set_wake; - /* for standby we pass on to the shared syswake IRQ */ - gc->chip_types[0].chip.flags = IRQCHIP_MASK_ON_SUSPEND; - - /* level interrupts */ - gc->chip_types[1].type = IRQ_TYPE_LEVEL_MASK; - gc->chip_types[1].handler = handle_level_irq; - gc->chip_types[1].regs.ack = PDC_IRQ_CLEAR; - gc->chip_types[1].regs.mask = PDC_IRQ_ENABLE; - gc->chip_types[1].chip.irq_ack = irq_gc_ack_set_bit; - gc->chip_types[1].chip.irq_mask = irq_gc_mask_clr_bit; - gc->chip_types[1].chip.irq_unmask = irq_gc_mask_set_bit; - gc->chip_types[1].chip.irq_set_type = syswake_irq_set_type; - gc->chip_types[1].chip.irq_set_wake = pdc_irq_set_wake; - /* for standby we pass on to the shared syswake IRQ */ - gc->chip_types[1].chip.flags = IRQCHIP_MASK_ON_SUSPEND; - - /* Set up the hardware to enable interrupt routing */ - pdc_intc_setup(priv); - - /* Setup chained handlers for the peripheral IRQs */ - for (i = 0; i < priv->nr_perips; ++i) { - irq = priv->perip_irqs[i]; - irq_set_chained_handler_and_data(irq, pdc_intc_perip_isr, - priv); - } - - /* Setup chained handler for the syswake IRQ */ - irq_set_chained_handler_and_data(priv->syswake_irq, - pdc_intc_syswake_isr, priv); - - dev_info(&pdev->dev, - "PDC IRQ controller initialised (%u perip IRQs, %u syswake IRQs)\n", - priv->nr_perips, - priv->nr_syswakes); - - return 0; -err_generic: - irq_domain_remove(priv->domain); - return ret; -} - -static void pdc_intc_remove(struct platform_device *pdev) -{ - struct pdc_intc_priv *priv = platform_get_drvdata(pdev); - - for (unsigned int i = 0; i < priv->nr_perips; ++i) - irq_set_chained_handler_and_data(priv->perip_irqs[i], NULL, NULL); - - irq_set_chained_handler_and_data(priv->syswake_irq, NULL, NULL); - - irq_domain_remove(priv->domain); -} - -static const struct of_device_id pdc_intc_match[] = { - { .compatible = "img,pdc-intc" }, - {} -}; - -static struct platform_driver pdc_intc_driver = { - .driver = { - .name = "pdc-intc", - .of_match_table = pdc_intc_match, - }, - .probe = pdc_intc_probe, - .remove = pdc_intc_remove, -}; - -static int __init pdc_intc_init(void) -{ - return platform_driver_register(&pdc_intc_driver); -} -core_initcall(pdc_intc_init); diff --git a/drivers/irqchip/irq-qcom-mpm.c b/drivers/irqchip/irq-qcom-mpm.c index 181320528a47..8c575d7ce8a1 100644 --- a/drivers/irqchip/irq-qcom-mpm.c +++ b/drivers/irqchip/irq-qcom-mpm.c @@ -464,10 +464,8 @@ static int qcom_mpm_probe(struct platform_device *pdev, struct device_node *pare ret = devm_request_irq(dev, irq, qcom_mpm_handler, IRQF_NO_SUSPEND, "qcom_mpm", priv); - if (ret) { - dev_err(dev, "failed to request irq: %d\n", ret); + if (ret) goto remove_domain; - } return 0; diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c index 2ae3be7fa633..c8becb458da2 100644 --- a/drivers/irqchip/irq-realtek-rtl.c +++ b/drivers/irqchip/irq-realtek-rtl.c @@ -25,6 +25,15 @@ #define REG(cpu, x) (realtek_ictl_base[cpu] + x) +struct realtek_ictl_output { + struct fwnode_handle *fwnode; + struct irq_domain *domain; + unsigned int parent_irq; + unsigned int parent_hwirq; + unsigned int index; + u32 mask; +}; + static DEFINE_RAW_SPINLOCK(irq_lock); static void __iomem *realtek_ictl_base[NR_CPUS]; @@ -105,53 +114,141 @@ static struct irq_chip realtek_ictl_irq = { .irq_set_affinity = realtek_ictl_irq_affinity, }; -static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw) +static int intc_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw_irq) { + struct realtek_ictl_output *output = d->host_data; unsigned int cpu; irq_set_chip_and_handler(irq, &realtek_ictl_irq, handle_level_irq); guard(raw_spinlock_irqsave)(&irq_lock); + output->mask |= BIT(hw_irq); for_each_present_cpu(cpu) - write_irr(cpu, hw, 1); + write_irr(cpu, hw_irq, output->parent_hwirq - 1); return 0; } +static int intc_select(struct irq_domain *d, struct irq_fwspec *fwspec, + enum irq_domain_bus_token bus_token) +{ + struct realtek_ictl_output *output = d->host_data; + unsigned int index = 0; + + if (fwspec->fwnode != output->fwnode) + return false; + + if (fwspec->param_count == 2) + index = fwspec->param[1]; + + return index == output->index; +} + static const struct irq_domain_ops irq_domain_ops = { - .map = intc_map, - .xlate = irq_domain_xlate_onecell, + .map = intc_map, + .select = intc_select, + .xlate = irq_domain_xlate_onecell, }; static void realtek_irq_dispatch(struct irq_desc *desc) { + struct realtek_ictl_output *output = irq_desc_get_handler_data(desc); struct irq_chip *chip = irq_desc_get_chip(desc); unsigned int cpu = smp_processor_id(); - struct irq_domain *domain; unsigned long pending; - unsigned int soc_int; + unsigned int hw_irq; chained_irq_enter(chip, desc); - pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR)); + pending = readl(REG(cpu, RTL_ICTL_GIMR)) & readl(REG(cpu, RTL_ICTL_GISR)) & output->mask; if (unlikely(!pending)) { spurious_interrupt(); goto out; } - domain = irq_desc_get_handler_data(desc); - for_each_set_bit(soc_int, &pending, RTL_ICTL_NUM_INPUTS) - generic_handle_domain_irq(domain, soc_int); + for_each_set_bit(hw_irq, &pending, RTL_ICTL_NUM_INPUTS) + generic_handle_domain_irq(output->domain, hw_irq); out: chained_irq_exit(chip, desc); } -static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent) +static int __init realtek_setup_parents(struct device_node *node) { + int p, cnt, err, parent_irq, num_parents = of_irq_count(node); + struct realtek_ictl_output *output; + struct irq_data *parent_data; struct of_phandle_args oirq; struct irq_domain *domain; - int cpu, parent_irq; + + cnt = max(1, num_parents); + output = kcalloc(cnt, sizeof(*output), GFP_KERNEL); + if (!output) + return -ENOMEM; + + for (p = 0; p < cnt; p++) { + if (WARN_ON(!num_parents)) { + /* + * If DT contains no parent interrupts, assume MIPS IRQ 2 (HW0) is + * connected to the first output. This is the case for all known hardware. + */ + oirq.np = of_find_compatible_node(NULL, NULL, + "mti,cpu-interrupt-controller"); + if (!oirq.np) { + err = -EINVAL; + goto err_out; + } + + oirq.args_count = 1; + oirq.args[0] = 2; + parent_irq = irq_create_of_mapping(&oirq); + of_node_put(oirq.np); + } else { + parent_irq = of_irq_get(node, p); + } + + if (parent_irq <= 0) { + err = parent_irq ? parent_irq : -ENODEV; + goto err_out; + } + + parent_data = irq_get_irq_data(parent_irq); + if (!parent_data) { + err = -EINVAL; + goto err_out; + } + + domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, + &irq_domain_ops, &output[p]); + if (!domain) { + err = -ENOMEM; + goto err_out; + } + + output[p].domain = domain; + output[p].fwnode = of_fwnode_handle(node); + output[p].index = p; + output[p].parent_irq = parent_irq; + output[p].parent_hwirq = irqd_to_hwirq(parent_data); + irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, &output[p]); + } + + return 0; + +err_out: + while (p--) { + irq_set_chained_handler_and_data(output[p].parent_irq, NULL, NULL); + irq_domain_remove(output[p].domain); + } + + kfree(output); + + return err; +} + +static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent) +{ + unsigned int cpu; for_each_present_cpu(cpu) { realtek_ictl_base[cpu] = of_iomap(node, cpu); @@ -165,36 +262,7 @@ static int __init realtek_rtl_of_init(struct device_node *node, struct device_no } } - if (WARN_ON(!of_irq_count(node))) { - /* - * If DT contains no parent interrupts, assume MIPS CPU IRQ 2 - * (HW0) is connected to the first output. This is the case for - * all known hardware anyway. "interrupt-map" is deprecated, so - * don't bother trying to parse that. - */ - oirq.np = of_find_compatible_node(NULL, NULL, "mti,cpu-interrupt-controller"); - oirq.args_count = 1; - oirq.args[0] = 2; - - parent_irq = irq_create_of_mapping(&oirq); - - of_node_put(oirq.np); - } else { - parent_irq = of_irq_get(node, 0); - } - - if (parent_irq < 0) - return parent_irq; - else if (!parent_irq) - return -ENODEV; - - domain = irq_domain_create_linear(of_fwnode_handle(node), RTL_ICTL_NUM_INPUTS, &irq_domain_ops, NULL); - if (!domain) - return -ENOMEM; - - irq_set_chained_handler_and_data(parent_irq, realtek_irq_dispatch, domain); - - return 0; + return realtek_setup_parents(node); } IRQCHIP_DECLARE(realtek_rtl_intc, "realtek,rtl-intc", realtek_rtl_of_init); diff --git a/drivers/irqchip/irq-renesas-irqc.c b/drivers/irqchip/irq-renesas-irqc.c index a20a6471b0e4..1ff3535a4617 100644 --- a/drivers/irqchip/irq-renesas-irqc.c +++ b/drivers/irqchip/irq-renesas-irqc.c @@ -176,6 +176,7 @@ static int irqc_probe(struct platform_device *pdev) goto err_runtime_pm_disable; } + p->irq_domain->flags |= IRQ_DOMAIN_FLAG_DESTROY_GC; ret = irq_alloc_domain_generic_chips(p->irq_domain, p->number_of_irqs, 1, "irqc", handle_level_irq, 0, 0, IRQ_GC_INIT_NESTED_LOCK); diff --git a/drivers/irqchip/irq-renesas-rzt2h.c b/drivers/irqchip/irq-renesas-rzt2h.c index e06264add3cc..9086fc3a2043 100644 --- a/drivers/irqchip/irq-renesas-rzt2h.c +++ b/drivers/irqchip/irq-renesas-rzt2h.c @@ -398,7 +398,7 @@ static int rzt2h_icu_request_irqs(struct platform_device *pdev, struct irq_domai ret = devm_request_irq(dev, virq, handler, 0, dev_name(dev), data ?: (void *)(uintptr_t)offset); if (ret) - return dev_err_probe(dev, ret, "Failed to request IRQ %u\n", offset); + return ret; } return 0; diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c index 971ac83eee90..1a4703a353e9 100644 --- a/drivers/irqchip/irq-renesas-rzv2h.c +++ b/drivers/irqchip/irq-renesas-rzv2h.c @@ -816,7 +816,7 @@ static int rzv2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain ret = devm_request_irq(dev, virq, rzv2h_icu_swint_irq, 0, dev_name(dev), (void *)(uintptr_t)i); if (ret) - return dev_err_probe(dev, ret, "Failed to request int-ca55-%u IRQ\n", i); + return ret; } /* Unmask and clear all IP/CA55 error interrupts */ @@ -844,7 +844,7 @@ static int rzv2h_icu_setup_irqs(struct platform_device *pdev, struct irq_domain ret = devm_request_irq(dev, virq, rzv2h_icu_error_irq, 0, dev_name(dev), rzv2h_icu_data); if (ret) - return dev_err_probe(dev, ret, "Failed to request icu-error-ca55 IRQ\n"); + return ret; return 0; } diff --git a/drivers/irqchip/qcom-pdc.c b/drivers/irqchip/qcom-pdc.c index 2014dbb0bc43..ce6d80c7f17a 100644 --- a/drivers/irqchip/qcom-pdc.c +++ b/drivers/irqchip/qcom-pdc.c @@ -20,22 +20,19 @@ #include <linux/spinlock.h> #include <linux/slab.h> #include <linux/types.h> +#include <linux/firmware/qcom/qcom_scm.h> -#define PDC_MAX_GPIO_IRQS 256 -#define PDC_DRV_SIZE 0x10000 - -/* Valid only on HW version < 3.2 */ -#define IRQ_ENABLE_BANK 0x10 -#define IRQ_ENABLE_BANK_MAX (IRQ_ENABLE_BANK + BITS_TO_BYTES(PDC_MAX_GPIO_IRQS)) +#define PDC_MAX_IRQS 256 +#define IRQ_ENABLE_BANK_MAX BITS_TO_BYTES(PDC_MAX_IRQS) #define IRQ_ENABLE_BANK_INDEX_MASK GENMASK(31, 5) #define IRQ_ENABLE_BANK_BIT_MASK GENMASK(4, 0) -#define IRQ_i_CFG 0x110 - -/* Valid only on HW version >= 3.2 */ -#define IRQ_i_CFG_IRQ_ENABLE 3 -#define IRQ_i_CFG_TYPE_MASK GENMASK(2, 0) +/* Secure DRV register to configure the PDC mode via qcom_scm_io_writel() */ +#define PDC_GPIO_INT_CTL_ENABLE 0xb2045e8 +#define PDC_PASS_THROUGH_MODE 0x0 +#define PDC_SECONDARY_MODE 0x1 +#define PDC_DRV_SIZE 0x10000 #define PDC_VERSION_REG 0x1000 #define PDC_VERSION_MAJOR GENMASK(23, 16) #define PDC_VERSION_MINOR GENMASK(15, 8) @@ -46,22 +43,149 @@ /* Notable PDC versions */ #define PDC_VERSION_3_2 PDC_VERSION(3, 2, 0) +#define PDC_VERSION_3_0 PDC_VERSION(3, 0, 0) +#define PDC_VERSION_2_7 PDC_VERSION(2, 7, 0) + +/* + * PDC Hardware registers layout per version: + * + * IRQ_ENABLE_BANK[b], b = 0....BITS_TO_BYTES(PDC_MAX_IRQS) + * IRQ_CFG[n], n = 0....PDC_MAX_IRQS + * + * +---------------------------------------------------------------+ + * | v2.7 | v3.0 | v3.2 | + * |---------------------------------------------------------------| + * | BASE | BASE | BASE | + * |---------------------------------------------------------------| + * | | + * | IRQ_ENABLE_BANK | IRQ_ENABLE_BANK | NA | + * |---------------------------------------------------------------| + * | IRQ_CFG | IRQ_CFG | IRQ_CFG | + * | | | | + * | | | [31:6] Unused | + * | | [31:5] Unused | [5] GPIO_STATUS | + * | | [4] GPIO_STATUS| [4] GPIO_MASK | + * | [31:3] Unused | [3] GPIO_MASK | [3] IRQ_ENABLE | + * | [0:2] Type | [0:2] Type | [0:2] Type | + * |---------------------------------------------------------------| + * | IRQ_PARAM | IRQ_PARAM | IRQ_PARAM | + * | | | + * | [15:8] NUM_GPIO | [15:8] NUM_GPIO | [15:8] NUM_GPIO | + * | [7:0] NUM_SPI | [7:0] NUM_SPI | [7:0] NUM_SPI | + * +---------------------------------------------------------------+ + */ + +/** + * struct pdc_regs: PDC registers location + * + * @irq_en_reg: IRQ_ENABLE_BANK register location + * @irq_cfg_reg: IRQ_CFG register location + * @irq_param_reg: IRQ_PARAM register location + */ +struct pdc_regs { + u32 irq_en_reg; + u32 irq_cfg_reg; + u32 irq_param_reg; +}; + +/** + * struct pdc_irq_cfg: bit fields for PDC IRQ_CFG register + * + * @gpio_irq_sts: bit number for GPIO_STATUS field + * @gpio_irq_mask: bit number for GPIO_MASK field + * @irq_enable: bit number for IRQ_ENABLE field + * @irq_type: GENMASK for IRQ_TYPE field + */ +struct pdc_irq_cfg { + u32 gpio_irq_sts; + u32 gpio_irq_mask; + u32 irq_enable; + u32 irq_type; +}; + +/** + * struct pdc_desc: PDC driver state + * + * @base: PDC base register for DRV2 / HLOS + * @prev_base: PDC DRV1 base, applicable only for x1e RTL bug. + * @version: PDC version + * @num_spis: Total number of direct SPI interrupts + * @region: PDC interrupt continuous range + * @region_cnt: Total PDC ranges + * @mode: PDC_PASS_THROUGH_MODE or PDC_SECONDARY_MODE + * @x1e_quirk: x1e H/W Bug handling + * @lock: lock for IRQ_ENABLE_BANK protection + * @regs: PDC regs (IRQ_ENABLE_BANK and IRQ_CFG) + * @cfg_fields: Fields of IRQ_CFG reg + * @enable_intr: pointer to enable function based on PDC version + * @unmask_gpio: pointer to GPIO irq unmask function + * @clear_gpio: pointer to GPIO irq clear function + */ +struct pdc_desc { + void __iomem *base; + void __iomem *prev_base; + u32 version; + u32 num_spis; + + struct pdc_pin_region *region; + int region_cnt; + + bool x1e_quirk; + + u8 mode; + raw_spinlock_t lock; + + const struct pdc_regs *regs; + const struct pdc_irq_cfg *cfg_fields; + + void (*enable_intr)(int pin_out, bool on); + void (*unmask_gpio)(int pin_out, bool on); + void (*clear_gpio)(int pin_out); +}; + +static const struct pdc_regs pdc_v3_2 = { + .irq_cfg_reg = 0x110, + .irq_param_reg = 0x100c, +}; + +static const struct pdc_irq_cfg pdc_cfg_v3_2 = { + .gpio_irq_sts = 5, + .gpio_irq_mask = 4, + .irq_enable = 3, + .irq_type = GENMASK(2, 0), +}; + +static const struct pdc_regs pdc_v3_0 = { + .irq_en_reg = 0x10, + .irq_cfg_reg = 0x110, + .irq_param_reg = 0x100c, +}; + +static const struct pdc_irq_cfg pdc_cfg_v3_0 = { + .gpio_irq_sts = 4, + .gpio_irq_mask = 3, + .irq_type = GENMASK(2, 0), +}; + +static const struct pdc_regs pdc_v2_7 = { + .irq_en_reg = 0x10, + .irq_cfg_reg = 0x110, + .irq_param_reg = 0x100c, +}; + +static const struct pdc_irq_cfg pdc_cfg_v2_7 = { + .irq_type = GENMASK(2, 0), +}; struct pdc_pin_region { - u32 pin_base; - u32 parent_base; - u32 cnt; + u32 pin_base; + u32 parent_base; + u32 cnt; }; #define pin_to_hwirq(r, p) ((r)->parent_base + (p) - (r)->pin_base) -static DEFINE_RAW_SPINLOCK(pdc_lock); -static void __iomem *pdc_base; -static void __iomem *pdc_prev_base; -static struct pdc_pin_region *pdc_region; -static int pdc_region_cnt; -static unsigned int pdc_version; -static bool pdc_x1e_quirk; +static struct pdc_desc *pdc; static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val) { @@ -70,12 +194,21 @@ static void pdc_base_reg_write(void __iomem *base, int reg, u32 i, u32 val) static void pdc_reg_write(int reg, u32 i, u32 val) { - pdc_base_reg_write(pdc_base, reg, i, val); + pdc_base_reg_write(pdc->base, reg, i, val); } static u32 pdc_reg_read(int reg, u32 i) { - return readl_relaxed(pdc_base + reg + i * sizeof(u32)); + return readl_relaxed(pdc->base + reg + i * sizeof(u32)); +} + +static inline bool pdc_pin_is_gpio(int pin_out) +{ + /* + * PDC allocates direct SPIs at the beginning and + * all GPIOs as SPIs are allocated after direct SPIs. + */ + return pin_out >= pdc->num_spis; } static void pdc_x1e_irq_enable_write(u32 bank, u32 enable) @@ -86,24 +219,24 @@ static void pdc_x1e_irq_enable_write(u32 bank, u32 enable) switch (bank) { case 0 ... 1: /* Use previous DRV (client) region and shift to bank 3-4 */ - base = pdc_prev_base; + base = pdc->prev_base; bank += 3; break; case 2 ... 4: /* Use our own region and shift to bank 0-2 */ - base = pdc_base; + base = pdc->base; bank -= 2; break; case 5: /* No fixup required for bank 5 */ - base = pdc_base; + base = pdc->base; break; default: WARN_ON(1); return; } - pdc_base_reg_write(base, IRQ_ENABLE_BANK, bank, enable); + pdc_base_reg_write(base, pdc->regs->irq_en_reg, bank, enable); } static void pdc_enable_intr_bank(int pin_out, bool on) @@ -114,52 +247,97 @@ static void pdc_enable_intr_bank(int pin_out, bool on) index = FIELD_GET(IRQ_ENABLE_BANK_INDEX_MASK, pin_out); mask = FIELD_GET(IRQ_ENABLE_BANK_BIT_MASK, pin_out); - enable = pdc_reg_read(IRQ_ENABLE_BANK, index); + guard(raw_spinlock_irqsave)(&pdc->lock); + + enable = pdc_reg_read(pdc->regs->irq_en_reg, index); __assign_bit(mask, &enable, on); - if (pdc_x1e_quirk) + if (pdc->x1e_quirk) pdc_x1e_irq_enable_write(index, enable); else - pdc_reg_write(IRQ_ENABLE_BANK, index, enable); + pdc_reg_write(pdc->regs->irq_en_reg, index, enable); } -static void pdc_enable_intr_cfg(int pin_out, bool on) +static void pdc_clear_gpio_cfg(int pin_out) { - unsigned long enable = pdc_reg_read(IRQ_i_CFG, pin_out); + unsigned long gpio_sts; - __assign_bit(IRQ_i_CFG_IRQ_ENABLE, &enable, on); - pdc_reg_write(IRQ_i_CFG, pin_out, enable); + gpio_sts = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); + __clear_bit(pdc->cfg_fields->gpio_irq_sts, &gpio_sts); + pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, gpio_sts); } -static void __pdc_enable_intr(int pin_out, bool on) +static void pdc_unmask_gpio_cfg(int pin_out, bool unmask) { - if (pdc_version < PDC_VERSION_3_2) - pdc_enable_intr_bank(pin_out, on); - else - pdc_enable_intr_cfg(pin_out, on); + unsigned long gpio_mask; + + gpio_mask = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); + __assign_bit(pdc->cfg_fields->gpio_irq_mask, &gpio_mask, !unmask); + pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, gpio_mask); } -static void pdc_enable_intr(struct irq_data *d, bool on) +static void pdc_enable_intr_cfg(int pin_out, bool on) { - unsigned long flags; + unsigned long enable = pdc_reg_read(pdc->regs->irq_cfg_reg, pin_out); - raw_spin_lock_irqsave(&pdc_lock, flags); - __pdc_enable_intr(d->hwirq, on); - raw_spin_unlock_irqrestore(&pdc_lock, flags); + __assign_bit(pdc->cfg_fields->irq_enable, &enable, on); + pdc_reg_write(pdc->regs->irq_cfg_reg, pin_out, enable); +} + +static void qcom_pdc_gic_secondary_disable(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, false); + pdc->unmask_gpio(d->hwirq, false); + irq_chip_disable_parent(d); } static void qcom_pdc_gic_disable(struct irq_data *d) { - pdc_enable_intr(d, false); + pdc->enable_intr(d->hwirq, false); irq_chip_disable_parent(d); } static void qcom_pdc_gic_enable(struct irq_data *d) { - pdc_enable_intr(d, true); + pdc->enable_intr(d->hwirq, true); + irq_chip_enable_parent(d); +} + +static void qcom_pdc_gic_secondary_enable(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, true); + pdc->unmask_gpio(d->hwirq, true); irq_chip_enable_parent(d); } +static void qcom_pdc_secondary_ack(struct irq_data *d) +{ + if (!irqd_is_level_type(d)) + pdc->clear_gpio(d->hwirq); +} + +static void qcom_pdc_gic_secondary_eoi(struct irq_data *d) +{ + if (irqd_is_level_type(d)) + pdc->clear_gpio(d->hwirq); + + irq_chip_eoi_parent(d); +} + +static void qcom_pdc_secondary_mask(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, false); + pdc->unmask_gpio(d->hwirq, false); + irq_chip_mask_parent(d); +} + +static void qcom_pdc_secondary_unmask(struct irq_data *d) +{ + pdc->enable_intr(d->hwirq, true); + pdc->unmask_gpio(d->hwirq, true); + irq_chip_unmask_parent(d); +} + /* * GIC does not handle falling edge or active low. To allow falling edge and * active low interrupts to be handled at GIC, PDC has an inverter that inverts @@ -186,18 +364,18 @@ enum pdc_irq_config_bits { /** * qcom_pdc_gic_set_type: Configure PDC for the interrupt * - * @d: the interrupt data + * @d: the interrupt data * @type: the interrupt type * - * If @type is edge triggered, forward that as Rising edge as PDC - * takes care of converting falling edge to rising edge signal + * If @type is edge triggered, forward that as rising edge as PDC + * takes care of converting all edge types to rising edge signal * If @type is level, then forward that as level high as PDC - * takes care of converting falling edge to rising edge signal + * takes care of converting all level types to level high signal */ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) { - enum pdc_irq_config_bits pdc_type; enum pdc_irq_config_bits old_pdc_type; + enum pdc_irq_config_bits pdc_type; int ret; switch (type) { @@ -224,9 +402,75 @@ static int qcom_pdc_gic_set_type(struct irq_data *d, unsigned int type) return -EINVAL; } - old_pdc_type = pdc_reg_read(IRQ_i_CFG, d->hwirq); - pdc_type |= (old_pdc_type & ~IRQ_i_CFG_TYPE_MASK); - pdc_reg_write(IRQ_i_CFG, d->hwirq, pdc_type); + old_pdc_type = pdc_reg_read(pdc->regs->irq_cfg_reg, d->hwirq); + pdc_type |= (old_pdc_type & ~pdc->cfg_fields->irq_type); + pdc_reg_write(pdc->regs->irq_cfg_reg, d->hwirq, pdc_type); + + ret = irq_chip_set_type_parent(d, type); + if (ret) + return ret; + + /* + * When we change types the PDC can give a phantom interrupt. + * Clear it. Specifically the phantom shows up when reconfiguring + * polarity of interrupt without changing the state of the signal + * but let's be consistent and clear it always. + * + * Doing this works because we have IRQCHIP_SET_TYPE_MASKED so the + * interrupt will be cleared before the rest of the system sees it. + */ + if (old_pdc_type != pdc_type) + irq_chip_set_parent_state(d, IRQCHIP_STATE_PENDING, false); + + return 0; +} + +/** + * qcom_pdc_gic_secondary_set_type: Configure PDC for the interrupt in secondary mode + * + * @d: the interrupt data + * @type: the interrupt type + * + * All @type are forwarded as level high type to parent GIC + */ +static int qcom_pdc_gic_secondary_set_type(struct irq_data *d, unsigned int type) +{ + enum pdc_irq_config_bits old_pdc_type; + enum pdc_irq_config_bits pdc_type; + int ret; + + switch (type) { + case IRQ_TYPE_EDGE_RISING: + pdc_type = PDC_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + pdc_type = PDC_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + pdc_type = PDC_EDGE_DUAL; + break; + case IRQ_TYPE_LEVEL_HIGH: + pdc_type = PDC_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + pdc_type = PDC_LEVEL_LOW; + break; + default: + WARN_ON(1); + return -EINVAL; + } + + old_pdc_type = pdc_reg_read(pdc->regs->irq_cfg_reg, d->hwirq); + pdc_type |= (old_pdc_type & ~pdc->cfg_fields->irq_type); + pdc_reg_write(pdc->regs->irq_cfg_reg, d->hwirq, pdc_type); + + /* + * PDC forwards GPIOs as level high to GIC in secondary + * mode. Update the type and clear any previously latched + * phantom interrupt at PDC. + */ + type = IRQ_TYPE_LEVEL_HIGH; + pdc->clear_gpio(d->hwirq); ret = irq_chip_set_type_parent(d, type); if (ret) @@ -266,14 +510,32 @@ static struct irq_chip qcom_pdc_gic_chip = { .irq_set_affinity = irq_chip_set_affinity_parent, }; +static struct irq_chip qcom_pdc_gic_secondary_chip = { + .name = "PDC", + .irq_ack = qcom_pdc_secondary_ack, + .irq_eoi = qcom_pdc_gic_secondary_eoi, + .irq_mask = qcom_pdc_secondary_mask, + .irq_unmask = qcom_pdc_secondary_unmask, + .irq_disable = qcom_pdc_gic_secondary_disable, + .irq_enable = qcom_pdc_gic_secondary_enable, + .irq_get_irqchip_state = irq_chip_get_parent_state, + .irq_set_irqchip_state = irq_chip_set_parent_state, + .irq_retrigger = irq_chip_retrigger_hierarchy, + .irq_set_type = qcom_pdc_gic_secondary_set_type, + .flags = IRQCHIP_MASK_ON_SUSPEND | + IRQCHIP_SET_TYPE_MASKED | + IRQCHIP_SKIP_SET_WAKE | + IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND, + .irq_set_vcpu_affinity = irq_chip_set_vcpu_affinity_parent, + .irq_set_affinity = irq_chip_set_affinity_parent, +}; + static struct pdc_pin_region *get_pin_region(int pin) { - int i; - - for (i = 0; i < pdc_region_cnt; i++) { - if (pin >= pdc_region[i].pin_base && - pin < pdc_region[i].pin_base + pdc_region[i].cnt) - return &pdc_region[i]; + for (int i = 0; i < pdc->region_cnt; i++) { + if (pin >= pdc->region[i].pin_base && + pin < pdc->region[i].pin_base + pdc->region[i].cnt) + return &pdc->region[i]; } return NULL; @@ -301,24 +563,45 @@ static int qcom_pdc_alloc(struct irq_domain *domain, unsigned int virq, if (ret) return ret; + /* + * PDC secondary chip is only set for the GPIO interrupts as SPIs. + * Direct SPI interrupts are still in pass through mode (no latching + * at PDC). + */ + if (pdc->mode == PDC_SECONDARY_MODE && pdc_pin_is_gpio(hwirq)) { + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &qcom_pdc_gic_secondary_chip, + NULL); + if (ret) + return ret; + + /* Secondary mode converts all interrupts to LEVEL HIGH type */ + type = IRQ_TYPE_LEVEL_HIGH; + } else { + ret = irq_domain_set_hwirq_and_chip(domain, virq, hwirq, + &qcom_pdc_gic_chip, + NULL); + if (ret) + return ret; + + if (type & IRQ_TYPE_EDGE_BOTH) + type = IRQ_TYPE_EDGE_RISING; + + if (type & IRQ_TYPE_LEVEL_MASK) + type = IRQ_TYPE_LEVEL_HIGH; + } + region = get_pin_region(hwirq); if (!region) return irq_domain_disconnect_hierarchy(domain->parent, virq); - if (type & IRQ_TYPE_EDGE_BOTH) - type = IRQ_TYPE_EDGE_RISING; - - if (type & IRQ_TYPE_LEVEL_MASK) - type = IRQ_TYPE_LEVEL_HIGH; - parent_fwspec.fwnode = domain->parent->fwnode; parent_fwspec.param_count = 3; parent_fwspec.param[0] = 0; parent_fwspec.param[1] = pin_to_hwirq(region, hwirq); parent_fwspec.param[2] = type; - return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, - &parent_fwspec); + return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_fwspec); } static const struct irq_domain_ops qcom_pdc_ops = { @@ -327,52 +610,56 @@ static const struct irq_domain_ops qcom_pdc_ops = { .free = irq_domain_free_irqs_common, }; -static int pdc_setup_pin_mapping(struct device_node *np) +static int pdc_setup_pin_mapping(struct device *dev) { - int ret, n, i; + struct device_node *np = dev->of_node; + int ret, n; n = of_property_count_elems_of_size(np, "qcom,pdc-ranges", sizeof(u32)); if (n <= 0 || n % 3) return -EINVAL; - pdc_region_cnt = n / 3; - pdc_region = kzalloc_objs(*pdc_region, pdc_region_cnt); - if (!pdc_region) { - pdc_region_cnt = 0; + pdc->region_cnt = n / 3; + pdc->region = devm_kcalloc(dev, pdc->region_cnt, sizeof(*pdc->region), GFP_KERNEL); + if (!pdc->region) { + pdc->region_cnt = 0; return -ENOMEM; } - for (n = 0; n < pdc_region_cnt; n++) { - ret = of_property_read_u32_index(np, "qcom,pdc-ranges", - n * 3 + 0, - &pdc_region[n].pin_base); + for (n = 0; n < pdc->region_cnt; n++) { + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 0, + &pdc->region[n].pin_base); if (ret) return ret; - ret = of_property_read_u32_index(np, "qcom,pdc-ranges", - n * 3 + 1, - &pdc_region[n].parent_base); + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 1, + &pdc->region[n].parent_base); if (ret) return ret; - ret = of_property_read_u32_index(np, "qcom,pdc-ranges", - n * 3 + 2, - &pdc_region[n].cnt); + ret = of_property_read_u32_index(np, "qcom,pdc-ranges", n * 3 + 2, + &pdc->region[n].cnt); if (ret) return ret; - for (i = 0; i < pdc_region[n].cnt; i++) - __pdc_enable_intr(i + pdc_region[n].pin_base, 0); + for (int i = 0; i < pdc->region[n].cnt; i++) { + if (pdc_pin_is_gpio(i + pdc->region[n].pin_base) && + pdc->mode == PDC_SECONDARY_MODE) + pdc->clear_gpio(i + pdc->region[n].pin_base); + + pdc->enable_intr(i + pdc->region[n].pin_base, false); + } } return 0; } - static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *parent) { struct irq_domain *parent_domain, *pdc_domain; struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; resource_size_t res_size; struct resource res; + u32 irq_param; int ret; /* compat with old sm8150 DT which had very small region for PDC */ @@ -383,6 +670,34 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare if (res_size > resource_size(&res)) pr_warn("%pOF: invalid reg size, please fix DT\n", node); + pdc = devm_kzalloc(dev, sizeof(*pdc), GFP_KERNEL); + if (!pdc) + return -ENOMEM; + + pdc->base = devm_ioremap(dev, res.start, res_size); + if (!pdc->base) { + pr_err("%pOF: unable to map PDC registers\n", node); + return -ENXIO; + } + + pdc->version = pdc_reg_read(PDC_VERSION_REG, 0); + + if (pdc->version >= PDC_VERSION_3_2) { + pdc->cfg_fields = &pdc_cfg_v3_2; + pdc->regs = &pdc_v3_2; + pdc->enable_intr = pdc_enable_intr_cfg; + } else if (pdc->version >= PDC_VERSION_3_0) { + pdc->cfg_fields = &pdc_cfg_v3_0; + pdc->regs = &pdc_v3_0; + pdc->enable_intr = pdc_enable_intr_bank; + } else { + pdc->cfg_fields = &pdc_cfg_v2_7; + pdc->regs = &pdc_v2_7; + pdc->enable_intr = pdc_enable_intr_bank; + } + + pdc->mode = PDC_PASS_THROUGH_MODE; + /* * PDC has multiple DRV regions, each one provides the same set of * registers for a particular client in the system. Due to a hardware @@ -392,57 +707,54 @@ static int qcom_pdc_probe(struct platform_device *pdev, struct device_node *pare * region with the expected offset to preserve support for old DTs. */ if (of_device_is_compatible(node, "qcom,x1e80100-pdc")) { - pdc_prev_base = ioremap(res.start - PDC_DRV_SIZE, IRQ_ENABLE_BANK_MAX); - if (!pdc_prev_base) { + pdc->prev_base = devm_ioremap(dev, res.start - PDC_DRV_SIZE, + pdc->regs->irq_en_reg + IRQ_ENABLE_BANK_MAX); + if (!pdc->prev_base) { pr_err("%pOF: unable to map previous PDC DRV region\n", node); return -ENXIO; } - pdc_x1e_quirk = true; - } + pdc->x1e_quirk = true; - pdc_base = ioremap(res.start, res_size); - if (!pdc_base) { - pr_err("%pOF: unable to map PDC registers\n", node); - ret = -ENXIO; - goto fail; + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + ret = qcom_scm_io_writel(PDC_GPIO_INT_CTL_ENABLE, PDC_PASS_THROUGH_MODE); + if (ret) { + pdc->mode = PDC_SECONDARY_MODE; + pdc->unmask_gpio = pdc_unmask_gpio_cfg; + pdc->clear_gpio = pdc_clear_gpio_cfg; + } } - pdc_version = pdc_reg_read(PDC_VERSION_REG, 0); + irq_param = pdc_reg_read(pdc->regs->irq_param_reg, 0); + pdc->num_spis = FIELD_GET(GENMASK(7, 0), irq_param); parent_domain = irq_find_host(parent); if (!parent_domain) { pr_err("%pOF: unable to find PDC's parent domain\n", node); - ret = -ENXIO; - goto fail; + return -ENXIO; } - ret = pdc_setup_pin_mapping(node); + raw_spin_lock_init(&pdc->lock); + + ret = pdc_setup_pin_mapping(dev); if (ret) { pr_err("%pOF: failed to init PDC pin-hwirq mapping\n", node); - goto fail; + return ret; } - pdc_domain = irq_domain_create_hierarchy(parent_domain, - IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP, - PDC_MAX_GPIO_IRQS, - of_fwnode_handle(node), - &qcom_pdc_ops, NULL); + pdc_domain = irq_domain_create_hierarchy(parent_domain, IRQ_DOMAIN_FLAG_QCOM_PDC_WAKEUP, + PDC_MAX_IRQS, of_fwnode_handle(node), + &qcom_pdc_ops, NULL); if (!pdc_domain) { pr_err("%pOF: PDC domain add failed\n", node); - ret = -ENOMEM; - goto fail; + return -ENOMEM; } irq_domain_update_bus_token(pdc_domain, DOMAIN_BUS_WAKEUP); return 0; - -fail: - kfree(pdc_region); - iounmap(pdc_base); - iounmap(pdc_prev_base); - return ret; } IRQCHIP_PLATFORM_DRIVER_BEGIN(qcom_pdc) |
