chrome-ec/board/cr50/board.c

1693 lines
48 KiB
C

/* Copyright 2014 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "board_id.h"
#include "ccd_config.h"
#include "clock.h"
#include "closed_source_set1.h"
#include "common.h"
#include "console.h"
#include "dcrypto/dcrypto.h"
#include "ec_version.h"
#include "endian.h"
#include "extension.h"
#include "flash.h"
#include "flash_config.h"
#include "gpio.h"
#include "ite_sync.h"
#include "hooks.h"
#include "i2c.h"
#include "i2cs.h"
#include "init_chip.h"
#include "nvmem.h"
#include "nvmem_vars.h"
#include "rbox.h"
#include "rdd.h"
#include "recovery_button.h"
#include "registers.h"
#include "scratch_reg1.h"
#include "signed_header.h"
#include "spi.h"
#include "system.h"
#include "system_chip.h"
#include "task.h"
#include "tpm_registers.h"
#include "trng.h"
#include "uart_bitbang.h"
#include "uartn.h"
#include "usart.h"
#include "usb_descriptor.h"
#include "usb_hid.h"
#include "usb_i2c.h"
#include "usb_spi.h"
#include "util.h"
#include "wp.h"
/* Define interrupt and gpio structs */
#include "gpio_list.h"
#include "cryptoc/sha.h"
/*
* Need to include Implementation.h here to make sure that NVRAM size
* definitions match across different git repos.
*
* MAX() definition from include/utils.h does not work in Implementation.h, as
* it is used in a preprocessor expression there;
*
* SHA_DIGEST_SIZE is defined to be the same in both git repos, but using
* different expressions.
*
* To untangle compiler errors let's just undefine MAX() and SHA_DIGEST_SIZE
* here, as nether is necessary in this case: all we want from
* Implementation.h at this point is the definition for NV_MEMORY_SIZE.
*/
#undef MAX
#undef SHA_DIGEST_SIZE
#include "Implementation.h"
#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
#define NVMEM_TPM_SIZE ((sizeof((struct nvmem_partition *)0)->buffer) \
- NVMEM_CR50_SIZE)
/*
* Make sure NV memory size definition in Implementation.h matches reality. It
* should be set to
*
* NVMEM_PARTITION_SIZE - NVMEM_CR50_SIZE - 8
*
* Both of these macros are defined in board.h.
*/
BUILD_ASSERT(NVMEM_TPM_SIZE == NV_MEMORY_SIZE);
/* NvMem user buffer lengths table */
uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = {
NVMEM_TPM_SIZE,
NVMEM_CR50_SIZE
};
/* Board specific configuration settings */
static uint32_t board_properties; /* Mainly used as a cache for strap config. */
static uint8_t reboot_request_posted;
/* Which UARTs we'd like to be able to bitbang. */
struct uart_bitbang_properties bitbang_config = {
.uart = UART_EC,
.tx_gpio = GPIO_DETECT_SERVO, /* This is TX to EC console. */
.rx_gpio = GPIO_EC_TX_CR50_RX,
.rx_irq = GC_IRQNUM_GPIO1_GPIO11INT, /* Must match gpoi.inc */
/*
* The rx/tx_pinmux_regval values MUST agree with the pin config for
* both the TX and RX GPIOs in gpio.inc. Don't change one without
* changing the other.
*/
.tx_pinmux_reg = GBASE(PINMUX) + GOFFSET(PINMUX, DIOB5_SEL),
.tx_pinmux_regval = GC_PINMUX_GPIO1_GPIO3_SEL,
.rx_pinmux_reg = GBASE(PINMUX) + GOFFSET(PINMUX, DIOB6_SEL),
.rx_pinmux_regval = GC_PINMUX_GPIO1_GPIO11_SEL,
};
DECLARE_IRQ(GC_IRQNUM_GPIO1_GPIO11INT, uart_bitbang_irq, 0);
const char *device_state_names[] = {
"init",
"init_debouncing",
"init_rx_only",
"disconnected",
"off",
"undetectable",
"connected",
"on",
"debouncing",
"unknown",
"ignored"
};
BUILD_ASSERT(ARRAY_SIZE(device_state_names) == DEVICE_STATE_COUNT);
const char *device_state_name(enum device_state state)
{
if (state >= 0 && state < DEVICE_STATE_COUNT)
return device_state_names[state];
else
return "?";
}
int board_use_plt_rst(void)
{
return !!(board_properties & BOARD_USE_PLT_RESET);
}
/* Allow enabling deep sleep if the board supports it. */
int board_deep_sleep_allowed(void)
{
return !(board_properties & BOARD_DEEP_SLEEP_DISABLED);
}
int board_rst_pullup_needed(void)
{
return !!(board_properties & BOARD_NEEDS_SYS_RST_PULL_UP);
}
int board_tpm_uses_i2c(void)
{
return !!(board_properties & BOARD_SLAVE_CONFIG_I2C);
}
int board_tpm_uses_spi(void)
{
return !!(board_properties & BOARD_SLAVE_CONFIG_SPI);
}
int board_uses_closed_source_set1(void)
{
return !!(board_properties & BOARD_CLOSED_SOURCE_SET1);
}
int board_uses_closed_loop_reset(void)
{
return !!(board_properties & BOARD_CLOSED_LOOP_RESET);
}
int board_has_ina_support(void)
{
return !(board_properties & BOARD_NO_INA_SUPPORT);
}
int board_tpm_mode_change_allowed(void)
{
return !!(board_properties & BOARD_ALLOW_CHANGE_TPM_MODE);
}
/* Get header address of the backup RW copy. */
const struct SignedHeader *get_other_rw_addr(void)
{
if (system_get_image_copy() == SYSTEM_IMAGE_RW)
return (const struct SignedHeader *)
get_program_memory_addr(SYSTEM_IMAGE_RW_B);
return (const struct SignedHeader *)
get_program_memory_addr(SYSTEM_IMAGE_RW);
}
/* Return true if the other RW is not ready to run. */
static int other_rw_is_inactive(void)
{
const struct SignedHeader *header = get_other_rw_addr();
return !!(header->image_size & TOP_IMAGE_SIZE_BIT);
}
/* I2C Port definition */
const struct i2c_port_t i2c_ports[] = {
{"master", I2C_PORT_MASTER, 100,
GPIO_I2C_SCL_INA, GPIO_I2C_SDA_INA},
};
const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports);
/* Strapping pin info structure */
#define STRAP_PIN_DELAY_USEC 100
enum strap_list {
a0,
a1,
b0,
b1,
};
struct strap_desc {
/* GPIO enum from gpio.inc for the strap pin */
uint8_t gpio_signal;
/* Offset into pinmux register section for pad SEL register */
uint8_t sel_offset;
/* Entry in the pinmux peripheral selector table for pad */
uint8_t pad_select;
const char *pad_name;
};
struct board_cfg {
/* Value the strap pins should read for a given board */
uint8_t strap_cfg;
/* Properties required for a given board */
uint32_t board_properties;
};
/*
* This table contains both the GPIO and pad specific information required to
* configure each strapping pin to be either a GPIO input or output.
*/
const struct strap_desc strap_regs[] = {
{GPIO_STRAP_A0, GOFFSET(PINMUX, DIOA1_SEL), GC_PINMUX_DIOA1_SEL, "a1"},
{GPIO_STRAP_A1, GOFFSET(PINMUX, DIOA9_SEL), GC_PINMUX_DIOA9_SEL, "a9"},
{GPIO_STRAP_B0, GOFFSET(PINMUX, DIOA6_SEL), GC_PINMUX_DIOA6_SEL, "a6"},
{GPIO_STRAP_B1, GOFFSET(PINMUX, DIOA12_SEL), GC_PINMUX_DIOA12_SEL,
"a12"},
};
#define BOARD_PROPERTIES_DEFAULT (BOARD_SLAVE_CONFIG_I2C | BOARD_USE_PLT_RESET)
static struct board_cfg board_cfg_table[] = {
/* SPI Variants: DIOA12 = 1M PD, DIOA6 = 1M PD */
/* Kevin/Gru: DI0A9 = 5k PD, DIOA1 = 1M PU */
{
.strap_cfg = 0x02,
.board_properties = BOARD_SLAVE_CONFIG_SPI |
BOARD_NEEDS_SYS_RST_PULL_UP,
},
/* Poppy: DI0A9 = 1M PU, DIOA1 = 1M PU */
{
.strap_cfg = 0x0A,
.board_properties = BOARD_SLAVE_CONFIG_SPI |
BOARD_USE_PLT_RESET,
},
/* Mistral: DI0A9 = 1M PU, DIOA1 = 5k PU */
{
.strap_cfg = 0x0B,
.board_properties = BOARD_SLAVE_CONFIG_SPI |
BOARD_USE_PLT_RESET | BOARD_NO_INA_SUPPORT |
BOARD_CLOSED_LOOP_RESET,
},
/* Kukui: DI0A9 = 5k PU, DIOA1 = 5k PU */
{
.strap_cfg = 0x0F,
.board_properties = BOARD_SLAVE_CONFIG_SPI |
BOARD_USE_PLT_RESET,
},
/* I2C Variants: DIOA9 = 1M PD, DIOA1 = 1M PD */
/* Reef/Eve: DIOA12 = 5k PD, DIOA6 = 1M PU */
{
.strap_cfg = 0x20,
.board_properties = BOARD_SLAVE_CONFIG_I2C |
BOARD_USE_PLT_RESET,
},
/* Rowan: DIOA12 = 5k PD, DIOA6 = 5k PU */
{
.strap_cfg = 0x30,
.board_properties = BOARD_SLAVE_CONFIG_I2C |
BOARD_DEEP_SLEEP_DISABLED | BOARD_DETECT_AP_WITH_UART,
},
/* Sarien/Arcada: DIOA12 = 1M PD, DIOA6 = 5k PU */
{
.strap_cfg = 0x70,
.board_properties = BOARD_SLAVE_CONFIG_I2C |
BOARD_USE_PLT_RESET | BOARD_WP_DISABLE_DELAY |
BOARD_CLOSED_SOURCE_SET1 | BOARD_NO_INA_SUPPORT |
BOARD_ALLOW_CHANGE_TPM_MODE,
},
};
void post_reboot_request(void)
{
/* Reboot the device next time TPM reset is requested. */
reboot_request_posted = 1;
}
/*****************************************************************************/
/* */
/*
* Battery cutoff monitor is needed on the devices where hardware alone does
* not provide proper battery cutoff functionality.
*
* The sequence is as follows: set up an interrupt to react to the charger
* disconnect event. When the interrupt happens observe status of the buttons
* connected to PWRB_IN and KEY0_IN.
*
* If both are pressed, start the 5 second timeout, while keeping monitoring
* the charger connection state. If it remains disconnected for the entire
* duration - generate 5 second pulses on EC_RST_L and BAT_EN outputs.
*
* In reality the BAT_EN output pulse will cause the complete power cut off,
* so strictly speaking the code does not need to do anything once BAT_EN
* output is deasserted.
*/
/* Time to wait before initiating battery cutoff procedure. */
#define CUTOFF_TIMEOUT_US (5 * SECOND)
/* A timeout hook to run in the end of the 5 s interval. */
static void ac_stayed_disconnected(void)
{
uint32_t saved_override_state;
CPRINTS("%s", __func__);
/* assert EC_RST_L and deassert BAT_EN */
GREG32(RBOX, ASSERT_EC_RST) = 1;
/*
* BAT_EN needs to use the RBOX override ability, bit 1 is battery
* disable bit.
*/
saved_override_state = GREG32(RBOX, OVERRIDE_OUTPUT);
GWRITE_FIELD(RBOX, OVERRIDE_OUTPUT, VAL, 0); /* Setting it to zero. */
GWRITE_FIELD(RBOX, OVERRIDE_OUTPUT, OEN, 1);
GWRITE_FIELD(RBOX, OVERRIDE_OUTPUT, EN, 1);
msleep(5000);
/*
* The system was supposed to be shut down the moment battery
* disconnect was asserted, but if we made it here we might as well
* restore the original state.
*/
GREG32(RBOX, OVERRIDE_OUTPUT) = saved_override_state;
GREG32(RBOX, ASSERT_EC_RST) = 0;
}
DECLARE_DEFERRED(ac_stayed_disconnected);
/*
* Just a shortcut to make use of these AC power interrupt states better
* readable. RED means rising edge and FED means falling edge.
*/
enum {
ac_pres_red = GC_RBOX_INT_STATE_INTR_AC_PRESENT_RED_MASK,
ac_pres_fed = GC_RBOX_INT_STATE_INTR_AC_PRESENT_FED_MASK,
buttons_not_pressed = GC_RBOX_CHECK_INPUT_KEY0_IN_MASK |
GC_RBOX_CHECK_INPUT_PWRB_IN_MASK
};
/*
* ISR reacting to both falling and raising edges of the AC_PRESENT signal.
* Falling edge indicates AC no longer present (removal of the charger cable)
* and rising edge indicates AP present (insertion of charger cable).
*/
static void ac_power_state_changed(void)
{
uint32_t req;
/* Get current status and clear it. */
req = GREG32(RBOX, INT_STATE) & (ac_pres_red | ac_pres_fed);
GREG32(RBOX, INT_STATE) = req;
CPRINTS("AC: %c%c",
req & ac_pres_red ? 'R' : '-',
req & ac_pres_fed ? 'F' : '-');
/* Delay sleep so RDD state machines can stabilize */
delay_sleep_by(5 * SECOND);
/* The remaining code is only used for battery cutoff */
if (!system_battery_cutoff_support_required())
return;
/* Raising edge gets priority, stop timeout timer and go. */
if (req & ac_pres_red) {
hook_call_deferred(&ac_stayed_disconnected_data, -1);
return;
}
/*
* If this is not a falling edge, or either of the buttons is not
* pressed - bail out.
*/
if (!(req & ac_pres_fed) ||
(GREG32(RBOX, CHECK_INPUT) & buttons_not_pressed))
return;
/*
* Charger cable was yanked while the power and key0 buttons were kept
* pressed - user wants a battery cut off.
*/
hook_call_deferred(&ac_stayed_disconnected_data, CUTOFF_TIMEOUT_US);
}
DECLARE_IRQ(GC_IRQNUM_RBOX0_INTR_AC_PRESENT_RED_INT, ac_power_state_changed, 1);
DECLARE_IRQ(GC_IRQNUM_RBOX0_INTR_AC_PRESENT_FED_INT, ac_power_state_changed, 1);
/* Enable interrupts on plugging in and yanking out of the charger cable. */
static void init_ac_detect(void)
{
/* It is set in idle.c also. */
GWRITE_FIELD(RBOX, WAKEUP, ENABLE, 1);
GWRITE_FIELD(RBOX, INT_ENABLE, INTR_AC_PRESENT_RED, 1);
GWRITE_FIELD(RBOX, INT_ENABLE, INTR_AC_PRESENT_FED, 1);
task_enable_irq(GC_IRQNUM_RBOX0_INTR_AC_PRESENT_RED_INT);
task_enable_irq(GC_IRQNUM_RBOX0_INTR_AC_PRESENT_FED_INT);
}
/* */
/*****************************************************************************/
/*
* There's no way to trigger on both rising and falling edges, so force a
* compiler error if we try. The workaround is to use the pinmux to connect
* two GPIOs to the same input and configure each one for a separate edge.
*/
#define GPIO_INT(name, pin, flags, signal) \
BUILD_ASSERT(((flags) & GPIO_INT_BOTH) != GPIO_INT_BOTH);
#include "gpio.wrap"
/**
* Reset wake logic
*
* If any wake pins are edge triggered, the pad logic latches the wakeup. Clear
* and restore EXITEN0 to reset the wakeup logic.
*/
static void reset_wake_logic(void)
{
uint32_t exiten = GREG32(PINMUX, EXITEN0);
GREG32(PINMUX, EXITEN0) = 0;
GREG32(PINMUX, EXITEN0) = exiten;
}
static void init_pmu(void)
{
clock_enable_module(MODULE_PMU, 1);
/* This boot sequence may be a result of previous soft reset,
* in which case the PMU low power sequence register needs to
* be reset. */
GREG32(PMU, LOW_POWER_DIS) = 0;
/* Enable wakeup interrupt */
task_enable_irq(GC_IRQNUM_PMU_INTR_WAKEUP_INT);
GWRITE_FIELD(PMU, INT_ENABLE, INTR_WAKEUP, 1);
}
void pmu_wakeup_interrupt(void)
{
int wakeup_src;
static uint8_t count;
static uint8_t ws;
static uint8_t line_length;
static const char wheel[] = { '|', '/', '-', '\\' };
delay_sleep_by(1 * MSEC);
wakeup_src = GR_PMU_EXITPD_SRC;
/* Clear interrupt state */
GWRITE_FIELD(PMU, INT_STATE, INTR_WAKEUP, 1);
/* Clear pmu reset */
GWRITE(PMU, CLRRST, 1);
/*
* This will print the next state of the "rotating wheel" every time
* cr50 resumes from regular sleep (8 is the ASCII code for
* 'backspace'). Each time wake source changes, its hex value is
* printed out preceded by a space.
*
* In steady state when there is no other activity Cr50 wakes up every
* half second for HOOK_TICK, so that is the rate the wheel will be
* spinning at when device is idle.
*/
if (ws == wakeup_src) {
ccprintf("%c%c%c%2x%c", 8, 8, 8, ws,
wheel[count++ % sizeof(wheel)]);
} else {
ws = wakeup_src;
line_length += 3;
if (line_length > 50) {
ccprintf("\n");
line_length = 0;
}
ccprintf(" %2x ", wakeup_src);
}
if (wakeup_src & GC_PMU_EXITPD_SRC_RBOX_WAKEUP_MASK)
rbox_clear_wakeup();
/* Disable rbox wakeup. It will be reenabled before entering sleep. */
GREG32(RBOX, WAKEUP) = 0;
if (wakeup_src & GC_PMU_EXITPD_SRC_PIN_PD_EXIT_MASK) {
reset_wake_logic();
/*
* Delay sleep long enough for a SPI slave transaction to start
* or for the system to be reset.
*/
delay_sleep_by(5 * SECOND);
}
/* Trigger timer0 interrupt */
if (wakeup_src & GC_PMU_EXITPD_SRC_TIMELS0_PD_EXIT_TIMER0_MASK)
task_trigger_irq(GC_IRQNUM_TIMELS0_TIMINT0);
/* Trigger timer1 interrupt */
if (wakeup_src & GC_PMU_EXITPD_SRC_TIMELS0_PD_EXIT_TIMER1_MASK)
task_trigger_irq(GC_IRQNUM_TIMELS0_TIMINT1);
}
DECLARE_IRQ(GC_IRQNUM_PMU_INTR_WAKEUP_INT, pmu_wakeup_interrupt, 1);
void board_configure_deep_sleep_wakepins(void)
{
/*
* Disable the i2c and spi slave wake sources since the TPM is
* not being used and reenable them in their init functions on
* resume.
*/
GWRITE_FIELD(PINMUX, EXITEN0, DIOA12, 0); /* SPS_CS_L */
GWRITE_FIELD(PINMUX, EXITEN0, DIOA1, 0); /* I2CS_SDA */
GWRITE_FIELD(PINMUX, EXITEN0, DIOA9, 0); /* I2CS_SCL */
/* Remove the pulldown on EC uart tx and disable the input */
GWRITE_FIELD(PINMUX, DIOB5_CTL, PD, 0);
GWRITE_FIELD(PINMUX, DIOB5_CTL, IE, 0);
/*
* Configure the TPM_RST_L signal as wake on high. There is a
* requirement the tpm reset has to remain asserted when cr50 should
* be in deep sleep, so cr50 should not wake up until it goes high.
*
* Whether it is a short pulse or long one waking on the high level is
* fine, because the goal of TPM_RST_L is to reset the TPM and after
* resuming from deep sleep the TPM will be reset. Cr50 doesn't need to
* read the low value and then reset.
*/
if (board_use_plt_rst()) {
/* Configure plt_rst_l to wake on high */
/* Disable plt_rst_l as a wake pin */
GWRITE_FIELD(PINMUX, EXITEN0, DIOM3, 0);
/* Reconfigure the pin */
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOM3, 0); /* level sensitive */
GWRITE_FIELD(PINMUX, EXITINV0, DIOM3, 0); /* wake on high */
/* enable powerdown exit */
GWRITE_FIELD(PINMUX, EXITEN0, DIOM3, 1);
} else {
/* Configure plt_rst_l to wake on high */
/* Disable sys_rst_l as a wake pin */
GWRITE_FIELD(PINMUX, EXITEN0, DIOM0, 0);
/* Reconfigure the pin */
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOM0, 0); /* level sensitive */
GWRITE_FIELD(PINMUX, EXITINV0, DIOM0, 0); /* wake on high */
/* enable powerdown exit */
GWRITE_FIELD(PINMUX, EXITEN0, DIOM0, 1);
}
}
static void deferred_tpm_rst_isr(void);
DECLARE_DEFERRED(deferred_tpm_rst_isr);
static void configure_board_specific_gpios(void)
{
/* Add a pullup to sys_rst_l */
if (board_rst_pullup_needed())
GWRITE_FIELD(PINMUX, DIOM0_CTL, PU, 1);
/*
* Connect either plt_rst_l or sys_rst_l to GPIO_TPM_RST_L based on the
* board type. This signal is used to monitor AP resets and reset the
* TPM.
*
* Also configure these pins to be wake triggers on the rising edge,
* this will apply to regular sleep only, entering deep sleep would
* reconfigure this.
*
* plt_rst_l is on diom3, and sys_rst_l is on diom0.
*/
if (board_use_plt_rst()) {
/* Use plt_rst_l as the tpm reset signal. */
/* Select for TPM_RST_L */
GWRITE(PINMUX, GPIO1_GPIO0_SEL, GC_PINMUX_DIOM3_SEL);
/* Select for DETECT_TPM_RST_L_ASSERTED */
GWRITE(PINMUX, GPIO1_GPIO4_SEL, GC_PINMUX_DIOM3_SEL);
/* Enable the input */
GWRITE_FIELD(PINMUX, DIOM3_CTL, IE, 1);
/*
* Make plt_rst_l routed to DIOM3 a low level sensitive wake
* source. This way when a plt_rst_l pulse comes along while
* H1 is in sleep, the H1 wakes from sleep first, enabling all
* necessary clocks, and becomes ready to generate an
* interrupt on the rising edge of plt_rst_l.
*
* It takes at most 150 us to wake up, and the pulse is at
* least 1ms long.
*/
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOM3, 0);
GWRITE_FIELD(PINMUX, EXITINV0, DIOM3, 1);
/* Enable powerdown exit on DIOM3 */
GWRITE_FIELD(PINMUX, EXITEN0, DIOM3, 1);
} else {
/* Use sys_rst_l as the tpm reset signal. */
/* Select for TPM_RST_L */
GWRITE(PINMUX, GPIO1_GPIO0_SEL, GC_PINMUX_DIOM0_SEL);
/* Select for DETECT_TPM_RST_L_ASSERTED */
GWRITE(PINMUX, GPIO1_GPIO4_SEL, GC_PINMUX_DIOM0_SEL);
/* Enable the input */
GWRITE_FIELD(PINMUX, DIOM0_CTL, IE, 1);
/* Set to be level sensitive */
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOM0, 0);
/* wake on low */
GWRITE_FIELD(PINMUX, EXITINV0, DIOM0, 1);
/* Enable powerdown exit on DIOM0 */
GWRITE_FIELD(PINMUX, EXITEN0, DIOM0, 1);
}
if (board_uses_closed_source_set1())
closed_source_set1_configure_gpios();
}
static uint8_t mismatched_board_id;
int board_id_is_mismatched(void)
{
return !!mismatched_board_id;
}
static void check_board_id_mismatch(void)
{
if (!board_id_mismatch(NULL))
return;
if (system_rollback_detected()) {
/*
* We are in a rollback, the other image must be no good.
* Let's keep going with the TPM disabled, only updates will
* be allowed.
*/
mismatched_board_id = 1;
ccprintf("Board ID mismatched, but can not reboot.\n");
/* Force CCD disabled */
ccd_disable();
return;
}
system_ensure_rollback();
ccprintf("Rebooting due to board ID mismatch\n");
cflush();
system_reset(0);
}
/*
* Check if ITE SYNC sequence generation was requested before the reset, if so
* - clear the request and call the function to generate the sequence.
*/
static void maybe_trigger_ite_sync(void)
{
uint32_t lls1;
lls1 = GREG32(PMU, LONG_LIFE_SCRATCH1);
if (!(lls1 & BOARD_ITE_EC_SYNC_NEEDED))
return;
/* Clear the sync required bit, this should work only once. */
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 1);
GREG32(PMU, LONG_LIFE_SCRATCH1) = lls1 & ~BOARD_ITE_EC_SYNC_NEEDED;
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 0);
generate_ite_sync();
}
/* Initialize board. */
static void board_init(void)
{
#ifdef CR50_DEV
static enum ccd_state ccd_init_state = CCD_STATE_OPENED;
#else
static enum ccd_state ccd_init_state = CCD_STATE_LOCKED;
#endif
/*
* Deep sleep resets should be considered valid and should not impact
* the rolling reboot count.
*/
if (system_get_reset_flags() & RESET_FLAG_HIBERNATE)
system_decrement_retry_counter();
configure_board_specific_gpios();
init_pmu();
reset_wake_logic();
init_trng();
maybe_trigger_ite_sync();
init_jittery_clock(1);
init_runlevel(PERMISSION_MEDIUM);
/* Initialize NvMem partitions */
nvmem_init();
/*
* If this was a low power wake and not a rollback, restore the ccd
* state from the long-life register.
*/
if ((system_get_reset_flags() & RESET_FLAG_HIBERNATE) &&
!system_rollback_detected()) {
ccd_init_state = (GREG32(PMU, LONG_LIFE_SCRATCH1) &
BOARD_CCD_STATE) >> BOARD_CCD_SHIFT;
}
/* Load case-closed debugging config. Must be after initvars(). */
ccd_config_init(ccd_init_state);
system_update_rollback_mask_with_both_imgs();
/* Indication that firmware is running, for debug purposes. */
GREG32(PMU, PWRDN_SCRATCH16) = 0xCAFECAFE;
/*
* Call the function twice to make it harder to glitch execution into
* passing the check when not supposed to.
*/
check_board_id_mismatch();
check_board_id_mismatch();
/*
* Start monitoring AC detect to wake Cr50 from deep sleep. This is
* needed to detect RDD cable changes in deep sleep. AC detect is also
* used for battery cutoff software support on detachable devices.
*/
init_ac_detect();
init_rdd_state();
/* Initialize write protect. Must be after CCD config init. */
init_wp_state();
/*
* Need to do this at run time as compile time constant initialization
* to a variable value (even to a const known at compile time) is not
* supported.
*/
bitbang_config.uart_in = ec_uart.producer.queue;
/*
* Enable interrupt handler for RBOX key combo so it can be used to
* store the recovery request.
*/
if (board_uses_closed_source_set1()) {
/* Enable interrupt handler for reset button combo */
task_enable_irq(GC_IRQNUM_RBOX0_INTR_BUTTON_COMBO0_RDY_INT);
GWRITE_FIELD(RBOX, INT_ENABLE, INTR_BUTTON_COMBO0_RDY, 1);
}
/*
* Note that the AP, EC, and servo state machines do not have explicit
* init_xxx_state() functions, because they don't need to configure
* registers prior to starting their state machines. Their state
* machines run in HOOK_SECOND, which first triggers right after
* HOOK_INIT, not at +1.0 seconds.
*/
}
DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT);
/**
* Hook for CCD config loaded/changed.
*/
static void board_ccd_config_changed(void)
{
/* Store the current CCD state so we can restore it after deep sleep */
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 1);
GREG32(PMU, LONG_LIFE_SCRATCH1) &= ~BOARD_CCD_STATE;
GREG32(PMU, LONG_LIFE_SCRATCH1) |= (ccd_get_state() << BOARD_CCD_SHIFT)
& BOARD_CCD_STATE;
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 0);
if (board_uses_closed_source_set1())
closed_source_set1_update_factory_mode();
/* Update CCD state */
ccd_update_state();
}
DECLARE_HOOK(HOOK_CCD_CHANGE, board_ccd_config_changed, HOOK_PRIO_DEFAULT);
#if defined(CONFIG_USB)
const void * const usb_strings[] = {
[USB_STR_DESC] = usb_string_desc,
[USB_STR_VENDOR] = USB_STRING_DESC("Google Inc."),
[USB_STR_PRODUCT] = USB_STRING_DESC("Cr50"),
[USB_STR_VERSION] = USB_STRING_DESC(CROS_EC_VERSION32),
[USB_STR_CONSOLE_NAME] = USB_STRING_DESC("Shell"),
[USB_STR_BLOB_NAME] = USB_STRING_DESC("Blob"),
[USB_STR_HID_KEYBOARD_NAME] = USB_STRING_DESC("PokeyPokey"),
[USB_STR_AP_NAME] = USB_STRING_DESC("AP"),
[USB_STR_EC_NAME] = USB_STRING_DESC("EC"),
[USB_STR_UPGRADE_NAME] = USB_STRING_DESC("Firmware upgrade"),
[USB_STR_SPI_NAME] = USB_STRING_DESC("AP EC upgrade"),
[USB_STR_SERIALNO] = USB_STRING_DESC(DEFAULT_SERIALNO),
[USB_STR_I2C_NAME] = USB_STRING_DESC("I2C"),
};
BUILD_ASSERT(ARRAY_SIZE(usb_strings) == USB_STR_COUNT);
#endif
/* SPI devices */
const struct spi_device_t spi_devices[] = {
[CONFIG_SPI_FLASH_PORT] = {0, 2, GPIO_COUNT}
};
const unsigned int spi_devices_used = ARRAY_SIZE(spi_devices);
int flash_regions_to_enable(struct g_flash_region *regions,
int max_regions)
{
/*
* This needs to account for two regions: the "other" RW partition and
* the NVRAM in TOP_B.
*
* When running from RW_A the two regions are adjacent, but it is
* simpler to keep function logic the same and always configure two
* separate regions.
*/
if (max_regions < 3)
return 0;
/* Enable access to the other RW image... */
if (system_get_image_copy() == SYSTEM_IMAGE_RW)
/* Running RW_A, enable RW_B */
regions[0].reg_base = CONFIG_MAPPED_STORAGE_BASE +
CONFIG_RW_B_MEM_OFF;
else
/* Running RW_B, enable RW_A */
regions[0].reg_base = CONFIG_MAPPED_STORAGE_BASE +
CONFIG_RW_MEM_OFF;
/* Size is the same */
regions[0].reg_size = CONFIG_RW_SIZE;
regions[0].reg_perms = FLASH_REGION_EN_ALL;
/* Enable access to the NVRAM partition A region */
regions[1].reg_base = CONFIG_MAPPED_STORAGE_BASE +
CFG_TOP_A_OFF;
regions[1].reg_size = CFG_TOP_SIZE;
regions[1].reg_perms = FLASH_REGION_EN_ALL;
/* Enable access to the NVRAM partition B region */
regions[2].reg_base = CONFIG_MAPPED_STORAGE_BASE +
CFG_TOP_B_OFF;
regions[2].reg_size = CFG_TOP_SIZE;
regions[2].reg_perms = FLASH_REGION_EN_ALL;
return 3;
}
/**
* Deferred TPM reset interrupt handling
*
* This is always called from the HOOK task.
*/
static void deferred_tpm_rst_isr(void)
{
CPRINTS("%s", __func__);
/*
* TPM reset is used to detect the AP, connect AP. Let the AP state
* machine know the AP is on.
*/
set_ap_on();
/*
* If no reboot request is posted, OR if the other RW's header is not
* ready to run - do not try rebooting the device, just reset the
* TPM.
*
* The inactive header will have to be restored by the appropriate
* vendor command, the device will be rebooted then.
*/
if (!reboot_request_posted || other_rw_is_inactive()) {
/* Reset TPM, no need to wait for completion. */
tpm_reset_request(0, 0);
return;
}
/*
* Reset TPM and wait to completion to make sure nvmem is
* committed before reboot.
*/
tpm_reset_request(1, 0);
/* This will never return. */
system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED | SYSTEM_RESET_HARD);
}
/**
* Handle TPM_RST_L deasserting
*
* This can also be called explicitly from AP detection, if it thinks the
* interrupt handler missed the rising edge.
*/
void tpm_rst_deasserted(enum gpio_signal signal)
{
hook_call_deferred(&deferred_tpm_rst_isr_data, 0);
}
void assert_sys_rst(void)
{
/* Assert it */
gpio_set_level(GPIO_SYS_RST_L_OUT, 0);
}
void deassert_sys_rst(void)
{
/* Deassert it */
gpio_set_level(GPIO_SYS_RST_L_OUT, 1);
}
static int is_sys_rst_asserted(void)
{
/*
* SYS_RST_L is pseudo open drain. It is only an output when it's
* asserted.
*/
return gpio_get_flags(GPIO_SYS_RST_L_OUT) & GPIO_OUTPUT;
}
/**
* Reboot the AP
*/
void board_reboot_ap(void)
{
if (board_uses_closed_loop_reset()) {
board_closed_loop_reset();
return;
}
assert_sys_rst();
msleep(20);
deassert_sys_rst();
}
/**
* Reboot the EC
*/
void board_reboot_ec(void)
{
if (board_uses_closed_loop_reset()) {
board_closed_loop_reset();
return;
}
assert_ec_rst();
deassert_ec_rst();
}
/*
* This interrupt handler will be called if the RBOX key combo is detected.
*/
static void key_combo0_irq(void)
{
GWRITE_FIELD(RBOX, INT_STATE, INTR_BUTTON_COMBO0_RDY, 1);
recovery_button_record();
board_reboot_ec();
CPRINTS("Recovery Requested");
}
DECLARE_IRQ(GC_IRQNUM_RBOX0_INTR_BUTTON_COMBO0_RDY_INT, key_combo0_irq, 0);
/**
* Console command to toggle system (AP) reset
*/
static int command_sys_rst(int argc, char **argv)
{
int val;
char *e;
int ms = 20;
if (argc > 1) {
if (!ccd_is_cap_enabled(CCD_CAP_REBOOT_EC_AP))
return EC_ERROR_ACCESS_DENIED;
if (!strcasecmp("pulse", argv[1])) {
if (argc == 3) {
ms = strtoi(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
}
ccprintf("Pulsing AP reset for %dms\n", ms);
assert_sys_rst();
msleep(ms);
deassert_sys_rst();
} else if (parse_bool(argv[1], &val)) {
if (val)
assert_sys_rst();
else
deassert_sys_rst();
} else
return EC_ERROR_PARAM1;
}
ccprintf("SYS_RST_L is %s\n", is_sys_rst_asserted() ?
"asserted" : "deasserted");
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(sysrst, command_sys_rst,
"[pulse [time] | <BOOLEAN>]",
"Assert/deassert SYS_RST_L to reset the AP");
/*
* Set RBOX register controlling EC reset and wait until RBOX updates the
* output.
*
* Input parameter is treated as a Boolean, 1 means reset needs to be
* asserted, 0 means reset needs to be deasserted.
*/
static void wait_ec_rst(int level)
{
int i;
/* Just in case. */
level = !!level;
GWRITE(RBOX, ASSERT_EC_RST, level);
/*
* If ec_rst value is being explicitly set while power button is held
* pressed after reset, do not let "power button release" ISR change
* the ec_rst value.
*/
power_button_release_enable_interrupt(0);
/*
* RBOX is running on its own clock, let's make sure we don't exit
* this function until the ecr_rst output matches the desired setting.
* 1000 cycles is way more than needed for RBOX to react.
*
* Note that the read back value is the inversion of the value written
* into the register once it propagates through RBOX.
*/
for (i = 0; i < 1000; i++)
if (GREAD_FIELD(RBOX, CHECK_OUTPUT, EC_RST) != level)
break;
}
void assert_ec_rst(void)
{
/* Prevent bit bang interrupt storm. */
if (uart_bitbang_is_enabled())
task_disable_irq(bitbang_config.rx_irq);
wait_ec_rst(1);
}
static void deassert_ec_rst_now(void)
{
wait_ec_rst(0);
if (uart_bitbang_is_enabled())
task_enable_irq(bitbang_config.rx_irq);
}
DECLARE_DEFERRED(deassert_ec_rst_now);
void deassert_ec_rst(void)
{
/*
* On closed source set1, the EC requires a minimum 30 ms pulse to
* properly reset. Ensure EC reset is never de-asesrted for less
* than this time.
*/
if (board_uses_closed_source_set1())
hook_call_deferred(&deassert_ec_rst_now_data, 30 * MSEC);
else
deassert_ec_rst_now();
}
int is_ec_rst_asserted(void)
{
return GREAD(RBOX, ASSERT_EC_RST);
}
/**
* Console command to toggle EC reset
*/
static int command_ec_rst(int argc, char **argv)
{
int val;
if (argc > 1) {
if (!ccd_is_cap_enabled(CCD_CAP_REBOOT_EC_AP))
return EC_ERROR_ACCESS_DENIED;
if (!strcasecmp("cl", argv[1])) {
/* Assert EC_RST_L until TPM_RST_L is asserted */
board_closed_loop_reset();
} else if (!strcasecmp("pulse", argv[1])) {
ccprintf("Pulsing EC reset\n");
board_reboot_ec();
} else if (parse_bool(argv[1], &val)) {
if (val)
assert_ec_rst();
else
deassert_ec_rst();
} else
return EC_ERROR_PARAM1;
}
ccprintf("EC_RST_L is %s\n", is_ec_rst_asserted() ?
"asserted" : "deasserted");
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(ecrst, command_ec_rst,
"[cl | pulse | <BOOLEAN>]",
"Assert/deassert EC_RST_L to reset the EC (and AP)");
/*
* This function duplicates some of the functionality in chip/g/gpio.c in order
* to configure a given strap pin to be either a low gpio output, a gpio input
* with or without an internal pull resistor, or disconnect the gpio signal
* from the pin pad.
*
* The desired gpio functionality is contained in the input parameter flags,
* while the strap parameter is an index into the array strap_regs.
*/
static void strap_config_pin(enum strap_list strap, int flags)
{
const struct gpio_info *g = gpio_list + strap_regs[strap].gpio_signal;
int bitnum = GPIO_MASK_TO_NUM(g->mask);
int mask = DIO_CTL_IE_MASK | DIO_CTL_PD_MASK | DIO_CTL_PU_MASK;
int val;
if (!flags) {
/* Reset strap pins, disconnect output and clear pull up/dn */
/* Disconnect gpio from pin mux */
DIO_SEL_REG(strap_regs[strap].sel_offset) = 0;
/* Clear input enable and pulldown/pullup in pinmux */
REG_WRITE_MLV(DIO_CTL_REG(strap_regs[strap].sel_offset),
mask, 0, 0);
return;
}
if (flags & GPIO_OUT_LOW) {
/* Config gpio to output and drive low */
gpio_set_flags(strap_regs[strap].gpio_signal, GPIO_OUT_LOW);
/* connect pin mux to gpio */
DIO_SEL_REG(strap_regs[strap].sel_offset) =
GET_GPIO_FUNC(g->port, bitnum);
return;
}
if (flags & GPIO_INPUT) {
/* Configure gpio pin to be an input */
gpio_set_flags(strap_regs[strap].gpio_signal, GPIO_INPUT);
/* Connect pad to gpio */
GET_GPIO_SEL_REG(g->port, bitnum) =
strap_regs[strap].pad_select;
/*
* Input enable is bit 2 of the CTL register. Pulldown enable is
* bit 3, and pullup enable is bit 4. Always set input enable
* and clear the pullup/pulldown bits unless the flags variable
* specifies that pulldown or pullup should be enabled.
*/
val = DIO_CTL_IE_MASK;
if (flags & GPIO_PULL_DOWN)
val |= DIO_CTL_PD_MASK;
if (flags & GPIO_PULL_UP)
val |= DIO_CTL_PU_MASK;
/* Set input enable and pulldown/pullup in pinmux */
REG_WRITE_MLV(DIO_CTL_REG(strap_regs[strap].sel_offset),
mask, 0, val);
}
}
static int get_strap_config(uint8_t *config)
{
enum strap_list s0;
int lvl;
int flags;
uint8_t use_i2c;
uint8_t i2c_prop;
uint8_t use_spi;
uint8_t spi_prop;
/*
* There are 4 pins that are used to determine Cr50 board strapping
* options. These pins are:
* 1. DIOA1 -> I2CS_SDA
* 2. DI0A9 -> I2CS_SCL
* 3. DIOA6 -> SPS_CLK
* 4. DIOA12 -> SPS_CS_L
* There are two main configuration options based on whether I2C or SPI
* is used for TPM2 communication to/from the host AP. If SPI is the
* TPM2 bus, then the pair of pins DIOA9|DIOA1 are used to designate
* strapping options. If TPM uses I2C, then DIOA12|DIOA6 are the
* strapping pins.
*
* Each strapping pin will have either an external pullup or pulldown
* resistor. The external pull resistors have two levels, 5k for strong
* and 1M for weak. Cr50 has internal pullup/pulldown 50k resistors that
* can be configured via pinmux register settings. This combination of
* external and internal pullup/pulldown resistors allows for 4 possible
* states per strapping pin. The following table shows the different
* combinations. Note that when a strong external pull down/up resistor
* is used, the internal resistor is a don't care and those cases are
* marked by n/a. The bits column represents the signal level read on
* the gpio pin. Bit 1 of this field is the value read with the internal
* pull down/up resistors disabled, and bit 0 is the gpio signal level
* of the same pin when the internal pull resistor is selected as shown
* in the 'internal' column.
* external internal bits
* -------- -------- ----
* 5K PD n/a 00
* 1M PD 50k PU 01
* 1M PU 50k PD 10
* 5K PU n/a 11
*
* To determine the bits associated with each strapping pin, the
* following method is used.
* 1. Set all 4 pins as inputs with internal pulls disabled.
* 2. For each pin do the following to encode 2 bits b1:b0
* a. b1 = gpio_get_level(pin)
* b. If b1 == 1, then enable internal pulldown, else enable
* internal pullup resistor.
* c. b0 = gpio_get_level(pin)
*
* To be considered a valid strap configuraiton, the upper 4 bits must
* have no pullups and at least one pullup in the lower 4 bits or vice
* versa. So can use 0xA0 and 0x0A as masks to check for each
* condition. Once this check is passed, the 4 bits which are used to
* distinguish between SPI vs I2C are masked since reading them as weak
* pulldowns is not being explicitly required due to concerns that the
* AP could prevent accurate differentiation between strong and weak
* pull down cases.
*/
/* Drive all 4 strap pins low to discharge caps. */
for (s0 = a0; s0 < ARRAY_SIZE(strap_regs); s0++)
strap_config_pin(s0, GPIO_OUT_LOW);
/* Delay long enough to discharge any caps. */
udelay(STRAP_PIN_DELAY_USEC);
/* Set all 4 strap pins as inputs with pull resistors disabled. */
for (s0 = a0; s0 < ARRAY_SIZE(strap_regs); s0++)
strap_config_pin(s0, GPIO_INPUT);
/* Delay so voltage levels can settle. */
udelay(STRAP_PIN_DELAY_USEC);
*config = 0;
/* Read 2 bit value of each strapping pin. */
ccprintf("strap pin readings:");
for (s0 = a0; s0 < ARRAY_SIZE(strap_regs); s0++) {
lvl = gpio_get_level(strap_regs[s0].gpio_signal);
flags = GPIO_INPUT;
if (lvl)
flags |= GPIO_PULL_DOWN;
else
flags |= GPIO_PULL_UP;
/* Enable internal pull down/up resistor. */
strap_config_pin(s0, flags);
udelay(STRAP_PIN_DELAY_USEC);
lvl = (lvl << 1) |
gpio_get_level(strap_regs[s0].gpio_signal);
ccprintf(" %s:%d", strap_regs[s0].pad_name, lvl);
*config |= lvl << s0 * 2;
/*
* Finished with this pin. Disable internal pull up/dn resistor
* and disconnect gpio from pin mux. The pins used for straps
* are configured for their desired role when either the SPI or
* I2C interfaces are initialized.
*/
strap_config_pin(s0, 0);
}
ccprintf("\n");
/*
* The strap bits for DIOA12|DIOA6 are in the upper 4 bits of 'config'
* while the strap bits for DIOA9|DIOA1 are in the lower 4 bits. Check
* for SPI vs I2C config by checking for presence of external pullups in
* one group of 4 bits and confirming no external pullups in the other
* group. For SPI config the weak pulldowns may not be accurately read
* on DIOA12|DIOA6 and similarly for I2C config on
* DIOA9|DIOA1. Therefore, only requiring that there be no external
* pullups on these pins and will mask the bits so they will match the
* config table entries.
*/
use_i2c = *config & 0xa0;
use_spi = *config & 0x0a;
/*
* The strap signals should have at least one pullup. Nothing can
* interfere with these. If we did not read any pullups, these are
* invalid straps. The config can't be salvaged.
*/
if (!use_i2c && !use_spi)
return EC_ERROR_INVAL;
/*
* The unused strap signals are used for the bus to the AP. If the AP
* has added pullups to the signals, it could interfere with the strap
* readings. If pullups are found on both the SPI and I2C straps, use
* the board properties to determine SPI vs I2C. We can use this to mask
* unused config pins the AP is interfering with.
*/
if (use_i2c && use_spi) {
spi_prop = (GREG32(PMU, LONG_LIFE_SCRATCH1) &
BOARD_SLAVE_CONFIG_SPI);
i2c_prop = (GREG32(PMU, LONG_LIFE_SCRATCH1) &
BOARD_SLAVE_CONFIG_I2C);
/* Make sure exactly one interface is selected */
if ((i2c_prop && spi_prop) || (!spi_prop && !i2c_prop))
return EC_ERROR_INVAL;
use_spi = spi_prop;
CPRINTS("Ambiguous strap config. Use %s based on old "
"brdprop.", use_spi ? "spi" : "i2c");
}
/* Now that I2C vs SPI is known, mask the unused strap bits. */
*config &= use_spi ? 0xf : 0xf0;
return EC_SUCCESS;
}
static uint32_t get_properties(void)
{
int i;
uint8_t config;
uint32_t properties;
if (chip_factory_mode()) {
CPRINTS("Chip factory mode, short circuit to SPI");
return BOARD_SLAVE_CONFIG_SPI;
}
#ifdef H1_RED_BOARD
CPRINTS("Unconditionally enabling SPI and platform reset");
return (BOARD_SLAVE_CONFIG_SPI | BOARD_USE_PLT_RESET);
#endif
if (get_strap_config(&config) != EC_SUCCESS) {
/*
* No pullups were detected on any of the strap pins so there
* is no point in checking for a matching config table entry.
* For this case use default properties.
*/
CPRINTS("Invalid strap pins! Default properties = 0x%x",
BOARD_PROPERTIES_DEFAULT);
return BOARD_PROPERTIES_DEFAULT;
}
/* Search board config table to find a matching entry */
for (i = 0; i < ARRAY_SIZE(board_cfg_table); i++) {
if (board_cfg_table[i].strap_cfg == config) {
properties = board_cfg_table[i].board_properties;
CPRINTS("Valid strap: 0x%x properties: 0x%x",
config, properties);
/* Read board properties for this config */
return properties;
}
}
/*
* Reached the end of the table and didn't find a matching config entry.
* However, the SPI vs I2C determination can still be made as
* get_strap_config() returned EC_SUCCESS.
*/
if (config & 0xa) {
properties = BOARD_SLAVE_CONFIG_SPI;
/*
* Determine PLT_RST_L vs SYS_RST_L. Any board with a pullup on
* DIOA9 uses PLT_RST_L.
*/
properties |= config & 0x8 ? BOARD_USE_PLT_RESET : 0;
} else {
/* All I2C boards use same default properties. */
properties = BOARD_PROPERTIES_DEFAULT;
}
CPRINTS("strap_cfg 0x%x has no table entry, prop = 0x%x",
config, properties);
return properties;
}
static void init_board_properties(void)
{
uint32_t properties;
properties = GREG32(PMU, LONG_LIFE_SCRATCH1);
/*
* This must be a power on reset or maybe restart due to a software
* update from a version not setting the register.
*/
if (!(properties & BOARD_ALL_PROPERTIES) || (system_get_reset_flags() &
RESET_FLAG_HARD)) {
/*
* Mask board properties because following hard reset, they
* won't be cleared.
*/
properties &= ~BOARD_ALL_PROPERTIES;
properties |= get_properties();
/*
* Now save the properties value for future use.
*
* Enable access to LONG_LIFE_SCRATCH1 reg.
*/
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 1);
/* Save properties in LONG_LIFE register */
GREG32(PMU, LONG_LIFE_SCRATCH1) = properties;
/* Disable access to LONG_LIFE_SCRATCH1 reg */
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 0);
}
/* Save this configuration setting */
board_properties = properties;
}
DECLARE_HOOK(HOOK_INIT, init_board_properties, HOOK_PRIO_FIRST);
void i2cs_set_pinmux(void)
{
/* Connect I2CS SDA/SCL output to A1/A9 pads */
GWRITE(PINMUX, DIOA1_SEL, GC_PINMUX_I2CS0_SDA_SEL);
GWRITE(PINMUX, DIOA9_SEL, GC_PINMUX_I2CS0_SCL_SEL);
/* Connect A1/A9 pads to I2CS input SDA/SCL */
GWRITE(PINMUX, I2CS0_SDA_SEL, GC_PINMUX_DIOA1_SEL);
GWRITE(PINMUX, I2CS0_SCL_SEL, GC_PINMUX_DIOA9_SEL);
/* Enable SDA/SCL inputs from A1/A9 pads */
GWRITE_FIELD(PINMUX, DIOA1_CTL, IE, 1); /* I2CS_SDA */
GWRITE_FIELD(PINMUX, DIOA9_CTL, IE, 1); /* I2CS_SCL */
/*
* Provide access to the SDA line to be able to detect 'hosed i2c
* slave' condition.
*/
GWRITE(PINMUX, GPIO0_GPIO14_SEL, GC_PINMUX_DIOA1_SEL);
/* Allow I2CS_SCL to wake from sleep */
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOA9, 1); /* edge sensitive */
GWRITE_FIELD(PINMUX, EXITINV0, DIOA9, 1); /* wake on low */
GWRITE_FIELD(PINMUX, EXITEN0, DIOA9, 1); /* enable powerdown exit */
/* Allow I2CS_SDA to wake from sleep */
GWRITE_FIELD(PINMUX, EXITEDGE0, DIOA1, 1); /* edge sensitive */
GWRITE_FIELD(PINMUX, EXITINV0, DIOA1, 1); /* wake on low */
GWRITE_FIELD(PINMUX, EXITEN0, DIOA1, 1); /* enable powerdown exit */
}
/* Determine key type based on the key ID. */
static const char *key_type(const struct SignedHeader *h)
{
if (G_SIGNED_FOR_PROD(h))
return "prod";
else
return "dev";
}
static int command_sysinfo(int argc, char **argv)
{
enum system_image_copy_t active;
uintptr_t vaddr;
const struct SignedHeader *h;
int reset_count = GREG32(PMU, LONG_LIFE_SCRATCH0);
char rollback_str[15];
uint8_t tpm_mode;
ccprintf("Reset flags: 0x%08x (", system_get_reset_flags());
system_print_reset_flags();
ccprintf(")\n");
if (system_rollback_detected())
ccprintf("Rollback detected\n");
ccprintf("Reset count: %d\n", reset_count);
ccprintf("Chip: %s %s %s\n", system_get_chip_vendor(),
system_get_chip_name(), system_get_chip_revision());
active = system_get_ro_image_copy();
vaddr = get_program_memory_addr(active);
h = (const struct SignedHeader *)vaddr;
ccprintf("RO keyid: 0x%08x(%s)\n", h->keyid, key_type(h));
active = system_get_image_copy();
vaddr = get_program_memory_addr(active);
h = (const struct SignedHeader *)vaddr;
ccprintf("RW keyid: 0x%08x(%s)\n", h->keyid, key_type(h));
ccprintf("DEV_ID: 0x%08x 0x%08x\n",
GREG32(FUSE, DEV_ID0), GREG32(FUSE, DEV_ID1));
system_get_rollback_bits(rollback_str, sizeof(rollback_str));
ccprintf("Rollback: %s\n", rollback_str);
tpm_mode = get_tpm_mode();
ccprintf("TPM MODE: %s (%d)\n",
(tpm_mode == TPM_MODE_DISABLED) ? "disabled" : "enabled",
tpm_mode);
ccprintf("Key Ladder: %s\n",
DCRYPTO_ladder_is_enabled() ? "enabled" : "disabled");
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(sysinfo, command_sysinfo,
NULL,
"Print system info");
/*
* SysInfo command:
* There are no input args.
* Output is this struct, all fields in network order.
*/
struct sysinfo_s {
uint32_t ro_keyid;
uint32_t rw_keyid;
uint32_t dev_id0;
uint32_t dev_id1;
} __packed;
static enum vendor_cmd_rc vc_sysinfo(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
enum system_image_copy_t active;
uintptr_t vaddr;
const struct SignedHeader *h;
struct sysinfo_s *sysinfo = buf;
active = system_get_ro_image_copy();
vaddr = get_program_memory_addr(active);
h = (const struct SignedHeader *)vaddr;
sysinfo->ro_keyid = htobe32(h->keyid);
active = system_get_image_copy();
vaddr = get_program_memory_addr(active);
h = (const struct SignedHeader *)vaddr;
sysinfo->rw_keyid = htobe32(h->keyid);
sysinfo->dev_id0 = htobe32(GREG32(FUSE, DEV_ID0));
sysinfo->dev_id1 = htobe32(GREG32(FUSE, DEV_ID1));
*response_size = sizeof(*sysinfo);
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_SYSINFO, vc_sysinfo);
static enum vendor_cmd_rc vc_invalidate_inactive_rw(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
const struct SignedHeader *header;
uint32_t ctrl;
uint32_t base_addr;
uint32_t size;
const char zero[4] = {}; /* value to write to magic. */
*response_size = 0;
/* Update INFO1 mask based on the currently active image. */
system_update_rollback_mask_with_active_img();
if (other_rw_is_inactive()) {
CPRINTS("%s: Inactive region is disabled", __func__);
return VENDOR_RC_SUCCESS;
}
/* save the original flash region6 register values */
ctrl = GREAD(GLOBALSEC, FLASH_REGION6_CTRL);
base_addr = GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR);
size = GREG32(GLOBALSEC, FLASH_REGION6_SIZE);
header = get_other_rw_addr();
/* Enable RW access to the other header. */
GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) = (uint32_t) header;
GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = 1023;
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, EN, 1);
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, RD_EN, 1);
GWRITE_FIELD(GLOBALSEC, FLASH_REGION6_CTRL, WR_EN, 1);
CPRINTS("%s: TPM verified corrupting inactive image, magic before %x",
__func__, header->magic);
flash_physical_write((intptr_t)&header->magic -
CONFIG_PROGRAM_MEMORY_BASE,
sizeof(zero), zero);
CPRINTS("%s: magic after: %x", __func__, header->magic);
/* Restore original values */
GREG32(GLOBALSEC, FLASH_REGION6_BASE_ADDR) = base_addr;
GREG32(GLOBALSEC, FLASH_REGION6_SIZE) = size;
GREG32(GLOBALSEC, FLASH_REGION6_CTRL) = ctrl;
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_INVALIDATE_INACTIVE_RW,
vc_invalidate_inactive_rw);
static enum vendor_cmd_rc vc_commit_nvmem(enum vendor_cmd_cc code,
void *buf,
size_t input_size,
size_t *response_size)
{
nvmem_enable_commits();
*response_size = 0;
return VENDOR_RC_SUCCESS;
}
DECLARE_VENDOR_COMMAND(VENDOR_CC_COMMIT_NVMEM, vc_commit_nvmem);
static int command_board_properties(int argc, char **argv)
{
/*
* The board properties are stored in LONG_LIFE_SCRATCH1. Note that we
* don't just simply return board_properties here since that's just a
* cached value from init time.
*/
ccprintf("properties = 0x%x\n", GREG32(PMU, LONG_LIFE_SCRATCH1));
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(brdprop, command_board_properties,
NULL, "Display board properties");
int chip_factory_mode(void)
{
static uint8_t mode_set;
/*
* Bit 0x2 used to indicate that mode has been set, bit 0x1 is the
* actual indicator of the chip factory mode.
*/
if (!mode_set)
mode_set = 2 | !!gpio_get_level(GPIO_DIOB4);
return mode_set & 1;
}
#ifdef CR50_RELAXED
static int command_rollback(int argc, char **argv)
{
system_ensure_rollback();
ccprintf("Rebooting to alternate RW due to manual request\n");
cflush();
system_reset(0);
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(rollback, command_rollback,
"", "Force rollback to escape DEV image.");
#endif
/*
* Set long life register bit requesting generating of the ITE SYNC sequence
* and reboot.
*/
static void deferred_ite_sync_reset(void)
{
/* Enable writing to the long life register */
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 1);
GREG32(PMU, LONG_LIFE_SCRATCH1) |= BOARD_ITE_EC_SYNC_NEEDED;
/* Disable writing to the long life register */
GWRITE_FIELD(PMU, LONG_LIFE_SCRATCH_WR_EN, REG1, 0);
system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED |
SYSTEM_RESET_HARD);
}
DECLARE_DEFERRED(deferred_ite_sync_reset);
void board_start_ite_sync(void)
{
/* Let the usb reply to make it to the host. */
hook_call_deferred(&deferred_ite_sync_reset_data, 10 * MSEC);
}