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