1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2018 Rubicon Communications, LLC (Netgate) 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 * $FreeBSD$ 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include <sys/param.h> 34 #include <sys/systm.h> 35 #include <sys/bus.h> 36 37 #include <sys/kernel.h> 38 #include <sys/module.h> 39 #include <sys/rman.h> 40 #include <sys/lock.h> 41 #include <sys/mutex.h> 42 43 #include <machine/bus.h> 44 #include <machine/resource.h> 45 #include <machine/intr.h> 46 47 #include <dev/fdt/simplebus.h> 48 49 #include <dev/ofw/ofw_bus.h> 50 #include <dev/ofw/ofw_bus_subr.h> 51 52 #include <dt-bindings/interrupt-controller/irq.h> 53 54 #include "pic_if.h" 55 #include "msi_if.h" 56 57 #define ICU_TYPE_NSR 1 58 #define ICU_TYPE_SEI 2 59 60 #define ICU_GRP_NSR 0x0 61 #define ICU_GRP_SR 0x1 62 #define ICU_GRP_SEI 0x4 63 #define ICU_GRP_REI 0x5 64 65 #define ICU_SETSPI_NSR_AL 0x10 66 #define ICU_SETSPI_NSR_AH 0x14 67 #define ICU_CLRSPI_NSR_AL 0x18 68 #define ICU_CLRSPI_NSR_AH 0x1c 69 #define ICU_SETSPI_SEI_AL 0x50 70 #define ICU_SETSPI_SEI_AH 0x54 71 #define ICU_INT_CFG(x) (0x100 + (x) * 4) 72 #define ICU_INT_ENABLE (1 << 24) 73 #define ICU_INT_EDGE (1 << 28) 74 #define ICU_INT_GROUP_SHIFT 29 75 #define ICU_INT_MASK 0x3ff 76 77 #define ICU_INT_SATA0 109 78 #define ICU_INT_SATA1 107 79 80 #define MV_CP110_ICU_MAX_NIRQS 207 81 82 #define MV_CP110_ICU_CLRSPI_OFFSET 0x8 83 84 struct mv_cp110_icu_softc { 85 device_t dev; 86 device_t parent; 87 struct resource *res; 88 struct intr_map_data_fdt *parent_map_data; 89 bool initialized; 90 int type; 91 }; 92 93 static struct resource_spec mv_cp110_icu_res_spec[] = { 94 { SYS_RES_MEMORY, 0, RF_ACTIVE | RF_SHAREABLE }, 95 { -1, 0 } 96 }; 97 98 static struct ofw_compat_data compat_data[] = { 99 {"marvell,cp110-icu-nsr", ICU_TYPE_NSR}, 100 {"marvell,cp110-icu-sei", ICU_TYPE_SEI}, 101 {NULL, 0} 102 }; 103 104 #define RD4(sc, reg) bus_read_4((sc)->res, (reg)) 105 #define WR4(sc, reg, val) bus_write_4((sc)->res, (reg), (val)) 106 107 static int 108 mv_cp110_icu_probe(device_t dev) 109 { 110 111 if (!ofw_bus_status_okay(dev)) 112 return (ENXIO); 113 114 if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) 115 return (ENXIO); 116 117 device_set_desc(dev, "Marvell Interrupt Consolidation Unit"); 118 return (BUS_PROBE_DEFAULT); 119 } 120 121 static int 122 mv_cp110_icu_attach(device_t dev) 123 { 124 struct mv_cp110_icu_softc *sc; 125 phandle_t node, msi_parent; 126 uint32_t reg, icu_grp; 127 int i; 128 129 sc = device_get_softc(dev); 130 sc->dev = dev; 131 node = ofw_bus_get_node(dev); 132 sc->type = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data; 133 sc->initialized = false; 134 135 if (OF_getencprop(node, "msi-parent", &msi_parent, 136 sizeof(phandle_t)) <= 0) { 137 device_printf(dev, "cannot find msi-parent property\n"); 138 return (ENXIO); 139 } 140 141 if ((sc->parent = OF_device_from_xref(msi_parent)) == NULL) { 142 device_printf(dev, "cannot find msi-parent device\n"); 143 return (ENXIO); 144 } 145 if (bus_alloc_resources(dev, mv_cp110_icu_res_spec, &sc->res) != 0) { 146 device_printf(dev, "cannot allocate resources for device\n"); 147 return (ENXIO); 148 } 149 150 if (intr_pic_register(dev, OF_xref_from_node(node)) == NULL) { 151 device_printf(dev, "Cannot register ICU\n"); 152 goto fail; 153 } 154 155 /* Allocate GICP/SEI compatible mapping entry (2 cells) */ 156 sc->parent_map_data = (struct intr_map_data_fdt *)intr_alloc_map_data( 157 INTR_MAP_DATA_FDT, sizeof(struct intr_map_data_fdt) + 158 + 3 * sizeof(phandle_t), M_WAITOK | M_ZERO); 159 160 /* Clear any previous mapping done by firmware. */ 161 for (i = 0; i < MV_CP110_ICU_MAX_NIRQS; i++) { 162 reg = RD4(sc, ICU_INT_CFG(i)); 163 icu_grp = reg >> ICU_INT_GROUP_SHIFT; 164 165 if (icu_grp == ICU_GRP_NSR || icu_grp == ICU_GRP_SEI) 166 WR4(sc, ICU_INT_CFG(i), 0); 167 } 168 169 return (0); 170 171 fail: 172 bus_release_resources(dev, mv_cp110_icu_res_spec, &sc->res); 173 return (ENXIO); 174 } 175 176 static struct intr_map_data * 177 mv_cp110_icu_convert_map_data(struct mv_cp110_icu_softc *sc, struct intr_map_data *data) 178 { 179 struct intr_map_data_fdt *daf; 180 uint32_t reg, irq_no, irq_type; 181 182 daf = (struct intr_map_data_fdt *)data; 183 if (daf->ncells != 2) 184 return (NULL); 185 186 irq_no = daf->cells[0]; 187 if (irq_no >= MV_CP110_ICU_MAX_NIRQS) 188 return (NULL); 189 190 irq_type = daf->cells[1]; 191 if (irq_type != IRQ_TYPE_LEVEL_HIGH && 192 irq_type != IRQ_TYPE_EDGE_RISING) 193 return (NULL); 194 195 /* ICU -> GICP/SEI mapping is set in mv_cp110_icu_map_intr. */ 196 reg = RD4(sc, ICU_INT_CFG(irq_no)); 197 198 /* Construct GICP compatible mapping. */ 199 sc->parent_map_data->ncells = 2; 200 sc->parent_map_data->cells[0] = reg & ICU_INT_MASK; 201 sc->parent_map_data->cells[1] = irq_type; 202 203 return ((struct intr_map_data *)sc->parent_map_data); 204 } 205 206 static int 207 mv_cp110_icu_detach(device_t dev) 208 { 209 210 return (EBUSY); 211 } 212 213 static int 214 mv_cp110_icu_activate_intr(device_t dev, struct intr_irqsrc *isrc, 215 struct resource *res, struct intr_map_data *data) 216 { 217 struct mv_cp110_icu_softc *sc; 218 219 sc = device_get_softc(dev); 220 data = mv_cp110_icu_convert_map_data(sc, data); 221 if (data == NULL) 222 return (EINVAL); 223 return (PIC_ACTIVATE_INTR(sc->parent, isrc, res, data)); 224 } 225 226 static void 227 mv_cp110_icu_enable_intr(device_t dev, struct intr_irqsrc *isrc) 228 { 229 struct mv_cp110_icu_softc *sc; 230 sc = device_get_softc(dev); 231 232 PIC_ENABLE_INTR(sc->parent, isrc); 233 } 234 235 static void 236 mv_cp110_icu_disable_intr(device_t dev, struct intr_irqsrc *isrc) 237 { 238 struct mv_cp110_icu_softc *sc; 239 240 sc = device_get_softc(dev); 241 242 PIC_DISABLE_INTR(sc->parent, isrc); 243 } 244 245 static void 246 mv_cp110_icu_init(struct mv_cp110_icu_softc *sc, uint64_t addr) 247 { 248 249 if (sc->initialized) 250 return; 251 252 switch (sc->type) { 253 case ICU_TYPE_NSR: 254 WR4(sc, ICU_SETSPI_NSR_AL, addr & UINT32_MAX); 255 WR4(sc, ICU_SETSPI_NSR_AH, (addr >> 32) & UINT32_MAX); 256 addr += MV_CP110_ICU_CLRSPI_OFFSET; 257 WR4(sc, ICU_CLRSPI_NSR_AL, addr & UINT32_MAX); 258 WR4(sc, ICU_CLRSPI_NSR_AH, (addr >> 32) & UINT32_MAX); 259 break; 260 case ICU_TYPE_SEI: 261 WR4(sc, ICU_SETSPI_SEI_AL, addr & UINT32_MAX); 262 WR4(sc, ICU_SETSPI_SEI_AH, (addr >> 32) & UINT32_MAX); 263 break; 264 default: 265 panic("Unkown ICU type."); 266 } 267 268 sc->initialized = true; 269 } 270 271 static int 272 mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data, 273 struct intr_irqsrc **isrcp) 274 { 275 struct mv_cp110_icu_softc *sc; 276 struct intr_map_data_fdt *daf; 277 uint32_t vector, irq_no, irq_type; 278 uint64_t addr; 279 int ret; 280 281 sc = device_get_softc(dev); 282 283 if (data->type != INTR_MAP_DATA_FDT) 284 return (ENOTSUP); 285 286 /* Parse original */ 287 daf = (struct intr_map_data_fdt *)data; 288 if (daf->ncells != 2) 289 return (EINVAL); 290 291 irq_no = daf->cells[0]; 292 if (irq_no >= MV_CP110_ICU_MAX_NIRQS) 293 return (EINVAL); 294 295 irq_type = daf->cells[1]; 296 if (irq_type != IRQ_TYPE_LEVEL_HIGH && 297 irq_type != IRQ_TYPE_EDGE_RISING) 298 return (EINVAL); 299 300 /* 301 * Allocate MSI vector. 302 * We don't use intr_alloc_msi wrapper, since it registers a new irq 303 * in the kernel. In our case irq was already added by the ofw code. 304 */ 305 ret = MSI_ALLOC_MSI(sc->parent, dev, 1, 1, NULL, isrcp); 306 if (ret != 0) 307 return (ret); 308 309 ret = MSI_MAP_MSI(sc->parent, dev, *isrcp, &addr, &vector); 310 if (ret != 0) 311 goto fail; 312 313 mv_cp110_icu_init(sc, addr); 314 vector |= ICU_INT_ENABLE; 315 316 if (sc->type == ICU_TYPE_NSR) 317 vector |= ICU_GRP_NSR << ICU_INT_GROUP_SHIFT; 318 else 319 vector |= ICU_GRP_SEI << ICU_INT_GROUP_SHIFT; 320 321 if (irq_type & IRQ_TYPE_EDGE_BOTH) 322 vector |= ICU_INT_EDGE; 323 324 WR4(sc, ICU_INT_CFG(irq_no), vector); 325 326 /* 327 * SATA controller has two ports, each gets its own interrupt. 328 * The problem is that only one irq is described in dts. 329 * Also ahci_generic driver supports only one irq per controller. 330 * As a workaround map both interrupts when one of them is allocated. 331 * This allows us to use both SATA ports. 332 */ 333 if (irq_no == ICU_INT_SATA0) 334 WR4(sc, ICU_INT_CFG(ICU_INT_SATA1), vector); 335 if (irq_no == ICU_INT_SATA1) 336 WR4(sc, ICU_INT_CFG(ICU_INT_SATA0), vector); 337 338 (*isrcp)->isrc_dev = sc->dev; 339 return (ret); 340 341 fail: 342 if (*isrcp != NULL) 343 MSI_RELEASE_MSI(sc->parent, dev, 1, isrcp); 344 345 return (ret); 346 } 347 348 static int 349 mv_cp110_icu_deactivate_intr(device_t dev, struct intr_irqsrc *isrc, 350 struct resource *res, struct intr_map_data *data) 351 { 352 struct mv_cp110_icu_softc *sc; 353 struct intr_map_data_fdt *daf; 354 int irq_no, ret; 355 356 if (data->type != INTR_MAP_DATA_FDT) 357 return (ENOTSUP); 358 359 sc = device_get_softc(dev); 360 daf = (struct intr_map_data_fdt *)data; 361 if (daf->ncells != 2) 362 return (EINVAL); 363 364 irq_no = daf->cells[0]; 365 data = mv_cp110_icu_convert_map_data(sc, data); 366 if (data == NULL) 367 return (EINVAL); 368 369 /* Clear the mapping. */ 370 WR4(sc, ICU_INT_CFG(irq_no), 0); 371 372 ret = PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data); 373 if (ret != 0) 374 return (ret); 375 376 return (MSI_RELEASE_MSI(sc->parent, dev, 1, &isrc)); 377 } 378 379 static int 380 mv_cp110_icu_setup_intr(device_t dev, struct intr_irqsrc *isrc, 381 struct resource *res, struct intr_map_data *data) 382 { 383 struct mv_cp110_icu_softc *sc; 384 385 sc = device_get_softc(dev); 386 data = mv_cp110_icu_convert_map_data(sc, data); 387 if (data == NULL) 388 return (EINVAL); 389 390 return (PIC_SETUP_INTR(sc->parent, isrc, res, data)); 391 } 392 393 static int 394 mv_cp110_icu_teardown_intr(device_t dev, struct intr_irqsrc *isrc, 395 struct resource *res, struct intr_map_data *data) 396 { 397 struct mv_cp110_icu_softc *sc; 398 399 sc = device_get_softc(dev); 400 data = mv_cp110_icu_convert_map_data(sc, data); 401 if (data == NULL) 402 return (EINVAL); 403 404 return (PIC_TEARDOWN_INTR(sc->parent, isrc, res, data)); 405 } 406 407 static void 408 mv_cp110_icu_pre_ithread(device_t dev, struct intr_irqsrc *isrc) 409 { 410 struct mv_cp110_icu_softc *sc; 411 412 sc = device_get_softc(dev); 413 414 PIC_PRE_ITHREAD(sc->parent, isrc); 415 } 416 417 static void 418 mv_cp110_icu_post_ithread(device_t dev, struct intr_irqsrc *isrc) 419 { 420 struct mv_cp110_icu_softc *sc; 421 422 sc = device_get_softc(dev); 423 424 PIC_POST_ITHREAD(sc->parent, isrc); 425 } 426 427 static void 428 mv_cp110_icu_post_filter(device_t dev, struct intr_irqsrc *isrc) 429 { 430 struct mv_cp110_icu_softc *sc; 431 432 sc = device_get_softc(dev); 433 434 PIC_POST_FILTER(sc->parent, isrc); 435 } 436 437 static device_method_t mv_cp110_icu_methods[] = { 438 /* Device interface */ 439 DEVMETHOD(device_probe, mv_cp110_icu_probe), 440 DEVMETHOD(device_attach, mv_cp110_icu_attach), 441 DEVMETHOD(device_detach, mv_cp110_icu_detach), 442 443 /* Interrupt controller interface */ 444 DEVMETHOD(pic_activate_intr, mv_cp110_icu_activate_intr), 445 DEVMETHOD(pic_disable_intr, mv_cp110_icu_disable_intr), 446 DEVMETHOD(pic_enable_intr, mv_cp110_icu_enable_intr), 447 DEVMETHOD(pic_map_intr, mv_cp110_icu_map_intr), 448 DEVMETHOD(pic_deactivate_intr, mv_cp110_icu_deactivate_intr), 449 DEVMETHOD(pic_setup_intr, mv_cp110_icu_setup_intr), 450 DEVMETHOD(pic_teardown_intr, mv_cp110_icu_teardown_intr), 451 DEVMETHOD(pic_post_filter, mv_cp110_icu_post_filter), 452 DEVMETHOD(pic_post_ithread, mv_cp110_icu_post_ithread), 453 DEVMETHOD(pic_pre_ithread, mv_cp110_icu_pre_ithread), 454 455 DEVMETHOD_END 456 }; 457 458 static devclass_t mv_cp110_icu_devclass; 459 460 static driver_t mv_cp110_icu_driver = { 461 "mv_cp110_icu", 462 mv_cp110_icu_methods, 463 sizeof(struct mv_cp110_icu_softc), 464 }; 465 466 EARLY_DRIVER_MODULE(mv_cp110_icu, mv_cp110_icu_bus, mv_cp110_icu_driver, 467 mv_cp110_icu_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST); 468