aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/platforms/ibm-fsp
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/platforms/ibm-fsp')
-rw-r--r--roms/skiboot/platforms/ibm-fsp/Makefile.inc9
-rw-r--r--roms/skiboot/platforms/ibm-fsp/common.c279
-rw-r--r--roms/skiboot/platforms/ibm-fsp/firenze-pci.c1044
-rw-r--r--roms/skiboot/platforms/ibm-fsp/firenze.c223
-rw-r--r--roms/skiboot/platforms/ibm-fsp/fsp-vpd.c152
-rw-r--r--roms/skiboot/platforms/ibm-fsp/hostservices.c912
-rw-r--r--roms/skiboot/platforms/ibm-fsp/ibm-fsp.h45
-rw-r--r--roms/skiboot/platforms/ibm-fsp/lxvpd.c371
-rw-r--r--roms/skiboot/platforms/ibm-fsp/lxvpd.h164
-rw-r--r--roms/skiboot/platforms/ibm-fsp/zz.c213
10 files changed, 3412 insertions, 0 deletions
diff --git a/roms/skiboot/platforms/ibm-fsp/Makefile.inc b/roms/skiboot/platforms/ibm-fsp/Makefile.inc
new file mode 100644
index 000000000..8883f09c1
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/Makefile.inc
@@ -0,0 +1,9 @@
+SUBDIRS += $(PLATDIR)/ibm-fsp
+
+IBM_FSP_OBJS = common.o lxvpd.o hostservices.o fsp-vpd.o \
+ firenze.o firenze-pci.o zz.o
+IBM_FSP = $(PLATDIR)/ibm-fsp/built-in.a
+
+ifeq ($(CONFIG_FSP),1)
+$(IBM_FSP): $(IBM_FSP_OBJS:%=$(PLATDIR)/ibm-fsp/%)
+endif
diff --git a/roms/skiboot/platforms/ibm-fsp/common.c b/roms/skiboot/platforms/ibm-fsp/common.c
new file mode 100644
index 000000000..4a723b25b
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/common.c
@@ -0,0 +1,279 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+
+#include <skiboot.h>
+#include <fsp.h>
+#include <fsp-sysparam.h>
+#include <opal.h>
+#include <console.h>
+#include <hostservices.h>
+#include <ipmi.h>
+#include <debug_descriptor.h>
+#include <occ.h>
+
+#include "ibm-fsp.h"
+
+static void map_debug_areas(void)
+{
+ uint64_t t, i;
+
+ /* Our memcons is in a section of its own and already
+ * aligned to 4K. The buffers are mapped as a whole
+ */
+ fsp_tce_map(PSI_DMA_MEMCONS, &memcons, 0x1000);
+ fsp_tce_map(PSI_DMA_LOG_BUF, (void*)INMEM_CON_START, INMEM_CON_LEN);
+
+ debug_descriptor.memcons_tce = cpu_to_be32(PSI_DMA_MEMCONS);
+ t = be64_to_cpu(memcons.obuf_phys) - INMEM_CON_START + PSI_DMA_LOG_BUF;
+ debug_descriptor.memcons_obuf_tce = cpu_to_be32(t);
+ t = be64_to_cpu(memcons.ibuf_phys) - INMEM_CON_START + PSI_DMA_LOG_BUF;
+ debug_descriptor.memcons_ibuf_tce = cpu_to_be32(t);
+
+ t = PSI_DMA_TRACE_BASE;
+ for (i = 0; i < be32_to_cpu(debug_descriptor.num_traces); i++) {
+ /*
+ * Trace buffers are misaligned by 0x10 due to the lock
+ * in the trace structure, and their size is also not
+ * completely aligned. (They are allocated so that with
+ * the lock included, they do cover entire multiple of
+ * a 4K page however).
+ *
+ * This means we have to map the lock into the TCEs and
+ * align everything. Not a huge deal but needs to be
+ * taken into account.
+ *
+ * Note: Maybe we should map them read-only...
+ */
+ uint64_t tstart, tend, toff, tsize;
+ uint64_t trace_phys = be64_to_cpu(debug_descriptor.trace_phys[i]);
+ uint32_t trace_size = be32_to_cpu(debug_descriptor.trace_size[i]);
+
+ tstart = ALIGN_DOWN(trace_phys, 0x1000);
+ tend = ALIGN_UP(trace_phys + trace_size, 0x1000);
+ toff = trace_phys - tstart;
+ tsize = tend - tstart;
+
+ fsp_tce_map(t, (void *)tstart, tsize);
+ debug_descriptor.trace_tce[i] = cpu_to_be32(t + toff);
+ t += tsize;
+ }
+}
+
+
+void ibm_fsp_init(void)
+{
+ /* Early initializations of the FSP interface */
+ fsp_init();
+ map_debug_areas();
+ fsp_sysparam_init();
+
+ /* Get ready to receive E0 class messages. We need to respond
+ * to some of these for the init sequence to make forward progress
+ */
+ fsp_console_preinit();
+
+ /* Get ready to receive OCC related messages */
+ occ_fsp_init();
+
+ /* Get ready to receive Memory [Un]corretable Error messages. */
+ fsp_memory_err_init();
+
+ /* Initialize elog access */
+ fsp_elog_read_init();
+ fsp_elog_write_init();
+
+ /* Initiate dump service */
+ fsp_dump_init();
+
+ /* Start FSP/HV state controller & perform OPL */
+ fsp_opl();
+
+ /* Preload hostservices lids */
+ hservices_lid_preload();
+
+ /* Initialize SP attention area */
+ fsp_attn_init();
+
+ /* Initialize monitoring of TOD topology change event notification */
+ fsp_chiptod_init();
+
+ /* Send MDST table notification to FSP */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0000);
+ fsp_mdst_table_init();
+
+ /* Initialize the panel */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0001);
+ fsp_oppanel_init();
+
+ /* Start the surveillance process */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0002);
+ fsp_init_surveillance();
+
+ /* IPMI */
+ fsp_ipmi_init();
+ ipmi_opal_init();
+
+ /* Initialize sensor access */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0003);
+ fsp_init_sensor();
+
+ /* LED */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0004);
+ fsp_led_init();
+
+ /* Monitor for DIAG events */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0005);
+ fsp_init_diag();
+
+ /* Finish initializing the console */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0006);
+ fsp_console_init();
+
+ /* Read our initial RTC value */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0008);
+ fsp_rtc_init();
+
+ /* Initialize code update access */
+ op_display(OP_LOG, OP_MOD_INIT, 0x0009);
+ fsp_code_update_init();
+
+ /* EPOW */
+ op_display(OP_LOG, OP_MOD_INIT, 0x000A);
+ fsp_epow_init();
+
+ /* EPOW */
+ op_display(OP_LOG, OP_MOD_INIT, 0x000B);
+ fsp_dpo_init();
+
+ /* Setup console */
+ if (fsp_present())
+ fsp_console_add_nodes();
+
+ if (proc_gen >= proc_gen_p9)
+ prd_init();
+
+ preload_io_vpd();
+}
+
+void ibm_fsp_finalise_dt(bool is_reboot)
+{
+ if (is_reboot)
+ return;
+
+ /*
+ * LED related SPCN commands might take a while to
+ * complete. Call this as late as possible to
+ * ensure we have all the LED information.
+ */
+ create_led_device_nodes();
+
+ /*
+ * OCC takes few secs to boot. Call this as late as
+ * as possible to avoid delay.
+ */
+ occ_pstates_init();
+
+ /* Wait for FW VPD data read to complete */
+ fsp_code_update_wait_vpd(true);
+
+ fsp_console_select_stdout();
+}
+
+void ibm_fsp_exit(void)
+{
+ op_panel_disable_src_echo();
+
+ /* Clear SRCs on the op-panel when Linux starts */
+ op_panel_clear_src();
+}
+
+int64_t ibm_fsp_cec_reboot(void)
+{
+ uint32_t cmd = FSP_CMD_REBOOT;
+
+ if (!fsp_present())
+ return OPAL_UNSUPPORTED;
+
+ /* Flash new firmware */
+ if (fsp_flash_term_hook &&
+ fsp_flash_term_hook() == OPAL_SUCCESS)
+ cmd = FSP_CMD_DEEP_REBOOT;
+
+ /* Clear flash hook */
+ fsp_flash_term_hook = NULL;
+
+ printf("FSP: Sending 0x%02x reboot command to FSP...\n", cmd);
+
+ /* If that failed, talk to the FSP */
+ if (fsp_sync_msg(fsp_mkmsg(cmd, 0), true))
+ return OPAL_BUSY_EVENT;
+
+ return OPAL_SUCCESS;
+}
+
+int64_t ibm_fsp_cec_power_down(uint64_t request)
+{
+ /* Request is:
+ *
+ * 0 = normal
+ * 1 = immediate
+ * (we do not allow 2 for "pci cfg reset" just yet)
+ */
+
+ if (request !=0 && request != 1)
+ return OPAL_PARAMETER;
+
+ if (!fsp_present())
+ return OPAL_UNSUPPORTED;
+
+ /* Flash new firmware */
+ if (fsp_flash_term_hook)
+ fsp_flash_term_hook();
+
+ /* Clear flash hook */
+ fsp_flash_term_hook = NULL;
+
+ printf("FSP: Sending shutdown command to FSP...\n");
+
+ if (fsp_sync_msg(fsp_mkmsg(FSP_CMD_POWERDOWN_NORM, 1, request), true))
+ return OPAL_BUSY_EVENT;
+
+ fsp_reset_links();
+ return OPAL_SUCCESS;
+}
+
+int64_t ibm_fsp_sensor_read(uint32_t sensor_hndl, int token,
+ __be64 *sensor_data)
+{
+ return fsp_opal_read_sensor(sensor_hndl, token, sensor_data);
+}
+
+int __attrconst fsp_heartbeat_time(void)
+{
+ /* Same as core/timer.c HEARTBEAT_DEFAULT_MS * 10 */
+ return 200 * 10;
+}
+
+static void fsp_psihb_interrupt(void)
+{
+ /* Poll the console buffers on any interrupt since we don't
+ * get send notifications
+ */
+ fsp_console_poll(NULL);
+}
+
+struct platform_psi fsp_platform_psi = {
+ .psihb_interrupt = fsp_psihb_interrupt,
+ .link_established = fsp_reinit_fsp,
+ .fsp_interrupt = fsp_interrupt,
+};
+
+struct platform_prd fsp_platform_prd = {
+ .msg_response = hservice_hbrt_msg_response,
+ .send_error_log = hservice_send_error_log,
+ .send_hbrt_msg = hservice_send_hbrt_msg,
+ .wakeup = hservice_wakeup,
+ .fsp_occ_load_start_status = fsp_occ_load_start_status,
+ .fsp_occ_reset_status = fsp_occ_reset_status,
+};
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);
+ }
+}
diff --git a/roms/skiboot/platforms/ibm-fsp/firenze.c b/roms/skiboot/platforms/ibm-fsp/firenze.c
new file mode 100644
index 000000000..887a9c0ac
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/firenze.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#include <skiboot.h>
+#include <device.h>
+#include <fsp.h>
+#include <pci.h>
+#include <pci-cfg.h>
+#include <chip.h>
+#include <i2c.h>
+#include <timebase.h>
+#include <hostservices.h>
+
+#include "ibm-fsp.h"
+#include "lxvpd.h"
+
+static struct dt_node *dt_create_i2c_master(struct dt_node *n, uint32_t eng_id)
+{
+ struct dt_node *i2cm;
+ uint64_t freq;
+ uint32_t clock;
+
+ /* Each master registers set is of length 0x20 */
+ i2cm = dt_new_addr(n, "i2cm", 0xa0000 + eng_id * 0x20);
+ if (!i2cm)
+ return NULL;
+
+ dt_add_property_string(i2cm, "compatible",
+ "ibm,power8-i2cm");
+ dt_add_property_cells(i2cm, "reg", 0xa0000 + eng_id * 0x20,
+ 0x20);
+ dt_add_property_cells(i2cm, "chip-engine#", eng_id);
+ dt_add_property_cells(i2cm, "#address-cells", 1);
+ dt_add_property_cells(i2cm, "#size-cells", 0);
+
+ /* Derive the clock source frequency */
+ freq = dt_prop_get_u64_def(n, "bus-frequency", 0);
+ clock = (u32)(freq / 4);
+ if (clock)
+ dt_add_property_cells(i2cm, "clock-frequency", clock);
+ else
+ dt_add_property_cells(i2cm, "clock-frequency", 125000000);
+ return i2cm;
+}
+
+static struct dt_node *dt_create_i2c_bus(struct dt_node *i2cm,
+ const char *port_name, uint32_t port_id)
+{
+ static struct dt_node *port;
+
+ port = dt_new_addr(i2cm, "i2c-bus", port_id);
+ if (!port)
+ return NULL;
+
+ dt_add_property_strings(port, "compatible", "ibm,power8-i2c-port",
+ "ibm,opal-i2c");
+ dt_add_property_string(port, "ibm,port-name", port_name);
+ dt_add_property_cells(port, "reg", port_id);
+ dt_add_property_cells(port, "bus-frequency", 400000);
+ dt_add_property_cells(port, "#address-cells", 1);
+ dt_add_property_cells(port, "#size-cells", 0);
+
+ return port;
+}
+
+static struct dt_node *dt_create_i2c_device(struct dt_node *bus, uint8_t addr,
+ const char *name, const char *compat,
+ const char *label)
+{
+ struct dt_node *dev;
+
+ dev = dt_new_addr(bus, name, addr);
+ if (!dev)
+ return NULL;
+
+ dt_add_property_string(dev, "compatible", compat);
+ dt_add_property_string(dev, "label", label);
+ dt_add_property_cells(dev, "reg", addr);
+ dt_add_property_string(dev, "status", "reserved");
+
+ return dev;
+}
+
+static void firenze_dt_fixup_i2cm(void)
+{
+ struct dt_node *master, *bus, *dev;
+ struct proc_chip *c;
+ const uint32_t *p;
+ char name[32];
+ uint64_t lx;
+
+ if (dt_find_compatible_node(dt_root, NULL, "ibm,power8-i2cm"))
+ return;
+
+ p = dt_prop_get_def(dt_root, "ibm,vpd-lx-info", NULL);
+ if (!p)
+ return;
+
+ lx = ((uint64_t)p[1] << 32) | p[2];
+
+ switch (lx) {
+ case LX_VPD_2S4U_BACKPLANE:
+ case LX_VPD_2S2U_BACKPLANE:
+ case LX_VPD_SHARK_BACKPLANE: /* XXX confirm ? */
+ /* i2c nodes on chip 0x10 */
+ c = get_chip(0x10);
+ if (c) {
+ /* Engine 1 */
+ master = dt_create_i2c_master(c->devnode, 1);
+ assert(master);
+ snprintf(name, sizeof(name), "p8_%08x_e%dp%d", c->id, 1, 0);
+ bus = dt_create_i2c_bus(master, name, 0);
+ assert(bus);
+ dev = dt_create_i2c_device(bus, 0x39, "power-control",
+ "maxim,5961", "pcie-hotplug");
+ assert(dev);
+ dt_add_property_strings(dev, "target-list", "slot-C4",
+ "slot-C5");
+
+ dev = dt_create_i2c_device(bus, 0x3a, "power-control",
+ "maxim,5961", "pcie-hotplug");
+ assert(dev);
+ dt_add_property_strings(dev, "target-list", "slot-C2",
+ "slot-C3");
+ } else {
+ prlog(PR_INFO, "PLAT: Chip not found for the id 0x10\n");
+ }
+
+ /* Fall through */
+ case LX_VPD_1S4U_BACKPLANE:
+ case LX_VPD_1S2U_BACKPLANE:
+ /* i2c nodes on chip 0 */
+ c = get_chip(0);
+ if (!c) {
+ prlog(PR_INFO, "PLAT: Chip not found for the id 0x0\n");
+ break;
+ }
+
+ /* Engine 1*/
+ master = dt_create_i2c_master(c->devnode, 1);
+ assert(master);
+ snprintf(name, sizeof(name), "p8_%08x_e%dp%d", c->id, 1, 0);
+ bus = dt_create_i2c_bus(master, name, 0);
+ assert(bus);
+ dev = dt_create_i2c_device(bus, 0x32, "power-control",
+ "maxim,5961", "pcie-hotplug");
+ assert(dev);
+ dt_add_property_strings(dev, "target-list", "slot-C10", "slot-C11");
+
+ dev = dt_create_i2c_device(bus, 0x35, "power-control",
+ "maxim,5961", "pcie-hotplug");
+ assert(dev);
+ dt_add_property_strings(dev, "target-list", "slot-C6", "slot-C7");
+
+ dev = dt_create_i2c_device(bus, 0x36, "power-control",
+ "maxim,5961", "pcie-hotplug");
+ assert(dev);
+ dt_add_property_strings(dev, "target-list", "slot-C8", "slot-C9");
+
+ dev = dt_create_i2c_device(bus, 0x39, "power-control", "maxim,5961",
+ "pcie-hotplug");
+ assert(dev);
+ dt_add_property_strings(dev, "target-list", "slot-C12");
+ break;
+ default:
+ break;
+ }
+}
+
+static bool firenze_probe(void)
+{
+ if (!dt_node_is_compatible(dt_root, "ibm,firenze"))
+ return false;
+
+ firenze_dt_fixup_i2cm();
+
+ return true;
+}
+
+static uint32_t ibm_fsp_occ_timeout(void)
+{
+ /* Use a fixed 60s value for now */
+ return 60;
+}
+
+static void firenze_init(void)
+{
+ /* We call hservices_init to relocate the hbrt image now, as the FSP
+ * may request an OCC load any time after ibm_fsp_init.
+ */
+ hservices_init();
+
+ ibm_fsp_init();
+}
+
+DECLARE_PLATFORM(firenze) = {
+ .name = "Firenze",
+ .psi = &fsp_platform_psi,
+ .prd = &fsp_platform_prd,
+ .probe = firenze_probe,
+ .init = firenze_init,
+ .fast_reboot_init = fsp_console_reset,
+ .finalise_dt = ibm_fsp_finalise_dt,
+ .exit = ibm_fsp_exit,
+ .cec_power_down = ibm_fsp_cec_power_down,
+ .cec_reboot = ibm_fsp_cec_reboot,
+ .pci_setup_phb = firenze_pci_setup_phb,
+ .pci_get_slot_info = firenze_pci_get_slot_info,
+ .pci_add_loc_code = firenze_pci_add_loc_code,
+ .pci_probe_complete = firenze_pci_send_inventory,
+ .nvram_info = fsp_nvram_info,
+ .nvram_start_read = fsp_nvram_start_read,
+ .nvram_write = fsp_nvram_write,
+ .occ_timeout = ibm_fsp_occ_timeout,
+ .elog_commit = elog_fsp_commit,
+ .start_preload_resource = fsp_start_preload_resource,
+ .resource_loaded = fsp_resource_loaded,
+ .sensor_read = ibm_fsp_sensor_read,
+ .terminate = ibm_fsp_terminate,
+ .op_display = fsp_op_display,
+ .vpd_iohub_load = vpd_iohub_load,
+ .heartbeat_time = fsp_heartbeat_time,
+};
diff --git a/roms/skiboot/platforms/ibm-fsp/fsp-vpd.c b/roms/skiboot/platforms/ibm-fsp/fsp-vpd.c
new file mode 100644
index 000000000..52d2c8255
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/fsp-vpd.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#include <skiboot.h>
+#include <vpd.h>
+#include <string.h>
+#include <fsp.h>
+#include <device.h>
+#include "ibm-fsp.h"
+
+static void *vpd_lid;
+static size_t vpd_lid_size;
+static uint32_t vpd_lid_no;
+
+
+void vpd_iohub_load(struct dt_node *hub_node)
+{
+ uint8_t record[4] = { 'L','X','R','0' }; /* not null terminated */
+ const void *valid_lx;
+ uint8_t lx_size;
+ int r;
+ const uint32_t *p;
+ const uint8_t *lx;
+ unsigned int lxrn;
+
+ if (!fsp_present())
+ return;
+
+ p = dt_prop_get_def(hub_node, "ibm,vpd-lx-info", NULL);
+ if (!p)
+ return;
+
+ lxrn = p[0];
+ lx = (const char *)&p[1];
+
+ /* verify the lid preload has started */
+ if (!vpd_lid || !vpd_lid_no) {
+ prlog(PR_WARNING, "VPD: WARNING: Unable to load VPD lid");
+ return;
+ }
+
+ r = fsp_wait_lid_loaded(vpd_lid_no);
+
+ if (r)
+ goto fail;
+
+ /* Validate it */
+ if (lxrn < 9)
+ record[3] = '0' + lxrn;
+ else
+ memcpy(record, "VINI", 4);
+
+ valid_lx = vpd_find(vpd_lid, vpd_lid_size, record, "LX", &lx_size);
+ if (!valid_lx || lx_size != 8) {
+ prerror("VPD: Cannot find validation LX record\n");
+ goto fail;
+ }
+ if (memcmp(valid_lx, lx, 8) != 0) {
+ prerror("VPD: LX record mismatch !\n");
+ goto fail;
+ }
+
+ printf("VPD: Loaded %zu bytes\n", vpd_lid_size);
+
+ dt_add_property(hub_node, "ibm,io-vpd", vpd_lid, vpd_lid_size);
+ free(vpd_lid);
+ return;
+
+fail:
+ free(vpd_lid);
+ vpd_lid = NULL;
+ prerror("VPD: Failed to load VPD LID\n");
+ return;
+}
+
+/* Helper to load a VPD LID. Pass a ptr to the corresponding LX keyword */
+static void *vpd_lid_preload(const uint8_t *lx)
+{
+ int rc;
+
+ if (!fsp_present())
+ return NULL;
+
+ /* Now this is a guess game as we don't have the info from the
+ * pHyp folks. But basically, it seems to boil down to loading
+ * a LID whose name is 0x80e000yy where yy is the last 2 digits
+ * of the LX record in hex.
+ *
+ * [ Correction: After a chat with some folks, it looks like it's
+ * actually 4 digits, though the lid number is limited to fff
+ * so we weren't far off. ]
+ *
+ * For safety, we look for a matching LX record in an LXRn
+ * (n = lxrn argument) or in VINI if lxrn=0xff
+ */
+ vpd_lid_no = 0x80e00000 | ((lx[6] & 0xf) << 8) | lx[7];
+
+ /* We don't quite know how to get to the LID directory so
+ * we don't know the size. Let's allocate 16K. All the VPD LIDs
+ * I've seen so far are much smaller.
+ */
+#define VPD_LID_MAX_SIZE 0x4000
+ vpd_lid = malloc(VPD_LID_MAX_SIZE);
+
+ if (!vpd_lid) {
+ prerror("VPD: Failed to allocate memory for LID\n");
+ return NULL;
+ }
+
+ /* Adjust LID number for flash side */
+ vpd_lid_no = fsp_adjust_lid_side(vpd_lid_no);
+ printf("VPD: Trying to load VPD LID 0x%08x...\n", vpd_lid_no);
+
+ vpd_lid_size = VPD_LID_MAX_SIZE;
+
+ /* Load it from the FSP */
+ rc = fsp_preload_lid(vpd_lid_no, vpd_lid, &vpd_lid_size);
+ if (rc) {
+ prerror("VPD: Error %d loading VPD LID\n", rc);
+ goto fail;
+ }
+
+ return vpd_lid;
+ fail:
+ free(vpd_lid);
+ return NULL;
+}
+
+void vpd_preload(struct dt_node *hub_node)
+{
+ const uint32_t *p;
+ const char *lxr;
+
+ p = dt_prop_get_def(hub_node, "ibm,vpd-lx-info", NULL);
+ if (!p)
+ return;
+
+ lxr = (const char *)&p[1];
+
+ vpd_lid = vpd_lid_preload(lxr);
+}
+
+void preload_io_vpd(void)
+{
+ const struct dt_property *prop;
+
+ prop = dt_find_property(dt_root, "ibm,io-vpd");
+ if (!prop) {
+ /* LX VPD Lid not already loaded */
+ vpd_preload(dt_root);
+ }
+}
diff --git a/roms/skiboot/platforms/ibm-fsp/hostservices.c b/roms/skiboot/platforms/ibm-fsp/hostservices.c
new file mode 100644
index 000000000..accc0989a
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/hostservices.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <lock.h>
+#include <device.h>
+#include <compiler.h>
+#include <hostservices.h>
+#include <mem_region.h>
+#include <xscom.h>
+#include <fsp.h>
+#include <chip.h>
+#include <console.h>
+#include <mem-map.h>
+#include <timebase.h>
+#include <occ.h>
+
+#define HOSTBOOT_RUNTIME_INTERFACE_VERSION 1
+
+struct host_interfaces {
+ /** Interface version. */
+ uint64_t interface_version;
+
+ /** Put a string to the console. */
+ void (*puts)(const char*);
+ /** Critical failure in runtime execution. */
+ void (*assert)(void);
+
+ /** OPTIONAL. Hint to environment that the page may be executed. */
+ int (*set_page_execute)(void*);
+
+ /** malloc */
+ void *(*malloc)(size_t);
+ /** free */
+ void (*free)(void*);
+ /** realloc */
+ void *(*realloc)(void*, size_t);
+
+ /** sendErrorLog
+ * @param[in] plid Platform Log identifier
+ * @param[in] data size in bytes
+ * @param[in] pointer to data
+ * @return 0 on success else error code
+ */
+ int (*send_error_log)(uint32_t,uint32_t,void *);
+
+ /** Scan communication read
+ * @param[in] chip_id (based on devtree defn)
+ * @param[in] address
+ * @param[in] pointer to 8-byte data buffer
+ * @return 0 on success else return code
+ */
+ int (*scom_read)(uint64_t, uint64_t, void*);
+
+ /** Scan communication write
+ * @param[in] chip_id (based on devtree defn)
+ * @param[in] address
+ * @param[in] pointer to 8-byte data buffer
+ * @return 0 on success else return code
+ */
+ int (*scom_write)(uint64_t, uint64_t, const void *);
+
+ /** lid_load
+ * Load a LID from PNOR, FSP, etc.
+ *
+ * @param[in] LID number.
+ * @param[out] Allocated buffer for LID.
+ * @param[out] Size of LID (in bytes).
+ *
+ * @return 0 on success, else RC.
+ */
+ int (*lid_load)(uint32_t lid, void **buf, size_t *len);
+
+ /** lid_unload
+ * Release memory from previously loaded LID.
+ *
+ * @param[in] Allocated buffer for LID to release.
+ *
+ * @return 0 on success, else RC.
+ */
+ int (*lid_unload)(void *buf);
+
+ /** Get the address of a reserved memory region by its devtree name.
+ *
+ * @param[in] Devtree name (ex. "ibm,hbrt-vpd-image")
+ * @return physical address of region (or NULL).
+ **/
+ uint64_t (*get_reserved_mem)(const char*);
+
+ /**
+ * @brief Force a core to be awake, or clear the force
+ * @param[in] i_core Core to wake up (pid)
+ * @param[in] i_mode 0=force awake
+ * 1=clear force
+ * 2=clear all previous forces
+ * @return rc non-zero on error
+ */
+ int (*wakeup)( uint32_t i_core, uint32_t i_mode );
+
+ /**
+ * @brief Delay/sleep for at least the time given
+ * @param[in] seconds
+ * @param[in] nano seconds
+ */
+ void (*nanosleep)(uint64_t i_seconds, uint64_t i_nano_seconds);
+
+ // Reserve some space for future growth.
+ void (*reserved[32])(void);
+};
+
+struct runtime_interfaces {
+ /** Interface version. */
+ uint64_t interface_version;
+
+ /** Execute CxxTests that may be contained in the image.
+ *
+ * @param[in] - Pointer to CxxTestStats structure for results reporting.
+ */
+ void (*cxxtestExecute)(void *);
+ /** Get a list of lids numbers of the lids known to HostBoot
+ *
+ * @param[out] o_num - the number of lids in the list
+ * @return a pointer to the list
+ */
+ const uint32_t * (*get_lid_list)(size_t * o_num);
+
+ /** Load OCC Image and common data into mainstore, also setup OCC BARSs
+ *
+ * @param[in] i_homer_addr_phys - The physical mainstore address of the
+ * start of the HOMER image
+ * @param[in] i_homer_addr_va - Virtual memory address of the HOMER image
+ * @param[in] i_common_addr_phys - The physical mainstore address of the
+ * OCC common area.
+ * @param[in] i_common_addr_va - Virtual memory address of the common area
+ * @param[in] i_chip - The HW chip id (XSCOM chip ID)
+ * @return 0 on success else return code
+ */
+ int(*loadOCC)(uint64_t i_homer_addr_phys,
+ uint64_t i_homer_addr_va,
+ uint64_t i_common_addr_phys,
+ uint64_t i_common_addr_va,
+ uint64_t i_chip);
+
+ /** Start OCC on all chips, by module
+ *
+ * @param[in] i_chip - Array of functional HW chip ids
+ * @Note The caller must include a complete modules worth of chips
+ * @param[in] i_num_chips - Number of chips in the array
+ * @return 0 on success else return code
+ */
+ int (*startOCCs)(uint64_t* i_chip,
+ size_t i_num_chips);
+
+ /** Stop OCC hold OCCs in reset
+ *
+ * @param[in] i_chip - Array of functional HW chip ids
+ * @Note The caller must include a complete modules worth of chips
+ * @param[in] i_num_chips - Number of chips in the array
+ * @return 0 on success else return code
+ */
+ int (*stopOCCs)(uint64_t* i_chip,
+ size_t i_num_chips);
+
+ /* Reserve some space for future growth. */
+ void (*reserved[32])(void);
+};
+
+static struct runtime_interfaces *hservice_runtime;
+
+static char *hbrt_con_buf = (char *)HBRT_CON_START;
+static size_t hbrt_con_pos;
+static bool hbrt_con_wrapped;
+
+#define HBRT_CON_IN_LEN 0
+#define HBRT_CON_OUT_LEN (HBRT_CON_LEN - HBRT_CON_IN_LEN)
+
+static struct memcons hbrt_memcons __section(".data.memcons") = {
+ .magic = CPU_TO_BE64(MEMCONS_MAGIC),
+ .obuf_phys = CPU_TO_BE64(HBRT_CON_START),
+ .ibuf_phys = CPU_TO_BE64(HBRT_CON_START + HBRT_CON_OUT_LEN),
+ .obuf_size = CPU_TO_BE32(HBRT_CON_OUT_LEN),
+ .ibuf_size = CPU_TO_BE32(HBRT_CON_IN_LEN),
+};
+
+static void hservice_putc(char c)
+{
+ uint32_t opos;
+
+ hbrt_con_buf[hbrt_con_pos++] = c;
+ if (hbrt_con_pos >= HBRT_CON_OUT_LEN) {
+ hbrt_con_pos = 0;
+ hbrt_con_wrapped = true;
+ }
+
+ /*
+ * We must always re-generate memcons.out_pos because
+ * under some circumstances, the console script will
+ * use a broken putmemproc that does RMW on the full
+ * 8 bytes containing out_pos and in_prod, thus corrupting
+ * out_pos
+ */
+ opos = hbrt_con_pos;
+ if (hbrt_con_wrapped)
+ opos |= MEMCONS_OUT_POS_WRAP;
+ lwsync();
+ hbrt_memcons.out_pos = cpu_to_be32(opos);
+}
+
+static void hservice_puts(const char *str)
+{
+ char c;
+
+ while((c = *(str++)) != 0)
+ hservice_putc(c);
+ hservice_putc(10);
+}
+
+static void hservice_mark(void)
+{
+ hservice_puts("--------------------------------------------------"
+ "--------------------------------------------------\n");
+}
+
+static void hservice_assert(void)
+{
+ /**
+ * @fwts-label HBRTassert
+ * @fwts-advice HBRT triggered assert: you need to debug HBRT
+ */
+ prlog(PR_EMERG, "HBRT: Assertion from hostservices\n");
+ abort();
+}
+
+static void *hservice_malloc(size_t size)
+{
+ return malloc(size);
+}
+
+static void hservice_free(void *ptr)
+{
+ free(ptr);
+}
+
+
+static void *hservice_realloc(void *ptr, size_t size)
+{
+ return realloc(ptr, size);
+}
+
+struct hbrt_elog_ent {
+ void *buf;
+ unsigned int size;
+ unsigned int plid;
+ struct list_node link;
+};
+static LIST_HEAD(hbrt_elogs);
+static struct lock hbrt_elog_lock = LOCK_UNLOCKED;
+static bool hbrt_elog_sending;
+static void hservice_start_elog_send(void);
+
+static void hservice_elog_write_complete(struct fsp_msg *msg)
+{
+ struct hbrt_elog_ent *ent = msg->user_data;
+
+ lock(&hbrt_elog_lock);
+ prlog(PR_DEBUG, "HBRT: Completed send of PLID 0x%08x\n", ent->plid);
+ hbrt_elog_sending = false;
+ fsp_tce_unmap(PSI_DMA_HBRT_LOG_WRITE_BUF,
+ PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
+ free(ent->buf);
+ free(ent);
+ fsp_freemsg(msg);
+ hservice_start_elog_send();
+ unlock(&hbrt_elog_lock);
+}
+
+static void hservice_start_elog_send(void)
+{
+ struct fsp_msg *msg;
+ struct hbrt_elog_ent *ent;
+
+ again:
+ if (list_empty(&hbrt_elogs))
+ return;
+ ent = list_pop(&hbrt_elogs, struct hbrt_elog_ent, link);
+
+ hbrt_elog_sending = true;
+
+ prlog(PR_DEBUG, "HBRT: Starting send of PLID 0x%08x\n", ent->plid);
+
+ fsp_tce_map(PSI_DMA_HBRT_LOG_WRITE_BUF, ent->buf,
+ PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
+
+ msg = fsp_mkmsg(FSP_CMD_WRITE_SP_DATA, 6, FSP_DATASET_HBRT_BLOB,
+ 0, 0, 0, PSI_DMA_HBRT_LOG_WRITE_BUF,
+ ent->size);
+
+ if (!msg) {
+ prerror("HBRT: Failed to create error msg log to FSP\n");
+ goto error;
+ }
+ msg->user_data = ent;
+ if (!fsp_queue_msg(msg, hservice_elog_write_complete))
+ return;
+ prerror("FSP: Error queueing elog update\n");
+ error:
+ if (msg)
+ fsp_freemsg(msg);
+ fsp_tce_unmap(PSI_DMA_HBRT_LOG_WRITE_BUF,
+ PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
+ free(ent->buf);
+ free(ent);
+ hbrt_elog_sending = false;
+ goto again;
+}
+
+int hservice_send_error_log(uint32_t plid, uint32_t dsize, void *data)
+{
+ struct hbrt_elog_ent *ent;
+ void *abuf;
+
+ prlog(PR_ERR, "HBRT: Error log generated with plid 0x%08x\n", plid);
+
+ /* We only know how to send error logs to FSP */
+ if (!fsp_present()) {
+ prerror("HBRT: Warning, error log from HBRT discarded !\n");
+ return OPAL_UNSUPPORTED;
+ }
+ if (dsize > PSI_DMA_HBRT_LOG_WRITE_BUF_SZ) {
+ prerror("HBRT: Warning, error log from HBRT too big (%d) !\n",
+ dsize);
+ dsize = PSI_DMA_HBRT_LOG_WRITE_BUF_SZ;
+ }
+
+ lock(&hbrt_elog_lock);
+
+ /* Create and populate a tracking structure */
+ ent = zalloc(sizeof(struct hbrt_elog_ent));
+ if (!ent) {
+ unlock(&hbrt_elog_lock);
+ return OPAL_NO_MEM;
+ }
+
+ /* Grab a 4k aligned page */
+ abuf = memalign(0x1000, PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
+ if (!abuf) {
+ free(ent);
+ unlock(&hbrt_elog_lock);
+ return OPAL_NO_MEM;
+ }
+ memset(abuf, 0, PSI_DMA_HBRT_LOG_WRITE_BUF_SZ);
+ memcpy(abuf, data, dsize);
+ ent->buf = abuf;
+ ent->size = dsize;
+ ent->plid = plid;
+ list_add_tail(&hbrt_elogs, &ent->link);
+ if (!hbrt_elog_sending)
+ hservice_start_elog_send();
+ unlock(&hbrt_elog_lock);
+
+ return 0;
+}
+
+static int hservice_scom_read(uint64_t chip_id, uint64_t addr, void *buf)
+{
+ return xscom_read(chip_id, addr, buf);
+}
+
+static int hservice_scom_write(uint64_t chip_id, uint64_t addr,
+ const void *buf)
+{
+ uint64_t val;
+
+ memcpy(&val, buf, sizeof(val));
+ return xscom_write(chip_id, addr, val);
+}
+
+struct hbrt_lid {
+ void *load_addr;
+ size_t len;
+ uint32_t id;
+ struct list_node link;
+};
+static LIST_HEAD(hbrt_lid_list);
+
+static bool hbrt_lid_preload_complete = false;
+
+bool hservices_lid_preload_complete(void)
+{
+ return hbrt_lid_preload_complete;
+}
+
+/* TODO: Few of the following routines can be generalized */
+static int __hservice_lid_load(uint32_t lid, void **buf, size_t *len)
+{
+ int rc;
+
+ /* Adjust LID side first or we get a cache mismatch */
+ lid = fsp_adjust_lid_side(lid);
+
+ /*
+ * Allocate a new buffer and load the LID into it
+ * XXX: We currently use the same size for each HBRT lid.
+ */
+ *buf = malloc(HBRT_LOAD_LID_SIZE);
+ *len = HBRT_LOAD_LID_SIZE;
+ rc = fsp_preload_lid(lid, *buf, len);
+ rc = fsp_wait_lid_loaded(lid);
+ if (rc != 0)
+ /* Take advantage of realloc corner case here. */
+ *len = 0;
+ *buf = realloc(*buf, *len);
+
+ prlog(PR_DEBUG, "HBRT: LID 0x%08x successfully loaded, len=0x%lx\n",
+ lid, (unsigned long)len);
+
+ return rc;
+}
+
+static int __hservice_lid_preload(const uint32_t lid)
+{
+ struct hbrt_lid *hlid;
+ void *buf;
+ size_t len;
+ int rc;
+
+ hlid = zalloc(sizeof(struct hbrt_lid));
+ if (!hlid) {
+ prerror("HBRT: Could not allocate struct hbrt_lid\n");
+ return OPAL_NO_MEM;
+ }
+
+ rc = __hservice_lid_load(lid, &buf, &len);
+ if (rc) {
+ free(hlid);
+ return rc;
+ }
+
+ hlid->load_addr = buf;
+ hlid->len = len;
+ hlid->id = lid;
+ list_add_tail(&hbrt_lid_list, &hlid->link);
+
+ return 0;
+}
+
+/* Find and preload all lids needed by hostservices */
+void hservices_lid_preload(void)
+{
+ const uint32_t *lid_list = NULL;
+ size_t num_lids;
+ int i;
+
+ if (!hservice_runtime)
+ return;
+
+ lid_list = (const uint32_t *)hservice_runtime->get_lid_list(&num_lids);
+ if (!lid_list) {
+ prerror("HBRT: get_lid_list() returned NULL\n");
+ return;
+ }
+
+ prlog(PR_INFO, "HBRT: %d lids to load\n", (int)num_lids);
+
+ /* Currently HBRT needs only one (OCC) lid */
+ for (i = 0; i < num_lids; i++)
+ __hservice_lid_preload(lid_list[i]);
+
+ hbrt_lid_preload_complete = true;
+ occ_poke_load_queue();
+}
+
+static int hservice_lid_load(uint32_t lid, void **buf, size_t *len)
+{
+ struct hbrt_lid *hlid;
+
+ prlog(PR_INFO, "HBRT: Lid load request for 0x%08x\n", lid);
+
+ if (list_empty(&hbrt_lid_list)) { /* Should not happen */
+ /**
+ * @fwts-label HBRTlidLoadFail
+ * @fwts-advice Firmware should have aborted boot
+ */
+ prlog(PR_CRIT, "HBRT: LID Load failed\n");
+ abort();
+ }
+
+ list_for_each(&hbrt_lid_list, hlid, link) {
+ if (hlid->id == lid) {
+ *buf = hlid->load_addr;
+ *len = hlid->len;
+ prlog(PR_DEBUG, "HBRT: LID Serviced from cache,"
+ " %x, len=0x%lx\n", hlid->id, hlid->len);
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static int hservice_lid_unload(void *buf __unused)
+{
+ /* We do nothing as the LID is held in cache */
+ return 0;
+}
+
+static uint64_t hservice_get_reserved_mem(const char *name)
+{
+ struct mem_region *region;
+ uint64_t ret;
+
+ /* We assume it doesn't change after we've unlocked it, but
+ * lock ensures list is safe to walk. */
+ lock(&mem_region_lock);
+ region = find_mem_region(name);
+ ret = region ? region->start : 0;
+ unlock(&mem_region_lock);
+
+ if (!ret)
+ prlog(PR_WARNING, "HBRT: Mem region '%s' not found !\n", name);
+
+ return ret;
+}
+
+static void hservice_nanosleep(uint64_t i_seconds, uint64_t i_nano_seconds)
+{
+ struct timespec ts;
+
+ ts.tv_sec = i_seconds;
+ ts.tv_nsec = i_nano_seconds;
+ nanosleep_nopoll(&ts, NULL);
+}
+
+int hservice_wakeup(uint32_t i_core, uint32_t i_mode)
+{
+ struct cpu_thread *cpu;
+ int rc = OPAL_SUCCESS;
+
+ switch (proc_gen) {
+ case proc_gen_p8:
+ /*
+ * Mask out the top nibble of i_core since it may contain
+ * 0x4 (which we use for XSCOM targeting)
+ */
+ i_core &= 0x0fffffff;
+ i_core <<= 3;
+ break;
+ case proc_gen_p9:
+ i_core &= SPR_PIR_P9_MASK;
+ i_core <<= 2;
+ break;
+ case proc_gen_p10:
+ i_core &= SPR_PIR_P10_MASK;
+ i_core <<= 2;
+ break;
+ default:
+ return OPAL_UNSUPPORTED;
+ }
+
+ /* What do we need to do ? */
+ switch(i_mode) {
+ case 0: /* Assert special wakeup */
+ cpu = find_cpu_by_pir(i_core);
+ if (!cpu)
+ return OPAL_PARAMETER;
+ prlog(PR_TRACE, "HBRT: Special wakeup assert for core 0x%x,"
+ " count=%d\n", i_core, cpu->hbrt_spec_wakeup);
+ if (cpu->hbrt_spec_wakeup == 0)
+ rc = dctl_set_special_wakeup(cpu);
+ if (rc == 0)
+ cpu->hbrt_spec_wakeup++;
+ return rc;
+ case 1: /* Deassert special wakeup */
+ cpu = find_cpu_by_pir(i_core);
+ if (!cpu)
+ return OPAL_PARAMETER;
+ prlog(PR_TRACE, "HBRT: Special wakeup release for core"
+ " 0x%x, count=%d\n", i_core, cpu->hbrt_spec_wakeup);
+ if (cpu->hbrt_spec_wakeup == 0) {
+ prerror("HBRT: Special wakeup clear"
+ " on core 0x%x with count=0\n",
+ i_core);
+ return OPAL_WRONG_STATE;
+ }
+ /* What to do with count on errors ? */
+ cpu->hbrt_spec_wakeup--;
+ if (cpu->hbrt_spec_wakeup == 0)
+ rc = dctl_clear_special_wakeup(cpu);
+ return rc;
+ case 2: /* Clear all special wakeups */
+ prlog(PR_DEBUG, "HBRT: Special wakeup release for all cores\n");
+ for_each_cpu(cpu) {
+ if (cpu->hbrt_spec_wakeup) {
+ cpu->hbrt_spec_wakeup = 0;
+ /* What to do on errors ? */
+ dctl_clear_special_wakeup(cpu);
+ }
+ }
+ return OPAL_SUCCESS;
+ default:
+ return OPAL_PARAMETER;
+ }
+}
+
+static struct host_interfaces hinterface = {
+ .interface_version = HOSTBOOT_RUNTIME_INTERFACE_VERSION,
+ .puts = hservice_puts,
+ .assert = hservice_assert,
+ .malloc = hservice_malloc,
+ .free = hservice_free,
+ .realloc = hservice_realloc,
+ .send_error_log = hservice_send_error_log,
+ .scom_read = hservice_scom_read,
+ .scom_write = hservice_scom_write,
+ .lid_load = hservice_lid_load,
+ .lid_unload = hservice_lid_unload,
+ .get_reserved_mem = hservice_get_reserved_mem,
+ .wakeup = hservice_wakeup,
+ .nanosleep = hservice_nanosleep,
+};
+
+int host_services_occ_load(void)
+{
+ struct proc_chip *chip;
+ int rc = 0;
+
+ prlog(PR_DEBUG, "HBRT: OCC Load requested\n");
+
+ if (!(hservice_runtime && hservice_runtime->loadOCC)) {
+ prerror("HBRT: No hservice_runtime->loadOCC\n");
+ return -ENOENT;
+ }
+
+ for_each_chip(chip) {
+
+ prlog(PR_DEBUG, "HBRT: Calling loadOCC() homer"
+ " %016llx, occ_common_area %016llx, chip %04x\n",
+ chip->homer_base,
+ chip->occ_common_base,
+ chip->id);
+
+ rc = hservice_runtime->loadOCC(chip->homer_base,
+ chip->homer_base,
+ chip->occ_common_base,
+ chip->occ_common_base,
+ chip->id);
+
+ hservice_mark();
+ prlog(PR_DEBUG, "HBRT: -> rc = %d\n", rc);
+ }
+ return rc;
+}
+
+int host_services_occ_start(void)
+{
+ struct proc_chip *chip;
+ int i, rc = 0, nr_chips=0;
+ uint64_t chipids[MAX_CHIPS];
+
+ prlog(PR_INFO, "HBRT: OCC Start requested\n");
+
+ if (!(hservice_runtime && hservice_runtime->startOCCs)) {
+ prerror("HBRT: No hservice_runtime->startOCCs\n");
+ return -ENOENT;
+ }
+
+ for_each_chip(chip) {
+ chipids[nr_chips++] = chip->id;
+ }
+
+ for (i = 0; i < nr_chips; i++)
+ prlog(PR_TRACE, "HBRT: Calling startOCC() for %04llx\n",
+ chipids[i]);
+
+ /* Lets start all OCC */
+ rc = hservice_runtime->startOCCs(chipids, nr_chips);
+ hservice_mark();
+ prlog(PR_DEBUG, "HBRT: startOCCs() rc = %d\n", rc);
+ return rc;
+}
+
+int host_services_occ_stop(void)
+{
+ int i, rc = 0, nr_slaves = 0, nr_masters = 0;
+ uint64_t *master_chipids = NULL, *slave_chipids = NULL;
+
+ prlog(PR_INFO, "HBRT: OCC Stop requested\n");
+
+ if (!(hservice_runtime && hservice_runtime->stopOCCs)) {
+ prerror("HBRT: No hservice_runtime->stopOCCs\n");
+ return -ENOENT;
+ }
+
+ rc = find_master_and_slave_occ(&master_chipids, &slave_chipids,
+ &nr_masters, &nr_slaves);
+ if (rc)
+ goto out;
+
+ for (i = 0; i < nr_slaves; i++)
+ prlog(PR_TRACE, "HBRT: Calling stopOCC() for %04llx ",
+ slave_chipids[i]);
+
+ if (!nr_slaves)
+ goto master;
+
+ /* Lets STOP all the slave OCC */
+ rc = hservice_runtime->stopOCCs(slave_chipids, nr_slaves);
+ prlog(PR_DEBUG, "HBRT: stopOCCs() slave rc = %d\n", rc);
+
+master:
+ for (i = 0; i < nr_masters; i++)
+ prlog(PR_TRACE, "HBRT: Calling stopOCC() for %04llx ",
+ master_chipids[i]);
+
+ /* Lets STOP all the master OCC */
+ rc = hservice_runtime->stopOCCs(master_chipids, nr_masters);
+
+ hservice_mark();
+ prlog(PR_DEBUG, "HBRT: stopOCCs() master rc = %d\n", rc);
+
+out:
+ free(master_chipids);
+ free(slave_chipids);
+ return rc;
+}
+
+bool hservices_init(void)
+{
+ void *code = NULL;
+ struct runtime_interfaces *(*hbrt_init)(struct host_interfaces *);
+
+ struct function_descriptor {
+ void *addr;
+ void *toc;
+ } fdesc;
+
+ code = (void *)hservice_get_reserved_mem("ibm,hbrt-code-image");
+ if (!code) {
+ prerror("HBRT: No ibm,hbrt-code-image found.\n");
+ return false;
+ }
+
+ if (memcmp(code, "HBRTVERS", 8) != 0) {
+ prerror("HBRT: Bad eyecatcher for ibm,hbrt-code-image!\n");
+ return false;
+ }
+
+ prlog(PR_INFO, "HBRT: Found HostBoot Runtime version %llu\n",
+ ((u64 *)code)[1]);
+
+ /* We enter at 0x100 into the image. */
+ fdesc.addr = code + 0x100;
+ /* It doesn't care about TOC */
+ fdesc.toc = NULL;
+
+ hbrt_init = (void *)&fdesc;
+
+ hservice_runtime = hbrt_init(&hinterface);
+ hservice_mark();
+ if (!hservice_runtime) {
+ prerror("HBRT: Host services init failed\n");
+ return false;
+ }
+
+ prlog(PR_INFO, "HBRT: Interface version %llu\n",
+ hservice_runtime->interface_version);
+
+ return true;
+}
+
+static void hservice_send_hbrt_msg_resp(struct fsp_msg *msg)
+{
+ int status = (msg->resp->word1 >> 8) & 0xff;
+
+ fsp_freemsg(msg);
+ if (status) {
+ prlog(PR_NOTICE, "HBRT: HBRT to FSP MBOX command failed "
+ "[rc=0x%x]\n", status);
+ }
+
+ fsp_tce_unmap(PSI_DMA_HBRT_FSP_MSG, PSI_DMA_HBRT_FSP_MSG_SIZE);
+ /* Send response data to HBRT */
+ prd_fw_resp_fsp_response(status);
+}
+
+#define FSP_STATUS_RR (-8193)
+/* Caller takes care of serializing MBOX message */
+int hservice_send_hbrt_msg(void *data, u64 dsize)
+{
+ uint32_t tce_len, offset;
+ int rc;
+ uint64_t addr;
+ struct fsp_msg *msg;
+
+ prlog(PR_NOTICE, "HBRT: HBRT - FSP message generated\n");
+
+ /* We only support FSP based system */
+ if (!fsp_present()) {
+ prlog(PR_DEBUG,
+ "HBRT: Warning, HBRT - FSP message discarded!\n");
+ return OPAL_UNSUPPORTED;
+ }
+
+ /*
+ * If FSP is in R/R then send specific return code to HBRT (inside
+ * HBRT message) and return success to caller (opal_prd_msg()).
+ */
+ if (fsp_in_rr()) {
+ prlog(PR_DEBUG,
+ "HBRT: FSP is in R/R. Dropping HBRT - FSP message\n");
+ prd_fw_resp_fsp_response(FSP_STATUS_RR);
+ return OPAL_SUCCESS;
+ }
+
+ /* Adjust address, size for TCE mapping */
+ addr = (u64)data & ~TCE_MASK;
+ offset = (u64)data & TCE_MASK;
+ tce_len = ALIGN_UP((dsize + offset), TCE_PSIZE);
+
+ if (tce_len > PSI_DMA_HBRT_FSP_MSG_SIZE) {
+ prlog(PR_DEBUG,
+ "HBRT: HBRT - FSP message is too big, discarded\n");
+ return OPAL_PARAMETER;
+ }
+ fsp_tce_map(PSI_DMA_HBRT_FSP_MSG, (void *)addr, tce_len);
+
+ msg = fsp_mkmsg(FSP_CMD_HBRT_TO_FSP, 3, 0,
+ (PSI_DMA_HBRT_FSP_MSG + offset), dsize);
+ if (!msg) {
+ prlog(PR_DEBUG,
+ "HBRT: Failed to create HBRT - FSP message to FSP\n");
+ rc = OPAL_NO_MEM;
+ goto out_tce_unmap;
+ }
+
+ rc = fsp_queue_msg(msg, hservice_send_hbrt_msg_resp);
+ if (rc == 0)
+ return rc;
+
+ prlog(PR_DEBUG, "HBRT: Failed to queue HBRT message to FSP\n");
+ fsp_freemsg(msg);
+
+out_tce_unmap:
+ fsp_tce_unmap(PSI_DMA_HBRT_FSP_MSG, PSI_DMA_HBRT_FSP_MSG_SIZE);
+ return rc;
+}
+
+void hservice_hbrt_msg_response(uint32_t rc)
+{
+ struct fsp_msg *resp;
+
+ resp = fsp_mkmsg(FSP_RSP_FSP_TO_HBRT | (uint8_t)rc, 0);
+ if (!resp) {
+ prlog(PR_DEBUG,
+ "HBRT: Failed to allocate FSP - HBRT response message\n");
+ return;
+ }
+
+ if (fsp_queue_msg(resp, fsp_freemsg)) {
+ prlog(PR_DEBUG,
+ "HBRT: Failed to send FSP - HBRT response message\n");
+ fsp_freemsg(resp);
+ return;
+ }
+}
+
+/* FSP sends HBRT notification message. Pass this message to HBRT */
+static bool hservice_hbrt_msg_notify(uint32_t cmd_sub_mod, struct fsp_msg *msg)
+{
+ u32 tce_token, len;
+ void *buf;
+
+ if (cmd_sub_mod != FSP_CMD_FSP_TO_HBRT)
+ return false;
+
+ prlog(PR_TRACE, "HBRT: FSP - HBRT message generated\n");
+
+ tce_token = fsp_msg_get_data_word(msg, 1);
+ len = fsp_msg_get_data_word(msg, 2);
+ buf = fsp_inbound_buf_from_tce(tce_token);
+ if (!buf) {
+ prlog(PR_DEBUG, "HBRT: Invalid inbound data\n");
+ hservice_hbrt_msg_response(FSP_STATUS_INVALID_DATA);
+ return true;
+ }
+
+ if (prd_hbrt_fsp_msg_notify(buf, len)) {
+ hservice_hbrt_msg_response(FSP_STATUS_GENERIC_ERROR);
+ prlog(PR_NOTICE, "Unable to send FSP - HBRT message\n");
+ }
+
+ return true;
+}
+
+static struct fsp_client fsp_hbrt_msg_client = {
+ .message = hservice_hbrt_msg_notify,
+};
+
+/* Register for FSP 0xF2 class messages */
+void hservice_fsp_init(void)
+{
+ if (proc_gen < proc_gen_p9)
+ return;
+
+ if (!fsp_present())
+ return;
+
+ /* Register for Class F2 */
+ fsp_register_client(&fsp_hbrt_msg_client, FSP_MCLASS_HBRT);
+}
diff --git a/roms/skiboot/platforms/ibm-fsp/ibm-fsp.h b/roms/skiboot/platforms/ibm-fsp/ibm-fsp.h
new file mode 100644
index 000000000..bb191645c
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/ibm-fsp.h
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#ifndef __IBM_FSP_COMMON_H
+#define __IBM_FSP_COMMON_H
+
+extern void ibm_fsp_init(void);
+extern void ibm_fsp_exit(void);
+void ibm_fsp_finalise_dt(bool is_reboot);
+
+extern int64_t ibm_fsp_cec_power_down(uint64_t request);
+extern int64_t ibm_fsp_cec_reboot(void);
+
+struct errorlog;
+extern int elog_fsp_commit(struct errorlog *buf);
+
+extern int64_t ibm_fsp_sensor_read(uint32_t sensor_hndl, int token,
+ __be64 *sensor_data);
+
+/* Apollo PCI support */
+extern void apollo_pci_setup_phb(struct phb *phb,
+ unsigned int index);
+extern void apollo_pci_get_slot_info(struct phb *phb,
+ struct pci_device *pd);
+
+/* Firenze PCI support */
+extern void firenze_pci_send_inventory(void);
+extern void firenze_pci_setup_phb(struct phb *phb,
+ unsigned int index);
+extern void firenze_pci_get_slot_info(struct phb *phb,
+ struct pci_device *pd);
+extern void firenze_pci_add_loc_code(struct dt_node *np,
+ struct pci_device *pd);
+
+/* VPD support */
+void vpd_iohub_load(struct dt_node *hub_node);
+void vpd_preload(struct dt_node *hub_node);
+
+/* Platform heartbeat time */
+int __attrconst fsp_heartbeat_time(void);
+
+extern struct platform_psi fsp_platform_psi;
+extern struct platform_prd fsp_platform_prd;
+
+#endif /* __IBM_FSP_COMMON_H */
diff --git a/roms/skiboot/platforms/ibm-fsp/lxvpd.c b/roms/skiboot/platforms/ibm-fsp/lxvpd.c
new file mode 100644
index 000000000..9b34a23a1
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/lxvpd.c
@@ -0,0 +1,371 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#define pr_fmt(fmt) "LXVPD: " fmt
+
+#include <skiboot.h>
+#include <device.h>
+#include <vpd.h>
+#include <pci.h>
+#include <pci-cfg.h>
+#include <pci-slot.h>
+
+#include "lxvpd.h"
+
+/*
+ * Currently, the lxvpd PCI slot struct is shared by multiple
+ * platforms (Apollo and Firenze), but each slot still has
+ * platform specific features. In order for unified data structs,
+ * "struct lxvpd_slot" is expected to be embedded in platform
+ * PCI slot struct. "entry_size" indicates the size of platform
+ * specific PCI slot instance.
+ */
+struct lxvpd_pci_slot_data {
+ uint8_t num_slots;
+ int32_t entry_size; /* Size of platform PCI slot */
+ void *slots; /* Data of platform PCI slots */
+};
+
+static bool lxvpd_supported_slot(struct phb *phb, struct pci_device *pd)
+{
+ /* PHB should always be valid */
+ if (!phb)
+ return false;
+
+ /* We expect platform slot for root complex */
+ if (!pd)
+ return true;
+
+ /* We support the root complex at the top level */
+ if (pd->dev_type == PCIE_TYPE_ROOT_PORT && !pd->parent)
+ return true;
+
+ /* We support an upstream switch port below the root complex */
+ if (pd->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
+ pd->parent && pd->parent->dev_type == PCIE_TYPE_ROOT_PORT &&
+ !pd->parent->parent)
+ return true;
+
+ /* We support a downstream switch port below an upstream port
+ * below the root complex
+ */
+ if (pd->dev_type == PCIE_TYPE_SWITCH_DNPORT &&
+ pd->parent && pd->parent->dev_type == PCIE_TYPE_SWITCH_UPPORT &&
+ pd->parent->parent &&
+ pd->parent->parent->dev_type == PCIE_TYPE_ROOT_PORT &&
+ !pd->parent->parent->parent)
+ return true;
+
+ /* Anything else, bail */
+ return false;
+}
+
+void *lxvpd_get_slot(struct pci_slot *slot)
+{
+ struct phb *phb = slot->phb;
+ struct pci_device *pd = slot->pd;
+ struct lxvpd_pci_slot_data *sdata = phb->platform_data;
+ struct lxvpd_pci_slot *s = NULL;
+ uint8_t slot_num = pd ? PCI_DEV(pd->bdfn) : 0xff;
+ bool is_phb = (pd && pd->parent) ? false : true;
+ uint8_t index;
+
+ /* Check if we have slot info */
+ if (!sdata) {
+ prlog(PR_DEBUG, "PHB%04x not have VPD data\n",
+ phb->opal_id);
+ return NULL;
+ }
+
+ /* Platform slot attached ? */
+ s = slot->data;
+ if (s) {
+ prlog(PR_DEBUG, "Slot %016llx had platform data [%s]\n",
+ slot->id, s->label);
+ return s;
+ }
+
+ /*
+ * This code only handles PHBs and PCIe switches at the
+ * top level. We do not handle any other switch nor any
+ * other type of PCI/PCI-X bridge. Generally, we have
+ * more strict rules to support slot than PCI core.
+ */
+ if (!lxvpd_supported_slot(phb, pd)) {
+ prlog(PR_DEBUG, "Slot %016llx not supported\n",
+ slot->id);
+ return NULL;
+ }
+
+ /* Iterate the platform slot array */
+ for (index = 0; index < sdata->num_slots; index++) {
+ s = sdata->slots + (index * sdata->entry_size);
+
+ /* Match PHB with switch_id == 0 */
+ if (is_phb && s->switch_id == 0) {
+ slot->data = s;
+ s->pci_slot = slot;
+ prlog(PR_DEBUG, "Found [%s] for PHB slot %016llx\n",
+ s->label, slot->id);
+
+ return s;
+ }
+
+ /* Match downstream switch port with switch_id != 0 */
+ if (!is_phb && s->switch_id != 0 && !s->upstream_port &&
+ s->dev_id == slot_num) {
+ slot->data = s;
+ s->pci_slot = slot;
+ prlog(PR_DEBUG, "Found [%s] for slot %016llx\n",
+ s->label, slot->id);
+
+ return s;
+ }
+ }
+
+ prlog(PR_DEBUG, "No data found for %sslot %016llx\n",
+ is_phb ? "PHB " : " ", slot->id);
+ return NULL;
+}
+
+void lxvpd_extract_info(struct pci_slot *slot, struct lxvpd_pci_slot *s)
+{
+ slot->pluggable = s->pluggable ? 1 : 0;
+ slot->power_ctl = s->power_ctl ? 1 : 0;
+ slot->power_led_ctl = s->pwr_led_ctl;
+ slot->attn_led_ctl = s->attn_led_ctl;
+ slot->connector_type = s->connector_type;
+ slot->card_desc = s->card_desc;
+ slot->card_mech = s->card_mech;
+ slot->wired_lanes = s->wired_lanes;
+
+ prlog(PR_DEBUG, "[%s]: pluggable: %d power_ctrl: %d\n",
+ s->label, (int) s->pluggable, (int) s->power_ctl);
+}
+
+static struct lxvpd_pci_slot_data *lxvpd_alloc_slots(struct phb *phb,
+ uint8_t count,
+ uint32_t slot_size)
+{
+ struct lxvpd_pci_slot_data *sdata;
+
+ sdata = zalloc(sizeof(struct lxvpd_pci_slot_data) + count * slot_size);
+ assert(sdata);
+ sdata->num_slots = count;
+ sdata->entry_size = slot_size;
+ sdata->slots = sdata + 1;
+ phb->platform_data = sdata;
+
+ return sdata;
+}
+
+static void lxvpd_format_label(char *dst, const char *src, size_t len)
+{
+ int i;
+
+ memcpy(dst, src, len);
+
+ /* Remove blank suffix */
+ for (i = strlen(dst) - 1; i >= 0; i--) {
+ if (dst[i] != ' ')
+ break;
+
+ dst[i] = 0;
+ }
+}
+
+static void lxvpd_parse_1004_map(struct phb *phb,
+ const uint8_t *sm,
+ uint8_t size,
+ uint32_t slot_size)
+{
+ struct lxvpd_pci_slot_data *sdata;
+ struct lxvpd_pci_slot *s;
+ const struct pci_slot_entry_1004 *entry;
+ uint8_t num_slots, slot;
+
+ num_slots = (size / sizeof(struct pci_slot_entry_1004));
+ sdata = lxvpd_alloc_slots(phb, num_slots, slot_size);
+
+ /* Iterate through the entries in the keyword */
+ entry = (const struct pci_slot_entry_1004 *)sm;
+ for (slot = 0; slot < num_slots; slot++, entry++) {
+ s = sdata->slots + slot * sdata->entry_size;
+
+ /* Figure out PCI slot info */
+ lxvpd_format_label(s->label, entry->label, 3);
+ s->slot_index = entry->slot_index;
+ s->switch_id = entry->pba >> 4;
+ s->vswitch_id = entry->pba & 0xf;
+ s->dev_id = entry->sba;
+ s->pluggable = ((entry->p0.byte & 0x20) == 0);
+ s->power_ctl = !!(entry->p0.byte & 0x40);
+ s->bus_clock = entry->p2.bus_clock - 4;
+ s->connector_type = entry->p2.connector_type - 5;
+ s->card_desc = entry->p3.byte >> 6;
+ if (entry->p3.byte < 0xc0)
+ s->card_desc -= 4;
+ s->card_mech = (entry->p3.byte >> 4) & 0x3;
+ s->pwr_led_ctl = (entry->p3.byte & 0xf) >> 2;
+ s->attn_led_ctl = entry->p3.byte & 0x3;
+
+ switch(entry->p1.wired_lanes) {
+ case 1: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIX_32; break;
+ case 2: /* fall through */
+ case 3: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIX_64; break;
+ case 4: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X1; break;
+ case 5: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X4; break;
+ case 6: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X8; break;
+ case 7: s->wired_lanes = PCI_SLOT_WIRED_LANES_PCIE_X16; break;
+ default:
+ s->wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
+ }
+
+ prlog(PR_DEBUG, "1004 Platform data [%s] %02x %02x on PHB%04x\n",
+ s->label, s->switch_id, s->dev_id, phb->opal_id);
+ }
+}
+
+static void lxvpd_parse_1005_map(struct phb *phb,
+ const uint8_t *sm,
+ uint8_t size,
+ uint32_t slot_size)
+{
+ struct lxvpd_pci_slot_data *sdata;
+ struct lxvpd_pci_slot *s;
+ const struct pci_slot_entry_1005 *entry;
+ uint8_t num_slots, slot;
+
+ num_slots = (size / sizeof(struct pci_slot_entry_1005));
+ sdata = lxvpd_alloc_slots(phb, num_slots, slot_size);
+
+ /* Iterate through the entries in the keyword */
+ entry = (const struct pci_slot_entry_1005 *)sm;
+ for (slot = 0; slot < num_slots; slot++, entry++) {
+ s = sdata->slots + slot * sdata->entry_size;
+
+ /* Put slot info into pci device structure */
+ lxvpd_format_label(s->label, entry->label, 8);
+ s->slot_index = entry->slot_index;
+ s->switch_id = entry->pba >> 4;
+ s->vswitch_id = entry->pba & 0xf;
+ s->dev_id = entry->switch_device_id;
+ s->pluggable = (entry->p0.pluggable == 0);
+ s->power_ctl = entry->p0.power_ctl;
+ s->upstream_port = entry->p0.upstream_port;
+ s->bus_clock = entry->p2.bus_clock;
+ s->connector_type = entry->p2.connector_type;
+ s->card_desc = entry->p3.byte >> 6;
+ s->card_mech = (entry->p3.byte >> 4) & 0x3;
+ s->pwr_led_ctl = (entry->p3.byte & 0xf) >> 2;
+ s->attn_led_ctl = entry->p3.byte & 0x3;
+ s->wired_lanes = entry->p1.wired_lanes;
+ if (s->wired_lanes > PCI_SLOT_WIRED_LANES_PCIE_X32)
+ s->wired_lanes = PCI_SLOT_WIRED_LANES_UNKNOWN;
+
+ prlog(PR_DEBUG, "1005 Platform data [%s] %02x %02x %s on PHB%04x\n",
+ s->label, s->switch_id, s->dev_id,
+ s->upstream_port ? "upstream" : "downstream",
+ phb->opal_id);
+ }
+}
+
+void lxvpd_process_slot_entries(struct phb *phb,
+ struct dt_node *node,
+ uint8_t chip_id,
+ uint8_t index,
+ uint32_t slot_size)
+{
+ const void *lxvpd;
+ const uint8_t *pr_rec, *pr_end, *sm;
+ size_t lxvpd_size, pr_size;
+ const beint16_t *mf = NULL;
+ char record[5] = "PR00";
+ uint8_t mf_sz, sm_sz;
+ bool found = false;
+
+ record[2] += chip_id;
+ record[3] += index;
+ record[4] = 0;
+
+ /* Get LX VPD pointer */
+ lxvpd = dt_prop_get_def_size(node, "ibm,io-vpd", NULL, &lxvpd_size);
+ if (!lxvpd) {
+ prlog(PR_WARNING, "No data found for PHB%04x %s\n",
+ phb->opal_id, record);
+ return;
+ }
+
+ pr_rec = vpd_find_record(lxvpd, lxvpd_size, record, &pr_size);
+ if (!pr_rec) {
+ prlog(PR_WARNING, "Record %s not found on PHB%04x\n",
+ record, phb->opal_id);
+ return;
+ }
+
+ /* As long as there's still something in the PRxy record */
+ prlog(PR_DEBUG, "PHB%04x record %s has %ld bytes\n",
+ phb->opal_id, record, pr_size);
+ pr_end = pr_rec + pr_size;
+ while (pr_rec < pr_end) {
+ pr_size = pr_end - pr_rec;
+
+ /* Find the next MF keyword */
+ mf = vpd_find_keyword(pr_rec, pr_size, "MF", &mf_sz);
+ /* And the corresponding SM */
+ sm = vpd_find_keyword(pr_rec, pr_size, "SM", &sm_sz);
+ if (!mf || !sm) {
+ if (!found)
+ prlog(PR_WARNING, "Slot Map keyword %s not found\n",
+ record);
+ return;
+ }
+
+ prlog(PR_DEBUG, "Found 0x%04x map...\n", be16_to_cpu(*mf));
+ switch (be16_to_cpu(*mf)) {
+ case 0x1004:
+ lxvpd_parse_1004_map(phb, sm + 1, sm_sz - 1, slot_size);
+ found = true;
+ break;
+ case 0x1005:
+ lxvpd_parse_1005_map(phb, sm + 1, sm_sz - 1, slot_size);
+ found = true;
+ break;
+ /* Add support for 0x1006 maps ... */
+ }
+
+ pr_rec = sm + sm_sz;
+ }
+}
+
+void lxvpd_add_slot_properties(struct pci_slot *slot,
+ struct dt_node *np)
+{
+ struct phb *phb = slot->phb;
+ struct lxvpd_pci_slot *s = slot->data;
+ char loc_code[LOC_CODE_SIZE];
+ size_t base_loc_code_len, slot_label_len;
+
+ /* Check if we have platform specific slot */
+ if (!s || !np)
+ return;
+
+ /* Check PHB base location code */
+ if (!phb->base_loc_code)
+ return;
+
+ /* Check location length is valid */
+ base_loc_code_len = strlen(phb->base_loc_code);
+ slot_label_len = strlen(s->label);
+ if ((base_loc_code_len + slot_label_len + 1) >= LOC_CODE_SIZE)
+ return;
+
+ /* Location code */
+ strcpy(loc_code, phb->base_loc_code);
+ strcat(loc_code, "-");
+ strcat(loc_code, s->label);
+ dt_add_property(np, "ibm,slot-location-code",
+ loc_code, strlen(loc_code) + 1);
+ dt_add_property_string(np, "ibm,slot-label",
+ s->label);
+}
diff --git a/roms/skiboot/platforms/ibm-fsp/lxvpd.h b/roms/skiboot/platforms/ibm-fsp/lxvpd.h
new file mode 100644
index 000000000..4b771f3f7
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/lxvpd.h
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2016 IBM Corp. */
+
+#ifndef __LXVPD_H
+#define __LXVPD_H
+
+#define LX_VPD_1S2U_BACKPLANE 0x3100040100300041ull
+#define LX_VPD_2S2U_BACKPLANE 0x3100040100300042ull
+#define LX_VPD_SHARK_BACKPLANE 0x3100040100300942ull
+#define LX_VPD_1S4U_BACKPLANE 0x3100040100300043ull
+#define LX_VPD_2S4U_BACKPLANE 0x3100040100300044ull
+
+struct slot_p0 {
+ union {
+ uint8_t byte;
+ struct {
+#if HAVE_BIG_ENDIAN
+ uint8_t pluggable:1;
+ uint8_t pluggable_location:3;
+ uint8_t power_ctl:1;
+ uint8_t rsvd_5:1;
+ uint8_t upstream_port:1;
+ uint8_t alt_load_source:1;
+#else
+ uint8_t alt_load_source:1;
+ uint8_t upstream_port:1;
+ uint8_t rsvd_5:1;
+ uint8_t power_ctl:1;
+ uint8_t pluggable_location:3;
+ uint8_t pluggable:1;
+#endif
+ };
+ };
+};
+
+struct slot_p1 {
+#if HAVE_BIG_ENDIAN
+ uint8_t rsvd_0:1;
+ uint8_t wired_lanes:3;
+ uint8_t rsvd_4:4;
+#else
+ uint8_t rsvd_4:4;
+ uint8_t wired_lanes:3;
+ uint8_t rsvd_0:1;
+#endif
+};
+
+struct slot_p2 {
+#if HAVE_BIG_ENDIAN
+ uint8_t rsvd_0:1;
+ uint8_t bus_clock:3;
+ uint8_t connector_type:4;
+#else
+ uint8_t connector_type:4;
+ uint8_t bus_clock:3;
+ uint8_t rsvd_0:1;
+#endif
+};
+
+struct slot_p3 {
+ union {
+ uint8_t byte;
+ struct {
+#if HAVE_BIG_ENDIAN
+ uint8_t height:1;
+ uint8_t length:1;
+ uint8_t left_mech:1;
+ uint8_t right_mech:1;
+ uint8_t pow_led_kvm:1;
+ uint8_t pow_led_fsp:1;
+ uint8_t attn_led_kvm:1;
+ uint8_t attn_led_fsp:1;
+#else
+ uint8_t attn_led_fsp:1;
+ uint8_t attn_led_kvm:1;
+ uint8_t pow_led_fsp:1;
+ uint8_t pow_led_kvm:1;
+ uint8_t right_mech:1;
+ uint8_t left_mech:1;
+ uint8_t length:1;
+ uint8_t height:1;
+#endif
+ };
+ };
+};
+
+struct pci_slot_entry_1004 {
+ uint8_t pba;
+ uint8_t sba;
+ uint8_t phb_or_slot_type;
+ char label[3];
+ __be16 bis;
+ struct slot_p0 p0;
+ struct slot_p1 p1;
+ struct slot_p2 p2;
+ struct slot_p3 p3;
+ uint8_t left_pitch;
+ uint8_t right_pitch;
+ uint8_t slot_index;
+ uint8_t max_slot_power;
+};
+
+/* P8 PCI Slot Entry Definitions -- 1005 */
+struct pci_slot_entry_1005 {
+ union {
+ uint8_t pba;
+ struct {
+#if HAVE_BIG_ENDIAN
+ uint8_t switch_id:4;
+ uint8_t vswitch_id:4;
+#else
+ uint8_t vswitch_id:4;
+ uint8_t switch_id:4;
+#endif
+ };
+ };
+ uint8_t switch_device_id;
+#if HAVE_BIG_ENDIAN
+ uint8_t slot_type:4;
+ uint8_t phb_id:4;
+#else
+ uint8_t phb_id:4;
+ uint8_t slot_type:4;
+#endif
+ char label[8];
+ uint8_t rsvd_11[4];
+ struct slot_p0 p0;
+ struct slot_p1 p1;
+ struct slot_p2 p2;
+ struct slot_p3 p3;
+ uint8_t left_pitch;
+ uint8_t right_pitch;
+ uint8_t slot_index;
+ uint8_t rsvd_22[2];
+};
+
+struct lxvpd_pci_slot {
+ struct pci_slot *pci_slot;
+ uint8_t switch_id;
+ uint8_t vswitch_id;
+ uint8_t dev_id;
+ char label[9];
+ bool pluggable;
+ bool power_ctl;
+ bool upstream_port;
+ uint8_t wired_lanes;
+ uint8_t bus_clock;
+ uint8_t connector_type;
+ uint8_t card_desc;
+ uint8_t card_mech;
+ uint8_t pwr_led_ctl;
+ uint8_t attn_led_ctl;
+ uint8_t slot_index;
+};
+
+extern void lxvpd_process_slot_entries(struct phb *phb, struct dt_node *node,
+ uint8_t chip_id, uint8_t index,
+ uint32_t slot_size);
+extern void *lxvpd_get_slot(struct pci_slot *slot);
+extern void lxvpd_extract_info(struct pci_slot *slot,
+ struct lxvpd_pci_slot *s);
+extern void lxvpd_add_slot_properties(struct pci_slot *slot,
+ struct dt_node *np);
+#endif /* __LXVPD_H */
diff --git a/roms/skiboot/platforms/ibm-fsp/zz.c b/roms/skiboot/platforms/ibm-fsp/zz.c
new file mode 100644
index 000000000..493d6030a
--- /dev/null
+++ b/roms/skiboot/platforms/ibm-fsp/zz.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2016-2019 IBM Corp. */
+
+#include <skiboot.h>
+#include <device.h>
+#include <fsp.h>
+#include <pci.h>
+#include <pci-cfg.h>
+#include <chip.h>
+#include <i2c.h>
+#include <timebase.h>
+#include <hostservices.h>
+#include <npu2.h>
+
+#include "ibm-fsp.h"
+#include "lxvpd.h"
+
+static const char *zz_ocapi_slot_label(uint32_t chip_id,
+ uint32_t brick_index)
+{
+ const char *name = NULL;
+
+ if (chip_id == 0) {
+ if (brick_index == 2)
+ name = "P1-T5";
+ else
+ name = "P1-T6";
+ } else {
+ if (brick_index == 2)
+ name = "P1-T7";
+ else
+ name = "P1-T8";
+ }
+ return name;
+}
+
+/* We don't yet create NPU device nodes on ZZ, but these values are correct */
+static const struct platform_ocapi zz_ocapi = {
+ .i2c_engine = 1,
+ .i2c_port = 4,
+ .i2c_reset_addr = 0x20,
+ .i2c_reset_brick2 = (1 << 1),
+ .i2c_reset_brick3 = (1 << 6),
+ .i2c_reset_brick4 = 0, /* unused */
+ .i2c_reset_brick5 = 0, /* unused */
+ .i2c_presence_addr = 0x20,
+ .i2c_presence_brick2 = (1 << 2), /* bottom connector */
+ .i2c_presence_brick3 = (1 << 7), /* top connector */
+ .i2c_presence_brick4 = 0, /* unused */
+ .i2c_presence_brick5 = 0, /* unused */
+ .odl_phy_swap = true,
+ .ocapi_slot_label = zz_ocapi_slot_label,
+};
+
+#define NPU_BASE 0x5011000
+#define NPU_SIZE 0x2c
+#define NPU_INDIRECT0 0x8000000009010c3f /* OB0 - no OB3 on ZZ */
+
+static void create_link(struct dt_node *npu, int group, int index)
+{
+ struct dt_node *link;
+ uint32_t lane_mask;
+
+ switch (index) {
+ case 2:
+ lane_mask = 0xf1e000; /* 0-3, 7-10 */
+ break;
+ case 3:
+ lane_mask = 0x00078f; /* 13-16, 20-23 */
+ break;
+ default:
+ assert(0);
+ }
+
+ link = dt_new_addr(npu, "link", index);
+ dt_add_property_string(link, "compatible", "ibm,npu-link");
+ dt_add_property_cells(link, "ibm,npu-link-index", index);
+ dt_add_property_u64s(link, "ibm,npu-phy", NPU_INDIRECT0);
+ dt_add_property_cells(link, "ibm,npu-lane-mask", lane_mask);
+ dt_add_property_cells(link, "ibm,npu-group-id", group);
+ dt_add_property_u64s(link, "ibm,link-speed", 25000000000ul);
+}
+
+static void add_opencapi_dt_nodes(void)
+{
+ struct dt_node *npu, *xscom;
+ int npu_index = 0;
+
+ /*
+ * In an ideal world, we should get all the NPU links
+ * information from HDAT. But after some effort, HDAT is still
+ * giving incorrect information for opencapi. As of this
+ * writing:
+ * 1. link usage is wrong for most FPGA cards (0xFFFF vs. 2)
+ * 2. the 24-bit lane mask is aligned differently than on
+ * other platforms (witherspoon)
+ * 3. connecting a link entry in HDAT to the real physical
+ * link will need extra work:
+ * - HDAT does presence detection and only lists links with
+ * an adapter, so we cannot use default ordering like on
+ * witherspoon
+ * - best option is probably the brick ID field (offset 8).
+ * It's coming straight from the MRW, but seems to match
+ * what we expect (2 or 3). Would need to be checked.
+ *
+ * To make things more fun, any change in the HDAT data needs
+ * to be coordinated with PHYP, which is using (some of) those
+ * fields.
+ *
+ * As a consequence:
+ * 1. the hdat parsing code in skiboot remains disabled (for
+ * opencapi)
+ * 2. we hard-code the NPU and links entries in the device
+ * tree.
+ *
+ * Getting the data from HDAT would have the advantage of
+ * providing the real link speed (20.0 vs. 25.78125 gbps),
+ * which is useful as there's one speed-dependent setting we
+ * need to do when initializing the NPU. Our hard coded
+ * definition assumes the higher speed and may need tuning in
+ * debug scenario using a lower link speed.
+ */
+ dt_for_each_compatible(dt_root, xscom, "ibm,xscom") {
+ /*
+ * our hdat parsing code may create NPU nodes with no
+ * links, so let's make sure we start from a clean
+ * state
+ */
+ npu = dt_find_by_name_addr(xscom, "npu", NPU_BASE);
+ if (npu)
+ dt_free(npu);
+
+ npu = dt_new_addr(xscom, "npu", NPU_BASE);
+ dt_add_property_cells(npu, "reg", NPU_BASE, NPU_SIZE);
+ dt_add_property_strings(npu, "compatible", "ibm,power9-npu");
+ dt_add_property_cells(npu, "ibm,npu-index", npu_index++);
+ dt_add_property_cells(npu, "ibm,npu-links", 2);
+
+ create_link(npu, 1, 2);
+ create_link(npu, 2, 3);
+ }
+}
+
+static bool zz_probe(void)
+{
+ /* FIXME: make this neater when the dust settles */
+ if (dt_node_is_compatible(dt_root, "ibm,zz-1s2u") ||
+ dt_node_is_compatible(dt_root, "ibm,zz-1s4u") ||
+ dt_node_is_compatible(dt_root, "ibm,zz-2s2u") ||
+ dt_node_is_compatible(dt_root, "ibm,zz-2s4u") ||
+ dt_node_is_compatible(dt_root, "ibm,zz-1s4u+gen4") ||
+ dt_node_is_compatible(dt_root, "ibm,zz-2s2u+gen4") ||
+ dt_node_is_compatible(dt_root, "ibm,zz-2s4u+gen4")) {
+
+ add_opencapi_dt_nodes();
+ return true;
+ }
+
+ /* Add Fleetwood FSP platform and map it to ZZ */
+ if (dt_node_is_compatible(dt_root, "ibm,fleetwood-m9s")) {
+ return true;
+ }
+
+ /* Add Denali FSP platform and map it to ZZ */
+ if (dt_node_is_compatible(dt_root, "ibm,denali")) {
+ return true;
+ }
+
+ return false;
+}
+
+static uint32_t ibm_fsp_occ_timeout(void)
+{
+ /* Use a fixed 60s value for now */
+ return 60;
+}
+
+static void zz_init(void)
+{
+ ibm_fsp_init();
+ hservice_fsp_init();
+}
+
+DECLARE_PLATFORM(zz) = {
+ .name = "ZZ",
+ .psi = &fsp_platform_psi,
+ .prd = &fsp_platform_prd,
+ .probe = zz_probe,
+ .init = zz_init,
+ .fast_reboot_init = fsp_console_reset,
+ .finalise_dt = ibm_fsp_finalise_dt,
+ .exit = ibm_fsp_exit,
+ .cec_power_down = ibm_fsp_cec_power_down,
+ .cec_reboot = ibm_fsp_cec_reboot,
+ .pci_setup_phb = firenze_pci_setup_phb,
+ .pci_get_slot_info = firenze_pci_get_slot_info,
+ .pci_add_loc_code = firenze_pci_add_loc_code,
+ .pci_probe_complete = firenze_pci_send_inventory,
+ .nvram_info = fsp_nvram_info,
+ .nvram_start_read = fsp_nvram_start_read,
+ .nvram_write = fsp_nvram_write,
+ .occ_timeout = ibm_fsp_occ_timeout,
+ .elog_commit = elog_fsp_commit,
+ .start_preload_resource = fsp_start_preload_resource,
+ .resource_loaded = fsp_resource_loaded,
+ .sensor_read = ibm_fsp_sensor_read,
+ .terminate = ibm_fsp_terminate,
+ .ocapi = &zz_ocapi,
+ .npu2_device_detect = npu2_i2c_presence_detect,
+ .op_display = fsp_op_display,
+ .vpd_iohub_load = vpd_iohub_load,
+ .heartbeat_time = fsp_heartbeat_time,
+};