1/* $NetBSD: sunxi_nmi.c,v 1.12 2021/11/07 17:13:38 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.12 2021/11/07 17:13:38 jmcneill 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 63struct 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 70static 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 77static 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 84static 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 91static 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 102struct 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 u_int sc_intr_nmi; 109 u_int sc_intr_cells; 110 111 kmutex_t sc_intr_lock; 112 113 const struct sunxi_nmi_config *sc_config; 114 115 struct intrsource sc_is; 116 void *sc_ih; 117}; 118 119#define NMI_READ(sc, reg) \ 120 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) 121#define NMI_WRITE(sc, reg, val) \ 122 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) 123 124static void 125sunxi_nmi_irq_ack(struct sunxi_nmi_softc *sc) 126{ 127 uint32_t val; 128 129 val = NMI_READ(sc, sc->sc_config->pend_reg); 130 val |= NMI_PEND_IRQ_ACK; 131 NMI_WRITE(sc, sc->sc_config->pend_reg, val); 132} 133 134static void 135sunxi_nmi_irq_enable(struct sunxi_nmi_softc *sc, bool on) 136{ 137 uint32_t val; 138 139 val = NMI_READ(sc, sc->sc_config->enable_reg); 140 if (on) 141 val |= NMI_ENABLE_IRQEN; 142 else 143 val &= ~NMI_ENABLE_IRQEN; 144 NMI_WRITE(sc, sc->sc_config->enable_reg, val); 145} 146 147static void 148sunxi_nmi_irq_set_type(struct sunxi_nmi_softc *sc, u_int irq_type) 149{ 150 uint32_t val; 151 152 val = NMI_READ(sc, sc->sc_config->ctrl_reg); 153 val &= ~NMI_CTRL_IRQ_TYPE; 154 val |= __SHIFTIN(irq_type, NMI_CTRL_IRQ_TYPE); 155 NMI_WRITE(sc, sc->sc_config->ctrl_reg, val); 156} 157 158static int 159sunxi_nmi_intr(void *priv) 160{ 161 struct sunxi_nmi_softc * const sc = priv; 162 int (*func)(void *); 163 int rv = 0; 164 165 func = atomic_load_acquire(&sc->sc_is.is_func); 166 if (func) 167 rv = func(sc->sc_is.is_arg); 168 169 /* 170 * We don't serialize access to this register because we're the 171 * only thing fiddling wth it. 172 */ 173 sunxi_nmi_irq_ack(sc); 174 175 return rv; 176} 177 178static void * 179sunxi_nmi_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, 180 int (*func)(void *), void *arg, const char *xname) 181{ 182 struct sunxi_nmi_softc * const sc = device_private(dev); 183 u_int irq_type, irq, pol; 184 int ist; 185 186 if (sc->sc_intr_cells == 2) { 187 /* 1st cell is the interrupt number */ 188 irq = be32toh(specifier[0]); 189 /* 2nd cell is polarity */ 190 pol = be32toh(specifier[1]); 191 } else { 192 /* 1st cell is the GIC interrupt type and must be GIC_SPI */ 193 if (be32toh(specifier[0]) != 0) { 194#ifdef DIAGNOSTIC 195 device_printf(dev, "GIC intr type %u is invalid\n", 196 be32toh(specifier[0])); 197#endif 198 return NULL; 199 } 200 /* 2nd cell is the interrupt number */ 201 irq = be32toh(specifier[1]); 202 /* 3rd cell is polarity */ 203 pol = be32toh(specifier[2]); 204 } 205 206 if (sc->sc_intr_cells == 3 && irq != sc->sc_intr_nmi) { 207 /* 208 * Driver is requesting a wakeup irq, which we don't 209 * support today. Just pass it through to the parent 210 * interrupt controller. 211 */ 212 const int ihandle = fdtbus_intr_parent(sc->sc_phandle); 213 if (ihandle == -1) { 214#ifdef DIAGNOSTIC 215 device_printf(dev, "couldn't find interrupt parent\n"); 216#endif 217 return NULL; 218 } 219 return fdtbus_intr_establish_raw(ihandle, specifier, ipl, 220 flags, func, arg, xname); 221 } 222 223 if (sc->sc_intr_cells == 2 && irq != 0) { 224#ifdef DIAGNOSTIC 225 device_printf(dev, "IRQ %u is invalid\n", irq); 226#endif 227 return NULL; 228 } 229 230 switch (pol & 0xf) { 231 case 1: /* IRQ_TYPE_EDGE_RISING */ 232 irq_type = NMI_CTRL_IRQ_HIGH_EDGE; 233 ist = IST_EDGE; 234 break; 235 case 2: /* IRQ_TYPE_EDGE_FALLING */ 236 irq_type = NMI_CTRL_IRQ_LOW_EDGE; 237 ist = IST_EDGE; 238 break; 239 case 4: /* IRQ_TYPE_LEVEL_HIGH */ 240 irq_type = NMI_CTRL_IRQ_HIGH_LEVEL; 241 ist = IST_LEVEL; 242 break; 243 case 8: /* IRQ_TYPE_LEVEL_LOW */ 244 irq_type = NMI_CTRL_IRQ_LOW_LEVEL; 245 ist = IST_LEVEL; 246 break; 247 default: 248 irq_type = NMI_CTRL_IRQ_LOW_LEVEL; 249 ist = IST_LEVEL; 250 break; 251 } 252 253 mutex_enter(&sc->sc_intr_lock); 254 255 if (atomic_load_relaxed(&sc->sc_is.is_func) != NULL) { 256 mutex_exit(&sc->sc_intr_lock); 257#ifdef DIAGNOSTIC 258 device_printf(dev, "%s in use\n", sc->sc_config->name); 259#endif 260 return NULL; 261 } 262 263 sc->sc_is.is_arg = arg; 264 atomic_store_release(&sc->sc_is.is_func, func); 265 266 sc->sc_is.is_type = ist; 267 sc->sc_is.is_ipl = ipl; 268 sc->sc_is.is_mpsafe = (flags & FDT_INTR_MPSAFE) ? true : false; 269 270 mutex_exit(&sc->sc_intr_lock); 271 272 sc->sc_ih = fdtbus_intr_establish_xname(sc->sc_phandle, 0, ipl, flags, 273 sunxi_nmi_intr, sc, device_xname(dev)); 274 275 mutex_enter(&sc->sc_intr_lock); 276 sunxi_nmi_irq_set_type(sc, irq_type); 277 sunxi_nmi_irq_enable(sc, true); 278 mutex_exit(&sc->sc_intr_lock); 279 280 return &sc->sc_is; 281} 282 283static void 284sunxi_nmi_fdt_mask(device_t dev, void *ih __unused) 285{ 286 struct sunxi_nmi_softc * const sc = device_private(dev); 287 288 mutex_enter(&sc->sc_intr_lock); 289 if (sc->sc_is.is_mask_count++ == 0) { 290 sunxi_nmi_irq_enable(sc, false); 291 } 292 mutex_exit(&sc->sc_intr_lock); 293} 294 295static void 296sunxi_nmi_fdt_unmask(device_t dev, void *ih __unused) 297{ 298 struct sunxi_nmi_softc * const sc = device_private(dev); 299 300 mutex_enter(&sc->sc_intr_lock); 301 if (sc->sc_is.is_mask_count-- == 1) { 302 sunxi_nmi_irq_enable(sc, true); 303 } 304 mutex_exit(&sc->sc_intr_lock); 305} 306 307static void 308sunxi_nmi_fdt_disestablish(device_t dev, void *ih) 309{ 310 struct sunxi_nmi_softc * const sc = device_private(dev); 311 struct intrsource * const is = ih; 312 313 KASSERT(is == &sc->sc_is); 314 315 mutex_enter(&sc->sc_intr_lock); 316 sunxi_nmi_irq_enable(sc, false); 317 is->is_mask_count = 0; 318 mutex_exit(&sc->sc_intr_lock); 319 320 fdtbus_intr_disestablish(sc->sc_phandle, sc->sc_ih); 321 sc->sc_ih = NULL; 322 323 mutex_enter(&sc->sc_intr_lock); 324 is->is_arg = NULL; 325 is->is_func = NULL; 326 mutex_exit(&sc->sc_intr_lock); 327} 328 329static bool 330sunxi_nmi_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) 331{ 332 struct sunxi_nmi_softc * const sc = device_private(dev); 333 334 if (sc->sc_intr_cells == 3) { 335 const u_int irq = be32toh(specifier[1]); 336 if (irq != sc->sc_intr_nmi) { 337 const int ihandle = fdtbus_intr_parent(sc->sc_phandle); 338 if (ihandle == -1) { 339 return false; 340 } 341 return fdtbus_intr_str_raw(ihandle, specifier, buf, 342 buflen); 343 } 344 } 345 346 snprintf(buf, buflen, "%s", sc->sc_config->name); 347 348 return true; 349} 350 351static const struct fdtbus_interrupt_controller_func sunxi_nmi_fdt_funcs = { 352 .establish = sunxi_nmi_fdt_establish, 353 .disestablish = sunxi_nmi_fdt_disestablish, 354 .intrstr = sunxi_nmi_fdt_intrstr, 355 .mask = sunxi_nmi_fdt_mask, 356 .unmask = sunxi_nmi_fdt_unmask, 357}; 358 359static int 360sunxi_nmi_match(device_t parent, cfdata_t cf, void *aux) 361{ 362 struct fdt_attach_args * const faa = aux; 363 364 return of_compatible_match(faa->faa_phandle, compat_data); 365} 366 367static void 368sunxi_nmi_attach(device_t parent, device_t self, void *aux) 369{ 370 struct sunxi_nmi_softc * const sc = device_private(self); 371 struct fdt_attach_args * const faa = aux; 372 const int phandle = faa->faa_phandle; 373 const u_int *interrupts; 374 bus_addr_t addr; 375 bus_size_t size; 376 int error, len; 377 378 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 379 aprint_error(": couldn't get registers\n"); 380 return; 381 } 382 383 sc->sc_dev = self; 384 sc->sc_phandle = phandle; 385 sc->sc_config = of_compatible_lookup(phandle, compat_data)->data; 386 sc->sc_bst = faa->faa_bst; 387 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 388 aprint_error(": couldn't map registers\n"); 389 return; 390 } 391 392 of_getprop_uint32(phandle, "#interrupt-cells", &sc->sc_intr_cells); 393 interrupts = fdtbus_get_prop(phandle, "interrupts", &len); 394 if (interrupts == NULL || len != 12 || 395 be32toh(interrupts[0]) != 0 /* GIC_SPI */ || 396 be32toh(interrupts[2]) != 4 /* IRQ_TYPE_LEVEL_HIGH */) { 397 aprint_error(": couldn't find GIC SPI for NMI\n"); 398 return; 399 } 400 sc->sc_intr_nmi = be32toh(interrupts[1]); 401 402 aprint_naive("\n"); 403 aprint_normal(": %s, NMI IRQ %u\n", sc->sc_config->name, sc->sc_intr_nmi); 404 405 mutex_init(&sc->sc_intr_lock, MUTEX_SPIN, IPL_HIGH); 406 407 /* 408 * Normally it's assumed that an intrsource can be passed to 409 * interrupt_distribute(). We're providing our own that's 410 * independent of our parent PIC, but because we will leave 411 * the intrsource::is_pic field NULL, the right thing 412 * (i.e. nothing) will happen in interrupt_distribute(). 413 */ 414 snprintf(sc->sc_is.is_source, sizeof(sc->sc_is.is_source), 415 "%s", sc->sc_config->name); 416 417 sunxi_nmi_irq_enable(sc, false); 418 sunxi_nmi_irq_ack(sc); 419 420 error = fdtbus_register_interrupt_controller(self, phandle, 421 &sunxi_nmi_fdt_funcs); 422 if (error) { 423 aprint_error_dev(self, "couldn't register with fdtbus: %d\n", 424 error); 425 return; 426 } 427} 428 429CFATTACH_DECL_NEW(sunxi_nmi, sizeof(struct sunxi_nmi_softc), 430 sunxi_nmi_match, sunxi_nmi_attach, NULL, NULL); 431