1 /* $NetBSD: am18xx_intc.c,v 1.2 2026/07/30 15:08:08 yurix Exp $ */ 2 3 /*- 4 * Copyright (c) 2025 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Yuri Honegger. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * Interrupt controller for the TI AM18XX SOC 34 */ 35 36 #define _INTR_PRIVATE 37 38 #include <sys/cdefs.h> 39 #include <sys/cpu.h> 40 #include <sys/device.h> 41 42 #include <dev/fdt/fdtvar.h> 43 44 #include <arm/armreg.h> 45 #include <arm/pic/picvar.h> 46 47 struct am18xx_intc_softc { 48 struct pic_softc sc_pic; 49 bus_space_tag_t sc_bst; 50 bus_space_handle_t sc_bsh; 51 int sc_num_irqs; 52 }; 53 54 static int am18xx_intc_match(device_t, cfdata_t, void *); 55 static void am18xx_intc_attach(device_t, device_t, void *); 56 57 static void am18xx_intc_unblock_irqs(struct pic_softc *, size_t, uint32_t); 58 static void am18xx_intc_block_irqs(struct pic_softc *, size_t, uint32_t); 59 static void am18xx_intc_establish_irq(struct pic_softc *, 60 struct intrsource *); 61 static void am18xx_intc_set_priority(struct pic_softc *, int); 62 static void am18xx_intc_irq_handler(void *); 63 static void *am18xx_intc_fdt_establish(device_t, u_int *, int, int, 64 int (*)(void *), void *, 65 const char *); 66 static void am18xx_intc_fdt_disestablish(device_t, void *); 67 static bool am18xx_intc_fdt_intrstr(device_t, u_int *, char *, size_t); 68 static void am18xx_intc_enable_interrupts(struct am18xx_intc_softc *); 69 70 #define AM18XX_AINTC_CR 0x4 71 #define AM18XX_AINTC_GER 0x10 72 #define AM18XX_AINTC_SECR1 0x280 73 #define AM18XX_AINTC_ESR1 0x300 74 #define AM18XX_AINTC_ECR1 0x380 75 #define AM18XX_AINTC_CMR0 0x400 76 #define AM18XX_AINTC_HIER 0x1500 77 78 #define AM18XX_AINTC_GER_ENABLE 1 79 #define AM18XX_AINTC_HIER_IRQ 2 80 #define AM18XX_AINTC_IRQ_CHANNEL 4 /* any number between 2 and 31 works */ 81 82 #define INTC_READ(sc, reg) \ 83 bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, reg) 84 #define INTC_WRITE(sc, reg, val) \ 85 bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, reg, val) 86 #define PICTOSOFTC(pic) \ 87 ((struct am18xx_intc_softc *) \ 88 ((uintptr_t)(pic) - offsetof(struct am18xx_intc_softc, sc_pic))) 89 90 static struct am18xx_intc_softc *intc_softc; 91 92 CFATTACH_DECL_NEW(am18xxintc, sizeof(struct am18xx_intc_softc), 93 am18xx_intc_match, am18xx_intc_attach, NULL, NULL); 94 95 static const struct device_compatible_entry compat_data[] = { 96 { .compat = "ti,cp-intc" }, 97 DEVICE_COMPAT_EOL 98 }; 99 100 static const struct fdtbus_interrupt_controller_func am18xx_intc_fdt_funcs = { 101 .establish = am18xx_intc_fdt_establish, 102 .disestablish = am18xx_intc_fdt_disestablish, 103 .intrstr = am18xx_intc_fdt_intrstr, 104 }; 105 106 static const struct pic_ops am18xx_intc_picops = { 107 .pic_unblock_irqs = am18xx_intc_unblock_irqs, 108 .pic_block_irqs = am18xx_intc_block_irqs, 109 .pic_establish_irq = am18xx_intc_establish_irq, 110 .pic_set_priority = am18xx_intc_set_priority, 111 }; 112 113 static void * 114 am18xx_intc_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags, 115 int (*func)(void *), void *arg, const char *xname) 116 { 117 struct am18xx_intc_softc * const sc = device_private(dev); 118 119 const u_int irq = be32toh(specifier[0]); 120 if (irq >= sc->sc_num_irqs) { 121 device_printf(dev, "IRQ %u is out of range\n", irq); 122 return NULL; 123 } 124 125 const u_int mpsafe = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0; 126 return intr_establish_xname(irq, ipl, IST_LEVEL | mpsafe, func, arg, 127 xname); 128 } 129 130 static void 131 am18xx_intc_fdt_disestablish(device_t dev, void *ih) 132 { 133 return intr_disestablish(ih); 134 } 135 136 static bool 137 am18xx_intc_fdt_intrstr(device_t dev, u_int *specifier, char *buf, 138 size_t buflen) 139 { 140 const u_int irq = be32toh(specifier[0]); 141 snprintf(buf, buflen, "irq %d", irq); 142 return true; 143 } 144 145 static void 146 am18xx_intc_unblock_irqs(struct pic_softc *pic, size_t irq_base, uint32_t mask) 147 { 148 struct am18xx_intc_softc * const sc = PICTOSOFTC(pic); 149 150 const size_t group = irq_base / 32; 151 uint32_t esr_reg = AM18XX_AINTC_ESR1 + 4 * group; 152 INTC_WRITE(sc, esr_reg, mask); 153 } 154 155 static void 156 am18xx_intc_block_irqs(struct pic_softc *pic, size_t irq_base, uint32_t mask) 157 { 158 struct am18xx_intc_softc * const sc = PICTOSOFTC(pic); 159 160 const size_t group = irq_base / 32; 161 uint32_t ecr_reg = AM18XX_AINTC_ECR1 + 4 * group; 162 INTC_WRITE(sc, ecr_reg, mask); 163 } 164 165 static void 166 am18xx_intc_establish_irq(struct pic_softc *pic, struct intrsource *is) 167 { 168 struct am18xx_intc_softc * const sc = PICTOSOFTC(pic); 169 170 /* there is one CMR register per 4 interrupts*/ 171 uint32_t cmr_reg = AM18XX_AINTC_CMR0 + (is->is_irq & (~0x3)); 172 uint32_t cmr_shift = (is->is_irq % 4) * 8; 173 174 /* update CMR register with channel */ 175 uint32_t cmr = INTC_READ(sc, cmr_reg); 176 cmr = cmr & ~(0xFF<<cmr_shift); 177 cmr |= AM18XX_AINTC_IRQ_CHANNEL << cmr_shift; 178 INTC_WRITE(sc, cmr_reg, cmr); 179 } 180 181 static void 182 am18xx_intc_set_priority(struct pic_softc *pic, int new_ipl) 183 { 184 curcpu()->ci_cpl = new_ipl; 185 } 186 187 static int 188 find_pending_irqs(struct am18xx_intc_softc *sc, size_t group) 189 { 190 uint32_t reg = AM18XX_AINTC_SECR1 + (4 * group); 191 uint32_t pending = INTC_READ(sc, reg); 192 193 /* clear interrupts */ 194 INTC_WRITE(sc, reg, pending); 195 196 if (pending == 0) 197 return 0; 198 199 return pic_mark_pending_sources(&sc->sc_pic, group * 32, pending); 200 } 201 202 static void 203 am18xx_intc_irq_handler(void *frame) 204 { 205 struct cpu_info * const ci = curcpu(); 206 struct am18xx_intc_softc * const sc = intc_softc; 207 const int old_ipl = ci->ci_cpl; 208 const uint32_t oldipl_mask = __BIT(old_ipl); 209 int ipl_mask = 0; 210 211 ci->ci_data.cpu_nintr++; 212 213 for (int group = 0; group <= sc->sc_num_irqs/32; group++) { 214 ipl_mask |= find_pending_irqs(sc, group); 215 } 216 217 if ((ipl_mask & ~oldipl_mask) > oldipl_mask) 218 pic_do_pending_ints(I32_bit, old_ipl, frame); 219 } 220 221 /* 222 * Enable interrupt according to the TRM section 11.3.2 223 */ 224 static void am18xx_intc_enable_interrupts(struct am18xx_intc_softc *sc) 225 { 226 /* disable interrupts */ 227 INTC_WRITE(sc, AM18XX_AINTC_GER, 0); 228 INTC_WRITE(sc, AM18XX_AINTC_HIER, 0); 229 230 /* disable nesting and prioritization in CR */ 231 INTC_WRITE(sc, AM18XX_AINTC_CR, 0); 232 233 /* disable all interrupts, clear their status */ 234 for (int i = 0; i <= sc->sc_num_irqs / 32; i++) { 235 INTC_WRITE(sc, AM18XX_AINTC_ECR1 + 4 * i, 0xFFFFFFFF); 236 INTC_WRITE(sc, AM18XX_AINTC_SECR1 + 4 * i, 0xFFFFFFFF); 237 } 238 239 /* enable IRQ interrupts */ 240 INTC_WRITE(sc, AM18XX_AINTC_HIER, AM18XX_AINTC_HIER_IRQ); 241 242 /* set enable bit in GER */ 243 INTC_WRITE(sc, AM18XX_AINTC_GER, AM18XX_AINTC_GER_ENABLE); 244 } 245 246 int 247 am18xx_intc_match(device_t parent, cfdata_t cf, void *aux) 248 { 249 struct fdt_attach_args * const faa = aux; 250 251 return of_compatible_match(faa->faa_phandle, compat_data); 252 } 253 254 void 255 am18xx_intc_attach(device_t parent, device_t self, void *aux) 256 { 257 struct am18xx_intc_softc * const sc = device_private(self); 258 struct fdt_attach_args * const faa = aux; 259 const int phandle = faa->faa_phandle; 260 bus_addr_t addr; 261 bus_size_t size; 262 uint32_t maxsources; 263 264 sc->sc_bst = faa->faa_bst; 265 intc_softc = sc; 266 267 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { 268 aprint_error(": couldn't get registers\n"); 269 return; 270 } 271 if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { 272 aprint_error(": couldn't map registers\n"); 273 return; 274 } 275 276 if (of_getprop_uint32(phandle, "ti,intc-size", &maxsources) != 0) { 277 aprint_error(": couldn't get max interrupt number\n"); 278 return; 279 } 280 sc->sc_num_irqs = maxsources; 281 282 sc->sc_pic.pic_ops = &am18xx_intc_picops; 283 sc->sc_pic.pic_maxsources = maxsources; 284 strlcpy(sc->sc_pic.pic_name, device_xname(self), 285 sizeof(sc->sc_pic.pic_name)); 286 287 pic_add(&sc->sc_pic, 0); 288 fdtbus_register_interrupt_controller(self, phandle, 289 &am18xx_intc_fdt_funcs); 290 291 arm_fdt_irq_set_handler(am18xx_intc_irq_handler); 292 am18xx_intc_enable_interrupts(sc); 293 294 aprint_normal("\n"); 295 } 296 297