/*
* Copyright (c) 1995, 1996, 1997, 1998
* Christopher G. Demetriou. All rights reserved.
* Copyright (c) 1994 Charles M. Hannum. All rights reserved.
*
* 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 by Charles M. Hannum.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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.
*/
/*
* Important note about PCI-ISA bridges:
*
* Callbacks are used to configure these devices so that ISA/EISA bridges
* can attach their child busses after PCI configuration is done.
*
* This works because:
* (1) there can be at most one ISA/EISA bridge per PCI bus, and
* (2) any ISA/EISA bridges must be attached to primary PCI
* busses (i.e. bus zero).
*
* That boils down to: there can only be one of these outstanding
* at a time, it is cleared when configuring PCI bus 0 before any
* subdevices have been found, and it is run after all subdevices
* of PCI bus 0 have been found.
*
* This is needed because there are some (legacy) PCI devices which
* can show up as ISA/EISA devices as well (the prime example of which
* are VGA controllers). If you attach ISA from a PCI-ISA/EISA bridge,
* and the bridge is seen before the video board is, the board can show
* up as an ISA device, and that can (bogusly) complicate the PCI device's
* attach code, or make the PCI device not be properly attached at all.
*
* We use the generic config_defer() facility to achieve this.
*/
int
pcirescan(device_t self, const char *ifattr, const int *locators)
{
struct pci_softc *sc = device_private(self);
if (io_enabled == 0 && mem_enabled == 0) {
aprint_error_dev(self, "no spaces enabled!\n");
goto fail;
}
#define PRINT(str) \
do { \
aprint_verbose("%s%s", sep, str); \
sep = ", "; \
} while (/*CONSTCOND*/0)
aprint_verbose_dev(self, "");
if (io_enabled)
PRINT("i/o space");
if (mem_enabled)
PRINT("memory space");
aprint_verbose(" enabled");
if (mrl_enabled || mrm_enabled || mwi_enabled) {
if (mrl_enabled)
PRINT("rd/line");
if (mrm_enabled)
PRINT("rd/mult");
if (mwi_enabled)
PRINT("wr/inv");
aprint_verbose(" ok");
}
if (device_call(sc->sc_dev, PCI_BUS_GET_CHILD_DEVHANDLE(&args)) != 0) {
/*
* The call is either not supported or the requested
* device was not found in the platform device tree.
* Return an invalid handle.
*/
return devhandle_invalid();
}
return args.devhandle;
}
int
pci_probe_device1(struct pci_softc *sc, pcitag_t tag,
int (*match)(void *, const struct pci_attach_args *), void *cookie,
struct pci_attach_args *pap)
{
pci_chipset_tag_t pc = sc->sc_pc;
struct pci_attach_args pa;
pcireg_t id, /* csr, */ pciclass, intr, bhlcr, bar, endbar;
#ifdef __HAVE_PCI_MSI_MSIX
pcireg_t cap;
int off;
#endif
int ret, pin, bus, device, function, i, width;
int locs[PCICF_NLOCS];
/* a driver already attached? */
if (sc->PCI_SC_DEVICESC(device, function).c_dev != NULL && !match)
return 0;
id = pci_conf_read(pc, tag, PCI_ID_REG);
/* Invalid vendor ID value? */
if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
return 0;
/* XXX Not invalid, but we've done this ~forever. */
if (PCI_VENDOR(id) == 0)
return 0;
/*
* Set up memory, I/O enable, and PCI command flags
* as appropriate.
*/
pa.pa_flags = sc->sc_flags;
/*
* If the cache line size is not configured, then
* clear the MRL/MRM/MWI command-ok flags.
*/
if (PCI_CACHELINE(bhlcr) == 0) {
pa.pa_flags &= ~(PCI_FLAGS_MRL_OKAY|
PCI_FLAGS_MRM_OKAY|PCI_FLAGS_MWI_OKAY);
}
#ifdef __HAVE_PCI_MSI_MSIX
if (pci_get_ht_capability(pc, tag, PCI_HT_CAP_MSIMAP, &off, &cap)) {
/*
* XXX Should we enable MSI mapping ourselves on
* systems that have it disabled?
*/
if (cap & PCI_HT_MSI_ENABLED) {
uint64_t addr;
if ((cap & PCI_HT_MSI_FIXED) == 0) {
addr = pci_conf_read(pc, tag,
off + PCI_HT_MSI_ADDR_LO);
addr |= (uint64_t)pci_conf_read(pc, tag,
off + PCI_HT_MSI_ADDR_HI) << 32;
} else
addr = PCI_HT_MSI_FIXED_ADDR;
/*
* XXX This will fail to enable MSI on systems
* that don't use the canonical address.
*/
if (addr == PCI_HT_MSI_FIXED_ADDR) {
pa.pa_flags |= PCI_FLAGS_MSI_OKAY;
pa.pa_flags |= PCI_FLAGS_MSIX_OKAY;
} else
aprint_verbose_dev(sc->sc_dev,
"HyperTransport MSI mapping is not supported yet. Disable MSI/MSI-X.\n");
}
}
#endif
if (match != NULL) {
ret = (*match)(cookie, &pa);
if (ret != 0 && pap != NULL)
*pap = pa;
} else {
struct pci_child *c;
locs[PCICF_DEV] = device;
locs[PCICF_FUNCTION] = function;
c = &sc->PCI_SC_DEVICESC(device, function);
pci_conf_capture(pc, tag, &c->c_conf);
if (pci_get_powerstate(pc, tag, &c->c_powerstate) == 0)
c->c_psok = true;
else
c->c_psok = false;
printf("Skipping broken PCI header on %d:%d:%d\n",
bus, device, function);
break;
}
reg = pci_conf_read(pc, tag, ofs);
if (PCI_CAPLIST_CAP(reg) == capid) {
if (offset)
*offset = ofs;
if (value)
*value = reg;
return 1;
}
ofs = PCI_CAPLIST_NEXT(reg);
}
return 0;
}
int
pci_get_ht_capability(pci_chipset_tag_t pc, pcitag_t tag, int capid,
int *offset, pcireg_t *value)
{
pcireg_t reg;
unsigned int ofs;
if (pci_get_capability(pc, tag, PCI_CAP_LDT, &ofs, NULL) == 0)
return 0;
while (ofs != 0) {
#ifdef DIAGNOSTIC
if ((ofs & 3) || (ofs < 0x40))
panic("pci_get_ht_capability");
#endif
reg = pci_conf_read(pc, tag, ofs);
if (PCI_HT_CAP(reg) == capid) {
if (offset)
*offset = ofs;
if (value)
*value = reg;
return 1;
}
ofs = PCI_CAPLIST_NEXT(reg);
}
return 0;
}
/*
* return number of the devices's MSI vectors
* return 0 if the device does not support MSI
*/
int
pci_msi_count(pci_chipset_tag_t pc, pcitag_t tag)
{
pcireg_t reg;
uint32_t mmc;
int count, offset;
if (pci_get_capability(pc, tag, PCI_CAP_MSI, &offset, NULL) == 0)
return 0;
reg = pci_conf_read(pc, tag, offset + PCI_MSI_CTL);
mmc = PCI_MSI_CTL_MMC(reg);
count = 1 << mmc;
if (count > PCI_MSI_MAX_VECTORS) {
aprint_error("detect an illegal device! The device use reserved MMC values.\n");
return 0;
}
return count;
}
/*
* return number of the devices's MSI-X vectors
* return 0 if the device does not support MSI-X
*/
int
pci_msix_count(pci_chipset_tag_t pc, pcitag_t tag)
{
pcireg_t reg;
int offset;
if (pci_get_capability(pc, tag, PCI_CAP_MSIX, &offset, NULL) == 0)
return 0;
n = pci_bus_devorder(sc->sc_pc, sc->sc_bus, devs, __arraycount(devs));
if (downstream_port) {
/* PCIe downstream ports only have a single child device */
n = 1;
}
for (i = 0; i < n; i++) {
device = devs[i];
if ((locators[PCICF_DEV] != PCICF_DEV_DEFAULT) &&
(locators[PCICF_DEV] != device))
continue;
tag = pci_make_tag(pc, sc->sc_bus, device, 0);
id = pci_conf_read(pc, tag, PCI_ID_REG);
/* Invalid vendor ID value? */
if (PCI_VENDOR(id) == PCI_VENDOR_INVALID)
continue;
/* XXX Not invalid, but we've done this ~forever. */
if (PCI_VENDOR(id) == 0)
continue;
bhlcr = pci_conf_read(pc, tag, PCI_BHLC_REG);
if (PCI_HDRTYPE_TYPE(bhlcr) > 2)
continue;
if (pci_get_capability(pc, tag, PCI_CAP_VPD, &ofs, ®) == 0)
return 1;
for (i = 0; i < count; offset += sizeof(*data), i++) {
reg &= 0x0000ffff;
reg &= ~PCI_VPD_OPFLAG;
reg |= PCI_VPD_ADDRESS(offset);
pci_conf_write(pc, tag, ofs, reg);
/*
* PCI 2.2 does not specify how long we should poll
* for completion nor whether the operation can fail.
*/
j = 0;
do {
if (j++ == 20)
return 1;
delay(4);
reg = pci_conf_read(pc, tag, ofs);
} while ((reg & PCI_VPD_OPFLAG) == 0);
data[i] = pci_conf_read(pc, tag, PCI_VPD_DATAREG(ofs));
}
return 0;
}
int
pci_vpd_write(pci_chipset_tag_t pc, pcitag_t tag, int offset, int count,
pcireg_t *data)
{
pcireg_t reg;
int ofs, i, j;
/*
* PCI 2.2 does not specify how long we should poll
* for completion nor whether the operation can fail.
*/
j = 0;
do {
if (j++ == 20)
return 1;
delay(1);
reg = pci_conf_read(pc, tag, ofs);
} while (reg & PCI_VPD_OPFLAG);
}
return 0;
}
int
pci_dma64_available(const struct pci_attach_args *pa)
{
#ifdef _PCI_HAVE_DMA64
if (BUS_DMA_TAG_VALID(pa->pa_dmat64))
return 1;
#endif
return 0;
}
for (off = 15; off >= 0; off--) {
val = pci_conf_read(pc, tag, (off * 4));
if (val != pcs->reg[off])
pci_conf_write(pc, tag, (off * 4), pcs->reg[off]);
}
/* For PCI-X */
if (pci_get_capability(pc, tag, PCI_CAP_PCIX, &off, NULL) != 0)
pci_conf_write(pc, tag, off + PCIX_CMD, pcs->x_csr);
/* For PCIe */
if (pci_get_capability(pc, tag, PCI_CAP_PCIEXPRESS, &off, NULL) != 0) {
pcireg_t xcap = pci_conf_read(pc, tag, off + PCIE_XCAP);
unsigned int devtype;
devtype = PCIE_XCAP_TYPE(xcap);
pci_conf_write(pc, tag, off + PCIE_DCSR, pcs->e_dcr);
/*
* PCIe capability is variable sized. To not to write the next
* area, check the existence of each register.
*/
if (PCIE_HAS_LINKREGS(devtype))
pci_conf_write(pc, tag, off + PCIE_LCSR, pcs->e_lcr);
if ((xcap & PCIE_XCAP_SI) != 0)
pci_conf_write(pc, tag, off + PCIE_SLCSR, pcs->e_slcr);
if (PCIE_HAS_ROOTREGS(devtype))
pci_conf_write(pc, tag, off + PCIE_RCR, pcs->e_rcr);
if (__SHIFTOUT(xcap, PCIE_XCAP_VER_MASK) >= 2) {
pci_conf_write(pc, tag, off + PCIE_DCSR2, pcs->e_dcr2);
if (PCIE_HAS_LINKREGS(devtype))
pci_conf_write(pc, tag, off + PCIE_LCSR2,
pcs->e_lcr2);
/* XXX PCIE_SLCSR2 (It's reserved by the PCIe spec) */
}
}
/* For MSI */
if (pci_get_capability(pc, tag, PCI_CAP_MSI, &off, NULL) != 0) {
pcireg_t reg;
bool bit64, pvmask;
/* First, drop Enable bit in case it's already set. */
reg = pci_conf_read(pc, tag, off + PCI_MSI_CTL);
pci_conf_write(pc, tag, off + PCI_MSI_CTL,
reg & ~PCI_MSI_CTL_MSI_ENABLE);
/* Address */
pci_conf_write(pc, tag, off + PCI_MSI_MADDR, pcs->msi_maddr);
if (bit64)
pci_conf_write(pc, tag,
off + PCI_MSI_MADDR64_HI, pcs->msi_maddr64_hi);
/* Data */
pci_conf_write(pc, tag,
off + (bit64 ? PCI_MSI_MDATA64 : PCI_MSI_MDATA),
pcs->msi_mdata);
/* Per-vector masking */
if (pvmask)
pci_conf_write(pc, tag,
off + (bit64 ? PCI_MSI_MASK64 : PCI_MSI_MASK),
pcs->msi_mask);
/* Write CTRL register in the end */
pci_conf_write(pc, tag, off + PCI_MSI_CTL, pcs->msi_ctl);
}
/* For MSI-X */
if (pci_get_capability(pc, tag, PCI_CAP_MSIX, &off, NULL) != 0)
pci_conf_write(pc, tag, off + PCI_MSIX_CTL, pcs->msix_ctl);
}
/*
* Power Management Capability (Rev 2.2)
*/
static int
pci_get_powerstate_int(pci_chipset_tag_t pc, pcitag_t tag , pcireg_t *state,
int offset)
{
pcireg_t value, now;
value = pci_conf_read(pc, tag, offset + PCI_PMCSR);
now = value & PCI_PMCSR_STATE_MASK;
switch (now) {
case PCI_PMCSR_STATE_D0:
case PCI_PMCSR_STATE_D1:
case PCI_PMCSR_STATE_D2:
case PCI_PMCSR_STATE_D3:
*state = now;
return 0;
default:
return EINVAL;
}
}
int
pci_get_powerstate(pci_chipset_tag_t pc, pcitag_t tag , pcireg_t *state)
{
int offset;
pcireg_t value;
if (!pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &offset, &value))
return EOPNOTSUPP;
static int
pci_set_powerstate_int(pci_chipset_tag_t pc, pcitag_t tag, pcireg_t state,
int offset, pcireg_t cap_reg)
{
pcireg_t value, cap, now;
cap = cap_reg >> PCI_PMCR_SHIFT;
value = pci_conf_read(pc, tag, offset + PCI_PMCSR);
now = value & PCI_PMCSR_STATE_MASK;
value &= ~PCI_PMCSR_STATE_MASK;
if (now == state)
return 0;
switch (state) {
case PCI_PMCSR_STATE_D0:
break;
case PCI_PMCSR_STATE_D1:
if (now == PCI_PMCSR_STATE_D2 || now == PCI_PMCSR_STATE_D3) {
printf("invalid transition from %d to D1\n", (int)now);
return EINVAL;
}
if (!(cap & PCI_PMCR_D1SUPP)) {
printf("D1 not supported\n");
return EOPNOTSUPP;
}
break;
case PCI_PMCSR_STATE_D2:
if (now == PCI_PMCSR_STATE_D3) {
printf("invalid transition from %d to D2\n", (int)now);
return EINVAL;
}
if (!(cap & PCI_PMCR_D2SUPP)) {
printf("D2 not supported\n");
return EOPNOTSUPP;
}
break;
case PCI_PMCSR_STATE_D3:
break;
default:
return EINVAL;
}
value |= state;
pci_conf_write(pc, tag, offset + PCI_PMCSR, value);
/* delay according to pcipm1.2, ch. 5.6.1 */
if (state == PCI_PMCSR_STATE_D3 || now == PCI_PMCSR_STATE_D3)
DELAY(10000);
else if (state == PCI_PMCSR_STATE_D2 || now == PCI_PMCSR_STATE_D2)
DELAY(200);
return 0;
}
int
pci_set_powerstate(pci_chipset_tag_t pc, pcitag_t tag, pcireg_t state)
{
int offset;
pcireg_t value;
if (!pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &offset, &value)) {
printf("pci_set_powerstate not supported\n");
return EOPNOTSUPP;
}
int
pci_activate(pci_chipset_tag_t pc, pcitag_t tag, device_t dev,
int (*wakefun)(pci_chipset_tag_t, pcitag_t, device_t, pcireg_t))
{
pcireg_t pmode;
int error;
if ((error = pci_get_powerstate(pc, tag, &pmode)))
return error;
switch (pmode) {
case PCI_PMCSR_STATE_D0:
break;
case PCI_PMCSR_STATE_D3:
if (wakefun == NULL) {
/*
* The card has lost all configuration data in
* this state, so punt.
*/
aprint_error_dev(dev,
"unable to wake up from power state D3\n");
return EOPNOTSUPP;
}
/*FALLTHROUGH*/
default:
if (wakefun) {
error = (*wakefun)(pc, tag, dev, pmode);
if (error)
return error;
}
aprint_normal_dev(dev, "waking up from power state D%d\n",
pmode);
if ((error = pci_set_powerstate(pc, tag, PCI_PMCSR_STATE_D0)))
return error;
}
return 0;
}