566 lines
14 KiB
C
566 lines
14 KiB
C
/* Copyright 2013-2016 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
|
|
#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.
|
|
*/
|
|
if (proc_gen != proc_gen_p9 && !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();
|
|
|
|
/* Send external interrupts to me */
|
|
psi_set_external_irq_policy(EXTERNAL_IRQ_POLICY_SKIBOOT);
|
|
|
|
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();
|
|
}
|
|
|
|
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),
|
|
};
|
|
|
|
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,
|
|
};
|
|
|
|
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,
|
|
};
|