aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/platforms/astbmc/common.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/platforms/astbmc/common.c')
-rw-r--r--roms/skiboot/platforms/astbmc/common.c564
1 files changed, 564 insertions, 0 deletions
diff --git a/roms/skiboot/platforms/astbmc/common.c b/roms/skiboot/platforms/astbmc/common.c
new file mode 100644
index 000000000..83ef70ad3
--- /dev/null
+++ b/roms/skiboot/platforms/astbmc/common.c
@@ -0,0 +1,564 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2013-2019 IBM Corp. */
+
+#include <skiboot.h>
+#include <device.h>
+#include <console.h>
+#include <psi.h>
+#include <chip.h>
+#include <xscom.h>
+#include <ast.h>
+#include <ipmi.h>
+#include <bt.h>
+#include <errorlog.h>
+#include <lpc.h>
+#include <timebase.h>
+
+#include "astbmc.h"
+
+/* UART1 config */
+#define UART_IO_BASE 0x3f8
+#define UART_IO_COUNT 8
+#define UART_LPC_IRQ 4
+
+/* BT config */
+#define BT_IO_BASE 0xe4
+#define BT_IO_COUNT 3
+#define BT_LPC_IRQ 10
+
+/* MBOX config */
+#define MBOX_IO_BASE 0x1000
+#define MBOX_IO_COUNT 6
+#define MBOX_LPC_IRQ 9
+
+void astbmc_ext_irq_serirq_cpld(unsigned int chip_id)
+{
+ lpc_all_interrupts(chip_id);
+}
+
+static void astbmc_ipmi_error(struct ipmi_msg *msg)
+{
+ prlog(PR_DEBUG, "ASTBMC: error sending msg. cc = %02x\n", msg->cc);
+
+ ipmi_free_msg(msg);
+}
+
+static void astbmc_ipmi_setenables(void)
+{
+ struct ipmi_msg *msg;
+
+ struct {
+ uint8_t oem2_en : 1;
+ uint8_t oem1_en : 1;
+ uint8_t oem0_en : 1;
+ uint8_t reserved : 1;
+ uint8_t sel_en : 1;
+ uint8_t msgbuf_en : 1;
+ uint8_t msgbuf_full_int_en : 1;
+ uint8_t rxmsg_queue_int_en : 1;
+ } data;
+
+ memset(&data, 0, sizeof(data));
+
+ /* The spec says we need to read-modify-write to not clobber
+ * the state of the other flags. These are set on by the bmc */
+ data.rxmsg_queue_int_en = 1;
+ data.sel_en = 1;
+
+ /* These are the ones we want to set on */
+ data.msgbuf_en = 1;
+
+ msg = ipmi_mkmsg_simple(IPMI_SET_ENABLES, &data, sizeof(data));
+ if (!msg) {
+ /**
+ * @fwts-label ASTBMCFailedSetEnables
+ * @fwts-advice AST BMC is likely to be non-functional
+ * when accessed from host.
+ */
+ prlog(PR_ERR, "ASTBMC: failed to set enables\n");
+ return;
+ }
+
+ msg->error = astbmc_ipmi_error;
+
+ ipmi_queue_msg(msg);
+
+}
+
+static int astbmc_fru_init(void)
+{
+ const struct dt_property *prop;
+ struct dt_node *node;
+ uint8_t fru_id;
+
+ node = dt_find_by_path(dt_root, "bmc");
+ if (!node)
+ return -1;
+
+ prop = dt_find_property(node, "firmware-fru-id");
+ if (!prop)
+ return -1;
+
+ fru_id = dt_property_get_cell(prop, 0) & 0xff;
+ ipmi_fru_init(fru_id);
+ return 0;
+}
+
+
+void astbmc_init(void)
+{
+ /* Register the BT interface with the IPMI layer
+ *
+ * Initialise this first to enable PNOR access
+ */
+ bt_init();
+
+ /* Initialize PNOR/NVRAM */
+ pnor_init();
+
+ /* Initialize elog */
+ elog_init();
+ ipmi_sel_init();
+ ipmi_wdt_init();
+ ipmi_rtc_init();
+ ipmi_opal_init();
+ astbmc_fru_init();
+ ipmi_sensor_init();
+
+ /* Request BMC information */
+ ipmi_get_bmc_info_request();
+
+ /* As soon as IPMI is up, inform BMC we are in "S0" */
+ ipmi_set_power_state(IPMI_PWR_SYS_S0_WORKING, IPMI_PWR_NOCHANGE);
+
+ /* Enable IPMI OEM message interrupts */
+ astbmc_ipmi_setenables();
+
+ ipmi_set_fw_progress_sensor(IPMI_FW_MOTHERBOARD_INIT);
+
+ /* Setup UART console for use by Linux via OPAL API */
+ set_opal_console(&uart_opal_con);
+}
+
+int64_t astbmc_ipmi_power_down(uint64_t request)
+{
+ if (request != IPMI_CHASSIS_PWR_DOWN) {
+ prlog(PR_WARNING, "PLAT: unexpected shutdown request %llx\n",
+ request);
+ }
+
+ return ipmi_chassis_control(request);
+}
+
+int64_t astbmc_ipmi_reboot(void)
+{
+ return ipmi_chassis_control(IPMI_CHASSIS_HARD_RESET);
+}
+
+void astbmc_seeprom_update(void)
+{
+ int flag_set, counter, rc;
+
+ rc = ipmi_get_chassis_boot_opt_request();
+
+ if (rc) {
+ prlog(PR_WARNING, "Failed to check SBE validation flag\n");
+ return;
+ }
+
+ flag_set = ipmi_chassis_check_sbe_validation();
+
+ if (flag_set <= 0) {
+ prlog(PR_DEBUG, "SBE validation flag unset or invalid\n");
+ return;
+ }
+
+ /*
+ * Flag is set, wait until SBE validation is complete and the flag
+ * has been reset.
+ */
+ prlog(PR_WARNING, "SBE validation required, waiting for completion\n");
+ prlog(PR_WARNING, "System will be powered off if validation fails\n");
+ counter = 0;
+
+ while (flag_set > 0) {
+ time_wait_ms(10000);
+ if (++counter % 3 == 0) {
+ /* Let the user know we're alive every 30s */
+ prlog(PR_WARNING, "waiting for completion...\n");
+ }
+ if (counter == 180) {
+ /* This is longer than expected and we have no way of
+ * checking if it's still running. Apologies if you
+ * ever see this message.
+ */
+ prlog(PR_WARNING, "30 minutes has elapsed, this is longer than expected for verification\n");
+ prlog(PR_WARNING, "If no progress is made a power reset of the BMC and Host may be required\n");
+ counter = 0;
+ }
+
+ /* As above, loop anyway if we fail to check the flag */
+ rc = ipmi_get_chassis_boot_opt_request();
+ if (rc == 0)
+ flag_set = ipmi_chassis_check_sbe_validation();
+ else
+ prlog(PR_WARNING, "Failed to check SBE validation flag\n");
+ }
+
+ /*
+ * The SBE validation can (will) leave the SBE in a bad state,
+ * preventing timers from working properly. Reboot so that we
+ * can boot normally with everything intact.
+ */
+ prlog(PR_WARNING, "SBE validation complete, rebooting\n");
+ if (platform.cec_reboot)
+ platform.cec_reboot();
+ else
+ abort();
+ while(true);
+}
+
+static void astbmc_fixup_dt_system_id(void)
+{
+ /* Make sure we don't already have one */
+ if (dt_find_property(dt_root, "system-id"))
+ return;
+
+ dt_add_property_strings(dt_root, "system-id", "unavailable");
+}
+
+static void astbmc_fixup_dt_bt(struct dt_node *lpc)
+{
+ struct dt_node *bt;
+ char namebuf[32];
+
+ /* First check if the BT interface is already there */
+ dt_for_each_child(lpc, bt) {
+ if (dt_node_is_compatible(bt, "bt"))
+ return;
+ }
+
+ snprintf(namebuf, sizeof(namebuf), "ipmi-bt@i%x", BT_IO_BASE);
+ bt = dt_new(lpc, namebuf);
+
+ dt_add_property_cells(bt, "reg",
+ 1, /* IO space */
+ BT_IO_BASE, BT_IO_COUNT);
+ dt_add_property_strings(bt, "compatible", "ipmi-bt");
+
+ /* Mark it as reserved to avoid Linux trying to claim it */
+ dt_add_property_strings(bt, "status", "reserved");
+
+ dt_add_property_cells(bt, "interrupts", BT_LPC_IRQ);
+ dt_add_property_cells(bt, "interrupt-parent", lpc->phandle);
+}
+
+static void astbmc_fixup_dt_mbox(struct dt_node *lpc)
+{
+ struct dt_node *mbox;
+ char namebuf[32];
+
+ if (!lpc)
+ return;
+
+ /*
+ * P9 machines always use hiomap, either by ipmi or mbox. P8 machines
+ * can indicate they support mbox using the scratch register, or ipmi
+ * by configuring the hiomap ipmi command. If neither are configured
+ * for P8 then skiboot will drive the flash controller directly.
+ * XXX P10
+ */
+ if (proc_gen == proc_gen_p8 && !ast_scratch_reg_is_mbox())
+ return;
+
+ /* First check if the mbox interface is already there */
+ dt_for_each_child(lpc, mbox) {
+ if (dt_node_is_compatible(mbox, "mbox"))
+ return;
+ }
+
+ snprintf(namebuf, sizeof(namebuf), "mbox@i%x", MBOX_IO_BASE);
+ mbox = dt_new(lpc, namebuf);
+
+ dt_add_property_cells(mbox, "reg",
+ 1, /* IO space */
+ MBOX_IO_BASE, MBOX_IO_COUNT);
+ dt_add_property_strings(mbox, "compatible", "mbox");
+
+ /* Mark it as reserved to avoid Linux trying to claim it */
+ dt_add_property_strings(mbox, "status", "reserved");
+
+ dt_add_property_cells(mbox, "interrupts", MBOX_LPC_IRQ);
+ dt_add_property_cells(mbox, "interrupt-parent", lpc->phandle);
+}
+
+static void astbmc_fixup_dt_uart(struct dt_node *lpc)
+{
+ /*
+ * The official OF ISA/LPC binding is a bit odd, it prefixes
+ * the unit address for IO with "i". It uses 2 cells, the first
+ * one indicating IO vs. Memory space (along with bits to
+ * represent aliasing).
+ *
+ * We pickup that binding and add to it "2" as a indication
+ * of FW space.
+ */
+ struct dt_node *uart;
+ char namebuf[32];
+
+ /* First check if the UART is already there */
+ dt_for_each_child(lpc, uart) {
+ if (dt_node_is_compatible(uart, "ns16550"))
+ return;
+ }
+
+ /* Otherwise, add a node for it */
+ snprintf(namebuf, sizeof(namebuf), "serial@i%x", UART_IO_BASE);
+ uart = dt_new(lpc, namebuf);
+
+ dt_add_property_cells(uart, "reg",
+ 1, /* IO space */
+ UART_IO_BASE, UART_IO_COUNT);
+ dt_add_property_strings(uart, "compatible",
+ "ns16550",
+ "pnpPNP,501");
+ dt_add_property_cells(uart, "clock-frequency", 1843200);
+ dt_add_property_cells(uart, "current-speed", 115200);
+
+ /*
+ * This is needed by Linux for some obscure reasons,
+ * we'll eventually need to sanitize it but in the meantime
+ * let's make sure it's there
+ */
+ dt_add_property_strings(uart, "device_type", "serial");
+
+ /* Add interrupt */
+ dt_add_property_cells(uart, "interrupts", UART_LPC_IRQ);
+ dt_add_property_cells(uart, "interrupt-parent", lpc->phandle);
+}
+
+static void del_compatible(struct dt_node *node)
+{
+ struct dt_property *prop;
+
+ prop = __dt_find_property(node, "compatible");
+ if (prop)
+ dt_del_property(node, prop);
+}
+
+
+static void astbmc_fixup_bmc_sensors(void)
+{
+ struct dt_node *parent, *node;
+
+ parent = dt_find_by_path(dt_root, "bmc");
+ if (!parent)
+ return;
+ del_compatible(parent);
+
+ parent = dt_find_by_name(parent, "sensors");
+ if (!parent)
+ return;
+ del_compatible(parent);
+
+ dt_for_each_child(parent, node) {
+ if (dt_find_property(node, "compatible"))
+ continue;
+ dt_add_property_string(node, "compatible", "ibm,ipmi-sensor");
+ }
+}
+
+static struct dt_node *dt_find_primary_lpc(void)
+{
+ struct dt_node *n, *primary_lpc = NULL;
+
+ /* Find the primary LPC bus */
+ dt_for_each_compatible(dt_root, n, "ibm,power8-lpc") {
+ if (!primary_lpc || dt_has_node_property(n, "primary", NULL))
+ primary_lpc = n;
+ if (dt_has_node_property(n, "#address-cells", NULL))
+ break;
+ }
+ dt_for_each_compatible(dt_root, n, "ibm,power9-lpc") {
+ if (!primary_lpc || dt_has_node_property(n, "primary", NULL))
+ primary_lpc = n;
+ if (dt_has_node_property(n, "#address-cells", NULL))
+ break;
+ }
+
+ return primary_lpc;
+}
+
+static void astbmc_fixup_dt(void)
+{
+ struct dt_node *primary_lpc;
+
+ primary_lpc = dt_find_primary_lpc();
+
+ if (!primary_lpc)
+ return;
+
+ /* Fixup the UART, that might be missing from HB */
+ astbmc_fixup_dt_uart(primary_lpc);
+
+ /* BT is not in HB either */
+ astbmc_fixup_dt_bt(primary_lpc);
+
+ /* The pel logging code needs a system-id property to work so
+ make sure we have one. */
+ astbmc_fixup_dt_system_id();
+
+ if (proc_gen == proc_gen_p8)
+ astbmc_fixup_bmc_sensors();
+}
+
+static void astbmc_fixup_psi_bar(void)
+{
+ struct proc_chip *chip = next_chip(NULL);
+ uint64_t psibar;
+
+ /* This is P8 specific */
+ if (proc_gen != proc_gen_p8)
+ return;
+
+ /* Read PSI BAR */
+ if (xscom_read(chip->id, 0x201090A, &psibar)) {
+ prerror("PLAT: Error reading PSI BAR\n");
+ return;
+ }
+ /* Already configured, bail out */
+ if (psibar & 1)
+ return;
+
+ /* Hard wire ... yuck */
+ psibar = 0x3fffe80000001UL;
+
+ printf("PLAT: Fixing up PSI BAR on chip %d BAR=%llx\n",
+ chip->id, psibar);
+
+ /* Now write it */
+ xscom_write(chip->id, 0x201090A, psibar);
+}
+
+static void astbmc_fixup_uart(void)
+{
+ /*
+ * Depending on which image we are running, it may be configuring the
+ * virtual UART or not. Check if VUART is enabled and use SIO if not.
+ * We also correct the configuration of VUART as some BMC images don't
+ * setup the interrupt properly
+ */
+ if (ast_is_vuart1_enabled()) {
+ printf("PLAT: Using virtual UART\n");
+ ast_disable_sio_uart1();
+ ast_setup_vuart1(UART_IO_BASE, UART_LPC_IRQ);
+ } else {
+ printf("PLAT: Using SuperIO UART\n");
+ ast_setup_sio_uart1(UART_IO_BASE, UART_LPC_IRQ);
+ }
+}
+
+void astbmc_early_init(void)
+{
+ /* Hostboot's device-tree isn't quite right yet */
+ astbmc_fixup_dt();
+
+ /* Hostboot forgets to populate the PSI BAR */
+ astbmc_fixup_psi_bar();
+
+ if (ast_sio_init()) {
+ if (ast_io_init()) {
+ astbmc_fixup_uart();
+ ast_setup_ibt(BT_IO_BASE, BT_LPC_IRQ);
+ } else
+ prerror("PLAT: AST IO initialisation failed!\n");
+
+ /*
+ * P9 prefers IPMI for HIOMAP but will use MBOX if IPMI is not
+ * supported. P8 either uses IPMI HIOMAP or direct IO, and
+ * never MBOX. Thus only populate the MBOX node on P9 to allow
+ * fallback.
+ */
+ if (proc_gen >= proc_gen_p9) {
+ astbmc_fixup_dt_mbox(dt_find_primary_lpc());
+ ast_setup_sio_mbox(MBOX_IO_BASE, MBOX_LPC_IRQ);
+ }
+ } else {
+ /*
+ * This may or may not be an error depending on if we set up
+ * hiomap or not. In the old days it *was* an error, but now
+ * with the way we configure the BMC hardware, this is actually
+ * the not error case.
+ */
+ prlog(PR_INFO, "PLAT: AST SIO unavailable!\n");
+ }
+
+ /* Setup UART and use it as console */
+ uart_init();
+
+ prd_init();
+}
+
+void astbmc_exit(void)
+{
+ ipmi_wdt_final_reset();
+}
+
+static const struct bmc_sw_config bmc_sw_ami = {
+ .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0),
+ .ipmi_oem_pnor_access_status = IPMI_CODE(0x3a, 0x07),
+ .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a),
+};
+
+static const struct bmc_sw_config bmc_sw_openbmc = {
+ .ipmi_oem_partial_add_esel = IPMI_CODE(0x3a, 0xf0),
+ .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a),
+};
+
+/* Extracted from a Palmetto */
+const struct bmc_hw_config bmc_hw_ast2400 = {
+ .scu_revision_id = 0x2010303,
+ .mcr_configuration = 0x00000577,
+ .mcr_scu_mpll = 0x000050c0,
+ .mcr_scu_strap = 0x00000000,
+};
+
+/* Extracted from a Witherspoon */
+const struct bmc_hw_config bmc_hw_ast2500 = {
+ .scu_revision_id = 0x04030303,
+ .mcr_configuration = 0x11200756,
+ .mcr_scu_mpll = 0x000071C1,
+ .mcr_scu_strap = 0x00000000,
+};
+
+/* XXX P10: Update with Rainier values */
+const struct bmc_hw_config bmc_hw_ast2600 = {
+ .scu_revision_id = 0x05000303,
+ .mcr_configuration = 0x11200756,
+ .mcr_scu_mpll = 0x1008405F,
+ .mcr_scu_strap = 0x000030E0,
+};
+
+const struct bmc_platform bmc_plat_ast2400_ami = {
+ .name = "ast2400:ami",
+ .hw = &bmc_hw_ast2400,
+ .sw = &bmc_sw_ami,
+};
+
+const struct bmc_platform bmc_plat_ast2500_ami = {
+ .name = "ast2500:ami",
+ .hw = &bmc_hw_ast2500,
+ .sw = &bmc_sw_ami,
+};
+
+const struct bmc_platform bmc_plat_ast2500_openbmc = {
+ .name = "ast2500:openbmc",
+ .hw = &bmc_hw_ast2500,
+ .sw = &bmc_sw_openbmc,
+};
+
+const struct bmc_platform bmc_plat_ast2600_openbmc = {
+ .name = "ast2600:openbmc",
+ .hw = &bmc_hw_ast2600,
+ .sw = &bmc_sw_openbmc,
+};