1 /* $NetBSD: pciex.c,v 1.4 2026/09/28 20:41:01 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.4 2026/09/28 20:41:01 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 * Errata PCIE_8: "Disable Slave MERR Assertion" 81 */ 82 #define PEGPL_CFG_DMER 0x02000000 /* bit 6: disable slave MERR */ 83 84 /* PECFG inbound-mapping registers, accessed via the port's XCFG window */ 85 #define PECFG_BAR0LMPA 0x210 /* Inbound BAR0 mask/enable, low */ 86 #define PECFG_BAR0HMPA 0x214 /* Inbound BAR0 mask/enable, high */ 87 #define PECFG_PIMEN 0x33c /* PIM enable */ 88 #define PECFG_PIM1LAL 0x348 /* PIM1 local (PLB) address low */ 89 #define PECFG_PIM1LAH 0x34c /* PIM1 local (PLB) address high */ 90 #define PECFG_POM0LAL 0x380 /* PCI-side base of the OMR1 window, low */ 91 #define PECFG_POM0LAH 0x384 /* PCI-side base of the OMR1 window, high */ 92 #define PECFG_POM1LAL 0x388 /* PCI-side base of the OMR2 window, low */ 93 #define PECFG_POM1LAH 0x38c /* PCI-side base of the OMR2 window, high */ 94 95 struct pciex_softc { 96 struct genppc_pci_chipset sc_pc; /* must be first */ 97 device_t sc_dev; 98 int sc_port; 99 bus_space_handle_t sc_cfgh; 100 }; 101 102 static int pciex_match(device_t, cfdata_t, void *); 103 static void pciex_attach(device_t, device_t, void *); 104 static int pciex_print(void *, const char *); 105 106 CFATTACH_DECL_NEW(pciex, sizeof(struct pciex_softc), 107 pciex_match, pciex_attach, NULL, NULL); 108 109 static pcireg_t pciex_conf_read(void *, pcitag_t, int); 110 static void pciex_conf_write(void *, pcitag_t, int, pcireg_t); 111 static void pciex_attach_hook(device_t, device_t, 112 struct pcibus_attach_args *); 113 static int pciex_conf_hook(void *, int, int, int, pcireg_t); 114 static int pciex_intr_map(const struct pci_attach_args *, 115 pci_intr_handle_t *); 116 static void pciex_conf_interrupt(void *, int, int, int, int, int *); 117 118 /* ECAM config windows */ 119 static struct powerpc_bus_space pciex_cfg_tag[PCIEX_NPORTS] = { 120 { 121 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 122 0x00000000, 123 AMCC460EX_PCIE0_CFG_PLBA, 124 AMCC460EX_PCIE0_CFG_PLBA + AMCC460EX_PCIE_CFG_SIZE, 125 }, 126 { 127 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 128 0x00000000, 129 AMCC460EX_PCIE1_CFG_PLBA, 130 AMCC460EX_PCIE1_CFG_PLBA + AMCC460EX_PCIE_CFG_SIZE, 131 }, 132 }; 133 134 static struct powerpc_bus_space pciex_mem_tag[PCIEX_NPORTS] = { 135 { 136 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 137 AMCC460EX_PCIE0_MEM_PLBA - AMCC460EX_PCIE_MEM_BASE, 138 AMCC460EX_PCIE_MEM_BASE, 139 AMCC460EX_PCIE_MEM_BASE + AMCC460EX_PCIE_MEM_SIZE, 140 }, 141 { 142 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 143 AMCC460EX_PCIE1_MEM_PLBA - AMCC460EX_PCIE_MEM_BASE, 144 AMCC460EX_PCIE_MEM_BASE, 145 AMCC460EX_PCIE_MEM_BASE + AMCC460EX_PCIE_MEM_SIZE, 146 }, 147 }; 148 149 /* Local-config (XCFG) windows: cfg-region-base + 0x10000000 per port */ 150 static struct powerpc_bus_space pciex_xcfg_tag[PCIEX_NPORTS] = { 151 { 152 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 153 0x00000000, 154 AMCC460EX_PCIE0_XCFG_PLBA, 155 AMCC460EX_PCIE0_XCFG_PLBA + 0x1000, 156 }, 157 { 158 _BUS_SPACE_LITTLE_ENDIAN | _BUS_SPACE_MEM_TYPE, 159 0x00000000, 160 AMCC460EX_PCIE1_XCFG_PLBA, 161 AMCC460EX_PCIE1_XCFG_PLBA + 0x1000, 162 }, 163 }; 164 165 static const struct genppc_pci_chipset pciex_chipset_template = { 166 .pc_conf_v = NULL, /* set to softc */ 167 .pc_attach_hook = pciex_attach_hook, 168 .pc_bus_maxdevs = ibm4xx_pci_bus_maxdevs, 169 .pc_make_tag = ibm4xx_pci_make_tag, 170 .pc_conf_read = pciex_conf_read, 171 .pc_conf_write = pciex_conf_write, 172 173 .pc_intr_v = NULL, /* set to softc */ 174 .pc_intr_map = pciex_intr_map, 175 .pc_intr_string = genppc_pci_intr_string, 176 .pc_intr_evcnt = genppc_pci_intr_evcnt, 177 .pc_intr_establish = genppc_pci_intr_establish, 178 .pc_intr_disestablish = genppc_pci_intr_disestablish, 179 .pc_intr_setattr = ibm4xx_pci_intr_setattr, 180 .pc_intr_type = genppc_pci_intr_type, 181 .pc_intr_alloc = genppc_pci_intr_alloc, 182 .pc_intr_release = genppc_pci_intr_release, 183 .pc_intx_alloc = genppc_pci_intx_alloc, 184 185 .pc_msi_v = NULL, /* set to softc */ 186 GENPPC_PCI_MSI_INITIALIZER, 187 188 .pc_msix_v = NULL, /* set to softc */ 189 GENPPC_PCI_MSIX_INITIALIZER, 190 191 .pc_conf_interrupt = pciex_conf_interrupt, 192 .pc_decompose_tag = ibm4xx_pci_decompose_tag, 193 .pc_conf_hook = pciex_conf_hook, 194 }; 195 196 static void 197 pciex_attach_hook(device_t parent, device_t self, 198 struct pcibus_attach_args *pba) 199 { 200 } 201 202 static int 203 pciex_conf_hook(void *v, int bus, int dev, int func, pcireg_t id) 204 { 205 206 return PCI_CONF_DEFAULT; 207 } 208 209 /* 210 * INTA-INTD are wired to consecutive UIC3 inputs per port. 211 * At least on 460EX, this may or may not be true for other SoCs... 212 */ 213 static int 214 pciex_intr_map(const struct pci_attach_args *pa, pci_intr_handle_t *ihp) 215 { 216 struct pciex_softc *sc = pa->pa_pc->pc_intr_v; 217 218 if (pa->pa_intrpin == 0 || pa->pa_intrpin > 4) 219 return 1; 220 *ihp = pciex_inta_irq(sc->sc_port) + pa->pa_intrpin - 1; 221 return 0; 222 } 223 224 static void 225 pciex_conf_interrupt(void *v, int bus, int dev, int pin, int swiz, 226 int *iline) 227 { 228 struct pciex_softc *sc = v; 229 230 *iline = pciex_inta_irq(sc->sc_port) + (pin - 1 + swiz + dev) % 4; 231 } 232 233 static bool 234 pciex_conf_ok(void *v, pcitag_t tag) 235 { 236 int bus, dev; 237 238 ibm4xx_pci_decompose_tag(v, tag, &bus, &dev, NULL); 239 240 if (bus <= 1 && dev != 0) 241 return false; 242 return bus < AMCC460EX_PCIE_CFG_SIZE / (1024 * 1024); 243 } 244 245 /* 246 * Errata PCIE_8 workaround. 247 */ 248 static void 249 pciex_set_dmer(int port, bool on) 250 { 251 uint32_t cfg; 252 253 if (port == 0) { 254 cfg = mfdcr(AMCC460EX_PCIE0_DCR_BASE + PEGPL_CFG); 255 cfg = on ? (cfg | PEGPL_CFG_DMER) : (cfg & ~PEGPL_CFG_DMER); 256 mtdcr(AMCC460EX_PCIE0_DCR_BASE + PEGPL_CFG, cfg); 257 } else { 258 cfg = mfdcr(AMCC460EX_PCIE1_DCR_BASE + PEGPL_CFG); 259 cfg = on ? (cfg | PEGPL_CFG_DMER) : (cfg & ~PEGPL_CFG_DMER); 260 mtdcr(AMCC460EX_PCIE1_DCR_BASE + PEGPL_CFG, cfg); 261 } 262 } 263 264 /* 265 * Config access: ECAM offset is the standard tag (bus<<16|dev<<11| 266 * func<<8) shifted left by 4. 267 */ 268 static pcireg_t 269 pciex_conf_read(void *v, pcitag_t tag, int reg) 270 { 271 struct pciex_softc *sc = v; 272 struct faultbuf env; 273 pcireg_t data; 274 int s; 275 276 if ((unsigned int)reg >= PCI_CONF_SIZE) 277 return (pcireg_t) -1; 278 if (!pciex_conf_ok(v, tag)) 279 return (pcireg_t) -1; 280 281 s = splhigh(); 282 pciex_set_dmer(sc->sc_port, true); 283 if (setfault(&env)) { 284 curpcb->pcb_onfault = NULL; 285 pciex_set_dmer(sc->sc_port, false); 286 splx(s); 287 return (pcireg_t) -1; 288 } 289 data = bus_space_read_4(&pciex_cfg_tag[sc->sc_port], sc->sc_cfgh, 290 (tag << 4) | reg); 291 curpcb->pcb_onfault = NULL; 292 pciex_set_dmer(sc->sc_port, false); 293 splx(s); 294 295 /* 296 * Errata PCIE_8 again... 297 */ 298 if (reg == PCI_ID_REG && data == 0) 299 return (pcireg_t) -1; 300 return data; 301 } 302 303 static void 304 pciex_conf_write(void *v, pcitag_t tag, int reg, pcireg_t data) 305 { 306 struct pciex_softc *sc = v; 307 struct faultbuf env; 308 int s; 309 310 if ((unsigned int)reg >= PCI_CONF_SIZE) 311 return; 312 if (!pciex_conf_ok(v, tag)) 313 return; 314 315 s = splhigh(); 316 pciex_set_dmer(sc->sc_port, true); 317 if (setfault(&env)) { 318 curpcb->pcb_onfault = NULL; 319 pciex_set_dmer(sc->sc_port, false); 320 splx(s); 321 return; 322 } 323 bus_space_write_4(&pciex_cfg_tag[sc->sc_port], sc->sc_cfgh, 324 (tag << 4) | reg, data); 325 curpcb->pcb_onfault = NULL; 326 pciex_set_dmer(sc->sc_port, false); 327 splx(s); 328 } 329 330 /* 331 * Program the PEGPL config and outbound memory windows. 332 * mtdcr() needs compile-time constant DCR numbers. 333 */ 334 #define PCIEX_PROGRAM_PORT(base, cfg_plba, mem_plba) \ 335 do { \ 336 mtdcr((base) + PEGPL_CFGMSK, 0); \ 337 mtdcr((base) + PEGPL_CFGBAH, AMCC460EX_PCIE_CFG_PA_HIGH); \ 338 mtdcr((base) + PEGPL_CFGBAL, (cfg_plba)); \ 339 mtdcr((base) + PEGPL_CFGMSK, \ 340 ~(AMCC460EX_PCIE_CFG_REGION_SIZE - 1) | 1); \ 341 mtdcr((base) + PEGPL_OMR1BAH, AMCC460EX_PCIE_MEM_PA_HIGH); \ 342 mtdcr((base) + PEGPL_OMR1BAL, (mem_plba)); \ 343 mtdcr((base) + PEGPL_OMR1MSKH, 0x7fffffff); \ 344 mtdcr((base) + PEGPL_OMR1MSKL, \ 345 ~(AMCC460EX_PCIE_MEM_SIZE - 1) | 3); \ 346 /* PCIE_4 erratum: clear PLE so inbound reads don't hang. */ \ 347 mtdcr((base) + PEGPL_CFG, \ 348 mfdcr((base) + PEGPL_CFG) & ~PEGPL_CFG_PLE); \ 349 } while (0) 350 351 static void 352 pciex_setup_windows(int port) 353 { 354 355 if (port == 0) 356 PCIEX_PROGRAM_PORT(AMCC460EX_PCIE0_DCR_BASE, 357 AMCC460EX_PCIE0_CFG_PLBA, AMCC460EX_PCIE0_MEM_PLBA); 358 else 359 PCIEX_PROGRAM_PORT(AMCC460EX_PCIE1_DCR_BASE, 360 AMCC460EX_PCIE1_CFG_PLBA, AMCC460EX_PCIE1_MEM_PLBA); 361 } 362 363 static int 364 pciex_match(device_t parent, cfdata_t cf, void *aux) 365 { 366 struct plb_attach_args *paa = aux; 367 368 if (strcmp(paa->plb_name, cf->cf_name) != 0) 369 return 0; 370 if (cf->cf_unit >= PCIEX_NPORTS) 371 return 0; 372 /* SERDES lane shared with SATA. */ 373 if (cf->cf_unit == 0 && !AMCC460EX_SERDES0_IS_PCIE()) 374 return 0; 375 376 return 1; 377 } 378 379 static void 380 pciex_attach(device_t parent, device_t self, void *aux) 381 { 382 struct pciex_softc *sc = device_private(self); 383 struct plb_attach_args *paa = aux; 384 struct pcibus_attach_args pba; 385 pci_chipset_tag_t pc; 386 387 sc->sc_dev = self; 388 sc->sc_port = device_unit(self); 389 sc->sc_pc = pciex_chipset_template; 390 sc->sc_pc.pc_conf_v = sc; 391 sc->sc_pc.pc_intr_v = sc; 392 sc->sc_pc.pc_msi_v = sc; 393 sc->sc_pc.pc_msix_v = sc; 394 pc = &sc->sc_pc; 395 396 aprint_normal(": PLB-PCIE root complex, port %d\n", sc->sc_port); 397 398 /* 399 * Touch the port only if firmware trained the link 400 */ 401 if ((mfsdr(sc->sc_port ? AMCC460EX_PESDR1_LOOP : 402 AMCC460EX_PESDR0_LOOP) & AMCC460EX_PESDR_LOOP_LNKUP) == 0) { 403 aprint_normal_dev(self, 404 "link is not up, port not configured by firmware\n"); 405 return; 406 } 407 408 if (bus_space_init(&pciex_cfg_tag[sc->sc_port], "pciexcfg", NULL, 0) || 409 bus_space_map(&pciex_cfg_tag[sc->sc_port], 410 pciex_cfg_tag[sc->sc_port].pbs_base, AMCC460EX_PCIE_CFG_SIZE, 411 0, &sc->sc_cfgh)) { 412 aprint_error_dev(self, "can't map config window\n"); 413 return; 414 } 415 if (bus_space_init(&pciex_mem_tag[sc->sc_port], "pciexmem", NULL, 0)) { 416 aprint_error_dev(self, "can't init MEM tag\n"); 417 return; 418 } 419 420 pciex_setup_windows(sc->sc_port); 421 422 #ifdef PCI_NETBSD_CONFIGURE 423 struct pciconf_resources *pcires = pciconf_resource_init(); 424 425 pciconf_resource_add(pcires, PCICONF_RESOURCE_MEM, 426 AMCC460EX_PCIE_MEM_BASE, AMCC460EX_PCIE_MEM_SIZE); 427 428 pci_configure_bus(pc, pcires, 0, 32); 429 pciconf_resource_fini(pcires); 430 #endif 431 432 /* 433 * Config of the port: PCI base of the window and root-complex inbound 434 * window whose PIM1 half goes to DRAM. 435 */ 436 { 437 struct powerpc_bus_space *xt = &pciex_xcfg_tag[sc->sc_port]; 438 bus_space_handle_t xh; 439 440 if (bus_space_init(xt, "pciexxcfg", NULL, 0) == 0 && 441 bus_space_map(xt, xt->pbs_base, 0x1000, 0, &xh) == 0) { 442 aprint_verbose_dev(self, "firmware POM0 0x%x_%08x " 443 "POM1 0x%x_%08x PIMEN 0x%x PIM1 0x%x_%08x " 444 "BAR0 0x%08x_%08x\n", 445 bus_space_read_4(xt, xh, PECFG_POM0LAH), 446 bus_space_read_4(xt, xh, PECFG_POM0LAL), 447 bus_space_read_4(xt, xh, PECFG_POM1LAH), 448 bus_space_read_4(xt, xh, PECFG_POM1LAL), 449 bus_space_read_4(xt, xh, PECFG_PIMEN), 450 bus_space_read_4(xt, xh, PECFG_PIM1LAH), 451 bus_space_read_4(xt, xh, PECFG_PIM1LAL), 452 bus_space_read_4(xt, xh, PECFG_BAR0HMPA), 453 bus_space_read_4(xt, xh, PECFG_BAR0LMPA)); 454 bus_space_write_4(xt, xh, PECFG_POM0LAH, 0); 455 bus_space_write_4(xt, xh, PECFG_POM0LAL, 456 AMCC460EX_PCIE_MEM_BASE); 457 bus_space_write_4(xt, xh, PECFG_PIMEN, 0); 458 bus_space_write_4(xt, xh, PECFG_PIM1LAH, 0); 459 bus_space_write_4(xt, xh, PECFG_PIM1LAL, 0); 460 bus_space_write_4(xt, xh, PECFG_PIMEN, 1); 461 bus_space_unmap(xt, xh, 0x1000); 462 } else { 463 aprint_error_dev(self, 464 "can't map XCFG to fix inbound window\n"); 465 } 466 } 467 468 pba.pba_iot = NULL; 469 pba.pba_memt = &pciex_mem_tag[sc->sc_port]; 470 pba.pba_dmat = paa->plb_dmat; 471 pba.pba_dmat64 = NULL; 472 pba.pba_pc = pc; 473 pba.pba_bus = 0; 474 pba.pba_bridgetag = NULL; 475 pba.pba_flags = PCI_FLAGS_MEM_OKAY; 476 config_found(self, &pba, pciex_print, CFARGS_NONE); 477 } 478 479 static int 480 pciex_print(void *aux, const char *p) 481 { 482 483 if (p == NULL) 484 return UNCONF; 485 return QUIET; 486 } 487