1 /* $NetBSD: sunxi_nmi.c,v 1.1 2018/05/02 21:20:20 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #define _INTR_PRIVATE 30 31 #include <sys/cdefs.h> 32 __KERNEL_RCSID(0, "$NetBSD: sunxi_nmi.c,v 1.1 2018/05/02 21:20:20 jmcneill Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/kernel.h> 36 #include <sys/bus.h> 37 #include <sys/device.h> 38 #include <sys/intr.h> 39 #include <sys/systm.h> 40 41 #include <dev/fdt/fdtvar.h> 42 43 #include <arm/cpu.h> 44 #include <arm/pic/picvar.h> 45 #include <arm/fdt/arm_fdtvar.h> 46 47 /* ctrl_reg */ 48 #define NMI_CTRL_IRQ_LOW_LEVEL 0 49 #define NMI_CTRL_IRQ_LOW_EDGE 1 50 #define NMI_CTRL_IRQ_HIGH_LEVEL 2 51 #define NMI_CTRL_IRQ_HIGH_EDGE 3 52 #define NMI_CTRL_IRQ_TYPE __BITS(1,0) 53 54 /* pend_reg */ 55 #define NMI_PEND_IRQ_ACK __BIT(0) 56 57 /* enable_reg */ 58 #define NMI_ENABLE_IRQEN __BIT(0) 59 60 struct sunxi_nmi_config { 61 const char * name; 62 bus_size_t ctrl_reg; 63 bus_size_t pend_reg; 64 bus_size_t enable_reg; 65 }; 66 67 static const struct sunxi_nmi_config sun7i_a20_sc_nmi_config = { 68 .name = "NMI", 69 .ctrl_reg = 0x00, 70 .pend_reg = 0x04, 71 .enable_reg = 0x08, 72 }; 73 74 static const struct sunxi_nmi_config sun6i_a31_r_intc_config = { 75 .name = "R_INTC", 76 .ctrl_reg = 0x0c, 77 .pend_reg = 0x10, 78 .enable_reg = 0x40, 79 }; 80 81 static const struct of_compat_data compat_data[] = { 82 { "allwinner,sun7i-a20-sc-nmi", (uintptr_t)&sun7i_a20_sc_nmi_config }, 83 { "allwinner,sun6i-a31-r-intc", (uintptr_t)&sun6i_a31_r_intc_config }, 84 { NULL } 85 }; 86 87 struct sunxi_nmi_softc { 88 device_t sc_dev; 89 bus_space_tag_t sc_bst; 90 bus_space_handle_t sc_bsh; 91 int sc_phandle; 92 93 const struct sunxi_nmi_config *sc_config; 94 95 int (*sc_func)(void *); 96 void *sc_arg; 97 }; 98 99 #define NMI_READ(sc, reg) \ 100 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 101 #define NMI_WRITE(sc, reg, val) \ 102 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 103 104 static void 105 sunxi_nmi_irq_ack(struct sunxi_nmi_softc *sc) 106 { 107 uint32_t val; 108 109 val = NMI_READ(sc, sc->sc_config->pend_reg); 110 val |= NMI_PEND_IRQ_ACK; 111 NMI_WRITE(sc, sc->sc_config->pend_reg, val); 112 } 113 114 static void 115 sunxi_nmi_irq_enable(struct sunxi_nmi_softc *sc, bool on) 116 { 117 uint32_t val; 118 119 val = NMI_READ(sc, sc->sc_config->enable_reg); 120 if (on) 121 val |= NMI_ENABLE_IRQEN; 122 else 123 val &= ~NMI_ENABLE_IRQEN; 124 NMI_WRITE(sc, sc->sc_config->enable_reg, val); 125 } 126 127 static void 128 sunxi_nmi_irq_set_type(struct sunxi_nmi_softc *sc, u_int irq_type) 129 { 130 uint32_t val; 131 132 val = NMI_READ(sc, sc->sc_config->ctrl_reg); 133 val &= ~NMI_CTRL_IRQ_TYPE; 134 val |= __SHIFTIN(irq_type, NMI_CTRL_IRQ_TYPE); 135 NMI_WRITE(sc, sc->sc_config->ctrl_reg, val); 136 } 137 138 static int 139 sunxi_nmi_intr(void *priv) 140 { 141 struct sunxi_nmi_softc * const sc = priv; 142 int rv = 0; 143 144 if (sc->sc_func) 145 rv = sc->sc_func(sc->sc_arg); 146 147 sunxi_nmi_irq_ack(sc); 148 149 return rv; 150 } 151 152 static void * 153 sunxi_nmi_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, 154 int (*func)(void *), void *arg) 155 { 156 struct sunxi_nmi_softc * const sc = device_private(dev); 157 u_int irq_type; 158 159 /* 1st cell is the interrupt number */ 160 const u_int irq = be32toh(specifier[0]); 161 /* 2nd cell is polarity */ 162 const u_int pol = be32toh(specifier[1]); 163 164 if (sc->sc_func != NULL) { 165 #ifdef DIAGNOSTIC 166 device_printf(dev, "%s in use\n", sc->sc_config->name); 167 #endif 168 return NULL; 169 } 170 171 if (irq != 0) { 172 #ifdef DIAGNOSTIC 173 device_printf(dev, "IRQ %u is invalid\n", irq); 174 #endif 175 return NULL; 176 } 177 178 switch (pol & 0x7) { 179 case 1: /* IRQ_TYPE_EDGE_RISING */ 180 irq_type = NMI_CTRL_IRQ_HIGH_EDGE; 181 break; 182 case 2: /* IRQ_TYPE_EDGE_FALLING */ 183 irq_type = NMI_CTRL_IRQ_LOW_EDGE; 184 break; 185 case 3: /* IRQ_TYPE_LEVEL_HIGH */ 186 irq_type = NMI_CTRL_IRQ_HIGH_LEVEL; 187 break; 188 case 4: /* IRQ_TYPE_LEVEL_LOW */ 189 irq_type = NMI_CTRL_IRQ_LOW_LEVEL; 190 break; 191 default: 192 irq_type = NMI_CTRL_IRQ_LOW_LEVEL; 193 break; 194 } 195 196 sc->sc_func = func; 197 sc->sc_arg = arg; 198 199 sunxi_nmi_irq_set_type(sc, irq_type); 200 sunxi_nmi_irq_enable(sc, true); 201 202 return fdtbus_intr_establish(sc->sc_phandle, 0, ipl, flags, 203 sunxi_nmi_intr, sc); 204 } 205 206 static void 207 sunxi_nmi_fdt_disestablish(device_t dev, void *ih) 208 { 209 struct sunxi_nmi_softc * const sc = device_private(dev); 210 211 sunxi_nmi_irq_enable(sc, false); 212 213 fdtbus_intr_disestablish(sc->sc_phandle, ih); 214 215 sc->sc_func = NULL; 216 sc->sc_arg = NULL; 217 } 218 219 static bool 220 sunxi_nmi_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) 221 { 222 struct sunxi_nmi_softc * const sc = device_private(dev); 223 224 snprintf(buf, buflen, "%s", sc->sc_config->name); 225 226 return true; 227 } 228 229 static const struct fdtbus_interrupt_controller_func sunxi_nmi_fdt_funcs = { 230 .establish = sunxi_nmi_fdt_establish, 231 .disestablish = sunxi_nmi_fdt_disestablish, 232 .intrstr = sunxi_nmi_fdt_intrstr, 233 }; 234 235 static int 236 sunxi_nmi_match(device_t parent, cfdata_t cf, void *aux) 237 { 238 struct fdt_attach_args * const faa = aux; 239 240 return of_match_compat_data(faa->faa_phandle, compat_data); 241 } 242 243 static void 244 sunxi_nmi_attach(device_t parent, device_t self, void *aux) 245 { 246 struct sunxi_nmi_softc * const sc = device_private(self); 247 struct fdt_attach_args * const faa = aux; 248 const int phandle = faa->faa_phandle; 249 bus_addr_t addr; 250 bus_size_t size; 251 int error; 252 253 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 254 aprint_error(": couldn't get registers\n"); 255 return; 256 } 257 258 sc->sc_dev = self; 259 sc->sc_phandle = phandle; 260 sc->sc_config = (void *)of_search_compatible(phandle, compat_data)->data; 261 sc->sc_bst = faa->faa_bst; 262 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 263 aprint_error(": couldn't map registers\n"); 264 return; 265 } 266 267 aprint_naive("\n"); 268 aprint_normal(": %s\n", sc->sc_config->name); 269 270 sunxi_nmi_irq_enable(sc, false); 271 sunxi_nmi_irq_ack(sc); 272 273 error = fdtbus_register_interrupt_controller(self, phandle, 274 &sunxi_nmi_fdt_funcs); 275 if (error) { 276 aprint_error_dev(self, "couldn't register with fdtbus: %d\n", 277 error); 278 return; 279 } 280 } 281 282 CFATTACH_DECL_NEW(sunxi_nmi, sizeof(struct sunxi_nmi_softc), 283 sunxi_nmi_match, sunxi_nmi_attach, NULL, NULL); 284