xref: /linux/drivers/irqchip/irq-sunxi-nmi.c (revision 621cde16e49b3ecf7d59a8106a20aaebfb4a59a9)
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>
2241a83e06SJoel Porquet #include <linux/irqchip.h>
236058bb36SCarlo Caione #include <linux/irqchip/chained_irq.h>
246058bb36SCarlo Caione 
256058bb36SCarlo Caione #define SUNXI_NMI_SRC_TYPE_MASK	0x00000003
266058bb36SCarlo Caione 
279ce18f6fSChen-Yu Tsai #define SUNXI_NMI_IRQ_BIT	BIT(0)
289ce18f6fSChen-Yu Tsai 
29173bda53SChen-Yu Tsai /*
30173bda53SChen-Yu Tsai  * For deprecated sun6i-a31-sc-nmi compatible.
31173bda53SChen-Yu Tsai  */
324e346146SSamuel Holland #define SUN6I_NMI_CTRL		0x00
334e346146SSamuel Holland #define SUN6I_NMI_PENDING	0x04
344e346146SSamuel Holland #define SUN6I_NMI_ENABLE	0x34
359ce18f6fSChen-Yu Tsai 
369ce18f6fSChen-Yu Tsai #define SUN7I_NMI_CTRL		0x00
379ce18f6fSChen-Yu Tsai #define SUN7I_NMI_PENDING	0x04
389ce18f6fSChen-Yu Tsai #define SUN7I_NMI_ENABLE	0x08
399ce18f6fSChen-Yu Tsai 
409ce18f6fSChen-Yu Tsai #define SUN9I_NMI_CTRL		0x00
419ce18f6fSChen-Yu Tsai #define SUN9I_NMI_ENABLE	0x04
429ce18f6fSChen-Yu Tsai #define SUN9I_NMI_PENDING	0x08
439ce18f6fSChen-Yu Tsai 
446058bb36SCarlo Caione enum {
456058bb36SCarlo Caione 	SUNXI_SRC_TYPE_LEVEL_LOW = 0,
466058bb36SCarlo Caione 	SUNXI_SRC_TYPE_EDGE_FALLING,
476058bb36SCarlo Caione 	SUNXI_SRC_TYPE_LEVEL_HIGH,
486058bb36SCarlo Caione 	SUNXI_SRC_TYPE_EDGE_RISING,
496058bb36SCarlo Caione };
506058bb36SCarlo Caione 
516058bb36SCarlo Caione struct sunxi_sc_nmi_reg_offs {
526058bb36SCarlo Caione 	u32 ctrl;
536058bb36SCarlo Caione 	u32 pend;
546058bb36SCarlo Caione 	u32 enable;
556058bb36SCarlo Caione };
566058bb36SCarlo Caione 
5711b345abSChen-Yu Tsai static const struct sunxi_sc_nmi_reg_offs sun6i_reg_offs __initconst = {
589ce18f6fSChen-Yu Tsai 	.ctrl	= SUN6I_NMI_CTRL,
599ce18f6fSChen-Yu Tsai 	.pend	= SUN6I_NMI_PENDING,
609ce18f6fSChen-Yu Tsai 	.enable	= SUN6I_NMI_ENABLE,
616058bb36SCarlo Caione };
626058bb36SCarlo Caione 
6311b345abSChen-Yu Tsai static const struct sunxi_sc_nmi_reg_offs sun7i_reg_offs __initconst = {
64c81a2480SChen-Yu Tsai 	.ctrl	= SUN7I_NMI_CTRL,
65c81a2480SChen-Yu Tsai 	.pend	= SUN7I_NMI_PENDING,
66c81a2480SChen-Yu Tsai 	.enable	= SUN7I_NMI_ENABLE,
67c81a2480SChen-Yu Tsai };
68c81a2480SChen-Yu Tsai 
6911b345abSChen-Yu Tsai static const struct sunxi_sc_nmi_reg_offs sun9i_reg_offs __initconst = {
709ce18f6fSChen-Yu Tsai 	.ctrl	= SUN9I_NMI_CTRL,
719ce18f6fSChen-Yu Tsai 	.pend	= SUN9I_NMI_PENDING,
729ce18f6fSChen-Yu Tsai 	.enable	= SUN9I_NMI_ENABLE,
73bbbb03c1SChen-Yu Tsai };
74bbbb03c1SChen-Yu Tsai 
sunxi_sc_nmi_write(struct irq_chip_generic * gc,u32 off,u32 val)756058bb36SCarlo Caione static inline void sunxi_sc_nmi_write(struct irq_chip_generic *gc, u32 off,
766058bb36SCarlo Caione 				      u32 val)
776058bb36SCarlo Caione {
78332fd7c4SKevin Cernekee 	irq_reg_writel(gc, val, off);
796058bb36SCarlo Caione }
806058bb36SCarlo Caione 
sunxi_sc_nmi_read(struct irq_chip_generic * gc,u32 off)816058bb36SCarlo Caione static inline u32 sunxi_sc_nmi_read(struct irq_chip_generic *gc, u32 off)
826058bb36SCarlo Caione {
83332fd7c4SKevin Cernekee 	return irq_reg_readl(gc, off);
846058bb36SCarlo Caione }
856058bb36SCarlo Caione 
sunxi_sc_nmi_handle_irq(struct irq_desc * desc)86bd0b9ac4SThomas Gleixner static void sunxi_sc_nmi_handle_irq(struct irq_desc *desc)
876058bb36SCarlo Caione {
886058bb36SCarlo Caione 	struct irq_domain *domain = irq_desc_get_handler_data(desc);
895b29264cSJiang Liu 	struct irq_chip *chip = irq_desc_get_chip(desc);
906058bb36SCarlo Caione 
916058bb36SCarlo Caione 	chained_irq_enter(chip, desc);
92*046a6ee2SMarc Zyngier 	generic_handle_domain_irq(domain, 0);
936058bb36SCarlo Caione 	chained_irq_exit(chip, desc);
946058bb36SCarlo Caione }
956058bb36SCarlo Caione 
sunxi_sc_nmi_set_type(struct irq_data * data,unsigned int flow_type)966058bb36SCarlo Caione static int sunxi_sc_nmi_set_type(struct irq_data *data, unsigned int flow_type)
976058bb36SCarlo Caione {
986058bb36SCarlo Caione 	struct irq_chip_generic *gc = irq_data_get_irq_chip_data(data);
996058bb36SCarlo Caione 	struct irq_chip_type *ct = gc->chip_types;
1006058bb36SCarlo Caione 	u32 src_type_reg;
1016058bb36SCarlo Caione 	u32 ctrl_off = ct->regs.type;
1026058bb36SCarlo Caione 	unsigned int src_type;
1036058bb36SCarlo Caione 	unsigned int i;
1046058bb36SCarlo Caione 
1056058bb36SCarlo Caione 	irq_gc_lock(gc);
1066058bb36SCarlo Caione 
1076058bb36SCarlo Caione 	switch (flow_type & IRQF_TRIGGER_MASK) {
1086058bb36SCarlo Caione 	case IRQ_TYPE_EDGE_FALLING:
1096058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_EDGE_FALLING;
1106058bb36SCarlo Caione 		break;
1116058bb36SCarlo Caione 	case IRQ_TYPE_EDGE_RISING:
1126058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_EDGE_RISING;
1136058bb36SCarlo Caione 		break;
1146058bb36SCarlo Caione 	case IRQ_TYPE_LEVEL_HIGH:
1156058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_LEVEL_HIGH;
1166058bb36SCarlo Caione 		break;
1176058bb36SCarlo Caione 	case IRQ_TYPE_NONE:
1186058bb36SCarlo Caione 	case IRQ_TYPE_LEVEL_LOW:
1196058bb36SCarlo Caione 		src_type = SUNXI_SRC_TYPE_LEVEL_LOW;
1206058bb36SCarlo Caione 		break;
1216058bb36SCarlo Caione 	default:
1226058bb36SCarlo Caione 		irq_gc_unlock(gc);
1232d6caaedSChen-Yu Tsai 		pr_err("Cannot assign multiple trigger modes to IRQ %d.\n",
1242d6caaedSChen-Yu Tsai 			data->irq);
1256058bb36SCarlo Caione 		return -EBADR;
1266058bb36SCarlo Caione 	}
1276058bb36SCarlo Caione 
1286058bb36SCarlo Caione 	irqd_set_trigger_type(data, flow_type);
1296058bb36SCarlo Caione 	irq_setup_alt_chip(data, flow_type);
1306058bb36SCarlo Caione 
131febe0696SAxel Lin 	for (i = 0; i < gc->num_ct; i++, ct++)
1326058bb36SCarlo Caione 		if (ct->type & flow_type)
1336058bb36SCarlo Caione 			ctrl_off = ct->regs.type;
1346058bb36SCarlo Caione 
1356058bb36SCarlo Caione 	src_type_reg = sunxi_sc_nmi_read(gc, ctrl_off);
1366058bb36SCarlo Caione 	src_type_reg &= ~SUNXI_NMI_SRC_TYPE_MASK;
1376058bb36SCarlo Caione 	src_type_reg |= src_type;
1386058bb36SCarlo Caione 	sunxi_sc_nmi_write(gc, ctrl_off, src_type_reg);
1396058bb36SCarlo Caione 
1406058bb36SCarlo Caione 	irq_gc_unlock(gc);
1416058bb36SCarlo Caione 
1426058bb36SCarlo Caione 	return IRQ_SET_MASK_OK;
1436058bb36SCarlo Caione }
1446058bb36SCarlo Caione 
sunxi_sc_nmi_irq_init(struct device_node * node,const struct sunxi_sc_nmi_reg_offs * reg_offs)1456058bb36SCarlo Caione static int __init sunxi_sc_nmi_irq_init(struct device_node *node,
14611b345abSChen-Yu Tsai 					const struct sunxi_sc_nmi_reg_offs *reg_offs)
1476058bb36SCarlo Caione {
1486058bb36SCarlo Caione 	struct irq_domain *domain;
1496058bb36SCarlo Caione 	struct irq_chip_generic *gc;
1506058bb36SCarlo Caione 	unsigned int irq;
1516058bb36SCarlo Caione 	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
1526058bb36SCarlo Caione 	int ret;
1536058bb36SCarlo Caione 
1546058bb36SCarlo Caione 
1556058bb36SCarlo Caione 	domain = irq_domain_add_linear(node, 1, &irq_generic_chip_ops, NULL);
1566058bb36SCarlo Caione 	if (!domain) {
1572d6caaedSChen-Yu Tsai 		pr_err("Could not register interrupt domain.\n");
1586058bb36SCarlo Caione 		return -ENOMEM;
1596058bb36SCarlo Caione 	}
1606058bb36SCarlo Caione 
1612d6caaedSChen-Yu Tsai 	ret = irq_alloc_domain_generic_chips(domain, 1, 2, DRV_NAME,
1626058bb36SCarlo Caione 					     handle_fasteoi_irq, clr, 0,
1636058bb36SCarlo Caione 					     IRQ_GC_INIT_MASK_CACHE);
1646058bb36SCarlo Caione 	if (ret) {
1652d6caaedSChen-Yu Tsai 		pr_err("Could not allocate generic interrupt chip.\n");
1666058bb36SCarlo Caione 		goto fail_irqd_remove;
1676058bb36SCarlo Caione 	}
1686058bb36SCarlo Caione 
1696058bb36SCarlo Caione 	irq = irq_of_parse_and_map(node, 0);
1706058bb36SCarlo Caione 	if (irq <= 0) {
1712d6caaedSChen-Yu Tsai 		pr_err("unable to parse irq\n");
1726058bb36SCarlo Caione 		ret = -EINVAL;
1736058bb36SCarlo Caione 		goto fail_irqd_remove;
1746058bb36SCarlo Caione 	}
1756058bb36SCarlo Caione 
1766058bb36SCarlo Caione 	gc = irq_get_domain_generic_chip(domain, 0);
1770e841b04SChen-Yu Tsai 	gc->reg_base = of_io_request_and_map(node, 0, of_node_full_name(node));
178cfe199afSVladimir Zapolskiy 	if (IS_ERR(gc->reg_base)) {
1792d6caaedSChen-Yu Tsai 		pr_err("unable to map resource\n");
180cfe199afSVladimir Zapolskiy 		ret = PTR_ERR(gc->reg_base);
1816058bb36SCarlo Caione 		goto fail_irqd_remove;
1826058bb36SCarlo Caione 	}
1836058bb36SCarlo Caione 
1846058bb36SCarlo Caione 	gc->chip_types[0].type			= IRQ_TYPE_LEVEL_MASK;
1856058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_mask		= irq_gc_mask_clr_bit;
1866058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_unmask	= irq_gc_mask_set_bit;
1876058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_eoi		= irq_gc_ack_set_bit;
1886058bb36SCarlo Caione 	gc->chip_types[0].chip.irq_set_type	= sunxi_sc_nmi_set_type;
1896058bb36SCarlo Caione 	gc->chip_types[0].chip.flags		= IRQCHIP_EOI_THREADED | IRQCHIP_EOI_IF_HANDLED;
1906058bb36SCarlo Caione 	gc->chip_types[0].regs.ack		= reg_offs->pend;
1916058bb36SCarlo Caione 	gc->chip_types[0].regs.mask		= reg_offs->enable;
1926058bb36SCarlo Caione 	gc->chip_types[0].regs.type		= reg_offs->ctrl;
1936058bb36SCarlo Caione 
1946058bb36SCarlo Caione 	gc->chip_types[1].type			= IRQ_TYPE_EDGE_BOTH;
1956058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_ack		= irq_gc_ack_set_bit;
1966058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_mask		= irq_gc_mask_clr_bit;
1976058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_unmask	= irq_gc_mask_set_bit;
1986058bb36SCarlo Caione 	gc->chip_types[1].chip.irq_set_type	= sunxi_sc_nmi_set_type;
1996058bb36SCarlo Caione 	gc->chip_types[1].regs.ack		= reg_offs->pend;
2006058bb36SCarlo Caione 	gc->chip_types[1].regs.mask		= reg_offs->enable;
2016058bb36SCarlo Caione 	gc->chip_types[1].regs.type		= reg_offs->ctrl;
2026058bb36SCarlo Caione 	gc->chip_types[1].handler		= handle_edge_irq;
2036058bb36SCarlo Caione 
204e3ece0d5SChen-Yu Tsai 	/* Disable any active interrupts */
2056058bb36SCarlo Caione 	sunxi_sc_nmi_write(gc, reg_offs->enable, 0);
206e3ece0d5SChen-Yu Tsai 
207e3ece0d5SChen-Yu Tsai 	/* Clear any pending NMI interrupts */
2089ce18f6fSChen-Yu Tsai 	sunxi_sc_nmi_write(gc, reg_offs->pend, SUNXI_NMI_IRQ_BIT);
2096058bb36SCarlo Caione 
2103200a712SThomas Gleixner 	irq_set_chained_handler_and_data(irq, sunxi_sc_nmi_handle_irq, domain);
2111b422ecdSHans de Goede 
2126058bb36SCarlo Caione 	return 0;
2136058bb36SCarlo Caione 
2146058bb36SCarlo Caione fail_irqd_remove:
2156058bb36SCarlo Caione 	irq_domain_remove(domain);
2166058bb36SCarlo Caione 
2176058bb36SCarlo Caione 	return ret;
2186058bb36SCarlo Caione }
2196058bb36SCarlo Caione 
sun6i_sc_nmi_irq_init(struct device_node * node,struct device_node * parent)2206058bb36SCarlo Caione static int __init sun6i_sc_nmi_irq_init(struct device_node *node,
2216058bb36SCarlo Caione 					struct device_node *parent)
2226058bb36SCarlo Caione {
2236058bb36SCarlo Caione 	return sunxi_sc_nmi_irq_init(node, &sun6i_reg_offs);
2246058bb36SCarlo Caione }
2256058bb36SCarlo Caione IRQCHIP_DECLARE(sun6i_sc_nmi, "allwinner,sun6i-a31-sc-nmi", sun6i_sc_nmi_irq_init);
2266058bb36SCarlo Caione 
sun7i_sc_nmi_irq_init(struct device_node * node,struct device_node * parent)2276058bb36SCarlo Caione static int __init sun7i_sc_nmi_irq_init(struct device_node *node,
2286058bb36SCarlo Caione 					struct device_node *parent)
2296058bb36SCarlo Caione {
2306058bb36SCarlo Caione 	return sunxi_sc_nmi_irq_init(node, &sun7i_reg_offs);
2316058bb36SCarlo Caione }
2326058bb36SCarlo Caione IRQCHIP_DECLARE(sun7i_sc_nmi, "allwinner,sun7i-a20-sc-nmi", sun7i_sc_nmi_irq_init);
233bbbb03c1SChen-Yu Tsai 
sun9i_nmi_irq_init(struct device_node * node,struct device_node * parent)234bbbb03c1SChen-Yu Tsai static int __init sun9i_nmi_irq_init(struct device_node *node,
235bbbb03c1SChen-Yu Tsai 				     struct device_node *parent)
236bbbb03c1SChen-Yu Tsai {
237bbbb03c1SChen-Yu Tsai 	return sunxi_sc_nmi_irq_init(node, &sun9i_reg_offs);
238bbbb03c1SChen-Yu Tsai }
239bbbb03c1SChen-Yu Tsai IRQCHIP_DECLARE(sun9i_nmi, "allwinner,sun9i-a80-nmi", sun9i_nmi_irq_init);
240