Message ID | 1353019586-21043-3-git-send-email-maxime.ripard@free-electrons.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 11/15/2012 11:46 PM, Maxime Ripard wrote: <snip> > diff --git a/drivers/irqchip/irq-sunxi.c b/drivers/irqchip/irq-sunxi.c > new file mode 100644 > index 0000000..9dcb323 > --- /dev/null > +++ b/drivers/irqchip/irq-sunxi.c > @@ -0,0 +1,173 @@ > +/* > + * Allwinner A1X SoCs IRQ chip driver. > + * > + * Copyright (C) 2012 Maxime Ripard > + * > + * Maxime Ripard <maxime.ripard@free-electrons.com> > + * > + * Based on code from > + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> > + * Benn Huang <benn@allwinnertech.com> > + * > + * 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/io.h> > +#include <linux/irq.h> > +#include <linux/of.h> > +#include <linux/of_address.h> > +#include <linux/of_irq.h> > + > +#include <linux/irqchip/sunxi.h> > + > +#define SUNXI_IRQ_PROTECTION_REG 0x08 > +#define SUNXI_IRQ_NMI_CTRL_REG 0x0c > +#define SUNXI_IRQ_PENDING_REG(x) (0x10 + 0x4 * x) > +#define SUNXI_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x) > +#define SUNXI_IRQ_ENABLE_REG(x) (0x40 + 0x4 * x) > +#define SUNXI_IRQ_MASK_REG(x) (0x50 + 0x4 * x) > + > +static void __iomem *sunxi_irq_base; > +static struct irq_domain *sunxi_irq_domain; > + > +void sunxi_irq_ack(struct irq_data *irqd) > +{ > + unsigned int irq = irqd_to_hwirq(irqd); > + unsigned int irq_off = irq % 32; > + int reg = irq / 32; > + u32 val; > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); > + writel(val & ~(1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); > + writel(val | (1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(reg)); > + writel(val | (1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_PENDING_REG(reg)); Are you sure that you need to touch all those 23registers to ack the interrupt? I know that the original code provided by Allwinner does exactly this. My tests have shown though, that writing to the pending reg is enough. > +} > + > +static void sunxi_irq_mask(struct irq_data *irqd) > +{ > + unsigned int irq = irqd_to_hwirq(irqd); > + unsigned int irq_off = irq % 32; > + int reg = irq / 32; > + u32 val; > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); > + writel(val & ~(1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); > + writel(val | (1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); I unmasked all interrupts in the mask register in the init function. Then only using the enable register for masking/unmasking seems to be enough. What do you think? > +} > + > +static void sunxi_irq_unmask(struct irq_data *irqd) > +{ > + unsigned int irq = irqd_to_hwirq(irqd); > + unsigned int irq_off = irq % 32; > + int reg = irq / 32; > + u32 val; > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); > + writel(val | (1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); > + > + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); > + writel(val & ~(1 << irq_off), > + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); > + > + /* Must clear pending bit when enabled */ > + if (irq == 0) > + writel(1, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(0)); Again. Might be that one register is enough here. > +} > + > +static struct irq_chip sunxi_irq_chip = { > + .name = "sunxi_irq", > + .irq_ack = sunxi_irq_ack, > + .irq_mask = sunxi_irq_mask, > + .irq_unmask = sunxi_irq_unmask, > +}; > + > +static int sunxi_irq_map(struct irq_domain *d, unsigned int virq, > + irq_hw_number_t hw) > +{ > + irq_set_chip(virq, &sunxi_irq_chip); > + irq_set_handler(virq, handle_level_irq); irq_set_chip_and_handler() > + set_irq_flags(virq, IRQF_VALID | IRQF_PROBE); > + > + return 0; > +} > + > +static struct irq_domain_ops sunxi_irq_ops = { > + .map = sunxi_irq_map, > + .xlate = irq_domain_xlate_onecell, > +}; > + > +static int __init sunxi_of_init(struct device_node *node, > + struct device_node *parent) > +{ > + sunxi_irq_base = of_iomap(node, 0); > + if (!sunxi_irq_base) > + panic("%s: unable to map IC registers\n", > + node->full_name); > + > + /* Disable all interrupts */ > + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(0)); > + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(1)); > + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(2)); > + > + /* Mask all the interrupts */ > + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(0)); > + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(1)); > + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(2)); Here is where I wrote 0 to the mask registers. > + /* Clear all the pending interrupts */ > + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(0)); > + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(1)); > + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(2)); > + > + /* Enable protection mode */ > + writel(0x01, sunxi_irq_base + SUNXI_IRQ_PROTECTION_REG); > + > + /* Configure the external interrupt source type */ > + writel(0x00, sunxi_irq_base + SUNXI_IRQ_NMI_CTRL_REG); > + > + sunxi_irq_domain = irq_domain_add_linear(node, 3 * 32, > + &sunxi_irq_ops, NULL); > + if (!sunxi_irq_domain) > + panic("%s: unable to create IRQ domain\n", node->full_name); > + > + return 0; > +} > + > +static struct of_device_id sunxi_irq_dt_ids[] __initconst = { > + { .compatible = "allwinner,sunxi-ic", .data = sunxi_of_init } > +}; > + > +void __init sunxi_init_irq(void) > +{ > + of_irq_init(sunxi_irq_dt_ids); > +} > + > +asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) > +{ > + u32 irq, reg; > + int i; > + > + for (i = 0; i < 3; i++) { > + reg = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(i)); > + if (reg == 0) > + continue; > + irq = ilog2(reg); > + break; > + } > + irq = irq_find_mapping(sunxi_irq_domain, irq); > + handle_IRQ(irq, regs); Why don't you use the interrupt-vector register to get the active interrupt source? Here is my version: asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) { u32 irq; irq = readl(int_base + SW_INT_VECTOR_REG) >> 2; irq = irq_find_mapping(sunxi_vic_domain, irq); handle_IRQ(irq, regs); } I suggest you give it a try. Cheers, Stefan
Hi Stefan, Le 16/11/2012 08:35, Stefan Roese a écrit : > On 11/15/2012 11:46 PM, Maxime Ripard wrote: > > <snip> > >> diff --git a/drivers/irqchip/irq-sunxi.c b/drivers/irqchip/irq-sunxi.c >> new file mode 100644 >> index 0000000..9dcb323 >> --- /dev/null >> +++ b/drivers/irqchip/irq-sunxi.c >> @@ -0,0 +1,173 @@ >> +/* >> + * Allwinner A1X SoCs IRQ chip driver. >> + * >> + * Copyright (C) 2012 Maxime Ripard >> + * >> + * Maxime Ripard <maxime.ripard@free-electrons.com> >> + * >> + * Based on code from >> + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> >> + * Benn Huang <benn@allwinnertech.com> >> + * >> + * 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/io.h> >> +#include <linux/irq.h> >> +#include <linux/of.h> >> +#include <linux/of_address.h> >> +#include <linux/of_irq.h> >> + >> +#include <linux/irqchip/sunxi.h> >> + >> +#define SUNXI_IRQ_PROTECTION_REG 0x08 >> +#define SUNXI_IRQ_NMI_CTRL_REG 0x0c >> +#define SUNXI_IRQ_PENDING_REG(x) (0x10 + 0x4 * x) >> +#define SUNXI_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x) >> +#define SUNXI_IRQ_ENABLE_REG(x) (0x40 + 0x4 * x) >> +#define SUNXI_IRQ_MASK_REG(x) (0x50 + 0x4 * x) >> + >> +static void __iomem *sunxi_irq_base; >> +static struct irq_domain *sunxi_irq_domain; >> + >> +void sunxi_irq_ack(struct irq_data *irqd) >> +{ >> + unsigned int irq = irqd_to_hwirq(irqd); >> + unsigned int irq_off = irq % 32; >> + int reg = irq / 32; >> + u32 val; >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); >> + writel(val & ~(1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); >> + writel(val | (1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(reg)); >> + writel(val | (1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_PENDING_REG(reg)); > > Are you sure that you need to touch all those 23registers to ack the > interrupt? I know that the original code provided by Allwinner does > exactly this. My tests have shown though, that writing to the pending > reg is enough. Ok, I'll test that and remove the first two writes then. > >> +} >> + >> +static void sunxi_irq_mask(struct irq_data *irqd) >> +{ >> + unsigned int irq = irqd_to_hwirq(irqd); >> + unsigned int irq_off = irq % 32; >> + int reg = irq / 32; >> + u32 val; >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); >> + writel(val & ~(1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); >> + writel(val | (1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); > > I unmasked all interrupts in the mask register in the init function. > Then only using the enable register for masking/unmasking seems to be > enough. What do you think? That's reasonable I guess. It will be in the v2. >> +} >> + >> +static void sunxi_irq_unmask(struct irq_data *irqd) >> +{ >> + unsigned int irq = irqd_to_hwirq(irqd); >> + unsigned int irq_off = irq % 32; >> + int reg = irq / 32; >> + u32 val; >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); >> + writel(val | (1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); >> + >> + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); >> + writel(val & ~(1 << irq_off), >> + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); >> + >> + /* Must clear pending bit when enabled */ >> + if (irq == 0) >> + writel(1, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(0)); > > Again. Might be that one register is enough here. Ok. >> +} >> + >> +static struct irq_chip sunxi_irq_chip = { >> + .name = "sunxi_irq", >> + .irq_ack = sunxi_irq_ack, >> + .irq_mask = sunxi_irq_mask, >> + .irq_unmask = sunxi_irq_unmask, >> +}; >> + >> +static int sunxi_irq_map(struct irq_domain *d, unsigned int virq, >> + irq_hw_number_t hw) >> +{ >> + irq_set_chip(virq, &sunxi_irq_chip); >> + irq_set_handler(virq, handle_level_irq); > > irq_set_chip_and_handler() Ok. >> + set_irq_flags(virq, IRQF_VALID | IRQF_PROBE); >> + >> + return 0; >> +} >> + >> +static struct irq_domain_ops sunxi_irq_ops = { >> + .map = sunxi_irq_map, >> + .xlate = irq_domain_xlate_onecell, >> +}; >> + >> +static int __init sunxi_of_init(struct device_node *node, >> + struct device_node *parent) >> +{ >> + sunxi_irq_base = of_iomap(node, 0); >> + if (!sunxi_irq_base) >> + panic("%s: unable to map IC registers\n", >> + node->full_name); >> + >> + /* Disable all interrupts */ >> + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(0)); >> + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(1)); >> + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(2)); >> + >> + /* Mask all the interrupts */ >> + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(0)); >> + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(1)); >> + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(2)); > > Here is where I wrote 0 to the mask registers. > >> + /* Clear all the pending interrupts */ >> + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(0)); >> + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(1)); >> + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(2)); >> + >> + /* Enable protection mode */ >> + writel(0x01, sunxi_irq_base + SUNXI_IRQ_PROTECTION_REG); >> + >> + /* Configure the external interrupt source type */ >> + writel(0x00, sunxi_irq_base + SUNXI_IRQ_NMI_CTRL_REG); >> + >> + sunxi_irq_domain = irq_domain_add_linear(node, 3 * 32, >> + &sunxi_irq_ops, NULL); >> + if (!sunxi_irq_domain) >> + panic("%s: unable to create IRQ domain\n", node->full_name); >> + >> + return 0; >> +} >> + >> +static struct of_device_id sunxi_irq_dt_ids[] __initconst = { >> + { .compatible = "allwinner,sunxi-ic", .data = sunxi_of_init } >> +}; >> + >> +void __init sunxi_init_irq(void) >> +{ >> + of_irq_init(sunxi_irq_dt_ids); >> +} >> + >> +asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) >> +{ >> + u32 irq, reg; >> + int i; >> + >> + for (i = 0; i < 3; i++) { >> + reg = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(i)); >> + if (reg == 0) >> + continue; >> + irq = ilog2(reg); >> + break; >> + } >> + irq = irq_find_mapping(sunxi_irq_domain, irq); >> + handle_IRQ(irq, regs); > > Why don't you use the interrupt-vector register to get the active > interrupt source? Here is my version: > > asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) > { > u32 irq; > > irq = readl(int_base + SW_INT_VECTOR_REG) >> 2; > irq = irq_find_mapping(sunxi_vic_domain, irq); > handle_IRQ(irq, regs); > } > > I suggest you give it a try. It definitely looks nicer. I'll try that and update. Maxime
On Fri, 16 Nov 2012 10:16:42 +0100, Maxime Ripard wrote: > >> +asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) > >> +{ > >> + u32 irq, reg; > >> + int i; > >> + > >> + for (i = 0; i < 3; i++) { > >> + reg = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(i)); > >> + if (reg == 0) > >> + continue; > >> + irq = ilog2(reg); > >> + break; > >> + } > >> + irq = irq_find_mapping(sunxi_irq_domain, irq); > >> + handle_IRQ(irq, regs); > > > > Why don't you use the interrupt-vector register to get the active > > interrupt source? Here is my version: > > > > asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) > > { > > u32 irq; > > > > irq = readl(int_base + SW_INT_VECTOR_REG) >> 2; > > irq = irq_find_mapping(sunxi_vic_domain, irq); > > handle_IRQ(irq, regs); > > } > > > > I suggest you give it a try. > > It definitely looks nicer. I'll try that and update. How does this SW_INT_VECTOR_REG behave when there are multiple interrupts pending? Shouldn't the code be something like: do { hwirq = readl(int_base + SW_INT_VECTOR_REG) >> 2; irq = irq_find_mapping(sunxi_vic_domain, hwirq); handle_IRQ(irq, regs); } while(hwirq != 0); Or maybe the != 0 is not the good condition, but the idea is to handle all pending interrupts. That said, the original code from Maxime was not doing that as well. Thomas
On 11/16/2012 11:38 AM, Thomas Petazzoni wrote: >>> asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) >>> { >>> u32 irq; >>> >>> irq = readl(int_base + SW_INT_VECTOR_REG) >> 2; >>> irq = irq_find_mapping(sunxi_vic_domain, irq); >>> handle_IRQ(irq, regs); >>> } >>> >>> I suggest you give it a try. >> >> It definitely looks nicer. I'll try that and update. > > How does this SW_INT_VECTOR_REG behave when there are multiple > interrupts pending? Not 100% sure. Hard to guess with this sparse documentation. I would expect that multiple interrupts would be queued here. > Shouldn't the code be something like: > > do { > hwirq = readl(int_base + SW_INT_VECTOR_REG) >> 2; > irq = irq_find_mapping(sunxi_vic_domain, hwirq); > handle_IRQ(irq, regs); > } while(hwirq != 0); > > Or maybe the != 0 is not the good condition, but the idea is to handle > all pending interrupts. That said, the original code from Maxime was > not doing that as well. Yes, that would be a good change. Thanks, Stefan
diff --git a/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-ic.txt b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-ic.txt new file mode 100644 index 0000000..7f9fb85 --- /dev/null +++ b/Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-ic.txt @@ -0,0 +1,104 @@ +Allwinner Sunxi Interrupt Controller + +Required properties: + +- compatible : should be "allwinner,sunxi-ic" +- reg : Specifies base physical address and size of the registers. +- interrupt-controller : Identifies the node as an interrupt controller +- #interrupt-cells : Specifies the number of cells needed to encode an + interrupt source. The value shall be 1. + +The interrupt sources are as follows: + +0: ENMI +1: UART0 +2: UART1 +3: UART2 +4: UART3 +5: IR0 +6: IR1 +7: I2C0 +8: I2C1 +9: I2C2 +10: SPI0 +11: SPI1 +12: SPI2 +13: SPDIF +14: AC97 +15: TS +16: I2S +17: UART4 +18: UART5 +19: UART6 +20: UART7 +21: KEYPAD +22: TIMER0 +23: TIMER1 +24: TIMER2 +25: TIMER3 +26: CAN +27: DMA +28: PIO +29: TOUCH_PANEL +30: AUDIO_CODEC +31: LRADC +32: SDMC0 +33: SDMC1 +34: SDMC2 +35: SDMC3 +36: MEMSTICK +37: NAND +38: USB0 +39: USB1 +40: USB2 +41: SCR +42: CSI0 +43: CSI1 +44: LCDCTRL0 +45: LCDCTRL1 +46: MP +47: DEFEBE0 +48: DEFEBE1 +49: PMU +50: SPI3 +51: TZASC +52: PATA +53: VE +54: SS +55: EMAC +56: SATA +57: GPS +58: HDMI +59: TVE +60: ACE +61: TVD +62: PS2_0 +63: PS2_1 +64: USB3 +65: USB4 +66: PLE_PFM +67: TIMER4 +68: TIMER5 +69: GPU_GP +70: GPU_GPMMU +71: GPU_PP0 +72: GPU_PPMMU0 +73: GPU_PMU +74: GPU_RSV0 +75: GPU_RSV1 +76: GPU_RSV2 +77: GPU_RSV3 +78: GPU_RSV4 +79: GPU_RSV5 +80: GPU_RSV6 +82: SYNC_TIMER0 +83: SYNC_TIMER1 + +Example: + +intc: interrupt-controller { + compatible = "allwinner,sunxi-ic"; + reg = <0x01c20400 0x400>; + interrupt-controller; + #interrupt-cells = <2>; +}; diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile index 054321d..2444d07 100644 --- a/drivers/irqchip/Makefile +++ b/drivers/irqchip/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_ARCH_BCM2835) += irq-bcm2835.o +obj-$(CONFIG_ARCH_SUNXI) += irq-sunxi.o diff --git a/drivers/irqchip/irq-sunxi.c b/drivers/irqchip/irq-sunxi.c new file mode 100644 index 0000000..9dcb323 --- /dev/null +++ b/drivers/irqchip/irq-sunxi.c @@ -0,0 +1,173 @@ +/* + * Allwinner A1X SoCs IRQ chip driver. + * + * Copyright (C) 2012 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * Based on code from + * Allwinner Technology Co., Ltd. <www.allwinnertech.com> + * Benn Huang <benn@allwinnertech.com> + * + * 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/io.h> +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> + +#include <linux/irqchip/sunxi.h> + +#define SUNXI_IRQ_PROTECTION_REG 0x08 +#define SUNXI_IRQ_NMI_CTRL_REG 0x0c +#define SUNXI_IRQ_PENDING_REG(x) (0x10 + 0x4 * x) +#define SUNXI_IRQ_FIQ_PENDING_REG(x) (0x20 + 0x4 * x) +#define SUNXI_IRQ_ENABLE_REG(x) (0x40 + 0x4 * x) +#define SUNXI_IRQ_MASK_REG(x) (0x50 + 0x4 * x) + +static void __iomem *sunxi_irq_base; +static struct irq_domain *sunxi_irq_domain; + +void sunxi_irq_ack(struct irq_data *irqd) +{ + unsigned int irq = irqd_to_hwirq(irqd); + unsigned int irq_off = irq % 32; + int reg = irq / 32; + u32 val; + + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); + writel(val & ~(1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); + + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); + writel(val | (1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); + + val = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(reg)); + writel(val | (1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_PENDING_REG(reg)); +} + +static void sunxi_irq_mask(struct irq_data *irqd) +{ + unsigned int irq = irqd_to_hwirq(irqd); + unsigned int irq_off = irq % 32; + int reg = irq / 32; + u32 val; + + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); + writel(val & ~(1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); + + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); + writel(val | (1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); +} + +static void sunxi_irq_unmask(struct irq_data *irqd) +{ + unsigned int irq = irqd_to_hwirq(irqd); + unsigned int irq_off = irq % 32; + int reg = irq / 32; + u32 val; + + val = readl(sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); + writel(val | (1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(reg)); + + val = readl(sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); + writel(val & ~(1 << irq_off), + sunxi_irq_base + SUNXI_IRQ_MASK_REG(reg)); + + /* Must clear pending bit when enabled */ + if (irq == 0) + writel(1, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(0)); +} + +static struct irq_chip sunxi_irq_chip = { + .name = "sunxi_irq", + .irq_ack = sunxi_irq_ack, + .irq_mask = sunxi_irq_mask, + .irq_unmask = sunxi_irq_unmask, +}; + +static int sunxi_irq_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip(virq, &sunxi_irq_chip); + irq_set_handler(virq, handle_level_irq); + set_irq_flags(virq, IRQF_VALID | IRQF_PROBE); + + return 0; +} + +static struct irq_domain_ops sunxi_irq_ops = { + .map = sunxi_irq_map, + .xlate = irq_domain_xlate_onecell, +}; + +static int __init sunxi_of_init(struct device_node *node, + struct device_node *parent) +{ + sunxi_irq_base = of_iomap(node, 0); + if (!sunxi_irq_base) + panic("%s: unable to map IC registers\n", + node->full_name); + + /* Disable all interrupts */ + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(0)); + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(1)); + writel(0, sunxi_irq_base + SUNXI_IRQ_ENABLE_REG(2)); + + /* Mask all the interrupts */ + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(0)); + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(1)); + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_MASK_REG(2)); + + /* Clear all the pending interrupts */ + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(0)); + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(1)); + writel(0xffffffff, sunxi_irq_base + SUNXI_IRQ_PENDING_REG(2)); + + /* Enable protection mode */ + writel(0x01, sunxi_irq_base + SUNXI_IRQ_PROTECTION_REG); + + /* Configure the external interrupt source type */ + writel(0x00, sunxi_irq_base + SUNXI_IRQ_NMI_CTRL_REG); + + sunxi_irq_domain = irq_domain_add_linear(node, 3 * 32, + &sunxi_irq_ops, NULL); + if (!sunxi_irq_domain) + panic("%s: unable to create IRQ domain\n", node->full_name); + + return 0; +} + +static struct of_device_id sunxi_irq_dt_ids[] __initconst = { + { .compatible = "allwinner,sunxi-ic", .data = sunxi_of_init } +}; + +void __init sunxi_init_irq(void) +{ + of_irq_init(sunxi_irq_dt_ids); +} + +asmlinkage void __exception_irq_entry sunxi_handle_irq(struct pt_regs *regs) +{ + u32 irq, reg; + int i; + + for (i = 0; i < 3; i++) { + reg = readl(sunxi_irq_base + SUNXI_IRQ_PENDING_REG(i)); + if (reg == 0) + continue; + irq = ilog2(reg); + break; + } + irq = irq_find_mapping(sunxi_irq_domain, irq); + handle_IRQ(irq, regs); +} diff --git a/include/linux/irqchip/sunxi.h b/include/linux/irqchip/sunxi.h new file mode 100644 index 0000000..1fe2c22 --- /dev/null +++ b/include/linux/irqchip/sunxi.h @@ -0,0 +1,27 @@ +/* + * Copyright 2012 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * 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. + */ + +#ifndef __LINUX_IRQCHIP_SUNXI_H +#define __LINUX_IRQCHIP_SUNXI_H + +#include <asm/exception.h> + +extern void sunxi_init_irq(void); + +extern asmlinkage void __exception_irq_entry sunxi_handle_irq( + struct pt_regs *regs); + +#endif
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> CC: Thomas Gleixner <tglx@linutronix.de> --- .../interrupt-controller/allwinner,sunxi-ic.txt | 104 ++++++++++++ drivers/irqchip/Makefile | 1 + drivers/irqchip/irq-sunxi.c | 173 ++++++++++++++++++++ include/linux/irqchip/sunxi.h | 27 +++ 4 files changed, 305 insertions(+) create mode 100644 Documentation/devicetree/bindings/interrupt-controller/allwinner,sunxi-ic.txt create mode 100644 drivers/irqchip/irq-sunxi.c create mode 100644 include/linux/irqchip/sunxi.h