/* * Low-Level PCI and SB support for BCM47xx * * Copyright 2005, Broadcom Corporation * All Rights Reserved. * * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE. * * $Id: sbpci.c,v 1.7 2005/03/07 08:35:32 kanki Exp $ */ #include #include #include #include #include #include #include #include #include #include #include /* Can free sbpci_init() memory after boot */ #ifndef linux #define __init #endif /* Emulated configuration space */ static pci_config_regs sb_config_regs[SB_MAXCORES]; /* Banned cores */ static uint16 pci_ban[32] = { 0 }; static uint pci_banned = 0; /* CardBus mode */ static bool cardbus = FALSE; /* Disable PCI host core */ static bool pci_disabled = FALSE; /* * Functions for accessing external PCI configuration space */ /* Assume one-hot slot wiring */ #define PCI_SLOT_MAX 16 static uint32 config_cmd(void *sbh, uint bus, uint dev, uint func, uint off) { uint coreidx; sbpciregs_t *regs; uint32 addr = 0; /* CardBusMode supports only one device */ if (cardbus && dev > 1) return 0; coreidx = sb_coreidx(sbh); regs = (sbpciregs_t *) sb_setcore(sbh, SB_PCI, 0); /* Type 0 transaction */ if (bus == 1) { /* Skip unwired slots */ if (dev < PCI_SLOT_MAX) { /* Slide the PCI window to the appropriate slot */ W_REG(®s->sbtopci1, SBTOPCI_CFG0 | ((1 << (dev + 16)) & SBTOPCI1_MASK)); addr = SB_PCI_CFG | ((1 << (dev + 16)) & ~SBTOPCI1_MASK) | (func << 8) | (off & ~3); } } /* Type 1 transaction */ else { W_REG(®s->sbtopci1, SBTOPCI_CFG1); addr = SB_PCI_CFG | (bus << 16) | (dev << 11) | (func << 8) | (off & ~3); } sb_setcoreidx(sbh, coreidx); return addr; } static int extpci_read_config(void *sbh, uint bus, uint dev, uint func, uint off, void *buf, int len) { uint32 addr, *reg = NULL, val; int ret = 0; if (pci_disabled || !(addr = config_cmd(sbh, bus, dev, func, off)) || !(reg = (uint32 *) REG_MAP(addr, len)) || BUSPROBE(val, reg)) val = 0xffffffff; val >>= 8 * (off & 3); if (len == 4) *((uint32 *) buf) = val; else if (len == 2) *((uint16 *) buf) = (uint16) val; else if (len == 1) *((uint8 *) buf) = (uint8) val; else ret = -1; if (reg) REG_UNMAP(reg); return ret; } static int extpci_write_config(void *sbh, uint bus, uint dev, uint func, uint off, void *buf, int len) { uint32 addr, *reg = NULL, val; int ret = 0; if (pci_disabled || !(addr = config_cmd(sbh, bus, dev, func, off)) || !(reg = (uint32 *) REG_MAP(addr, len)) || BUSPROBE(val, reg)) goto done; if (len == 4) val = *((uint32 *) buf); else if (len == 2) { val &= ~(0xffff << (8 * (off & 3))); val |= *((uint16 *) buf) << (8 * (off & 3)); } else if (len == 1) { val &= ~(0xff << (8 * (off & 3))); val |= *((uint8 *) buf) << (8 * (off & 3)); } else ret = -1; W_REG(reg, val); done: if (reg) REG_UNMAP(reg); return ret; } /* * Functions for accessing translated SB configuration space */ static int sb_read_config(void *sbh, uint bus, uint dev, uint func, uint off, void *buf, int len) { pci_config_regs *cfg; if (dev >= SB_MAXCORES || (off + len) > sizeof(pci_config_regs)) return -1; cfg = &sb_config_regs[dev]; ASSERT(ISALIGNED(off, len)); ASSERT(ISALIGNED((uintptr)buf, len)); if (len == 4) *((uint32 *) buf) = ltoh32(*((uint32 *)((ulong) cfg + off))); else if (len == 2) *((uint16 *) buf) = ltoh16(*((uint16 *)((ulong) cfg + off))); else if (len == 1) *((uint8 *) buf) = *((uint8 *)((ulong) cfg + off)); else return -1; return 0; } static int sb_write_config(void *sbh, uint bus, uint dev, uint func, uint off, void *buf, int len) { uint coreidx, n; void *regs; sbconfig_t *sb; pci_config_regs *cfg; if (dev >= SB_MAXCORES || (off + len) > sizeof(pci_config_regs)) return -1; cfg = &sb_config_regs[dev]; ASSERT(ISALIGNED(off, len)); ASSERT(ISALIGNED((uintptr)buf, len)); /* Emulate BAR sizing */ if (off >= OFFSETOF(pci_config_regs, base[0]) && off <= OFFSETOF(pci_config_regs, base[3]) && len == 4 && *((uint32 *) buf) == ~0) { coreidx = sb_coreidx(sbh); if ((regs = sb_setcoreidx(sbh, dev))) { sb = (sbconfig_t *)((ulong) regs + SBCONFIGOFF); /* Highest numbered address match register */ n = (R_REG(&sb->sbidlow) & SBIDL_AR_MASK) >> SBIDL_AR_SHIFT; if (off == OFFSETOF(pci_config_regs, base[0])) cfg->base[0] = ~(sb_size(R_REG(&sb->sbadmatch0)) - 1); else if (off == OFFSETOF(pci_config_regs, base[1]) && n >= 1) cfg->base[1] = ~(sb_size(R_REG(&sb->sbadmatch1)) - 1); else if (off == OFFSETOF(pci_config_regs, base[2]) && n >= 2) cfg->base[2] = ~(sb_size(R_REG(&sb->sbadmatch2)) - 1); else if (off == OFFSETOF(pci_config_regs, base[3]) && n >= 3) cfg->base[3] = ~(sb_size(R_REG(&sb->sbadmatch3)) - 1); } sb_setcoreidx(sbh, coreidx); return 0; } if (len == 4) *((uint32 *)((ulong) cfg + off)) = htol32(*((uint32 *) buf)); else if (len == 2) *((uint16 *)((ulong) cfg + off)) = htol16(*((uint16 *) buf)); else if (len == 1) *((uint8 *)((ulong) cfg + off)) = *((uint8 *) buf); else return -1; return 0; } int sbpci_read_config(void *sbh, uint bus, uint dev, uint func, uint off, void *buf, int len) { if (bus == 0) return sb_read_config(sbh, bus, dev, func, off, buf, len); else return extpci_read_config(sbh, bus, dev, func, off, buf, len); } int sbpci_write_config(void *sbh, uint bus, uint dev, uint func, uint off, void *buf, int len) { if (bus == 0) return sb_write_config(sbh, bus, dev, func, off, buf, len); else return extpci_write_config(sbh, bus, dev, func, off, buf, len); } void sbpci_ban(uint16 core) { if (pci_banned < ARRAYSIZE(pci_ban)) pci_ban[pci_banned++] = core; } //#define CT4712_WR 1 /* Workaround for 4712 */ static int sbpci_init_pci(void *sbh) { uint chip, chiprev, chippkg, host; uint32 boardflags; sbpciregs_t *pci; sbconfig_t *sb; // int CT4712_WR; uint32 val; chip = sb_chip(sbh); chiprev = sb_chiprev(sbh); chippkg = sb_chippkg(sbh); if (!(pci = (sbpciregs_t *) sb_setcore(sbh, SB_PCI, 0))) { printf("PCI: no core\n"); pci_disabled = TRUE; return -1; } sb_core_reset(sbh, 0); #if 0 // removed zzz /* In some board, */ if((nvram_match("boardtype", "bcm94710dev")) || (nvram_match("boardtype", "0x042f"))) //Barry add for 4704 CT4712_WR = 0; else CT4712_WR = 1; #endif // checkme: ok to remove these checks? -- zzz if ((!nvram_match("boardtype", "bcm94710dev")) && (!nvram_match("boardtype", "0x042f")) && (!nvram_match("boardtype", "bcm94710ap")) && (!nvram_match("boardtype", "bcm95365r"))) { pci_disabled = TRUE; } boardflags = (uint32) getintvar(NULL, "boardflags"); if ((chip == BCM4310_DEVICE_ID) && (chiprev == 0)) pci_disabled = TRUE; /* * The 200-pin BCM4712 package does not bond out PCI. Even when * PCI is bonded out, some boards may leave the pins * floating. */ if (((chip == BCM4712_DEVICE_ID) && ((chippkg == BCM4712SMALL_PKG_ID) || (chippkg == BCM4712MID_PKG_ID))) || (boardflags & BFL_NOPCI)) pci_disabled = TRUE; #if 0 // zzz if (((chip == BCM4712_DEVICE_ID) && ((chippkg == BCM4712SMALL_PKG_ID) || (chippkg == BCM4712MID_PKG_ID))) || (boardflags & BFL_NOPCI) || CT4712_WR) pci_disabled = TRUE; #endif /* * If the PCI core should not be touched (disabled, not bonded * out, or pins floating), do not even attempt to access core * registers. Otherwise, try to determine if it is in host * mode. */ if (pci_disabled) host = 0; else host = !BUSPROBE(val, &pci->control); if (!host) { /* Disable PCI interrupts in client mode */ sb = (sbconfig_t *)((ulong) pci + SBCONFIGOFF); W_REG(&sb->sbintvec, 0); /* Disable the PCI bridge in client mode */ sbpci_ban(SB_PCI); printf("PCI: Disabled\n"); } else { /* Reset the external PCI bus and enable the clock */ W_REG(&pci->control, 0x5); /* enable the tristate drivers */ W_REG(&pci->control, 0xd); /* enable the PCI clock */ OSL_DELAY(150); /* delay > 100 us */ W_REG(&pci->control, 0xf); /* deassert PCI reset */ W_REG(&pci->arbcontrol, PCI_INT_ARB); /* use internal arbiter */ OSL_DELAY(1); /* delay 1 us */ /* Enable CardBusMode */ cardbus = nvram_match("cardbus", "1"); if (cardbus) { printf("PCI: Enabling CardBus\n"); /* GPIO 1 resets the CardBus device on bcm94710ap */ sb_gpioout(sbh, 1, 1); sb_gpioouten(sbh, 1, 1); W_REG(&pci->sprom[0], R_REG(&pci->sprom[0]) | 0x400); } /* 64 MB I/O access window */ W_REG(&pci->sbtopci0, SBTOPCI_IO); /* 64 MB configuration access window */ W_REG(&pci->sbtopci1, SBTOPCI_CFG0); /* 1 GB memory access window */ W_REG(&pci->sbtopci2, SBTOPCI_MEM | SB_PCI_DMA); /* Enable PCI bridge BAR0 prefetch and burst */ val = 6; sbpci_write_config(sbh, 1, 0, 0, PCI_CFG_CMD, &val, sizeof(val)); /* Enable PCI interrupts */ W_REG(&pci->intmask, PCI_INTA); } return 0; } static int sbpci_init_cores(void *sbh) { uint chip, chiprev, chippkg, coreidx, i; sbconfig_t *sb; pci_config_regs *cfg; void *regs; char varname[8]; uint wlidx = 0; uint16 vendor, core; uint8 class, subclass, progif; uint32 val; uint32 sbips_int_mask[] = { 0, SBIPS_INT1_MASK, SBIPS_INT2_MASK, SBIPS_INT3_MASK, SBIPS_INT4_MASK }; uint32 sbips_int_shift[] = { 0, 0, SBIPS_INT2_SHIFT, SBIPS_INT3_SHIFT, SBIPS_INT4_SHIFT }; chip = sb_chip(sbh); chiprev = sb_chiprev(sbh); chippkg = sb_chippkg(sbh); coreidx = sb_coreidx(sbh); /* Scan the SB bus */ bzero(sb_config_regs, sizeof(sb_config_regs)); for (cfg = sb_config_regs; cfg < &sb_config_regs[SB_MAXCORES]; cfg++) { cfg->vendor = 0xffff; if (!(regs = sb_setcoreidx(sbh, cfg - sb_config_regs))) continue; sb = (sbconfig_t *)((ulong) regs + SBCONFIGOFF); /* Read ID register and parse vendor and core */ val = R_REG(&sb->sbidhigh); vendor = (val & SBIDH_VC_MASK) >> SBIDH_VC_SHIFT; core = (val & SBIDH_CC_MASK) >> SBIDH_CC_SHIFT; progif = 0; /* Check if this core is banned */ for (i = 0; i < pci_banned; i++) if (core == pci_ban[i]) break; if (i < pci_banned) continue; /* Known vendor translations */ switch (vendor) { case SB_VEND_BCM: vendor = VENDOR_BROADCOM; break; } /* Determine class based on known core codes */ switch (core) { case SB_ILINE20: class = PCI_CLASS_NET; subclass = PCI_NET_ETHER; core = BCM47XX_ILINE_ID; break; case SB_ILINE100: class = PCI_CLASS_NET; subclass = PCI_NET_ETHER; core = BCM4610_ILINE_ID; break; case SB_ENET: class = PCI_CLASS_NET; subclass = PCI_NET_ETHER; core = BCM47XX_ENET_ID; break; case SB_SDRAM: case SB_MEMC: class = PCI_CLASS_MEMORY; subclass = PCI_MEMORY_RAM; break; case SB_PCI: class = PCI_CLASS_BRIDGE; subclass = PCI_BRIDGE_PCI; break; case SB_MIPS: case SB_MIPS33: class = PCI_CLASS_CPU; subclass = PCI_CPU_MIPS; break; case SB_CODEC: class = PCI_CLASS_COMM; subclass = PCI_COMM_MODEM; core = BCM47XX_V90_ID; break; case SB_USB: class = PCI_CLASS_SERIAL; subclass = PCI_SERIAL_USB; progif = 0x10; /* OHCI */ core = BCM47XX_USB_ID; break; case SB_USB11H: class = PCI_CLASS_SERIAL; subclass = PCI_SERIAL_USB; progif = 0x10; /* OHCI */ core = BCM47XX_USBH_ID; break; case SB_USB11D: class = PCI_CLASS_SERIAL; subclass = PCI_SERIAL_USB; core = BCM47XX_USBD_ID; break; case SB_IPSEC: class = PCI_CLASS_CRYPT; subclass = PCI_CRYPT_NETWORK; core = BCM47XX_IPSEC_ID; break; case SB_ROBO: class = PCI_CLASS_NET; subclass = PCI_NET_OTHER; core = BCM47XX_ROBO_ID; break; case SB_EXTIF: case SB_CC: class = PCI_CLASS_MEMORY; subclass = PCI_MEMORY_FLASH; break; case SB_D11: class = PCI_CLASS_NET; subclass = PCI_NET_OTHER; /* Let an nvram variable override this */ sprintf(varname, "wl%did", wlidx); wlidx++; if ((core = getintvar(NULL, varname)) == 0) { if (chip == BCM4712_DEVICE_ID) { if (chippkg == BCM4712SMALL_PKG_ID) core = BCM4306_D11G_ID; else core = BCM4306_D11DUAL_ID; } else { /* 4310 */ core = BCM4310_D11B_ID; } } break; default: class = subclass = progif = 0xff; break; } /* Supported translations */ cfg->vendor = htol16(vendor); cfg->device = htol16(core); cfg->rev_id = chiprev; cfg->prog_if = progif; cfg->sub_class = subclass; cfg->base_class = class; cfg->base[0] = htol32(sb_base(R_REG(&sb->sbadmatch0))); cfg->base[1] = htol32(sb_base(R_REG(&sb->sbadmatch1))); cfg->base[2] = htol32(sb_base(R_REG(&sb->sbadmatch2))); cfg->base[3] = htol32(sb_base(R_REG(&sb->sbadmatch3))); cfg->base[4] = 0; cfg->base[5] = 0; if (class == PCI_CLASS_BRIDGE && subclass == PCI_BRIDGE_PCI) cfg->header_type = PCI_HEADER_BRIDGE; else cfg->header_type = PCI_HEADER_NORMAL; /* Save core interrupt flag */ cfg->int_pin = R_REG(&sb->sbtpsflag) & SBTPS_NUM0_MASK; /* Default to MIPS shared interrupt 0 */ cfg->int_line = 0; /* MIPS sbipsflag maps core interrupt flags to interrupts 1 through 4 */ if ((regs = sb_setcore(sbh, SB_MIPS, 0)) || (regs = sb_setcore(sbh, SB_MIPS33, 0))) { sb = (sbconfig_t *)((ulong) regs + SBCONFIGOFF); val = R_REG(&sb->sbipsflag); for (cfg->int_line = 1; cfg->int_line <= 4; cfg->int_line++) { if (((val & sbips_int_mask[cfg->int_line]) >> sbips_int_shift[cfg->int_line]) == cfg->int_pin) break; } if (cfg->int_line > 4) cfg->int_line = 0; } /* Emulated core */ *((uint32 *) &cfg->sprom_control) = 0xffffffff; } sb_setcoreidx(sbh, coreidx); return 0; } int __init sbpci_init(void *sbh) { sbpci_init_pci(sbh); sbpci_init_cores(sbh); return 0; } void sbpci_check(void *sbh) { uint coreidx; sbpciregs_t *pci; uint32 sbtopci1; uint32 buf[64], *ptr, i; ulong pa; volatile uint j; coreidx = sb_coreidx(sbh); pci = (sbpciregs_t *) sb_setcore(sbh, SB_PCI, 0); /* Clear the test array */ pa = (ulong) DMA_MAP(NULL, buf, sizeof(buf), DMA_RX, NULL); ptr = (uint32 *) OSL_UNCACHED(&buf[0]); memset(ptr, 0, sizeof(buf)); /* Point PCI window 1 to memory */ sbtopci1 = R_REG(&pci->sbtopci1); W_REG(&pci->sbtopci1, SBTOPCI_MEM | (pa & SBTOPCI1_MASK)); /* Fill the test array via PCI window 1 */ ptr = (uint32 *) REG_MAP(SB_PCI_CFG + (pa & ~SBTOPCI1_MASK), sizeof(buf)); for (i = 0; i < ARRAYSIZE(buf); i++) { for (j = 0; j < 2; j++); W_REG(&ptr[i], i); } REG_UNMAP(ptr); /* Restore PCI window 1 */ W_REG(&pci->sbtopci1, sbtopci1); /* Check the test array */ DMA_UNMAP(NULL, pa, sizeof(buf), DMA_RX, NULL); ptr = (uint32 *) OSL_UNCACHED(&buf[0]); for (i = 0; i < ARRAYSIZE(buf); i++) { if (ptr[i] != i) break; } /* Change the clock if the test fails */ if (i < ARRAYSIZE(buf)) { uint32 req, cur; cur = sb_clock(sbh); printf("PCI: Test failed at %d MHz\n", (cur + 500000) / 1000000); for (req = 104000000; req < 176000000; req += 4000000) { printf("PCI: Resetting to %d MHz\n", (req + 500000) / 1000000); /* This will only reset if the clocks are valid and have changed */ sb_mips_setclock(sbh, req, 0, 0); } /* Should not reach here */ ASSERT(0); } sb_setcoreidx(sbh, coreidx); }