/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* SPARC specific code used by the pcieb driver */ #include #include #include #include #include #include #include #include #include #include #include "pcieb_plx.h" /*LINTLIBRARY*/ /* PLX specific functions */ #ifdef PX_PLX static void plx_ro_disable(pcieb_devstate_t *pcieb); #ifdef PRINT_PLX_SEEPROM_CRC static void pcieb_print_plx_seeprom_crc_data(pcieb_devstate_t *pcieb_p); #endif /* PRINT_PLX_SEEPROM_CRC */ #endif /* PX_PLX */ int pcieb_plat_peekpoke(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result) { return (ddi_ctlops(dip, rdip, ctlop, arg, result)); } /*ARGSUSED*/ void pcieb_set_prot_scan(dev_info_t *dip, ddi_acc_impl_t *hdlp) { } /*ARGSUSED*/ void pcieb_plat_attach_workaround(dev_info_t *dip) { } int pcieb_plat_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { dev_info_t *cdip = rdip; pci_regspec_t *pci_rp; int reglen, len; uint32_t d, intr; if ((intr_op == DDI_INTROP_SUPPORTED_TYPES) || (hdlp->ih_type != DDI_INTR_TYPE_FIXED)) goto done; /* * If the interrupt-map property is defined at this * node, it will have performed the interrupt * translation as part of the property, so no * rotation needs to be done. */ if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "interrupt-map", &len) == DDI_PROP_SUCCESS) goto done; cdip = pcie_get_my_childs_dip(dip, rdip); /* * Use the devices reg property to determine its * PCI bus number and device number. */ if (ddi_getlongprop(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS, "reg", (caddr_t)&pci_rp, ®len) != DDI_SUCCESS) return (DDI_FAILURE); intr = hdlp->ih_vector; d = (pcie_ari_is_enabled(dip) == PCIE_ARI_FORW_ENABLED) ? 0 : PCI_REG_DEV_G(pci_rp[0].pci_phys_hi); /* spin the interrupt */ if ((intr >= PCI_INTA) && (intr <= PCI_INTD)) hdlp->ih_vector = ((intr - 1 + (d % 4)) % 4 + 1); else cmn_err(CE_WARN, "%s%d: %s: PCI intr=%x out of range", ddi_driver_name(rdip), ddi_get_instance(rdip), ddi_driver_name(dip), intr); kmem_free(pci_rp, reglen); done: /* Pass up the request to our parent. */ return (i_ddi_intr_ops(dip, rdip, intr_op, hdlp, result)); } int pcieb_plat_pcishpc_probe(dev_info_t *dip, ddi_acc_handle_t config_handle) { uint16_t cap_ptr; if ((PCI_CAP_LOCATE(config_handle, PCI_CAP_ID_PCI_HOTPLUG, &cap_ptr)) != DDI_FAILURE) { return (DDI_SUCCESS); } return (DDI_FAILURE); } /* * Disable PM on PLX. For PLX Transitioning one port on this switch to * low power causes links on other ports on the same station to die. * Due to PLX erratum #34, we can't allow the downstream device go to * non-D0 state. */ boolean_t pcieb_plat_pwr_disable(dev_info_t *dip) { uint16_t vendor_id = (PCIE_DIP2UPBUS(dip)->bus_dev_ven_id) & 0xFFFF; return (IS_PLX_VENDORID(vendor_id) ? B_TRUE : B_FALSE); } /*ARGSUSED*/ boolean_t pcieb_plat_msi_supported(dev_info_t *dip) { return (B_TRUE); } /*ARGSUSED*/ void pcieb_plat_intr_attach(pcieb_devstate_t *pcieb) { } /*ARGSUSED*/ int pcieb_plat_ctlops(dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg) { return (DDI_SUCCESS); } void pcieb_plat_initchild(dev_info_t *child) { intptr_t ppd = (intptr_t)NULL; /* * XXX set ppd to 1 to disable iommu BDF protection on SPARC. * It relies on unused parent private data for PCI devices. */ if (ddi_prop_exists(DDI_DEV_T_NONE, child, DDI_PROP_DONTPASS, "dvma-share")) ppd = 1; ddi_set_parent_data(child, (void *)ppd); } void pcieb_plat_uninitchild(dev_info_t *child) { /* * XXX Clear parent private data used as a flag to disable * iommu BDF protection */ if ((intptr_t)ddi_get_parent_data(child) == 1) ddi_set_parent_data(child, NULL); } #ifdef PX_PLX /* * These are PLX specific workarounds needed during attach. */ void pcieb_attach_plx_workarounds(pcieb_devstate_t *pcieb) { dev_info_t *dip = pcieb->pcieb_dip; pcie_bus_t *bus_p = PCIE_DIP2BUS(dip); ddi_acc_handle_t config_handle = bus_p->bus_cfg_hdl; uint_t bus_num, primary, secondary; uint8_t dev_type = bus_p->bus_dev_type; uint16_t vendor_id = bus_p->bus_dev_ven_id & 0xFFFF; uint16_t device_id = bus_p->bus_dev_ven_id >> 16; int ce_mask = 0; if (!IS_PLX_VENDORID(vendor_id)) return; if ((device_id == PXB_DEVICE_PLX_8532) && (bus_p->bus_rev_id <= PXB_DEVICE_PLX_AA_REV)) /* Clear hotplug capability */ bus_p->bus_hp_sup_modes = PCIE_NONE_HP_MODE; /* * Due to a PLX HW bug we need to disable the receiver error CE on all * ports. To this end we create a property "pcie_ce_mask" with value * set to PCIE_AER_CE_RECEIVER_ERR. The pcie module will check for this * property before setting the AER CE mask. Be sure to honor all other * pcie_ce_mask settings. */ ce_mask = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "pcie_ce_mask", 0); (void) ddi_prop_update_int(DDI_DEV_T_NONE, dip, "pcie_ce_mask", (PCIE_AER_CE_RECEIVER_ERR|ce_mask)); /* * There is a bug in the PLX 8114 bridge, such that an 8-bit * write to the secondary bus number register will corrupt an * internal shadow copy of the primary bus number. Reading * out the registers and writing the same values back as * 16-bits resolves the problem. This bug was reported by * PLX as errata #19. */ primary = pci_config_get8(config_handle, PCI_BCNF_PRIBUS); secondary = pci_config_get8(config_handle, PCI_BCNF_SECBUS); bus_num = (secondary << 8) | primary; pci_config_put16(config_handle, PCI_BCNF_PRIBUS, bus_num); /* * Workaround for a race condition between hotplug * initialization and actual MSI interrupt registration * for hotplug functionality. The hotplug initialization * generates an INTx interrupt for hotplug events and this * INTx interrupt may interfere with shared leaf drivers * using same INTx interrupt, which may eventually block * the leaf drivers. */ if ((dev_type == PCIE_PCIECAP_DEV_TYPE_DOWN) || (dev_type == PCIE_PCIECAP_DEV_TYPE_ROOT) || (dev_type == PCIE_PCIECAP_DEV_TYPE_PCIE2PCI) || (dev_type == PCIE_PCIECAP_DEV_TYPE_PCI2PCIE)) { pci_config_put16(config_handle, PCI_CONF_COMM, pci_config_get16(config_handle, PCI_CONF_COMM) | PCI_COMM_INTX_DISABLE); } /* * Disable PLX Special Relaxed Ordering */ plx_ro_disable(pcieb); #ifdef PRINT_PLX_SEEPROM_CRC /* check seeprom CRC to ensure the platform config is right */ (void) pcieb_print_plx_seeprom_crc_data(pcieb); #endif /* PRINT_PLX_SEEPROM_CRC */ } /* * These are PLX specific workarounds called during child's initchild. */ int pcieb_init_plx_workarounds(pcieb_devstate_t *pcieb, dev_info_t *child) { int i; int result = DDI_FAILURE; uint16_t reg = 0; ddi_acc_handle_t config_handle; uint16_t vendor_id = (PCIE_DIP2UPBUS(pcieb->pcieb_dip))->bus_dev_ven_id & 0xFFFF; if (!IS_PLX_VENDORID(vendor_id)) return (DDI_SUCCESS); /* * Due to a PLX HW bug, a SW workaround to prevent the chip from * wedging is needed. SW just needs to tranfer 64 TLPs from * the downstream port to the child device. * The most benign way of doing this is to read the ID register * 64 times. This SW workaround should have minimum performance * impact and shouldn't cause a problem for all other bridges * and switches. * * The code needs to be written in a way to make sure it isn't * optimized out. */ if (!pxb_tlp_count) { result = DDI_SUCCESS; goto done; } if (pci_config_setup(child, &config_handle) != DDI_SUCCESS) { result = DDI_FAILURE; goto done; } for (i = 0; i < pxb_tlp_count; i += 1) reg |= pci_config_get16(config_handle, PCI_CONF_VENID); if (PCIE_IS_PCIE_BDG(PCIE_DIP2BUS(pcieb->pcieb_dip))) pcieb_set_pci_perf_parameters(child, config_handle); pci_config_teardown(&config_handle); result = DDI_SUCCESS; done: return (result); } /* * Disable PLX specific relaxed ordering mode. Due to PLX * erratum #6, use of this mode with Cut-Through Cancellation * can result in dropped Completion type packets. * * Clear the Relaxed Ordering Mode on 8533 and 8548 switches. * To disable RO, clear bit 5 in offset 0x664, an undocumented * bit in the PLX spec, on Ports 0, 8 and 12. Proprietary PLX * registers are normally accessible only via memspace from Port * 0. If port 0 is attached go ahead and disable RO on Port 0, * 8 and 12, if they exist. */ static void plx_ro_disable(pcieb_devstate_t *pcieb) { pcie_bus_t *bus_p = PCIE_DIP2BUS(pcieb->pcieb_dip); dev_info_t *dip = pcieb->pcieb_dip; uint16_t device_id = bus_p->bus_dev_ven_id >> 16; pci_regspec_t *reg_spec, *addr_spec; int rlen, alen; int orig_rsize, new_rsize; uint_t rnum, anum; ddi_device_acc_attr_t attr; ddi_acc_handle_t hdl; caddr_t regsp; uint32_t val, port_enable; char *offset; char *port_offset; if (!((device_id == PXB_DEVICE_PLX_8533) || (device_id == PXB_DEVICE_PLX_8548))) return; /* You can also only do this on Port 0 */ val = PCIE_CAP_GET(32, bus_p, PCIE_LINKCAP); val = (val >> PCIE_LINKCAP_PORT_NUMBER_SHIFT) & PCIE_LINKCAP_PORT_NUMBER_MASK; PCIEB_DEBUG(DBG_ATTACH, dip, "PLX RO Disable : bdf=0x%x port=%d\n", bus_p->bus_bdf, val); if (val != 0) return; /* * Read the reg property, but allocate extra space incase we need to add * a new entry later. */ if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", &orig_rsize) != DDI_SUCCESS) return; new_rsize = orig_rsize + sizeof (pci_regspec_t); reg_spec = kmem_alloc(new_rsize, KM_SLEEP); if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg", (caddr_t)reg_spec, &orig_rsize) != DDI_SUCCESS) goto fail; /* Find the mem32 reg property */ rlen = orig_rsize / sizeof (pci_regspec_t); for (rnum = 0; rnum < rlen; rnum++) { if ((reg_spec[rnum].pci_phys_hi & PCI_ADDR_MASK) == PCI_ADDR_MEM32) goto fix; } /* * Mem32 reg property was not found. * Look for it in assign-address property. */ addr_spec = bus_p->bus_assigned_addr; alen = bus_p->bus_assigned_entries; for (anum = 0; anum < alen; anum++) { if ((addr_spec[anum].pci_phys_hi & PCI_ADDR_MASK) == PCI_ADDR_MEM32) goto update; } /* Unable to find mem space assigned address, give up. */ goto fail; update: /* * Add the mem32 access to the reg spec. * Use the last entry which was previously allocated. */ reg_spec[rnum].pci_phys_hi = (addr_spec[anum].pci_phys_hi & ~PCI_REG_REL_M); reg_spec[rnum].pci_phys_mid = 0; reg_spec[rnum].pci_phys_low = 0; reg_spec[rnum].pci_size_hi = addr_spec[anum].pci_size_hi; reg_spec[rnum].pci_size_low = addr_spec[anum].pci_size_low; /* Create the new reg_spec data and update the property */ if (ddi_prop_update_int_array(DDI_DEV_T_NONE, dip, "reg", (int *)reg_spec, (new_rsize / sizeof (int))) != DDI_SUCCESS) goto fail; fix: attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; if (ddi_regs_map_setup(dip, rnum, ®sp, 0, 0, &attr, &hdl) != DDI_SUCCESS) goto fail; /* Grab register which shows which ports are enabled */ offset = (char *)regsp + PLX_INGRESS_PORT_ENABLE; port_enable = ddi_get32(hdl, (uint32_t *)offset); if ((port_enable == 0xFFFFFFFF) || (port_enable == 0)) goto done; offset = (char *)regsp + PLX_INGRESS_CONTROL_SHADOW; /* Disable RO on Port 0 */ port_offset = 0x0 + offset; val = ddi_get32(hdl, (uint32_t *)port_offset); if (val & PLX_RO_MODE_BIT) val ^= PLX_RO_MODE_BIT; ddi_put32(hdl, (uint32_t *)port_offset, val); /* Disable RO on Port 8, but make sure its enabled */ if (!(port_enable & (1 << 8))) goto port12; port_offset = (8 * 0x1000) + offset; val = ddi_get32(hdl, (uint32_t *)port_offset); if (val & PLX_RO_MODE_BIT) val ^= PLX_RO_MODE_BIT; ddi_put32(hdl, (uint32_t *)port_offset, val); port12: /* Disable RO on Port 12, but make sure it exists */ if (!(port_enable & (1 << 12))) goto done; port_offset = (12 * 0x1000) + offset; val = ddi_get32(hdl, (uint32_t *)port_offset); if (val & PLX_RO_MODE_BIT) val ^= PLX_RO_MODE_BIT; ddi_put32(hdl, (uint32_t *)port_offset, val); goto done; done: ddi_regs_map_free(&hdl); fail: kmem_free(reg_spec, new_rsize); } #ifdef PRINT_PLX_SEEPROM_CRC static void pcieb_print_plx_seeprom_crc_data(pcieb_devstate_t *pcieb_p) { ddi_acc_handle_t h; dev_info_t *dip = pcieb_p->pcieb_dip; uint16_t vendorid = (PCIE_DIP2BUS(dip)->bus_dev_ven_id) & 0xFFFF; int nregs; caddr_t mp; off_t bar_size; ddi_device_acc_attr_t mattr = { DDI_DEVICE_ATTR_V0, DDI_STRUCTURE_LE_ACC, DDI_STRICTORDER_ACC }; uint32_t addr_reg_off = 0x260, data_reg_off = 0x264, data = 0x6BE4; if (vendorid != PXB_VENDOR_PLX) return; if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS) return; if (nregs < 2) /* check for CONF entry only, no BARs */ return; if (ddi_dev_regsize(dip, 1, &bar_size) != DDI_SUCCESS) return; if (ddi_regs_map_setup(dip, 1, (caddr_t *)&mp, 0, bar_size, &mattr, &h) != DDI_SUCCESS) return; ddi_put32(h, (uint32_t *)((uchar_t *)mp + addr_reg_off), data); delay(drv_usectohz(1000000)); printf("%s#%d: EEPROM StatusReg = %x, CRC = %x\n", ddi_driver_name(dip), ddi_get_instance(dip), ddi_get32(h, (uint32_t *)((uchar_t *)mp + addr_reg_off)), ddi_get32(h, (uint32_t *)((uchar_t *)mp + data_reg_off))); #ifdef PLX_HOT_RESET_DISABLE /* prevent hot reset from propogating downstream. */ data = ddi_get32(h, (uint32_t *)((uchar_t *)mp + 0x1DC)); ddi_put32(h, (uint32_t *)((uchar_t *)mp + 0x1DC), data | 0x80000); delay(drv_usectohz(1000000)); printf("%s#%d: EEPROM 0x1DC prewrite=%x postwrite=%x\n", ddi_driver_name(dip), ddi_get_instance(dip), data, ddi_get32(h, (uint32_t *)((uchar_t *)mp + 0x1DC))); #endif /* PLX_HOT_RESET_DISABLE */ ddi_regs_map_free(&h); } #endif /* PRINT_PLX_SEEPROM_CRC */ #endif /* PX_PLX */