1 /* $NetBSD: sunxi_nmi.c,v 1.11 2021/01/27 03:10:20 thorpej 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.11 2021/01/27 03:10:20 thorpej Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/bus.h> 36 #include <sys/device.h> 37 #include <sys/intr.h> 38 #include <sys/kernel.h> 39 #include <sys/systm.h> 40 #include <sys/atomic.h> 41 #include <sys/mutex.h> 42 #include <sys/lwp.h> 43 44 #include <dev/fdt/fdtvar.h> 45 46 #include <arm/cpu.h> 47 #include <arm/pic/picvar.h> 48 #include <arm/fdt/arm_fdtvar.h> 49 50 /* ctrl_reg */ 51 #define NMI_CTRL_IRQ_LOW_LEVEL 0 52 #define NMI_CTRL_IRQ_LOW_EDGE 1 53 #define NMI_CTRL_IRQ_HIGH_LEVEL 2 54 #define NMI_CTRL_IRQ_HIGH_EDGE 3 55 #define NMI_CTRL_IRQ_TYPE __BITS(1,0) 56 57 /* pend_reg */ 58 #define NMI_PEND_IRQ_ACK __BIT(0) 59 60 /* enable_reg */ 61 #define NMI_ENABLE_IRQEN __BIT(0) 62 63 struct sunxi_nmi_config { 64 const char * name; 65 bus_size_t ctrl_reg; 66 bus_size_t pend_reg; 67 bus_size_t enable_reg; 68 }; 69 70 static const struct sunxi_nmi_config sun7i_a20_sc_nmi_config = { 71 .name = "NMI", 72 .ctrl_reg = 0x00, 73 .pend_reg = 0x04, 74 .enable_reg = 0x08, 75 }; 76 77 static const struct sunxi_nmi_config sun6i_a31_r_intc_config = { 78 .name = "R_INTC", 79 .ctrl_reg = 0x0c, 80 .pend_reg = 0x10, 81 .enable_reg = 0x40, 82 }; 83 84 static const struct sunxi_nmi_config sun9i_a80_nmi_config = { 85 .name = "NMI", 86 .ctrl_reg = 0x00, 87 .pend_reg = 0x04, 88 .enable_reg = 0x08, 89 }; 90 91 static const struct device_compatible_entry compat_data[] = { 92 { .compat = "allwinner,sun7i-a20-sc-nmi", 93 .data = &sun7i_a20_sc_nmi_config }, 94 { .compat = "allwinner,sun6i-a31-r-intc", 95 .data = &sun6i_a31_r_intc_config }, 96 { .compat = "allwinner,sun9i-a80-nmi", 97 .data = &sun9i_a80_nmi_config }, 98 99 DEVICE_COMPAT_EOL 100 }; 101 102 struct sunxi_nmi_softc { 103 device_t sc_dev; 104 bus_space_tag_t sc_bst; 105 bus_space_handle_t sc_bsh; 106 int sc_phandle; 107 108 kmutex_t sc_intr_lock; 109 110 const struct sunxi_nmi_config *sc_config; 111 112 struct intrsource sc_is; 113 void *sc_ih; 114 }; 115 116 #define NMI_READ(sc, reg) \ 117 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 118 #define NMI_WRITE(sc, reg, val) \ 119 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 120 121 static void 122 sunxi_nmi_irq_ack(struct sunxi_nmi_softc *sc) 123 { 124 uint32_t val; 125 126 val = NMI_READ(sc, sc->sc_config->pend_reg); 127 val |= NMI_PEND_IRQ_ACK; 128 NMI_WRITE(sc, sc->sc_config->pend_reg, val); 129 } 130 131 static void 132 sunxi_nmi_irq_enable(struct sunxi_nmi_softc *sc, bool on) 133 { 134 uint32_t val; 135 136 val = NMI_READ(sc, sc->sc_config->enable_reg); 137 if (on) 138 val |= NMI_ENABLE_IRQEN; 139 else 140 val &= ~NMI_ENABLE_IRQEN; 141 NMI_WRITE(sc, sc->sc_config->enable_reg, val); 142 } 143 144 static void 145 sunxi_nmi_irq_set_type(struct sunxi_nmi_softc *sc, u_int irq_type) 146 { 147 uint32_t val; 148 149 val = NMI_READ(sc, sc->sc_config->ctrl_reg); 150 val &= ~NMI_CTRL_IRQ_TYPE; 151 val |= __SHIFTIN(irq_type, NMI_CTRL_IRQ_TYPE); 152 NMI_WRITE(sc, sc->sc_config->ctrl_reg, val); 153 } 154 155 static int 156 sunxi_nmi_intr(void *priv) 157 { 158 struct sunxi_nmi_softc * const sc = priv; 159 int (*func)(void *); 160 int rv = 0; 161 162 func = atomic_load_acquire(&sc->sc_is.is_func); 163 if (func) 164 rv = func(sc->sc_is.is_arg); 165 166 /* 167 * We don't serialize access to this register because we're the 168 * only thing fiddling wth it. 169 */ 170 sunxi_nmi_irq_ack(sc); 171 172 return rv; 173 } 174 175 static void * 176 sunxi_nmi_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, 177 int (*func)(void *), void *arg, const char *xname) 178 { 179 struct sunxi_nmi_softc * const sc = device_private(dev); 180 u_int irq_type; 181 int ist; 182 183 /* 1st cell is the interrupt number */ 184 const u_int irq = be32toh(specifier[0]); 185 /* 2nd cell is polarity */ 186 const u_int pol = be32toh(specifier[1]); 187 188 if (irq != 0) { 189 #ifdef DIAGNOSTIC 190 device_printf(dev, "IRQ %u is invalid\n", irq); 191 #endif 192 return NULL; 193 } 194 195 switch (pol & 0x7) { 196 case 1: /* IRQ_TYPE_EDGE_RISING */ 197 irq_type = NMI_CTRL_IRQ_HIGH_EDGE; 198 ist = IST_EDGE; 199 break; 200 case 2: /* IRQ_TYPE_EDGE_FALLING */ 201 irq_type = NMI_CTRL_IRQ_LOW_EDGE; 202 ist = IST_EDGE; 203 break; 204 case 3: /* IRQ_TYPE_LEVEL_HIGH */ 205 irq_type = NMI_CTRL_IRQ_HIGH_LEVEL; 206 ist = IST_LEVEL; 207 break; 208 case 4: /* IRQ_TYPE_LEVEL_LOW */ 209 irq_type = NMI_CTRL_IRQ_LOW_LEVEL; 210 ist = IST_LEVEL; 211 break; 212 default: 213 irq_type = NMI_CTRL_IRQ_LOW_LEVEL; 214 ist = IST_LEVEL; 215 break; 216 } 217 218 mutex_enter(&sc->sc_intr_lock); 219 220 if (atomic_load_relaxed(&sc->sc_is.is_func) != NULL) { 221 mutex_exit(&sc->sc_intr_lock); 222 #ifdef DIAGNOSTIC 223 device_printf(dev, "%s in use\n", sc->sc_config->name); 224 #endif 225 return NULL; 226 } 227 228 sc->sc_is.is_arg = arg; 229 atomic_store_release(&sc->sc_is.is_func, func); 230 231 sc->sc_is.is_type = ist; 232 sc->sc_is.is_ipl = ipl; 233 sc->sc_is.is_mpsafe = (flags & FDT_INTR_MPSAFE) ? true : false; 234 235 mutex_exit(&sc->sc_intr_lock); 236 237 sc->sc_ih = fdtbus_intr_establish_xname(sc->sc_phandle, 0, ipl, flags, 238 sunxi_nmi_intr, sc, device_xname(dev)); 239 240 mutex_enter(&sc->sc_intr_lock); 241 sunxi_nmi_irq_set_type(sc, irq_type); 242 sunxi_nmi_irq_enable(sc, true); 243 mutex_exit(&sc->sc_intr_lock); 244 245 return &sc->sc_is; 246 } 247 248 static void 249 sunxi_nmi_fdt_mask(device_t dev, void *ih __unused) 250 { 251 struct sunxi_nmi_softc * const sc = device_private(dev); 252 253 mutex_enter(&sc->sc_intr_lock); 254 if (sc->sc_is.is_mask_count++ == 0) { 255 sunxi_nmi_irq_enable(sc, false); 256 } 257 mutex_exit(&sc->sc_intr_lock); 258 } 259 260 static void 261 sunxi_nmi_fdt_unmask(device_t dev, void *ih __unused) 262 { 263 struct sunxi_nmi_softc * const sc = device_private(dev); 264 265 mutex_enter(&sc->sc_intr_lock); 266 if (sc->sc_is.is_mask_count-- == 1) { 267 sunxi_nmi_irq_enable(sc, true); 268 } 269 mutex_exit(&sc->sc_intr_lock); 270 } 271 272 static void 273 sunxi_nmi_fdt_disestablish(device_t dev, void *ih) 274 { 275 struct sunxi_nmi_softc * const sc = device_private(dev); 276 struct intrsource * const is = ih; 277 278 KASSERT(is == &sc->sc_is); 279 280 mutex_enter(&sc->sc_intr_lock); 281 sunxi_nmi_irq_enable(sc, false); 282 is->is_mask_count = 0; 283 mutex_exit(&sc->sc_intr_lock); 284 285 fdtbus_intr_disestablish(sc->sc_phandle, sc->sc_ih); 286 sc->sc_ih = NULL; 287 288 mutex_enter(&sc->sc_intr_lock); 289 is->is_arg = NULL; 290 is->is_func = NULL; 291 mutex_exit(&sc->sc_intr_lock); 292 } 293 294 static bool 295 sunxi_nmi_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) 296 { 297 struct sunxi_nmi_softc * const sc = device_private(dev); 298 299 snprintf(buf, buflen, "%s", sc->sc_config->name); 300 301 return true; 302 } 303 304 static const struct fdtbus_interrupt_controller_func sunxi_nmi_fdt_funcs = { 305 .establish = sunxi_nmi_fdt_establish, 306 .disestablish = sunxi_nmi_fdt_disestablish, 307 .intrstr = sunxi_nmi_fdt_intrstr, 308 .mask = sunxi_nmi_fdt_mask, 309 .unmask = sunxi_nmi_fdt_unmask, 310 }; 311 312 static int 313 sunxi_nmi_match(device_t parent, cfdata_t cf, void *aux) 314 { 315 struct fdt_attach_args * const faa = aux; 316 317 return of_compatible_match(faa->faa_phandle, compat_data); 318 } 319 320 static void 321 sunxi_nmi_attach(device_t parent, device_t self, void *aux) 322 { 323 struct sunxi_nmi_softc * const sc = device_private(self); 324 struct fdt_attach_args * const faa = aux; 325 const int phandle = faa->faa_phandle; 326 bus_addr_t addr; 327 bus_size_t size; 328 int error; 329 330 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 331 aprint_error(": couldn't get registers\n"); 332 return; 333 } 334 335 sc->sc_dev = self; 336 sc->sc_phandle = phandle; 337 sc->sc_config = of_compatible_lookup(phandle, compat_data)->data; 338 sc->sc_bst = faa->faa_bst; 339 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 340 aprint_error(": couldn't map registers\n"); 341 return; 342 } 343 344 aprint_naive("\n"); 345 aprint_normal(": %s\n", sc->sc_config->name); 346 347 mutex_init(&sc->sc_intr_lock, MUTEX_SPIN, IPL_HIGH); 348 349 /* 350 * Normally it's assumed that an intrsource can be passed to 351 * interrupt_distribute(). We're providing our own that's 352 * independent of our parent PIC, but because we will leave 353 * the intrsource::is_pic field NULL, the right thing 354 * (i.e. nothing) will happen in interrupt_distribute(). 355 */ 356 snprintf(sc->sc_is.is_source, sizeof(sc->sc_is.is_source), 357 "%s", sc->sc_config->name); 358 359 sunxi_nmi_irq_enable(sc, false); 360 sunxi_nmi_irq_ack(sc); 361 362 error = fdtbus_register_interrupt_controller(self, phandle, 363 &sunxi_nmi_fdt_funcs); 364 if (error) { 365 aprint_error_dev(self, "couldn't register with fdtbus: %d\n", 366 error); 367 return; 368 } 369 } 370 371 CFATTACH_DECL_NEW(sunxi_nmi, sizeof(struct sunxi_nmi_softc), 372 sunxi_nmi_match, sunxi_nmi_attach, NULL, NULL); 373