1 /* $NetBSD: pciex.c,v 1.3 2026/06/22 12:34:20 rkujawa Exp $ */ 2 3 /* 4 * Copyright (c) 2012, 2014, 2024, 2026 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Radoslaw Kujawa. 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 AUTHOR ``AS IS'' AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /* 32 * IBM PLB-PCIE root complex 33 * as found on the 440SPe/460EX family of SoCs 34 */ 35 36 #include <sys/cdefs.h> 37 __KERNEL_RCSID(0, "$NetBSD: pciex.c,v 1.3 2026/06/22 12:34:20 rkujawa Exp $"); 38 39 #ifdef _KERNEL_OPT 40 #include "opt_pci.h" 41 #endif 42 43 #include <sys/param.h> 44 #include <sys/systm.h> 45 #include <sys/device.h> 46 #include <sys/extent.h> 47 #include <sys/bus.h> 48 49 #include <dev/pci/pcivar.h> 50 #include <dev/pci/pcireg.h> 51 #include <dev/pci/pciconf.h> 52 53 #include <powerpc/pcb.h> 54 55 #include <powerpc/ibm4xx/amcc460ex.h> 56 #include <powerpc/ibm4xx/cpu.h> 57 #include <powerpc/ibm4xx/spr.h> 58 #include <powerpc/ibm4xx/dev/plbvar.h> 59 #include <powerpc/ibm4xx/pci_machdep.h> 60 #include <powerpc/pci_machdep.h> 61 62 #define PCIEX_NPORTS 2 63 64 /* PEGPL DCR offsets */ 65 #define PEGPL_CFGBAH 0x00 66 #define PEGPL_CFGBAL 0x01 67 #define PEGPL_CFGMSK 0x02 68 #define PEGPL_OMR1BAH 0x06 69 #define PEGPL_OMR1BAL 0x07 70 #define PEGPL_OMR1MSKH 0x08 71 #define PEGPL_OMR1MSKL 0x09 72 #define PEGPL_CFG 0x16 /* GPL configuration register */ 73 74 /* 75 * PEGPLn_CFG bits 76 * inbound-read PLB pipeline MUST be cleared for inbound DMA to work. 77 */ 78 #define PEGPL_CFG_PLE 0x20000000 /* bit 2: inbound read pipeline enable */ 79 80 /* PECFG inbound-mapping registers, accessed via the port's XCFG window */ 81 #define PECFG_PIMEN 0x33c /* PIM enable */ 82 #define PECFG_PIM1LAL 0x348 /* PIM1 local (PLB) address low */ 83 #define PECFG_PIM1LAH 0x34c /* PIM1 local (PLB) address high */ 84 85 struct pciex_softc { 86 struct genppc_pci_chipset sc_pc; /* must be first */ 87 device_t sc_dev; 88 int sc_port; 89 bus_space_handle_t sc_cfgh; 90 }; 91 92 static int pciex_match(device_t, cfdata_t, void *); 93 static void pciex_attach(device_t, device_t, void *); 94 static int pciex_print(void *, const char *); 95 96 CFATTACH_DECL_NEW(pciex, sizeof(struct pciex_softc), 97 pciex_match, pciex_attach, NULL, NULL); 98 99 static pcireg_t pciex_conf_read(void *, pcitag_t, int); 100 static void pciex_conf_write(void *, pcitag_t, int, pcireg_t); 101 static void pciex_attach_hook(device_t, device_t, 102 struct pcibus_attach_args *); 103 static int pciex_conf_hook(void *, int, int, int, pcireg_t); 104 static int pciex_intr_map(const struct pci_attach_args *, 105 pci_intr_handle_t *); 106 static void pciex_conf_interrupt(void *, int, int, int, int, int *); 107 108 /* ECAM config windows */ 109 static struct powerpc_bus_space pciex_cfg_tag[PCIEX_NPORTS] = { 110 { 111 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 112 0x00000000, 113 AMCC460EX_PCIE0_CFG_PLBA, 114 AMCC460EX_PCIE0_CFG_PLBA + AMCC460EX_PCIE_CFG_SIZE, 115 }, 116 { 117 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 118 0x00000000, 119 AMCC460EX_PCIE1_CFG_PLBA, 120 AMCC460EX_PCIE1_CFG_PLBA + AMCC460EX_PCIE_CFG_SIZE, 121 }, 122 }; 123 124 static struct powerpc_bus_space pciex_mem_tag[PCIEX_NPORTS] = { 125 { 126 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 127 AMCC460EX_PCIE0_MEM_PLBA - AMCC460EX_PCIE_MEM_BASE, 128 AMCC460EX_PCIE_MEM_BASE, 129 AMCC460EX_PCIE_MEM_BASE + AMCC460EX_PCIE_MEM_SIZE, 130 }, 131 { 132 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 133 AMCC460EX_PCIE1_MEM_PLBA - AMCC460EX_PCIE_MEM_BASE, 134 AMCC460EX_PCIE_MEM_BASE, 135 AMCC460EX_PCIE_MEM_BASE + AMCC460EX_PCIE_MEM_SIZE, 136 }, 137 }; 138 139 /* Local-config (XCFG) windows: cfg-region-base + 0x10000000 per port */ 140 static struct powerpc_bus_space pciex_xcfg_tag[PCIEX_NPORTS] = { 141 { 142 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 143 0x00000000, 144 AMCC460EX_PCIE0_CFG_PLBA + AMCC460EX_PCIE_XCFG_OFFSET, 145 AMCC460EX_PCIE0_CFG_PLBA + AMCC460EX_PCIE_XCFG_OFFSET + 0x1000, 146 }, 147 { 148 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 149 0x00000000, 150 AMCC460EX_PCIE1_XCFG_PLBA, 151 AMCC460EX_PCIE1_XCFG_PLBA + 0x1000, 152 }, 153 }; 154 155 static const struct genppc_pci_chipset pciex_chipset_template = { 156 .pc_conf_v = NULL, /* set to softc */ 157 .pc_attach_hook = pciex_attach_hook, 158 .pc_bus_maxdevs = ibm4xx_pci_bus_maxdevs, 159 .pc_make_tag = ibm4xx_pci_make_tag, 160 .pc_conf_read = pciex_conf_read, 161 .pc_conf_write = pciex_conf_write, 162 163 .pc_intr_v = NULL, /* set to softc */ 164 .pc_intr_map = pciex_intr_map, 165 .pc_intr_string = genppc_pci_intr_string, 166 .pc_intr_evcnt = genppc_pci_intr_evcnt, 167 .pc_intr_establish = genppc_pci_intr_establish, 168 .pc_intr_disestablish = genppc_pci_intr_disestablish, 169 .pc_intr_setattr = ibm4xx_pci_intr_setattr, 170 .pc_intr_type = genppc_pci_intr_type, 171 .pc_intr_alloc = genppc_pci_intr_alloc, 172 .pc_intr_release = genppc_pci_intr_release, 173 .pc_intx_alloc = genppc_pci_intx_alloc, 174 175 .pc_msi_v = NULL, /* set to softc */ 176 GENPPC_PCI_MSI_INITIALIZER, 177 178 .pc_msix_v = NULL, /* set to softc */ 179 GENPPC_PCI_MSIX_INITIALIZER, 180 181 .pc_conf_interrupt = pciex_conf_interrupt, 182 .pc_decompose_tag = ibm4xx_pci_decompose_tag, 183 .pc_conf_hook = pciex_conf_hook, 184 }; 185 186 static void 187 pciex_attach_hook(device_t parent, device_t self, 188 struct pcibus_attach_args *pba) 189 { 190 } 191 192 static int 193 pciex_conf_hook(void *v, int bus, int dev, int func, pcireg_t id) 194 { 195 196 return PCI_CONF_DEFAULT; 197 } 198 199 /* 200 * INTA-INTD are wired to consecutive UIC3 inputs per port. 201 * At least on 460EX, this may or may not be true for other SoCs... 202 */ 203 static int 204 pciex_intr_map(const struct pci_attach_args *pa, pci_intr_handle_t *ihp) 205 { 206 struct pciex_softc *sc = pa->pa_pc->pc_intr_v; 207 208 if (pa->pa_intrpin == 0 || pa->pa_intrpin > 4) 209 return 1; 210 *ihp = pciex_inta_irq(sc->sc_port) + pa->pa_intrpin - 1; 211 return 0; 212 } 213 214 static void 215 pciex_conf_interrupt(void *v, int bus, int dev, int pin, int swiz, 216 int *iline) 217 { 218 struct pciex_softc *sc = v; 219 220 *iline = pciex_inta_irq(sc->sc_port) + (pin - 1 + swiz + dev) % 4; 221 } 222 223 static bool 224 pciex_conf_ok(void *v, pcitag_t tag) 225 { 226 int bus, dev; 227 228 ibm4xx_pci_decompose_tag(v, tag, &bus, &dev, NULL); 229 return bus <= 1 && dev == 0; 230 } 231 232 /* 233 * Config access: ECAM offset is the standard tag (bus<<16|dev<<11| 234 * func<<8) shifted left by 4. 235 */ 236 static pcireg_t 237 pciex_conf_read(void *v, pcitag_t tag, int reg) 238 { 239 struct pciex_softc *sc = v; 240 struct faultbuf env; 241 pcireg_t data; 242 243 if ((unsigned int)reg >= PCI_CONF_SIZE) 244 return (pcireg_t) -1; 245 if (!pciex_conf_ok(v, tag)) 246 return (pcireg_t) -1; 247 248 if (setfault(&env)) { 249 curpcb->pcb_onfault = NULL; 250 return (pcireg_t) -1; 251 } 252 data = bus_space_read_4(&pciex_cfg_tag[sc->sc_port], sc->sc_cfgh, 253 (tag << 4) | reg); 254 curpcb->pcb_onfault = NULL; 255 return data; 256 } 257 258 static void 259 pciex_conf_write(void *v, pcitag_t tag, int reg, pcireg_t data) 260 { 261 struct pciex_softc *sc = v; 262 struct faultbuf env; 263 264 if ((unsigned int)reg >= PCI_CONF_SIZE) 265 return; 266 if (!pciex_conf_ok(v, tag)) 267 return; 268 269 if (setfault(&env)) { 270 curpcb->pcb_onfault = NULL; 271 return; 272 } 273 bus_space_write_4(&pciex_cfg_tag[sc->sc_port], sc->sc_cfgh, 274 (tag << 4) | reg, data); 275 curpcb->pcb_onfault = NULL; 276 } 277 278 /* 279 * Program the PEGPL config and outbound memory windows. 280 * mtdcr() needs compile-time constant DCR numbers. 281 */ 282 #define PCIEX_PROGRAM_PORT(base, cfg_plba, mem_plba) \ 283 do { \ 284 mtdcr((base) + PEGPL_CFGMSK, 0); \ 285 mtdcr((base) + PEGPL_CFGBAH, AMCC460EX_PCIE_CFG_PA_HIGH); \ 286 mtdcr((base) + PEGPL_CFGBAL, (cfg_plba)); \ 287 mtdcr((base) + PEGPL_CFGMSK, \ 288 ~(AMCC460EX_PCIE_CFG_REGION_SIZE - 1) | 1); \ 289 mtdcr((base) + PEGPL_OMR1BAH, AMCC460EX_PCIE_MEM_PA_HIGH); \ 290 mtdcr((base) + PEGPL_OMR1BAL, (mem_plba)); \ 291 mtdcr((base) + PEGPL_OMR1MSKH, 0x7fffffff); \ 292 mtdcr((base) + PEGPL_OMR1MSKL, \ 293 ~(AMCC460EX_PCIE_MEM_SIZE - 1) | 3); \ 294 /* PCIE_4 erratum: clear PLE so inbound reads don't hang. */ \ 295 mtdcr((base) + PEGPL_CFG, \ 296 mfdcr((base) + PEGPL_CFG) & ~PEGPL_CFG_PLE); \ 297 } while (0) 298 299 static void 300 pciex_setup_windows(int port) 301 { 302 303 if (port == 0) 304 PCIEX_PROGRAM_PORT(AMCC460EX_PCIE0_DCR_BASE, 305 AMCC460EX_PCIE0_CFG_PLBA, AMCC460EX_PCIE0_MEM_PLBA); 306 else 307 PCIEX_PROGRAM_PORT(AMCC460EX_PCIE1_DCR_BASE, 308 AMCC460EX_PCIE1_CFG_PLBA, AMCC460EX_PCIE1_MEM_PLBA); 309 } 310 311 static int 312 pciex_match(device_t parent, cfdata_t cf, void *aux) 313 { 314 struct plb_attach_args *paa = aux; 315 316 if (strcmp(paa->plb_name, cf->cf_name) != 0) 317 return 0; 318 if (cf->cf_unit >= PCIEX_NPORTS) 319 return 0; 320 321 return 1; 322 } 323 324 static void 325 pciex_attach(device_t parent, device_t self, void *aux) 326 { 327 struct pciex_softc *sc = device_private(self); 328 struct plb_attach_args *paa = aux; 329 struct pcibus_attach_args pba; 330 pci_chipset_tag_t pc; 331 332 sc->sc_dev = self; 333 sc->sc_port = device_unit(self); 334 sc->sc_pc = pciex_chipset_template; 335 sc->sc_pc.pc_conf_v = sc; 336 sc->sc_pc.pc_intr_v = sc; 337 sc->sc_pc.pc_msi_v = sc; 338 sc->sc_pc.pc_msix_v = sc; 339 pc = &sc->sc_pc; 340 341 aprint_normal(": PLB-PCIE root complex, port %d\n", sc->sc_port); 342 343 /* 344 * Touch the port only if firmware trained the link 345 */ 346 if ((mfsdr(sc->sc_port ? AMCC460EX_PESDR1_LOOP : 347 AMCC460EX_PESDR0_LOOP) & AMCC460EX_PESDR_LOOP_LNKUP) == 0) { 348 aprint_normal_dev(self, 349 "link is not up, port not configured by firmware\n"); 350 return; 351 } 352 353 if (bus_space_init(&pciex_cfg_tag[sc->sc_port], "pciexcfg", NULL, 0) || 354 bus_space_map(&pciex_cfg_tag[sc->sc_port], 355 pciex_cfg_tag[sc->sc_port].pbs_base, AMCC460EX_PCIE_CFG_SIZE, 356 0, &sc->sc_cfgh)) { 357 aprint_error_dev(self, "can't map config window\n"); 358 return; 359 } 360 if (bus_space_init(&pciex_mem_tag[sc->sc_port], "pciexmem", NULL, 0)) { 361 aprint_error_dev(self, "can't init MEM tag\n"); 362 return; 363 } 364 365 pciex_setup_windows(sc->sc_port); 366 367 #ifdef PCI_NETBSD_CONFIGURE 368 struct pciconf_resources *pcires = pciconf_resource_init(); 369 370 pciconf_resource_add(pcires, PCICONF_RESOURCE_MEM, 371 AMCC460EX_PCIE_MEM_BASE, AMCC460EX_PCIE_MEM_SIZE); 372 373 pci_configure_bus(pc, pcires, 0, 32); 374 pciconf_resource_fini(pcires); 375 #endif 376 377 /* 378 * root-complex inbound window's PIM1 half goes to DRAM. 379 */ 380 { 381 struct powerpc_bus_space *xt = &pciex_xcfg_tag[sc->sc_port]; 382 bus_space_handle_t xh; 383 384 if (bus_space_init(xt, "pciexxcfg", NULL, 0) == 0 && 385 bus_space_map(xt, xt->pbs_base, 0x1000, 0, &xh) == 0) { 386 bus_space_write_4(xt, xh, PECFG_PIMEN, 0); 387 bus_space_write_4(xt, xh, PECFG_PIM1LAH, 0); 388 bus_space_write_4(xt, xh, PECFG_PIM1LAL, 0); 389 bus_space_write_4(xt, xh, PECFG_PIMEN, 1); 390 bus_space_unmap(xt, xh, 0x1000); 391 } else { 392 aprint_error_dev(self, 393 "can't map XCFG to fix inbound window\n"); 394 } 395 } 396 397 pba.pba_iot = NULL; 398 pba.pba_memt = &pciex_mem_tag[sc->sc_port]; 399 pba.pba_dmat = paa->plb_dmat; 400 pba.pba_dmat64 = NULL; 401 pba.pba_pc = pc; 402 pba.pba_bus = 0; 403 pba.pba_bridgetag = NULL; 404 pba.pba_flags = PCI_FLAGS_MEM_OKAY; 405 config_found(self, &pba, pciex_print, CFARGS_NONE); 406 } 407 408 static int 409 pciex_print(void *aux, const char *p) 410 { 411 412 if (p == NULL) 413 return UNCONF; 414 return QUIET; 415 } 416