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 <bnemec@google.com> Commit-Queue: Brian Nemec <bnemec@google.com> Reviewed-by: zhi cheng yuan <jasonyuan@chromium.org> Tested-by: zhi cheng yuan <jasonyuan@chromium.org> Tested-by: Brian Nemec <bnemec@google.com>
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; +}