/* Copyright 2021 The ChromiumOS Authors * 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 "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 "temp_sensor/thermistor.h" #include "thermal.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 * 1500) #define PWR_FRONT_LOW (5 * 900) #define PWR_REAR (5 * 1500) #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; /* * 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_A1_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 (!gpio_get_level(GPIO_HDMI_CONN2_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); } /* * Reverse current protection: * When the board asserts +5Vs_V2_ADP_PRESENT_L active low, the EC needs * to turn off the jack charger by setting EN_AC_JACK_CHARGER_EC_L high. * * When the board asserts BJ_ADP_PRESENT_L active low, the EC needs to * enable the jack charger by setting EN_AC_JACK_CHARGER_EC_L low. */ static void ads_5v_deferred(void) { int ads_5v_enable = !gpio_get_level(GPIO_ADS_5VS_V2_ADP_PRESENT_L); if (ads_5v_enable) gpio_set_level(GPIO_EN_AC_JACK_CHARGER_EC_L, 1); } DECLARE_DEFERRED(ads_5v_deferred); void ads_5v_interrupt(enum gpio_signal signal) { /* ADS 5v control time*/ hook_call_deferred(&ads_5v_deferred_data, (5 * MSEC)); } /** * Handle debounced ADS 12v handler. */ static void ads_12v_deferred(void) { int ads_12v_enable = !gpio_get_level(GPIO_BJ_ADP_PRESENT_L); if (ads_12v_enable) gpio_set_level(GPIO_EN_AC_JACK_CHARGER_EC_L, 0); } DECLARE_DEFERRED(ads_12v_deferred); void ads_12v_interrupt(enum gpio_signal signal) { /* ADS 12v control time */ hook_call_deferred(&ads_12v_deferred_data, (5 * MSEC)); } /******************************************************************************/ /* Must come after other header files and interrupt handler declarations */ #include "gpio_list.h" /******************************************************************************/ /* 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_ACTIVE_LOW | PWM_CONFIG_DSLEEP, .freq = 2000 }, [PWM_CH_LED_WHITE] = { .channel = 2, .flags = PWM_CONFIG_ACTIVE_LOW | PWM_CONFIG_DSLEEP, .freq = 2000 }, }; /******************************************************************************/ /* I2C port map configuration */ const struct i2c_port_t i2c_ports[] = { { .name = "ina", .port = I2C_PORT_INA, .kbps = 400, .scl = GPIO_I2C0_SCL, .sda = GPIO_I2C0_SDA }, { .name = "tcpc0", .port = I2C_PORT_TCPC0, .kbps = 400, .scl = GPIO_I2C3_SCL, .sda = GPIO_I2C3_SDA }, { .name = "pse", .port = I2C_PORT_PSE, .kbps = 400, .scl = GPIO_I2C4_SCL, .sda = GPIO_I2C4_SDA }, { .name = "power", .port = I2C_PORT_POWER, .kbps = 400, .scl = GPIO_I2C5_SCL, .sda = GPIO_I2C5_SDA }, { .name = "eeprom", .port = I2C_PORT_EEPROM, .kbps = 400, .scl = GPIO_I2C7_SCL, .sda = 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 = 5300, }; 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. */ static const int temp_fan_off = C_TO_K(35); static const int temp_fan_max = C_TO_K(55); struct ec_thermal_config thermal_params[] = { [TEMP_SENSOR_CORE] = { .temp_host = { [EC_TEMP_THRESH_WARN] = 0, [EC_TEMP_THRESH_HIGH] = C_TO_K(85), [EC_TEMP_THRESH_HALT] = C_TO_K(90), }, .temp_host_release = { [EC_TEMP_THRESH_WARN] = 0, [EC_TEMP_THRESH_HIGH] = C_TO_K(78), [EC_TEMP_THRESH_HALT] = 0, }, .temp_fan_off = temp_fan_off, .temp_fan_max = temp_fan_max, }, }; BUILD_ASSERT(ARRAY_SIZE(thermal_params) == TEMP_SENSOR_COUNT); static const struct fan_step_1_1 fan_table0[] = { { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(35), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(41), .rpm = 2500 }, { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(40), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(44), .rpm = 2900 }, { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(42), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(46), .rpm = 3400 }, { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(44), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(48), .rpm = 3900 }, { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(46), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(50), .rpm = 4400 }, { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(48), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(52), .rpm = 4900 }, { .decreasing_temp_ratio_threshold = TEMP_TO_RATIO(50), .increasing_temp_ratio_threshold = TEMP_TO_RATIO(55), .rpm = 5300 }, }; #define NUM_FAN_LEVELS ARRAY_SIZE(fan_table0) static const struct fan_step_1_1 *fan_table = fan_table0; int fan_percent_to_rpm(int fan, int temp_ratio) { return temp_ratio_to_rpm_hysteresis(fan_table, NUM_FAN_LEVELS, fan, temp_ratio, NULL); } /* 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(BUTTON_RECOVERY); /* ADS GPIO interrupt enable*/ gpio_enable_interrupt(GPIO_ADS_5VS_V2_ADP_PRESENT_L); gpio_enable_interrupt(GPIO_BJ_ADP_PRESENT_L); } DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT); /******************************************************************************/ /* USB-A port control */ const int usb_port_enable[USB_PORT_COUNT] = {}; 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) { return 0; } void board_enable_s0_rails(int enable) { } /* * 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. */ /* No front USB A ports on moonbuggy. */ /* * [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); } hook_call_deferred(&power_monitor_data, delay); }