Twinkie_v2: Add Twinkie_v2 project to Gerrit

Adds the Twinkie v2 project from the external Github repo
https://github.com/ualbertagreen/Twinkie_V2 to the hdctools_firmware
project. Minimal cleanup was done to remove external artifacts
unrelated to the project and to fit the project structure with the
existing repo.

CommitID: 37fe0b4: Jason Yuan: Fixing tests
CommitID: fec6594: Jason Yuan: Twinkie V2 repo

BUG=b:312565460

TEST=Build the image
    `west build -p always twinkie_v2`
TEST=Flash the firmware and verify Twinkie_v2 shows up
    `sudo dfu-util -a 0 -s 0x8000000:leave -D build/zephyr/zephyr.bin`
    or if right after build `west flash`
TEST=Install and run the Spyglass UI, verify analog measurements
    USB PD packets are visible with devices connected.

Change-Id: I6d87a47235109724713567e809d509b00fd0cca2
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/hdctools_firmware/+/5052745
Reviewed-by: Brian Nemec <[email protected]>
Commit-Queue: Brian Nemec <[email protected]>
Reviewed-by: zhi cheng yuan <[email protected]>
Tested-by: zhi cheng yuan <[email protected]>
Tested-by: Brian Nemec <[email protected]>
diff --git a/zephyr_projects/README.md b/zephyr_projects/README.md
index 85f219f..04abd09 100644
--- a/zephyr_projects/README.md
+++ b/zephyr_projects/README.md
@@ -34,3 +34,7 @@
 ### Flash Starfish
 
 `sudo dfu-util -a 0 -s 0x8000000:leave -D build/zephyr/zephyr.bin`
+
+### Build Twinkie
+
+`west build -p always twinkie_v2`
diff --git a/zephyr_projects/twinkie_v2/CMakeLists.txt b/zephyr_projects/twinkie_v2/CMakeLists.txt
new file mode 100644
index 0000000..63a93dd
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Copyright 2023 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set(BOARD google_twinkie_v2)
+
+cmake_minimum_required(VERSION 3.20.5)
+find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
+
+zephyr_include_directories(include)
+
+FILE(GLOB app_sources src/*.c)
+target_sources(app PRIVATE ${app_sources})
+
+project(twinkie_v2)
diff --git a/zephyr_projects/twinkie_v2/Kconfig b/zephyr_projects/twinkie_v2/Kconfig
new file mode 100644
index 0000000..47c4b29
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/Kconfig
@@ -0,0 +1,21 @@
+# Copyright 2023 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config STM32_UCPD
+        bool "USBC TCPC device controller driver"
+        depends on SOC_FAMILY_STM32
+        default y
+	select USE_STM32_LL_BUS
+	select USE_STM32_LL_SYSTEM
+	select USE_STM32_LL_RCC
+	select USE_STM32_LL_DMA
+        select USE_STM32_LL_UCPD
+	select USE_STM32_LL_USB
+	select USE_STM32_HAL_PCD
+	select USE_STM32_HAL_PCD_EX
+        help
+          Enable USBC TCPC support on the STM32 G0, G4, L5, and U5 family of
+          processors.
+
+source "Kconfig.zephyr"
\ No newline at end of file
diff --git a/zephyr_projects/twinkie_v2/app.overlay b/zephyr_projects/twinkie_v2/app.overlay
new file mode 100644
index 0000000..f5422fd
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/app.overlay
@@ -0,0 +1,73 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/ {
+	chosen {
+		zephyr,shell_uart = &cdc_acm_uart0;
+		zephyr,console = &cdc_acm_uart1;
+	};
+
+	cc_config {
+		compatible = "gpio-leds";
+		cc1_en: cc1en {
+			gpios = <&gpiob 2 GPIO_ACTIVE_HIGH>;
+		};
+
+		cc2_en: cc2en {
+			gpios = <&gpiob 13 GPIO_ACTIVE_HIGH>;
+		};
+	};
+
+	aliases {
+		encc1 = &cc1_en;
+		encc2 = &cc2_en;
+	};
+};
+
+zephyr_udc0: &usb {
+	/* Twinkie V2 Command Shell */
+	cdc_acm_uart0: cdc_acm_uart0 {
+		compatible = "zephyr,cdc-acm-uart";
+	};
+
+	/* Twinkie V2 Packet Logging */
+	cdc_acm_uart1: cdc_acm_uart1 {
+		compatible = "zephyr,cdc-acm-uart";
+	};
+
+	pinctrl-0 = <&usb_dm_pa11 &usb_dp_pa12>;
+	pinctrl-names = "default";
+	status = "okay";
+};
+
+&clk_lsi {
+	status = "okay";
+};
+
+&clk_hsi48 {
+	status = "okay";
+};
+
+&adc1 {
+	channel@1 {
+		zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 2)>;
+	};
+
+	channel@3 {
+		zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 2)>;
+	};
+
+	channel@15 {
+		zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 2)>;
+	};
+
+	channel@17 {
+		zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 2)>;
+	};
+
+	channel@18 {
+		zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 2)>;
+	};
+};
diff --git a/zephyr_projects/twinkie_v2/include/controls.h b/zephyr_projects/twinkie_v2/include/controls.h
new file mode 100644
index 0000000..3f1b23c
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/include/controls.h
@@ -0,0 +1,31 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef _CONTROLS_H_
+#define _CONTROLS_H_
+
+#include <stdbool.h>
+
+/**
+ * @brief Initializes the control module, configure both cc enable pins as active
+ *
+ * @return 0 on success
+ */
+int controls_init(void);
+
+/**
+ * @brief Enables the cc1 pin
+ *
+ * @param en true for enable, false for disable
+ */
+void en_cc1(bool en);
+
+/**
+ * @brief Enables the cc2 pin
+ *
+ * @param en true for enable, false for disable
+ */
+void en_cc2(bool en);
+#endif
diff --git a/zephyr_projects/twinkie_v2/include/ll_ucpd_patch.h b/zephyr_projects/twinkie_v2/include/ll_ucpd_patch.h
new file mode 100644
index 0000000..4be37d0
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/include/ll_ucpd_patch.h
@@ -0,0 +1,28 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Patch file for easy removal when the following issue is resolved:
+ * LL Function for checking UCPD RxErr Flag is missing #42
+ * https://github.com/STMicroelectronics/STM32CubeG0/issues/42
+ */
+
+#include "stm32g0xx_ll_ucpd.h"
+
+#ifndef LL_UCPD_PATCH_H
+#define LL_UCPD_PATCH_H
+
+/**
+ * @brief  Check if Rx error interrupt
+ * @rmtoll SR          RXERR         LL_UCPD_IsActiveFlag_RxErr
+ * @param  UCPDx UCPD Instance
+ * @retval None
+ */
+__STATIC_INLINE uint32_t LL_UCPD_IsActiveFlag_RxErr(UCPD_TypeDef const *const UCPDx)
+{
+	return ((READ_BIT(UCPDx->SR, UCPD_SR_RXERR) == UCPD_SR_RXERR) ? 1UL : 0UL);
+}
+
+#endif
diff --git a/zephyr_projects/twinkie_v2/include/mask.h b/zephyr_projects/twinkie_v2/include/mask.h
new file mode 100644
index 0000000..252e537
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/include/mask.h
@@ -0,0 +1,23 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef _MASK_H_
+#define _MASK_H_
+
+/**
+ * @defgroup Bitmask for snooper state
+ * Bit positions for the bitmask for setting the role,
+ * active CC line, and pull resistors of the Twinkie
+ * @{
+ */
+#define snooper_mask_t uint8_t
+
+#define PULL_RESISTOR_BITS (BIT(0) | BIT(1))
+#define SINK_BIT           BIT(2)
+#define CC1_CHANNEL_BIT    BIT(3)
+#define CC2_CHANNEL_BIT    BIT(4)
+/** @} */
+
+#endif
diff --git a/zephyr_projects/twinkie_v2/include/meas.h b/zephyr_projects/twinkie_v2/include/meas.h
new file mode 100644
index 0000000..c6b8ff9
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/include/meas.h
@@ -0,0 +1,62 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef __MEAS_H__
+#define __MEAS_H__
+
+/**
+ * @brief Initializes the measurement module, sets up all the adc channels through the device tree
+ * binding
+ *
+ * @return 0 on success
+ */
+int meas_init(void);
+
+/**
+ * @brief Measure the voltage on VBUS
+ *
+ * @param v pointer where VBUS voltage, in millivolts, is stored
+ *
+ * @return 0 on success
+ */
+int meas_vbus_v(int32_t *v);
+
+/**
+ * @brief Measure the current on VBUS
+ *
+ * @param c pointer where VBUS current, in milliamperes, is stored
+ *
+ * @return 0 on success
+ */
+int meas_vbus_c(int32_t *c);
+
+/**
+ * @brief Measure the voltage on CC1
+ *
+ * @param v pointer where CC1 voltage, in millivolts, is stored
+ *
+ * @return 0 on success
+ */
+int meas_cc1_v(int32_t *v);
+
+/**
+ * @brief Measure the voltage on CC2
+ *
+ * @param v pointer where CC2 voltage, in millivolts, is stored
+ *
+ * @return 0 on success
+ */
+int meas_cc2_v(int32_t *v);
+
+/**
+ * @brief Measure the current on VCON
+ *
+ * @param c pointer where VCON current, in milliamperes, is stored
+ *
+ * @return 0 on success
+ */
+int meas_vcon_c(int32_t *c);
+
+#endif
diff --git a/zephyr_projects/twinkie_v2/include/model.h b/zephyr_projects/twinkie_v2/include/model.h
new file mode 100644
index 0000000..4010f1c
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/include/model.h
@@ -0,0 +1,73 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef _MODEL_H_
+#define _MODEL_H_
+
+#include <zephyr/kernel.h>
+#include "mask.h"
+
+/**
+ * @brief Initializes the snooper model
+ *
+ * @param dev pointer to the Twinkie device
+ *
+ * @return 0 on success
+ */
+int model_init(const struct device *dev);
+
+/**
+ * @brief Resets the snooper model
+ */
+void reset_snooper();
+
+/**
+ * @brief Sets the role as source or sink, and set the pull up resistor and active cc line if source
+ *
+ * @param role_mask a bit mask of the role to be set
+ */
+void set_role(snooper_mask_t role_mask);
+
+/**
+ * @brief Starts or stops the snooper by setting the snoop status
+ *
+ * @param s true for start, false for stop
+ */
+void start_snooper(bool s);
+
+/**
+ * @brief Sets whether the Twinkie will continuously output data when no pd messages are received
+ *
+ * @param e true for continuous output, false for output on pd messages only
+ */
+void set_empty_print(bool e);
+
+/**
+ * @brief Sets how fast the twinkie will output data when no pd messages are received
+ *
+ * @param s true for slow output, false for fast output
+ */
+void set_sleep_time(uint32_t st);
+
+/**
+ * @brief Sets whether twinkie automatically turns off when no receiver is connected.
+ *
+ * @param s true for auto stop, false for continuous output
+ */
+void set_auto_stop(bool s);
+
+/**
+ * @brief Print to shell the current status of the Twinkie
+ *
+ * @param ret char array for output of status
+ */
+void print_status(char *ret, int i);
+
+/**
+ * @brief Used only for sharing isr
+ */
+void ucpd_isr(void);
+
+#endif
diff --git a/zephyr_projects/twinkie_v2/include/view.h b/zephyr_projects/twinkie_v2/include/view.h
new file mode 100644
index 0000000..4d6f559
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/include/view.h
@@ -0,0 +1,47 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef _VIEW_H_
+#define _VIEW_H_
+
+#include "mask.h"
+
+/**
+ * @brief Set the cc line that the snooper is monitoring
+ *
+ * @param vs bitmask that describes which cc lines should be monitored
+ *
+ * @return 0 on success
+ * @return EIO on failure
+ */
+int view_set_snoop(snooper_mask_t vs);
+
+/**
+ * @brief Records the current cc line connection the Twinkie is detecting
+ *
+ * @param vc bitmask that describes the cc line that is currently connected
+ *
+ * @return 0 on success
+ * @return EIO on failure
+ */
+int view_set_connection(snooper_mask_t vc);
+
+/**
+ * @brief Returns the value of the cc line currently being snooped.
+ *
+ * @return bitmask for the current view status
+ */
+snooper_mask_t get_view_snoop();
+
+/**
+ * @brief Initializes the snooper view
+ *
+ * @param dev pointer to the Twinkie device
+ *
+ * @return 0 on success
+ */
+int view_init();
+
+#endif
diff --git a/zephyr_projects/twinkie_v2/prj.conf b/zephyr_projects/twinkie_v2/prj.conf
new file mode 100644
index 0000000..a46b4f6
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/prj.conf
@@ -0,0 +1,42 @@
+# Copyright 2023 The ChromiumOS Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+CONFIG_COMPILER_SAVE_TEMPS=y
+
+CONFIG_SMF=y
+
+CONFIG_USB_DEVICE_STACK=y
+CONFIG_USB_DEVICE_MANUFACTURER="Google LLC"
+CONFIG_USB_DEVICE_PRODUCT="Twinkie V2"
+CONFIG_USB_DEVICE_VID=0x18d1
+CONFIG_USB_DEVICE_PID=0x5213
+
+CONFIG_USB_DEVICE_STACK=y
+CONFIG_USB_COMPOSITE_DEVICE=y
+CONFIG_USB_CDC_ACM_RINGBUF_SIZE=512
+
+CONFIG_STDOUT_CONSOLE=y
+CONFIG_SERIAL=y
+CONFIG_CONSOLE=y
+CONFIG_UART_CONSOLE=y
+CONFIG_UART_INTERRUPT_DRIVEN=y
+
+CONFIG_PRINTK=y
+CONFIG_SHELL=y
+CONFIG_SHELL_STACK_SIZE=1024
+CONFIG_SHELL_BACKEND_SERIAL=y
+
+CONFIG_THREAD_MONITOR=y
+CONFIG_INIT_STACKS=y
+CONFIG_BOOT_BANNER=n
+CONFIG_THREAD_NAME=y
+CONFIG_CBPRINTF_NANO=y
+
+CONFIG_ADC=y
+CONFIG_ADC_STM32=y
+CONFIG_ADC_SHELL=y
+CONFIG_GPIO=y
+
+# A modified version of usb_dc_stm32.c is required.
+CONFIG_USB_DC_STM32=n
diff --git a/zephyr_projects/twinkie_v2/src/controls.c b/zephyr_projects/twinkie_v2/src/controls.c
new file mode 100644
index 0000000..f6bcc35
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/controls.c
@@ -0,0 +1,49 @@
+/* Copyright 2023 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/kernel.h>
+#include <zephyr/drivers/gpio.h>
+
+/* The devicetree node identifier for the cc enable pins. */
+#define ENCC1_NODE DT_ALIAS(encc1)
+#define ENCC2_NODE DT_ALIAS(encc2)
+
+static const struct gpio_dt_spec encc1 = GPIO_DT_SPEC_GET(ENCC1_NODE, gpios);
+static const struct gpio_dt_spec encc2 = GPIO_DT_SPEC_GET(ENCC2_NODE, gpios);
+
+void en_cc1(bool en)
+{
+	gpio_pin_set_dt(&encc1, en);
+}
+
+void en_cc2(bool en)
+{
+	gpio_pin_set_dt(&encc2, en);
+}
+
+int controls_init(void)
+{
+	int ret;
+
+	if (!device_is_ready(encc1.port)) {
+		return -EIO;
+	}
+
+	if (!device_is_ready(encc2.port)) {
+		return -EIO;
+	}
+
+	ret = gpio_pin_configure_dt(&encc1, GPIO_OUTPUT_ACTIVE);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = gpio_pin_configure_dt(&encc2, GPIO_OUTPUT_ACTIVE);
+	if (ret < 0) {
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/zephyr_projects/twinkie_v2/src/main.c b/zephyr_projects/twinkie_v2/src/main.c
new file mode 100644
index 0000000..d777641
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/main.c
@@ -0,0 +1,35 @@
+/* Copyright 2023 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/kernel.h>
+#include <zephyr/smf.h>
+#include <zephyr/drivers/gpio.h>
+
+#include <zephyr/logging/log.h>
+#include <zephyr/usb/usb_device.h>
+#include <zephyr/device.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/shell/shell.h>
+
+#include "controls.h"
+#include "meas.h"
+#include "view.h"
+#include "model.h"
+
+int main(void)
+{
+	const struct device *dev_pd_analyzer = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
+
+	if (usb_enable(NULL)) {
+		return -1;
+	}
+
+	meas_init();
+	controls_init();
+	model_init(dev_pd_analyzer);
+	view_init();
+
+	return 0;
+}
diff --git a/zephyr_projects/twinkie_v2/src/meas.c b/zephyr_projects/twinkie_v2/src/meas.c
new file mode 100644
index 0000000..a966064
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/meas.c
@@ -0,0 +1,213 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdio.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/adc.h>
+#include <zephyr/drivers/adc/voltage_divider.h>
+#include <zephyr/drivers/adc/current_sense_amplifier.h>
+
+/* The devicetree node identifier for the adc aliases. */
+#define CC1_V_MEAS_NODE  DT_ALIAS(vcc1)
+#define CC2_V_MEAS_NODE  DT_ALIAS(vcc2)
+#define VBUS_V_MEAS_NODE DT_ALIAS(vbus)
+#define VBUS_C_MEAS_NODE DT_ALIAS(cbus)
+#define VCON_C_MEAS_NODE DT_ALIAS(ccon)
+
+static const struct voltage_divider_dt_spec adc_cc1_v =
+	VOLTAGE_DIVIDER_DT_SPEC_GET(CC1_V_MEAS_NODE);
+static const struct voltage_divider_dt_spec adc_cc2_v =
+	VOLTAGE_DIVIDER_DT_SPEC_GET(CC2_V_MEAS_NODE);
+static const struct voltage_divider_dt_spec adc_vbus_v =
+	VOLTAGE_DIVIDER_DT_SPEC_GET(VBUS_V_MEAS_NODE);
+static const struct current_sense_amplifier_dt_spec adc_vbus_c =
+	CURRENT_SENSE_AMPLIFIER_DT_SPEC_GET(VBUS_C_MEAS_NODE);
+static const struct current_sense_amplifier_dt_spec adc_vcon_c =
+	CURRENT_SENSE_AMPLIFIER_DT_SPEC_GET(VCON_C_MEAS_NODE);
+
+int meas_vbus_v(int32_t *v)
+{
+	int ret;
+	int32_t sample_buffer = 0;
+
+	/* Structure defining an ADC sampling sequence */
+	struct adc_sequence sequence = {
+		.buffer = &sample_buffer,
+		/* buffer size in bytes, not number of samples */
+		.buffer_size = sizeof(sample_buffer),
+		.calibrate = true,
+	};
+	adc_sequence_init_dt(&adc_vbus_v.port, &sequence);
+
+	ret = adc_read(adc_vbus_v.port.dev, &sequence);
+	if (ret != 0) {
+		return ret;
+	}
+
+	*v = sample_buffer;
+	ret = adc_raw_to_millivolts_dt(&adc_vbus_v.port, v);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = voltage_divider_scale_dt(&adc_vbus_v, v);
+	if (ret != 0) {
+		return ret;
+	}
+
+	*v *= 1.22;
+
+	return 0;
+}
+
+int meas_vbus_c(int32_t *c)
+{
+	int ret;
+	int32_t sample_buffer = 0;
+
+	/* Structure defining an ADC sampling sequence */
+	struct adc_sequence sequence = {
+		.buffer = &sample_buffer,
+		/* buffer size in bytes, not number of samples */
+		.buffer_size = sizeof(sample_buffer),
+		.calibrate = true,
+	};
+	adc_sequence_init_dt(&adc_vbus_c.port, &sequence);
+
+	ret = adc_read(adc_vbus_c.port.dev, &sequence);
+	if (ret != 0) {
+		return ret;
+	}
+
+	*c = sample_buffer;
+	ret = adc_raw_to_millivolts_dt(&adc_vbus_c.port, c);
+	if (ret != 0) {
+		return ret;
+	}
+
+	/* prescaling the voltage offset */
+	*c -= adc_vbus_c.port.vref_mv / 2;
+	current_sense_amplifier_scale_dt(&adc_vbus_c, c);
+
+	return 0;
+}
+
+int meas_cc1_v(int32_t *v)
+{
+	int ret;
+	int32_t sample_buffer = 0;
+
+	/* Structure defining an ADC sampling sequence */
+	struct adc_sequence sequence = {
+		.buffer = &sample_buffer,
+		/* buffer size in bytes, not number of samples */
+		.buffer_size = sizeof(sample_buffer),
+		.calibrate = true,
+	};
+	adc_sequence_init_dt(&adc_cc1_v.port, &sequence);
+
+	ret = adc_read(adc_cc1_v.port.dev, &sequence);
+	if (ret != 0) {
+		return ret;
+	}
+
+	*v = sample_buffer;
+	ret = adc_raw_to_millivolts_dt(&adc_cc1_v.port, v);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+int meas_cc2_v(int32_t *v)
+{
+	int ret;
+	int32_t sample_buffer = 0;
+
+	/* Structure defining an ADC sampling sequence */
+	struct adc_sequence sequence = {
+		.buffer = &sample_buffer,
+		/* buffer size in bytes, not number of samples */
+		.buffer_size = sizeof(sample_buffer),
+		.calibrate = true,
+	};
+	adc_sequence_init_dt(&adc_cc2_v.port, &sequence);
+
+	ret = adc_read(adc_cc2_v.port.dev, &sequence);
+	if (ret != 0) {
+		return ret;
+	}
+
+	*v = sample_buffer;
+	ret = adc_raw_to_millivolts_dt(&adc_cc2_v.port, v);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
+
+int meas_vcon_c(int32_t *c)
+{
+	int ret;
+	int32_t sample_buffer = 0;
+
+	/* Structure defining an ADC sampling sequence */
+	struct adc_sequence sequence = {
+		.buffer = &sample_buffer,
+		/* buffer size in bytes, not number of samples */
+		.buffer_size = sizeof(sample_buffer),
+		.calibrate = true,
+	};
+	adc_sequence_init_dt(&adc_vcon_c.port, &sequence);
+
+	ret = adc_read(adc_vcon_c.port.dev, &sequence);
+	if (ret != 0) {
+		return ret;
+	}
+
+	*c = sample_buffer;
+	ret = adc_raw_to_millivolts_dt(&adc_vcon_c.port, c);
+	if (ret != 0) {
+		return ret;
+	}
+
+	current_sense_amplifier_scale_dt(&adc_vcon_c, c);
+
+	return 0;
+}
+
+int meas_init(void)
+{
+	int ret;
+
+	ret = adc_channel_setup_dt(&adc_cc1_v.port);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = adc_channel_setup_dt(&adc_cc2_v.port);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = adc_channel_setup_dt(&adc_vbus_v.port);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = adc_channel_setup_dt(&adc_vbus_c.port);
+	if (ret != 0) {
+		return ret;
+	}
+
+	ret = adc_channel_setup_dt(&adc_vcon_c.port);
+	if (ret != 0) {
+		return ret;
+	}
+
+	return 0;
+}
diff --git a/zephyr_projects/twinkie_v2/src/model.c b/zephyr_projects/twinkie_v2/src/model.c
new file mode 100644
index 0000000..d5687b9
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/model.c
@@ -0,0 +1,481 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/sys/ring_buffer.h>
+
+#include <stm32g0xx_ll_bus.h>
+#include <stm32g0xx_ll_system.h>
+#include <stm32g0xx_ll_dma.h>
+#include <stm32g0xx_ll_rcc.h>
+#include <stm32g0xx_ll_gpio.h>
+#include <stm32g0xx_ll_ucpd.h>
+
+#include "meas.h"
+#include "controls.h"
+#include "view.h"
+#include "model.h"
+
+#include "ll_ucpd_patch.h"
+
+/* STM32 interrupt registers */
+#define UCPD_IRQ           8
+#define DMA1_CHANNEL1_IRQ  9
+#define DMA1_CHANNEL1_PRIO 2
+#define UCPD_PRIO          2
+
+/* snooper model thread parameters */
+#define MODEL_THREAD_STACK_SIZE 500
+#define MODEL_THREAD_PRIORITY   5
+
+/* STM32 UCPD parameters */
+static LL_UCPD_InitTypeDef ucpd_params;
+
+/* Byte size of various portions of the packet */
+#define MOD_BUFFERS          40
+#define PACKET_HEADER_LEN    20
+#define PD_SAMPLES           492
+#define PACKET_BYTE_SIZE     (PACKET_HEADER_LEN + PD_SAMPLES)
+#define MAX_PACKET_XFER_SIZE 64
+
+/* container for information of the type of packet to be sent */
+struct packet_type_t {
+	uint16_t unused1: 4;
+	uint16_t polarity: 2;
+	uint16_t lost: 1;
+	uint16_t partial: 1;
+	uint16_t version: 4;
+	uint16_t type: 4;
+};
+
+/* container for header of the packet */
+struct header_t {
+	uint32_t sequence;
+	uint16_t cc1_voltage;
+	uint16_t cc2_voltage;
+	uint16_t vcon_current;
+	uint16_t vbus_voltage;
+	uint16_t vbus_current;
+	struct packet_type_t packet_type;
+	uint16_t data_len;
+	uint16_t unused;
+};
+
+/* container for the entire packet */
+struct packet_t {
+	struct header_t header;
+	uint8_t data[PD_SAMPLES];
+};
+
+/* storage for all the information of the current state of the snooper */
+static struct model_t {
+	const struct device *dev;
+	struct packet_t packet;
+	uint8_t dma_buffer[PD_SAMPLES];
+	k_tid_t tid;
+	bool start;
+	bool empty_print;
+	bool slow_print;
+	bool auto_stop;
+
+	uint8_t mod_buff[MOD_BUFFERS][PD_SAMPLES];
+	uint16_t mod_size[MOD_BUFFERS];
+	struct packet_type_t sop[MOD_BUFFERS];
+	uint8_t mw;
+	uint8_t mr;
+	uint32_t sleep_time;
+} model;
+
+K_THREAD_STACK_DEFINE(model_stack_area, MODEL_THREAD_STACK_SIZE);
+static struct k_thread model_thread_data;
+static int pd_line;
+
+void start_snooper(bool s)
+{
+	model.start = s;
+	if (s) {
+		model.packet.header.sequence = 0;
+		view_set_snoop(CC1_CHANNEL_BIT | CC2_CHANNEL_BIT);
+	} else {
+		view_set_snoop(0);
+	}
+}
+
+void reset_snooper()
+{
+	memset(&model.mod_buff, 0, MOD_BUFFERS * PD_SAMPLES);
+	memset(&model.mod_size, 0, MOD_BUFFERS * sizeof(uint16_t));
+	memset(&model.sop, 0, MOD_BUFFERS * sizeof(uint16_t));
+	model.mr = 0;
+	model.mw = 0;
+	model.packet.header.sequence = 0;
+}
+
+void set_role(snooper_mask_t role_mask)
+{
+	if (role_mask & SINK_BIT) {
+		LL_UCPD_SetccEnable(UCPD1, LL_UCPD_CCENABLE_CC1CC2);
+		LL_UCPD_SetSNKRole(UCPD1);
+		return;
+	}
+
+	if (role_mask & CC1_CHANNEL_BIT) {
+		LL_UCPD_SetccEnable(UCPD1, LL_UCPD_CCENABLE_CC1);
+	} else if (role_mask & CC2_CHANNEL_BIT) {
+		LL_UCPD_SetccEnable(UCPD1, LL_UCPD_CCENABLE_CC2);
+	}
+	LL_UCPD_SetSRCRole(UCPD1);
+
+	uint8_t Rp = role_mask & PULL_RESISTOR_BITS;
+
+	switch (Rp) {
+	case 0:
+		LL_UCPD_SetRpResistor(UCPD1, LL_UCPD_RESISTOR_NONE);
+		break;
+	case 1:
+		LL_UCPD_SetRpResistor(UCPD1, LL_UCPD_RESISTOR_DEFAULT);
+		break;
+	case 2:
+		LL_UCPD_SetRpResistor(UCPD1, LL_UCPD_RESISTOR_1_5A);
+		break;
+	case 3:
+		LL_UCPD_SetRpResistor(UCPD1, LL_UCPD_RESISTOR_3_0A);
+	}
+}
+
+void set_empty_print(bool e)
+{
+	model.empty_print = e;
+}
+
+void set_sleep_time(uint32_t st)
+{
+	model.sleep_time = st;
+}
+
+void set_auto_stop(bool s)
+{
+	model.auto_stop = s;
+}
+
+void print_status(char *ret, int i)
+{
+	switch (i) {
+	case 0:
+		sprintf(ret, "Twinkie status: %s", model.start ? "on" : "off");
+		break;
+	case 1:
+		sprintf(ret, "Current packet number: %i", model.packet.header.sequence);
+		break;
+	}
+}
+
+#define CC_VOLTAGE_LOW  500
+#define CC_VOLTAGE_HIGH 2000
+
+static void model_thread(void *arg1, void *arg2, void *arg3)
+{
+	int32_t vbus_v, vbus_c;
+	int32_t vcon_c;
+	int32_t cc1_v, cc2_v;
+	struct model_t *sm = (struct model_t *)arg1;
+
+	while (1) {
+		if (sm->start) {
+			sm->packet.header.sequence++;
+
+			if (sm->packet.header.sequence % 10 == 0) {
+				meas_vbus_v(&vbus_v);
+				meas_vbus_c(&vbus_c);
+				meas_cc1_v(&cc1_v);
+				meas_cc2_v(&cc2_v);
+				meas_vcon_c(&vcon_c);
+
+				/* because the twinkie itself is a port, detecting a
+				 * valid connection through the UCPD line will cause
+				 * false positives. e.g. if the line being snooped is a
+				 * source to source connection and the twinkie is set as
+				 * a sink, the twinkie UCPD will incorrectly detect a
+				 * valid connection. So the connection has to be
+				 * detected using the ADC pins.
+				 */
+				if ((cc1_v < CC_VOLTAGE_HIGH) && (cc1_v > CC_VOLTAGE_LOW)) {
+					/*connect to non-active line if the active line is not set
+					 * to view*/
+					if (get_view_snoop() & CC1_CHANNEL_BIT) {
+						LL_UCPD_SetCCPin(UCPD1, LL_UCPD_CCPIN_CC1);
+					} else {
+						LL_UCPD_SetCCPin(UCPD1, LL_UCPD_CCPIN_CC2);
+					}
+					view_set_connection(CC1_CHANNEL_BIT);
+					pd_line = 1;
+				} else if ((cc2_v < CC_VOLTAGE_HIGH) && (cc2_v > CC_VOLTAGE_LOW)) {
+					/*connect to non-active line if the active line is not set
+					 * to view*/
+					if (get_view_snoop() & CC2_CHANNEL_BIT) {
+						LL_UCPD_SetCCPin(UCPD1, LL_UCPD_CCPIN_CC2);
+					} else {
+						LL_UCPD_SetCCPin(UCPD1, LL_UCPD_CCPIN_CC1);
+					}
+					view_set_connection(CC2_CHANNEL_BIT);
+					pd_line = 2;
+				} else {
+					view_set_connection(0);
+				}
+			}
+			if (sm->packet.header.sequence % 10 < 8) {
+				sm->packet.header.vbus_voltage = vbus_v;
+				sm->packet.header.vbus_current = vbus_c;
+				sm->packet.header.cc1_voltage = cc1_v;
+				sm->packet.header.cc2_voltage = cc2_v;
+				sm->packet.header.vcon_current = vcon_c;
+
+				/* put pd message in the packet if any are stored */
+				if (sm->mw != sm->mr) {
+					sm->packet.header.packet_type = sm->sop[sm->mr];
+					sm->packet.header.data_len = sm->mod_size[sm->mr];
+					memcpy(sm->packet.data, sm->mod_buff[sm->mr],
+					       sm->mod_size[sm->mr]);
+					memset((uint8_t *)&sm->sop[sm->mr], 0, sizeof(uint16_t));
+					sm->mod_size[sm->mr] = 0;
+					memset(sm->mod_buff[sm->mr], 0, PD_SAMPLES);
+					sm->mr++;
+					if (sm->mr == MOD_BUFFERS) {
+						sm->mr = 0;
+					}
+				}
+
+				if (sm->empty_print || sm->packet.header.data_len != 0) {
+					int stop_timer = 0;
+					for (int i = 0, w = 0; i < PACKET_BYTE_SIZE; i += w) {
+						w = uart_fifo_fill(sm->dev,
+								   (const uint8_t *)&sm->packet + i,
+								   PACKET_BYTE_SIZE - i);
+						if (w <= 0) {
+							stop_timer++;
+							k_usleep(sm->sleep_time);
+							if (stop_timer > 100 && sm->auto_stop) {
+								start_snooper(false);
+								break;
+							}
+						}
+					}
+				}
+				memset(sm->packet.data, 0, PD_SAMPLES);
+				sm->packet.header.data_len = 0;
+			}
+		}
+		if (sm->mw == sm->mr) {
+			k_usleep(sm->sleep_time);
+		}
+	}
+}
+
+#define MY_DEV_IRQ   12
+#define MY_DEV_PRIO  2
+#define MY_ISR_ARG   NULL
+#define MY_IRQ_FLAGS 0
+
+void ucpd_isr(void)
+{
+	struct model_t *sm = &model;
+
+	/* TypeCEvent flag currently not used */
+	if (LL_UCPD_IsActiveFlag_TypeCEventCC1(UCPD1) ||
+	    LL_UCPD_IsActiveFlag_TypeCEventCC2(UCPD1)) {
+		LL_UCPD_ClearFlag_TypeCEventCC1(UCPD1);
+		LL_UCPD_ClearFlag_TypeCEventCC2(UCPD1);
+	}
+
+	if (LL_UCPD_IsActiveFlag_RxErr(UCPD1)) {
+		LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
+		LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, PD_SAMPLES);
+		memcpy(sm->mod_buff[sm->mw], sm->dma_buffer, PD_SAMPLES);
+		memset(sm->dma_buffer, 0, PD_SAMPLES);
+		sm->mod_size[sm->mw] = LL_UCPD_ReadRxPaySize(UCPD1);
+		sm->sop[sm->mr].type = LL_UCPD_ReadRxOrderSet(UCPD1);
+		sm->sop[sm->mw].polarity = pd_line;
+		sm->sop[sm->mw].partial = true;
+		sm->mw++;
+		if (sm->mw == MOD_BUFFERS) {
+			sm->mw = 0;
+		}
+		LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
+		k_wakeup(sm->tid);
+	}
+
+	if (LL_UCPD_IsActiveFlag_RxMsgEnd(UCPD1)) {
+		LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
+		LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, PD_SAMPLES);
+		memcpy(sm->mod_buff[sm->mw], sm->dma_buffer, PD_SAMPLES);
+		memset(sm->dma_buffer, 0, PD_SAMPLES);
+		sm->mod_size[sm->mw] = LL_UCPD_ReadRxPaySize(UCPD1);
+		sm->sop[sm->mr].type = LL_UCPD_ReadRxOrderSet(UCPD1);
+		sm->sop[sm->mw].polarity = pd_line;
+		sm->sop[sm->mw].partial = false;
+		sm->mw++;
+		if (sm->mw == MOD_BUFFERS) {
+			sm->mw = 0;
+		}
+		LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
+		k_wakeup(sm->tid);
+		LL_UCPD_ClearFlag_RxMsgEnd(UCPD1);
+	}
+}
+
+static void update_stm32g0x_cc_line(void)
+{
+	SYSCFG->CFGR1 |= SYSCFG_CFGR1_UCPD1_STROBE_Msk;
+}
+
+enum pd_cc_t {
+	PD_OFF,
+	PD_BOTH,
+	PD_CC1,
+	PD_CC2
+};
+
+static void pd_on_cc(enum pd_cc_t p)
+{
+	switch (p) {
+	case PD_OFF:
+		LL_UCPD_TypeCDetectionCC1Disable(UCPD1);
+		LL_UCPD_TypeCDetectionCC2Disable(UCPD1);
+		break;
+	case PD_BOTH:
+		LL_UCPD_TypeCDetectionCC1Enable(UCPD1);
+		LL_UCPD_TypeCDetectionCC2Enable(UCPD1);
+		break;
+	case PD_CC1:
+		LL_UCPD_TypeCDetectionCC2Disable(UCPD1);
+		LL_UCPD_TypeCDetectionCC1Enable(UCPD1);
+		break;
+	case PD_CC2:
+		LL_UCPD_TypeCDetectionCC1Disable(UCPD1);
+		LL_UCPD_TypeCDetectionCC2Enable(UCPD1);
+		break;
+	}
+}
+
+int model_init(const struct device *dev)
+{
+	int ret;
+
+	model.dev = dev;
+	model.mw = 0;
+	model.mr = 0;
+
+	update_stm32g0x_cc_line();
+
+	/** Configure CC pins */
+
+	/* Set PIN A8 as analog */
+	LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_8, LL_GPIO_MODE_ANALOG);
+
+	/* Set PIN B15 as analog */
+	LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_15, LL_GPIO_MODE_ANALOG);
+
+	/** Configure DMA */
+
+	/* DMA CLOCK */
+	LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
+
+	LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
+
+	LL_DMA_ConfigTransfer(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
+
+	/* DMA from UCPD RXDR register */
+	LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, (uint32_t)&UCPD1->RXDR,
+			       (uint32_t)model.dma_buffer, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
+
+	LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);
+
+	LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
+
+	LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
+
+	LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_BYTE);
+
+	LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_BYTE);
+
+	LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_VERYHIGH);
+
+	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, PD_SAMPLES);
+
+	LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_UCPD1_RX);
+
+	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
+
+	model.packet.header.sequence = 0;
+
+	/** Configure UCPD */
+
+	ucpd_params.psc_ucpdclk = 0;
+	ucpd_params.transwin = 7;
+	ucpd_params.IfrGap = 16;
+	ucpd_params.HbitClockDiv = 26;
+
+	/*
+	 * The UCPD port is disabled in the LL_UCPD_Init function
+	 *
+	 * NOTE: For proper Power Management operation, this function
+	 *       should not be used because it circumvents the zephyr
+	 *       clock API. Instead, DTS clock settings and the zephyr
+	 *       clock API should be used to enable clocks.
+	 */
+	ret = LL_UCPD_Init(UCPD1, &ucpd_params);
+	if (ret == SUCCESS) {
+		/* ORDSET */
+		LL_UCPD_SetRxOrderSet(UCPD1, LL_UCPD_ORDERSET_SOP | LL_UCPD_ORDERSET_SOP1 |
+						     LL_UCPD_ORDERSET_SOP2 |
+						     LL_UCPD_ORDERSET_HARDRST |
+						     LL_UCPD_ORDERSET_CABLERST);
+
+		/* ENABLE DMA */
+		LL_UCPD_RxDMAEnable(UCPD1);
+
+		/* Enable UCPD port */
+		LL_UCPD_Enable(UCPD1);
+		start_snooper(false);
+
+		pd_on_cc(PD_BOTH);
+		update_stm32g0x_cc_line();
+
+		LL_UCPD_EnableIT_RxNE(UCPD1);
+
+		LL_UCPD_EnableIT_TypeCEventCC1(UCPD1);
+		LL_UCPD_EnableIT_TypeCEventCC2(UCPD1);
+		LL_UCPD_ClearFlag_TypeCEventCC1(UCPD1);
+		LL_UCPD_ClearFlag_TypeCEventCC2(UCPD1);
+
+		LL_UCPD_SetccEnable(UCPD1, LL_UCPD_CCENABLE_CC1CC2);
+
+		LL_UCPD_EnableIT_RxMsgEnd(UCPD1);
+		LL_UCPD_ClearFlag_RxMsgEnd(UCPD1);
+
+		LL_UCPD_RxEnable(UCPD1);
+	} else {
+		return -EIO;
+	}
+
+	LL_UCPD_SetSNKRole(UCPD1);
+
+	en_cc1(true);
+	en_cc2(true);
+
+	set_auto_stop(true);
+	set_empty_print(true);
+	set_sleep_time(500);
+
+	model.tid = k_thread_create(&model_thread_data, model_stack_area,
+				    K_THREAD_STACK_SIZEOF(model_stack_area), model_thread, &model,
+				    NULL, NULL, MODEL_THREAD_PRIORITY, 0, K_NO_WAIT);
+
+	return 0;
+}
diff --git a/zephyr_projects/twinkie_v2/src/shell.c b/zephyr_projects/twinkie_v2/src/shell.c
new file mode 100644
index 0000000..57cdd8b
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/shell.c
@@ -0,0 +1,237 @@
+/* Copyright 2023 The ChromiumOS Authors
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdlib.h>
+#include <zephyr/kernel.h>
+#include <zephyr/usb/usb_device.h>
+#include <zephyr/device.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/shell/shell.h>
+#include <zephyr/drivers/usb/usb_dc.h>
+
+#include "view.h"
+#include "meas.h"
+#include "model.h"
+#include "mask.h"
+
+static int cmd_meas_cc2(const struct shell *shell, size_t argc, char **argv)
+{
+	int32_t out;
+
+	if (argc == 2 && *argv[1] == 'c') {
+		meas_vcon_c(&out);
+		shell_print(shell, "current of cc2: %i", out);
+	} else {
+		meas_cc2_v(&out);
+		shell_print(shell, "voltage of cc2: %i", out);
+	}
+
+	return 0;
+}
+
+static int cmd_meas_vb(const struct shell *shell, size_t argc, char **argv)
+{
+	int32_t out;
+
+	if (argc == 2 && *argv[1] == 'c') {
+		meas_vbus_c(&out);
+		shell_print(shell, "current of vbus: %i", out);
+	} else {
+		meas_vbus_v(&out);
+		shell_print(shell, "voltage of vbus: %i", out);
+	}
+
+	return 0;
+}
+
+static int cmd_meas_cc1(const struct shell *shell, size_t argc, char **argv)
+{
+	int32_t out;
+
+	meas_cc1_v(&out);
+	shell_print(shell, "voltage of cc1: %i", out);
+
+	return 0;
+}
+
+SHELL_STATIC_SUBCMD_SET_CREATE(sub_meas, SHELL_CMD(cc1, NULL, "Print cc1 voltage.", cmd_meas_cc1),
+			       SHELL_CMD(cc2, NULL, "Print cc2 voltage or current.", cmd_meas_cc2),
+			       SHELL_CMD(vb, NULL, "Print vbus voltage or current.", cmd_meas_vb),
+			       SHELL_SUBCMD_SET_END);
+
+SHELL_CMD_REGISTER(meas, &sub_meas, "Reads current or voltage of the selected line", NULL);
+
+static int cmd_version(const struct shell *shell, size_t argc, char **argv)
+{
+	ARG_UNUSED(argc);
+	ARG_UNUSED(argv);
+
+	shell_print(shell, "Twinkie version 2.2.1");
+
+	return 0;
+}
+
+SHELL_CMD_REGISTER(version, NULL, "Show Twinkie version", cmd_version);
+
+static int cmd_reset(const struct shell *shell, size_t argc, char **argv)
+{
+	start_snooper(false);
+	reset_snooper();
+	usb_dc_reset();
+
+	start_snooper(true);
+	return 0;
+}
+
+SHELL_CMD_REGISTER(reset, NULL, "Resets the Twinkie device", cmd_reset);
+
+static int cmd_snoop(const struct shell *shell, size_t argc, char **argv)
+{
+	if (argc >= 2) {
+		switch (*argv[1]) {
+		case '0':
+			view_set_snoop(0);
+			break;
+		case '1':
+			view_set_snoop(CC1_CHANNEL_BIT);
+			break;
+		case '2':
+			view_set_snoop(CC2_CHANNEL_BIT);
+			break;
+		case '3':
+			view_set_snoop(CC1_CHANNEL_BIT | CC2_CHANNEL_BIT);
+		}
+	}
+
+	return 0;
+}
+
+SHELL_CMD_REGISTER(snoop, NULL, "Sets the snoop CC line, 0 for neither, 3 for both", cmd_snoop);
+
+static int cmd_start(const struct shell *shell, size_t argc, char **argv)
+{
+	start_snooper(true);
+	return 0;
+}
+SHELL_CMD_REGISTER(start, NULL, "Start snooper", cmd_start);
+
+static int cmd_stop(const struct shell *shell, size_t argc, char **argv)
+{
+	usb_dc_reset();
+	start_snooper(false);
+	return 0;
+}
+SHELL_CMD_REGISTER(stop, NULL, "Stop snooper", cmd_stop);
+
+static int cmd_role(const struct shell *shell, size_t argc, char **argv, void *data)
+{
+	snooper_mask_t pull_mask = (uint32_t)data;
+
+	set_role(pull_mask);
+
+	return 0;
+}
+
+SHELL_SUBCMD_DICT_SET_CREATE(role_options, cmd_role, (sink, SINK_BIT, "sink"),
+			     (source, 0, "source"));
+
+SHELL_CMD_REGISTER(role, &role_options, "Sets role as sink or source", cmd_role);
+
+static int cmd_cc1_pull(const struct shell *shell, size_t argc, char **argv, void *data)
+{
+	snooper_mask_t pull_mask = (uint32_t)data;
+
+	set_role(pull_mask & CC1_CHANNEL_BIT);
+
+	return 0;
+}
+
+static int cmd_cc2_pull(const struct shell *shell, size_t argc, char **argv, void *data)
+{
+	snooper_mask_t pull_mask = (uint32_t)data;
+
+	set_role(pull_mask & CC2_CHANNEL_BIT);
+
+	return 0;
+}
+
+SHELL_SUBCMD_DICT_SET_CREATE(cc1_options, cmd_cc1_pull, (rd, 0, "resistor disconnected"),
+			     (ru, 1, "default resistor"), (r1, 2, "1.5A resistor"),
+			     (r3, 3, "3A resistor"));
+
+SHELL_SUBCMD_DICT_SET_CREATE(cc2_options, cmd_cc2_pull, (rd, 0, "resistor disconnected"),
+			     (ru, 1, "default resistor"), (r1, 2, "1.5A resistor"),
+			     (r3, 3, "3A resistor"));
+
+SHELL_STATIC_SUBCMD_SET_CREATE(
+	sub_rpull, SHELL_CMD(cc1, &cc1_options, "Sets pull resistor on cc1", cmd_cc1_pull),
+	SHELL_CMD(cc2, &cc2_options, "Sets pull resistor on cc2", cmd_cc2_pull),
+	SHELL_SUBCMD_SET_END);
+
+SHELL_CMD_REGISTER(rpull, &sub_rpull, "Place pull resistor", NULL);
+
+static int cmd_output(const struct shell *shell, size_t argc, char **argv, void *data)
+{
+	bool p = (int)data;
+
+	set_empty_print(p);
+
+	return 0;
+}
+
+SHELL_SUBCMD_DICT_SET_CREATE(output_options, cmd_output, (cont, true, "continuous output"),
+			     (pd_only, false, "output only on receiving pd messages"));
+
+SHELL_CMD_REGISTER(output, &output_options,
+		   "Sets console output as continuous or only on receiving PD messages",
+		   cmd_output);
+
+static int cmd_auto_stop(const struct shell *shell, size_t argc, char **argv, void *data)
+{
+	bool p = (bool)data;
+
+	set_auto_stop(p);
+
+	return 0;
+}
+
+SHELL_SUBCMD_DICT_SET_CREATE(cmd_auto_stop_options, cmd_auto_stop,
+			     (on, true, "twinkie turns off when no reciever is connected"),
+			     (off, false,
+			      "twinkie continues sending even when there is no response "
+			      "from host (WARNING: may cause data corruption or desync)"));
+
+SHELL_CMD_REGISTER(auto_stop, &cmd_auto_stop_options,
+		   "Sets to automatically turn off when no valid receiver is connected",
+		   cmd_auto_stop);
+
+static int cmd_sleep_time(const struct shell *shell, size_t argc, char **argv, void *data)
+{
+	if (argc == 2) {
+		set_sleep_time(atoi(argv[1]));
+		shell_print(shell, "%d", atoi(argv[1]));
+	}
+
+	return 0;
+}
+
+SHELL_CMD_REGISTER(sleep_time, NULL,
+		   "Sets console output as continuous or only on receiving PD messages",
+		   cmd_sleep_time);
+
+static int cmd_status(const struct shell *shell, size_t argc, char **argv)
+{
+
+	char p[50];
+
+	print_status(p, 0);
+	shell_print(shell, "%s", p);
+	print_status(p, 1);
+	shell_print(shell, "%s", p);
+
+	return 0;
+}
+
+SHELL_CMD_REGISTER(status, NULL, "Print the twinkie status", cmd_status);
diff --git a/zephyr_projects/twinkie_v2/src/usb_dc_stm32.c b/zephyr_projects/twinkie_v2/src/usb_dc_stm32.c
new file mode 100644
index 0000000..2e39e41
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/usb_dc_stm32.c
@@ -0,0 +1,1170 @@
+/*
+ * Copyright (c) 2017 Christer Weinigel.
+ * Copyright (c) 2017, I-SENSE group of ICCS
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file
+ * @brief USB device controller shim driver for STM32 devices
+ *
+ * This driver uses the STM32 Cube low level drivers to talk to the USB
+ * device controller on the STM32 family of devices using the
+ * STM32Cube HAL layer.
+ */
+
+/*
+ * This file is derived from `zephyr/drivers/usb/device/usb_dc_stm32.c`
+ * usb_dc_stm32_isr() has been modified. The shared irq has to handle both the
+ * ucpd_isr for packet sniffing and usb_isr for communication with the host.
+ */
+
+#include <soc.h>
+#include <stm32_ll_bus.h>
+#include <stm32_ll_pwr.h>
+#include <stm32_ll_rcc.h>
+#include <stm32_ll_system.h>
+#include <string.h>
+#include <zephyr/usb/usb_device.h>
+#include <zephyr/drivers/clock_control/stm32_clock_control.h>
+#include <zephyr/sys/util.h>
+#include <zephyr/drivers/gpio.h>
+#include <zephyr/drivers/pinctrl.h>
+#include "stm32_hsem.h"
+
+/* Modified line to import ucpd_isr() */
+#include "model.h"
+
+#define LOG_LEVEL CONFIG_USB_DRIVER_LOG_LEVEL
+#include <zephyr/logging/log.h>
+#include <zephyr/irq.h>
+LOG_MODULE_REGISTER(usb_dc_stm32);
+
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) && DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
+#error "Only one interface should be enabled at a time, OTG FS or OTG HS"
+#endif
+
+/*
+ * Vbus sensing is determined based on the presence of the hardware detection
+ * pin(s) in the device tree. E.g: pinctrl-0 = <&usb_otg_fs_vbus_pa9 ...>;
+ *
+ * The detection pins are dependent on the enabled USB driver and the physical
+ * interface(s) offered by the hardware. These are mapped to PA9 and/or PB13
+ * (subject to MCU), being the former the most widespread option.
+ */
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
+#define DT_DRV_COMPAT    st_stm32_otghs
+#define USB_IRQ_NAME     otghs
+#define USB_VBUS_SENSING (DT_NODE_EXISTS(DT_CHILD(DT_NODELABEL(pinctrl), usb_otg_hs_vbus_pa9)) || \
+			  DT_NODE_EXISTS(DT_CHILD(DT_NODELABEL(pinctrl), usb_otg_hs_vbus_pb13)))
+#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs)
+#define DT_DRV_COMPAT    st_stm32_otgfs
+#define USB_IRQ_NAME     otgfs
+#define USB_VBUS_SENSING DT_NODE_EXISTS(DT_CHILD(DT_NODELABEL(pinctrl), usb_otg_fs_vbus_pa9))
+#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb)
+#define DT_DRV_COMPAT    st_stm32_usb
+#define USB_IRQ_NAME     usb
+#define USB_VBUS_SENSING false
+#endif
+
+#define USB_BASE_ADDRESS	DT_INST_REG_ADDR(0)
+#define USB_IRQ			DT_INST_IRQ_BY_NAME(0, USB_IRQ_NAME, irq)
+#define USB_IRQ_PRI		DT_INST_IRQ_BY_NAME(0, USB_IRQ_NAME, priority)
+#define USB_NUM_BIDIR_ENDPOINTS	DT_INST_PROP(0, num_bidir_endpoints)
+#define USB_RAM_SIZE		DT_INST_PROP(0, ram_size)
+
+static const struct stm32_pclken pclken[] = STM32_DT_INST_CLOCKS(0);
+
+#if DT_INST_NODE_HAS_PROP(0, maximum_speed)
+#define USB_MAXIMUM_SPEED	DT_INST_PROP(0, maximum_speed)
+#endif
+
+PINCTRL_DT_INST_DEFINE(0);
+static const struct pinctrl_dev_config *usb_pcfg =
+					PINCTRL_DT_INST_DEV_CONFIG_GET(0);
+
+#define USB_OTG_HS_EMB_PHY (DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usbphyc) && \
+			    DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs))
+
+#define USB_OTG_HS_ULPI_PHY (DT_HAS_COMPAT_STATUS_OKAY(usb_ulpi_phy) && \
+			    DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs))
+
+#if USB_OTG_HS_ULPI_PHY
+static const struct gpio_dt_spec ulpi_reset =
+	GPIO_DT_SPEC_GET_OR(DT_PHANDLE(DT_INST(0, st_stm32_otghs), phys), reset_gpios, {0});
+#endif
+
+/*
+ * USB, USB_OTG_FS and USB_DRD_FS are defined in STM32Cube HAL and allows to
+ * distinguish between two kind of USB DC. STM32 F0, F3, L0 and G4 series
+ * support USB device controller. STM32 F4 and F7 series support USB_OTG_FS
+ * device controller. STM32 F1 and L4 series support either USB or USB_OTG_FS
+ * device controller.STM32 G0 series supports USB_DRD_FS device controller.
+ *
+ * WARNING: Don't mix USB defined in STM32Cube HAL and CONFIG_USB_* from Zephyr
+ * Kconfig system.
+ */
+#if defined(USB) || defined(USB_DRD_FS)
+
+#define EP0_MPS 64U
+#define EP_MPS 64U
+
+/*
+ * USB BTABLE is stored in the PMA. The size of BTABLE is 4 bytes
+ * per endpoint.
+ *
+ */
+#define USB_BTABLE_SIZE  (8 * USB_NUM_BIDIR_ENDPOINTS)
+
+#else /* USB_OTG_FS */
+
+/*
+ * STM32L4 series USB LL API doesn't provide HIGH and HIGH_IN_FULL speed
+ * defines.
+ */
+#if defined(CONFIG_SOC_SERIES_STM32L4X)
+#define USB_OTG_SPEED_HIGH                     0U
+#define USB_OTG_SPEED_HIGH_IN_FULL             1U
+#endif /* CONFIG_SOC_SERIES_STM32L4X */
+
+#define EP0_MPS USB_OTG_MAX_EP0_SIZE
+
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
+#define EP_MPS USB_OTG_HS_MAX_PACKET_SIZE
+#elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs) || DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usb)
+#define EP_MPS USB_OTG_FS_MAX_PACKET_SIZE
+#endif
+
+/* We need n TX IN FIFOs */
+#define TX_FIFO_NUM USB_NUM_BIDIR_ENDPOINTS
+
+/* We need a minimum size for RX FIFO */
+#define USB_FIFO_RX_MIN 160
+
+/* 4-byte words TX FIFO */
+#define TX_FIFO_WORDS ((USB_RAM_SIZE - USB_FIFO_RX_MIN - 64) / 4)
+
+/* Allocate FIFO memory evenly between the TX FIFOs */
+/* except the first TX endpoint need only 64 bytes */
+#define TX_FIFO_EP_WORDS (TX_FIFO_WORDS / (TX_FIFO_NUM - 1))
+
+#endif /* USB */
+
+/* Size of a USB SETUP packet */
+#define SETUP_SIZE 8
+
+/* Helper macros to make it easier to work with endpoint numbers */
+#define EP0_IDX 0
+#define EP0_IN (EP0_IDX | USB_EP_DIR_IN)
+#define EP0_OUT (EP0_IDX | USB_EP_DIR_OUT)
+
+/* Endpoint state */
+struct usb_dc_stm32_ep_state {
+	uint16_t ep_mps;		/** Endpoint max packet size */
+	uint16_t ep_pma_buf_len;	/** Previously allocated buffer size */
+	uint8_t ep_type;		/** Endpoint type (STM32 HAL enum) */
+	uint8_t ep_stalled;	/** Endpoint stall flag */
+	usb_dc_ep_callback cb;	/** Endpoint callback function */
+	uint32_t read_count;	/** Number of bytes in read buffer  */
+	uint32_t read_offset;	/** Current offset in read buffer */
+	struct k_sem write_sem;	/** Write boolean semaphore */
+};
+
+/* Driver state */
+struct usb_dc_stm32_state {
+	PCD_HandleTypeDef pcd;	/* Storage for the HAL_PCD api */
+	usb_dc_status_callback status_cb; /* Status callback */
+	struct usb_dc_stm32_ep_state out_ep_state[USB_NUM_BIDIR_ENDPOINTS];
+	struct usb_dc_stm32_ep_state in_ep_state[USB_NUM_BIDIR_ENDPOINTS];
+	uint8_t ep_buf[USB_NUM_BIDIR_ENDPOINTS][EP_MPS];
+
+#if defined(USB) || defined(USB_DRD_FS)
+	uint32_t pma_offset;
+#endif /* USB */
+};
+
+static struct usb_dc_stm32_state usb_dc_stm32_state;
+
+/* Internal functions */
+
+static struct usb_dc_stm32_ep_state *usb_dc_stm32_get_ep_state(uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state_base;
+
+	if (USB_EP_GET_IDX(ep) >= USB_NUM_BIDIR_ENDPOINTS) {
+		return NULL;
+	}
+
+	if (USB_EP_DIR_IS_OUT(ep)) {
+		ep_state_base = usb_dc_stm32_state.out_ep_state;
+	} else {
+		ep_state_base = usb_dc_stm32_state.in_ep_state;
+	}
+
+	return ep_state_base + USB_EP_GET_IDX(ep);
+}
+
+static void usb_dc_stm32_isr(const void *arg)
+{
+	/* Modified line to call the ucpd_isr() */
+	ucpd_isr();
+
+	HAL_PCD_IRQHandler(&usb_dc_stm32_state.pcd);
+}
+
+#ifdef CONFIG_USB_DEVICE_SOF
+void HAL_PCD_SOFCallback(PCD_HandleTypeDef *hpcd)
+{
+	usb_dc_stm32_state.status_cb(USB_DC_SOF, NULL);
+}
+#endif
+
+static int usb_dc_stm32_clock_enable(void)
+{
+	const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
+
+	if (!device_is_ready(clk)) {
+		LOG_ERR("clock control device not ready");
+		return -ENODEV;
+	}
+
+#if defined(PWR_USBSCR_USB33SV) || defined(PWR_SVMCR_USV)
+
+	/*
+	 * VDDUSB independent USB supply (PWR clock is on)
+	 * with LL_PWR_EnableVDDUSB function (higher case)
+	 */
+	LL_PWR_EnableVDDUSB();
+#endif /* PWR_USBSCR_USB33SV or PWR_SVMCR_USV */
+
+	if (DT_INST_NUM_CLOCKS(0) > 1) {
+		if (clock_control_configure(clk, (clock_control_subsys_t)&pclken[1],
+									NULL) != 0) {
+			LOG_ERR("Could not select USB domain clock");
+			return -EIO;
+		}
+	}
+
+	if (clock_control_on(clk, (clock_control_subsys_t)&pclken[0]) != 0) {
+		LOG_ERR("Unable to enable USB clock");
+		return -EIO;
+	}
+
+	if (IS_ENABLED(CONFIG_USB_DC_STM32_CLOCK_CHECK)) {
+		uint32_t usb_clock_rate;
+
+		if (clock_control_get_rate(clk,
+					   (clock_control_subsys_t)&pclken[1],
+					   &usb_clock_rate) != 0) {
+			LOG_ERR("Failed to get USB domain clock rate");
+			return -EIO;
+		}
+
+		if (usb_clock_rate != MHZ(48)) {
+			LOG_ERR("USB Clock is not 48MHz (%d)", usb_clock_rate);
+			return -ENOTSUP;
+		}
+	}
+
+	/* Previous check won't work in case of F1/F3. Add build time check */
+#if defined(RCC_CFGR_OTGFSPRE) || defined(RCC_CFGR_USBPRE)
+
+#if (MHZ(48) == CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC) && !defined(STM32_PLL_USBPRE)
+	/* PLL output clock is set to 48MHz, it should not be divided */
+#warning USBPRE/OTGFSPRE should be set in rcc node
+#endif
+
+#endif /* RCC_CFGR_OTGFSPRE / RCC_CFGR_USBPRE */
+
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_usbphyc)
+	LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_OTGHSULPI);
+	LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_OTGPHYC);
+#elif defined(CONFIG_SOC_SERIES_STM32H7X)
+#if !USB_OTG_HS_ULPI_PHY
+	/* Disable ULPI interface (for external high-speed PHY) clock in sleep
+	 * mode.
+	 */
+	LL_AHB1_GRP1_DisableClockSleep(LL_AHB1_GRP1_PERIPH_USB1OTGHSULPI);
+#endif
+#else
+	/* Disable ULPI interface (for external high-speed PHY) clock in low
+	 * power mode. It is disabled by default in run power mode, no need to
+	 * disable it.
+	 */
+	LL_AHB1_GRP1_DisableClockLowPower(LL_AHB1_GRP1_PERIPH_OTGHSULPI);
+#endif
+#endif
+
+	return 0;
+}
+
+static int usb_dc_stm32_clock_disable(void)
+{
+	const struct device *clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
+
+	if (clock_control_off(clk, (clock_control_subsys_t)&pclken[0]) != 0) {
+		LOG_ERR("Unable to disable USB clock");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+#if defined(USB_OTG_FS) || defined(USB_OTG_HS)
+static uint32_t usb_dc_stm32_get_maximum_speed(void)
+{
+	/*
+	 * If max-speed is not passed via DT, set it to USB controller's
+	 * maximum hardware capability.
+	 */
+#if USB_OTG_HS_EMB_PHY || USB_OTG_HS_ULPI_PHY
+	uint32_t speed = USB_OTG_SPEED_HIGH;
+#else
+	uint32_t speed = USB_OTG_SPEED_FULL;
+#endif
+
+#ifdef USB_MAXIMUM_SPEED
+
+	if (!strncmp(USB_MAXIMUM_SPEED, "high-speed", 10)) {
+		speed = USB_OTG_SPEED_HIGH;
+	} else if (!strncmp(USB_MAXIMUM_SPEED, "full-speed", 10)) {
+#if defined(CONFIG_SOC_SERIES_STM32H7X) || defined(USB_OTG_HS_EMB_PHY)
+		speed = USB_OTG_SPEED_HIGH_IN_FULL;
+#else
+		speed = USB_OTG_SPEED_FULL;
+#endif
+	} else {
+		LOG_DBG("Unsupported maximum speed defined in device tree. "
+			"USB controller will default to its maximum HW "
+			"capability");
+	}
+#endif
+
+	return speed;
+}
+#endif /* USB_OTG_FS || USB_OTG_HS */
+
+static int usb_dc_stm32_init(void)
+{
+	HAL_StatusTypeDef status;
+	int ret;
+	unsigned int i;
+
+#if defined(USB) || defined(USB_DRD_FS)
+#ifdef USB
+	usb_dc_stm32_state.pcd.Instance = USB;
+#else
+	usb_dc_stm32_state.pcd.Instance = USB_DRD_FS;
+#endif
+	usb_dc_stm32_state.pcd.Init.speed = PCD_SPEED_FULL;
+	usb_dc_stm32_state.pcd.Init.dev_endpoints = USB_NUM_BIDIR_ENDPOINTS;
+	usb_dc_stm32_state.pcd.Init.phy_itface = PCD_PHY_EMBEDDED;
+	usb_dc_stm32_state.pcd.Init.ep0_mps = PCD_EP0MPS_64;
+	usb_dc_stm32_state.pcd.Init.low_power_enable = 0;
+#else /* USB_OTG_FS || USB_OTG_HS */
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otghs)
+	usb_dc_stm32_state.pcd.Instance = USB_OTG_HS;
+#else
+	usb_dc_stm32_state.pcd.Instance = USB_OTG_FS;
+#endif
+	usb_dc_stm32_state.pcd.Init.dev_endpoints = USB_NUM_BIDIR_ENDPOINTS;
+	usb_dc_stm32_state.pcd.Init.speed = usb_dc_stm32_get_maximum_speed();
+#if USB_OTG_HS_EMB_PHY
+	usb_dc_stm32_state.pcd.Init.phy_itface = USB_OTG_HS_EMBEDDED_PHY;
+#elif USB_OTG_HS_ULPI_PHY
+	usb_dc_stm32_state.pcd.Init.phy_itface = USB_OTG_ULPI_PHY;
+#else
+	usb_dc_stm32_state.pcd.Init.phy_itface = PCD_PHY_EMBEDDED;
+#endif
+	usb_dc_stm32_state.pcd.Init.ep0_mps = USB_OTG_MAX_EP0_SIZE;
+	usb_dc_stm32_state.pcd.Init.vbus_sensing_enable = USB_VBUS_SENSING ? ENABLE : DISABLE;
+
+#ifndef CONFIG_SOC_SERIES_STM32F1X
+	usb_dc_stm32_state.pcd.Init.dma_enable = DISABLE;
+#endif
+
+#endif /* USB */
+
+#ifdef CONFIG_USB_DEVICE_SOF
+	usb_dc_stm32_state.pcd.Init.Sof_enable = 1;
+#endif /* CONFIG_USB_DEVICE_SOF */
+
+#if defined(CONFIG_SOC_SERIES_STM32H7X)
+#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_otgfs)
+	/* The USB2 controller only works in FS mode, but the ULPI clock needs
+	 * to be disabled in sleep mode for it to work. For the USB1
+	 * controller, as it is an HS one, the clock is disabled in the common
+	 * path.
+	 */
+
+	LL_AHB1_GRP1_DisableClockSleep(LL_AHB1_GRP1_PERIPH_USB2OTGHSULPI);
+#endif
+
+	LL_PWR_EnableUSBVoltageDetector();
+
+	/* Per AN2606: USBREGEN not supported when running in FS mode. */
+	LL_PWR_DisableUSBReg();
+	while (!LL_PWR_IsActiveFlag_USB()) {
+		LOG_INF("PWR not active yet");
+		k_sleep(K_MSEC(100));
+	}
+#endif
+
+	LOG_DBG("Pinctrl signals configuration");
+	ret = pinctrl_apply_state(usb_pcfg, PINCTRL_STATE_DEFAULT);
+	if (ret < 0) {
+		LOG_ERR("USB pinctrl setup failed (%d)", ret);
+		return ret;
+	}
+
+	LOG_DBG("HAL_PCD_Init");
+	status = HAL_PCD_Init(&usb_dc_stm32_state.pcd);
+	if (status != HAL_OK) {
+		LOG_ERR("PCD_Init failed, %d", (int)status);
+		return -EIO;
+	}
+
+	/* On a soft reset force USB to reset first and switch it off
+	 * so the USB connection can get re-initialized
+	 */
+	LOG_DBG("HAL_PCD_Stop");
+	status = HAL_PCD_Stop(&usb_dc_stm32_state.pcd);
+	if (status != HAL_OK) {
+		LOG_ERR("PCD_Stop failed, %d", (int)status);
+		return -EIO;
+	}
+
+	LOG_DBG("HAL_PCD_Start");
+	status = HAL_PCD_Start(&usb_dc_stm32_state.pcd);
+	if (status != HAL_OK) {
+		LOG_ERR("PCD_Start failed, %d", (int)status);
+		return -EIO;
+	}
+
+	usb_dc_stm32_state.out_ep_state[EP0_IDX].ep_mps = EP0_MPS;
+	usb_dc_stm32_state.out_ep_state[EP0_IDX].ep_type = EP_TYPE_CTRL;
+	usb_dc_stm32_state.in_ep_state[EP0_IDX].ep_mps = EP0_MPS;
+	usb_dc_stm32_state.in_ep_state[EP0_IDX].ep_type = EP_TYPE_CTRL;
+
+#if defined(USB) || defined(USB_DRD_FS)
+	/* Start PMA configuration for the endpoints after the BTABLE. */
+	usb_dc_stm32_state.pma_offset = USB_BTABLE_SIZE;
+
+	for (i = 0U; i < USB_NUM_BIDIR_ENDPOINTS; i++) {
+		k_sem_init(&usb_dc_stm32_state.in_ep_state[i].write_sem, 1, 1);
+	}
+#else /* USB_OTG_FS */
+
+	/* TODO: make this dynamic (depending usage) */
+	HAL_PCDEx_SetRxFiFo(&usb_dc_stm32_state.pcd, USB_FIFO_RX_MIN);
+	for (i = 0U; i < USB_NUM_BIDIR_ENDPOINTS; i++) {
+		if (i == 0) {
+			/* first endpoint need only 64 byte for EP_TYPE_CTRL */
+			HAL_PCDEx_SetTxFiFo(&usb_dc_stm32_state.pcd, i,	16);
+		} else {
+			HAL_PCDEx_SetTxFiFo(&usb_dc_stm32_state.pcd, i,
+					TX_FIFO_EP_WORDS);
+		}
+		k_sem_init(&usb_dc_stm32_state.in_ep_state[i].write_sem, 1, 1);
+	}
+#endif /* USB */
+
+	IRQ_CONNECT(USB_IRQ, USB_IRQ_PRI,
+		    usb_dc_stm32_isr, 0, 0);
+	irq_enable(USB_IRQ);
+	return 0;
+}
+
+/* Zephyr USB device controller API implementation */
+
+int usb_dc_attach(void)
+{
+	int ret;
+
+	LOG_DBG("");
+
+#ifdef SYSCFG_CFGR1_USB_IT_RMP
+	/*
+	 * STM32F302/F303: USB IRQ collides with CAN_1 IRQ (§14.1.3, RM0316)
+	 * Remap IRQ by default to enable use of both IPs simultaneoulsy
+	 * This should be done before calling any HAL function
+	 */
+	if (LL_APB2_GRP1_IsEnabledClock(LL_APB2_GRP1_PERIPH_SYSCFG)) {
+		LL_SYSCFG_EnableRemapIT_USB();
+	} else {
+		LOG_ERR("System Configuration Controller clock is "
+			"disabled. Unable to enable IRQ remapping.");
+	}
+#endif
+
+#if USB_OTG_HS_ULPI_PHY
+	if (ulpi_reset.port != NULL) {
+		if (!gpio_is_ready_dt(&ulpi_reset)) {
+			LOG_ERR("Reset GPIO device not ready");
+			return -EINVAL;
+		}
+		if (gpio_pin_configure_dt(&ulpi_reset, GPIO_OUTPUT_INACTIVE)) {
+			LOG_ERR("Couldn't configure reset pin");
+			return -EIO;
+		}
+	}
+#endif
+
+	ret = usb_dc_stm32_clock_enable();
+	if (ret) {
+		return ret;
+	}
+
+	ret = usb_dc_stm32_init();
+	if (ret) {
+		return ret;
+	}
+
+	/*
+	 * Required for at least STM32L4 devices as they electrically
+	 * isolate USB features from VddUSB. It must be enabled before
+	 * USB can function. Refer to section 5.1.3 in DM00083560 or
+	 * DM00310109.
+	 */
+#ifdef PWR_CR2_USV
+#if defined(LL_APB1_GRP1_PERIPH_PWR)
+	if (LL_APB1_GRP1_IsEnabledClock(LL_APB1_GRP1_PERIPH_PWR)) {
+		LL_PWR_EnableVddUSB();
+	} else {
+		LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
+		LL_PWR_EnableVddUSB();
+		LL_APB1_GRP1_DisableClock(LL_APB1_GRP1_PERIPH_PWR);
+	}
+#else
+	LL_PWR_EnableVddUSB();
+#endif /* defined(LL_APB1_GRP1_PERIPH_PWR) */
+#endif /* PWR_CR2_USV */
+
+	return 0;
+}
+
+int usb_dc_ep_set_callback(const uint8_t ep, const usb_dc_ep_callback cb)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	LOG_DBG("ep 0x%02x", ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	ep_state->cb = cb;
+
+	return 0;
+}
+
+void usb_dc_set_status_callback(const usb_dc_status_callback cb)
+{
+	LOG_DBG("");
+
+	usb_dc_stm32_state.status_cb = cb;
+}
+
+int usb_dc_set_address(const uint8_t addr)
+{
+	HAL_StatusTypeDef status;
+
+	LOG_DBG("addr %u (0x%02x)", addr, addr);
+
+	status = HAL_PCD_SetAddress(&usb_dc_stm32_state.pcd, addr);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_SetAddress failed(0x%02x), %d", addr,
+			(int)status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_start_read(uint8_t ep, uint8_t *data, uint32_t max_data_len)
+{
+	HAL_StatusTypeDef status;
+
+	LOG_DBG("ep 0x%02x, len %u", ep, max_data_len);
+
+	/* we flush EP0_IN by doing a 0 length receive on it */
+	if (!USB_EP_DIR_IS_OUT(ep) && (ep != EP0_IN || max_data_len)) {
+		LOG_ERR("invalid ep 0x%02x", ep);
+		return -EINVAL;
+	}
+
+	if (max_data_len > EP_MPS) {
+		max_data_len = EP_MPS;
+	}
+
+	status = HAL_PCD_EP_Receive(&usb_dc_stm32_state.pcd, ep,
+				    usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)],
+				    max_data_len);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_EP_Receive failed(0x%02x), %d", ep,
+			(int)status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_get_read_count(uint8_t ep, uint32_t *read_bytes)
+{
+	if (!USB_EP_DIR_IS_OUT(ep) || !read_bytes) {
+		LOG_ERR("invalid ep 0x%02x", ep);
+		return -EINVAL;
+	}
+
+	*read_bytes = HAL_PCD_EP_GetRxCount(&usb_dc_stm32_state.pcd, ep);
+
+	return 0;
+}
+
+int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg)
+{
+	uint8_t ep_idx = USB_EP_GET_IDX(cfg->ep_addr);
+
+	LOG_DBG("ep %x, mps %d, type %d", cfg->ep_addr, cfg->ep_mps,
+		cfg->ep_type);
+
+	if ((cfg->ep_type == USB_DC_EP_CONTROL) && ep_idx) {
+		LOG_ERR("invalid endpoint configuration");
+		return -1;
+	}
+
+	if (ep_idx > (USB_NUM_BIDIR_ENDPOINTS - 1)) {
+		LOG_ERR("endpoint index/address out of range");
+		return -1;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg)
+{
+	uint8_t ep = ep_cfg->ep_addr;
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	LOG_DBG("ep 0x%02x, previous ep_mps %u, ep_mps %u, ep_type %u",
+		ep_cfg->ep_addr, ep_state->ep_mps, ep_cfg->ep_mps,
+		ep_cfg->ep_type);
+#if defined(USB) || defined(USB_DRD_FS)
+	if (ep_cfg->ep_mps > ep_state->ep_pma_buf_len) {
+		if (ep_cfg->ep_type == USB_DC_EP_ISOCHRONOUS) {
+			if (USB_RAM_SIZE <=
+			   (usb_dc_stm32_state.pma_offset + ep_cfg->ep_mps*2)) {
+				return -EINVAL;
+			}
+		} else if (USB_RAM_SIZE <=
+			  (usb_dc_stm32_state.pma_offset + ep_cfg->ep_mps)) {
+			return -EINVAL;
+		}
+
+		if (ep_cfg->ep_type == USB_DC_EP_ISOCHRONOUS) {
+			HAL_PCDEx_PMAConfig(&usb_dc_stm32_state.pcd, ep, PCD_DBL_BUF,
+				usb_dc_stm32_state.pma_offset +
+				((usb_dc_stm32_state.pma_offset + ep_cfg->ep_mps) << 16));
+			ep_state->ep_pma_buf_len = ep_cfg->ep_mps*2;
+			usb_dc_stm32_state.pma_offset += ep_cfg->ep_mps*2;
+		} else {
+			HAL_PCDEx_PMAConfig(&usb_dc_stm32_state.pcd, ep, PCD_SNG_BUF,
+				usb_dc_stm32_state.pma_offset);
+			ep_state->ep_pma_buf_len = ep_cfg->ep_mps;
+			usb_dc_stm32_state.pma_offset += ep_cfg->ep_mps;
+		}
+	}
+	if (ep_cfg->ep_type == USB_DC_EP_ISOCHRONOUS) {
+		ep_state->ep_mps = ep_cfg->ep_mps*2;
+	} else {
+		ep_state->ep_mps = ep_cfg->ep_mps;
+	}
+#else
+	ep_state->ep_mps = ep_cfg->ep_mps;
+#endif
+
+	switch (ep_cfg->ep_type) {
+	case USB_DC_EP_CONTROL:
+		ep_state->ep_type = EP_TYPE_CTRL;
+		break;
+	case USB_DC_EP_ISOCHRONOUS:
+		ep_state->ep_type = EP_TYPE_ISOC;
+		break;
+	case USB_DC_EP_BULK:
+		ep_state->ep_type = EP_TYPE_BULK;
+		break;
+	case USB_DC_EP_INTERRUPT:
+		ep_state->ep_type = EP_TYPE_INTR;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_set_stall(const uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+	HAL_StatusTypeDef status;
+
+	LOG_DBG("ep 0x%02x", ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	status = HAL_PCD_EP_SetStall(&usb_dc_stm32_state.pcd, ep);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_EP_SetStall failed(0x%02x), %d", ep,
+			(int)status);
+		return -EIO;
+	}
+
+	ep_state->ep_stalled = 1U;
+
+	return 0;
+}
+
+int usb_dc_ep_clear_stall(const uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+	HAL_StatusTypeDef status;
+
+	LOG_DBG("ep 0x%02x", ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	status = HAL_PCD_EP_ClrStall(&usb_dc_stm32_state.pcd, ep);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_EP_ClrStall failed(0x%02x), %d", ep,
+			(int)status);
+		return -EIO;
+	}
+
+	ep_state->ep_stalled = 0U;
+	ep_state->read_count = 0U;
+
+	return 0;
+}
+
+int usb_dc_ep_is_stalled(const uint8_t ep, uint8_t *const stalled)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	LOG_DBG("ep 0x%02x", ep);
+
+	if (!ep_state || !stalled) {
+		return -EINVAL;
+	}
+
+	*stalled = ep_state->ep_stalled;
+
+	return 0;
+}
+
+int usb_dc_ep_enable(const uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+	HAL_StatusTypeDef status;
+
+	LOG_DBG("ep 0x%02x", ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	LOG_DBG("HAL_PCD_EP_Open(0x%02x, %u, %u)", ep, ep_state->ep_mps,
+		ep_state->ep_type);
+
+	status = HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, ep,
+				 ep_state->ep_mps, ep_state->ep_type);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_EP_Open failed(0x%02x), %d", ep,
+			(int)status);
+		return -EIO;
+	}
+
+	if (USB_EP_DIR_IS_OUT(ep) && ep != EP0_OUT) {
+		return usb_dc_ep_start_read(ep,
+					  usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)],
+					  ep_state->ep_mps);
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_disable(const uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+	HAL_StatusTypeDef status;
+
+	LOG_DBG("ep 0x%02x", ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	status = HAL_PCD_EP_Close(&usb_dc_stm32_state.pcd, ep);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_EP_Close failed(0x%02x), %d", ep,
+			(int)status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_write(const uint8_t ep, const uint8_t *const data,
+		    const uint32_t data_len, uint32_t * const ret_bytes)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+	HAL_StatusTypeDef status;
+	uint32_t len = data_len;
+	int ret = 0;
+
+	LOG_DBG("ep 0x%02x, len %u", ep, data_len);
+
+	if (!ep_state || !USB_EP_DIR_IS_IN(ep)) {
+		LOG_ERR("invalid ep 0x%02x", ep);
+		return -EINVAL;
+	}
+
+	ret = k_sem_take(&ep_state->write_sem, K_NO_WAIT);
+	if (ret) {
+		LOG_ERR("Unable to get write lock (%d)", ret);
+		return -EAGAIN;
+	}
+
+	if (!k_is_in_isr()) {
+		irq_disable(USB_IRQ);
+	}
+
+	if (ep == EP0_IN && len > USB_MAX_CTRL_MPS) {
+		len = USB_MAX_CTRL_MPS;
+	}
+
+	status = HAL_PCD_EP_Transmit(&usb_dc_stm32_state.pcd, ep,
+				     (void *)data, len);
+	if (status != HAL_OK) {
+		LOG_ERR("HAL_PCD_EP_Transmit failed(0x%02x), %d", ep,
+			(int)status);
+		k_sem_give(&ep_state->write_sem);
+		ret = -EIO;
+	}
+
+	if (!ret && ep == EP0_IN && len > 0) {
+		/* Wait for an empty package as from the host.
+		 * This also flushes the TX FIFO to the host.
+		 */
+		usb_dc_ep_start_read(ep, NULL, 0);
+	}
+
+	if (!k_is_in_isr()) {
+		irq_enable(USB_IRQ);
+	}
+
+	if (!ret && ret_bytes) {
+		*ret_bytes = len;
+	}
+
+	return ret;
+}
+
+int usb_dc_ep_read_wait(uint8_t ep, uint8_t *data, uint32_t max_data_len,
+			uint32_t *read_bytes)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+	uint32_t read_count;
+
+	if (!ep_state) {
+		LOG_ERR("Invalid Endpoint %x", ep);
+		return -EINVAL;
+	}
+
+	read_count = ep_state->read_count;
+
+	LOG_DBG("ep 0x%02x, %u bytes, %u+%u, %p", ep, max_data_len,
+		ep_state->read_offset, read_count, data);
+
+	if (!USB_EP_DIR_IS_OUT(ep)) { /* check if OUT ep */
+		LOG_ERR("Wrong endpoint direction: 0x%02x", ep);
+		return -EINVAL;
+	}
+
+	/* When both buffer and max data to read are zero, just ignore reading
+	 * and return available data in buffer. Otherwise, return data
+	 * previously stored in the buffer.
+	 */
+	if (data) {
+		read_count = MIN(read_count, max_data_len);
+		memcpy(data, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)] +
+		       ep_state->read_offset, read_count);
+		ep_state->read_count -= read_count;
+		ep_state->read_offset += read_count;
+	} else if (max_data_len) {
+		LOG_ERR("Wrong arguments");
+	}
+
+	if (read_bytes) {
+		*read_bytes = read_count;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_read_continue(uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	if (!ep_state || !USB_EP_DIR_IS_OUT(ep)) { /* Check if OUT ep */
+		LOG_ERR("Not valid endpoint: %02x", ep);
+		return -EINVAL;
+	}
+
+	/* If no more data in the buffer, start a new read transaction.
+	 * DataOutStageCallback will called on transaction complete.
+	 */
+	if (!ep_state->read_count) {
+		usb_dc_ep_start_read(ep, usb_dc_stm32_state.ep_buf[USB_EP_GET_IDX(ep)],
+				     ep_state->ep_mps);
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_read(const uint8_t ep, uint8_t *const data, const uint32_t max_data_len,
+		   uint32_t * const read_bytes)
+{
+	if (usb_dc_ep_read_wait(ep, data, max_data_len, read_bytes) != 0) {
+		return -EINVAL;
+	}
+
+	if (usb_dc_ep_read_continue(ep) != 0) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+int usb_dc_ep_halt(const uint8_t ep)
+{
+	return usb_dc_ep_set_stall(ep);
+}
+
+int usb_dc_ep_flush(const uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	LOG_ERR("Not implemented");
+
+	return 0;
+}
+
+int usb_dc_ep_mps(const uint8_t ep)
+{
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	if (!ep_state) {
+		return -EINVAL;
+	}
+
+	return ep_state->ep_mps;
+}
+
+int usb_dc_wakeup_request(void)
+{
+	HAL_StatusTypeDef status;
+
+	status = HAL_PCD_ActivateRemoteWakeup(&usb_dc_stm32_state.pcd);
+	if (status != HAL_OK) {
+		return -EAGAIN;
+	}
+
+	/* Must be active from 1ms to 15ms as per reference manual. */
+	k_sleep(K_MSEC(2));
+
+	status = HAL_PCD_DeActivateRemoteWakeup(&usb_dc_stm32_state.pcd);
+	if (status != HAL_OK) {
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+int usb_dc_detach(void)
+{
+	HAL_StatusTypeDef status;
+	int ret;
+
+	LOG_DBG("HAL_PCD_DeInit");
+	status = HAL_PCD_DeInit(&usb_dc_stm32_state.pcd);
+	if (status != HAL_OK) {
+		LOG_ERR("PCD_DeInit failed, %d", (int)status);
+		return -EIO;
+	}
+
+	ret = usb_dc_stm32_clock_disable();
+	if (ret) {
+		return ret;
+	}
+
+	if (irq_is_enabled(USB_IRQ)) {
+		irq_disable(USB_IRQ);
+	}
+
+	return 0;
+}
+
+int usb_dc_reset(void)
+{
+	LOG_ERR("Not implemented");
+
+	return 0;
+}
+
+/* Callbacks from the STM32 Cube HAL code */
+
+void HAL_PCD_ResetCallback(PCD_HandleTypeDef *hpcd)
+{
+	int i;
+
+	LOG_DBG("");
+
+	HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, EP0_IN, EP0_MPS, EP_TYPE_CTRL);
+	HAL_PCD_EP_Open(&usb_dc_stm32_state.pcd, EP0_OUT, EP0_MPS,
+			EP_TYPE_CTRL);
+
+	/* The DataInCallback will never be called at this point for any pending
+	 * transactions. Reset the IN semaphores to prevent perpetual locked state.
+	 * */
+	for (i = 0; i < USB_NUM_BIDIR_ENDPOINTS; i++) {
+		k_sem_give(&usb_dc_stm32_state.in_ep_state[i].write_sem);
+	}
+
+	if (usb_dc_stm32_state.status_cb) {
+		usb_dc_stm32_state.status_cb(USB_DC_RESET, NULL);
+	}
+}
+
+void HAL_PCD_ConnectCallback(PCD_HandleTypeDef *hpcd)
+{
+	LOG_DBG("");
+
+	if (usb_dc_stm32_state.status_cb) {
+		usb_dc_stm32_state.status_cb(USB_DC_CONNECTED, NULL);
+	}
+}
+
+void HAL_PCD_DisconnectCallback(PCD_HandleTypeDef *hpcd)
+{
+	LOG_DBG("");
+
+	if (usb_dc_stm32_state.status_cb) {
+		usb_dc_stm32_state.status_cb(USB_DC_DISCONNECTED, NULL);
+	}
+}
+
+void HAL_PCD_SuspendCallback(PCD_HandleTypeDef *hpcd)
+{
+	LOG_DBG("");
+
+	if (usb_dc_stm32_state.status_cb) {
+		usb_dc_stm32_state.status_cb(USB_DC_SUSPEND, NULL);
+	}
+}
+
+void HAL_PCD_ResumeCallback(PCD_HandleTypeDef *hpcd)
+{
+	LOG_DBG("");
+
+	if (usb_dc_stm32_state.status_cb) {
+		usb_dc_stm32_state.status_cb(USB_DC_RESUME, NULL);
+	}
+}
+
+void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd)
+{
+	struct usb_setup_packet *setup = (void *)usb_dc_stm32_state.pcd.Setup;
+	struct usb_dc_stm32_ep_state *ep_state;
+
+	LOG_DBG("");
+
+	ep_state = usb_dc_stm32_get_ep_state(EP0_OUT); /* can't fail for ep0 */
+	__ASSERT(ep_state, "No corresponding ep_state for EP0");
+
+	ep_state->read_count = SETUP_SIZE;
+	ep_state->read_offset = 0U;
+	memcpy(&usb_dc_stm32_state.ep_buf[EP0_IDX],
+	       usb_dc_stm32_state.pcd.Setup, ep_state->read_count);
+
+	if (ep_state->cb) {
+		ep_state->cb(EP0_OUT, USB_DC_EP_SETUP);
+
+		if (!(setup->wLength == 0U) &&
+		    usb_reqtype_is_to_device(setup)) {
+			usb_dc_ep_start_read(EP0_OUT,
+					     usb_dc_stm32_state.ep_buf[EP0_IDX],
+					     setup->wLength);
+		}
+	}
+}
+
+void HAL_PCD_DataOutStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
+{
+	uint8_t ep_idx = USB_EP_GET_IDX(epnum);
+	uint8_t ep = ep_idx | USB_EP_DIR_OUT;
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	LOG_DBG("epnum 0x%02x, rx_count %u", epnum,
+		HAL_PCD_EP_GetRxCount(&usb_dc_stm32_state.pcd, epnum));
+
+	/* Transaction complete, data is now stored in the buffer and ready
+	 * for the upper stack (usb_dc_ep_read to retrieve).
+	 */
+	usb_dc_ep_get_read_count(ep, &ep_state->read_count);
+	ep_state->read_offset = 0U;
+
+	if (ep_state->cb) {
+		ep_state->cb(ep, USB_DC_EP_DATA_OUT);
+	}
+}
+
+void HAL_PCD_DataInStageCallback(PCD_HandleTypeDef *hpcd, uint8_t epnum)
+{
+	uint8_t ep_idx = USB_EP_GET_IDX(epnum);
+	uint8_t ep = ep_idx | USB_EP_DIR_IN;
+	struct usb_dc_stm32_ep_state *ep_state = usb_dc_stm32_get_ep_state(ep);
+
+	LOG_DBG("epnum 0x%02x", epnum);
+
+	__ASSERT(ep_state, "No corresponding ep_state for ep");
+
+	k_sem_give(&ep_state->write_sem);
+
+	if (ep_state->cb) {
+		ep_state->cb(ep, USB_DC_EP_DATA_IN);
+	}
+}
+
+#if (defined(USB) || defined(USB_DRD_FS)) && DT_INST_NODE_HAS_PROP(0, disconnect_gpios)
+void HAL_PCDEx_SetConnectionState(PCD_HandleTypeDef *hpcd, uint8_t state)
+{
+	struct gpio_dt_spec usb_disconnect = GPIO_DT_SPEC_INST_GET(0, disconnect_gpios);
+
+	gpio_pin_configure_dt(&usb_disconnect,
+			   (state ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE));
+}
+#endif /* USB && DT_INST_NODE_HAS_PROP(0, disconnect_gpios) */
\ No newline at end of file
diff --git a/zephyr_projects/twinkie_v2/src/view.c b/zephyr_projects/twinkie_v2/src/view.c
new file mode 100644
index 0000000..ef81df7
--- /dev/null
+++ b/zephyr_projects/twinkie_v2/src/view.c
@@ -0,0 +1,350 @@
+/* Copyright 2023 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/kernel.h>
+#include <zephyr/smf.h>
+#include <zephyr/drivers/gpio.h>
+
+#include <zephyr/logging/log.h>
+#include <zephyr/usb/usb_device.h>
+#include <zephyr/device.h>
+#include <zephyr/drivers/uart.h>
+#include <zephyr/shell/shell.h>
+
+#include "controls.h"
+#include "meas.h"
+#include "view.h"
+#include "model.h"
+
+LOG_MODULE_REGISTER(main);
+
+#define DEBUG 1
+
+/* The devicetree node identifier for the led aliases. */
+#define LED0_NODE DT_ALIAS(led0)
+#define LED1_NODE DT_ALIAS(led1)
+#define LED2_NODE DT_ALIAS(led2)
+
+/* various flags for the current state of the snooper */
+#define FLAGS_SNOOP0         0
+#define FLAGS_SNOOP1         1
+#define FLAGS_SNOOP2         2
+#define FLAGS_SNOOP3         3
+#define FLAGS_NO_CONNECTION  4
+#define FLAGS_CC1_CONNECTION 5
+#define FLAGS_CC2_CONNECTION 6
+
+static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
+static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(LED1_NODE, gpios);
+static const struct gpio_dt_spec led2 = GPIO_DT_SPEC_GET(LED2_NODE, gpios);
+
+static const struct smf_state view_states[];
+
+enum led_t {
+	LED_OFF,
+	LED_RED,
+	LED_GREEN,
+	LED_BLUE
+};
+enum view_state_t {
+	SNOOP0,
+	SNOOP1,
+	SNOOP2,
+	SNOOP3
+};
+
+struct view_obj_t {
+	struct smf_ctx ctx;
+	k_timeout_t timeout;
+	bool toggle;
+	atomic_t flags;
+} view_obj;
+
+snooper_mask_t get_view_snoop()
+{
+	if (atomic_test_bit(&view_obj.flags, FLAGS_SNOOP0)) {
+		return 0;
+	}
+	if (atomic_test_bit(&view_obj.flags, FLAGS_SNOOP1)) {
+		return CC1_CHANNEL_BIT;
+	}
+	if (atomic_test_bit(&view_obj.flags, FLAGS_SNOOP2)) {
+		return CC2_CHANNEL_BIT;
+	}
+	if (atomic_test_bit(&view_obj.flags, FLAGS_SNOOP3)) {
+		return (CC1_CHANNEL_BIT | CC2_CHANNEL_BIT);
+	}
+	return 0;
+}
+
+int view_set_connection(snooper_mask_t vc)
+{
+	switch (vc) {
+	case 0:
+		atomic_clear_bit(&view_obj.flags, FLAGS_CC1_CONNECTION);
+		atomic_clear_bit(&view_obj.flags, FLAGS_CC2_CONNECTION);
+		atomic_set_bit(&view_obj.flags, FLAGS_NO_CONNECTION);
+		break;
+	case CC1_CHANNEL_BIT:
+		atomic_clear_bit(&view_obj.flags, FLAGS_NO_CONNECTION);
+		atomic_clear_bit(&view_obj.flags, FLAGS_CC2_CONNECTION);
+		atomic_set_bit(&view_obj.flags, FLAGS_CC1_CONNECTION);
+		break;
+	case CC2_CHANNEL_BIT:
+		atomic_clear_bit(&view_obj.flags, FLAGS_NO_CONNECTION);
+		atomic_clear_bit(&view_obj.flags, FLAGS_CC1_CONNECTION);
+		atomic_set_bit(&view_obj.flags, FLAGS_CC2_CONNECTION);
+		break;
+	default:
+		return -EIO;
+	}
+	return 0;
+}
+
+int view_set_snoop(snooper_mask_t vs)
+{
+	switch (vs) {
+	case 0:
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP1);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP2);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP3);
+		atomic_set_bit(&view_obj.flags, FLAGS_SNOOP0);
+		break;
+	case CC1_CHANNEL_BIT:
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP0);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP2);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP3);
+		atomic_set_bit(&view_obj.flags, FLAGS_SNOOP1);
+		break;
+	case CC2_CHANNEL_BIT:
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP0);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP1);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP3);
+		atomic_set_bit(&view_obj.flags, FLAGS_SNOOP2);
+		break;
+	case (CC1_CHANNEL_BIT | CC2_CHANNEL_BIT):
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP0);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP1);
+		atomic_clear_bit(&view_obj.flags, FLAGS_SNOOP2);
+		atomic_set_bit(&view_obj.flags, FLAGS_SNOOP3);
+		break;
+	default:
+		return -EIO;
+	}
+
+	view_set_connection(0);
+	return 0;
+}
+
+static void set_led(enum led_t led)
+{
+	switch (led) {
+	case LED_OFF:
+		gpio_pin_set_dt(&led0, 0);
+		gpio_pin_set_dt(&led1, 0);
+		gpio_pin_set_dt(&led2, 0);
+		break;
+	case LED_RED:
+		gpio_pin_set_dt(&led0, 1);
+		gpio_pin_set_dt(&led1, 0);
+		gpio_pin_set_dt(&led2, 0);
+		break;
+	case LED_GREEN:
+		gpio_pin_set_dt(&led0, 0);
+		gpio_pin_set_dt(&led1, 1);
+		gpio_pin_set_dt(&led2, 0);
+		break;
+	case LED_BLUE:
+		gpio_pin_set_dt(&led0, 0);
+		gpio_pin_set_dt(&led1, 0);
+		gpio_pin_set_dt(&led2, 1);
+		break;
+	}
+}
+
+static void snoop0_entry(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	set_led(LED_OFF);
+	vo->timeout = K_MSEC(1000);
+}
+
+static void snoop0_run(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	if (atomic_test_bit(&vo->flags, FLAGS_SNOOP1)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP1]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP2)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP2]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP3)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP3]);
+	}
+}
+
+static void snoop0_exit(void *o)
+{
+}
+
+static void snoop1_entry(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	vo->timeout = K_MSEC(1000);
+}
+
+static void snoop1_run(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	if (atomic_test_bit(&vo->flags, FLAGS_SNOOP0)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP0]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP2)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP2]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP3)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP3]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_NO_CONNECTION)) {
+		if (vo->toggle) {
+			set_led(LED_BLUE);
+			vo->toggle = false;
+		} else {
+			set_led(LED_OFF);
+			vo->toggle = true;
+		}
+	} else if (atomic_test_bit(&vo->flags, FLAGS_CC1_CONNECTION)) {
+		set_led(LED_GREEN);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_CC2_CONNECTION)) {
+		set_led(LED_RED);
+	}
+}
+
+static void snoop1_exit(void *o)
+{
+	set_led(LED_OFF);
+}
+
+static void snoop2_entry(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	vo->timeout = K_MSEC(500);
+}
+
+static void snoop2_run(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	if (atomic_test_bit(&vo->flags, FLAGS_SNOOP0)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP0]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP1)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP1]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP3)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP3]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_NO_CONNECTION)) {
+		if (vo->toggle) {
+			set_led(LED_BLUE);
+			vo->toggle = false;
+		} else {
+			set_led(LED_OFF);
+			vo->toggle = true;
+		}
+	} else if (atomic_test_bit(&vo->flags, FLAGS_CC1_CONNECTION)) {
+		set_led(LED_RED);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_CC2_CONNECTION)) {
+		set_led(LED_GREEN);
+	}
+}
+
+static void snoop2_exit(void *o)
+{
+	set_led(LED_OFF);
+}
+
+static void snoop3_entry(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	vo->timeout = K_MSEC(250);
+}
+
+static void snoop3_run(void *o)
+{
+	struct view_obj_t *vo = (struct view_obj_t *)o;
+
+	if (atomic_test_bit(&vo->flags, FLAGS_SNOOP0)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP0]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP1)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP1]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_SNOOP2)) {
+		smf_set_state(SMF_CTX(vo), &view_states[SNOOP2]);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_NO_CONNECTION)) {
+		if (vo->toggle) {
+			set_led(LED_BLUE);
+			vo->toggle = false;
+		} else {
+			set_led(LED_OFF);
+			vo->toggle = true;
+		}
+	} else if (atomic_test_bit(&vo->flags, FLAGS_CC1_CONNECTION)) {
+		set_led(LED_GREEN);
+	} else if (atomic_test_bit(&vo->flags, FLAGS_CC2_CONNECTION)) {
+		set_led(LED_GREEN);
+	}
+}
+
+static void snoop3_exit(void *o)
+{
+	set_led(LED_OFF);
+}
+
+static const struct smf_state view_states[] = {
+	[SNOOP0] = SMF_CREATE_STATE(snoop0_entry, snoop0_run, snoop0_exit),
+	[SNOOP1] = SMF_CREATE_STATE(snoop1_entry, snoop1_run, snoop1_exit),
+	[SNOOP2] = SMF_CREATE_STATE(snoop2_entry, snoop2_run, snoop2_exit),
+	[SNOOP3] = SMF_CREATE_STATE(snoop3_entry, snoop3_run, snoop3_exit),
+};
+
+int view_init(void)
+{
+	int ret;
+
+	if (!device_is_ready(led0.port)) {
+		return -EIO;
+	}
+
+	if (!device_is_ready(led1.port)) {
+		return -EIO;
+	}
+
+	if (!device_is_ready(led2.port)) {
+		return -EIO;
+	}
+
+	ret = gpio_pin_configure_dt(&led0, GPIO_OUTPUT_ACTIVE);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
+	if (ret < 0) {
+		return ret;
+	}
+
+	ret = gpio_pin_configure_dt(&led2, GPIO_OUTPUT_ACTIVE);
+	if (ret < 0) {
+		return ret;
+	}
+
+	view_obj.flags = ATOMIC_INIT(0);
+
+	smf_set_initial(SMF_CTX(&view_obj), &view_states[SNOOP0]);
+
+	while (1) {
+		smf_run_state(SMF_CTX(&view_obj));
+		k_sleep(view_obj.timeout);
+	}
+	return 0;
+}