/*
* Copyright (c) 2001 Wasabi Systems, Inc.
* All rights reserved.
*
* Written by Jason R. Thorpe for Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the NetBSD Project by
* Wasabi Systems, Inc.
* 4. The name of Wasabi Systems, Inc. may not be used to endorse
* or promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef PCI_DEBUG
int rmixl_pcie_debug = PCI_DEBUG;
# define DPRINTF(x) do { if (rmixl_pcie_debug) printf x ; } while (0)
#else
# define DPRINTF(x)
#endif
/*
* get PCI config space base addr from SBC PCIe CFG BAR
* initialize it if necessary
*/
bar = RMIXL_IOREG_READ(RMIXL_IO_DEV_BRIDGE + RMIXLS_SBC_PCIE_CFG_BAR);
DPRINTF(("%s: PCIE_CFG_BAR %#x\n", __func__, bar));
if ((bar & RMIXL_PCIE_CFG_BAR_ENB) == 0) {
u_long n = RMIXL_PCIE_CFG_SIZE / (1024 * 1024);
RMIXL_PCIE_BAR_INIT(CFG, bar, n, n);
}
rcp->rc_pci_cfg_pbase = (bus_addr_t)RMIXL_PCIE_CFG_BAR_TO_BA(bar);
rcp->rc_pci_cfg_size = (bus_size_t)RMIXL_PCIE_CFG_SIZE;
/*
* get PCIE Extended config space base addr from SBC PCIe ECFG BAR
* initialize it if necessary
*/
bar = RMIXL_IOREG_READ(RMIXL_IO_DEV_BRIDGE + RMIXLS_SBC_PCIE_ECFG_BAR);
DPRINTF(("%s: PCIE_ECFG_BAR %#x\n", __func__, bar));
if ((bar & RMIXL_PCIE_ECFG_BAR_ENB) == 0) {
u_long n = RMIXL_PCIE_ECFG_SIZE / (1024 * 1024);
RMIXL_PCIE_BAR_INIT(ECFG, bar, n, n);
}
rcp->rc_pci_ecfg_pbase = (bus_addr_t)RMIXL_PCIE_ECFG_BAR_TO_BA(bar);
rcp->rc_pci_ecfg_size = (bus_size_t)RMIXL_PCIE_ECFG_SIZE;
/*
* get PCI MEM space base [addr, size] from SBC PCIe MEM BAR
* initialize it if necessary
*/
bar = RMIXL_IOREG_READ(RMIXL_IO_DEV_BRIDGE + RMIXLS_SBC_PCIE_MEM_BAR);
DPRINTF(("%s: PCIE_MEM_BAR %#x\n", __func__, bar));
if ((bar & RMIXL_PCIE_MEM_BAR_ENB) == 0) {
u_long n = 256; /* 256 MB */
RMIXL_PCIE_BAR_INIT(MEM, bar, n, n);
}
rcp->rc_pci_mem_pbase = (bus_addr_t)RMIXL_PCIE_MEM_BAR_TO_BA(bar);
rcp->rc_pci_mem_size = (bus_size_t)RMIXL_PCIE_MEM_BAR_TO_SIZE(bar);
/*
* get PCI IO space base [addr, size] from SBC PCIe IO BAR
* initialize it if necessary
*/
bar = RMIXL_IOREG_READ(RMIXL_IO_DEV_BRIDGE + RMIXLS_SBC_PCIE_IO_BAR);
DPRINTF(("%s: PCIE_IO_BAR %#x\n", __func__, bar));
if ((bar & RMIXL_PCIE_IO_BAR_ENB) == 0) {
u_long n = 32; /* 32 MB */
RMIXL_PCIE_BAR_INIT(IO, bar, n, n);
}
rcp->rc_pci_io_pbase = (bus_addr_t)RMIXL_PCIE_IO_BAR_TO_BA(bar);
rcp->rc_pci_io_size = (bus_size_t)RMIXL_PCIE_IO_BAR_TO_SIZE(bar);
/*
* initialize the PCI CFG, ECFG bus space tags
*/
rmixl_pci_cfg_bus_mem_init(&rcp->rc_pci_cfg_memt, rcp);
sc->sc_pci_cfg_memt = &rcp->rc_pci_cfg_memt;
switch (MIPS_PRID_IMPL(mips_options.mips_cpu_id)) {
case MIPS_XLS104:
case MIPS_XLS108:
rmixl_pcie_lnkcfg_1xx(&sc->sc_pcie_lnktab, r);
break;
case MIPS_XLS204:
case MIPS_XLS208:
rmixl_pcie_lnkcfg_2xx(&sc->sc_pcie_lnktab, r);
break;
case MIPS_XLS404LITE:
case MIPS_XLS408LITE:
rmixl_pcie_lnkcfg_408Lite(&sc->sc_pcie_lnktab, r);
break;
case MIPS_XLS404:
case MIPS_XLS408:
case MIPS_XLS416:
case MIPS_XLS608:
case MIPS_XLS616:
/* 6xx uses same table as 4xx */
rmixl_pcie_lnkcfg_4xx(&sc->sc_pcie_lnktab, r);
break;
default:
panic("%s: unknown RMI PRID IMPL", __func__);
}
aprint_normal("%s: link config %s\n",
device_xname(sc->sc_dev), sc->sc_pcie_lnktab.str);
}
/*
* 3.9.1 PCIe Link-0 Registers Reset to Incorrect Values
* check if it allies to this CPU implementation and revision
*/
rev = MIPS_PRID_REV(cpu_id);
switch (MIPS_PRID_IMPL(cpu_id)) {
case MIPS_XLS104:
case MIPS_XLS108:
break;
case MIPS_XLS204:
case MIPS_XLS208:
/* stepping A0 is affected */
if (rev == 0)
e391 = true;
break;
case MIPS_XLS404LITE:
case MIPS_XLS408LITE:
break;
case MIPS_XLS404:
case MIPS_XLS408:
case MIPS_XLS416:
/* steppings A0 and A1 are affected */
if ((rev == 0) || (rev == 1))
e391 = true;
break;
case MIPS_XLS608:
case MIPS_XLS616:
break;
default:
panic("unknown RMI PRID IMPL");
}
/*
* for XLS we only need to check entry #0
* this may need to change for later XL family chips
*/
lanes = sc->sc_pcie_lnktab.cfg[0].lanes;
if ((e391 != false) && ((lanes == 2) || (lanes == 4))) {
/*
* attempt work around for errata 3.9.1
* "PCIe Link-0 Registers Reset to Incorrect Values"
* the registers are write-once: if the firmware already wrote,
* then our writes are ignored; hope they did it right.
*/
uint32_t queuectrl;
uint32_t bufdepth;
#ifdef DIAGNOSTIC
uint32_t r;
#endif
aprint_normal("%s: attempt work around for errata 3.9.1",
device_xname(sc->sc_dev));
if (lanes == 4) {
queuectrl = 0x00018074;
bufdepth = 0x001901D1;
} else {
queuectrl = 0x00018036;
bufdepth = 0x001900D9;
}
int
rmixl_pcie_bus_maxdevs(void *v, int busno)
{
return (32); /* XXX depends on the family of XLS SoC */
}
/*
* rmixl_tag_to_ecfg - convert cfg address (generic tag) to ecfg address
*
* 39:29 (reserved)
* 28 Swap (0=little, 1=big endian)
* 27:20 Bus number
* 19:15 Device number
* 14:12 Function number
* 11:8 Extended Register number
* 7:0 Register number
*/
static pcitag_t
rmixl_tag_to_ecfg(pcitag_t tag)
{
KASSERT((tag & __BITS(7,0)) == 0);
return (tag << 4);
}
/*
* XLS pci tag is a 40 bit address composed thusly:
* 39:25 (reserved)
* 24 Swap (0=little, 1=big endian)
* 23:16 Bus number
* 15:11 Device number
* 10:8 Function number
* 7:0 Register number
*
* Note: this is the "native" composition for addressing CFG space, but not for ECFG space.
*/
pcitag_t
rmixl_pcie_make_tag(void *v, int bus, int dev, int fun)
{
return ((bus << 16) | (dev << 11) | (fun << 8));
}
void
rmixl_pcie_decompose_tag(void *v, pcitag_t tag, int *bp, int *dp, int *fp)
{
if (bp != NULL)
*bp = (tag >> 16) & 0xff;
if (dp != NULL)
*dp = (tag >> 11) & 0x1f;
if (fp != NULL)
*fp = (tag >> 8) & 0x7;
}
void
rmixl_pcie_tag_print(const char *restrict s, void *v, pcitag_t tag, int offset,
vaddr_t va, u_long r)
{
int bus, dev, fun;
int
rmixl_pcie_intr_map(const struct pci_attach_args *pa, pci_intr_handle_t *pih)
{
int device;
u_int link;
u_int irq;
/*
* The bus is unimportant since it can change depending on the
* configuration. We are tied to device # of PCIe bridge we are
* ultimately attached to.
*/
pci_decompose_tag(pa->pa_pc, pa->pa_intrtag,
NULL, &device, NULL);
/*
* PCIe Link INT irq assignment is cpu implementation specific
*/
switch (MIPS_PRID_IMPL(mips_options.mips_cpu_id)) {
case MIPS_XLS104:
case MIPS_XLS108:
case MIPS_XLS404LITE:
case MIPS_XLS408LITE:
if (device > 1)
panic("%s: bad bus %d", __func__, device);
link = device;
irq = device + 26;
break;
case MIPS_XLS204:
case MIPS_XLS208: {
if (device > 3)
panic("%s: bad bus %d", __func__, device);
link = device;
irq = device + (device & 2 ? 21 : 26);
break;
}
case MIPS_XLS404:
case MIPS_XLS408:
case MIPS_XLS416:
case MIPS_XLS608:
case MIPS_XLS616:
if (device > 3)
panic("%s: bad bus %d", __func__, device);
link = device;
irq = device + 26;
break;
default:
panic("%s: cpu IMPL %#x not supported\n",
__func__, MIPS_PRID_IMPL(mips_options.mips_cpu_id));
}
switch (MIPS_PRID_IMPL(mips_options.mips_cpu_id)) {
case MIPS_XLS104:
case MIPS_XLS108:
case MIPS_XLS404LITE:
case MIPS_XLS408LITE:
switch (irq) {
case 26:
case 27:
name = rmixl_intr_string(RMIXL_IRT_VECTOR(irq));
break;
}
break;
case MIPS_XLS204:
case MIPS_XLS208:
switch (irq) {
case 23:
case 24:
case 26:
case 27:
name = rmixl_intr_string(RMIXL_IRT_VECTOR(irq));
break;
}
break;
case MIPS_XLS404:
case MIPS_XLS408:
case MIPS_XLS416:
case MIPS_XLS608:
case MIPS_XLS616:
switch (irq) {
case 26:
case 27:
case 28:
case 29:
name = rmixl_intr_string(RMIXL_IRT_VECTOR(irq));
break;
}
break;
default:
panic("%s: cpu IMPL %#x not supported\n",
__func__, MIPS_PRID_IMPL(mips_options.mips_cpu_id));
}
dip->func = NULL; /* mark unused, prevent further dispatch */
/*
* if no other dispatch handle is using this interrupt,
* we can disable it
*/
busy = false;
for (int i=0; i < lip->dispatch_count; i++) {
rmixl_pcie_link_dispatch_t *d = &lip->dispatch_data[i];
if (d == dip)
continue;
if (d->bitno == dip->bitno) {
busy = true;
break;
}
}
if (! busy) {
if (dip->bitno < 32) {
bit = 1 << dip->bitno;
offset = int_enb_offset[dip->link].r0;
other = int_enb_offset[dip->link].r1;
} else {
bit = 1 << (dip->bitno - 32);
offset = int_enb_offset[dip->link].r1;
other = int_enb_offset[dip->link].r0;
}
/* disable this interrupt in the PCIe bridge */
r = RMIXL_IOREG_READ(RMIXL_IO_DEV_PCIE_LE + offset);
r &= ~bit;
RMIXL_IOREG_WRITE(RMIXL_IO_DEV_PCIE_LE + offset, r);
/*
* if both ENABLE0 and ENABLE1 are 0
* disable the link interrupt
*/
if (r == 0) {
/* check the other reg */
if (RMIXL_IOREG_READ(RMIXL_IO_DEV_PCIE_LE + other) == 0) {
DPRINTF(("%s: disable link %d\n", __func__, lip->link));
/* tear down interrupt on this link */
rmixl_intr_disestablish(lip->ih);
/* commit NULL interrupt set */
sc->sc_link_intr[dip->link] = NULL;
/* schedule delayed free of the old link interrupt set */
rmixl_pcie_lip_free_callout(lip);
}
}
}
/*
* initializae our new interrupt, the last element in dispatch_data[]
*/
dip = &lip->dispatch_data[lip->dispatch_count - 1];
dip->link = link;
dip->bitno = bitno;
dip->irq = irq;
dip->func = func;
dip->arg = arg;
dip->counts = RMIXL_PCIE_EVCNT(sc, link, bitno, 0);
if (bitno < 32) {
offset = int_enb_offset[link].r0;
bit = 1 << bitno;
} else {
offset = int_enb_offset[link].r1;
bit = 1 << (bitno - 32);
}
/* commit the new link interrupt set */
sc->sc_link_intr[link] = lip;
/* enable this interrupt in the PCIe bridge */
r = RMIXL_IOREG_READ(RMIXL_IO_DEV_PCIE_LE + offset);
r |= bit;
RMIXL_IOREG_WRITE(RMIXL_IO_DEV_PCIE_LE + offset, r);
dispatch_count = 1;
size = sizeof(rmixl_pcie_link_intr_t);
if (lip_old != NULL) {
/*
* count only those dispatch elements still in use
* unused ones will be pruned during copy
* i.e. we are "lazy" there is no rmixl_pcie_lip_sub_1
*/
for (int i=0; i < lip_old->dispatch_count; i++) {
if (lip_old->dispatch_data[i].func != NULL) {
dispatch_count++;
size += sizeof(rmixl_pcie_link_intr_t);
}
}
}
/*
* allocate and initialize link intr struct
* with one or more dispatch handles
*/
lip_new = malloc(size, M_DEVBUF, M_WAITOK);
if (lip_old == NULL) {
/* initialize the link interrupt struct */
lip_new->sc = sc;
lip_new->link = link;
lip_new->ipl = ipl;
lip_new->ih = rmixl_intr_establish(irq, sc->sc_tmsk,
ipl, RMIXL_TRIG_LEVEL, RMIXL_POLR_HIGH,
rmixl_pcie_intr, lip_new, false);
if (lip_new->ih == NULL)
panic("%s: cannot establish irq %d", __func__, irq);
} else {
/*
* all intrs on a link get same ipl and sc
* first intr established sets the standard
*/
KASSERT(sc == lip_old->sc);
if (sc != lip_old->sc) {
printf("%s: sc %p mismatch\n", __func__, sc);
free(lip_new, M_DEVBUF);
return NULL;
}
KASSERT (ipl == lip_old->ipl);
if (ipl != lip_old->ipl) {
printf("%s: ipl %d mismatch\n", __func__, ipl);
free(lip_new, M_DEVBUF);
return NULL;
}
/*
* copy lip_old to lip_new, skipping unused dispatch elemets
*/
memcpy(lip_new, lip_old, sizeof(rmixl_pcie_link_intr_t));
for (int j=0, i=0; i < lip_old->dispatch_count; i++) {
if (lip_old->dispatch_data[i].func != NULL) {
memcpy(&lip_new->dispatch_data[j],
&lip_old->dispatch_data[i],
sizeof(rmixl_pcie_link_dispatch_t));
j++;
}
}
/*
* schedule delayed free of old link interrupt set
*/
rmixl_pcie_lip_free_callout(lip_old);
}
lip_new->dispatch_count = dispatch_count;
return lip_new;
}
/*
* delay free of the old link interrupt set
* to allow anyone still using it to do so safely
* XXX 2 seconds should be plenty?
*/
static void
rmixl_pcie_lip_free_callout(rmixl_pcie_link_intr_t *lip)
{
callout_init(&lip->callout, 0);
callout_reset(&lip->callout, 2 * hz, rmixl_pcie_lip_free, lip);
}
#if defined(DEBUG) || defined(DDB)
/* this function exists to facilitate call from ddb */
int
rmixl_pcie_error_check(void)
{
if (rmixl_pcie_v != 0)
return _rmixl_pcie_error_check(rmixl_pcie_v);
return -1;
}
#endif
STATIC int
_rmixl_pcie_error_check(void *v)
{
int i, offset;
pcireg_t r;
pcitag_t tag;
int err=0;
#ifdef DIAGNOSTIC
pcireg_t regs[PCIE_ECFG_ERRS_OFFTAB_NENTRIES];
#endif
tag = rmixl_pcie_make_tag(v, 0, 0, 0); /* XXX */
for (i=0; i < PCIE_ECFG_ERRS_OFFTAB_NENTRIES; i++) {
offset = pcie_ecfg_errs_tab[i].offset;
r = rmixl_pcie_conf_read(v, tag, offset);
#ifdef DIAGNOSTIC
regs[i] = r;
#endif
if (r != 0) {
pcireg_t rw1c = r & pcie_ecfg_errs_tab[i].rw1c;
if (rw1c != 0) {
/* attempt to clear the error */
rmixl_pcie_conf_write(v, tag, offset, rw1c);
};
if (offset == RMIXL_PCIE_ECFG_CESR)
err |= 1; /* correctable */
else
err |= 2; /* uncorrectable */
}
}
#ifdef DIAGNOSTIC
if (err != 0) {
for (i=0; i < PCIE_ECFG_ERRS_OFFTAB_NENTRIES; i++) {
offset = pcie_ecfg_errs_tab[i].offset;
printf("%s: %#x: %#x\n", __func__, offset, regs[i]);
}
}
#endif
return err;
}
static int
rmixl_pcie_error_intr(void *v)
{
if (_rmixl_pcie_error_check(v) < 2)
return 0; /* correctable */
/* uncorrectable */
#if DDB
Debugger();
#endif
/* XXX reset and recover? */
panic("%s\n", __func__);
}
/*
* rmixl_physaddr_init_pcie:
* called from rmixl_physaddr_init to get region addrs & sizes
* from PCIE CFG, ECFG, IO, MEM BARs
*/
void
rmixl_physaddr_init_pcie(struct extent *ext)
{
u_long base;
u_long size;
uint32_t r;