xref: /linux/drivers/irqchip/irq-sunxi-nmi.c (revision 762f99f4f3cb41a775b5157dd761217beba65873)
16058bb36SCarlo Caione /*
26058bb36SCarlo Caione  * Allwinner A20/A31 SoCs NMI IRQ chip driver.
36058bb36SCarlo Caione  *
46058bb36SCarlo Caione  * Carlo Caione <carlo.caione@gmail.com>
56058bb36SCarlo Caione  *
66058bb36SCarlo Caione  * This file is licensed under the terms of the GNU General Public
76058bb36SCarlo Caione  * License version 2.  This program is licensed "as is" without any
86058bb36SCarlo Caione  * warranty of any kind, whether express or implied.
96058bb36SCarlo Caione  */
106058bb36SCarlo Caione 
112d6caaedSChen-Yu Tsai #define DRV_NAME	"sunxi-nmi"
122d6caaedSChen-Yu Tsai #define pr_fmt(fmt)	DRV_NAME ": " fmt
132d6caaedSChen-Yu Tsai 
146058bb36SCarlo Caione #include <linux/bitops.h>
156058bb36SCarlo Caione #include <linux/device.h>
166058bb36SCarlo Caione #include <linux/io.h>
176058bb36SCarlo Caione #include <linux/irq.h>
186058bb36SCarlo Caione #include <linux/interrupt.h>
196058bb36SCarlo Caione #include <linux/irqdomain.h>
206058bb36SCarlo Caione #include <linux/of_irq.h>
216058bb36SCarlo Caione #include <linux/of_address.h>
226058bb36SCarlo Caione #include <linux/of_platform.h>
2341a83e06SJoel Porquet #include <linux/irqchip.h>
246058bb36SCarlo Caione #include <linux/irqchip/chained_irq.h>
256058bb36SCarlo Caione 
266058bb36SCarlo Caione #define SUNXI_NMI_SRC_TYPE_MASK	0x00000003
276058bb36SCarlo Caione 
289ce18f6fSChen-Yu Tsai #define SUNXI_NMI_IRQ_BIT	BIT(0)
299ce18f6fSChen-Yu Tsai 
30173bda53SChen-Yu Tsai /*
31173bda53SChen-Yu Tsai  * For deprecated sun6i-a31-sc-nmi compatible.
32173bda53SChen-Yu Tsai  */
334e346146SSamuel Holland #define SUN6I_NMI_CTRL		0x00
344e346146SSamuel Holland #define SUN6I_NMI_PENDING	0x04
354e346146SSamuel Holland #define SUN6I_NMI_ENABLE	0x34
369ce18f6fSChen-Yu Tsai 
379ce18f6fSChen-Yu Tsai #define SUN7I_NMI_CTRL		0x00
389ce18f6fSChen-Yu Tsai #define SUN7I_NMI_PENDING	0x04
399ce18f6fSChen-Yu Tsai #define SUN7I_NMI_ENABLE	0x08
409ce18f6fSChen-Yu Tsai 
419ce18f6fSChen-Yu Tsai #define SUN9I_NMI_CTRL		0x00
429ce18f6fSChen-Yu Tsai #define SUN9I_NMI_ENABLE	0x04
439ce18f6fSChen-Yu Tsai #define SUN9I_NMI_PENDING	0x08
449ce18f6fSChen-Yu Tsai 
456058bb36SCarlo Caione enum {
466058bb36SCarlo Caione 	SUNXI_SRC_TYPE_LEVEL_LOW = 0,
476058bb36SCarlo Caione 	SUNXI_SRC_TYPE_EDGE_FALLING,
486058bb36SCarlo Caione 	SUNXI_SRC_TYPE_LEVEL_HIGH,
496058bb36SCarlo Caione 	SUNXI_SRC_TYPE_EDGE_RISING,
506058bb36SCarlo Caione };
516058bb36SCarlo Caione 
526058bb36SCarlo Caione struct sunxi_sc_nmi_reg_offs {
536058bb36SCarlo Caione 	u32 ctrl;
546058bb36SCarlo Caione 	u32 pend;
556058bb36SCarlo Caione 	u32 enable;
566058bb36SCarlo Caione };
576058bb36SCarlo Caione 
5811b345abSChen-Yu Tsai static const struct sunxi_sc_nmi_reg_offs sun6i_reg_offs __initconst = {
599ce18f6fSChen-Yu Tsai 	.ctrl	= SUN6I_NMI_CTRL,
609ce18f6fSChen-Yu Tsai 	.pend	= SUN6I_NMI_PENDING,
619ce18f6fSChen-Yu Tsai 	.enable	= SUN6I_NMI_ENABLE,
626058bb36SCarlo Caione };
636058bb36SCarlo Caione 
6411b345abSChen-Yu Tsai static const struct sunxi_sc_nmi_reg_offs sun7i_reg_offs __initconst = {
65c81a2480SChen-Yu Tsai 	.ctrl	= SUN7I_NMI_CTRL,
66c81a2480SChen-Yu Tsai 	.pend	= SUN7I_NMI_PENDING,
67c81a2480SChen-Yu Tsai 	.enable	= SUN7I_NMI_ENABLE,
68c81a2480SChen-Yu Tsai };
69c81a2480SChen-Yu Tsai 
7011b345abSChen-Yu Tsai static const struct sunxi_sc_nmi_reg_offs sun9i_reg_offs __initconst = {
719ce18f6fSChen-Yu Tsai 	.ctrl	= SUN9I_NMI_CTRL,
729ce18f6fSChen-Yu Tsai 	.pend	= SUN9I_NMI_PENDING,
739ce18f6fSChen-Yu Tsai 	.enable	= SUN9I_NMI_ENABLE,
74bbbb03c1SChen-Yu Tsai };
75bbbb03c1SChen-Yu Tsai 
766058bb36SCarlo Caione static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
776058bb36SCarlo Caione 				      u32 val)
786058bb36SCarlo Caione {
79332fd7c4SKevin Cernekee 	irq_reg_writel(gc, val, off);
806058bb36SCarlo Caione }
816058bb36SCarlo Caione 
826058bb36SCarlo Caione static inline u32 sunxi_sc_nmi_read(struct irq_chip_generic *gc, u32 off)
836058bb36SCarlo Caione {
84332fd7c4SKevin Cernekee 	return irq_reg_readl(gc, off);
856058bb36SCarlo Caione }
866058bb36SCarlo Caione 
87bd0b9ac4SThomas Gleixner static void sunxi_sc_nmi_handle_irq(struct irq_desc *desc)
886058bb36SCarlo Caione {
896058bb36SCarlo Caione 	struct irq_domain *domain = irq_desc_get_handler_data(desc);
905b29264cSJiang Liu 	struct irq_chip *chip = irq_desc_get_chip(desc);
916058bb36SCarlo Caione 
926058bb36SCarlo Caione 	chained_irq_enter(chip, desc);
93*046a6ee2SMarc Zyngier 	generic_handle_domain_irq(domain, 0);
946058bb36SCarlo Caione 	chained_irq_exit(chip, desc);
956058bb36SCarlo Caione }
966058bb36SCarlo Caione 
976058bb36SCarlo Caione static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type)
986058bb36SCarlo Caione {
996058bb36SCarlo Caione 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
1006058bb36SCarlo Caione 	struct irq_chip_type *ct = gc->chip_types;
1016058bb36SCarlo Caione 	u32 src_type_reg;
1026058bb36SCarlo Caione 	u32 ctrl_off = ct->regs.type;
1036058bb36SCarlo Caione 	unsigned int src_type;
1046058bb36SCarlo Caione 	unsigned int i;
1056058bb36SCarlo Caione 
1066058bb36SCarlo Caione 	irq_gc_lock(gc);
1076058bb36SCarlo Caione 
1086058bb36SCarlo Caione 	switch (flow_type & IRQF_TRIGGER_MASK) {
1096058bb36SCarlo Caione 	case IRQ_TYPE_EDGE_FALLING:
1106058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_EDGE_FALLING;
1116058bb36SCarlo Caione 		break;
1126058bb36SCarlo Caione 	case IRQ_TYPE_EDGE_RISING:
1136058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_EDGE_RISING;
1146058bb36SCarlo Caione 		break;
1156058bb36SCarlo Caione 	case IRQ_TYPE_LEVEL_HIGH:
1166058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_LEVEL_HIGH;
1176058bb36SCarlo Caione 		break;
1186058bb36SCarlo Caione 	case IRQ_TYPE_NONE:
1196058bb36SCarlo Caione 	case IRQ_TYPE_LEVEL_LOW:
1206058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_LEVEL_LOW;
1216058bb36SCarlo Caione 		break;
1226058bb36SCarlo Caione 	default:
1236058bb36SCarlo Caione 		irq_gc_unlock(gc);
1242d6caaedSChen-Yu Tsai 		pr_err("Cannot assign multiple trigger modes to IRQ %d.\n",
1252d6caaedSChen-Yu Tsai 			data->irq);
1266058bb36SCarlo Caione 		return -EBADR;
1276058bb36SCarlo Caione 	}
1286058bb36SCarlo Caione 
1296058bb36SCarlo Caione 	irqd_set_trigger_type(data, flow_type);
1306058bb36SCarlo Caione 	irq_setup_alt_chip(data, flow_type);
1316058bb36SCarlo Caione 
132febe0696SAxel Lin 	for (i = 0; i < gc->num_ct; i++, ct++)
1336058bb36SCarlo Caione 		if (ct->type & flow_type)
1346058bb36SCarlo Caione 			ctrl_off = ct->regs.type;
1356058bb36SCarlo Caione 
1366058bb36SCarlo Caione 	src_type_reg = sunxi_sc_nmi_read(gc, ctrl_off);
1376058bb36SCarlo Caione 	src_type_reg &= ~SUNXI_NMI_SRC_TYPE_MASK;
1386058bb36SCarlo Caione 	src_type_reg |= src_type;
1396058bb36SCarlo Caione 	sunxi_sc_nmi_write(gc, ctrl_off, src_type_reg);
1406058bb36SCarlo Caione 
1416058bb36SCarlo Caione 	irq_gc_unlock(gc);
1426058bb36SCarlo Caione 
1436058bb36SCarlo Caione 	return IRQ_SET_MASK_OK;
1446058bb36SCarlo Caione }
1456058bb36SCarlo Caione 
1466058bb36SCarlo Caione static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
14711b345abSChen-Yu Tsai 					const struct sunxi_sc_nmi_reg_offs *reg_offs)
1486058bb36SCarlo Caione {
1496058bb36SCarlo Caione 	struct irq_domain *domain;
1506058bb36SCarlo Caione 	struct irq_chip_generic *gc;
1516058bb36SCarlo Caione 	unsigned int irq;
1526058bb36SCarlo Caione 	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
1536058bb36SCarlo Caione 	int ret;
1546058bb36SCarlo Caione 
1556058bb36SCarlo Caione 
1566058bb36SCarlo Caione 	domain = irq_domain_add_linear(node, 1, &irq_generic_chip_ops, NULL);
1576058bb36SCarlo Caione 	if (!domain) {
1582d6caaedSChen-Yu Tsai 		pr_err("Could not register interrupt domain.\n");
1596058bb36SCarlo Caione 		return -ENOMEM;
1606058bb36SCarlo Caione 	}
1616058bb36SCarlo Caione 
1622d6caaedSChen-Yu Tsai 	ret = irq_alloc_domain_generic_chips(domain, 1, 2, DRV_NAME,
1636058bb36SCarlo Caione 					     handle_fasteoi_irq, clr, 0,
1646058bb36SCarlo Caione 					     IRQ_GC_INIT_MASK_CACHE);
1656058bb36SCarlo Caione 	if (ret) {
1662d6caaedSChen-Yu Tsai 		pr_err("Could not allocate generic interrupt chip.\n");
1676058bb36SCarlo Caione 		goto fail_irqd_remove;
1686058bb36SCarlo Caione 	}
1696058bb36SCarlo Caione 
1706058bb36SCarlo Caione 	irq = irq_of_parse_and_map(node, 0);
1716058bb36SCarlo Caione 	if (irq <= 0) {
1722d6caaedSChen-Yu Tsai 		pr_err("unable to parse irq\n");
1736058bb36SCarlo Caione 		ret = -EINVAL;
1746058bb36SCarlo Caione 		goto fail_irqd_remove;
1756058bb36SCarlo Caione 	}
1766058bb36SCarlo Caione 
1776058bb36SCarlo Caione 	gc = irq_get_domain_generic_chip(domain, 0);
1780e841b04SChen-Yu Tsai 	gc->reg_base = of_io_request_and_map(node, 0, of_node_full_name(node));
179cfe199afSVladimir Zapolskiy 	if (IS_ERR(gc->reg_base)) {
1802d6caaedSChen-Yu Tsai 		pr_err("unable to map resource\n");
181cfe199afSVladimir Zapolskiy 		ret = PTR_ERR(gc->reg_base);
1826058bb36SCarlo Caione 		goto fail_irqd_remove;
1836058bb36SCarlo Caione 	}
1846058bb36SCarlo Caione 
1856058bb36SCarlo Caione 	gc->chip_types[0].type			= IRQ_TYPE_LEVEL_MASK;
1866058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_mask		= irq_gc_mask_clr_bit;
1876058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_unmask	= irq_gc_mask_set_bit;
1886058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_eoi		= irq_gc_ack_set_bit;
1896058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_set_type	= sunxi_sc_nmi_set_type;
1906058bb36SCarlo Caione 	gc->chip_types[0].chip.flags		= IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED;
1916058bb36SCarlo Caione 	gc->chip_types[0].regs.ack		= reg_offs->pend;
1926058bb36SCarlo Caione 	gc->chip_types[0].regs.mask		= reg_offs->enable;
1936058bb36SCarlo Caione 	gc->chip_types[0].regs.type		= reg_offs->ctrl;
1946058bb36SCarlo Caione 
1956058bb36SCarlo Caione 	gc->chip_types[1].type			= IRQ_TYPE_EDGE_BOTH;
1966058bb36SCarlo Caione 	gc->chip_types[1].chip.name		= gc->chip_types[0].chip.name;
1976058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_ack		= irq_gc_ack_set_bit;
1986058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_mask		= irq_gc_mask_clr_bit;
1996058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_unmask	= irq_gc_mask_set_bit;
2006058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_set_type	= sunxi_sc_nmi_set_type;
2016058bb36SCarlo Caione 	gc->chip_types[1].regs.ack		= reg_offs->pend;
2026058bb36SCarlo Caione 	gc->chip_types[1].regs.mask		= reg_offs->enable;
2036058bb36SCarlo Caione 	gc->chip_types[1].regs.type		= reg_offs->ctrl;
2046058bb36SCarlo Caione 	gc->chip_types[1].handler		= handle_edge_irq;
2056058bb36SCarlo Caione 
206e3ece0d5SChen-Yu Tsai 	/* Disable any active interrupts */
2076058bb36SCarlo Caione 	sunxi_sc_nmi_write(gc, reg_offs->enable, 0);
208e3ece0d5SChen-Yu Tsai 
209e3ece0d5SChen-Yu Tsai 	/* Clear any pending NMI interrupts */
2109ce18f6fSChen-Yu Tsai 	sunxi_sc_nmi_write(gc, reg_offs->pend, SUNXI_NMI_IRQ_BIT);
2116058bb36SCarlo Caione 
2123200a712SThomas Gleixner 	irq_set_chained_handler_and_data(irq, sunxi_sc_nmi_handle_irq, domain);
2131b422ecdSHans de Goede 
2146058bb36SCarlo Caione 	return 0;
2156058bb36SCarlo Caione 
2166058bb36SCarlo Caione fail_irqd_remove:
2176058bb36SCarlo Caione 	irq_domain_remove(domain);
2186058bb36SCarlo Caione 
2196058bb36SCarlo Caione 	return ret;
2206058bb36SCarlo Caione }
2216058bb36SCarlo Caione 
2226058bb36SCarlo Caione static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
2236058bb36SCarlo Caione 					struct device_node *parent)
2246058bb36SCarlo Caione {
2256058bb36SCarlo Caione 	return sunxi_sc_nmi_irq_init(node, &sun6i_reg_offs);
2266058bb36SCarlo Caione }
2276058bb36SCarlo Caione IRQCHIP_DECLARE(sun6i_sc_nmi, "allwinner,sun6i-a31-sc-nmi", sun6i_sc_nmi_irq_init);
2286058bb36SCarlo Caione 
2296058bb36SCarlo Caione static int __init sun7i_sc_nmi_irq_init(struct device_node *node,
2306058bb36SCarlo Caione 					struct device_node *parent)
2316058bb36SCarlo Caione {
2326058bb36SCarlo Caione 	return sunxi_sc_nmi_irq_init(node, &sun7i_reg_offs);
2336058bb36SCarlo Caione }
2346058bb36SCarlo Caione IRQCHIP_DECLARE(sun7i_sc_nmi, "allwinner,sun7i-a20-sc-nmi", sun7i_sc_nmi_irq_init);
235bbbb03c1SChen-Yu Tsai 
236bbbb03c1SChen-Yu Tsai static int __init sun9i_nmi_irq_init(struct device_node *node,
237bbbb03c1SChen-Yu Tsai 				     struct device_node *parent)
238bbbb03c1SChen-Yu Tsai {
239bbbb03c1SChen-Yu Tsai 	return sunxi_sc_nmi_irq_init(node, &sun9i_reg_offs);
240bbbb03c1SChen-Yu Tsai }
241bbbb03c1SChen-Yu Tsai IRQCHIP_DECLARE(sun9i_nmi, "allwinner,sun9i-a80-nmi", sun9i_nmi_irq_init);
242