aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/platforms/ibm-fsp/firenze-pci.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/platforms/ibm-fsp/firenze-pci.c')
-rw-r--r--roms/skiboot/platforms/ibm-fsp/firenze-pci.c1044
1 files changed, 1044 insertions, 0 deletions
diff --git a/roms/skiboot/platforms/ibm-fsp/firenze-pci.c b/roms/skiboot/platforms/ibm-fsp/firenze-pci.c
new file mode 100644
index 000000000..5fbbc2ff2
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/firenze-pci.c
@@ -0,0 +1,1044 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#define pr_fmt(fmt) "FIRENZE-PCI: " fmt
+#include <skiboot.h>
+#include <device.h>
+#include <fsp.h>
+#include <lock.h>
+#include <timer.h>
+#include <xscom.h>
+#include <pci-cfg.h>
+#include <pci.h>
+#include <pci-slot.h>
+#include <phb3.h>
+#include <chip.h>
+#include <i2c.h>
+
+#include "ibm-fsp.h"
+#include "lxvpd.h"
+
+/* Dump PCI slots before sending to FSP */
+#define FIRENZE_PCI_INVENTORY_DUMP
+
+/*
+ * Firenze PCI slot states to override the default set.
+ * Refer to pci-slot.h for the default PCI state set
+ * when you're going to change below values.
+ */
+#define FIRENZE_PCI_SLOT_NORMAL PCI_SLOT_STATE_NORMAL
+#define FIRENZE_PCI_SLOT_LINK PCI_SLOT_STATE_LINK
+#define FIRENZE_PCI_SLOT_LINK_START (FIRENZE_PCI_SLOT_LINK + 1)
+#define FIRENZE_PCI_SLOT_HRESET PCI_SLOT_STATE_HRESET
+#define FIRENZE_PCI_SLOT_HRESET_START (FIRENZE_PCI_SLOT_HRESET + 1)
+#define FIRENZE_PCI_SLOT_FRESET PCI_SLOT_STATE_FRESET
+#define FIRENZE_PCI_SLOT_FRESET_START (FIRENZE_PCI_SLOT_FRESET + 1)
+#define FIRENZE_PCI_SLOT_FRESET_WAIT_RSP (FIRENZE_PCI_SLOT_FRESET + 2)
+#define FIRENZE_PCI_SLOT_FRESET_DELAY (FIRENZE_PCI_SLOT_FRESET + 3)
+#define FIRENZE_PCI_SLOT_FRESET_POWER_STATE (FIRENZE_PCI_SLOT_FRESET + 4)
+#define FIRENZE_PCI_SLOT_FRESET_POWER_OFF (FIRENZE_PCI_SLOT_FRESET + 5)
+#define FIRENZE_PCI_SLOT_FRESET_POWER_ON (FIRENZE_PCI_SLOT_FRESET + 6)
+#define FIRENZE_PCI_SLOT_PERST_DEASSERT (FIRENZE_PCI_SLOT_FRESET + 7)
+#define FIRENZE_PCI_SLOT_PERST_DELAY (FIRENZE_PCI_SLOT_FRESET + 8)
+#define FIRENZE_PCI_SLOT_GPOWER PCI_SLOT_STATE_GPOWER
+#define FIRENZE_PCI_SLOT_GPOWER_START (FIRENZE_PCI_SLOT_GPOWER + 1)
+#define FIRENZE_PCI_SLOT_SPOWER PCI_SLOT_STATE_SPOWER
+#define FIRENZE_PCI_SLOT_SPOWER_START (FIRENZE_PCI_SLOT_SPOWER + 1)
+#define FIRENZE_PCI_SLOT_SPOWER_DONE (FIRENZE_PCI_SLOT_SPOWER + 2)
+
+/* Timeout for power status */
+#define FIRENZE_PCI_SLOT_RETRIES 500
+#define FIRENZE_PCI_SLOT_DELAY 10 /* ms */
+#define FIRENZE_PCI_I2C_TIMEOUT 500 /* ms */
+
+/*
+ * Need figure out more stuff later: LED and presence
+ * detection sensors are accessed from PSI/FSP.
+ */
+struct firenze_pci_slot {
+ struct lxvpd_pci_slot lxvpd_slot; /* LXVPD slot data */
+
+ /* Next slot state */
+ uint32_t next_state;
+
+ /* Power management */
+ struct i2c_bus *i2c_bus; /* Where MAX5961 seats */
+ struct i2c_request *req; /* I2C request message */
+ uint8_t i2c_rw_buf[8]; /* I2C read/write buffer */
+ uint8_t power_mask; /* Bits for power status */
+ uint8_t power_on; /* Bits for power on */
+ uint8_t power_off; /* Bits for power off */
+ uint8_t *power_status; /* Last power status */
+ uint16_t perst_reg; /* PERST config register */
+ uint16_t perst_bit; /* PERST bit */
+};
+
+struct firenze_pci_slot_info {
+ uint8_t index;
+ const char *label;
+ uint8_t external_power_mgt;
+ uint8_t inband_perst;
+ uint8_t chip_id;
+ uint8_t master_id;
+ uint8_t port_id;
+ uint8_t slave_addr;
+ uint8_t channel;
+ uint8_t power_status;
+ uint8_t buddy;
+};
+
+struct firenze_pci_slot_fixup_info {
+ const char *label;
+ uint8_t reg;
+ uint8_t val;
+};
+
+struct firenze_pci_inv {
+ __be32 hw_proc_id;
+ __be16 slot_idx;
+ __be16 reserved;
+ __be16 vendor_id;
+ __be16 device_id;
+ __be16 subsys_vendor_id;
+ __be16 subsys_device_id;
+} __packed;
+
+struct firenze_pci_inv_data {
+ __be32 version; /* currently 1 */
+ __be32 num_entries;
+ __be32 entry_size;
+ __be32 entry_offset;
+ struct firenze_pci_inv entries[];
+} __packed;
+
+/*
+ * Note: According to Tuleta system workbook, I didn't figure
+ * out the I2C mapping info for slot C14/C15.
+ */
+static struct firenze_pci_inv_data *firenze_inv_data;
+static uint32_t firenze_inv_cnt;
+static struct firenze_pci_slot_info firenze_pci_slots[] = {
+ { 0x0B, "C7", 1, 1, 0, 1, 0, 0x35, 1, 0xAA, 0 },
+ { 0x11, "C14", 0, 1, 0, 0, 0, 0x00, 0, 0xAA, 1 },
+ { 0x0F, "C11", 1, 1, 0, 1, 0, 0x32, 1, 0xAA, 2 },
+ { 0x10, "C12", 1, 1, 0, 1, 0, 0x39, 0, 0xAA, 3 },
+ { 0x0A, "C6", 1, 1, 0, 1, 0, 0x35, 0, 0xAA, 0 },
+ { 0x12, "C15", 0, 1, 0, 0, 0, 0x00, 0, 0xAA, 5 },
+ { 0x01, "USB", 0, 0, 0, 0, 0, 0x00, 0, 0xAA, 6 },
+ { 0x0C, "C8", 1, 1, 0, 1, 0, 0x36, 0, 0xAA, 7 },
+ { 0x0D, "C9", 1, 1, 0, 1, 0, 0x36, 1, 0xAA, 7 },
+ { 0x0E, "C10", 1, 1, 0, 1, 0, 0x32, 0, 0xAA, 2 },
+ { 0x09, "C5", 1, 1, 0x10, 1, 0, 0x39, 1, 0xAA, 10 },
+ { 0x08, "C4", 1, 1, 0x10, 1, 0, 0x39, 0, 0xAA, 10 },
+ { 0x07, "C3", 1, 1, 0x10, 1, 0, 0x3A, 1, 0xAA, 12 },
+ { 0x06, "C2", 1, 1, 0x10, 1, 0, 0x3A, 0, 0xAA, 12 }
+};
+
+/*
+ * I2C power controller register fix up table. Not sure what they do, but
+ * they seem to relate to the fast-trip setpoint.
+ */
+static struct firenze_pci_slot_fixup_info firenze_pci_slot_fixup_tbl[] = {
+ { "C3", 0x5e, 0xfb },
+ { "C3", 0x5b, 0xff },
+ { "C5", 0x5e, 0xfb },
+ { "C5", 0x5b, 0xff },
+ { "C6", 0x5e, 0xfa },
+ { "C6", 0x5a, 0xff },
+ { "C6", 0x5b, 0xff },
+ { "C7", 0x5e, 0xfa },
+ { "C7", 0x5a, 0xff },
+ { "C7", 0x5b, 0xff }
+};
+
+static void firenze_pci_add_inventory(struct phb *phb,
+ struct pci_device *pd)
+{
+ struct lxvpd_pci_slot *lxvpd_slot;
+ struct firenze_pci_inv *entry;
+ struct proc_chip *chip;
+ size_t size;
+ bool need_init = false;
+ u32 num_entries;
+ u16 tmp16;
+
+ /*
+ * Do we need to add that to the FSP inventory for power
+ * management?
+ *
+ * For now, we only add devices that:
+ *
+ * - Are function 0
+ * - Are not an RC or a downstream bridge
+ * - Have a direct parent that has a slot entry
+ * - Slot entry says pluggable
+ * - Aren't an upstream switch that has slot info
+ */
+ if (!pd || !pd->parent)
+ return;
+ if (pd->bdfn & 7)
+ return;
+ if (pd->dev_type == PCIE_TYPE_ROOT_PORT ||
+ pd->dev_type == PCIE_TYPE_SWITCH_DNPORT)
+ return;
+ if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
+ pd->slot && pd->slot->data)
+ return;
+ if (!pd->parent->slot ||
+ !pd->parent->slot->data)
+ return;
+ lxvpd_slot = pd->parent->slot->data;
+ if (!lxvpd_slot->pluggable)
+ return;
+
+ /* Check if we need to do some (Re)allocation */
+ if (!firenze_inv_data ||
+ be32_to_cpu(firenze_inv_data->num_entries) == firenze_inv_cnt) {
+ need_init = !firenze_inv_data;
+
+ /* (Re)allocate the block to the new size */
+ firenze_inv_cnt += 4;
+ size = sizeof(struct firenze_pci_inv_data) +
+ sizeof(struct firenze_pci_inv) * firenze_inv_cnt;
+ firenze_inv_data = realloc(firenze_inv_data, size);
+ }
+
+ /* Initialize the header for a new inventory */
+ if (need_init) {
+ firenze_inv_data->version = cpu_to_be32(1);
+ firenze_inv_data->num_entries = 0;
+ firenze_inv_data->entry_size =
+ cpu_to_be32(sizeof(struct firenze_pci_inv));
+ firenze_inv_data->entry_offset =
+ cpu_to_be32(offsetof(struct firenze_pci_inv_data, entries));
+ }
+
+ /* Append slot entry */
+ num_entries = be32_to_cpu(firenze_inv_data->num_entries);
+ firenze_inv_data->num_entries = cpu_to_be32(num_entries + 1);
+ entry = &firenze_inv_data->entries[num_entries];
+ chip = get_chip(dt_get_chip_id(phb->dt_node));
+ if (!chip) {
+ /**
+ * @fwts-label FirenzePCIInventory
+ * @fwts-advice Device tree didn't contain enough information
+ * to correctly report back PCI inventory. Service processor
+ * is likely to be missing information about what hardware
+ * is physically present in the machine.
+ */
+ prlog(PR_ERR, "No chip device node for PHB%04x\n",
+ phb->opal_id);
+ return;
+ }
+
+ entry->hw_proc_id = cpu_to_be32(chip->pcid);
+ entry->reserved = 0;
+ if (pd->parent &&
+ pd->parent->slot &&
+ pd->parent->slot->data) {
+ lxvpd_slot = pd->parent->slot->data;
+ entry->slot_idx = cpu_to_be16(lxvpd_slot->slot_index);
+ }
+
+ pci_cfg_read16(phb, pd->bdfn, PCI_CFG_VENDOR_ID, &tmp16);
+ entry->vendor_id = cpu_to_be16(tmp16);
+ pci_cfg_read16(phb, pd->bdfn, PCI_CFG_DEVICE_ID, &tmp16);
+ entry->device_id = cpu_to_be16(tmp16);
+ if (pd->is_bridge) {
+ int64_t ssvc = pci_find_cap(phb, pd->bdfn,
+ PCI_CFG_CAP_ID_SUBSYS_VID);
+ if (ssvc <= 0) {
+ entry->subsys_vendor_id = cpu_to_be16(0xffff);
+ entry->subsys_device_id = cpu_to_be16(0xffff);
+ } else {
+ pci_cfg_read16(phb, pd->bdfn,
+ ssvc + PCICAP_SUBSYS_VID_VENDOR, &tmp16);
+ entry->subsys_vendor_id = cpu_to_be16(tmp16);
+ pci_cfg_read16(phb, pd->bdfn,
+ ssvc + PCICAP_SUBSYS_VID_DEVICE, &tmp16);
+ entry->subsys_device_id = cpu_to_be16(tmp16);
+ }
+ } else {
+ pci_cfg_read16(phb, pd->bdfn, PCI_CFG_SUBSYS_VENDOR_ID, &tmp16);
+ entry->subsys_vendor_id = cpu_to_be16(tmp16);
+ pci_cfg_read16(phb, pd->bdfn, PCI_CFG_SUBSYS_ID, &tmp16);
+ entry->subsys_device_id = cpu_to_be16(tmp16);
+ }
+}
+
+static void firenze_dump_pci_inventory(void)
+{
+#ifdef FIRENZE_PCI_INVENTORY_DUMP
+ struct firenze_pci_inv *e;
+ uint32_t i;
+
+ if (!firenze_inv_data)
+ return;
+
+ prlog(PR_INFO, "Dumping Firenze PCI inventory\n");
+ prlog(PR_INFO, "HWP SLT VDID DVID SVID SDID\n");
+ prlog(PR_INFO, "---------------------------\n");
+ for (i = 0; i < be32_to_cpu(firenze_inv_data->num_entries); i++) {
+ e = &firenze_inv_data->entries[i];
+
+ prlog(PR_INFO, "%03d %03d %04x %04x %04x %04x\n",
+ be32_to_cpu(e->hw_proc_id),
+ be16_to_cpu(e->slot_idx),
+ be16_to_cpu(e->vendor_id),
+ be16_to_cpu(e->device_id),
+ be16_to_cpu(e->subsys_vendor_id),
+ be16_to_cpu(e->subsys_device_id));
+ }
+#endif /* FIRENZE_PCI_INVENTORY_DUMP */
+}
+
+void firenze_pci_send_inventory(void)
+{
+ uint64_t base, abase, end, aend, offset;
+ int64_t rc;
+
+ if (!firenze_inv_data)
+ return;
+
+ /* Dump the inventory */
+ prlog(PR_INFO, "Sending %d inventory to FSP\n",
+ be32_to_cpu(firenze_inv_data->num_entries));
+ firenze_dump_pci_inventory();
+
+ /* Memory location for inventory */
+ base = (uint64_t)firenze_inv_data;
+ end = base + sizeof(struct firenze_pci_inv_data) +
+ be32_to_cpu(firenze_inv_data->num_entries) *
+ be32_to_cpu(firenze_inv_data->entry_size);
+ abase = base & ~0xffful;
+ aend = (end + 0xffful) & ~0xffful;
+ offset = PSI_DMA_PCIE_INVENTORY + (base & 0xfff);
+
+ /* We can only accomodate so many entries in the PSI map */
+ if ((aend - abase) > PSI_DMA_PCIE_INVENTORY_SIZE) {
+ /**
+ * @fwts-label FirenzePCIInventoryTooLarge
+ * @fwts-advice More PCI inventory than we can send to service
+ * processor. The service processor will have an incomplete
+ * view of the world.
+ */
+ prlog(PR_ERR, "Inventory (%lld bytes) too large\n",
+ aend - abase);
+ goto bail;
+ }
+
+ /* Map this in the TCEs */
+ fsp_tce_map(PSI_DMA_PCIE_INVENTORY, (void *)abase, aend - abase);
+
+ /* Send FSP message */
+ rc = fsp_sync_msg(fsp_mkmsg(FSP_CMD_PCI_POWER_CONF, 3,
+ hi32(offset), lo32(offset),
+ end - base), true);
+ if (rc)
+ {
+ /**
+ * @fwts-label FirenzePCIInventoryError
+ * @fwts-advice Error communicating with service processor
+ * when sending PCI Inventory.
+ */
+ prlog(PR_ERR, "Error %lld sending inventory\n", rc);
+ }
+
+ /* Unmap */
+ fsp_tce_unmap(PSI_DMA_PCIE_INVENTORY, aend - abase);
+bail:
+ /*
+ * We free the inventory. We'll have to redo that on hotplug
+ * when we support it but that isn't the case yet
+ */
+ free(firenze_inv_data);
+ firenze_inv_data = NULL;
+ firenze_inv_cnt = 0;
+}
+
+/* The function is called when the I2C request is completed
+ * successfully, or with errors.
+ */
+static void firenze_i2c_req_done(int rc, struct i2c_request *req)
+{
+ struct pci_slot *slot = req->user_data;
+ uint32_t state;
+
+ /* Check if there are errors for the completion */
+ if (rc) {
+ /**
+ * @fwts-label FirenzePCII2CError
+ * @fwts-advice On Firenze platforms, I2C is used to control
+ * power to PCI slots. Errors here mean we may be in trouble
+ * in regards to PCI slot power on/off.
+ */
+ prlog(PR_ERR, "Error %d from I2C request on slot %016llx\n",
+ rc, slot->id);
+ return;
+ }
+
+ /* Check the request type */
+ if (req->op != SMBUS_READ && req->op != SMBUS_WRITE) {
+ /**
+ * @fwts-label FirenzePCII2CInvalid
+ * @fwts-advice Likely a coding error: invalid I2C request.
+ */
+ prlog(PR_ERR, "Invalid I2C request %d on slot %016llx\n",
+ req->op, slot->id);
+ return;
+ }
+
+ /* After writting power status to I2C slave, we need at least
+ * 5ms delay for the slave to settle down. We also have the
+ * delay after reading the power status as well.
+ */
+ switch (slot->state) {
+ case FIRENZE_PCI_SLOT_FRESET_WAIT_RSP:
+ prlog(PR_DEBUG, "%016llx FRESET: I2C request completed\n",
+ slot->id);
+ state = FIRENZE_PCI_SLOT_FRESET_DELAY;
+ break;
+ case FIRENZE_PCI_SLOT_SPOWER_START:
+ prlog(PR_DEBUG, "%016llx SPOWER: I2C request completed\n",
+ slot->id);
+ state = FIRENZE_PCI_SLOT_SPOWER_DONE;
+ break;
+ default:
+ /**
+ * @fwts-label FirenzePCISlotI2CStateError
+ * @fwts-advice The Firenze platform uses I2C to control
+ * power to PCI slots. Something went wrong in the state
+ * machine controlling that. Slots may/may not have power.
+ */
+ prlog(PR_ERR, "Wrong state %08x on slot %016llx\n",
+ slot->state, slot->id);
+ return;
+ }
+
+ /* Switch to net state */
+ pci_slot_set_state(slot, state);
+}
+
+/* This function is called to setup normal PCI device or PHB slot.
+ * For the later case, the slot doesn't have the associated PCI
+ * device. Besides, the I2C response timeout is set to 5s. We might
+ * improve I2C in future to support priorized requests so that the
+ * timeout can be shortened.
+ */
+static int64_t firenze_pci_slot_freset(struct pci_slot *slot)
+{
+ struct firenze_pci_slot *plat_slot = slot->data;
+ uint8_t *pval, presence = 1;
+ uint32_t timeout;
+
+ switch (slot->state) {
+ case FIRENZE_PCI_SLOT_NORMAL:
+ case FIRENZE_PCI_SLOT_FRESET_START:
+ prlog(PR_DEBUG, "%016llx FRESET: Starts\n",
+ slot->id);
+
+ /* Bail if nothing is connected */
+ if (slot->ops.get_presence_state)
+ slot->ops.get_presence_state(slot, &presence);
+ if (!presence) {
+ prlog(PR_DEBUG, "%016llx FRESET: No device\n",
+ slot->id);
+ return OPAL_SUCCESS;
+ }
+
+ /* Prepare link down */
+ if (slot->ops.prepare_link_change) {
+ prlog(PR_DEBUG, "%016llx FRESET: Prepares link down\n",
+ slot->id);
+ slot->ops.prepare_link_change(slot, false);
+ }
+
+ /* Send I2C request */
+ prlog(PR_DEBUG, "%016llx FRESET: Check power state\n",
+ slot->id);
+ plat_slot->next_state =
+ FIRENZE_PCI_SLOT_FRESET_POWER_STATE;
+ plat_slot->req->op = SMBUS_READ;
+ slot->retries = FIRENZE_PCI_SLOT_RETRIES;
+ pci_slot_set_state(slot,
+ FIRENZE_PCI_SLOT_FRESET_WAIT_RSP);
+ if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
+ plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT;
+ else
+ plat_slot->req->timeout = 0ul;
+ i2c_queue_req(plat_slot->req);
+ return pci_slot_set_sm_timeout(slot,
+ msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
+ case FIRENZE_PCI_SLOT_FRESET_WAIT_RSP:
+ if (slot->retries-- == 0) {
+ prlog(PR_DEBUG, "%016llx FRESET: Timeout waiting for %08x\n",
+ slot->id, plat_slot->next_state);
+ goto out;
+ }
+
+ check_timers(false);
+ return pci_slot_set_sm_timeout(slot,
+ msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
+ case FIRENZE_PCI_SLOT_FRESET_DELAY:
+ prlog(PR_DEBUG, "%016llx FRESET: Delay %dms on I2C completion\n",
+ slot->id, FIRENZE_PCI_SLOT_DELAY);
+ pci_slot_set_state(slot, plat_slot->next_state);
+ return pci_slot_set_sm_timeout(slot,
+ msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
+ case FIRENZE_PCI_SLOT_FRESET_POWER_STATE:
+ /* Update last power status */
+ pval = (uint8_t *)(plat_slot->req->rw_buf);
+ *plat_slot->power_status = *pval;
+
+ /* Power is on, turn it off */
+ if (((*pval) & plat_slot->power_mask) == plat_slot->power_on) {
+ prlog(PR_DEBUG, "%016llx FRESET: Power (%02x) on, turn off\n",
+ slot->id, *pval);
+ (*pval) &= ~plat_slot->power_mask;
+ (*pval) |= plat_slot->power_off;
+ plat_slot->req->op = SMBUS_WRITE;
+ slot->retries = FIRENZE_PCI_SLOT_RETRIES;
+ plat_slot->next_state =
+ FIRENZE_PCI_SLOT_FRESET_POWER_OFF;
+ pci_slot_set_state(slot,
+ FIRENZE_PCI_SLOT_FRESET_WAIT_RSP);
+
+ if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
+ timeout = FIRENZE_PCI_I2C_TIMEOUT;
+ else
+ timeout = 0ul;
+ plat_slot->req->timeout = timeout;
+
+ i2c_queue_req(plat_slot->req);
+ return pci_slot_set_sm_timeout(slot,
+ msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
+ }
+
+ /* Power is off, turn it on */
+ /* Fallthrough */
+ case FIRENZE_PCI_SLOT_FRESET_POWER_OFF:
+ /* Update last power status */
+ pval = (uint8_t *)(plat_slot->req->rw_buf);
+ *plat_slot->power_status = *pval;
+
+ prlog(PR_DEBUG, "%016llx FRESET: Power (%02x) off, turn on\n",
+ slot->id, *pval);
+ (*pval) &= ~plat_slot->power_mask;
+ (*pval) |= plat_slot->power_on;
+ plat_slot->req->op = SMBUS_WRITE;
+ plat_slot->next_state =
+ FIRENZE_PCI_SLOT_FRESET_POWER_ON;
+ slot->retries = FIRENZE_PCI_SLOT_RETRIES;
+ pci_slot_set_state(slot,
+ FIRENZE_PCI_SLOT_FRESET_WAIT_RSP);
+
+ if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
+ plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT;
+ else
+ plat_slot->req->timeout = 0ul;
+ i2c_queue_req(plat_slot->req);
+ return pci_slot_set_sm_timeout(slot,
+ msecs_to_tb(FIRENZE_PCI_SLOT_DELAY));
+ case FIRENZE_PCI_SLOT_FRESET_POWER_ON:
+ /* Update last power status */
+ pval = (uint8_t *)(plat_slot->req->rw_buf);
+ *plat_slot->power_status = *pval;
+
+ pci_slot_set_state(slot, FIRENZE_PCI_SLOT_LINK_START);
+ return slot->ops.poll_link(slot);
+ default:
+ prlog(PR_DEBUG, "%016llx FRESET: Unexpected state %08x\n",
+ slot->id, slot->state);
+ }
+
+out:
+ pci_slot_set_state(slot, FIRENZE_PCI_SLOT_NORMAL);
+ return OPAL_HARDWARE;
+}
+
+static int64_t firenze_pci_slot_perst(struct pci_slot *slot)
+{
+ struct firenze_pci_slot *plat_slot = slot->data;
+ uint8_t presence = 1;
+ uint16_t ctrl;
+
+ switch (slot->state) {
+ case FIRENZE_PCI_SLOT_NORMAL:
+ case FIRENZE_PCI_SLOT_FRESET_START:
+ prlog(PR_DEBUG, "%016llx PERST: Starts\n",
+ slot->id);
+
+ /* Bail if nothing is connected */
+ if (slot->ops.get_presence_state)
+ slot->ops.get_presence_state(slot, &presence);
+ if (!presence) {
+ prlog(PR_DEBUG, "%016llx PERST: No device\n",
+ slot->id);
+ return OPAL_SUCCESS;
+ }
+
+ /* Prepare link down */
+ if (slot->ops.prepare_link_change) {
+ prlog(PR_DEBUG, "%016llx PERST: Prepare link down\n",
+ slot->id);
+ slot->ops.prepare_link_change(slot, false);
+ }
+
+ /* Assert PERST */
+ prlog(PR_DEBUG, "%016llx PERST: Assert\n",
+ slot->id);
+ pci_cfg_read16(slot->phb, slot->pd->bdfn,
+ plat_slot->perst_reg, &ctrl);
+ ctrl |= plat_slot->perst_bit;
+ pci_cfg_write16(slot->phb, slot->pd->bdfn,
+ plat_slot->perst_reg, ctrl);
+ pci_slot_set_state(slot,
+ FIRENZE_PCI_SLOT_PERST_DEASSERT);
+ return pci_slot_set_sm_timeout(slot, msecs_to_tb(250));
+ case FIRENZE_PCI_SLOT_PERST_DEASSERT:
+ /* Deassert PERST */
+ pci_cfg_read16(slot->phb, slot->pd->bdfn,
+ plat_slot->perst_reg, &ctrl);
+ ctrl &= ~plat_slot->perst_bit;
+ pci_cfg_write16(slot->phb, slot->pd->bdfn,
+ plat_slot->perst_reg, ctrl);
+ pci_slot_set_state(slot,
+ FIRENZE_PCI_SLOT_PERST_DELAY);
+ return pci_slot_set_sm_timeout(slot, msecs_to_tb(1500));
+ case FIRENZE_PCI_SLOT_PERST_DELAY:
+ pci_slot_set_state(slot, FIRENZE_PCI_SLOT_LINK_START);
+ return slot->ops.poll_link(slot);
+ default:
+ prlog(PR_DEBUG, "%016llx PERST: Unexpected state %08x\n",
+ slot->id, slot->state);
+ }
+
+ pci_slot_set_state(slot, FIRENZE_PCI_SLOT_NORMAL);
+ return OPAL_HARDWARE;
+}
+
+static int64_t firenze_pci_slot_get_power_state(struct pci_slot *slot,
+ uint8_t *val)
+{
+ if (slot->state != FIRENZE_PCI_SLOT_NORMAL)
+ {
+ /**
+ * @fwts-label FirenzePCISlotGPowerState
+ * @fwts-advice Unexpected state in the FIRENZE PCI Slot
+ * state machine. This could mean PCI is not functioning
+ * correctly.
+ */
+ prlog(PR_ERR, "%016llx GPOWER: Unexpected state %08x\n",
+ slot->id, slot->state);
+ }
+
+ *val = slot->power_state;
+ return OPAL_SUCCESS;
+}
+
+static int64_t firenze_pci_slot_set_power_state(struct pci_slot *slot,
+ uint8_t val)
+{
+ struct firenze_pci_slot *plat_slot = slot->data;
+ uint8_t *pval;
+
+ if (slot->state != FIRENZE_PCI_SLOT_NORMAL)
+ {
+ /**
+ * @fwts-label FirenzePCISlotSPowerState
+ * @fwts-advice Unexpected state in the FIRENZE PCI Slot
+ * state machine. This could mean PCI is not functioning
+ * correctly.
+ */
+ prlog(PR_ERR, "%016llx SPOWER: Unexpected state %08x\n",
+ slot->id, slot->state);
+ }
+
+ if (val != PCI_SLOT_POWER_OFF && val != PCI_SLOT_POWER_ON)
+ return OPAL_PARAMETER;
+
+ if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_ENFORCE) &&
+ slot->power_state == val)
+ return OPAL_SUCCESS;
+
+ /* Update with the requested power state and bail immediately when
+ * surprise hotplug is supported on the slot. It keeps the power
+ * supply to the slot on and it guarentees the link state change
+ * events will be raised properly during surprise hot add/remove.
+ */
+ if (!pci_slot_has_flags(slot, PCI_SLOT_FLAG_ENFORCE) &&
+ slot->surprise_pluggable) {
+ slot->power_state = val;
+ return OPAL_SUCCESS;
+ }
+
+ slot->power_state = val;
+ pci_slot_set_state(slot, FIRENZE_PCI_SLOT_SPOWER_START);
+
+ plat_slot->req->op = SMBUS_WRITE;
+ pval = (uint8_t *)plat_slot->req->rw_buf;
+ if (val == PCI_SLOT_POWER_ON) {
+ *pval = *plat_slot->power_status;
+ (*pval) &= ~plat_slot->power_mask;
+ (*pval) |= plat_slot->power_on;
+ } else {
+ *pval = *plat_slot->power_status;
+ (*pval) &= ~plat_slot->power_mask;
+ (*pval) |= plat_slot->power_off;
+ }
+
+ if (pci_slot_has_flags(slot, PCI_SLOT_FLAG_BOOTUP))
+ plat_slot->req->timeout = FIRENZE_PCI_I2C_TIMEOUT;
+ else
+ plat_slot->req->timeout = 0ul;
+ i2c_queue_req(plat_slot->req);
+
+ return OPAL_ASYNC_COMPLETION;
+}
+
+static struct i2c_bus *firenze_pci_find_i2c_bus(uint8_t chip,
+ uint8_t eng,
+ uint8_t port)
+{
+ struct dt_node *np, *child;
+ uint32_t reg;
+
+ /* Iterate I2C masters */
+ dt_for_each_compatible(dt_root, np, "ibm,power8-i2cm") {
+ if (!np->parent ||
+ !dt_node_is_compatible(np->parent, "ibm,power8-xscom"))
+ continue;
+
+ /* Check chip index */
+ reg = dt_prop_get_u32(np->parent, "ibm,chip-id");
+ if (reg != chip)
+ continue;
+
+ /* Check I2C master index */
+ reg = dt_prop_get_u32(np, "chip-engine#");
+ if (reg != eng)
+ continue;
+
+ /* Iterate I2C buses */
+ dt_for_each_child(np, child) {
+ if (!dt_node_is_compatible(child, "ibm,power8-i2c-port"))
+ continue;
+
+ /* Check I2C port index */
+ reg = dt_prop_get_u32(child, "reg");
+ if (reg != port)
+ continue;
+
+ reg = dt_prop_get_u32(child, "ibm,opal-id");
+ return i2c_find_bus_by_id(reg);
+ }
+ }
+
+ return NULL;
+}
+
+static int64_t firenze_pci_slot_fixup_one_reg(struct pci_slot *slot,
+ struct firenze_pci_slot_fixup_info *fixup)
+{
+ struct firenze_pci_slot *plat_slot = slot->data;
+ struct i2c_request req;
+ uint8_t buf;
+ int64_t rc;
+
+ /*
+ * Fill out our own request structure since we don't want to invoke the
+ * normal completion handler.
+ */
+ memset(&req, 0, sizeof(req));
+ req.dev_addr = plat_slot->req->dev_addr;
+ req.bus = plat_slot->req->bus;
+ req.offset = fixup->reg;
+ req.offset_bytes = 1;
+ req.rw_buf = &buf;
+ req.rw_len = 1;
+ req.timeout = FIRENZE_PCI_I2C_TIMEOUT;
+
+ req.op = SMBUS_WRITE;
+ buf = fixup->val;
+ rc = i2c_request_sync(&req);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Check the register fixup has been applied. It's not the end of the
+ * world we don't, but eh...
+ */
+ req.op = SMBUS_READ;
+ rc = i2c_request_sync(&req);
+ if (rc == OPAL_SUCCESS && buf != fixup->val) {
+ prlog(PR_ERR, "Error verifying fixup [%s] - (%02x, %02x, %02x)\n",
+ fixup->label, fixup->reg, fixup->val, buf);
+ }
+
+ return rc;
+}
+
+static void firenze_pci_slot_fixup(struct pci_slot *slot,
+ struct firenze_pci_slot_info *info)
+{
+ int64_t rc, i, applied = 0;
+ const uint32_t *p;
+ uint64_t id;
+
+ p = dt_prop_get_def(dt_root, "ibm,vpd-lx-info", NULL);
+ id = p ? (((uint64_t)p[1] << 32) | p[2]) : 0ul;
+ if (id != LX_VPD_2S4U_BACKPLANE &&
+ id != LX_VPD_1S4U_BACKPLANE)
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(firenze_pci_slot_fixup_tbl); i++) {
+ struct firenze_pci_slot_fixup_info *fixup =
+ &firenze_pci_slot_fixup_tbl[i];
+
+ if (strcmp(info->label, fixup->label))
+ continue;
+
+ rc = firenze_pci_slot_fixup_one_reg(slot, fixup);
+ if (rc) {
+ prlog(PR_ERR, "I2C error (%lld) applying fixup [%s] - (%02x, %02x)\n",
+ rc, fixup->label, fixup->reg, fixup->val);
+ return;
+ }
+
+ applied++;
+ }
+
+ if (applied)
+ prlog(PR_INFO, "Applied %lld fixups for [%s]\n",
+ applied, info->label);
+}
+
+static void firenze_pci_setup_power_mgt(struct pci_slot *slot,
+ struct firenze_pci_slot *plat_slot,
+ struct firenze_pci_slot_info *info)
+{
+ plat_slot->i2c_bus = firenze_pci_find_i2c_bus(info->chip_id,
+ info->master_id,
+ info->port_id);
+ if (!plat_slot->i2c_bus)
+ return;
+
+ plat_slot->req = zalloc(sizeof(*plat_slot->req));
+ if (!plat_slot->req)
+ return;
+
+ plat_slot->req->dev_addr = info->slave_addr;
+ plat_slot->req->offset_bytes = 1;
+ plat_slot->req->rw_buf = plat_slot->i2c_rw_buf;
+ plat_slot->req->rw_len = 1;
+ plat_slot->req->completion = firenze_i2c_req_done;
+ plat_slot->req->user_data = slot;
+ plat_slot->req->bus = plat_slot->i2c_bus;
+
+ firenze_pci_slot_fixup(slot, info);
+
+ /*
+ * For all slots, the register used to change the power state is
+ * always 0x69. It could have been set to something else in the
+ * above fixup. Lets fix it to 0x69 here.
+ *
+ * The power states of two slots are controlled by one register.
+ * This means two slots have to share data buffer for power states,
+ * which are tracked by struct firenze_pci_slot_info::power_status.
+ * With it, we can avoid affecting slot#B's power state when trying
+ * to adjust that on slot#A. Also, the initial power states for all
+ * slots are assumed to be PCI_SLOT_POWER_ON.
+ */
+ plat_slot->req->offset = 0x69;
+ plat_slot->power_status = &firenze_pci_slots[info->buddy].power_status;
+ switch (info->channel) {
+ case 0:
+ plat_slot->power_mask = 0x33;
+ plat_slot->power_on = 0x22;
+ plat_slot->power_off = 0;
+ break;
+ case 1:
+ plat_slot->power_status = &firenze_pci_slots[info->buddy].power_status;
+ plat_slot->power_mask = 0xcc;
+ plat_slot->power_on = 0x88;
+ plat_slot->power_off = 0;
+ break;
+ default:
+ prlog(PR_ERR, "%016llx: Invalid channel %d\n",
+ slot->id, info->channel);
+ }
+}
+
+static void firenze_pci_slot_init(struct pci_slot *slot)
+{
+ struct lxvpd_pci_slot *s = slot->data;
+ struct firenze_pci_slot *plat_slot = slot->data;
+ struct firenze_pci_slot_info *info = NULL;
+ uint32_t vdid;
+ int i;
+
+ /* Init the slot info from the LXVPD */
+ slot->ops.add_properties = lxvpd_add_slot_properties;
+
+ /* Search for power control information in the per-system table */
+ for (i = 0; i < ARRAY_SIZE(firenze_pci_slots); i++) {
+ if (firenze_pci_slots[i].index == s->slot_index &&
+ !strcmp(firenze_pci_slots[i].label, s->label)) {
+ info = &firenze_pci_slots[i];
+ break;
+ }
+ }
+ if (!info)
+ return;
+
+ /* Search I2C bus for external power mgt */
+ if (slot->power_ctl)
+ firenze_pci_setup_power_mgt(slot, plat_slot, info);
+
+ /*
+ * If the slot has external power logic, to override the
+ * default power management methods. Because of the bad
+ * I2C design, the API supplied by I2C is really hard to
+ * be utilized. To figure out power status retrival or
+ * configuration after we have a blocking API for that.
+ */
+ if (plat_slot->req) {
+ slot->ops.freset = firenze_pci_slot_freset;
+ slot->ops.get_power_state = firenze_pci_slot_get_power_state;
+ slot->ops.set_power_state = firenze_pci_slot_set_power_state;
+ prlog(PR_DEBUG, "%016llx: External power mgt initialized\n",
+ slot->id);
+ } else if (info->inband_perst) {
+ /*
+ * For PLX downstream ports, PCI config register can be
+ * leveraged to do PERST. If the slot doesn't have external
+ * power management stuff, lets try to stick to the PERST
+ * logic if applicable
+ */
+ if (slot->pd->dev_type == PCIE_TYPE_SWITCH_DNPORT) {
+ pci_cfg_read32(slot->phb, slot->pd->bdfn,
+ PCI_CFG_VENDOR_ID, &vdid);
+ switch (vdid) {
+ case 0x873210b5: /* PLX8732 */
+ case 0x874810b5: /* PLX8748 */
+ plat_slot->perst_reg = 0x80;
+ plat_slot->perst_bit = 0x0400;
+ slot->ops.freset = firenze_pci_slot_perst;
+ break;
+ }
+ }
+ }
+}
+
+void firenze_pci_setup_phb(struct phb *phb, unsigned int index)
+{
+ uint32_t hub_id;
+
+ /* Grab Hub ID used to parse VPDs */
+ hub_id = dt_prop_get_u32_def(phb->dt_node, "ibm,hub-id", 0);
+
+ /* Process the pcie slot entries from the lx vpd lid */
+ lxvpd_process_slot_entries(phb, dt_root, hub_id,
+ index, sizeof(struct firenze_pci_slot));
+}
+
+void firenze_pci_get_slot_info(struct phb *phb, struct pci_device *pd)
+{
+ struct pci_slot *slot;
+ struct lxvpd_pci_slot *s;
+
+ /* Prepare the PCI inventory */
+ firenze_pci_add_inventory(phb, pd);
+
+ if (pd->dev_type != PCIE_TYPE_ROOT_PORT &&
+ pd->dev_type != PCIE_TYPE_SWITCH_UPPORT &&
+ pd->dev_type != PCIE_TYPE_SWITCH_DNPORT &&
+ pd->dev_type != PCIE_TYPE_PCIE_TO_PCIX)
+ return;
+
+ /* Create PCIe slot */
+ slot = pcie_slot_create(phb, pd);
+ if (!slot)
+ return;
+
+ /* Root complex inherits methods from PHB slot */
+ if (!pd->parent && phb->slot)
+ memcpy(&slot->ops, &phb->slot->ops, sizeof(struct pci_slot_ops));
+
+ /* Patch PCIe slot */
+ s = lxvpd_get_slot(slot);
+ if (s) {
+ lxvpd_extract_info(slot, s);
+ firenze_pci_slot_init(slot);
+ }
+}
+
+void firenze_pci_add_loc_code(struct dt_node *np, struct pci_device *pd)
+{
+ struct dt_node *p;
+ const char *blcode = NULL;
+ char *lcode;
+ uint32_t class_code;
+ uint8_t class,sub;
+ uint8_t pos, len;
+
+
+ /*
+ * prefer fully-qualified slot-location-code, walk-up parent tree
+ * to find one
+ */
+ for (p = np->parent; p; p = p->parent) {
+ blcode = dt_prop_get_def(p, "ibm,slot-location-code", NULL);
+ if (blcode)
+ break;
+ }
+
+ /* try the node itself if none is found */
+ if (!blcode)
+ blcode = dt_prop_get_def(np, "ibm,slot-location-code", NULL);
+
+ if (!blcode) {
+ /* still not found, fall back to ibm,loc-code */
+
+ for (p = np->parent; p; p = p->parent) {
+ blcode = dt_prop_get_def(p, "ibm,loc-code", NULL);
+ if (blcode)
+ break;
+ }
+ }
+
+ if (!blcode) {
+ prlog(PR_ERR,
+ "No suitable location code to add for device PHB#%04x:%02x:%02x.%x\n",
+ pd->phb->opal_id, PCI_BUS_NUM(pd->bdfn),
+ PCI_DEV(pd->bdfn), PCI_FUNC(pd->bdfn));
+ return;
+ }
+
+ /* ethernet devices get port codes */
+ class_code = dt_prop_get_u32(np, "class-code");
+ class = class_code >> 16;
+ sub = (class_code >> 8) & 0xff;
+
+ if (class == 0x02 && sub == 0x00) {
+ /* There's usually several spaces at the end of the property.
+ Test for, but don't rely on, that being the case */
+ len = strlen(blcode);
+ for (pos = 0; pos < len; pos++)
+ if (blcode[pos] == ' ') break;
+ if (pos + 3 < len)
+ lcode = strdup(blcode);
+ else {
+ lcode = malloc(pos + 3);
+ memcpy(lcode, blcode, len);
+ }
+ lcode[pos++] = '-';
+ lcode[pos++] = 'T';
+ lcode[pos++] = (char)PCI_FUNC(pd->bdfn) + '1';
+ lcode[pos++] = '\0';
+ dt_add_property_string(np, "ibm,loc-code", lcode);
+ free(lcode);
+ } else {
+ dt_add_property_string(np, "ibm,loc-code", blcode);
+ }
+}