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