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