/* $OpenBSD: mvicu.c,v 1.4 2019/02/03 14:03:36 patrick Exp $ */ /* * Copyright (c) 2018 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include /* Registers. */ #define ICU_SETSPI_NSR_AL 0x10 #define ICU_SETSPI_NSR_AH 0x14 #define ICU_CLRSPI_NSR_AL 0x18 #define ICU_CLRSPI_NSR_AH 0x1c #define ICU_SET_SEI_AL 0x50 #define ICU_SET_SEI_AH 0x54 #define ICU_CLR_SEI_AL 0x58 #define ICU_CLR_SEI_AH 0x5c #define ICU_INT_CFG(x) (0x100 + (x) * 4) #define ICU_INT_ENABLE (1 << 24) #define ICU_INT_EDGE (1 << 28) #define ICU_INT_GROUP_SHIFT 29 #define ICU_INT_MASK 0x3ff #define GICP_SETSPI_NSR 0x00 #define GICP_CLRSPI_NSR 0x08 /* Devices */ #define ICU_DEVICE_SATA0 109 #define ICU_DEVICE_SATA1 107 #define ICU_DEVICE_NIRQ 207 /* Groups. */ #define ICU_GRP_NSR 0x0 #define ICU_GRP_SR 0x1 #define ICU_GRP_SEI 0x4 #define ICU_GRP_REI 0x5 #define HREAD4(sc, reg) \ (bus_space_read_4((sc)->sc_iot, (sc)->sc_ioh, (reg))) #define HWRITE4(sc, reg, val) \ bus_space_write_4((sc)->sc_iot, (sc)->sc_ioh, (reg), (val)) struct mvicu_softc; struct mvicu_subnode { struct mvicu_softc *sn_sc; int sn_group; struct interrupt_controller sn_ic; struct interrupt_controller *sn_parent_ic; }; struct mvicu_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; uint64_t sc_nsr_addr; uint64_t sc_sei_addr; int sc_legacy; struct mvicu_subnode *sc_nodes; }; int mvicu_match(struct device *, void *, void *); void mvicu_attach(struct device *, struct device *, void *); struct cfattach mvicu_ca = { sizeof (struct mvicu_softc), mvicu_match, mvicu_attach }; struct cfdriver mvicu_cd = { NULL, "mvicu", DV_DULL }; void mvicu_register(struct mvicu_softc *, int, int); void *mvicu_intr_establish(void *, int *, int, int (*)(void *), void *, char *); void mvicu_intr_disestablish(void *); int mvicu_match(struct device *parent, void *match, void *aux) { struct fdt_attach_args *faa = aux; return OF_is_compatible(faa->fa_node, "marvell,cp110-icu"); } void mvicu_attach(struct device *parent, struct device *self, void *aux) { struct mvicu_softc *sc = (struct mvicu_softc *)self; struct fdt_attach_args *faa = aux; int i, node, nchildren; if (faa->fa_nreg < 1) { printf(": no registers\n"); return; } sc->sc_iot = faa->fa_iot; if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size, 0, &sc->sc_ioh)) { printf(": can't map registers\n"); return; } printf("\n"); if (OF_child(faa->fa_node) == 0) { sc->sc_legacy = 1; sc->sc_nodes = mallocarray(1, sizeof(*sc->sc_nodes), M_DEVBUF, M_WAITOK | M_ZERO); mvicu_register(sc, faa->fa_node, 0); } else { for (node = OF_child(faa->fa_node), nchildren = 0; node; node = OF_peer(node)) nchildren++; sc->sc_nodes = mallocarray(nchildren, sizeof(*sc->sc_nodes), M_DEVBUF, M_WAITOK | M_ZERO); for (node = OF_child(faa->fa_node), i = 0; node; node = OF_peer(node)) mvicu_register(sc, node, i++); } } void mvicu_register(struct mvicu_softc *sc, int node, int idx) { struct mvicu_subnode *sn = &sc->sc_nodes[idx]; struct interrupt_controller *ic; uint32_t phandle = 0; uint32_t group; int i; sn->sn_group = -1; if (OF_is_compatible(node, "marvell,cp110-icu") || OF_is_compatible(node, "marvell,cp110-icu-nsr")) sn->sn_group = ICU_GRP_NSR; if (OF_is_compatible(node, "marvell,cp110-icu-sei")) sn->sn_group = ICU_GRP_SEI; for (i = 0; i < ICU_DEVICE_NIRQ; i++) { group = HREAD4(sc, ICU_INT_CFG(i)) >> ICU_INT_GROUP_SHIFT; if ((sn->sn_group == ICU_GRP_NSR && group == ICU_GRP_NSR) || (sn->sn_group == ICU_GRP_SEI && group == ICU_GRP_SEI)) HWRITE4(sc, ICU_INT_CFG(i), 0); } sn->sn_sc = sc; sn->sn_ic.ic_node = node; sn->sn_ic.ic_cookie = sn; sn->sn_ic.ic_establish = mvicu_intr_establish; sn->sn_ic.ic_disestablish = mvicu_intr_disestablish; while (node && !phandle) { phandle = OF_getpropint(node, "msi-parent", 0); node = OF_parent(node); } if (phandle == 0) return; extern LIST_HEAD(, interrupt_controller) interrupt_controllers; LIST_FOREACH(ic, &interrupt_controllers, ic_list) { if (ic->ic_phandle == phandle) break; } if (ic == NULL) return; sn->sn_parent_ic = ic; fdt_intr_register(&sn->sn_ic); } void * mvicu_intr_establish(void *cookie, int *cell, int level, int (*func)(void *), void *arg, char *name) { struct mvicu_subnode *sn = cookie; struct mvicu_softc *sc = sn->sn_sc; struct interrupt_controller *ic = sn->sn_parent_ic; uint32_t idx, flags; uint64_t addr, data; int edge = 0; if (sc->sc_legacy) { if (cell[0] != ICU_GRP_NSR) return NULL; idx = cell[1]; flags = cell[2]; edge = ((flags & 0xf) == 0x1); } else if (sn->sn_group == ICU_GRP_NSR) { idx = cell[0]; flags = cell[1]; edge = ((flags & 0xf) == 0x1); } else if (sn->sn_group == ICU_GRP_SEI) { idx = cell[0]; flags = cell[1]; edge = 1; } else { return NULL; } data = flags; cookie = ic->ic_establish_msi(ic->ic_cookie, &addr, &data, level, func, arg, name); if (cookie == NULL) return NULL; if (sn->sn_group == ICU_GRP_NSR && !sc->sc_nsr_addr) { sc->sc_nsr_addr = addr; HWRITE4(sc, ICU_SETSPI_NSR_AL, (addr + GICP_SETSPI_NSR) & 0xffffffff); HWRITE4(sc, ICU_SETSPI_NSR_AH, (addr + GICP_SETSPI_NSR) >> 32); HWRITE4(sc, ICU_CLRSPI_NSR_AL, (addr + GICP_CLRSPI_NSR) & 0xffffffff); HWRITE4(sc, ICU_CLRSPI_NSR_AH, (addr + GICP_CLRSPI_NSR) >> 32); } if (sn->sn_group == ICU_GRP_SEI && !sc->sc_sei_addr) { sc->sc_sei_addr = addr; HWRITE4(sc, ICU_SET_SEI_AL, addr & 0xffffffff); HWRITE4(sc, ICU_SET_SEI_AH, addr >> 32); } /* Configure ICU. */ HWRITE4(sc, ICU_INT_CFG(idx), data | ICU_INT_ENABLE | (sn->sn_group << ICU_INT_GROUP_SHIFT) | (edge ? ICU_INT_EDGE : 0)); /* Need to configure interrupt for both SATA ports. */ if (idx == ICU_DEVICE_SATA0 || idx == ICU_DEVICE_SATA1) { HWRITE4(sc, ICU_INT_CFG(ICU_DEVICE_SATA0), data | ICU_INT_ENABLE | (sn->sn_group << ICU_INT_GROUP_SHIFT) | (edge ? ICU_INT_EDGE : 0)); HWRITE4(sc, ICU_INT_CFG(ICU_DEVICE_SATA1), data | ICU_INT_ENABLE | (sn->sn_group << ICU_INT_GROUP_SHIFT) | (edge ? ICU_INT_EDGE : 0)); } return cookie; } void mvicu_intr_disestablish(void *cookie) { panic("%s", __func__); }