diff mbox

[2/6] irqchip: sunxi: Add irq controller driver

Message ID 1353019586-21043-3-git-send-email-maxime.ripard@free-electrons.com (mailing list archive)
State New, archived
Headers show

Commit Message

Maxime Ripard Nov. 15, 2012, 10:46 p.m. UTC
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

Comments

Stefan Roese Nov. 16, 2012, 7:35 a.m. UTC | #1
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
Maxime Ripard Nov. 16, 2012, 9:16 a.m. UTC | #2
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
Thomas Petazzoni Nov. 16, 2012, 10:38 a.m. UTC | #3
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
Stefan Roese Nov. 16, 2012, 10:47 a.m. UTC | #4
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 mbox

Patch

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