summaryrefslogtreecommitdiff
path: root/zephyr/projects/skyrim/src
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/projects/skyrim/src')
-rw-r--r--zephyr/projects/skyrim/src/common.c8
-rw-r--r--zephyr/projects/skyrim/src/morthal/ppc_config.c46
-rw-r--r--zephyr/projects/skyrim/src/morthal/usb_mux_config.c142
-rw-r--r--zephyr/projects/skyrim/src/power_signals.c231
-rw-r--r--zephyr/projects/skyrim/src/skyrim/alt_charger.c31
-rw-r--r--zephyr/projects/skyrim/src/skyrim/fan.c46
-rw-r--r--zephyr/projects/skyrim/src/skyrim/form_factor.c37
-rw-r--r--zephyr/projects/skyrim/src/skyrim/ppc_config.c46
-rw-r--r--zephyr/projects/skyrim/src/skyrim/usb_mux_config.c142
-rw-r--r--zephyr/projects/skyrim/src/stt.c28
-rw-r--r--zephyr/projects/skyrim/src/usb_pd_policy.c93
-rw-r--r--zephyr/projects/skyrim/src/usbc_config.c399
-rw-r--r--zephyr/projects/skyrim/src/winterhold/ppc_config.c27
-rw-r--r--zephyr/projects/skyrim/src/winterhold/usb_mux_config.c142
14 files changed, 1418 insertions, 0 deletions
diff --git a/zephyr/projects/skyrim/src/common.c b/zephyr/projects/skyrim/src/common.c
new file mode 100644
index 0000000000..af82139c1b
--- /dev/null
+++ b/zephyr/projects/skyrim/src/common.c
@@ -0,0 +1,8 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <zephyr/logging/log.h>
+
+LOG_MODULE_REGISTER(skyrim, CONFIG_SKYRIM_LOG_LEVEL);
diff --git a/zephyr/projects/skyrim/src/morthal/ppc_config.c b/zephyr/projects/skyrim/src/morthal/ppc_config.c
new file mode 100644
index 0000000000..f3ec1d312e
--- /dev/null
+++ b/zephyr/projects/skyrim/src/morthal/ppc_config.c
@@ -0,0 +1,46 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Morthal board-specific PPC code */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "driver/ppc/nx20p348x.h"
+#include "driver/ppc/aoz1380_public.h"
+#include "usbc_ppc.h"
+
+#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ##args)
+#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ##args)
+
+/*
+ * In the AOZ1380 PPC, there are no programmable features. We use
+ * the attached NCT3807 to control a GPIO to indicate 1A5 or 3A0
+ * current limits.
+ */
+int board_aoz1380_set_vbus_source_current_limit(int port, enum tcpc_rp_value rp)
+{
+ int rv = EC_SUCCESS;
+
+ rv = gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(ioex_usb_c0_ilim_3a_en),
+ (rp == TYPEC_RP_3A0) ? 1 : 0);
+
+ return rv;
+}
+
+void ppc_interrupt(enum gpio_signal signal)
+{
+ switch (signal) {
+ case GPIO_USB_C0_PPC_INT_ODL:
+ aoz1380_interrupt(0);
+ break;
+
+ case GPIO_USB_C1_PPC_INT_ODL:
+ nx20p348x_interrupt(1);
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/zephyr/projects/skyrim/src/morthal/usb_mux_config.c b/zephyr/projects/skyrim/src/morthal/usb_mux_config.c
new file mode 100644
index 0000000000..8fe76233e2
--- /dev/null
+++ b/zephyr/projects/skyrim/src/morthal/usb_mux_config.c
@@ -0,0 +1,142 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Morthal board-specific USB-C mux configuration */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "console.h"
+#include "cros_board_info.h"
+#include "cros_cbi.h"
+#include "driver/retimer/anx7483_public.h"
+#include "hooks.h"
+#include "ioexpander.h"
+#include "usb_mux.h"
+#include "usbc/usb_muxes.h"
+
+#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ##args)
+#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ##args)
+
+/*
+ * USB C0 (general) and C1 (just ANX DB) use IOEX pins to
+ * indicate flipped polarity to a protection switch.
+ */
+static int ioex_set_flip(int port, mux_state_t mux_state)
+{
+ if (port == 0) {
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c0_sbu_flip),
+ 1);
+ else
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c0_sbu_flip),
+ 0);
+ } else {
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c1_sbu_flip),
+ 1);
+ else
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c1_sbu_flip),
+ 0);
+ }
+
+ return EC_SUCCESS;
+}
+
+int board_anx7483_c0_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ /* Set the SBU polarity mux */
+ RETURN_ERROR(ioex_set_flip(me->usb_port, mux_state));
+
+ return anx7483_set_default_tuning(me, mux_state);
+}
+
+int board_anx7483_c1_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ bool flipped = mux_state & USB_PD_MUX_POLARITY_INVERTED;
+
+ /* Set the SBU polarity mux */
+ RETURN_ERROR(ioex_set_flip(me->usb_port, mux_state));
+
+ /* Remove flipped from the state for easier compraisons */
+ mux_state = mux_state & ~USB_PD_MUX_POLARITY_INVERTED;
+
+ RETURN_ERROR(anx7483_set_default_tuning(me, mux_state));
+
+ if (mux_state == USB_PD_MUX_USB_ENABLED) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DP_ENABLED) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DOCK && !flipped) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DOCK && flipped) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ }
+
+ return EC_SUCCESS;
+}
+
+int board_c1_ps8818_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ CPRINTSUSB("C1: PS8818 mux using default tuning");
+
+ /* Once a DP connection is established, we need to set IN_HPD */
+ if (mux_state & USB_PD_MUX_DP_ENABLED)
+ ioex_set_level(IOEX_USB_C1_HPD_IN_DB, 1);
+ else
+ ioex_set_level(IOEX_USB_C1_HPD_IN_DB, 0);
+
+ return 0;
+}
+
+static void setup_mux(void)
+{
+ uint32_t val;
+
+ if (cros_cbi_get_fw_config(FW_IO_DB, &val) != 0)
+ CPRINTSUSB("Error finding FW_DB_IO in CBI FW_CONFIG");
+ /* Val will have our dts default on error, so continue setup */
+
+ if (val == FW_IO_DB_PS8811_PS8818) {
+ CPRINTSUSB("C1: Setting PS8818 mux");
+ USB_MUX_ENABLE_ALTERNATIVE(usb_mux_chain_ps8818_port1);
+ } else if (val == FW_IO_DB_NONE_ANX7483) {
+ CPRINTSUSB("C1: Setting ANX7483 mux");
+ } else {
+ CPRINTSUSB("Unexpected DB_IO board: %d", val);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, setup_mux, HOOK_PRIO_INIT_I2C);
diff --git a/zephyr/projects/skyrim/src/power_signals.c b/zephyr/projects/skyrim/src/power_signals.c
new file mode 100644
index 0000000000..3c56e51ddc
--- /dev/null
+++ b/zephyr/projects/skyrim/src/power_signals.c
@@ -0,0 +1,231 @@
+/* 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.
+ */
+
+#include "ap_power/ap_power.h"
+#include "charger.h"
+#include "chipset.h"
+#include "config.h"
+#include "cros_board_info.h"
+#include "gpio_signal.h"
+#include "gpio/gpio_int.h"
+#include "hooks.h"
+#include "i2c.h"
+#include "ioexpander.h"
+#include "power.h"
+#include "power/amd_x86.h"
+#include "timer.h"
+
+/* Power Signal Input List */
+/* TODO: b/218904113: Convert to using Zephyr GPIOs */
+const struct power_signal_info power_signal_list[] = {
+ [X86_SLP_S3_N] = {
+ .gpio = GPIO_PCH_SLP_S3_L,
+ .flags = POWER_SIGNAL_ACTIVE_HIGH,
+ .name = "SLP_S3_DEASSERTED",
+ },
+ [X86_SLP_S5_N] = {
+ .gpio = GPIO_PCH_SLP_S5_L,
+ .flags = POWER_SIGNAL_ACTIVE_HIGH,
+ .name = "SLP_S5_DEASSERTED",
+ },
+ [X86_S0_PGOOD] = {
+ .gpio = GPIO_S0_PGOOD,
+ .flags = POWER_SIGNAL_ACTIVE_HIGH,
+ .name = "S0_PGOOD",
+ },
+ [X86_S5_PGOOD] = {
+ .gpio = GPIO_S5_PGOOD,
+ .flags = POWER_SIGNAL_ACTIVE_HIGH,
+ .name = "S5_PGOOD",
+ },
+};
+BUILD_ASSERT(ARRAY_SIZE(power_signal_list) == POWER_SIGNAL_COUNT);
+
+/* Chipset hooks */
+static void baseboard_suspend_change(struct ap_power_ev_callback *cb,
+ struct ap_power_ev_data data)
+{
+ switch (data.event) {
+ default:
+ return;
+
+ case AP_POWER_SUSPEND:
+ /* Disable display backlight and retimer */
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_ec_disable_disp_bl),
+ 1);
+ ioex_set_level(IOEX_USB_A1_RETIMER_EN, 0);
+ break;
+
+ case AP_POWER_RESUME:
+ /* Enable retimer and display backlight */
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_ec_disable_disp_bl),
+ 0);
+ ioex_set_level(IOEX_USB_A1_RETIMER_EN, 1);
+ /* Any retimer tuning can be done after the retimer turns on */
+ break;
+ }
+}
+
+static void baseboard_init(void)
+{
+ static struct ap_power_ev_callback cb;
+
+ /* Setup a suspend/resume callback */
+ ap_power_ev_init_callback(&cb, baseboard_suspend_change,
+ AP_POWER_RESUME | AP_POWER_SUSPEND);
+ ap_power_ev_add_callback(&cb);
+ /* Enable Power Group interrupts. */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_pg_groupc_s0));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_pg_lpddr_s0));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_pg_lpddr_s3));
+
+ /* Enable thermtrip interrupt */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_soc_thermtrip));
+}
+DECLARE_HOOK(HOOK_INIT, baseboard_init, HOOK_PRIO_POST_I2C);
+
+/**
+ * b/227296844: On G3->S5, wait for RSMRST_L to be deasserted before asserting
+ * PCH_PWRBTN_L. This can be as long as ~65ms after cold boot. Then wait an
+ * additional delay of T1a defined in the EDS before changing the power button.
+ */
+#define RSMRST_WAIT_DELAY 70
+#define EDS_PWR_BTN_RSMRST_T1A_DELAY 16
+void board_pwrbtn_to_pch(int level)
+{
+ timestamp_t start;
+
+ /* Add delay for G3 exit if asserting PWRBTN_L and RSMRST_L is low. */
+ if (!level &&
+ !gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_ec_soc_rsmrst_l))) {
+ start = get_time();
+ do {
+ usleep(500);
+ if (gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(
+ gpio_ec_soc_rsmrst_l)))
+ break;
+ } while (time_since32(start) < (RSMRST_WAIT_DELAY * MSEC));
+
+ if (!gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_ec_soc_rsmrst_l)))
+ ccprints("Error pwrbtn: RSMRST_L still low");
+
+ msleep(EDS_PWR_BTN_RSMRST_T1A_DELAY);
+ }
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_ec_soc_pwr_btn_l), level);
+}
+
+/* Note: signal parameter unused */
+void baseboard_set_soc_pwr_pgood(enum gpio_signal unused)
+{
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_ec_soc_pwr_good),
+ gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_en_pwr_pcore_s0_r)) &&
+ gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_pg_lpddr5_s0_od)) &&
+ gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_s0_pgood)));
+}
+
+/* TODO: Remove when board versions are no longer supported */
+#define MP2845A_I2C_ADDR_FLAGS 0x20
+#define MP2854A_MFR_VOUT_CMPS_MAX_REG 0x69
+#define MP2854A_MFR_LOW_PWR_SEL BIT(12)
+
+static void setup_mp2845(void)
+{
+ int version;
+
+ /* TODO: Remove when board versions are no longer supported */
+ if ((cbi_get_board_version(&version) == EC_SUCCESS) && version > 3)
+ return;
+
+ if (i2c_update16(chg_chips[CHARGER_SOLO].i2c_port,
+ MP2845A_I2C_ADDR_FLAGS, MP2854A_MFR_VOUT_CMPS_MAX_REG,
+ MP2854A_MFR_LOW_PWR_SEL, MASK_CLR))
+ ccprints("Failed to send mp2845 workaround");
+}
+DECLARE_DEFERRED(setup_mp2845);
+
+void baseboard_s0_pgood(enum gpio_signal signal)
+{
+ baseboard_set_soc_pwr_pgood(signal);
+
+ /* Chain off power signal interrupt handler for PG_PCORE_S0_R_OD */
+ power_signal_interrupt(signal);
+
+ /* Set up the MP2845, which is powered in S0 */
+ if (gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_s0_pgood)))
+ hook_call_deferred(&setup_mp2845_data, 50 * MSEC);
+}
+
+/* Note: signal parameter unused */
+void baseboard_set_en_pwr_pcore(enum gpio_signal unused)
+{
+ /*
+ * EC must AND signals PG_LPDDR5_S3_OD, PG_GROUPC_S0_OD, and
+ * EN_PWR_S0_R
+ */
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_en_pwr_pcore_s0_r),
+ gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_pg_lpddr5_s3_od)) &&
+ gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_pg_groupc_s0_od)) &&
+ gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_en_pwr_s0_r)));
+
+ /* Update EC_SOC_PWR_GOOD based on our results */
+ baseboard_set_soc_pwr_pgood(unused);
+}
+
+void baseboard_en_pwr_s0(enum gpio_signal signal)
+{
+ /* EC must AND signals SLP_S3_L and PG_PWR_S5 */
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_en_pwr_s0_r),
+ gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_slp_s3_l)) &&
+ gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_pg_pwr_s5)));
+
+ /* Change EN_PWR_PCORE_S0_R if needed*/
+ baseboard_set_en_pwr_pcore(signal);
+
+ /* Now chain off to the normal power signal interrupt handler. */
+ power_signal_interrupt(signal);
+}
+
+void baseboard_enable_hub(void)
+{
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_hub_rst), 0);
+}
+DECLARE_DEFERRED(baseboard_enable_hub);
+
+void baseboard_s5_pgood(enum gpio_signal signal)
+{
+ /* We must enable the USB hub at least 30ms after S5 PGOOD */
+ if (gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_pg_pwr_s5)))
+ hook_call_deferred(&baseboard_enable_hub_data, 30 * MSEC);
+ else
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_hub_rst), 1);
+
+ /* Continue to our signal AND-ing and power interrupt */
+ baseboard_en_pwr_s0(signal);
+}
+
+void baseboard_set_en_pwr_s3(enum gpio_signal signal)
+{
+ /* EC must enable PWR_S3 when SLP_S5_L goes high, disable on low */
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_en_pwr_s3),
+ gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_slp_s5_l)));
+
+ /* Chain off the normal power signal interrupt handler */
+ power_signal_interrupt(signal);
+}
+
+void baseboard_soc_thermtrip(enum gpio_signal signal)
+{
+ ccprints("SoC thermtrip reported, shutting down");
+ chipset_force_shutdown(CHIPSET_SHUTDOWN_THERMAL);
+}
diff --git a/zephyr/projects/skyrim/src/skyrim/alt_charger.c b/zephyr/projects/skyrim/src/skyrim/alt_charger.c
new file mode 100644
index 0000000000..4b717901cd
--- /dev/null
+++ b/zephyr/projects/skyrim/src/skyrim/alt_charger.c
@@ -0,0 +1,31 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <zephyr/devicetree.h>
+#include <zephyr/logging/log.h>
+
+#include "charger_chips.h"
+#include "common.h"
+#include "cros_board_info.h"
+#include "cros_cbi.h"
+#include "hooks.h"
+
+LOG_MODULE_DECLARE(skyrim, CONFIG_SKYRIM_LOG_LEVEL);
+
+static void alt_charger_init(void)
+{
+ int ret;
+ uint32_t val;
+
+ ret = cros_cbi_get_fw_config(FW_CHARGER, &val);
+ if (ret != 0) {
+ LOG_ERR("Error retrieving CBI FW_CONFIG field %d", FW_CHARGER);
+ return;
+ }
+
+ if (val == FW_CHARGER_ISL9538)
+ CHG_ENABLE_ALTERNATE(0);
+}
+DECLARE_HOOK(HOOK_INIT, alt_charger_init, HOOK_PRIO_POST_FIRST);
diff --git a/zephyr/projects/skyrim/src/skyrim/fan.c b/zephyr/projects/skyrim/src/skyrim/fan.c
new file mode 100644
index 0000000000..70d512bb78
--- /dev/null
+++ b/zephyr/projects/skyrim/src/skyrim/fan.c
@@ -0,0 +1,46 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <zephyr/devicetree.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/logging/log.h>
+
+#include "cros_board_info.h"
+#include "cros_cbi.h"
+#include "fan.h"
+#include "gpio/gpio.h"
+#include "hooks.h"
+
+LOG_MODULE_DECLARE(skyrim, CONFIG_SKYRIM_LOG_LEVEL);
+
+/*
+ * Skyrim fan support
+ */
+static void fan_init(void)
+{
+ int ret;
+ uint32_t val;
+ uint32_t board_version;
+ /*
+ * Retrieve the fan config.
+ */
+ ret = cros_cbi_get_fw_config(FW_FAN, &val);
+ if (ret != 0) {
+ LOG_ERR("Error retrieving CBI FW_CONFIG field %d", FW_FAN);
+ return;
+ }
+
+ ret = cbi_get_board_version(&board_version);
+ if (ret != EC_SUCCESS) {
+ LOG_ERR("Error retrieving CBI board version");
+ return;
+ }
+
+ if ((board_version >= 3) && (val != FW_FAN_PRESENT)) {
+ /* Disable the fan */
+ fan_set_count(0);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, fan_init, HOOK_PRIO_POST_FIRST);
diff --git a/zephyr/projects/skyrim/src/skyrim/form_factor.c b/zephyr/projects/skyrim/src/skyrim/form_factor.c
new file mode 100644
index 0000000000..f137c6db31
--- /dev/null
+++ b/zephyr/projects/skyrim/src/skyrim/form_factor.c
@@ -0,0 +1,37 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <zephyr/devicetree.h>
+#include <zephyr/logging/log.h>
+#include "common.h"
+#include "accelgyro.h"
+#include "cros_board_info.h"
+#include "hooks.h"
+#include "motionsense_sensors.h"
+
+LOG_MODULE_DECLARE(skyrim, CONFIG_SKYRIM_LOG_LEVEL);
+
+/*
+ * Mainboard orientation support.
+ */
+
+#define ALT_MAT SENSOR_ROT_STD_REF_NAME(DT_NODELABEL(lid_rot_ref1))
+#define LID_ACCEL SENSOR_ID(DT_NODELABEL(lid_accel))
+
+static void form_factor_init(void)
+{
+ int ret;
+ uint32_t val;
+ /*
+ * If the board version >=4
+ * use ver1 rotation matrix.
+ */
+ ret = cbi_get_board_version(&val);
+ if (ret == EC_SUCCESS && val >= 4) {
+ LOG_INF("Switching to ver1 lid");
+ motion_sensors[LID_ACCEL].rot_standard_ref = &ALT_MAT;
+ }
+}
+DECLARE_HOOK(HOOK_INIT, form_factor_init, HOOK_PRIO_POST_I2C);
diff --git a/zephyr/projects/skyrim/src/skyrim/ppc_config.c b/zephyr/projects/skyrim/src/skyrim/ppc_config.c
new file mode 100644
index 0000000000..bebc8adcc7
--- /dev/null
+++ b/zephyr/projects/skyrim/src/skyrim/ppc_config.c
@@ -0,0 +1,46 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Skyrim board-specific PPC code */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "driver/ppc/nx20p348x.h"
+#include "driver/ppc/aoz1380_public.h"
+#include "usbc_ppc.h"
+
+#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ##args)
+#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ##args)
+
+/*
+ * In the AOZ1380 PPC, there are no programmable features. We use
+ * the attached NCT3807 to control a GPIO to indicate 1A5 or 3A0
+ * current limits.
+ */
+int board_aoz1380_set_vbus_source_current_limit(int port, enum tcpc_rp_value rp)
+{
+ int rv = EC_SUCCESS;
+
+ rv = gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(ioex_usb_c0_ilim_3a_en),
+ (rp == TYPEC_RP_3A0) ? 1 : 0);
+
+ return rv;
+}
+
+void ppc_interrupt(enum gpio_signal signal)
+{
+ switch (signal) {
+ case GPIO_USB_C0_PPC_INT_ODL:
+ aoz1380_interrupt(0);
+ break;
+
+ case GPIO_USB_C1_PPC_INT_ODL:
+ nx20p348x_interrupt(1);
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/zephyr/projects/skyrim/src/skyrim/usb_mux_config.c b/zephyr/projects/skyrim/src/skyrim/usb_mux_config.c
new file mode 100644
index 0000000000..6c65e56d9e
--- /dev/null
+++ b/zephyr/projects/skyrim/src/skyrim/usb_mux_config.c
@@ -0,0 +1,142 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Skyrim board-specific USB-C mux configuration */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "console.h"
+#include "cros_board_info.h"
+#include "cros_cbi.h"
+#include "driver/retimer/anx7483_public.h"
+#include "hooks.h"
+#include "ioexpander.h"
+#include "usb_mux.h"
+#include "usbc/usb_muxes.h"
+
+#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ##args)
+#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ##args)
+
+/*
+ * USB C0 (general) and C1 (just ANX DB) use IOEX pins to
+ * indicate flipped polarity to a protection switch.
+ */
+static int ioex_set_flip(int port, mux_state_t mux_state)
+{
+ if (port == 0) {
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c0_sbu_flip),
+ 1);
+ else
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c0_sbu_flip),
+ 0);
+ } else {
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c1_sbu_flip),
+ 1);
+ else
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c1_sbu_flip),
+ 0);
+ }
+
+ return EC_SUCCESS;
+}
+
+int board_anx7483_c0_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ /* Set the SBU polarity mux */
+ RETURN_ERROR(ioex_set_flip(me->usb_port, mux_state));
+
+ return anx7483_set_default_tuning(me, mux_state);
+}
+
+int board_anx7483_c1_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ bool flipped = mux_state & USB_PD_MUX_POLARITY_INVERTED;
+
+ /* Set the SBU polarity mux */
+ RETURN_ERROR(ioex_set_flip(me->usb_port, mux_state));
+
+ /* Remove flipped from the state for easier compraisons */
+ mux_state = mux_state & ~USB_PD_MUX_POLARITY_INVERTED;
+
+ RETURN_ERROR(anx7483_set_default_tuning(me, mux_state));
+
+ if (mux_state == USB_PD_MUX_USB_ENABLED) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DP_ENABLED) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DOCK && !flipped) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DOCK && flipped) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ }
+
+ return EC_SUCCESS;
+}
+
+int board_c1_ps8818_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ CPRINTSUSB("C1: PS8818 mux using default tuning");
+
+ /* Once a DP connection is established, we need to set IN_HPD */
+ if (mux_state & USB_PD_MUX_DP_ENABLED)
+ ioex_set_level(IOEX_USB_C1_HPD_IN_DB, 1);
+ else
+ ioex_set_level(IOEX_USB_C1_HPD_IN_DB, 0);
+
+ return 0;
+}
+
+static void setup_mux(void)
+{
+ uint32_t val;
+
+ if (cros_cbi_get_fw_config(FW_IO_DB, &val) != 0)
+ CPRINTSUSB("Error finding FW_DB_IO in CBI FW_CONFIG");
+ /* Val will have our dts default on error, so continue setup */
+
+ if (val == FW_IO_DB_PS8811_PS8818) {
+ CPRINTSUSB("C1: Setting PS8818 mux");
+ USB_MUX_ENABLE_ALTERNATIVE(usb_mux_chain_ps8818_port1);
+ } else if (val == FW_IO_DB_NONE_ANX7483) {
+ CPRINTSUSB("C1: Setting ANX7483 mux");
+ } else {
+ CPRINTSUSB("Unexpected DB_IO board: %d", val);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, setup_mux, HOOK_PRIO_INIT_I2C);
diff --git a/zephyr/projects/skyrim/src/stt.c b/zephyr/projects/skyrim/src/stt.c
new file mode 100644
index 0000000000..40743fbc68
--- /dev/null
+++ b/zephyr/projects/skyrim/src/stt.c
@@ -0,0 +1,28 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Support code for STT temperature reporting */
+
+#include "chipset.h"
+#include "temp_sensor/pct2075.h"
+#include "temp_sensor/temp_sensor.h"
+
+int board_get_soc_temp_mk(int *temp_mk)
+{
+ if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
+ return EC_ERROR_NOT_POWERED;
+
+ return pct2075_get_val_mk(PCT2075_SENSOR_ID(DT_NODELABEL(soc_pct2075)),
+ temp_mk);
+}
+
+int board_get_ambient_temp_mk(int *temp_mk)
+{
+ if (chipset_in_state(CHIPSET_STATE_HARD_OFF))
+ return EC_ERROR_NOT_POWERED;
+
+ return pct2075_get_val_mk(PCT2075_SENSOR_ID(DT_NODELABEL(amb_pct2075)),
+ temp_mk);
+}
diff --git a/zephyr/projects/skyrim/src/usb_pd_policy.c b/zephyr/projects/skyrim/src/usb_pd_policy.c
new file mode 100644
index 0000000000..ec9f873863
--- /dev/null
+++ b/zephyr/projects/skyrim/src/usb_pd_policy.c
@@ -0,0 +1,93 @@
+/* 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.
+ */
+
+/* Shared USB-C policy for Zork boards */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "charge_manager.h"
+#include "chipset.h"
+#include "common.h"
+#include "compile_time_macros.h"
+#include "console.h"
+#include "ec_commands.h"
+#include "ioexpander.h"
+#include "system.h"
+#include "usb_mux.h"
+#include "usb_pd.h"
+#include "usbc_ppc.h"
+#include "util.h"
+
+int pd_check_vconn_swap(int port)
+{
+ /*
+ * Do not allow vconn swap 5V rail is off
+ * S5_PGOOD depends on PG_PP5000_S5 being asserted,
+ * so GPIO_S5_PGOOD is a reasonable proxy for PP5000_S5
+ */
+ return gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(gpio_pg_pwr_s5));
+}
+
+void pd_power_supply_reset(int port)
+{
+ /* Disable VBUS. */
+ ppc_vbus_source_enable(port, 0);
+
+ /* Enable discharge if we were previously sourcing 5V */
+ if (IS_ENABLED(CONFIG_USB_PD_DISCHARGE))
+ pd_set_vbus_discharge(port, 1);
+
+ /* Notify host of power info change. */
+ pd_send_host_event(PD_EVENT_POWER_CHANGE);
+}
+
+int pd_set_power_supply_ready(int port)
+{
+ int rv;
+
+ /* Disable charging. */
+ rv = ppc_vbus_sink_enable(port, 0);
+ if (rv)
+ return rv;
+
+ if (IS_ENABLED(CONFIG_USB_PD_DISCHARGE))
+ pd_set_vbus_discharge(port, 0);
+
+ /* Provide Vbus. */
+ rv = ppc_vbus_source_enable(port, 1);
+ if (rv)
+ return rv;
+
+ /* Notify host of power info change. */
+ pd_send_host_event(PD_EVENT_POWER_CHANGE);
+
+ return EC_SUCCESS;
+}
+
+__override int board_pd_set_frs_enable(int port, int enable)
+{
+ /*
+ * Both PPCs require the FRS GPIO to be set as soon as FRS capability
+ * is established.
+ */
+ if (port == 0)
+ ioex_set_level(IOEX_USB_C0_TCPC_FASTSW_CTL_EN, enable);
+ else
+ ioex_set_level(IOEX_USB_C1_TCPC_FASTSW_CTL_EN, enable);
+
+ return EC_SUCCESS;
+}
+
+/* Used by Vbus discharge common code with CONFIG_USB_PD_DISCHARGE */
+int board_vbus_source_enabled(int port)
+{
+ return tcpm_get_src_ctrl(port);
+}
+
+/* Used by USB charger task with CONFIG_USB_PD_5V_EN_CUSTOM */
+int board_is_sourcing_vbus(int port)
+{
+ return board_vbus_source_enabled(port);
+}
diff --git a/zephyr/projects/skyrim/src/usbc_config.c b/zephyr/projects/skyrim/src/usbc_config.c
new file mode 100644
index 0000000000..0ca421deca
--- /dev/null
+++ b/zephyr/projects/skyrim/src/usbc_config.c
@@ -0,0 +1,399 @@
+/* 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.
+ */
+
+/* Skyrim family-specific USB-C configuration */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "cros_board_info.h"
+#include "cros_cbi.h"
+#include "battery_fuel_gauge.h"
+#include "charge_manager.h"
+#include "charge_ramp.h"
+#include "charge_state_v2.h"
+#include "charge_state.h"
+#include "charger.h"
+#include "driver/bc12/pi3usb9201.h"
+#include "driver/charger/isl9241.h"
+#include "driver/ppc/nx20p348x.h"
+#include "driver/retimer/anx7483_public.h"
+#include "driver/retimer/ps8811.h"
+#include "driver/retimer/ps8818_public.h"
+#include "driver/tcpm/nct38xx.h"
+#include "driver/usb_mux/amd_fp6.h"
+#include "gpio/gpio_int.h"
+#include "hooks.h"
+#include "ioexpander.h"
+#include "power.h"
+#include "usb_mux.h"
+#include "usb_pd_tcpm.h"
+#include "usbc_ppc.h"
+#include "usbc/usb_muxes.h"
+
+#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ##args)
+#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ##args)
+
+/* USB-A ports */
+enum usba_port { USBA_PORT_A0 = 0, USBA_PORT_A1, USBA_PORT_COUNT };
+
+/* USB-C ports */
+enum usbc_port { USBC_PORT_C0 = 0, USBC_PORT_C1, USBC_PORT_COUNT };
+BUILD_ASSERT(USBC_PORT_COUNT == CONFIG_USB_PD_PORT_MAX_COUNT);
+
+static void reset_nct38xx_port(int port);
+
+static void usbc_interrupt_init(void)
+{
+ /* Enable PPC interrupts. */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c0_ppc));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c1_ppc));
+
+ /* Enable TCPC interrupts. */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c0_tcpc));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c1_tcpc));
+
+ /* Enable BC 1.2 interrupts */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c0_bc12));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c1_bc12));
+
+ /* Enable SBU fault interrupts */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c0_sbu_fault));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_c1_sbu_fault));
+}
+DECLARE_HOOK(HOOK_INIT, usbc_interrupt_init, HOOK_PRIO_POST_I2C);
+
+static void usb_fault_interrupt_init(void)
+{
+ /* Enable USB fault interrupts when we hit S5 */
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_hub_fault));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_a0_fault));
+ gpio_enable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_a1_fault));
+}
+DECLARE_HOOK(HOOK_CHIPSET_STARTUP, usb_fault_interrupt_init, HOOK_PRIO_DEFAULT);
+
+static void usb_fault_interrupt_disable(void)
+{
+ /* Disable USB fault interrupts leaving S5 */
+ gpio_disable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_hub_fault));
+ gpio_disable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_a0_fault));
+ gpio_disable_dt_interrupt(GPIO_INT_FROM_NODELABEL(int_usb_a1_fault));
+}
+DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, usb_fault_interrupt_disable,
+ HOOK_PRIO_DEFAULT);
+
+int board_set_active_charge_port(int port)
+{
+ int is_valid_port = (port >= 0 && port < CONFIG_USB_PD_PORT_MAX_COUNT);
+ int i;
+ int rv;
+
+ if (port == CHARGE_PORT_NONE) {
+ CPRINTSUSB("Disabling all charger ports");
+
+ /* Disable all ports. */
+ for (i = 0; i < ppc_cnt; i++) {
+ /*
+ * If this port had booted in dead battery mode, go
+ * ahead and reset it so EN_SNK responds properly.
+ */
+ if (nct38xx_get_boot_type(i) ==
+ NCT38XX_BOOT_DEAD_BATTERY) {
+ reset_nct38xx_port(i);
+ pd_set_error_recovery(i);
+ }
+
+ /*
+ * Do not return early if one fails otherwise we can
+ * get into a boot loop assertion failure.
+ */
+ if (ppc_vbus_sink_enable(i, 0))
+ CPRINTSUSB("Disabling C%d as sink failed.", i);
+ }
+
+ return EC_SUCCESS;
+ } else if (!is_valid_port) {
+ return EC_ERROR_INVAL;
+ }
+
+ /*
+ * Check if we can reset any ports in dead battery mode
+ *
+ * The NCT3807 may continue to keep EN_SNK low on the dead battery port
+ * and allow a dangerous level of voltage to pass through to the initial
+ * charge port (see b/183660105). We must reset the ports if we have
+ * sufficient battery to do so, which will bring EN_SNK back under
+ * normal control.
+ */
+ rv = EC_SUCCESS;
+ for (i = 0; i < board_get_usb_pd_port_count(); i++) {
+ if (nct38xx_get_boot_type(i) == NCT38XX_BOOT_DEAD_BATTERY) {
+ CPRINTSUSB("Found dead battery on %d", i);
+ /*
+ * If we have battery, get this port reset ASAP.
+ * This means temporarily rejecting charge manager
+ * sets to it.
+ */
+ if (pd_is_battery_capable()) {
+ reset_nct38xx_port(i);
+ pd_set_error_recovery(i);
+
+ if (port == i)
+ rv = EC_ERROR_INVAL;
+ } else if (port != i) {
+ /*
+ * If other port is selected and in dead battery
+ * mode, reset this port. Otherwise, reject
+ * change because we'll brown out.
+ */
+ if (nct38xx_get_boot_type(port) ==
+ NCT38XX_BOOT_DEAD_BATTERY) {
+ reset_nct38xx_port(i);
+ pd_set_error_recovery(i);
+ } else {
+ rv = EC_ERROR_INVAL;
+ }
+ }
+ }
+ }
+
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ /* Check if the port is sourcing VBUS. */
+ if (tcpm_get_src_ctrl(port)) {
+ CPRINTSUSB("Skip enable C%d", port);
+ return EC_ERROR_INVAL;
+ }
+
+ CPRINTSUSB("New charge port: C%d", port);
+
+ /*
+ * Turn off the other ports' sink path FETs, before enabling the
+ * requested charge port.
+ */
+ for (i = 0; i < ppc_cnt; i++) {
+ if (i == port)
+ continue;
+
+ if (ppc_vbus_sink_enable(i, 0))
+ CPRINTSUSB("C%d: sink path disable failed.", i);
+ }
+
+ /* Enable requested charge port. */
+ if (ppc_vbus_sink_enable(port, 1)) {
+ CPRINTSUSB("C%d: sink path enable failed.", port);
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
+}
+
+void board_set_charge_limit(int port, int supplier, int charge_ma, int max_ma,
+ int charge_mv)
+{
+ charge_set_input_current_limit(
+ MAX(charge_ma, CONFIG_CHARGER_INPUT_CURRENT), charge_mv);
+}
+
+void sbu_fault_interrupt(enum gpio_signal signal)
+{
+ int port = signal == IOEX_USB_C1_FAULT_ODL ? 1 : 0;
+
+ CPRINTSUSB("C%d: SBU fault", port);
+ pd_handle_overcurrent(port);
+}
+
+void usb_fault_interrupt(enum gpio_signal signal)
+{
+ int out;
+
+ CPRINTSUSB("USB fault(%d), alerting the SoC", signal);
+ out = gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_usb_hub_fault_q_odl)) &&
+ gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(ioex_usb_a0_fault_odl)) &&
+ gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(ioex_usb_a1_fault_db_odl));
+
+ gpio_pin_set_dt(GPIO_DT_FROM_NODELABEL(gpio_usb_fault_odl), out);
+}
+
+void usb_pd_soc_interrupt(enum gpio_signal signal)
+{
+ /*
+ * This interrupt is unexpected with our use of the SoC mux, so just log
+ * it as a point of interest.
+ */
+ CPRINTSUSB("SOC PD Interrupt");
+}
+
+/* Round up 3250 max current to multiple of 128mA for ISL9241 AC prochot. */
+#define SKYRIM_AC_PROCHOT_CURRENT_MA 3328
+static void set_ac_prochot(void)
+{
+ isl9241_set_ac_prochot(CHARGER_SOLO, SKYRIM_AC_PROCHOT_CURRENT_MA);
+}
+DECLARE_HOOK(HOOK_INIT, set_ac_prochot, HOOK_PRIO_DEFAULT);
+
+void tcpc_alert_event(enum gpio_signal signal)
+{
+ int port;
+
+ switch (signal) {
+ case GPIO_USB_C0_TCPC_INT_ODL:
+ port = 0;
+ break;
+ case GPIO_USB_C1_TCPC_INT_ODL:
+ port = 1;
+ break;
+ default:
+ return;
+ }
+
+ schedule_deferred_pd_interrupt(port);
+}
+
+static void reset_nct38xx_port(int port)
+{
+ const struct gpio_dt_spec *reset_gpio_l;
+ const struct device *ioex_port0, *ioex_port1;
+
+ /* TODO(b/225189538): Save and restore ioex signals */
+ if (port == USBC_PORT_C0) {
+ reset_gpio_l = GPIO_DT_FROM_NODELABEL(gpio_usb_c0_tcpc_rst_l);
+ ioex_port0 = DEVICE_DT_GET(DT_NODELABEL(ioex_c0_port0));
+ ioex_port1 = DEVICE_DT_GET(DT_NODELABEL(ioex_c0_port1));
+ } else if (port == USBC_PORT_C1) {
+ reset_gpio_l = GPIO_DT_FROM_NODELABEL(gpio_usb_c1_tcpc_rst_l);
+ ioex_port0 = DEVICE_DT_GET(DT_NODELABEL(ioex_c1_port0));
+ ioex_port1 = DEVICE_DT_GET(DT_NODELABEL(ioex_c1_port1));
+ } else {
+ /* Invalid port: do nothing */
+ return;
+ }
+
+ gpio_pin_set_dt(reset_gpio_l, 0);
+ msleep(NCT38XX_RESET_HOLD_DELAY_MS);
+ gpio_pin_set_dt(reset_gpio_l, 1);
+ nct38xx_reset_notify(port);
+ if (NCT3807_RESET_POST_DELAY_MS != 0)
+ msleep(NCT3807_RESET_POST_DELAY_MS);
+
+ /* Re-enable the IO expander pins */
+ gpio_reset_port(ioex_port0);
+ gpio_reset_port(ioex_port1);
+}
+
+void board_reset_pd_mcu(void)
+{
+ /* Reset TCPC0 */
+ reset_nct38xx_port(USBC_PORT_C0);
+
+ /* Reset TCPC1 */
+ reset_nct38xx_port(USBC_PORT_C1);
+}
+
+uint16_t tcpc_get_alert_status(void)
+{
+ uint16_t status = 0;
+
+ /*
+ * Check which port has the ALERT line set and ignore if that TCPC has
+ * its reset line active.
+ */
+ if (!gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_usb_c0_tcpc_int_odl))) {
+ if (gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(
+ gpio_usb_c0_tcpc_rst_l)) != 0)
+ status |= PD_STATUS_TCPC_ALERT_0;
+ }
+
+ if (!gpio_pin_get_dt(
+ GPIO_DT_FROM_NODELABEL(gpio_usb_c1_tcpc_int_odl))) {
+ if (gpio_pin_get_dt(GPIO_DT_FROM_NODELABEL(
+ gpio_usb_c1_tcpc_rst_l)) != 0)
+ status |= PD_STATUS_TCPC_ALERT_1;
+ }
+
+ return status;
+}
+
+void bc12_interrupt(enum gpio_signal signal)
+{
+ switch (signal) {
+ case GPIO_USB_C0_BC12_INT_ODL:
+ usb_charger_task_set_event(0, USB_CHG_EVENT_BC12);
+ break;
+
+ case GPIO_USB_C1_BC12_INT_ODL:
+ usb_charger_task_set_event(1, USB_CHG_EVENT_BC12);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Return if VBUS is sagging too low
+ *
+ * For legacy BC1.2 charging with CONFIG_CHARGE_RAMP_SW, ramp up input current
+ * until voltage drops to 4.5V. Don't go lower than this to be kind to the
+ * charger (see b/67964166).
+ */
+#define BC12_MIN_VOLTAGE 4500
+int board_is_vbus_too_low(int port, enum chg_ramp_vbus_state ramp_state)
+{
+ int voltage = 0;
+ int rv;
+
+ rv = charger_get_vbus_voltage(port, &voltage);
+
+ if (rv) {
+ CPRINTSUSB("%s rv=%d", __func__, rv);
+ return 0;
+ }
+
+ /*
+ * b/168569046: The ISL9241 sometimes incorrectly reports 0 for unknown
+ * reason, causing ramp to stop at 0.5A. Workaround this by ignoring 0.
+ * This partly defeats the point of ramping, but will still catch
+ * VBUS below 4.5V and above 0V.
+ */
+ if (voltage == 0) {
+ CPRINTSUSB("%s vbus=0", __func__);
+ return 0;
+ }
+
+ if (voltage < BC12_MIN_VOLTAGE)
+ CPRINTSUSB("%s vbus=%d", __func__, voltage);
+
+ return voltage < BC12_MIN_VOLTAGE;
+}
+
+#define SAFE_RESET_VBUS_DELAY_MS 900
+#define SAFE_RESET_VBUS_MV 5000
+void board_hibernate(void)
+{
+ int port;
+ enum ec_error_list ret;
+
+ /*
+ * If we are charging, then drop the Vbus level down to 5V to ensure
+ * that we don't get locked out of the 6.8V OVLO for our PPCs in
+ * dead-battery mode. This is needed when the TCPC/PPC rails go away.
+ * (b/79218851, b/143778351, b/147007265)
+ */
+ port = charge_manager_get_active_charge_port();
+ if (port != CHARGE_PORT_NONE) {
+ pd_request_source_voltage(port, SAFE_RESET_VBUS_MV);
+
+ /* Give PD task and PPC chip time to get to 5V */
+ msleep(SAFE_RESET_VBUS_DELAY_MS);
+ }
+
+ /* Try to put our battery fuel gauge into sleep mode */
+ ret = battery_sleep_fuel_gauge();
+ if ((ret != EC_SUCCESS) && (ret != EC_ERROR_UNIMPLEMENTED))
+ cprints(CC_SYSTEM, "Failed to send battery sleep command");
+}
diff --git a/zephyr/projects/skyrim/src/winterhold/ppc_config.c b/zephyr/projects/skyrim/src/winterhold/ppc_config.c
new file mode 100644
index 0000000000..72ddb6ce6c
--- /dev/null
+++ b/zephyr/projects/skyrim/src/winterhold/ppc_config.c
@@ -0,0 +1,27 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Winterhold board-specific PPC code */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "driver/ppc/nx20p348x.h"
+#include "usbc_ppc.h"
+
+void ppc_interrupt(enum gpio_signal signal)
+{
+ switch (signal) {
+ case GPIO_USB_C0_PPC_INT_ODL:
+ nx20p348x_interrupt(0);
+ break;
+
+ case GPIO_USB_C1_PPC_INT_ODL:
+ nx20p348x_interrupt(1);
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/zephyr/projects/skyrim/src/winterhold/usb_mux_config.c b/zephyr/projects/skyrim/src/winterhold/usb_mux_config.c
new file mode 100644
index 0000000000..ca7b604d10
--- /dev/null
+++ b/zephyr/projects/skyrim/src/winterhold/usb_mux_config.c
@@ -0,0 +1,142 @@
+/* Copyright 2022 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Winterhold board-specific USB-C mux configuration */
+
+#include <zephyr/drivers/gpio.h>
+
+#include "console.h"
+#include "cros_board_info.h"
+#include "cros_cbi.h"
+#include "driver/retimer/anx7483_public.h"
+#include "hooks.h"
+#include "ioexpander.h"
+#include "usb_mux.h"
+#include "usbc/usb_muxes.h"
+
+#define CPRINTSUSB(format, args...) cprints(CC_USBCHARGE, format, ##args)
+#define CPRINTFUSB(format, args...) cprintf(CC_USBCHARGE, format, ##args)
+
+/*
+ * USB C0 (general) and C1 (just ANX DB) use IOEX pins to
+ * indicate flipped polarity to a protection switch.
+ */
+static int ioex_set_flip(int port, mux_state_t mux_state)
+{
+ if (port == 0) {
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c0_sbu_flip),
+ 1);
+ else
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c0_sbu_flip),
+ 0);
+ } else {
+ if (mux_state & USB_PD_MUX_POLARITY_INVERTED)
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c1_sbu_flip),
+ 1);
+ else
+ gpio_pin_set_dt(
+ GPIO_DT_FROM_NODELABEL(ioex_usb_c1_sbu_flip),
+ 0);
+ }
+
+ return EC_SUCCESS;
+}
+
+int board_anx7483_c0_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ /* Set the SBU polarity mux */
+ RETURN_ERROR(ioex_set_flip(me->usb_port, mux_state));
+
+ return anx7483_set_default_tuning(me, mux_state);
+}
+
+int board_anx7483_c1_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ bool flipped = mux_state & USB_PD_MUX_POLARITY_INVERTED;
+
+ /* Set the SBU polarity mux */
+ RETURN_ERROR(ioex_set_flip(me->usb_port, mux_state));
+
+ /* Remove flipped from the state for easier compraisons */
+ mux_state = mux_state & ~USB_PD_MUX_POLARITY_INVERTED;
+
+ RETURN_ERROR(anx7483_set_default_tuning(me, mux_state));
+
+ if (mux_state == USB_PD_MUX_USB_ENABLED) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DP_ENABLED) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DOCK && !flipped) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ } else if (mux_state == USB_PD_MUX_DOCK && flipped) {
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_URX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_UTX1,
+ ANX7483_EQ_SETTING_12_5DB));
+ RETURN_ERROR(anx7483_set_eq(me, ANX7483_PIN_DRX2,
+ ANX7483_EQ_SETTING_12_5DB));
+ }
+
+ return EC_SUCCESS;
+}
+
+int board_c1_ps8818_mux_set(const struct usb_mux *me, mux_state_t mux_state)
+{
+ CPRINTSUSB("C1: PS8818 mux using default tuning");
+
+ /* Once a DP connection is established, we need to set IN_HPD */
+ if (mux_state & USB_PD_MUX_DP_ENABLED)
+ ioex_set_level(IOEX_USB_C1_HPD_IN_DB, 1);
+ else
+ ioex_set_level(IOEX_USB_C1_HPD_IN_DB, 0);
+
+ return 0;
+}
+
+static void setup_mux(void)
+{
+ uint32_t val;
+
+ if (cros_cbi_get_fw_config(FW_IO_DB, &val) != 0)
+ CPRINTSUSB("Error finding FW_DB_IO in CBI FW_CONFIG");
+ /* Val will have our dts default on error, so continue setup */
+
+ if (val == FW_IO_DB_PS8811_PS8818) {
+ CPRINTSUSB("C1: Setting PS8818 mux");
+ USB_MUX_ENABLE_ALTERNATIVE(usb_mux_chain_ps8818_port1);
+ } else if (val == FW_IO_DB_NONE_ANX7483) {
+ CPRINTSUSB("C1: Setting ANX7483 mux");
+ } else {
+ CPRINTSUSB("Unexpected DB_IO board: %d", val);
+ }
+}
+DECLARE_HOOK(HOOK_INIT, setup_mux, HOOK_PRIO_INIT_I2C);