diff options
-rw-r--r-- | board/scout/board.c | 535 | ||||
-rw-r--r-- | board/scout/board.h | 231 | ||||
-rw-r--r-- | board/scout/build.mk | 15 | ||||
-rw-r--r-- | board/scout/ec.tasklist | 15 | ||||
-rw-r--r-- | board/scout/gpio.inc | 167 | ||||
-rw-r--r-- | board/scout/led.c | 274 | ||||
-rw-r--r-- | board/scout/pse.c | 266 |
7 files changed, 1503 insertions, 0 deletions
diff --git a/board/scout/board.c b/board/scout/board.c new file mode 100644 index 0000000000..e48ed0fe0d --- /dev/null +++ b/board/scout/board.c @@ -0,0 +1,535 @@ +/* Copyright 2021 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. + */ + +/* Puff board-specific configuration */ + +#include "adc.h" +#include "adc_chip.h" +#include "button.h" +#include "chipset.h" +#include "common.h" +#include "core/cortex-m/cpu.h" +#include "cros_board_info.h" +#include "driver/ina3221.h" +#include "ec_commands.h" +#include "extpower.h" +#include "fan.h" +#include "fan_chip.h" +#include "gpio.h" +#include "hooks.h" +#include "host_command.h" +#include "lid_switch.h" +#include "power.h" +#include "power/cometlake-discrete.h" +#include "power_button.h" +#include "pwm.h" +#include "pwm_chip.h" +#include "spi.h" +#include "switch.h" +#include "system.h" +#include "task.h" +#include "temp_sensor.h" +#include "thermal.h" +#include "temp_sensor/thermistor.h" +#include "uart.h" +#include "usb_common.h" +#include "util.h" + +#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) +#define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args) + +static void power_monitor(void); +DECLARE_DEFERRED(power_monitor); + +static uint8_t usbc_overcurrent; +static int32_t base_5v_power; + +/* + * Power usage for each port as measured or estimated. + * Units are milliwatts (5v x ma current) + */ +#define PWR_BASE_LOAD (5*1335) +#define PWR_FRONT_HIGH (5*1603) +#define PWR_FRONT_LOW (5*963) +#define PWR_REAR (5*1075) +#define PWR_HDMI (5*562) +#define PWR_C_HIGH (5*3740) +#define PWR_C_LOW (5*2090) +#define PWR_MAX (5*10000) + +/* + * Update the 5V power usage, assuming no throttling, + * and invoke the power monitoring. + */ +static void update_5v_usage(void) +{ + int front_ports = 0; + /* + * Recalculate the 5V load, assuming no throttling. + */ + base_5v_power = PWR_BASE_LOAD; + if (!gpio_get_level(GPIO_USB_A0_OC_ODL)) { + front_ports++; + base_5v_power += PWR_FRONT_LOW; + } + if (!gpio_get_level(GPIO_USB_A1_OC_ODL)) { + front_ports++; + base_5v_power += PWR_FRONT_LOW; + } + /* + * Only 1 front port can run higher power at a time. + */ + if (front_ports > 0) + base_5v_power += PWR_FRONT_HIGH - PWR_FRONT_LOW; + if (!gpio_get_level(GPIO_USB_A2_OC_ODL)) + base_5v_power += PWR_REAR; + if (!gpio_get_level(GPIO_USB_A3_OC_ODL)) + base_5v_power += PWR_REAR; + if (ec_config_get_usb4_present() && !gpio_get_level(GPIO_USB_A4_OC_ODL)) + base_5v_power += PWR_REAR; + if (!gpio_get_level(GPIO_HDMI_CONN0_OC_ODL)) + base_5v_power += PWR_HDMI; + if (!gpio_get_level(GPIO_HDMI_CONN1_OC_ODL)) + base_5v_power += PWR_HDMI; + if (usbc_overcurrent) + base_5v_power += PWR_C_HIGH; + /* + * Invoke the power handler immediately. + */ + hook_call_deferred(&power_monitor_data, 0); +} +DECLARE_DEFERRED(update_5v_usage); +/* + * Start power monitoring after ADCs have been initialised. + */ +DECLARE_HOOK(HOOK_INIT, update_5v_usage, HOOK_PRIO_INIT_ADC + 1); + +static void port_ocp_interrupt(enum gpio_signal signal) +{ + hook_call_deferred(&update_5v_usage_data, 0); +} + +/******************************************************************************/ + +#include "gpio_list.h" /* Must come after other header files. */ + +/******************************************************************************/ +/* SPI devices */ +const struct spi_device_t spi_devices[] = { +}; +const unsigned int spi_devices_used = ARRAY_SIZE(spi_devices); + +/******************************************************************************/ +/* PWM channels. Must be in the exactly same order as in enum pwm_channel. */ +const struct pwm_t pwm_channels[] = { + [PWM_CH_FAN] = { .channel = 5, + .flags = PWM_CONFIG_OPEN_DRAIN, + .freq = 25000}, + [PWM_CH_LED_RED] = { .channel = 0, + .flags = PWM_CONFIG_DSLEEP, + .freq = 2000 }, + [PWM_CH_LED_WHITE] = { .channel = 2, + .flags = PWM_CONFIG_DSLEEP, + .freq = 2000 }, +}; + +/******************************************************************************/ +/* I2C port map configuration */ +const struct i2c_port_t i2c_ports[] = { + {"ina", I2C_PORT_INA, 400, GPIO_I2C0_SCL, GPIO_I2C0_SDA}, + {"ppc0", I2C_PORT_PPC0, 400, GPIO_I2C1_SCL, GPIO_I2C1_SDA}, + {"tcpc0", I2C_PORT_TCPC0, 400, GPIO_I2C3_SCL, GPIO_I2C3_SDA}, + {"pse", I2C_PORT_PSE, 400, GPIO_I2C4_SCL, GPIO_I2C4_SDA}, + {"power", I2C_PORT_POWER, 400, GPIO_I2C5_SCL, GPIO_I2C5_SDA}, + {"eeprom", I2C_PORT_EEPROM, 400, GPIO_I2C7_SCL, GPIO_I2C7_SDA}, +}; +const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); + +const struct adc_t adc_channels[] = { + [ADC_SNS_PP3300] = { + /* + * 4700/5631 voltage divider: can take the value out of range + * for 32-bit signed integers, so truncate to 470/563 yielding + * <0.1% error and a maximum intermediate value of 1623457792, + * which comfortably fits in int32. + */ + .name = "SNS_PP3300", + .input_ch = NPCX_ADC_CH2, + .factor_mul = ADC_MAX_VOLT * 563, + .factor_div = (ADC_READ_MAX + 1) * 470, + }, + [ADC_SNS_PP1050] = { + .name = "SNS_PP1050", + .input_ch = NPCX_ADC_CH7, + .factor_mul = ADC_MAX_VOLT, + .factor_div = ADC_READ_MAX + 1, + }, + [ADC_VBUS] = { /* 5/39 voltage divider */ + .name = "VBUS", + .input_ch = NPCX_ADC_CH4, + .factor_mul = ADC_MAX_VOLT * 39, + .factor_div = (ADC_READ_MAX + 1) * 5, + }, + [ADC_PPVAR_IMON] = { /* 500 mV/A */ + .name = "PPVAR_IMON", + .input_ch = NPCX_ADC_CH9, + .factor_mul = ADC_MAX_VOLT * 2, /* Milliamps */ + .factor_div = ADC_READ_MAX + 1, + }, + [ADC_TEMP_SENSOR_1] = { + .name = "TEMP_SENSOR_1", + .input_ch = NPCX_ADC_CH0, + .factor_mul = ADC_MAX_VOLT, + .factor_div = ADC_READ_MAX + 1, + }, +}; +BUILD_ASSERT(ARRAY_SIZE(adc_channels) == ADC_CH_COUNT); + +const struct temp_sensor_t temp_sensors[] = { + [TEMP_SENSOR_CORE] = { + .name = "Core", + .type = TEMP_SENSOR_TYPE_BOARD, + .read = get_temp_3v3_30k9_47k_4050b, + .idx = ADC_TEMP_SENSOR_1, + }, +}; +BUILD_ASSERT(ARRAY_SIZE(temp_sensors) == TEMP_SENSOR_COUNT); + +/******************************************************************************/ +/* Wake up pins */ +const enum gpio_signal hibernate_wake_pins[] = { +}; +const int hibernate_wake_pins_used = ARRAY_SIZE(hibernate_wake_pins); + +/******************************************************************************/ +/* Physical fans. These are logically separate from pwm_channels. */ +const struct fan_conf fan_conf_0 = { + .flags = FAN_USE_RPM_MODE, + .ch = MFT_CH_0, /* Use MFT id to control fan */ + .pgood_gpio = -1, + .enable_gpio = -1, +}; + +const struct fan_rpm fan_rpm_0 = { + .rpm_min = 2500, + .rpm_start = 2500, + .rpm_max = 5200, +}; + +const struct fan_t fans[] = { + [FAN_CH_0] = { .conf = &fan_conf_0, .rpm = &fan_rpm_0, }, +}; +BUILD_ASSERT(ARRAY_SIZE(fans) == FAN_CH_COUNT); + +/******************************************************************************/ +/* MFT channels. These are logically separate from pwm_channels. */ +const struct mft_t mft_channels[] = { + [MFT_CH_0] = {NPCX_MFT_MODULE_2, TCKC_LFCLK, PWM_CH_FAN}, +}; +BUILD_ASSERT(ARRAY_SIZE(mft_channels) == MFT_CH_COUNT); + +/******************************************************************************/ +/* Thermal control; drive fan based on temperature sensors. */ +const static struct ec_thermal_config thermal_a = { + .temp_host = { + [EC_TEMP_THRESH_WARN] = 0, + [EC_TEMP_THRESH_HIGH] = C_TO_K(78), + [EC_TEMP_THRESH_HALT] = C_TO_K(85), + }, + .temp_host_release = { + [EC_TEMP_THRESH_WARN] = 0, + [EC_TEMP_THRESH_HIGH] = C_TO_K(70), + [EC_TEMP_THRESH_HALT] = 0, + }, + .temp_fan_off = C_TO_K(25), + .temp_fan_max = C_TO_K(84), +}; + +const static struct ec_thermal_config thermal_b = { + .temp_host = { + [EC_TEMP_THRESH_WARN] = 0, + [EC_TEMP_THRESH_HIGH] = C_TO_K(78), + [EC_TEMP_THRESH_HALT] = C_TO_K(85), + }, + .temp_host_release = { + [EC_TEMP_THRESH_WARN] = 0, + [EC_TEMP_THRESH_HIGH] = C_TO_K(70), + [EC_TEMP_THRESH_HALT] = 0, + }, +}; + +struct ec_thermal_config thermal_params[] = { + [TEMP_SENSOR_CORE] = thermal_a, +}; +BUILD_ASSERT(ARRAY_SIZE(thermal_params) == TEMP_SENSOR_COUNT); + +/* Power sensors */ +const struct ina3221_t ina3221[] = { + { I2C_PORT_INA, 0x40, { "PP3300_G", "PP5000_A", "PP3300_WLAN" } }, + { I2C_PORT_INA, 0x42, { "PP3300_A", "PP3300_SSD", "PP3300_LAN" } }, + { I2C_PORT_INA, 0x43, { NULL, "PP1200_U", "PP2500_DRAM" } } +}; +const unsigned int ina3221_count = ARRAY_SIZE(ina3221); + +static uint16_t board_version; +static uint32_t sku_id; +static uint32_t fw_config; + +static void cbi_init(void) +{ + /* + * Load board info from CBI to control per-device configuration. + * + * If unset it's safe to treat the board as a proto, just C10 gating + * won't be enabled. + */ + uint32_t val; + + if (cbi_get_board_version(&val) == EC_SUCCESS && val <= UINT16_MAX) + board_version = val; + if (cbi_get_sku_id(&val) == EC_SUCCESS) + sku_id = val; + if (cbi_get_fw_config(&val) == EC_SUCCESS) + fw_config = val; + CPRINTS("Board Version: %d, SKU ID: 0x%08x, F/W config: 0x%08x", + board_version, sku_id, fw_config); +} +DECLARE_HOOK(HOOK_INIT, cbi_init, HOOK_PRIO_INIT_I2C + 1); + +static void board_init(void) +{ + uint8_t *memmap_batt_flags; + + /* Override some GPIO interrupt priorities. + * + * These interrupts are timing-critical for AP power sequencing, so we + * increase their NVIC priority from the default of 3. This affects + * whole MIWU groups of 8 GPIOs since they share an IRQ. + * + * Latency at the default priority level can be hundreds of + * microseconds while other equal-priority IRQs are serviced, so GPIOs + * requiring faster response must be higher priority. + */ + /* CPU_C10_GATE_L on GPIO6.7: must be ~instant for ~60us response. */ + cpu_set_interrupt_priority(NPCX_IRQ_WKINTH_1, 1); + /* + * slp_s3_interrupt (GPIOA.5 on WKINTC_0) must respond within 200us + * (tPLT18); less critical than the C10 gate. + */ + cpu_set_interrupt_priority(NPCX_IRQ_WKINTC_0, 2); + + /* Always claim AC is online, because we don't have a battery. */ + memmap_batt_flags = host_get_memmap(EC_MEMMAP_BATT_FLAG); + *memmap_batt_flags |= EC_BATT_FLAG_AC_PRESENT; + /* + * For board version < 2, the directly connected recovery + * button is not available. + */ + if (board_version < 2) + button_disable_gpio(GPIO_EC_RECOVERY_BTN_ODL); +} +DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT); + +/******************************************************************************/ +/* USB-A port control */ +const int usb_port_enable[USB_PORT_COUNT] = { + GPIO_EN_PP5000_USB_VBUS, +}; + +int64_t get_time_dsw_pwrok(void) +{ + /* DSW_PWROK is turned on before EC was powered. */ + return -20 * MSEC; +} + +int extpower_is_present(void) +{ + /* genesis: If the EC is running, then there is external power */ + return 1; +} + +int board_is_c10_gate_enabled(void) +{ + /* + * Puff proto drives EN_PP5000_HDMI from EN_S0_RAILS so we cannot gate + * core rails while in S0 because HDMI should remain powered. + * EN_PP5000_HDMI is a separate EC output on all other boards. + */ + return board_version != 0; +} + +void board_enable_s0_rails(int enable) +{ + /* This output isn't connected on protos; safe to set anyway. */ + gpio_set_level(GPIO_EN_PP5000_HDMI, enable); +} + +int ec_config_get_usb4_present(void) +{ + return !(fw_config & EC_CFG_NO_USB4_MASK); +} + +unsigned int ec_config_get_thermal_solution(void) +{ + return (fw_config & EC_CFG_THERMAL_MASK) >> EC_CFG_THERMAL_L; +} + +static void setup_thermal(void) +{ + unsigned int table = ec_config_get_thermal_solution(); + /* Configure Fan */ + switch (table) { + /* Default and table0 use single fan */ + case 0: + default: + thermal_params[TEMP_SENSOR_CORE] = thermal_a; + break; + /* Table1 is fanless */ + case 1: + fan_set_count(0); + thermal_params[TEMP_SENSOR_CORE] = thermal_b; + break; + } +} +/* fan_set_count should be called before HOOK_INIT/HOOK_PRIO_DEFAULT */ +DECLARE_HOOK(HOOK_INIT, setup_thermal, HOOK_PRIO_DEFAULT - 1); + +/* + * Power monitoring and management. + * + * The overall goal is to gracefully manage the power demand so that + * the power budgets are met without letting the system fall into + * power deficit (perhaps causing a brownout). + * + * There are 2 power budgets that need to be managed: + * - overall system power as measured on the main power supply rail. + * - 5V power delivered to the USB and HDMI ports. + * + * The actual system power demand is calculated from the VBUS voltage and + * the input current (read from a shunt), averaged over 5 readings. + * The power budget limit is from the charge manager. + * + * The 5V power cannot be read directly. Instead, we rely on overcurrent + * inputs from the USB and HDMI ports to indicate that the port is in use + * (and drawing maximum power). + * + * There are 3 throttles that can be applied (in priority order): + * + * - Type A BC1.2 front port restriction (3W) + * - Type C PD (throttle to 1.5A if sourcing) + * - Turn on PROCHOT, which immediately throttles the CPU. + * + * The first 2 throttles affect both the system power and the 5V rails. + * The third is a last resort to force an immediate CPU throttle to + * reduce the overall power use. + * + * The strategy is to determine what the state of the throttles should be, + * and to then turn throttles off or on as needed to match this. + * + * This function runs on demand, or every 2 ms when the CPU is up, + * and continually monitors the power usage, applying the + * throttles when necessary. + * + * All measurements are in milliwatts. + */ +#define THROT_TYPE_A BIT(0) +#define THROT_TYPE_C BIT(1) +#define THROT_PROCHOT BIT(2) + +/* + * Power gain if front USB A ports are limited. + */ +#define POWER_GAIN_TYPE_A 3200 +/* + * Power gain if Type C port is limited. + */ +#define POWER_GAIN_TYPE_C 8800 +/* + * Power is averaged over 10 ms, with a reading every 2 ms. + */ +#define POWER_DELAY_MS 2 +#define POWER_READINGS (10/POWER_DELAY_MS) + +static void power_monitor(void) +{ + static uint32_t current_state; + int32_t delay; + uint32_t new_state = 0, diff; + int32_t headroom_5v = PWR_MAX - base_5v_power; + + /* + * If CPU is off or suspended, no need to throttle + * or restrict power. + */ + if (chipset_in_state(CHIPSET_STATE_ANY_OFF | + CHIPSET_STATE_SUSPEND)) { + /* + * Slow down monitoring, assume no throttling required. + */ + delay = 20 * MSEC; + } else { + delay = POWER_DELAY_MS * MSEC; + } + /* + * Check the 5v power usage and if necessary, + * adjust the throttles in priority order. + * + * Either throttle may have already been activated by + * the overall power control. + * + * We rely on the overcurrent detection to inform us + * if the port is in use. + * + * - If type C not already throttled: + * * If not overcurrent, prefer to limit type C [1]. + * * If in overcurrentuse: + * - limit type A first [2] + * - If necessary, limit type C [3]. + * - If type A not throttled, if necessary limit it [2]. + */ + if (headroom_5v < 0) { + /* + * Check whether type C is not throttled, + * and is not overcurrent. + */ + if (!((new_state & THROT_TYPE_C) || usbc_overcurrent)) { + /* + * [1] Type C not in overcurrent, throttle it. + */ + headroom_5v += PWR_C_HIGH - PWR_C_LOW; + new_state |= THROT_TYPE_C; + } + /* + * [2] If type A not already throttled, and power still + * needed, limit type A. + */ + if (!(new_state & THROT_TYPE_A) && headroom_5v < 0) { + headroom_5v += PWR_FRONT_HIGH - PWR_FRONT_LOW; + new_state |= THROT_TYPE_A; + } + /* + * [3] If still under-budget, limit type C. + * No need to check if it is already throttled or not. + */ + if (headroom_5v < 0) + new_state |= THROT_TYPE_C; + } + /* + * Turn the throttles on or off if they have changed. + */ + diff = new_state ^ current_state; + current_state = new_state; + if (diff & THROT_PROCHOT) { + int prochot = (new_state & THROT_PROCHOT) ? 0 : 1; + + gpio_set_level(GPIO_EC_PROCHOT_ODL, prochot); + } + if (diff & THROT_TYPE_A) { + int typea_bc = (new_state & THROT_TYPE_A) ? 1 : 0; + + gpio_set_level(GPIO_USB_A_LOW_PWR_OD, typea_bc); + } + hook_call_deferred(&power_monitor_data, delay); +} diff --git a/board/scout/board.h b/board/scout/board.h new file mode 100644 index 0000000000..859d86e020 --- /dev/null +++ b/board/scout/board.h @@ -0,0 +1,231 @@ +/* Copyright 2021 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. + */ + +/* Puff board configuration */ + +#ifndef __CROS_EC_BOARD_H +#define __CROS_EC_BOARD_H + +#undef CONFIG_UART_TX_BUF_SIZE +#define CONFIG_UART_TX_BUF_SIZE 4096 + +/* NPCX7 config */ +#define NPCX7_PWM1_SEL 0 /* GPIO C2 is not used as PWM1. */ +#define NPCX_UART_MODULE2 1 /* GPIO64/65 are used as UART pins. */ + +/* Internal SPI flash on NPCX796FC is 512 kB */ +#define CONFIG_FLASH_SIZE_BYTES (512 * 1024) +#define CONFIG_SPI_FLASH_REGS +#define CONFIG_SPI_FLASH_W25Q80 /* Internal SPI flash type. */ + +/* EC Defines */ +#define CONFIG_ADC +#define CONFIG_BOARD_HAS_RTC_RESET +#define CONFIG_BOARD_VERSION_CBI +#define CONFIG_DEDICATED_RECOVERY_BUTTON +#define CONFIG_DEDICATED_RECOVERY_BUTTON_2 +#define CONFIG_BUTTONS_RUNTIME_CONFIG +#define CONFIG_BOARD_RESET_AFTER_POWER_ON +/* TODO: (b/143496253) re-enable CEC */ +/* #define CONFIG_CEC */ +#define CONFIG_CRC8 +#define CONFIG_CBI_EEPROM +#define CONFIG_EMULATED_SYSRQ +#undef CONFIG_KEYBOARD_BOOT_KEYS +#define CONFIG_MKBP_INPUT_DEVICES +#define CONFIG_MKBP_USE_HOST_EVENT +#undef CONFIG_KEYBOARD_RUNTIME_KEYS +#undef CONFIG_HIBERNATE +#define CONFIG_HOSTCMD_ESPI +#define CONFIG_LED_COMMON +#undef CONFIG_LID_SWITCH +#define CONFIG_LTO +#define CONFIG_PWM +#define CONFIG_VBOOT_EFS2 +#define CONFIG_VBOOT_HASH +#define CONFIG_VSTORE +#define CONFIG_VSTORE_SLOT_COUNT 1 +#define CONFIG_SHA256 + +/* EC Commands */ +#define CONFIG_CMD_BUTTON +/* Include CLI command needed to support CCD testing. */ +#define CONFIG_CMD_CHARGEN +#undef CONFIG_CMD_FASTCHARGE +#undef CONFIG_CMD_KEYBOARD +#define CONFIG_HOSTCMD_PD_CONTROL +#undef CONFIG_CMD_PWR_AVG +#define CONFIG_CMD_PPC_DUMP +#define CONFIG_CMD_TCPC_DUMP +#ifdef SECTION_IS_RO +/* Reduce RO size by removing less-relevant commands. */ +#undef CONFIG_CMD_APTHROTTLE +#undef CONFIG_CMD_CHARGEN +#undef CONFIG_CMD_HCDEBUG +#undef CONFIG_CMD_MMAPINFO +#endif + +#undef CONFIG_CONSOLE_CMDHELP + +/* Don't generate host command debug by default */ +#undef CONFIG_HOSTCMD_DEBUG_MODE +#define CONFIG_HOSTCMD_DEBUG_MODE HCDEBUG_OFF + +/* Enable AP Reset command for TPM with old firmware version to detect it. */ +#define CONFIG_CMD_AP_RESET_LOG +#define CONFIG_HOSTCMD_AP_RESET + +/* Chipset config */ +#define CONFIG_CHIPSET_COMETLAKE_DISCRETE +/* check */ +#define CONFIG_CHIPSET_CAN_THROTTLE +#define CONFIG_CHIPSET_RESET_HOOK +#define CONFIG_CPU_PROCHOT_ACTIVE_LOW + +#define CONFIG_POWER_BUTTON +#define CONFIG_POWER_BUTTON_IGNORE_LID +#define CONFIG_POWER_BUTTON_X86 +/* Check: */ +#define CONFIG_POWER_BUTTON_INIT_IDLE +#define CONFIG_POWER_COMMON +#define CONFIG_POWER_SIGNAL_INTERRUPT_STORM_DETECT_THRESHOLD 30 +#define CONFIG_DELAY_DSW_PWROK_TO_PWRBTN +#define CONFIG_POWER_PP5000_CONTROL +#define CONFIG_POWER_S0IX +#define CONFIG_POWER_SLEEP_FAILURE_DETECTION +#define CONFIG_POWER_TRACK_HOST_SLEEP_STATE +#define CONFIG_INA3221 + +/* Fan and temp. */ +#define CONFIG_FANS 1 +#undef CONFIG_FAN_INIT_SPEED +#define CONFIG_FAN_INIT_SPEED 0 +#define CONFIG_TEMP_SENSOR +#define CONFIG_TEMP_SENSOR_POWER_GPIO GPIO_EN_ROA_RAILS +#define CONFIG_THERMISTOR +#define CONFIG_STEINHART_HART_3V3_30K9_47K_4050B +#define CONFIG_THROTTLE_AP + +#define CONFIG_USB_PD_PORT_MAX_COUNT 0 + +/* USB Type A Features */ +#define CONFIG_USB_PORT_POWER_DUMB +/* There are five ports, but power enable is ganged across all of them. */ +#define USB_PORT_COUNT 1 + +/* I2C Bus Configuration */ +#define CONFIG_I2C +#define CONFIG_I2C_CONTROLLER +#define I2C_PORT_INA NPCX_I2C_PORT0_0 +#define I2C_PORT_PPC0 NPCX_I2C_PORT1_0 +#define I2C_PORT_TCPC0 NPCX_I2C_PORT3_0 +#define I2C_PORT_PSE NPCX_I2C_PORT4_1 +#define I2C_PORT_POWER NPCX_I2C_PORT5_0 +#define I2C_PORT_EEPROM NPCX_I2C_PORT7_0 +#define I2C_ADDR_EEPROM_FLAGS 0x50 + +#define PP5000_PGOOD_POWER_SIGNAL_MASK POWER_SIGNAL_MASK(PP5000_A_PGOOD) + +#ifndef __ASSEMBLER__ + +#include "gpio_signal.h" +#include "registers.h" + +enum adc_channel { + ADC_SNS_PP3300, /* ADC2 */ + ADC_SNS_PP1050, /* ADC7 */ + ADC_VBUS, /* ADC4 */ + ADC_PPVAR_IMON, /* ADC9 */ + ADC_TEMP_SENSOR_1, /* ADC0 */ + /* Number of ADC channels */ + ADC_CH_COUNT +}; + +enum pwm_channel { + PWM_CH_FAN, + PWM_CH_LED_RED, + PWM_CH_LED_WHITE, + /* Number of PWM channels */ + PWM_CH_COUNT +}; + +enum fan_channel { + FAN_CH_0, + /* Number of FAN channels */ + FAN_CH_COUNT +}; + +enum mft_channel { + MFT_CH_0 = 0, + /* Number of MFT channels */ + MFT_CH_COUNT, +}; + +enum temp_sensor_id { + TEMP_SENSOR_CORE, + TEMP_SENSOR_COUNT +}; + + +/* Board specific handlers */ +void board_reset_pd_mcu(void); +void board_set_tcpc_power_mode(int port, int mode); +void led_alert(int enable); +void show_critical_error(void); + +/* + * firmware config fields + */ +/* + * Barrel-jack power (4 bits). + */ +#define EC_CFG_BJ_POWER_L 0 +#define EC_CFG_BJ_POWER_H 3 +#define EC_CFG_BJ_POWER_MASK GENMASK(EC_CFG_BJ_POWER_H, EC_CFG_BJ_POWER_L) +/* + * USB Connector 4 not present (1 bit). + */ +#define EC_CFG_NO_USB4_L 4 +#define EC_CFG_NO_USB4_H 4 +#define EC_CFG_NO_USB4_MASK GENMASK(EC_CFG_NO_USB4_H, EC_CFG_NO_USB4_L) +/* + * Thermal solution config (3 bits). + */ +#define EC_CFG_THERMAL_L 5 +#define EC_CFG_THERMAL_H 7 +#define EC_CFG_THERMAL_MASK GENMASK(EC_CFG_THERMAL_H, EC_CFG_THERMAL_L) + +int ec_config_get_usb4_present(void); +unsigned int ec_config_get_thermal_solution(void); + +#endif /* !__ASSEMBLER__ */ + +/* Pin renaming */ +#define GPIO_WP_L GPIO_EC_WP_ODL +#define GPIO_PP5000_A_PG_OD GPIO_PG_PP5000_A_OD +#define GPIO_EN_PP5000 GPIO_EN_PP5000_A +#define GPIO_RECOVERY_L GPIO_EC_RECOVERY_BTN_ODL +#define GPIO_RECOVERY_L_2 GPIO_H1_EC_RECOVERY_BTN_ODL +#define GPIO_POWER_BUTTON_L GPIO_H1_EC_PWR_BTN_ODL +#define GPIO_PCH_WAKE_L GPIO_EC_PCH_WAKE_ODL +#define GPIO_PCH_PWRBTN_L GPIO_EC_PCH_PWR_BTN_ODL +#define GPIO_ENTERING_RW GPIO_EC_ENTERING_RW +#define GPIO_SYS_RESET_L GPIO_SYS_RST_ODL +#define GPIO_PCH_RSMRST_L GPIO_EC_PCH_RSMRST_L +#define GPIO_CPU_PROCHOT GPIO_EC_PROCHOT_ODL +#define GPIO_PCH_RTCRST GPIO_EC_PCH_RTCRST +#define GPIO_PCH_SYS_PWROK GPIO_EC_PCH_SYS_PWROK +#define GPIO_PCH_SLP_S0_L GPIO_SLP_S0_L +#define GPIO_PCH_SLP_S3_L GPIO_SLP_S3_L +#define GPIO_PCH_SLP_S4_L GPIO_SLP_S4_L +#define GPIO_AC_PRESENT GPIO_BJ_ADP_PRESENT_L + +/* + * There is no RSMRST input, so alias it to the output. This short-circuits + * common_intel_x86_handle_rsmrst. + */ +#define GPIO_RSMRST_L_PGOOD GPIO_PCH_RSMRST_L + +#endif /* __CROS_EC_BOARD_H */ diff --git a/board/scout/build.mk b/board/scout/build.mk new file mode 100644 index 0000000000..0acd315b39 --- /dev/null +++ b/board/scout/build.mk @@ -0,0 +1,15 @@ +# -*- makefile -*- +# Copyright 2021 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. +# +# Board specific files build +# + +CHIP:=npcx +CHIP_FAMILY:=npcx7 +CHIP_VARIANT:=npcx7m6fc + +board-y=board.o +board-y+=led.o +board-y+=pse.o diff --git a/board/scout/ec.tasklist b/board/scout/ec.tasklist new file mode 100644 index 0000000000..787ffbf479 --- /dev/null +++ b/board/scout/ec.tasklist @@ -0,0 +1,15 @@ +/* Copyright 2021 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. + */ + +/* + * See CONFIG_TASK_LIST in config.h for details. + */ + +#define CONFIG_TASK_LIST \ + TASK_ALWAYS(HOOKS, hook_task, NULL, LARGER_TASK_STACK_SIZE) \ + TASK_NOTEST(CHIPSET, chipset_task, NULL, LARGER_TASK_STACK_SIZE) \ + TASK_ALWAYS(HOSTCMD, host_command_task, NULL, 2048) \ + TASK_ALWAYS(POWERBTN, power_button_task, NULL, LARGER_TASK_STACK_SIZE) \ + TASK_ALWAYS(CONSOLE, console_task, NULL, LARGER_TASK_STACK_SIZE) diff --git a/board/scout/gpio.inc b/board/scout/gpio.inc new file mode 100644 index 0000000000..10f9ce3018 --- /dev/null +++ b/board/scout/gpio.inc @@ -0,0 +1,167 @@ +/* -*- mode:c -*- + * + * Copyright 2021 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. + */ + +/* Declare symbolic names for all the GPIOs that we care about. + * Pin names follow the schematic, and are aliased to other names if necessary. + * Note: Those with interrupt handlers must be declared first. */ + +/* Latency on this interrupt is extremely critical, so it comes first to ensure + * it gets placed first in gpio_wui_table so gpio_interrupt() needs to do + * minimal scanning. */ +GPIO_INT(CPU_C10_GATE_L, PIN(6, 7), GPIO_INT_BOTH, c10_gate_interrupt) + +/* Wake Source interrupts */ +GPIO_INT(EC_WP_ODL, PIN(A, 1), GPIO_INT_BOTH, switch_interrupt) +GPIO_INT(H1_EC_PWR_BTN_ODL, PIN(0, 1), GPIO_INT_BOTH, power_button_interrupt) + +/* Power sequencing interrupts */ +GPIO_INT(PG_PP5000_A_OD, PIN(D, 7), GPIO_INT_BOTH, power_signal_interrupt) +GPIO_INT(PG_PP1800_A_OD, PIN(3, 1), GPIO_INT_BOTH, power_signal_interrupt) +GPIO_INT(PG_VPRIM_CORE_A_OD, PIN(2, 3), GPIO_INT_BOTH, power_signal_interrupt) +GPIO_INT(PG_PP1050_A_OD, PIN(2, 2), GPIO_INT_BOTH, power_signal_interrupt) +/* EC output, but also interrupt so this can be polled as a power signal */ +GPIO_INT(EC_PCH_RSMRST_L, PIN(A, 6), GPIO_OUTPUT | GPIO_INT_F_RISING | GPIO_INT_F_FALLING, power_signal_interrupt) +#ifndef CONFIG_HOSTCMD_ESPI_VW_SLP_S4 +GPIO_INT(SLP_S4_L, PIN(D, 4), GPIO_INT_BOTH, power_signal_interrupt) +#endif +GPIO_INT(PG_PP2500_DRAM_U_OD, PIN(2, 0), GPIO_INT_BOTH, power_signal_interrupt) +GPIO_INT(PG_PP1200_U_OD, PIN(2, 1), GPIO_INT_BOTH, power_signal_interrupt) +#ifndef CONFIG_HOSTCMD_ESPI_VW_SLP_S3 +GPIO_INT(SLP_S3_L, PIN(A, 5), GPIO_INT_BOTH, slp_s3_interrupt) +#endif +GPIO_INT(PG_PP950_VCCIO_OD, PIN(1, 7), GPIO_INT_BOTH, power_signal_interrupt) +GPIO_INT(SLP_S0_L, PIN(D, 5), GPIO_INT_BOTH, power_signal_interrupt) +GPIO_INT(IMVP8_VRRDY_OD, PIN(1, 6), GPIO_INT_BOTH, power_signal_interrupt) + +/* + * Directly connected recovery button (not available on some boards). + */ +GPIO_INT(EC_RECOVERY_BTN_ODL, PIN(F, 1), GPIO_INT_BOTH, button_interrupt) +/* + * Recovery button input from H1. + */ +GPIO_INT(H1_EC_RECOVERY_BTN_ODL, PIN(2, 4), GPIO_INT_BOTH, button_interrupt) +GPIO(BJ_ADP_PRESENT_L, PIN(8, 2), GPIO_INT_BOTH | GPIO_PULL_UP) + +/* Port power control interrupts */ +GPIO_INT(HDMI_CONN0_OC_ODL, PIN(0, 7), GPIO_INT_BOTH, port_ocp_interrupt) +GPIO_INT(HDMI_CONN1_OC_ODL, PIN(0, 6), GPIO_INT_BOTH, port_ocp_interrupt) +GPIO_INT(USB_A0_OC_ODL, PIN(E, 4), GPIO_INT_BOTH, port_ocp_interrupt) +GPIO_INT(USB_A1_OC_ODL, PIN(A, 2), GPIO_INT_BOTH, port_ocp_interrupt) +GPIO_INT(USB_A2_OC_ODL, PIN(F, 5), GPIO_INT_BOTH, port_ocp_interrupt) +GPIO_INT(USB_A3_OC_ODL, PIN(0, 3), GPIO_INT_BOTH, port_ocp_interrupt) +/* May be reconfigured as input */ +GPIO_INT(USB_A4_OC_ODL, PIN(B, 0), GPIO_OUT_LOW | GPIO_INT_BOTH, port_ocp_interrupt) + +/* PCH/CPU signals */ +GPIO(EC_PCH_PWROK, PIN(0, 5), GPIO_OUT_LOW) +GPIO(EC_PCH_SYS_PWROK, PIN(3, 7), GPIO_OUT_LOW) +GPIO(EC_PCH_PWR_BTN_ODL, PIN(C, 1), GPIO_ODR_HIGH) +GPIO(EC_PCH_RTCRST, PIN(7, 6), GPIO_ODR_HIGH) +GPIO(EC_PCH_WAKE_ODL, PIN(7, 4), GPIO_ODR_HIGH) +GPIO(EC_PROCHOT_IN_OD, PIN(3, 4), GPIO_INPUT) +GPIO(EC_PROCHOT_ODL, PIN(6, 3), GPIO_ODR_HIGH) +GPIO(SYS_RST_ODL, PIN(C, 5), GPIO_ODR_HIGH) + +/* Power control outputs */ +GPIO(EN_PP5000_A, PIN(A, 4), GPIO_OUT_LOW) +GPIO(EN_PP3300_INA_H1_EC_ODL, PIN(5, 7), GPIO_ODR_HIGH) +GPIO(EN_PP1800_A, PIN(1, 5), GPIO_OUT_LOW) +GPIO(VCCST_PG_OD, PIN(1, 4), GPIO_ODR_LOW) +GPIO(EN_S0_RAILS, PIN(1, 1), GPIO_OUT_LOW) +GPIO(EN_ROA_RAILS, PIN(A, 3), GPIO_OUT_LOW) +GPIO(EN_PP950_VCCIO, PIN(1, 0), GPIO_OUT_LOW) +GPIO(EC_IMVP8_PE, PIN(A, 7), GPIO_OUT_LOW) +GPIO(EN_IMVP8_VR, PIN(F, 4), GPIO_OUT_LOW) + +/* Barreljack */ +GPIO(EN_PPVAR_BJ_ADP_L, PIN(0, 4), GPIO_OUT_LOW) + +/* USB type A */ +GPIO(EN_PP5000_USB_VBUS, PIN(8, 3), GPIO_OUT_LOW) +GPIO(USB_A_LOW_PWR_OD, PIN(9, 4), GPIO_ODR_LOW) +GPIO(USB_A2_STATUS_L, PIN(6, 1), GPIO_INPUT) +GPIO(USB_A3_STATUS_L, PIN(C, 7), GPIO_INPUT) + +/* USB type C */ +GPIO(USB_C0_TCPC_RST, PIN(9, 7), GPIO_OUT_LOW) +GPIO(USB_C0_POL_L, PIN(0, 0), GPIO_INPUT | GPIO_SEL_1P8V) /* USB-C Polarity */ + +/* Misc. */ +GPIO(M2_SSD_PLN, PIN(A, 0), GPIO_INPUT) +GPIO(EC_ENTERING_RW, PIN(E, 3), GPIO_OUT_LOW) +GPIO(CCD_MODE_ODL, PIN(E, 5), GPIO_ODR_HIGH) +GPIO(PACKET_MODE_EN, PIN(7, 5), GPIO_OUT_LOW) +GPIO(EC_RST_LTC4291_L, PIN(9, 6), GPIO_OUT_HIGH) /* PSE controller reset */ + +/* HDMI/CEC */ +GPIO(EN_PP5000_HDMI, PIN(5, 0), GPIO_OUT_LOW) +GPIO(HDMI_CONN0_CEC_OUT, PIN(B, 1), GPIO_ODR_HIGH) +GPIO(HDMI_CONN0_CEC_IN, PIN(4, 0), GPIO_INPUT) +GPIO(HDMI_CONN1_CEC_OUT, PIN(9, 5), GPIO_ODR_HIGH) +GPIO(HDMI_CONN1_CEC_IN, PIN(D, 3), GPIO_INPUT) + +/* I2C pins - Alternate function below configures I2C module on these pins */ +GPIO(I2C0_SCL, PIN(B, 5), GPIO_INPUT) /* EC_I2C_INA_SCL */ +GPIO(I2C0_SDA, PIN(B, 4), GPIO_INPUT) /* EC_I2C_INA_SDA */ +GPIO(I2C1_SCL, PIN(9, 0), GPIO_INPUT) /* EC_I2C_USB_C0_TCPPC_SCL */ +GPIO(I2C1_SDA, PIN(8, 7), GPIO_INPUT) /* EC_I2C_USB_C0_TCPPC_SDA */ +GPIO(I2C3_SCL, PIN(D, 1), GPIO_INPUT) /* EC_I2C_USB_C0_TCPC_SCL */ +GPIO(I2C3_SDA, PIN(D, 0), GPIO_INPUT) /* EC_I2C_USB_C0_TCPC_SDA */ +GPIO(I2C4_SCL, PIN(F, 3), GPIO_INPUT) /* EC_I2C_LTC_SCL */ +GPIO(I2C4_SDA, PIN(F, 2), GPIO_INPUT) /* EC_I2C_LTC_SDA */ +GPIO(I2C5_SCL, PIN(3, 3), GPIO_INPUT) /* EC_I2C_IMVP8_SCL */ +GPIO(I2C5_SDA, PIN(3, 6), GPIO_INPUT) /* EC_I2C_IMVP8_SDA */ +GPIO(I2C7_SCL, PIN(B, 3), GPIO_INPUT) /* EC_I2C_EEPROM_SCL */ +GPIO(I2C7_SDA, PIN(B, 2), GPIO_INPUT) /* EC_I2C_EEPROM_SDA */ + +/* Alternate functions GPIO definitions */ +ALTERNATE(PIN_MASK(B, 0x30), 0, MODULE_I2C, 0) /* I2C0 */ +ALTERNATE(PIN_MASK(9, 0x01), 0, MODULE_I2C, 0) /* I2C1 SCL */ +ALTERNATE(PIN_MASK(8, 0x80), 0, MODULE_I2C, 0) /* I2C1 SDA */ +ALTERNATE(PIN_MASK(D, 0x03), 0, MODULE_I2C, 0) /* I2C3 */ +ALTERNATE(PIN_MASK(F, 0x0C), 0, MODULE_I2C, 0) /* I2C4 */ +ALTERNATE(PIN_MASK(3, 0x48), 0, MODULE_I2C, 0) /* I2C5 */ +ALTERNATE(PIN_MASK(B, 0x0C), 0, MODULE_I2C, 0) /* I2C7 */ + +/* PWM */ +ALTERNATE(PIN_MASK(C, 0x08), 0, MODULE_PWM, 0) /* PWM0 - Red Led */ +ALTERNATE(PIN_MASK(C, 0x10), 0, MODULE_PWM, 0) /* PWM2 - White Led */ +ALTERNATE(PIN_MASK(B, 0x80), 0, MODULE_PWM, 0) /* PWM5 - Fan 1 */ +ALTERNATE(PIN_MASK(7, 0x08), 0, MODULE_PWM, 0) /* TA2 - Fan Tachometer */ + +/* ADC */ +ALTERNATE(PIN_MASK(4, 0x2A), 0, MODULE_ADC, 0) /* ADC0, ADC2, ADC4 */ +ALTERNATE(PIN_MASK(E, 0x02), 0, MODULE_ADC, 0) /* ADC7 */ +ALTERNATE(PIN_MASK(F, 0x01), 0, MODULE_ADC, 0) /* ADC9 */ + +/* UART */ +ALTERNATE(PIN_MASK(6, 0x30), 0, MODULE_UART, 0) /* UART from EC to Servo */ + +/* Unused pins */ +UNUSED(PIN(1, 3)) /* EC_GP_SEL1_ODL */ +UNUSED(PIN(C, 0)) /* FAN_PWM_2 */ +UNUSED(PIN(8, 0)) /* LED_BLUE_L */ +UNUSED(PIN(4, 4)) /* ADC1/TEMP_SENSOR_2 */ +UNUSED(PIN(4, 2)) /* ADC3/TEMP_SENSOR_3 */ +UNUSED(PIN(C, 2)) /* A12 NC */ +UNUSED(PIN(9, 2)) /* K8 NC */ +UNUSED(PIN(9, 1)) /* L8 NC */ +UNUSED(PIN(1, 2)) /* C6 NC */ +UNUSED(PIN(6, 6)) /* H4 NC */ +UNUSED(PIN(8, 1)) /* L6 NC */ +UNUSED(PIN(C, 6)) /* B11 NC */ +UNUSED(PIN(E, 2)) /* B8 NC */ +UNUSED(PIN(8, 5)) /* L7 NC */ +UNUSED(PIN(3, 2)) /* E5 NC */ +UNUSED(PIN(D, 6)) /* F6 NC */ +UNUSED(PIN(3, 5)) /* F5 NC */ +UNUSED(PIN(5, 6)) /* M2 NC */ +UNUSED(PIN(D, 2)) /* C11 NC */ +UNUSED(PIN(8, 6)) /* J8 NC */ +UNUSED(PIN(9, 3)) /* M11 NC */ +UNUSED(PIN(7, 2)) /* H6 NC */ diff --git a/board/scout/led.c b/board/scout/led.c new file mode 100644 index 0000000000..a9f70d2d40 --- /dev/null +++ b/board/scout/led.c @@ -0,0 +1,274 @@ +/* Copyright 2021 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. + * + * Power LED control for Puff. + * Solid green - active power + * Green flashing - suspended + * Red flashing - alert + * Solid red - critical + */ + +#include "chipset.h" +#include "console.h" +#include "ec_commands.h" +#include "gpio.h" +#include "hooks.h" +#include "led_common.h" +#include "pwm.h" +#include "timer.h" +#include "util.h" + +#define CPRINTS(format, args...) cprints(CC_GPIO, format, ## args) + +/* + * Due to the CSME-Lite processing, upon startup the CPU transitions through + * S0->S3->S5->S3->S0, causing the LED to turn on/off/on, so + * delay turning off the LED during suspend/shutdown. + */ +#define LED_CPU_DELAY_MS (2000 * MSEC) + +const enum ec_led_id supported_led_ids[] = {EC_LED_ID_POWER_LED}; +const int supported_led_ids_count = ARRAY_SIZE(supported_led_ids); + +enum led_color { + LED_OFF = 0, + LED_RED, + LED_WHITE, + LED_AMBER, + + /* Number of colors, not a color itself */ + LED_COLOR_COUNT +}; + +static int set_color_power(enum led_color color, int duty) +{ + int white = 0; + int red = 0; + + if (duty < 0 || 100 < duty) + return EC_ERROR_UNKNOWN; + + switch (color) { + case LED_OFF: + break; + case LED_WHITE: + white = 1; + break; + case LED_RED: + red = 1; + break; + case LED_AMBER: + red = 1; + white = 1; + break; + default: + return EC_ERROR_UNKNOWN; + } + + if (red) + pwm_set_duty(PWM_CH_LED_RED, duty); + else + pwm_set_duty(PWM_CH_LED_RED, 0); + + if (white) + pwm_set_duty(PWM_CH_LED_WHITE, duty); + else + pwm_set_duty(PWM_CH_LED_WHITE, 0); + + return EC_SUCCESS; +} + +static int set_color(enum ec_led_id id, enum led_color color, int duty) +{ + switch (id) { + case EC_LED_ID_POWER_LED: + return set_color_power(color, duty); + default: + return EC_ERROR_UNKNOWN; + } +} + +#define LED_PULSE_US (2 * SECOND) +/* 40 msec for nice and smooth transition. */ +#define LED_PULSE_TICK_US (40 * MSEC) + +/* When pulsing is enabled, brightness is incremented by <duty_inc> every + * <interval> usec from 0 to 100% in LED_PULSE_US usec. Then it's decremented + * likewise in LED_PULSE_US usec. + */ +static struct { + uint32_t interval; + int duty_inc; + enum led_color color; + int duty; +} led_pulse; + +#define CONFIG_TICK(interval, color) \ + config_tick((interval), 100 / (LED_PULSE_US / (interval)), (color)) + +static void config_tick(uint32_t interval, int duty_inc, enum led_color color) +{ + led_pulse.interval = interval; + led_pulse.duty_inc = duty_inc; + led_pulse.color = color; + led_pulse.duty = 0; +} + +static void pulse_power_led(enum led_color color) +{ + set_color(EC_LED_ID_POWER_LED, color, led_pulse.duty); + if (led_pulse.duty + led_pulse.duty_inc > 100) + led_pulse.duty_inc = led_pulse.duty_inc * -1; + else if (led_pulse.duty + led_pulse.duty_inc < 0) + led_pulse.duty_inc = led_pulse.duty_inc * -1; + led_pulse.duty += led_pulse.duty_inc; +} + +static void led_tick(void); +DECLARE_DEFERRED(led_tick); +static void led_tick(void) +{ + uint32_t elapsed; + uint32_t next = 0; + uint32_t start = get_time().le.lo; + + if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED)) + pulse_power_led(led_pulse.color); + elapsed = get_time().le.lo - start; + next = led_pulse.interval > elapsed ? led_pulse.interval - elapsed : 0; + hook_call_deferred(&led_tick_data, next); +} + +static void led_suspend(void) +{ + CONFIG_TICK(LED_PULSE_TICK_US, LED_WHITE); + led_tick(); +} +DECLARE_DEFERRED(led_suspend); + +static void led_shutdown(void) +{ + if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED)) + set_color(EC_LED_ID_POWER_LED, LED_OFF, 0); +} +DECLARE_DEFERRED(led_shutdown); + +static void led_shutdown_hook(void) +{ + hook_call_deferred(&led_tick_data, -1); + hook_call_deferred(&led_suspend_data, -1); + hook_call_deferred(&led_shutdown_data, LED_CPU_DELAY_MS); +} +DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, led_shutdown_hook, HOOK_PRIO_DEFAULT); + +static void led_suspend_hook(void) +{ + hook_call_deferred(&led_shutdown_data, -1); + hook_call_deferred(&led_suspend_data, LED_CPU_DELAY_MS); +} +DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, led_suspend_hook, HOOK_PRIO_DEFAULT); + +static void led_resume(void) +{ + /* Assume there is no race condition with led_tick, which also + * runs in hook_task. + */ + hook_call_deferred(&led_tick_data, -1); + /* + * Avoid invoking the suspend/shutdown delayed hooks. + */ + hook_call_deferred(&led_suspend_data, -1); + hook_call_deferred(&led_shutdown_data, -1); + if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED)) + set_color(EC_LED_ID_POWER_LED, LED_WHITE, 100); +} +DECLARE_HOOK(HOOK_CHIPSET_RESUME, led_resume, HOOK_PRIO_DEFAULT); + +static void led_init(void) +{ + pwm_enable(PWM_CH_LED_RED, 1); + pwm_enable(PWM_CH_LED_WHITE, 1); + + if (chipset_in_state(CHIPSET_STATE_ON)) + led_resume(); + else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) + led_suspend(); + else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) + led_shutdown(); +} +DECLARE_HOOK(HOOK_INIT, led_init, HOOK_PRIO_INIT_PWM + 1); + +void led_alert(int enable) +{ + if (enable) { + /* Overwrite the current signal */ + config_tick(1 * SECOND, 100, LED_RED); + led_tick(); + } else { + /* Restore the previous signal */ + if (chipset_in_state(CHIPSET_STATE_ON)) + led_resume(); + else if (chipset_in_state(CHIPSET_STATE_SUSPEND)) + led_suspend_hook(); + else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) + led_shutdown_hook(); + } +} + +void show_critical_error(void) +{ + hook_call_deferred(&led_tick_data, -1); + if (led_auto_control_is_enabled(EC_LED_ID_POWER_LED)) + set_color(EC_LED_ID_POWER_LED, LED_RED, 100); +} + +static int command_led(int argc, char **argv) +{ + enum ec_led_id id = EC_LED_ID_POWER_LED; + + if (argc < 2) + return EC_ERROR_PARAM_COUNT; + + if (!strcasecmp(argv[1], "debug")) { + led_auto_control(id, !led_auto_control_is_enabled(id)); + ccprintf("o%s\n", led_auto_control_is_enabled(id) ? "ff" : "n"); + } else if (!strcasecmp(argv[1], "off")) { + set_color(id, LED_OFF, 0); + } else if (!strcasecmp(argv[1], "red")) { + set_color(id, LED_RED, 100); + } else if (!strcasecmp(argv[1], "white")) { + set_color(id, LED_WHITE, 100); + } else if (!strcasecmp(argv[1], "amber")) { + set_color(id, LED_AMBER, 100); + } else if (!strcasecmp(argv[1], "alert")) { + led_alert(1); + } else if (!strcasecmp(argv[1], "crit")) { + show_critical_error(); + } else { + return EC_ERROR_PARAM1; + } + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(led, command_led, + "[debug|red|white|amber|off|alert|crit]", + "Turn on/off LED."); + +void led_get_brightness_range(enum ec_led_id led_id, uint8_t *brightness_range) +{ + brightness_range[EC_LED_COLOR_RED] = 100; + brightness_range[EC_LED_COLOR_WHITE] = 100; + brightness_range[EC_LED_COLOR_AMBER] = 100; +} + +int led_set_brightness(enum ec_led_id id, const uint8_t *brightness) +{ + if (brightness[EC_LED_COLOR_RED]) + return set_color(id, LED_RED, brightness[EC_LED_COLOR_RED]); + else if (brightness[EC_LED_COLOR_WHITE]) + return set_color(id, LED_WHITE, brightness[EC_LED_COLOR_WHITE]); + else if (brightness[EC_LED_COLOR_AMBER]) + return set_color(id, LED_AMBER, brightness[EC_LED_COLOR_AMBER]); + else + return set_color(id, LED_OFF, 0); +} diff --git a/board/scout/pse.c b/board/scout/pse.c new file mode 100644 index 0000000000..1c1b6bb41b --- /dev/null +++ b/board/scout/pse.c @@ -0,0 +1,266 @@ +/* Copyright 2021 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. + */ + +/* + * The LTC4291 is a power over ethernet (PoE) power sourcing equipment (PSE) + * controller. + */ + +#include "common.h" +#include "console.h" +#include "ec_commands.h" +#include "hooks.h" +#include "host_command.h" +#include "i2c.h" +#include "string.h" +#include "timer.h" +#include "util.h" + +#define LTC4291_I2C_ADDR 0x2C + +#define LTC4291_REG_SUPEVN_COR 0x0B +#define LTC4291_REG_STATPWR 0x10 +#define LTC4291_REG_STATPIN 0x11 +#define LTC4291_REG_OPMD 0x12 +#define LTC4291_REG_DISENA 0x13 +#define LTC4291_REG_DETENA 0x14 +#define LTC4291_REG_DETPB 0x18 +#define LTC4291_REG_PWRPB 0x19 +#define LTC4291_REG_RSTPB 0x1A +#define LTC4291_REG_ID 0x1B +#define LTC4291_REG_DEVID 0x43 +#define LTC4291_REG_HPMD1 0x46 +#define LTC4291_REG_HPMD2 0x4B +#define LTC4291_REG_HPMD3 0x50 +#define LTC4291_REG_HPMD4 0x55 +#define LTC4291_REG_LPWRPB 0x6E + +#define LTC4291_FLD_STATPIN_AUTO BIT(0) +#define LTC4291_FLD_RSTPB_RSTALL BIT(4) + +#define LTC4291_STATPWR_ON_PORT(port) (0x01 << (port)) +#define LTC4291_DETENA_EN_PORT(port) (0x11 << (port)) +#define LTC4291_DETPB_EN_PORT(port) (0x11 << (port)) +#define LTC4291_PWRPB_OFF_PORT(port) (0x10 << (port)) + +#define LTC4291_OPMD_AUTO 0xFF +#define LTC4291_DISENA_ALL 0x0F +#define LTC4291_DETENA_ALL 0xFF +#define LTC4291_ID 0x64 +#define LTC4291_DEVID 0x38 +#define LTC4291_HPMD_MIN 0x00 +#define LTC4291_HPMD_MAX 0xA8 + +#define LTC4291_PORT_MAX 4 + +#define LTC4291_RESET_DELAY_US (20 * MSEC) + +#define I2C_PSE_READ(reg, data) \ + i2c_read8(I2C_PORT_PSE, LTC4291_I2C_ADDR, LTC4291_REG_##reg, (data)) + +#define I2C_PSE_WRITE(reg, data) \ + i2c_write8(I2C_PORT_PSE, LTC4291_I2C_ADDR, LTC4291_REG_##reg, (data)) + +#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) + +static int pse_write_hpmd(int port, int val) +{ + switch (port) { + case 0: + return I2C_PSE_WRITE(HPMD1, val); + case 1: + return I2C_PSE_WRITE(HPMD2, val); + case 2: + return I2C_PSE_WRITE(HPMD3, val); + case 3: + return I2C_PSE_WRITE(HPMD4, val); + default: + return EC_ERROR_INVAL; + } +} + +/* + * Port 1: 100W + * Port 2-4: 15W + */ +static int pse_port_hpmd[4] = { + LTC4291_HPMD_MAX, + LTC4291_HPMD_MIN, + LTC4291_HPMD_MIN, + LTC4291_HPMD_MIN, +}; + +static int pse_port_enable(int port) +{ + /* Enable detection and classification */ + return I2C_PSE_WRITE(DETPB, LTC4291_DETPB_EN_PORT(port)); +} + +static int pse_port_disable(int port) +{ + /* Request power off (this also disables detection/classification) */ + return I2C_PSE_WRITE(PWRPB, LTC4291_PWRPB_OFF_PORT(port)); +} + +static int pse_init_worker(void) +{ + timestamp_t deadline; + int err, id, devid, statpin, port; + + /* Ignore errors -- may already be resetting */ + I2C_PSE_WRITE(RSTPB, LTC4291_FLD_RSTPB_RSTALL); + + deadline.val = get_time().val + LTC4291_RESET_DELAY_US; + while ((err = I2C_PSE_READ(ID, &id)) != 0) { + if (timestamp_expired(deadline, NULL)) + return err; + } + + err = I2C_PSE_READ(DEVID, &devid); + if (err != 0) + return err; + + if (id != LTC4291_ID || devid != LTC4291_DEVID) + return EC_ERROR_INVAL; + + err = I2C_PSE_READ(STATPIN, &statpin); + if (err != 0) + return err; + + /* + * We don't want to supply power until we've had a chance to set the + * limits. + */ + if (statpin & LTC4291_FLD_STATPIN_AUTO) + CPRINTS("WARN: PSE reset in AUTO mode"); + + err = I2C_PSE_WRITE(OPMD, LTC4291_OPMD_AUTO); + if (err != 0) + return err; + + /* Set maximum power each port is allowed to allocate. */ + for (port = 0; port < LTC4291_PORT_MAX; port++) { + err = pse_write_hpmd(port, pse_port_hpmd[port]); + if (err != 0) + return err; + } + + err = I2C_PSE_WRITE(DISENA, LTC4291_DISENA_ALL); + if (err != 0) + return err; + + err = I2C_PSE_WRITE(DETENA, LTC4291_DETENA_ALL); + if (err != 0) + return err; + + return 0; +} + +static void pse_init(void) +{ + int err; + + err = pse_init_worker(); + if (err != 0) + CPRINTS("PSE init failed: %d", err); + else + CPRINTS("PSE init done"); +} +DECLARE_HOOK(HOOK_CHIPSET_RESUME, pse_init, HOOK_PRIO_DEFAULT); + +/* Also reset the PSE on a reboot to toggle the power. */ +DECLARE_HOOK(HOOK_CHIPSET_RESET, pse_init, HOOK_PRIO_DEFAULT); + +static int command_pse(int argc, char **argv) +{ + int port; + + /* + * TODO(b/156399232): endeavour: PSE controller reset by PLTRST + * + * Initialization does not reliably work after reset because the device + * is held in reset by the AP. Running this command after boot finishes + * always succeeds. Remove once the reset signal changes. + */ + if (!strncmp(argv[1], "init", 4)) + return pse_init_worker(); + + if (argc != 3) + return EC_ERROR_PARAM_COUNT; + + port = atoi(argv[1]); + if (port < 0 || port >= LTC4291_PORT_MAX) + return EC_ERROR_PARAM1; + + if (!strncmp(argv[2], "off", 3)) + return pse_port_disable(port); + else if (!strncmp(argv[2], "on", 2)) + return pse_port_enable(port); + else if (!strncmp(argv[2], "min", 3)) + return pse_write_hpmd(port, LTC4291_HPMD_MIN); + else if (!strncmp(argv[2], "max", 3)) + return pse_write_hpmd(port, LTC4291_HPMD_MAX); + else + return EC_ERROR_PARAM2; +} +DECLARE_CONSOLE_COMMAND(pse, command_pse, + "<port# 0-3> <off | on | min | max>", + "Set PSE port power"); + +static int ec_command_pse_status(int port, uint8_t *status) +{ + int detena, statpwr; + int err; + + err = I2C_PSE_READ(DETENA, &detena); + if (err != 0) + return err; + + err = I2C_PSE_READ(STATPWR, &statpwr); + if (err != 0) + return err; + + if ((detena & LTC4291_DETENA_EN_PORT(port)) == 0) + *status = EC_PSE_STATUS_DISABLED; + else if ((statpwr & LTC4291_STATPWR_ON_PORT(port)) == 0) + *status = EC_PSE_STATUS_ENABLED; + else + *status = EC_PSE_STATUS_POWERED; + + return 0; +} + +static enum ec_status ec_command_pse(struct host_cmd_handler_args *args) +{ + const struct ec_params_pse *p = args->params; + int err = 0; + + if (p->port >= LTC4291_PORT_MAX) + return EC_RES_INVALID_PARAM; + + switch (p->cmd) { + case EC_PSE_STATUS: { + struct ec_response_pse_status *r = args->response; + + args->response_size = sizeof(*r); + err = ec_command_pse_status(p->port, &r->status); + break; + } + case EC_PSE_ENABLE: + err = pse_port_enable(p->port); + break; + case EC_PSE_DISABLE: + err = pse_port_disable(p->port); + break; + default: + return EC_RES_INVALID_PARAM; + } + + if (err) + return EC_RES_ERROR; + + return EC_RES_SUCCESS; +} +DECLARE_HOST_COMMAND(EC_CMD_PSE, ec_command_pse, EC_VER_MASK(0)); |