blob: bfbba2d5f62f165248ff35ad8762795ce94b7042 [file] [log] [blame]
// 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();
ipmi_set_boot_count();
/*
* Booting into an OS that may not call back into skiboot for
* some time. Ensure all IPMI messages are processed first.
*/
ipmi_flush();
}
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,
};