pciex.c revision 1.3 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