-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ARM: sun7i/sun6i: irqchip: Add irqchip driver for NMI controller
Allwinner A20/A31 SoCs have special registers to control / (un)mask / acknowledge NMI. This NMI controller is separated and independent from GIC. This patch adds a new irqchip to manage NMI. Signed-off-by: Carlo Caione <carlo@caione.org> Acked-by: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: linux-arm-kernel@lists.infradead.org Cc: linux-sunxi@googlegroups.com Cc: mark.rutland@arm.com Cc: hdegoede@redhat.com Link: http://lkml.kernel.org/r/1395256879-8475-2-git-send-email-carlo@caione.org Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
- Loading branch information
Carlo Caione
authored and
Thomas Gleixner
committed
Mar 26, 2014
1 parent
d6ee6d2
commit 6058bb3
Showing
2 changed files
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
/* | ||
* Allwinner A20/A31 SoCs NMI IRQ chip driver. | ||
* | ||
* Carlo Caione <carlo.caione@gmail.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/bitops.h> | ||
#include <linux/device.h> | ||
#include <linux/io.h> | ||
#include <linux/irq.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/irqdomain.h> | ||
#include <linux/of_irq.h> | ||
#include <linux/of_address.h> | ||
#include <linux/of_platform.h> | ||
#include <linux/irqchip/chained_irq.h> | ||
#include "irqchip.h" | ||
|
||
#define SUNXI_NMI_SRC_TYPE_MASK 0x00000003 | ||
|
||
enum { | ||
SUNXI_SRC_TYPE_LEVEL_LOW = 0, | ||
SUNXI_SRC_TYPE_EDGE_FALLING, | ||
SUNXI_SRC_TYPE_LEVEL_HIGH, | ||
SUNXI_SRC_TYPE_EDGE_RISING, | ||
}; | ||
|
||
struct sunxi_sc_nmi_reg_offs { | ||
u32 ctrl; | ||
u32 pend; | ||
u32 enable; | ||
}; | ||
|
||
static struct sunxi_sc_nmi_reg_offs sun7i_reg_offs = { | ||
.ctrl = 0x00, | ||
.pend = 0x04, | ||
.enable = 0x08, | ||
}; | ||
|
||
static struct sunxi_sc_nmi_reg_offs sun6i_reg_offs = { | ||
.ctrl = 0x00, | ||
.pend = 0x04, | ||
.enable = 0x34, | ||
}; | ||
|
||
static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off, | ||
u32 val) | ||
{ | ||
irq_reg_writel(val, gc->reg_base + off); | ||
} | ||
|
||
static inline u32 sunxi_sc_nmi_read(struct irq_chip_generic *gc, u32 off) | ||
{ | ||
return irq_reg_readl(gc->reg_base + off); | ||
} | ||
|
||
static void sunxi_sc_nmi_handle_irq(unsigned int irq, struct irq_desc *desc) | ||
{ | ||
struct irq_domain *domain = irq_desc_get_handler_data(desc); | ||
struct irq_chip *chip = irq_get_chip(irq); | ||
unsigned int virq = irq_find_mapping(domain, 0); | ||
|
||
chained_irq_enter(chip, desc); | ||
generic_handle_irq(virq); | ||
chained_irq_exit(chip, desc); | ||
} | ||
|
||
static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type) | ||
{ | ||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data); | ||
struct irq_chip_type *ct = gc->chip_types; | ||
u32 src_type_reg; | ||
u32 ctrl_off = ct->regs.type; | ||
unsigned int src_type; | ||
unsigned int i; | ||
|
||
irq_gc_lock(gc); | ||
|
||
switch (flow_type & IRQF_TRIGGER_MASK) { | ||
case IRQ_TYPE_EDGE_FALLING: | ||
src_type = SUNXI_SRC_TYPE_EDGE_FALLING; | ||
break; | ||
case IRQ_TYPE_EDGE_RISING: | ||
src_type = SUNXI_SRC_TYPE_EDGE_RISING; | ||
break; | ||
case IRQ_TYPE_LEVEL_HIGH: | ||
src_type = SUNXI_SRC_TYPE_LEVEL_HIGH; | ||
break; | ||
case IRQ_TYPE_NONE: | ||
case IRQ_TYPE_LEVEL_LOW: | ||
src_type = SUNXI_SRC_TYPE_LEVEL_LOW; | ||
break; | ||
default: | ||
irq_gc_unlock(gc); | ||
pr_err("%s: Cannot assign multiple trigger modes to IRQ %d.\n", | ||
__func__, data->irq); | ||
return -EBADR; | ||
} | ||
|
||
irqd_set_trigger_type(data, flow_type); | ||
irq_setup_alt_chip(data, flow_type); | ||
|
||
for (i = 0; i <= gc->num_ct; i++, ct++) | ||
if (ct->type & flow_type) | ||
ctrl_off = ct->regs.type; | ||
|
||
src_type_reg = sunxi_sc_nmi_read(gc, ctrl_off); | ||
src_type_reg &= ~SUNXI_NMI_SRC_TYPE_MASK; | ||
src_type_reg |= src_type; | ||
sunxi_sc_nmi_write(gc, ctrl_off, src_type_reg); | ||
|
||
irq_gc_unlock(gc); | ||
|
||
return IRQ_SET_MASK_OK; | ||
} | ||
|
||
static int __init sunxi_sc_nmi_irq_init(struct device_node *node, | ||
struct sunxi_sc_nmi_reg_offs *reg_offs) | ||
{ | ||
struct irq_domain *domain; | ||
struct irq_chip_generic *gc; | ||
unsigned int irq; | ||
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; | ||
int ret; | ||
|
||
|
||
domain = irq_domain_add_linear(node, 1, &irq_generic_chip_ops, NULL); | ||
if (!domain) { | ||
pr_err("%s: Could not register interrupt domain.\n", node->name); | ||
return -ENOMEM; | ||
} | ||
|
||
ret = irq_alloc_domain_generic_chips(domain, 1, 2, node->name, | ||
handle_fasteoi_irq, clr, 0, | ||
IRQ_GC_INIT_MASK_CACHE); | ||
if (ret) { | ||
pr_err("%s: Could not allocate generic interrupt chip.\n", | ||
node->name); | ||
goto fail_irqd_remove; | ||
} | ||
|
||
irq = irq_of_parse_and_map(node, 0); | ||
if (irq <= 0) { | ||
pr_err("%s: unable to parse irq\n", node->name); | ||
ret = -EINVAL; | ||
goto fail_irqd_remove; | ||
} | ||
|
||
gc = irq_get_domain_generic_chip(domain, 0); | ||
gc->reg_base = of_iomap(node, 0); | ||
if (!gc->reg_base) { | ||
pr_err("%s: unable to map resource\n", node->name); | ||
ret = -ENOMEM; | ||
goto fail_irqd_remove; | ||
} | ||
|
||
gc->chip_types[0].type = IRQ_TYPE_LEVEL_MASK; | ||
gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit; | ||
gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit; | ||
gc->chip_types[0].chip.irq_eoi = irq_gc_ack_set_bit; | ||
gc->chip_types[0].chip.irq_set_type = sunxi_sc_nmi_set_type; | ||
gc->chip_types[0].chip.flags = IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED; | ||
gc->chip_types[0].regs.ack = reg_offs->pend; | ||
gc->chip_types[0].regs.mask = reg_offs->enable; | ||
gc->chip_types[0].regs.type = reg_offs->ctrl; | ||
|
||
gc->chip_types[1].type = IRQ_TYPE_EDGE_BOTH; | ||
gc->chip_types[1].chip.name = gc->chip_types[0].chip.name; | ||
gc->chip_types[1].chip.irq_ack = irq_gc_ack_set_bit; | ||
gc->chip_types[1].chip.irq_mask = irq_gc_mask_clr_bit; | ||
gc->chip_types[1].chip.irq_unmask = irq_gc_mask_set_bit; | ||
gc->chip_types[1].chip.irq_set_type = sunxi_sc_nmi_set_type; | ||
gc->chip_types[1].regs.ack = reg_offs->pend; | ||
gc->chip_types[1].regs.mask = reg_offs->enable; | ||
gc->chip_types[1].regs.type = reg_offs->ctrl; | ||
gc->chip_types[1].handler = handle_edge_irq; | ||
|
||
irq_set_handler_data(irq, domain); | ||
irq_set_chained_handler(irq, sunxi_sc_nmi_handle_irq); | ||
|
||
sunxi_sc_nmi_write(gc, reg_offs->enable, 0); | ||
sunxi_sc_nmi_write(gc, reg_offs->pend, 0x1); | ||
|
||
return 0; | ||
|
||
fail_irqd_remove: | ||
irq_domain_remove(domain); | ||
|
||
return ret; | ||
} | ||
|
||
static int __init sun6i_sc_nmi_irq_init(struct device_node *node, | ||
struct device_node *parent) | ||
{ | ||
return sunxi_sc_nmi_irq_init(node, &sun6i_reg_offs); | ||
} | ||
IRQCHIP_DECLARE(sun6i_sc_nmi, "allwinner,sun6i-a31-sc-nmi", sun6i_sc_nmi_irq_init); | ||
|
||
static int __init sun7i_sc_nmi_irq_init(struct device_node *node, | ||
struct device_node *parent) | ||
{ | ||
return sunxi_sc_nmi_irq_init(node, &sun7i_reg_offs); | ||
} | ||
IRQCHIP_DECLARE(sun7i_sc_nmi, "allwinner,sun7i-a20-sc-nmi", sun7i_sc_nmi_irq_init); |