Home | History | Annotate | Line # | Download | only in pci
      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