mirror of
https://github.com/torvalds/linux.git
synced 2024-11-16 17:12:06 +00:00
fd4b9b3650
The GPIO interrupts can be configured as either level triggered or edge triggered, with a default of level triggered. When an edge triggered interrupt is requested, the gpio_irq_set_type method is called which currently switches the given IRQ descriptor between two struct irq_chip instances: orion_gpio_irq_level_chip and orion_gpio_irq_edge_chip. This happens via __setup_irq() which also calls irq_chip_set_defaults() to assign default methods to uninitialized ones. The problem is that irq_chip_set_defaults() is called before the irq_chip reference is switched, leaving the new irq_chip (orion_gpio_irq_edge_chip in this case) with uninitialized methods such as chip->startup() causing a kernel oops. Many solutions are possible, such as making irq_chip_set_defaults() global and calling it from gpio_irq_set_type(), or calling __irq_set_trigger() before irq_chip_set_defaults() in __setup_irq(). But those require modifications to the generic IRQ code which might have adverse effect on other architectures, and that would still be a fragile arrangement. Manually copying the missing methods from within gpio_irq_set_type() would be really ugly and it would break again the day new methods with automatic defaults are added. A better solution is to have a single irq_chip instance which can deal with both edge and level triggered interrupts. It is also a good idea to switch the IRQ handler instead, as the edge IRQ handler allows for one edge IRQ event to be queued as the IRQ is actually masked only when that second IRQ is received, at which point the hardware can queue an additional IRQ event, making edge triggered interrupts a bit more reliable. Tested-by: Martin Michlmayr <tbm@cyrius.com> Signed-off-by: Nicolas Pitre <nico@marvell.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
53 lines
1.5 KiB
C
53 lines
1.5 KiB
C
/*
|
|
* arch/arm/mach-mv78xx0/irq.c
|
|
*
|
|
* MV78xx0 IRQ handling.
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/irq.h>
|
|
#include <asm/gpio.h>
|
|
#include <mach/mv78xx0.h>
|
|
#include <plat/irq.h>
|
|
#include "common.h"
|
|
|
|
static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
|
|
{
|
|
BUG_ON(irq < IRQ_MV78XX0_GPIO_0_7 || irq > IRQ_MV78XX0_GPIO_24_31);
|
|
|
|
orion_gpio_irq_handler((irq - IRQ_MV78XX0_GPIO_0_7) << 3);
|
|
}
|
|
|
|
void __init mv78xx0_init_irq(void)
|
|
{
|
|
int i;
|
|
|
|
orion_irq_init(0, (void __iomem *)(IRQ_VIRT_BASE + IRQ_MASK_LOW_OFF));
|
|
orion_irq_init(32, (void __iomem *)(IRQ_VIRT_BASE + IRQ_MASK_HIGH_OFF));
|
|
orion_irq_init(64, (void __iomem *)(IRQ_VIRT_BASE + IRQ_MASK_ERR_OFF));
|
|
|
|
/*
|
|
* Mask and clear GPIO IRQ interrupts.
|
|
*/
|
|
writel(0, GPIO_LEVEL_MASK(0));
|
|
writel(0, GPIO_EDGE_MASK(0));
|
|
writel(0, GPIO_EDGE_CAUSE(0));
|
|
|
|
for (i = IRQ_MV78XX0_GPIO_START; i < NR_IRQS; i++) {
|
|
set_irq_chip(i, &orion_gpio_irq_chip);
|
|
set_irq_handler(i, handle_level_irq);
|
|
irq_desc[i].status |= IRQ_LEVEL;
|
|
set_irq_flags(i, IRQF_VALID);
|
|
}
|
|
set_irq_chained_handler(IRQ_MV78XX0_GPIO_0_7, gpio_irq_handler);
|
|
set_irq_chained_handler(IRQ_MV78XX0_GPIO_8_15, gpio_irq_handler);
|
|
set_irq_chained_handler(IRQ_MV78XX0_GPIO_16_23, gpio_irq_handler);
|
|
set_irq_chained_handler(IRQ_MV78XX0_GPIO_24_31, gpio_irq_handler);
|
|
}
|