[PATCH] irq: bcm2835: Re-implement the hardware IRQ handler.

Craig McGeachie slapdau at yahoo.com.au
Sat Sep 21 02:18:32 PDT 2013


The justification for doing this is:
 * Simplify the decode and dispatch logic to make it easier to under-
   stand and maintain.
 * Re-arrange the hardware IRQ numbers to match the numbering scheme of
   the FIQ register.
 * Restore the flow of control that re-reads the base pending register
   after handling any interrupt. The current version handles all
   interrupts found in a GPU pending register before re-reading the
   base pending register. In the original Broadcom assembly code, there
   appear to be defect tracking numbers next to code inserted to create
   this behaviour.
 * DTS IRQ specifications can refer to either a shortcut bit in base or
   the GPU bits.  E.g. UART can be either <0, 19> or <2, 25>.
 * Add .irq_ack to the chip operations.

Signed-off-by: Craig McGeachie <slapdau at yahoo.com.au>
---

This was meant to be just an exercise in understanding how BCM2835 interrupts
work. It got a little out of hand.

v2:
 * Correct a stupid defect.  Should always test even minor changes.
 * Add forgotten bits to the commit message.

Cc: linux-rpi-kernel at lists.infradead.org

---
 drivers/irqchip/irq-bcm2835.c | 358 ++++++++++++++++++++++--------------------
 1 file changed, 186 insertions(+), 172 deletions(-)

diff --git a/drivers/irqchip/irq-bcm2835.c b/drivers/irqchip/irq-bcm2835.c
index 1693b8e7..5e43252c 100644
--- a/drivers/irqchip/irq-bcm2835.c
+++ b/drivers/irqchip/irq-bcm2835.c
@@ -1,6 +1,7 @@
 /*
  * Copyright 2010 Broadcom
  * Copyright 2012 Simon Arlott, Chris Boot, Stephen Warren
+ * Copyright 2013 Craig McGeachie
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -11,212 +12,225 @@
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- *
- * Quirk 1: Shortcut interrupts don't set the bank 1/2 register pending bits
- *
- * If an interrupt fires on bank 1 that isn't in the shortcuts list, bit 8
- * on bank 0 is set to signify that an interrupt in bank 1 has fired, and
- * to look in the bank 1 status register for more information.
- *
- * If an interrupt fires on bank 1 that _is_ in the shortcuts list, its
- * shortcut bit in bank 0 is set as well as its interrupt bit in the bank 1
- * status register, but bank 0 bit 8 is _not_ set.
- *
- * Quirk 2: You can't mask the register 1/2 pending interrupts
- *
- * In a proper cascaded interrupt controller, the interrupt lines with
- * cascaded interrupt controllers on them are just normal interrupt lines.
- * You can mask the interrupts and get on with things. With this controller
- * you can't do that.
- *
- * Quirk 3: The shortcut interrupts can't be (un)masked in bank 0
- *
- * Those interrupts that have shortcuts can only be masked/unmasked in
- * their respective banks' enable/disable registers. Doing so in the bank 0
- * enable/disable registers has no effect.
- *
- * The FIQ control register:
- *  Bits 0-6: IRQ (index in order of interrupts from banks 1, 2, then 0)
- *  Bit    7: Enable FIQ generation
- *  Bits  8+: Unused
- *
- * An interrupt must be disabled before configuring it for FIQ generation
- * otherwise both handlers will fire at the same time!
  */
-
+#include <linux/bitops.h>
 #include <linux/io.h>
-#include <linux/slab.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
-#include <linux/irqdomain.h>
-
 #include <asm/exception.h>
-#include <asm/mach/irq.h>
-
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include "irqchip.h"
 
-/* Put the bank and irq (32 bits) into the hwirq */
-#define MAKE_HWIRQ(b, n)	((b << 5) | (n))
-#define HWIRQ_BANK(i)		(i >> 5)
-#define HWIRQ_BIT(i)		BIT(i & 0x1f)
-
-#define NR_IRQS_BANK0		8
-#define BANK0_HWIRQ_MASK	0xff
-/* Shortcuts can't be disabled so any unknown new ones need to be masked */
-#define SHORTCUT1_MASK		0x00007c00
-#define SHORTCUT2_MASK		0x001f8000
-#define SHORTCUT_SHIFT		10
-#define BANK1_HWIRQ		BIT(8)
-#define BANK2_HWIRQ		BIT(9)
-#define BANK0_VALID_MASK	(BANK0_HWIRQ_MASK | BANK1_HWIRQ | BANK2_HWIRQ \
-					| SHORTCUT1_MASK | SHORTCUT2_MASK)
-
-#define REG_FIQ_CONTROL		0x0c
-
-#define NR_BANKS		3
-#define IRQS_PER_BANK		32
-
-static int reg_pending[] __initconst = { 0x00, 0x04, 0x08 };
-static int reg_enable[] __initconst = { 0x18, 0x10, 0x14 };
-static int reg_disable[] __initconst = { 0x24, 0x1c, 0x20 };
-static int bank_irqs[] __initconst = { 8, 32, 32 };
-
-static const int shortcuts[] = {
-	7, 9, 10, 18, 19,		/* Bank 1 */
-	21, 22, 23, 24, 25, 30		/* Bank 2 */
-};
+/*
+ * Internal to this file, IRQs are numbered as per the enable and disable
+ * registers.  The register banks GPU1, GPU2, and BASE are numbered 0, 1, and 2
+ * respectively. IRQs 0..31 are bits 0..31 of the GPU1 register.  IRQs 32..63
+ * are bits 0..31 of the GPU2 register.  IRQs 64..71 are bits 0..7 of the BASE
+ * register.  This is the same numbering scheme used in bits 0..6 of the FIQ
+ * register.
+ *
+ * Translation into and out of this address space happens at two
+ * points:
+ * - external drivers registering handlers will result in a call to
+ *   bmc2835_domain_xlate()
+ * - interrupt pending status registers are read in bcm2835_handle_irq() which
+ *   uses bmc2835_dispatch_irq() to map through the internal hardware IRQ space
+ *   into the kernel's registered IRQ address space.
+ */
 
-struct armctrl_ic {
-	void __iomem *base;
-	void __iomem *pending[NR_BANKS];
-	void __iomem *enable[NR_BANKS];
-	void __iomem *disable[NR_BANKS];
+#define MAKE_HWIRQ(b, n)	(((b) << 5) | ((n) & 0x1f))
+#define HWIRQ_BANK(i)		((i) >> 5)
+#define HWIRQ_BIT(i)		BIT((i) & 0x1f)
+
+#define GPU1_BANK	0
+#define GPU2_BANK	1
+#define BASE_BANK	2
+#define NR_BANKS	3
+
+#define BITS_PER_REG	32
+
+#define BASE_READABLE	0x001fffff
+#define BASE_GPU1	BIT(8)
+#define BASE_GPU2	BIT(9)
+#define BASE_IRQS	(BASE_READABLE & ~(BASE_GPU1 | BASE_GPU2))
+#define GPU1_IRQS	~(BIT(7) | BIT(9) | BIT(10) | BIT(18) | BIT(19))
+#define GPU2_IRQS	~(BIT(21) | BIT(22) | BIT(23) | BIT(23) | BIT(25) \
+				| BIT(30))
+
+#define NR_CHIP_IRQS	(BITS_PER_REG * 2 + 8)
+#define BAD_IRQ_NUM	-1
+
+static struct ctrlr {
+	void __iomem *iobase;
+	void __iomem *base_pend;
+	void __iomem *gpu1_pend;
+	void __iomem *gpu2_pend;
 	struct irq_domain *domain;
-};
-
-static struct armctrl_ic intc __read_mostly;
-static asmlinkage void __exception_irq_entry bcm2835_handle_irq(
-	struct pt_regs *regs);
-
-static void armctrl_mask_irq(struct irq_data *d)
-{
-	writel_relaxed(HWIRQ_BIT(d->hwirq), intc.disable[HWIRQ_BANK(d->hwirq)]);
-}
-
-static void armctrl_unmask_irq(struct irq_data *d)
-{
-	writel_relaxed(HWIRQ_BIT(d->hwirq), intc.enable[HWIRQ_BANK(d->hwirq)]);
-}
-
-static struct irq_chip armctrl_chip = {
-	.name = "ARMCTRL-level",
-	.irq_mask = armctrl_mask_irq,
-	.irq_unmask = armctrl_unmask_irq
-};
-
-static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr,
+	unsigned int hwirq_xlat[BITS_PER_REG * NR_BANKS];
+} ctrlr __read_mostly;
+
+/**
+ * bmc2835_domain_xlate() - translate FTD IRQ specification to hardware IRQ
+ * @d:		controller domain
+ * @np:		device node
+ * @intspec:	interrupt specification values
+ * @intsize:	interrupt specification size
+ * @out_hwirq:	return the hardware IRQ
+ * @out_type:	return the IRQ type
+ *
+ * The interrupt is specified as two u32 values.  The first is the FDT bank
+ * number, the second is the bit number in bank.  The FDT bank numbers are
+ * different to the internal bank numbers.  GPU1, GPU2, and BASE are numbered
+ * 2, 0, and 1 respectively. (intspec[0] + 2) % 3 performs the translation.
+ *
+ * Interrupt specifiers may refer to an interrupt with a shortcut bit in the
+ * BASE register in two ways. Either by refering to the BASE register shortcut
+ * bit, or by referring to the GPU1/GPU2 register bit.  For example, UART may
+ * be either <0, 19> or <2, 25>.
+ */
+static int bmc2835_domain_xlate(struct irq_domain *d, struct device_node *np,
 	const u32 *intspec, unsigned int intsize,
 	unsigned long *out_hwirq, unsigned int *out_type)
 {
-	if (WARN_ON(intsize != 2))
+	unsigned int hwirq;
+	if (WARN_ON(intsize != 2 || intspec[0] >= NR_BANKS || intspec[1] >= 32))
 		return -EINVAL;
-
-	if (WARN_ON(intspec[0] >= NR_BANKS))
-		return -EINVAL;
-
-	if (WARN_ON(intspec[1] >= IRQS_PER_BANK))
+	hwirq = ctrlr.hwirq_xlat[MAKE_HWIRQ((intspec[0] + 2) % 3, intspec[1])];
+	if (WARN_ON(hwirq == BAD_IRQ_NUM))
 		return -EINVAL;
-
-	if (WARN_ON(intspec[0] == 0 && intspec[1] >= NR_IRQS_BANK0))
-		return -EINVAL;
-
-	*out_hwirq = MAKE_HWIRQ(intspec[0], intspec[1]);
+	*out_hwirq = hwirq;
 	*out_type = IRQ_TYPE_NONE;
 	return 0;
 }
 
-static struct irq_domain_ops armctrl_ops = {
-	.xlate = armctrl_xlate
+static struct irq_domain_ops bmc2835_ops = {
+	.xlate = bmc2835_domain_xlate
 };
 
-static int __init armctrl_of_init(struct device_node *node,
-	struct device_node *parent)
+static void bmc2835_mask_irq(struct irq_data *d)
 {
-	void __iomem *base;
-	int irq, b, i;
+	void __iomem *ioreg = ctrlr.iobase + 0x1c + (HWIRQ_BANK(d->hwirq) << 2);
+	u32 bit = HWIRQ_BIT(d->hwirq);
+	writel_relaxed(bit, ioreg);
+}
 
-	base = of_iomap(node, 0);
-	if (!base)
-		panic("%s: unable to map IC registers\n",
-			node->full_name);
+static void bmc2835_unmask_irq(struct irq_data *d)
+{
+	void __iomem *ioreg = ctrlr.iobase + 0x10 + (HWIRQ_BANK(d->hwirq) << 2);
+	u32 bit = HWIRQ_BIT(d->hwirq);
+	writel_relaxed(bit, ioreg);
+}
 
-	intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0),
-			&armctrl_ops, NULL);
-	if (!intc.domain)
-		panic("%s: unable to create IRQ domain\n", node->full_name);
+static struct irq_chip bmc2835_chip = {
+	.name = "bmc2835-level",
+	.irq_mask = bmc2835_mask_irq,
+	.irq_unmask = bmc2835_unmask_irq,
+	.irq_ack =  bmc2835_mask_irq,
+};
 
-	for (b = 0; b < NR_BANKS; b++) {
-		intc.pending[b] = base + reg_pending[b];
-		intc.enable[b] = base + reg_enable[b];
-		intc.disable[b] = base + reg_disable[b];
-
-		for (i = 0; i < bank_irqs[b]; i++) {
-			irq = irq_create_mapping(intc.domain, MAKE_HWIRQ(b, i));
-			BUG_ON(irq <= 0);
-			irq_set_chip_and_handler(irq, &armctrl_chip,
-				handle_level_irq);
-			set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
-		}
+/**
+ * bmc2835_dispatch_irq() - translate bank/bit to kernel IRQ and dispatch
+ * @bank:	Register bank (BASE, GPU1, or GPU2).
+ * @bits:	Pending interrupt status bits.
+ * @regs:	register pointer passed on to the handler.
+ *
+ * The bits passed are the full set of bits read from register.  Only the lowest
+ * order bit is translated and dispatched. Dispatching the other IRQs requires
+ * re-reading the register and calling this function again.
+ */
+static void __exception_irq_entry bmc2835_dispatch_irq(int bank, u32 bits,
+	struct pt_regs *regs) {
+	unsigned int irq = ctrlr.hwirq_xlat[MAKE_HWIRQ(bank, ffs(bits) - 1)];
+	if (irq == BAD_IRQ_NUM) {
+		BUG();
+		return;
 	}
-
-	set_handle_irq(bcm2835_handle_irq);
-	return 0;
+	handle_IRQ(irq_linear_revmap(ctrlr.domain, irq), regs);
 }
 
-/*
- * Handle each interrupt across the entire interrupt controller.  This reads the
- * status register before handling each interrupt, which is necessary given that
- * handle_IRQ may briefly re-enable interrupts for soft IRQ handling.
- */
-
-static void armctrl_handle_bank(int bank, struct pt_regs *regs)
+static asmlinkage void __exception_irq_entry bcm2835_handle_irq(
+	struct pt_regs *regs)
 {
-	u32 stat, irq;
+	u32 stat;
 
-	while ((stat = readl_relaxed(intc.pending[bank]))) {
-		irq = MAKE_HWIRQ(bank, ffs(stat) - 1);
-		handle_IRQ(irq_linear_revmap(intc.domain, irq), regs);
+	while ((stat = readl_relaxed(ctrlr.base_pend) & BASE_READABLE)) {
+		if (stat & BASE_IRQS) {
+			bmc2835_dispatch_irq(BASE_BANK, stat & BASE_IRQS, regs);
+			continue;
+		}
+		if (stat & BASE_GPU1) {
+			stat = readl_relaxed(ctrlr.gpu1_pend) & GPU1_IRQS;
+			if (stat)
+				bmc2835_dispatch_irq(GPU1_BANK, stat, regs);
+			continue;
+		}
+		if (stat & BASE_GPU2) {
+			stat = readl_relaxed(ctrlr.gpu2_pend) & GPU2_IRQS;
+			if (stat)
+				bmc2835_dispatch_irq(GPU2_BANK, stat, regs);
+			continue;
+		}
+		BUG();
 	}
 }
 
-static void armctrl_handle_shortcut(int bank, struct pt_regs *regs,
-	u32 stat)
-{
-	u32 irq = MAKE_HWIRQ(bank, shortcuts[ffs(stat >> SHORTCUT_SHIFT) - 1]);
-	handle_IRQ(irq_linear_revmap(intc.domain, irq), regs);
-}
+/**
+ * int shortcut_xlat[] - normalisation of BASE pending register shortcut bits.
+ *
+ * Bits 10..20 of the BASE pending register are shortcuts.  For example, bit 19
+ * is the UART IRQ bit.  As an internal IRQ number, that would be 83.  Its
+ * canonical internal IRQ number is 57 (GPU1, bit 25).  When this array is
+ * copied into the appropriate subrange of ctrlr.hwirq_xlat, then
+ * ctrlr.hwirq_xlat[83] = 57.
+ */
+static int shortcut_xlat[] __initconst = {
+	MAKE_HWIRQ(GPU1_BANK,  7),
+	MAKE_HWIRQ(GPU1_BANK,  9),
+	MAKE_HWIRQ(GPU1_BANK, 10),
+	MAKE_HWIRQ(GPU1_BANK, 18),
+	MAKE_HWIRQ(GPU1_BANK, 19),
+	MAKE_HWIRQ(GPU2_BANK, 21),
+	MAKE_HWIRQ(GPU2_BANK, 22),
+	MAKE_HWIRQ(GPU2_BANK, 23),
+	MAKE_HWIRQ(GPU2_BANK, 24),
+	MAKE_HWIRQ(GPU2_BANK, 25),
+	MAKE_HWIRQ(GPU2_BANK, 30),
+};
 
-static asmlinkage void __exception_irq_entry bcm2835_handle_irq(
-	struct pt_regs *regs)
+static int __init bmc2835_of_init(struct device_node *node,
+	struct device_node *parent)
 {
-	u32 stat, irq;
-
-	while ((stat = readl_relaxed(intc.pending[0]) & BANK0_VALID_MASK)) {
-		if (stat & BANK0_HWIRQ_MASK) {
-			irq = MAKE_HWIRQ(0, ffs(stat & BANK0_HWIRQ_MASK) - 1);
-			handle_IRQ(irq_linear_revmap(intc.domain, irq), regs);
-		} else if (stat & SHORTCUT1_MASK) {
-			armctrl_handle_shortcut(1, regs, stat & SHORTCUT1_MASK);
-		} else if (stat & SHORTCUT2_MASK) {
-			armctrl_handle_shortcut(2, regs, stat & SHORTCUT2_MASK);
-		} else if (stat & BANK1_HWIRQ) {
-			armctrl_handle_bank(1, regs);
-		} else if (stat & BANK2_HWIRQ) {
-			armctrl_handle_bank(2, regs);
-		} else {
-			BUG();
-		}
+	int i;
+
+	ctrlr.iobase = of_iomap(node, 0);
+	if (!ctrlr.iobase)
+		panic("%s: unable to map IC registers\n", node->full_name);
+	ctrlr.domain = irq_domain_add_linear(node, NR_CHIP_IRQS, &bmc2835_ops,
+		&ctrlr);
+	if (!ctrlr.domain)
+		panic("%s: unable to create IRQ domain\n", node->full_name);
+
+	ctrlr.base_pend = ctrlr.iobase + 0x00;
+	ctrlr.gpu1_pend = ctrlr.iobase + 0x04;
+	ctrlr.gpu2_pend = ctrlr.iobase + 0x08;
+
+	for (i = 0; i != NR_CHIP_IRQS; i++)
+		ctrlr.hwirq_xlat[i] = i;
+	for (i = NR_CHIP_IRQS; i != BITS_PER_REG * NR_BANKS; i++)
+		ctrlr.hwirq_xlat[i] = BAD_IRQ_NUM;
+	for (i = 0; i != ARRAY_SIZE(shortcut_xlat); i++)
+		ctrlr.hwirq_xlat[i + NR_CHIP_IRQS + 2] = shortcut_xlat[i];
+
+	for (i = 0; i != NR_CHIP_IRQS; i++) {
+		int irq;
+		BUG_ON((irq = irq_create_mapping(ctrlr.domain, i)) <= 0);
+		irq_set_chip_and_handler(irq, &bmc2835_chip, handle_level_irq);
+		set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
 	}
+
+	set_handle_irq(bcm2835_handle_irq);
+	return 0;
 }
 
-IRQCHIP_DECLARE(bcm2835_armctrl_ic, "brcm,bcm2835-armctrl-ic", armctrl_of_init);
+IRQCHIP_DECLARE(bcm2835_armctrl_ic, "brcm,bcm2835-armctrl-ic", bmc2835_of_init);
-- 
1.8.3.1




More information about the linux-rpi-kernel mailing list