aboutsummaryrefslogtreecommitdiff
path: root/pkgs/patches-linux-5.15/0034-PCI-mvebu-Implement-support-for-legacy-INTx-interrup.patch
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/patches-linux-5.15/0034-PCI-mvebu-Implement-support-for-legacy-INTx-interrup.patch')
-rw-r--r--pkgs/patches-linux-5.15/0034-PCI-mvebu-Implement-support-for-legacy-INTx-interrup.patch251
1 files changed, 251 insertions, 0 deletions
diff --git a/pkgs/patches-linux-5.15/0034-PCI-mvebu-Implement-support-for-legacy-INTx-interrup.patch b/pkgs/patches-linux-5.15/0034-PCI-mvebu-Implement-support-for-legacy-INTx-interrup.patch
new file mode 100644
index 0000000..d02a2c4
--- /dev/null
+++ b/pkgs/patches-linux-5.15/0034-PCI-mvebu-Implement-support-for-legacy-INTx-interrup.patch
@@ -0,0 +1,251 @@
+From 09e38b818e55358009d7c20aeddb7c59f0b3a3e1 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
+Date: Tue, 2 Nov 2021 10:30:20 +0100
+Subject: [PATCH 34/90] PCI: mvebu: Implement support for legacy INTx
+ interrupts
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This adds support for legacy INTx interrupts received from other PCIe
+devices and which are reported by a new INTx irq chip.
+
+With this change, kernel can distinguish between INTA, INTB, INTC and INTD
+interrupts.
+
+Note that for this support, device tree files has to be properly adjusted
+to provide "interrupts" or "interrupts-extended" property with intx
+interrupt source, "interrupt-names" property with "intx" string and also
+'interrupt-controller' subnode must be defined.
+
+If device tree files do not provide these nodes then driver would work as
+before.
+
+Signed-off-by: Pali Rohár <pali@kernel.org>
+---
+ drivers/pci/controller/pci-mvebu.c | 159 +++++++++++++++++++++++++++++
+ 1 file changed, 159 insertions(+)
+
+diff --git a/drivers/pci/controller/pci-mvebu.c b/drivers/pci/controller/pci-mvebu.c
+index 5f8b8b4ddbea..c9311829dfe3 100644
+--- a/drivers/pci/controller/pci-mvebu.c
++++ b/drivers/pci/controller/pci-mvebu.c
+@@ -111,6 +111,9 @@ struct mvebu_pcie_port {
+ struct mvebu_pcie_window iowin;
+ u32 saved_pcie_stat;
+ struct resource regs;
++ struct irq_domain *intx_irq_domain;
++ raw_spinlock_t irq_lock;
++ int intx_irq;
+ };
+
+ static inline void mvebu_writel(struct mvebu_pcie_port *port, u32 val, u32 reg)
+@@ -289,7 +292,18 @@ static void mvebu_pcie_setup_hw(struct mvebu_pcie_port *port)
+ /* Point PCIe unit MBUS decode windows to DRAM space. */
+ mvebu_pcie_setup_wins(port);
+
++ /* Mask all interrupt sources. */
++ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
++
++ /* Clear all interrupt causes. */
++ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
++
++ /* Check if "intx" interrupt was specified in DT. */
++ if (port->intx_irq > 0)
++ return;
++
+ /*
++ * Fallback code when "intx" interrupt was not specified in DT:
+ * Unmask all legacy INTx interrupts as driver does not provide a way
+ * for masking and unmasking of individual legacy INTx interrupts.
+ * Legacy INTx are reported via one shared GIC source and therefore
+@@ -934,6 +948,108 @@ static struct pci_ops mvebu_pcie_ops = {
+ .write = mvebu_pcie_wr_conf,
+ };
+
++static void mvebu_pcie_intx_irq_mask(struct irq_data *d)
++{
++ struct mvebu_pcie_port *port = d->domain->host_data;
++ irq_hw_number_t hwirq = irqd_to_hwirq(d);
++ unsigned long flags;
++ u32 unmask;
++
++ raw_spin_lock_irqsave(&port->irq_lock, flags);
++ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++ unmask &= ~PCIE_INT_INTX(hwirq);
++ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
++}
++
++static void mvebu_pcie_intx_irq_unmask(struct irq_data *d)
++{
++ struct mvebu_pcie_port *port = d->domain->host_data;
++ irq_hw_number_t hwirq = irqd_to_hwirq(d);
++ unsigned long flags;
++ u32 unmask;
++
++ raw_spin_lock_irqsave(&port->irq_lock, flags);
++ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++ unmask |= PCIE_INT_INTX(hwirq);
++ mvebu_writel(port, unmask, PCIE_INT_UNMASK_OFF);
++ raw_spin_unlock_irqrestore(&port->irq_lock, flags);
++}
++
++static struct irq_chip intx_irq_chip = {
++ .name = "mvebu-INTx",
++ .irq_mask = mvebu_pcie_intx_irq_mask,
++ .irq_unmask = mvebu_pcie_intx_irq_unmask,
++};
++
++static int mvebu_pcie_intx_irq_map(struct irq_domain *h,
++ unsigned int virq, irq_hw_number_t hwirq)
++{
++ struct mvebu_pcie_port *port = h->host_data;
++
++ irq_set_status_flags(virq, IRQ_LEVEL);
++ irq_set_chip_and_handler(virq, &intx_irq_chip, handle_level_irq);
++ irq_set_chip_data(virq, port);
++
++ return 0;
++}
++
++static const struct irq_domain_ops mvebu_pcie_intx_irq_domain_ops = {
++ .map = mvebu_pcie_intx_irq_map,
++ .xlate = irq_domain_xlate_onecell,
++};
++
++static int mvebu_pcie_init_irq_domain(struct mvebu_pcie_port *port)
++{
++ struct device *dev = &port->pcie->pdev->dev;
++ struct device_node *pcie_intc_node;
++
++ raw_spin_lock_init(&port->irq_lock);
++
++ pcie_intc_node = of_get_next_child(port->dn, NULL);
++ if (!pcie_intc_node) {
++ dev_err(dev, "No PCIe Intc node found for %s\n", port->name);
++ return -ENODEV;
++ }
++
++ port->intx_irq_domain = irq_domain_add_linear(pcie_intc_node, PCI_NUM_INTX,
++ &mvebu_pcie_intx_irq_domain_ops,
++ port);
++ of_node_put(pcie_intc_node);
++ if (!port->intx_irq_domain) {
++ dev_err(dev, "Failed to get INTx IRQ domain for %s\n", port->name);
++ return -ENOMEM;
++ }
++
++ return 0;
++}
++
++static void mvebu_pcie_irq_handler(struct irq_desc *desc)
++{
++ struct mvebu_pcie_port *port = irq_desc_get_handler_data(desc);
++ struct irq_chip *chip = irq_desc_get_chip(desc);
++ struct device *dev = &port->pcie->pdev->dev;
++ u32 cause, unmask, status;
++ int i;
++
++ chained_irq_enter(chip, desc);
++
++ cause = mvebu_readl(port, PCIE_INT_CAUSE_OFF);
++ unmask = mvebu_readl(port, PCIE_INT_UNMASK_OFF);
++ status = cause & unmask;
++
++ /* Process legacy INTx interrupts */
++ for (i = 0; i < PCI_NUM_INTX; i++) {
++ if (!(status & PCIE_INT_INTX(i)))
++ continue;
++
++ if (generic_handle_domain_irq(port->intx_irq_domain, i) == -EINVAL)
++ dev_err_ratelimited(dev, "unexpected INT%c IRQ\n", (char)i+'A');
++ }
++
++ chained_irq_exit(chip, desc);
++}
++
+ static int mvebu_pcie_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
+ {
+ /* Interrupt support on mvebu emulated bridges is not implemented yet */
+@@ -1131,6 +1247,21 @@ static int mvebu_pcie_parse_port(struct mvebu_pcie *pcie,
+ port->io_attr = -1;
+ }
+
++ /*
++ * Old DT bindings do not contain "intx" interrupt
++ * so do not fail probing driver when interrupt does not exist.
++ */
++ port->intx_irq = of_irq_get_byname(child, "intx");
++ if (port->intx_irq == -EPROBE_DEFER) {
++ ret = port->intx_irq;
++ goto err;
++ }
++ if (port->intx_irq <= 0) {
++ dev_warn(dev, "%s: legacy INTx interrupts cannot be masked individually, "
++ "%pOF does not contain intx interrupt\n",
++ port->name, child);
++ }
++
+ reset_gpio = of_get_named_gpio_flags(child, "reset-gpios", 0, &flags);
+ if (reset_gpio == -EPROBE_DEFER) {
+ ret = reset_gpio;
+@@ -1327,6 +1458,7 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+
+ for (i = 0; i < pcie->nports; i++) {
+ struct mvebu_pcie_port *port = &pcie->ports[i];
++ int irq = port->intx_irq;
+
+ child = port->dn;
+ if (!child)
+@@ -1354,6 +1486,22 @@ static int mvebu_pcie_probe(struct platform_device *pdev)
+ continue;
+ }
+
++ if (irq > 0) {
++ ret = mvebu_pcie_init_irq_domain(port);
++ if (ret) {
++ dev_err(dev, "%s: cannot init irq domain\n",
++ port->name);
++ pci_bridge_emul_cleanup(&port->bridge);
++ devm_iounmap(dev, port->base);
++ port->base = NULL;
++ mvebu_pcie_powerdown(port);
++ continue;
++ }
++ irq_set_chained_handler_and_data(irq,
++ mvebu_pcie_irq_handler,
++ port);
++ }
++
+ /*
+ * PCIe topology exported by mvebu hw is quite complicated. In
+ * reality has something like N fully independent host bridges
+@@ -1458,6 +1606,7 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+
+ for (i = 0; i < pcie->nports; i++) {
+ struct mvebu_pcie_port *port = &pcie->ports[i];
++ int irq = port->intx_irq;
+
+ if (!port->base)
+ continue;
+@@ -1470,6 +1619,16 @@ static int mvebu_pcie_remove(struct platform_device *pdev)
+ /* Mask all interrupt sources. */
+ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_UNMASK_OFF);
+
++ /* Clear all interrupt causes. */
++ mvebu_writel(port, ~PCIE_INT_ALL_MASK, PCIE_INT_CAUSE_OFF);
++
++ if (irq > 0)
++ irq_set_chained_handler_and_data(irq, NULL, NULL);
++
++ /* Remove IRQ domains. */
++ if (port->intx_irq_domain)
++ irq_domain_remove(port->intx_irq_domain);
++
+ /* Free config space for emulated root bridge. */
+ pci_bridge_emul_cleanup(&port->bridge);
+
+--
+2.34.1
+