diff --git a/dts/bindings/serial/zephyr,cp210x-uart.yaml b/dts/bindings/serial/zephyr,cp210x-uart.yaml new file mode 100644 index 0000000000000..9a7211f892f91 --- /dev/null +++ b/dts/bindings/serial/zephyr,cp210x-uart.yaml @@ -0,0 +1,28 @@ +# Copyright (c) 2025 Sergey Matsievskiy +# SPDX-License-Identifier: Apache-2.0 + +description: USB CP210X UART + +compatible: "zephyr,cp210x-uart" + +include: uart-controller.yaml + +on-bus: usb + +properties: + tx-fifo-size: + type: int + default: 1024 + description: | + Size of the virtual CP210X UART TX FIFO + + rx-fifo-size: + type: int + default: 1024 + description: | + Size of the virtual CP210X UART RX FIFO + + label: + description: | + The string defined by the label property is also used for the USB + interface string descriptor. diff --git a/include/zephyr/usb/class/usb_cp210x.h b/include/zephyr/usb/class/usb_cp210x.h new file mode 100644 index 0000000000000..afd984bab14fe --- /dev/null +++ b/include/zephyr/usb/class/usb_cp210x.h @@ -0,0 +1,438 @@ +/* usb_cp210x.h - USB CP210x public header */ + +/* + * Copyright (c) 2025 Sergey Matsievskiy + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +/** + * @file + * @brief USB CP210x public header + * + * Header uses information from Silicon Laboratories's + * CP210X Virtual COM Port Interface AN571 specification. + */ + +#ifndef ZEPHYR_INCLUDE_USB_CLASS_USB_CP210X_H_ +#define ZEPHYR_INCLUDE_USB_CLASS_USB_CP210X_H_ + +/** + * @brief CP210x Virtual COM Port Interface Control Commands + * @note AN571, 5 + */ +#define USB_CP210X_IFC_ENABLE 0x00 +#define USB_CP210X_SET_BAUDDIV 0x01 +#define USB_CP210X_GET_BAUDDIV 0x02 +#define USB_CP210X_SET_LINE_CTL 0x03 +#define USB_CP210X_GET_LINE_CTL 0x04 +#define USB_CP210X_SET_BREAK 0x05 +#define USB_CP210X_IMM_CHAR 0x06 +#define USB_CP210X_SET_MHS 0x07 +#define USB_CP210X_GET_MDMSTS 0x08 +#define USB_CP210X_SET_XON 0x09 +#define USB_CP210X_SET_XOFF 0x0A +#define USB_CP210X_SET_EVENTMASK 0x0B +#define USB_CP210X_GET_EVENTMASK 0x0C +#define USB_CP210X_SET_CHAR 0x0D +#define USB_CP210X_GET_CHARS 0x0E +#define USB_CP210X_GET_PROPS 0x0F +#define USB_CP210X_GET_COMM_STATUS 0x10 +#define USB_CP210X_RESET 0x11 +#define USB_CP210X_PURGE 0x12 +#define USB_CP210X_SET_FLOW 0x13 +#define USB_CP210X_GET_FLOW 0x14 +#define USB_CP210X_EMBED_EVENTS 0x15 +#define USB_CP210X_GET_EVENTSTATE 0x16 +#define USB_CP210X_SET_RECEIVE 0x17 +#define USB_CP210X_GET_RECEIVE 0x18 +#define USB_CP210X_SET_CHARS 0x19 +#define USB_CP210X_GET_BAUDRATE 0x1D +#define USB_CP210X_SET_BAUDRATE 0x1E +#define USB_CP210X_VENDOR_SPECIFIC 0xFF + +/** + * @brief CP210x Virtual COM Port Interface Line Control + * @note AN571, 5.1 IFC_ENABLE + */ +#define USB_CP210X_ENABLE 0x0001 +#define USB_CP210X_DISABLE 0x0000 + +/** + * @brief CP210x Virtual COM Port Baud Divisor + * @note AN571, 5.3 SET_BAUDDIV; 5.4 GET_BAUDDIV + */ +#define USB_CP210X_BAUDDIV_FREQ 3686400 + +/** + * @brief CP210x Virtual COM Port Interface Line Control + * @note AN571, 5.7 SET_LINE_CTL + */ +#define USB_CP210X_BITS_DATA 0xff00 +#define USB_CP210X_BITS_DATA_5 5 +#define USB_CP210X_BITS_DATA_6 6 +#define USB_CP210X_BITS_DATA_7 7 +#define USB_CP210X_BITS_DATA_8 8 + +#define USB_CP210X_BITS_PARITY 0x00f0 +#define USB_CP210X_BITS_PARITY_NONE 0 +#define USB_CP210X_BITS_PARITY_ODD 1 +#define USB_CP210X_BITS_PARITY_EVEN 2 +#define USB_CP210X_BITS_PARITY_MARK 3 +#define USB_CP210X_BITS_PARITY_SPACE 4 + +#define USB_CP210X_BITS_STOP 0x000f +#define USB_CP210X_BITS_STOP_1 0 +#define USB_CP210X_BITS_STOP_1_5 1 +#define USB_CP210X_BITS_STOP_2 2 + +union usb_cp210x_line_ctl { + struct { + uint8_t stop_bits: 4; + uint8_t parity: 4; + uint8_t word_length; + } __packed fld; + uint16_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_line_ctl) == sizeof(uint16_t), + "usb_cp210x_line_ctl inconsistent"); + +/** + * @brief CP210x Virtual COM Port Interface Line Control + * @note AN571, 5.20 SET_BREAK + */ +#define USB_CP210X_BREAK_ON 0x0001 +#define USB_CP210X_BREAK_OFF 0x0000 + +/** + * @brief CP210x Virtual COM Modem Status + * @note AN571, 5.9 SET_MHS + */ +union usb_cp210x_mhs { + struct { + bool dtr_state: 1; + bool rts_state: 1; + uint8_t: 6; + bool dtr_mask: 1; + bool rts_mask: 1; + } __packed fld; + uint16_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_mhs) == sizeof(uint16_t), + "usb_cp210x_mhs inconsistent"); + +/** + * @brief CP210x Virtual COM Modem Status Report + * @note AN571, 5.10 GET_MDMSTS + */ +union usb_cp210x_mdmsts { + struct { + bool dtr: 1; + bool rts: 1; + uint8_t: 2; + bool cts: 1; + bool dsr: 1; + bool ri: 1; + bool dcd: 1; + } __packed fld; + uint8_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_mdmsts) == sizeof(uint8_t), + "usb_cp210x_mhs inconsistent"); + +/** + * @brief CP210x Virtual COM Purge + * @note AN571, 5.27 PURGE + */ +union usb_cp210x_purge { + struct { + bool tx1: 1; + bool rx1: 1; + bool tx2: 1; + bool rx2: 1; + } __packed fld; + uint8_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_purge) == sizeof(uint8_t), + "usb_cp210x_purge inconsistent"); + +/** + * @brief CP210x Virtual COM Vendor Specific + */ +#define USB_CP210X_GET_FW_VER 0x000E +#define USB_CP210X_READ_2NCONFIG 0x000E +#define USB_CP210X_GET_FW_VER_2N 0x0010 +#define USB_CP210X_READ_LATCH 0x00C2 +#define USB_CP210X_GET_PARTNUM 0x370B +#define USB_CP210X_GET_PORTCONFIG 0x370C +#define USB_CP210X_GET_DEVICEMODE 0x3711 +#define USB_CP210X_WRITE_LATCH 0x37E1 + +/** + * @brief CP210x Virtual COM Vendor Part Number Definitions + */ +#define USB_CP210X_PARTNUM_CP2101 0x01 +#define USB_CP210X_PARTNUM_CP2102 0x02 +#define USB_CP210X_PARTNUM_CP2103 0x03 +#define USB_CP210X_PARTNUM_CP2104 0x04 +#define USB_CP210X_PARTNUM_CP2105 0x05 +#define USB_CP210X_PARTNUM_CP2108 0x08 +#define USB_CP210X_PARTNUM_CP2102N_QFN28 0x20 +#define USB_CP210X_PARTNUM_CP2102N_QFN24 0x21 +#define USB_CP210X_PARTNUM_CP2102N_QFN20 0x22 + +/** + * @brief CP210x Virtual COM Modem Status + * @note AN571, 6, Table 10 Control Handshake + */ +#define USB_CP210X_FCS_DTR_MASK_INACTIVE 0x0 +#define USB_CP210X_FCS_DTR_MASK_ACTIVE 0x1 +#define USB_CP210X_FCS_DTR_MASK_DEV 0x2 + +union usb_cp210x_ul_control_handshake { + struct { + uint8_t dtr_mask: 2; + bool: 1; + bool cts_hsk: 1; + bool dsr_hsk: 1; + bool dsd_hsk: 1; + bool dsr_sens: 1; + } __packed fld; + uint32_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_ul_control_handshake) == sizeof(uint32_t), + "usb_cp210x_mhs inconsistent"); + +/** + * @brief CP210x Virtual COM Modem Status + * @note AN571, 6, Table 11 Flow Replace + */ +#define USB_CP210X_RTS_MASK_INACTIVE 0x0 +#define USB_CP210X_RTS_MASK_ACTIVE 0x1 +#define USB_CP210X_RTS_MASK_RCV 0x2 +#define USB_CP210X_RTS_MASK_TRNS 0x3 + +union usb_cp210x_ul_flow_replace { + struct { + bool auto_transmit: 1; + bool auto_receive: 1; + bool error_char: 1; + bool null_stripping: 1; + bool break_char: 1; + bool: 1; + bool: 1; + uint8_t rts_mask: 1; + uint8_t: 7; + uint16_t: 15; + bool xoff_continue: 1; + } __packed fld; + uint32_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_ul_flow_replace) == sizeof(uint32_t), + "usb_cp210x_ul_flow_replace inconsistent"); + +/** + * @brief CP210x Virtual COM Modem Status + * @note AN571, 6, Table 9 Flow Control State + */ +struct usb_cp210x_flow_control { + union usb_cp210x_ul_control_handshake ulControlHandshake; + union usb_cp210x_ul_flow_replace ulFlowReplace; + uint32_t ulXonLimit; + uint32_t ulXoffLimit; +} __packed; + +BUILD_ASSERT(sizeof(struct usb_cp210x_flow_control) == 4 * sizeof(uint32_t), + "usb_cp210x_flow_control inconsistent"); + +/** + * @brief CP210x Virtual COM Event + * @note AN571, 5.15 SET_EVENTMASK + */ +union usb_cp210x_event { + struct { + bool ri_trailing_edge_occurred: 1; + bool: 1; + bool rcv_buf_80pct_full: 1; + uint8_t: 5; + bool char_received: 1; + bool special_char_received: 1; + bool transmit_queue_empty: 1; + bool cts_changed: 1; + bool dsr_changed: 1; + bool dsd_changed: 1; + bool line_break_received: 1; + bool line_status_error_occurred: 1; + } __packed fld; + uint16_t val; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_event) == sizeof(uint16_t), + "usb_cp210x_event inconsistent"); + +/** + * @brief CP210x Virtual COM Char + * @note AN571, 5.23 SET_CHAR + */ +struct usb_cp210x_char { + uint8_t char_idx; + uint8_t char_val; +} __packed; + +BUILD_ASSERT(sizeof(struct usb_cp210x_char) == sizeof(uint16_t), + "usb_cp210x_char inconsistent"); + +union usb_cp210x_char_vals { + struct { + uint8_t eof; + uint8_t error; + uint8_t brk; + uint8_t event; + uint8_t xon; + uint8_t xoff; + } __packed fld; + uint8_t chr[6]; +}; + +BUILD_ASSERT(sizeof(union usb_cp210x_char_vals) == 6 * sizeof(uint8_t), + "usb_cp210x_char_vals inconsistent"); + +/** + * @brief CP210x Virtual COM Communication Properties + * @note AN571, 6, Table 7 Communication Properties + */ +#define USB_CP210X_PROPS_BSD_VERSION 0x0100 +#define USB_CP210X_PROPS_SERVICE_MASK 1 +#define USB_CP210X_PROPS_MAX_BAUD 115200 +#define USB_CP210X_PROPS_PROVSUBTYPE_UNSPECIFIED 0 +#define USB_CP210X_PROPS_PROVSUBTYPE_RS232 1 +#define USB_CP210X_PROPS_PROVSUBTYPE_MODEM 6 + +struct usb_cp210x_props { + uint16_t wLength; + uint16_t bcdVersion; + uint32_t ulServiceMask; + uint32_t: 32; + uint32_t ulMaxTxQueue; + uint32_t ulMaxRxQueue; + uint32_t ulMaBaud; + uint32_t ulProvSubType; + union { + struct { + bool dtr_dsr_support: 1; + bool rts_cts_support: 1; + bool dcd_support: 1; + bool can_check_parity: 1; + bool xon_xoff_support: 1; + bool can_set_xon_xoff_characters: 1; + bool: 1; + bool: 1; + bool can_set_special_characters: 1; + bool bit16_mode_supports: 1; + } __packed fld; + uint32_t val; + } ulProvCapabilities; + union { + struct { + bool can_set_parity_type: 1; + bool can_set_baud: 1; + bool can_set_number_of_data_bits: 1; + bool can_set_stop_bits: 1; + bool can_set_handshaking: 1; + bool can_set_parity_checking: 1; + bool can_set_carrier_detect_checking: 1; + } __packed fld; + uint32_t val; + } ulSettableParams; + union { + struct { + bool baud_75: 1; + bool baud_110: 1; + bool baud_134_5: 1; + bool baud_150: 1; + bool baud_300: 1; + bool baud_600: 1; + bool baud_1200: 1; + bool baud_1800: 1; + bool baud_2400: 1; + bool baud_4800: 1; + bool baud_7200: 1; + bool baud_9600: 1; + bool baud_14400: 1; + bool baud_19200: 1; + bool baud_38400: 1; + bool baud_56000: 1; + bool baud_128000: 1; + bool baud_115200: 1; + bool baud_57600: 1; + } __packed fld; + uint32_t val; + } ulSettableBaud; + union { + struct { + bool data_bits_5: 1; + bool data_bits_6: 1; + bool data_bits_7: 1; + bool data_bits_8: 1; + bool data_bits_16: 1; + bool data_bits_16_extended: 1; + } __packed fld; + uint32_t val; + } wSettableData; + uint32_t ulCurrentTxQueue; + uint32_t ulCurrentRxQueue; + uint32_t: 32; + uint32_t: 32; + char uniProvName[15]; +} __packed; + +BUILD_ASSERT(sizeof(struct usb_cp210x_props) == 75 * sizeof(uint8_t), + "usb_cp210x_props inconsistent"); + +/** + * @brief CP210x Virtual COM Serial Status Response + * @note AN571, 6, Table 8 Serial Status Response + */ +struct usb_cp210x_serial_status { + union { + struct { + bool break_event: 1; + bool framing_error: 1; + bool hardware_overrun: 1; + bool queue_overrun: 1; + bool parity_error: 1; + } __packed fld; + uint32_t val; + } ulErrors; + union { + struct { + bool wait_cts: 1; + bool wait_dsr: 1; + bool wait_dsd: 1; + bool wait_xon: 1; + bool wait_xoff: 1; + bool wait_break: 1; + bool wait_dsr_rcv: 1; + } __packed fld; + uint32_t val; + } ulHoldReasons; + uint32_t ulAmountInInQueue; + uint32_t ulAmountInOutQueue; + uint8_t bEofReceived; + uint8_t bWaitForImmediate; + uint8_t: 8; +} __packed; + +BUILD_ASSERT(sizeof(struct usb_cp210x_serial_status) == 19 * sizeof(uint8_t), + "usb_cp210x_serial_status inconsistent"); + +#endif /* ZEPHYR_INCLUDE_USB_CLASS_USB_CP210X_H_ */ diff --git a/samples/subsys/usb/cp210x/CMakeLists.txt b/samples/subsys/usb/cp210x/CMakeLists.txt new file mode 100644 index 0000000000000..c1e2330298649 --- /dev/null +++ b/samples/subsys/usb/cp210x/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(cp210x) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/subsys/usb/cp210x/Kconfig b/samples/subsys/usb/cp210x/Kconfig new file mode 100644 index 0000000000000..10b70ff328c34 --- /dev/null +++ b/samples/subsys/usb/cp210x/Kconfig @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Sergey Matsievskiy +# SPDX-License-Identifier: Apache-2.0 + +# Source common USB sample options used to initialize new experimental USB +# device stack. The scope of these options is limited to USB samples in project +# tree, you cannot use them in your own application. +source "samples/subsys/usb/common/Kconfig.sample_usbd" + +source "Kconfig.zephyr" diff --git a/samples/subsys/usb/cp210x/README.rst b/samples/subsys/usb/cp210x/README.rst new file mode 100644 index 0000000000000..aef8938c3b0f3 --- /dev/null +++ b/samples/subsys/usb/cp210x/README.rst @@ -0,0 +1,93 @@ +.. zephyr:code-sample:: usb-cp210x + :name: USB CP210x UART sample + :relevant-api: usbd_api uart_interface + + Use USB CP210x UART driver to implement a serial port echo. + +Overview +******** + +This sample app demonstrates use of a USB CP210x virtual COM port +driver provided by the Zephyr project. +Received data from the serial port is echoed back to the same port +provided by this driver. +This sample can be found under :zephyr_file:`samples/subsys/usb/cp210x` in the +Zephyr project tree. + +Requirements +************ + +This project requires an USB device driver, which is available for multiple +boards supported in Zephyr. +The operating system needs to be configured to bind PID-VID pairs to the +appropriate driver. For example, on Linux, to bind PID 0x2fe3 VID 0x0002 +to the cp210x driver, one needs to create a file +file:`/etc/udev/rules.d/99-zephyr-cp210x.rules` with the following lines + +.. code-block:: udev + + ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="2fe3", ATTRS{idProduct}=="0002", \ + RUN+="/sbin/modprobe cp210x", \ + RUN+="/bin/sh -c 'echo 2fe3 0002 > /sys/bus/usb-serial/drivers/cp210x/new_id'" + + +Building and Running +******************** + +Reel Board +=========== + +To see the console output of the app, open a serial port emulator and +attach it to the USB to TTL Serial cable. Build and flash the project: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/usb/cdc_acm + :board: reel_board + :goals: flash + :compact: + +Running +======= + +Plug the board into a host device, for example, a PC running Linux. +The board will be detected as shown by the Linux dmesg command: + +.. code-block:: console + + usb 1-1: new full-speed USB device number 43 using xhci_hcd + usb 1-1: New USB device found, idVendor=1ba4, idProduct=0002, bcdDevice= 4.02 + usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3 + usb 1-1: Product: CP210X serial backend + usb 1-1: Manufacturer: Zephyr Project + usb 1-1: SerialNumber: 000000000000000000000000 + cp210x 1-1:1.0: cp210x converter detected + usb 1-1: cp210x converter now attached to ttyUSB1 + +The app prints on serial output (UART1), used for the console: + +.. code-block:: console + + Wait for DTR + +Open a serial port emulator, for example minicom +and attach it to detected CP210x device: + +.. code-block:: console + + minicom --device /dev/ttyUSB1 + +The app should respond on serial output with: + +.. code-block:: console + + DTR set, start test + Baudrate detected: 115200 + +And on ttyUSB device, provided by zephyr USB device stack: + +.. code-block:: console + + Send characters to the UART device + Characters read: + +The characters entered in serial port emulator will be echoed back. diff --git a/samples/subsys/usb/cp210x/app.overlay b/samples/subsys/usb/cp210x/app.overlay new file mode 100644 index 0000000000000..d947b4349eefa --- /dev/null +++ b/samples/subsys/usb/cp210x/app.overlay @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2025 Sergey Matsievskiy + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&zephyr_udc0 { + cp210x_uart0 { + compatible = "zephyr,cp210x-uart"; + label = "Zephyr USB CP210x"; + }; +}; diff --git a/samples/subsys/usb/cp210x/prj.conf b/samples/subsys/usb/cp210x/prj.conf new file mode 100644 index 0000000000000..11eed684a7eec --- /dev/null +++ b/samples/subsys/usb/cp210x/prj.conf @@ -0,0 +1,14 @@ +CONFIG_USB_DEVICE_STACK_NEXT=y + +CONFIG_STDOUT_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_LINE_CTRL=y +CONFIG_USBD_CP210X_CLASS=y +CONFIG_CP210X_SERIAL_INITIALIZE_AT_BOOT=y + +CONFIG_LOG=y +CONFIG_USBD_LOG_LEVEL_ERR=y +CONFIG_UDC_DRIVER_LOG_LEVEL_ERR=y +CONFIG_USBD_CP210X_LOG_LEVEL_OFF=y + +CONFIG_SAMPLE_USBD_PRODUCT="USBD CP210X sample" diff --git a/samples/subsys/usb/cp210x/sample.yaml b/samples/subsys/usb/cp210x/sample.yaml new file mode 100644 index 0000000000000..40963a987c85c --- /dev/null +++ b/samples/subsys/usb/cp210x/sample.yaml @@ -0,0 +1,2 @@ +sample: + name: CP210x USB diff --git a/samples/subsys/usb/cp210x/src/main.c b/samples/subsys/usb/cp210x/src/main.c new file mode 100644 index 0000000000000..548005083d2a6 --- /dev/null +++ b/samples/subsys/usb/cp210x/src/main.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025 Sergey Matsievskiy + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +LOG_MODULE_REGISTER(cp210x_echo, LOG_LEVEL_INF); + +const struct device *const uart_dev = DEVICE_DT_GET_ONE(zephyr_cp210x_uart); + +#define RING_BUF_SIZE 1024 +uint8_t ring_buffer[RING_BUF_SIZE]; + +struct ring_buf ringbuf; + +static bool rx_throttled; + +static void interrupt_handler(const struct device *dev, void *user_data) +{ + ARG_UNUSED(user_data); + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (!rx_throttled && uart_irq_rx_ready(dev)) { + int recv_len, rb_len; + uint8_t buffer[64]; + size_t len = MIN(ring_buf_space_get(&ringbuf), + sizeof(buffer)); + + if (len == 0) { + /* Throttle because ring buffer is full */ + uart_irq_rx_disable(dev); + rx_throttled = true; + continue; + } + + recv_len = uart_fifo_read(dev, buffer, len); + if (recv_len < 0) { + LOG_ERR("Failed to read UART FIFO"); + recv_len = 0; + } + + rb_len = ring_buf_put(&ringbuf, buffer, recv_len); + if (rb_len < recv_len) { + LOG_ERR("Drop %u bytes", recv_len - rb_len); + } + + LOG_DBG("tty fifo -> ringbuf %d bytes", rb_len); + if (rb_len) { + uart_irq_tx_enable(dev); + } + } + + if (uart_irq_tx_ready(dev)) { + uint8_t buffer[64]; + int rb_len, send_len; + + rb_len = ring_buf_get(&ringbuf, buffer, sizeof(buffer)); + if (!rb_len) { + LOG_DBG("Ring buffer empty, disable TX IRQ"); + uart_irq_tx_disable(dev); + continue; + } + + if (rx_throttled) { + uart_irq_rx_enable(dev); + rx_throttled = false; + } + + send_len = uart_fifo_fill(dev, buffer, rb_len); + if (send_len < rb_len) { + LOG_ERR("Drop %d bytes", rb_len - send_len); + } + + LOG_DBG("ringbuf -> tty fifo %d bytes", send_len); + } + } +} + +int main(void) +{ + uint32_t dtr = 0; + + if (!device_is_ready(uart_dev)) { + LOG_ERR("CDC ACM device not ready"); + return 0; + } + + ring_buf_init(&ringbuf, sizeof(ring_buffer), ring_buffer); + + LOG_INF("Wait for DTR"); + while (!dtr) { + uart_line_ctrl_get(uart_dev, UART_LINE_CTRL_DTR, &dtr); + k_sleep(K_MSEC(100)); + } + LOG_INF("DTR set"); + + /* Wait 100ms for the host to do all settings */ + k_msleep(100); + + uart_irq_callback_set(uart_dev, interrupt_handler); + /* Enable rx interrupts */ + uart_irq_rx_enable(uart_dev); + + return 0; +} diff --git a/scripts/build/check_init_priorities.py b/scripts/build/check_init_priorities.py index 63ae4aa95708d..adcc7a8e2b2f6 100755 --- a/scripts/build/check_init_priorities.py +++ b/scripts/build/check_init_priorities.py @@ -47,6 +47,7 @@ # device controller, the logical connection is established after USB # device support is enabled. "zephyr,cdc-acm-uart", + "zephyr,cp210x-uart", ]) # The offset of the init pointer in "struct device", in number of pointers. diff --git a/subsys/usb/device_next/CMakeLists.txt b/subsys/usb/device_next/CMakeLists.txt index 717cdb9da37b0..db277bce75284 100644 --- a/subsys/usb/device_next/CMakeLists.txt +++ b/subsys/usb/device_next/CMakeLists.txt @@ -99,6 +99,11 @@ zephyr_library_sources_ifdef( class/usbd_dfu_flash.c ) +zephyr_library_sources_ifdef( + CONFIG_USBD_CP210X_CLASS + class/usbd_cp210x.c +) + zephyr_linker_sources_ifdef( CONFIG_USBD_DFU SECTIONS class/usbd_dfu.ld diff --git a/subsys/usb/device_next/Kconfig b/subsys/usb/device_next/Kconfig index 5e7b02e5ee713..5fa94c8f1abdf 100644 --- a/subsys/usb/device_next/Kconfig +++ b/subsys/usb/device_next/Kconfig @@ -107,5 +107,6 @@ config USBD_HWINFO_DEVID_LENGTH rsource "class/Kconfig" rsource "app/Kconfig.cdc_acm_serial" +rsource "app/Kconfig.cp210x_serial" endif # USB_DEVICE_STACK_NEXT diff --git a/subsys/usb/device_next/app/CMakeLists.txt b/subsys/usb/device_next/app/CMakeLists.txt index 116d422def3e4..beea39de6f24a 100644 --- a/subsys/usb/device_next/app/CMakeLists.txt +++ b/subsys/usb/device_next/app/CMakeLists.txt @@ -2,3 +2,4 @@ # SPDX-License-Identifier: Apache-2.0 zephyr_sources_ifdef(CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT cdc_acm_serial.c) +zephyr_sources_ifdef(CONFIG_CP210X_SERIAL_INITIALIZE_AT_BOOT cp210x_serial.c) diff --git a/subsys/usb/device_next/app/Kconfig.cp210x_serial b/subsys/usb/device_next/app/Kconfig.cp210x_serial new file mode 100644 index 0000000000000..df7230290d617 --- /dev/null +++ b/subsys/usb/device_next/app/Kconfig.cp210x_serial @@ -0,0 +1,66 @@ +# Copyright (c) 2025 Sergey Matsievskiy +# SPDX-License-Identifier: Apache-2.0 + +# This file contains Kconfig options and defaults for configuring the USB +# device stack and CP210X instance to be used as the default serial backend +# for logging or shell. + + +menuconfig CP210X_SERIAL_INITIALIZE_AT_BOOT + bool "Initialize USB device and CP210X UART at boot" + depends on USBD_CP210X_CLASS + help + This is intended for use with cp210x-snippet or as a default serial + backend only in applications where no other USB features are + required, configured, and enabled. + +if CP210X_SERIAL_INITIALIZE_AT_BOOT + +config CP210X_SERIAL_ENABLE_AT_BOOT + bool "USB device will be enabled at boot" + default y + help + When disabled, the application is responsible for enabling/disabling + the USB device. + +config CP210X_SERIAL_MANUFACTURER_STRING + string "USB device manufacturer string descriptor" + default "Zephyr Project" + help + USB device manufacturer string descriptor. + +config CP210X_SERIAL_PRODUCT_STRING + string "USB device product string descriptor" + default "CP210X serial backend" + help + USB device product string descriptor. + +config CP210X_SERIAL_VID + hex "USB device Vendor ID" + default 0x2fe3 + help + You must use your own VID for samples and applications outside of + Zephyr Project. Additional configuration of the operation system + for proper device binding (i.e. udev) may be required. + +config CP210X_SERIAL_PID + hex "USB device Product ID" + default 0x0002 + help + You must use your own PID for samples and applications outside of + Zephyr Project. Additional configuration of the operation system + for proper device binding (i.e. udev) may be required. + +config CP210X_SERIAL_SELF_POWERED + bool "USB device Self-powered attribute" + help + Set the Self-powered attribute in the configuration. + +config CP210X_SERIAL_MAX_POWER + int "USB device bMaxPower value" + default 125 + range 0 250 + help + bMaxPower value in the configuration in 2 mA units. + +endif #CP210X_SERIAL_INITIALIZE_AT_BOOT diff --git a/subsys/usb/device_next/app/cp210x_serial.c b/subsys/usb/device_next/app/cp210x_serial.c new file mode 100644 index 0000000000000..1b4f6927f23cc --- /dev/null +++ b/subsys/usb/device_next/app/cp210x_serial.c @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025 Sergey Matsievskiy + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(cp210x_serial, CONFIG_USBD_LOG_LEVEL); + +/* + * This is intended for use with cp210x-snippet or as a default serial backend + * only in applications where no other USB features are required, configured, + * and enabled. This code only registers the first CP210X instance. + */ + +USBD_DEVICE_DEFINE(cp210x_serial, + DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), + CONFIG_CP210X_SERIAL_VID, CONFIG_CP210X_SERIAL_PID); + +USBD_DESC_LANG_DEFINE(cp210x_serial_lang); +USBD_DESC_MANUFACTURER_DEFINE(cp210x_serial_mfr, CONFIG_CP210X_SERIAL_MANUFACTURER_STRING); +USBD_DESC_PRODUCT_DEFINE(cp210x_serial_product, CONFIG_CP210X_SERIAL_PRODUCT_STRING); +IF_ENABLED(CONFIG_HWINFO, (USBD_DESC_SERIAL_NUMBER_DEFINE(cp210x_serial_sn))); + +USBD_DESC_CONFIG_DEFINE(fs_cfg_desc, "FS Configuration"); + +static const uint8_t attributes = IS_ENABLED(CONFIG_CP210X_SERIAL_SELF_POWERED) ? + USB_SCD_SELF_POWERED : 0; + +USBD_CONFIGURATION_DEFINE(cp210x_serial_fs_config, + attributes, + CONFIG_CP210X_SERIAL_MAX_POWER, &fs_cfg_desc); + +static int register_cp210x_0(struct usbd_context *const uds_ctx, + const enum usbd_speed speed) +{ + struct usbd_config_node *cfg_nd; + int err; + + cfg_nd = &cp210x_serial_fs_config; + + err = usbd_add_configuration(uds_ctx, speed, cfg_nd); + if (err) { + LOG_ERR("Failed to add configuration"); + return err; + } + + err = usbd_register_class(&cp210x_serial, "cp210x_0", speed, 1); + if (err) { + LOG_ERR("Failed to register classes"); + return err; + } + + return usbd_device_set_code_triple(uds_ctx, speed, 0, 0, 0); +} + +static int cp210x_serial_init_device(void) +{ + int err; + + err = usbd_add_descriptor(&cp210x_serial, &cp210x_serial_lang); + if (err) { + LOG_ERR("Failed to initialize %s (%d)", "language descriptor", err); + return err; + } + + err = usbd_add_descriptor(&cp210x_serial, &cp210x_serial_mfr); + if (err) { + LOG_ERR("Failed to initialize %s (%d)", "manufacturer descriptor", err); + return err; + } + + err = usbd_add_descriptor(&cp210x_serial, &cp210x_serial_product); + if (err) { + LOG_ERR("Failed to initialize %s (%d)", "product descriptor", err); + return err; + } + + IF_ENABLED(CONFIG_HWINFO, ( + err = usbd_add_descriptor(&cp210x_serial, &cp210x_serial_sn); + )) + if (err) { + LOG_ERR("Failed to initialize %s (%d)", "SN descriptor", err); + return err; + } + + err = register_cp210x_0(&cp210x_serial, USBD_SPEED_FS); + if (err) { + return err; + } + + err = usbd_init(&cp210x_serial); + if (err) { + LOG_ERR("Failed to initialize %s (%d)", "device support", err); + return err; + } + + if (IS_ENABLED(CONFIG_CP210X_SERIAL_ENABLE_AT_BOOT)) { + err = usbd_enable(&cp210x_serial); + if (err) { + LOG_ERR("Failed to enable %s (%d)", "device support", err); + return err; + } + } + + return 0; +} + +SYS_INIT(cp210x_serial_init_device, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/subsys/usb/device_next/class/Kconfig b/subsys/usb/device_next/class/Kconfig index d3d9a946488e3..b7d05ec399c5c 100644 --- a/subsys/usb/device_next/class/Kconfig +++ b/subsys/usb/device_next/class/Kconfig @@ -13,3 +13,4 @@ rsource "Kconfig.hid" rsource "Kconfig.midi2" rsource "Kconfig.dfu" rsource "Kconfig.uvc" +rsource "Kconfig.cp210x" diff --git a/subsys/usb/device_next/class/Kconfig.cp210x b/subsys/usb/device_next/class/Kconfig.cp210x new file mode 100644 index 0000000000000..8a9fd5c680636 --- /dev/null +++ b/subsys/usb/device_next/class/Kconfig.cp210x @@ -0,0 +1,45 @@ +# Copyright (c) 2025 Sergey Matsievskiy +# +# SPDX-License-Identifier: Apache-2.0 + +config USBD_CP210X_CLASS + bool "USB CP210X implementation [EXPERIMENTAL]" + depends on SERIAL + depends on DT_HAS_ZEPHYR_CP210X_UART_ENABLED + select SERIAL_HAS_DRIVER + select SERIAL_SUPPORT_INTERRUPT + select RING_BUFFER + select UART_INTERRUPT_DRIVEN + default y + help + USB device CP210X class implementation. + +if USBD_CP210X_CLASS + +config USBD_CP210X_WORKQUEUE + bool "Use dedicated workqueue in CP210X" + help + Use the dedicated queue in CP210X implementation if the systemwork + queue cannot be used due to performance issues or other conflicts. + +config USBD_CP210X_STACK_SIZE + int "USB CP210X workqueue stack size" + depends on USBD_CP210X_WORKQUEUE + default 1024 + help + USB CDC ACM workqueue stack size. + +config USBD_CP210X_BUF_POOL + bool "Use dedicated buffer pool" + default y if !USBD_MAX_SPEED_FULL + help + Use a dedicated buffer pool whose size is based on the number of + CP210X instances and the size of the bulk endpoints. When + disabled, the implementation uses the UDC driver's pool. + +module = USBD_CP210X +module-str = usbd cp210x +default-count = 1 +source "subsys/logging/Kconfig.template.log_config" + +endif diff --git a/subsys/usb/device_next/class/usbd_cp210x.c b/subsys/usb/device_next/class/usbd_cp210x.c new file mode 100644 index 0000000000000..31607365bdbff --- /dev/null +++ b/subsys/usb/device_next/class/usbd_cp210x.c @@ -0,0 +1,1392 @@ +/* + * Copyright (c) 2025 Sergey Matsievskiy + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_cp210x_uart + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "usbd_msg.h" + +#include + +LOG_MODULE_REGISTER(usbd_cp210x, CONFIG_USBD_CP210X_LOG_LEVEL); + +#define CP210X_DEFAULT_LINECODING \ + .baudrate = 115200, \ + .parity = UART_CFG_PARITY_NONE, \ + .stop_bits = UART_CFG_STOP_BITS_1, \ + .data_bits = UART_CFG_DATA_BITS_8, \ + .flow_ctrl = UART_CFG_FLOW_CTRL_NONE + +enum { + CP210X_CLASS_ENABLED, + CP210X_CLASS_SUSPENDED, + CP210X_IRQ_RX_ENABLED, + CP210X_IRQ_TX_ENABLED, + CP210X_RX_FIFO_BUSY, + CP210X_TX_FIFO_BUSY, +}; + +struct cp210x_uart_fifo { + struct ring_buf *rb; + bool irq; + bool altered; +}; + +struct usbd_cp210x_desc { + struct usb_if_descriptor if0; + struct usb_ep_descriptor if0_in_ep; + struct usb_ep_descriptor if0_out_ep; + struct usb_desc_header nil_desc; +}; + +struct cp210x_uart_config { + /* Pointer to the associated USBD class node */ + struct usbd_class_data *c_data; + /* Pointer to the interface description node or NULL */ + struct usbd_desc_node *const if_desc_data; + /* Pointer to the class interface descriptors */ + struct usbd_cp210x_desc *const desc; + const struct usb_desc_header **const fs_desc; +}; + +struct cp210x_uart_data { + const struct device *dev; + /* Serial state bitmap */ + uint16_t serial_state; + /* UART actual configuration */ + struct uart_config uart_cfg; + /* UART actual RTS state */ + bool line_state_rts; + /* UART actual DTR state */ + bool line_state_dtr; + /* When flow_ctrl is set, poll out is blocked when the buffer is full, + * roughly emulating flow control. + */ + bool flow_ctrl; + bool zlp_needed; + /* UART API IRQ callback */ + uart_irq_callback_user_data_t cb; + /* UART API user callback data */ + void *cb_data; + /* UART API IRQ callback work */ + struct k_work irq_cb_work; + struct cp210x_uart_fifo rx_fifo; + struct cp210x_uart_fifo tx_fifo; + /* USBD CDC ACM TX fifo work */ + struct k_work_delayable tx_fifo_work; + /* USBD CDC ACM RX fifo work */ + struct k_work rx_fifo_work; + atomic_t state; + struct k_spinlock lock; +}; + +static void cp210x_irq_rx_enable(const struct device *dev); + +#if CONFIG_USBD_CP210X_BUF_POOL +UDC_BUF_POOL_DEFINE(cp210x_ep_pool, + DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) * 2, + USBD_MAX_BULK_MPS, sizeof(struct udc_buf_info), NULL); + +static struct net_buf *cp210x_buf_alloc(struct usbd_class_data *const c_data, const uint8_t ep) +{ + ARG_UNUSED(c_data); + struct net_buf *buf = NULL; + struct udc_buf_info *bi; + + buf = net_buf_alloc(&cp210x_ep_pool, K_NO_WAIT); + if (!buf) { + return NULL; + } + + bi = udc_get_buf_info(buf); + bi->ep = ep; + + return buf; +} +#else +/* + * The required buffer is 128 bytes per instance on a full-speed device. Use + * common (UDC) buffer, as this results in a smaller footprint. + */ +static struct net_buf *cp210x_buf_alloc(struct usbd_class_data *const c_data, const uint8_t ep) +{ + return usbd_ep_buf_alloc(c_data, ep, USBD_MAX_BULK_MPS); +} +#endif /* CONFIG_USBD_CP210X_BUF_POOL */ + +#if CONFIG_USBD_CP210X_WORKQUEUE +static struct k_work_q cp210x_work_q; +static K_KERNEL_STACK_DEFINE(cp210x_stack, + CONFIG_USBD_CP210X_STACK_SIZE); + +static int usbd_cp210x_init_wq(void) +{ + k_work_queue_init(&cp210x_work_q); + k_work_queue_start(&cp210x_work_q, cp210x_stack, + K_KERNEL_STACK_SIZEOF(cp210x_stack), + CONFIG_SYSTEM_WORKQUEUE_PRIORITY, NULL); + k_thread_name_set(&cp210x_work_q.thread, "cp210x_work_q"); + + return 0; +} + +SYS_INIT(usbd_cp210x_init_wq, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); + +static ALWAYS_INLINE int cp210x_work_submit(struct k_work *work) +{ + return k_work_submit_to_queue(&cp210x_work_q, work); +} + +static ALWAYS_INLINE int cp210x_work_schedule(struct k_work_delayable *work, k_timeout_t delay) +{ + return k_work_schedule_for_queue(&cp210x_work_q, work, delay); +} + +static ALWAYS_INLINE bool check_wq_ctx(const struct device *dev) +{ + return k_current_get() == k_work_queue_thread_get(&cp210x_work_q); +} + +#else /* Use system workqueue */ + +static ALWAYS_INLINE int cp210x_work_submit(struct k_work *work) +{ + return k_work_submit(work); +} + +static ALWAYS_INLINE int cp210x_work_schedule(struct k_work_delayable *work, k_timeout_t delay) +{ + return k_work_schedule(work, delay); +} + +#define check_wq_ctx(dev) true + +#endif /* CONFIG_USBD_CP210X_WORKQUEUE */ + +static uint8_t cp210x_get_bulk_in(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct cp210x_uart_config *cfg = dev->config; + struct usbd_cp210x_desc *desc = cfg->desc; + + return desc->if0_in_ep.bEndpointAddress; +} + +static uint8_t cp210x_get_bulk_out(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct cp210x_uart_config *cfg = dev->config; + struct usbd_cp210x_desc *desc = cfg->desc; + + return desc->if0_out_ep.bEndpointAddress; +} + +static size_t cp210x_get_bulk_mps(struct usbd_class_data *const c_data) +{ + return 64U; +} + +static int usbd_cp210x_request(struct usbd_class_data *const c_data, + struct net_buf *buf, int err) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + struct udc_buf_info *bi; + + bi = udc_get_buf_info(buf); + if (err) { + if (err == -ECONNABORTED) { + LOG_WRN("request ep 0x%02x, len %u cancelled", + bi->ep, buf->len); + } else { + LOG_ERR("request ep 0x%02x, len %u failed", + bi->ep, buf->len); + } + + if (bi->ep == cp210x_get_bulk_out(c_data)) { + atomic_clear_bit(&data->state, CP210X_RX_FIFO_BUSY); + } + + if (bi->ep == cp210x_get_bulk_in(c_data)) { + atomic_clear_bit(&data->state, CP210X_TX_FIFO_BUSY); + } + + goto ep_request_error; + } + + if (bi->ep == cp210x_get_bulk_out(c_data)) { + /* RX transfer completion */ + size_t done; + + LOG_HEXDUMP_INF(buf->data, buf->len, ""); + done = ring_buf_put(data->rx_fifo.rb, buf->data, buf->len); + if (done && data->cb) { + cp210x_work_submit(&data->irq_cb_work); + } + + atomic_clear_bit(&data->state, CP210X_RX_FIFO_BUSY); + cp210x_work_submit(&data->rx_fifo_work); + } + + if (bi->ep == cp210x_get_bulk_in(c_data)) { + /* TX transfer completion */ + if (data->cb) { + cp210x_work_submit(&data->irq_cb_work); + } + + atomic_clear_bit(&data->state, CP210X_TX_FIFO_BUSY); + + if (!ring_buf_is_empty(data->tx_fifo.rb)) { + /* Queue pending TX data on IN endpoint */ + cp210x_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } + } + +ep_request_error: + return usbd_ep_buf_free(uds_ctx, buf); +} + +static void usbd_cp210x_update(struct usbd_class_data *const c_data, + uint8_t iface, uint8_t alternate) +{ + LOG_DBG("New configuration, interface %u alternate %u", + iface, alternate); +} + +static void usbd_cp210x_enable(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + + atomic_set_bit(&data->state, CP210X_CLASS_ENABLED); + LOG_INF("Configuration enabled"); + + if (atomic_test_bit(&data->state, CP210X_IRQ_RX_ENABLED)) { + cp210x_irq_rx_enable(dev); + } + + if (atomic_test_bit(&data->state, CP210X_IRQ_TX_ENABLED)) { + if (ring_buf_space_get(data->tx_fifo.rb)) { + /* Raise TX ready interrupt */ + cp210x_work_submit(&data->irq_cb_work); + } else { + /* Queue pending TX data on IN endpoint */ + cp210x_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } + } +} + +static void usbd_cp210x_disable(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + + atomic_clear_bit(&data->state, CP210X_CLASS_ENABLED); + atomic_clear_bit(&data->state, CP210X_CLASS_SUSPENDED); + LOG_INF("Configuration disabled"); +} + +static void usbd_cp210x_suspended(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + + /* FIXME: filter stray suspended events earlier */ + atomic_set_bit(&data->state, CP210X_CLASS_SUSPENDED); +} + +static void usbd_cp210x_resumed(struct usbd_class_data *const c_data) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + + atomic_clear_bit(&data->state, CP210X_CLASS_SUSPENDED); +} + +static void *usbd_cp210x_get_desc(struct usbd_class_data *const c_data, + const enum usbd_speed speed) +{ + const struct device *dev = usbd_class_get_private(c_data); + const struct cp210x_uart_config *cfg = dev->config; + + return cfg->fs_desc; +} + +static int usbd_cp210x_cth(struct usbd_class_data *const c_data, + const struct usb_setup_packet *const setup, + struct net_buf *const buf) +{ + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + struct uart_config *const cfg = &data->uart_cfg; + + switch (setup->bRequest) { + case USB_CP210X_GET_BAUDDIV: + net_buf_add_le16(buf, USB_CP210X_BAUDDIV_FREQ / cfg->baudrate); + return 0; + case USB_CP210X_GET_BAUDRATE: + net_buf_add_le32(buf, cfg->baudrate); + return 0; + case USB_CP210X_GET_LINE_CTL: { + union usb_cp210x_line_ctl lctrl = {0}; + + switch (cfg->stop_bits) { + case UART_CFG_STOP_BITS_1: + lctrl.fld.stop_bits = USB_CP210X_BITS_STOP_1; + break; + case UART_CFG_STOP_BITS_1_5: + lctrl.fld.stop_bits = USB_CP210X_BITS_STOP_1_5; + break; + case UART_CFG_STOP_BITS_2: + lctrl.fld.stop_bits = USB_CP210X_BITS_STOP_2; + break; + default: + break; + } + + switch (cfg->stop_bits) { + case UART_CFG_PARITY_NONE: + lctrl.fld.parity = USB_CP210X_BITS_PARITY_NONE; + break; + case UART_CFG_PARITY_ODD: + lctrl.fld.parity = USB_CP210X_BITS_PARITY_ODD; + break; + case UART_CFG_PARITY_EVEN: + lctrl.fld.parity = USB_CP210X_BITS_PARITY_EVEN; + break; + case UART_CFG_PARITY_MARK: + lctrl.fld.parity = USB_CP210X_BITS_PARITY_MARK; + break; + case UART_CFG_PARITY_SPACE: + lctrl.fld.parity = USB_CP210X_BITS_PARITY_SPACE; + break; + default: + break; + } + + switch (cfg->stop_bits) { + case UART_CFG_DATA_BITS_5: + lctrl.fld.stop_bits = USB_CP210X_BITS_DATA_5; + break; + case UART_CFG_DATA_BITS_6: + lctrl.fld.stop_bits = USB_CP210X_BITS_DATA_6; + break; + case UART_CFG_DATA_BITS_7: + lctrl.fld.stop_bits = USB_CP210X_BITS_DATA_7; + break; + case UART_CFG_DATA_BITS_8: + lctrl.fld.stop_bits = USB_CP210X_BITS_DATA_8; + break; + default: + break; + } + + net_buf_add_le16(buf, lctrl.val); + return 0; + } + case USB_CP210X_GET_MDMSTS: { + union usb_cp210x_mdmsts mdmsts = { + .fld = { + .dtr = data->line_state_dtr, + .rts = data->line_state_rts, + .cts = true, + .dsr = true, + }, + }; + + net_buf_add_u8(buf, mdmsts.val); + return 0; + } + case USB_CP210X_GET_FLOW: { + struct usb_cp210x_flow_control flow_ctl = { + .ulControlHandshake = { + .fld = { + .dtr_mask = USB_CP210X_FCS_DTR_MASK_INACTIVE, + }, + }, + }; + + sys_put_le(&flow_ctl, &flow_ctl, sizeof(flow_ctl)); + net_buf_add_mem(buf, &flow_ctl, sizeof(flow_ctl)); + return 0; + } + case USB_CP210X_GET_EVENTMASK: { + union usb_cp210x_event event = {0}; + + sys_cpu_to_le(&event, sizeof(event)); + net_buf_add_mem(buf, &event, sizeof(event)); + return 0; + } + case USB_CP210X_GET_EVENTSTATE: { + union usb_cp210x_event event = {0}; + + sys_cpu_to_le(&event, sizeof(event)); + net_buf_add_mem(buf, &event, sizeof(event)); + return 0; + } + case USB_CP210X_GET_COMM_STATUS: { + struct usb_cp210x_serial_status status = { + .ulErrors = { + .fld = { + .break_event = false, + .framing_error = false, + .hardware_overrun = false, + .queue_overrun = false, + .parity_error = false, + }, + }, + .ulHoldReasons = { + .fld = { + .wait_cts = false, + .wait_dsr = false, + .wait_dsd = false, + .wait_xon = false, + .wait_xoff = false, + .wait_break = false, + .wait_dsr_rcv = false, + }, + }, + .ulAmountInInQueue = ring_buf_size_get(data->rx_fifo.rb), + .ulAmountInOutQueue = ring_buf_size_get(data->tx_fifo.rb), + .bEofReceived = 0, + .bWaitForImmediate = 0, + }; + + sys_cpu_to_le(&status, sizeof(status)); + net_buf_add_mem(buf, &status, sizeof(status)); + return 0; + } + case USB_CP210X_GET_CHARS: { + union usb_cp210x_char_vals chars = {0}; + + net_buf_add_mem(buf, &chars, sizeof(chars)); + return 0; + } + case USB_CP210X_GET_PROPS: { + struct usb_cp210x_props props = { + .wLength = sizeof(struct usb_cp210x_props), + .bcdVersion = USB_CP210X_PROPS_BSD_VERSION, + .ulServiceMask = USB_CP210X_PROPS_SERVICE_MASK, + .ulMaxTxQueue = ring_buf_size_get(data->tx_fifo.rb), + .ulMaxRxQueue = ring_buf_size_get(data->rx_fifo.rb), + .ulMaBaud = USB_CP210X_PROPS_MAX_BAUD, + .ulProvSubType = USB_CP210X_PROPS_PROVSUBTYPE_RS232, + .ulProvCapabilities = { + .fld = { + .dtr_dsr_support = false, + .rts_cts_support = false, + .dcd_support = false, + .can_check_parity = false, + .xon_xoff_support = false, + .can_set_xon_xoff_characters = false, + .can_set_special_characters = false, + .bit16_mode_supports = false, + }, + }, + .ulSettableParams = { + .fld = { + .can_set_parity_type = true, + .can_set_baud = true, + .can_set_number_of_data_bits = true, + .can_set_stop_bits = true, + .can_set_handshaking = false, + .can_set_parity_checking = true, + .can_set_carrier_detect_checking = false, + }, + }, + .ulSettableBaud = { + .fld = { + .baud_75 = true, + .baud_110 = true, + .baud_134_5 = true, + .baud_150 = true, + .baud_300 = true, + .baud_600 = true, + .baud_1200 = true, + .baud_1800 = true, + .baud_2400 = true, + .baud_4800 = true, + .baud_7200 = false, + .baud_9600 = true, + .baud_14400 = false, + .baud_19200 = true, + .baud_38400 = true, + .baud_56000 = false, + .baud_128000 = false, + .baud_115200 = true, + .baud_57600 = true, + }, + }, + .wSettableData = { + .fld = { + .data_bits_5 = true, + .data_bits_6 = true, + .data_bits_7 = true, + .data_bits_8 = true, + .data_bits_16 = true, + }, + }, + .ulCurrentTxQueue = ring_buf_space_get(data->tx_fifo.rb), + .ulCurrentRxQueue = ring_buf_space_get(data->rx_fifo.rb), + .uniProvName = "SILABS USB V1.0", + }; + + sys_cpu_to_le(&props, sizeof(props)); + net_buf_add_mem(buf, &props, sizeof(props)); + return 0; + } + default: + break; + } + + LOG_DBG("bmRequestType 0x%02x bRequest 0x%02x unsupported", + setup->bmRequestType, setup->bRequest); + errno = -ENOTSUP; + + return 0; +} + +static int usbd_cp210x_ctd(struct usbd_class_data *const c_data, + const struct usb_setup_packet *const setup, + const struct net_buf *const buf) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + const struct device *dev = usbd_class_get_private(c_data); + struct cp210x_uart_data *data = dev->data; + struct uart_config *const cfg = &data->uart_cfg; + + switch (setup->bRequest) { + case USB_CP210X_IFC_ENABLE: + switch (setup->wValue) { + case USB_CP210X_ENABLE: + usbd_cp210x_enable(c_data); + return 0; + case USB_CP210X_DISABLE: + usbd_cp210x_disable(c_data); + return 0; + default: + break; + } + break; + case USB_CP210X_RESET: + /* compatibility placeholder */ + return 0; + case USB_CP210X_SET_BAUDDIV: + cfg->baudrate = USB_CP210X_BAUDDIV_FREQ / setup->wValue; + usbd_msg_pub_device(uds_ctx, USBD_MSG_CDC_ACM_LINE_CODING, dev); + return 0; + case USB_CP210X_SET_BAUDRATE: + cfg->baudrate = sys_get_le32(buf->data); + usbd_msg_pub_device(uds_ctx, USBD_MSG_CDC_ACM_LINE_CODING, dev); + return 0; + case USB_CP210X_SET_LINE_CTL: + switch (FIELD_GET(USB_CP210X_BITS_STOP, setup->wValue)) { + case USB_CP210X_BITS_STOP_1: + cfg->stop_bits = UART_CFG_STOP_BITS_1; + break; + case USB_CP210X_BITS_STOP_1_5: + cfg->stop_bits = UART_CFG_STOP_BITS_1_5; + break; + case USB_CP210X_BITS_STOP_2: + cfg->stop_bits = UART_CFG_STOP_BITS_2; + break; + default: + break; + } + + switch (FIELD_GET(USB_CP210X_BITS_PARITY, setup->wValue)) { + case USB_CP210X_BITS_PARITY_NONE: + cfg->stop_bits = UART_CFG_PARITY_NONE; + break; + case USB_CP210X_BITS_PARITY_ODD: + cfg->stop_bits = UART_CFG_PARITY_ODD; + break; + case USB_CP210X_BITS_PARITY_EVEN: + cfg->stop_bits = UART_CFG_PARITY_EVEN; + break; + case USB_CP210X_BITS_PARITY_MARK: + cfg->stop_bits = UART_CFG_PARITY_MARK; + break; + case USB_CP210X_BITS_PARITY_SPACE: + cfg->stop_bits = UART_CFG_PARITY_SPACE; + break; + default: + break; + } + + switch (FIELD_GET(USB_CP210X_BITS_DATA, setup->wValue)) { + case USB_CP210X_BITS_DATA_5: + cfg->stop_bits = UART_CFG_DATA_BITS_5; + break; + case USB_CP210X_BITS_DATA_6: + cfg->stop_bits = UART_CFG_DATA_BITS_6; + break; + case USB_CP210X_BITS_DATA_7: + cfg->stop_bits = UART_CFG_DATA_BITS_7; + break; + case USB_CP210X_BITS_DATA_8: + cfg->stop_bits = UART_CFG_DATA_BITS_8; + break; + default: + break; + } + + usbd_msg_pub_device(uds_ctx, USBD_MSG_CDC_ACM_LINE_CODING, dev); + return 0; + case USB_CP210X_SET_MHS: { + union usb_cp210x_mhs mhs; + + mhs.val = sys_cpu_to_le16(setup->wValue); + if (mhs.fld.dtr_mask) { + data->line_state_dtr = mhs.fld.dtr_state; + } + if (mhs.fld.rts_mask) { + data->line_state_rts = mhs.fld.rts_state; + } + return 0; + } + case USB_CP210X_SET_FLOW: { + struct usb_cp210x_flow_control flow_ctrl; + + if (setup->wLength != sizeof(flow_ctrl)) { + errno = -ENOTSUP; + return 0; + } + sys_put_le(&flow_ctrl, buf->data, setup->wLength); + return 0; + } + case USB_CP210X_SET_XON: + /* FIXME: add xon/xoff support */ + return 0; + case USB_CP210X_SET_XOFF: + /* FIXME: add xon/xoff support */ + return 0; + case USB_CP210X_SET_EVENTMASK: { + union usb_cp210x_event event; + + if (setup->wLength != sizeof(event)) { + errno = -ENOTSUP; + return 0; + } + sys_get_le(&event, buf->data, setup->wLength); + return 0; + } + case USB_CP210X_SET_RECEIVE: + /* not supported for cp2101 */ + return 0; + case USB_CP210X_SET_BREAK: + return 0; + case USB_CP210X_IMM_CHAR: + return 0; + case USB_CP210X_SET_CHAR: { + struct usb_cp210x_char chr; + + sys_get_le(&chr, &setup->wValue, sizeof(chr)); + if (chr.char_idx < 6) { + errno = -ENOTSUP; + return 0; + } + return 0; + } + case USB_CP210X_SET_CHARS: { + union usb_cp210x_char_vals chars = {0}; + + if (setup->wLength != sizeof(chars)) { + errno = -ENOTSUP; + return 0; + } + memcpy(&chars, buf->data, setup->wLength); + return 0; + } + case USB_CP210X_PURGE: { + union usb_cp210x_purge purge; + + purge.val = sys_cpu_to_le16(setup->wValue); + if (purge.fld.rx1 && purge.fld.rx2) { + ring_buf_reset(data->rx_fifo.rb); + } + if (purge.fld.tx1 && purge.fld.tx2) { + ring_buf_reset(data->tx_fifo.rb); + } + return 0; + } + case USB_CP210X_EMBED_EVENTS: { + return 0; + } + default: + break; + } + + LOG_DBG("bmRequestType 0x%02x bRequest 0x%02x unsupported", + setup->bmRequestType, setup->bRequest); + errno = -ENOTSUP; + + return 0; +} + +static int usbd_cp210x_vendor_cth(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + struct net_buf *const buf) +{ + if (setup->bRequest != USB_CP210X_VENDOR_SPECIFIC) { + errno = -ENOTSUP; + return 0; + } + + if (setup->wValue == USB_CP210X_GET_PARTNUM) { + net_buf_add_u8(buf, USB_CP210X_PARTNUM_CP2101); + return 0; + } + + LOG_DBG("vendor request bRequest 0x%02x unsupported", + setup->wValue); + errno = -ENOTSUP; + + return 0; +} + +static int usbd_cp210x_vendor_ctd(const struct usbd_context *const ctx, + const struct usb_setup_packet *const setup, + const struct net_buf *const buf) +{ + LOG_DBG("vendor request bRequest 0x%02x unsupported", + setup->wValue); + errno = -ENOTSUP; + + return 0; +} + +USBD_VREQUEST_DEFINE(sample_vrequest, 0xff, usbd_cp210x_vendor_cth, usbd_cp210x_vendor_ctd); + +static int usbd_cp210x_init(struct usbd_class_data *const c_data) +{ + struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); + const struct device *dev = usbd_class_get_private(c_data); + const struct cp210x_uart_config *cfg = dev->config; + struct usbd_cp210x_desc *desc = cfg->desc; + + if (cfg->if_desc_data && desc->if0.iInterface == 0) { + if (usbd_add_descriptor(uds_ctx, cfg->if_desc_data)) { + LOG_ERR("Failed to add interface string descriptor"); + } else { + desc->if0.iInterface = usbd_str_desc_get_idx(cfg->if_desc_data); + } + } + + usbd_device_register_vreq(uds_ctx, &sample_vrequest); + + return 0; +} + +/* + * TX handler is triggered when the state of TX fifo has been altered. + */ +static void cp210x_tx_fifo_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct cp210x_uart_data *data; + const struct cp210x_uart_config *cfg; + struct usbd_class_data *c_data; + struct net_buf *buf; + size_t len; + int ret; + + data = CONTAINER_OF(dwork, struct cp210x_uart_data, tx_fifo_work); + cfg = data->dev->config; + c_data = cfg->c_data; + + if (!atomic_test_bit(&data->state, CP210X_CLASS_ENABLED)) { + LOG_DBG("USB configuration is not enabled"); + return; + } + + if (atomic_test_bit(&data->state, CP210X_CLASS_SUSPENDED)) { + LOG_INF("USB support is suspended (FIXME: submit rwup)"); + return; + } + + if (atomic_test_and_set_bit(&data->state, CP210X_TX_FIFO_BUSY)) { + LOG_DBG("TX transfer already in progress"); + return; + } + + buf = cp210x_buf_alloc(c_data, cp210x_get_bulk_in(c_data)); + if (!buf) { + atomic_clear_bit(&data->state, CP210X_TX_FIFO_BUSY); + cp210x_work_schedule(&data->tx_fifo_work, K_MSEC(1)); + return; + } + + len = ring_buf_get(data->tx_fifo.rb, buf->data, buf->size); + net_buf_add(buf, len); + + data->zlp_needed = len != 0 && len % cp210x_get_bulk_mps(c_data) == 0; + + ret = usbd_ep_enqueue(c_data, buf); + if (ret) { + LOG_ERR("Failed to enqueue"); + net_buf_unref(buf); + atomic_clear_bit(&data->state, CP210X_TX_FIFO_BUSY); + } +} + +/* + * RX handler should be conditionally triggered at: + * - (x) cp210x_irq_rx_enable() + * - (x) RX transfer completion + * - (x) the end of cp210x_irq_cb_handler + * - (x) USBD class API enable call + * - ( ) USBD class API resumed call (TODO) + */ +static void cp210x_rx_fifo_handler(struct k_work *work) +{ + struct cp210x_uart_data *data; + const struct cp210x_uart_config *cfg; + struct usbd_class_data *c_data; + struct net_buf *buf; + int ret; + + data = CONTAINER_OF(work, struct cp210x_uart_data, rx_fifo_work); + cfg = data->dev->config; + c_data = cfg->c_data; + + if (!atomic_test_bit(&data->state, CP210X_CLASS_ENABLED) || + atomic_test_bit(&data->state, CP210X_CLASS_SUSPENDED)) { + LOG_INF("USB configuration is not enabled or suspended"); + return; + } + + if (ring_buf_space_get(data->rx_fifo.rb) < cp210x_get_bulk_mps(c_data)) { + LOG_INF("RX buffer to small, throttle"); + return; + } + + if (atomic_test_and_set_bit(&data->state, CP210X_RX_FIFO_BUSY)) { + LOG_WRN("RX transfer already in progress"); + return; + } + + buf = cp210x_buf_alloc(c_data, cp210x_get_bulk_out(c_data)); + if (!buf) { + return; + } + + /* Shrink the buffer size if operating on a full speed bus */ + buf->size = MIN(cp210x_get_bulk_mps(c_data), buf->size); + + ret = usbd_ep_enqueue(c_data, buf); + if (ret) { + LOG_ERR("Failed to enqueue net_buf for 0x%02x", + cp210x_get_bulk_out(c_data)); + net_buf_unref(buf); + } +} + +static void cp210x_irq_tx_enable(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + atomic_set_bit(&data->state, CP210X_IRQ_TX_ENABLED); + + if (ring_buf_space_get(data->tx_fifo.rb)) { + LOG_INF("tx_en: trigger irq_cb_work"); + cp210x_work_submit(&data->irq_cb_work); + } +} + +static void cp210x_irq_tx_disable(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + atomic_clear_bit(&data->state, CP210X_IRQ_TX_ENABLED); +} + +static void cp210x_irq_rx_enable(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + atomic_set_bit(&data->state, CP210X_IRQ_RX_ENABLED); + + /* Permit buffer to be drained regardless of USB state */ + if (!ring_buf_is_empty(data->rx_fifo.rb)) { + LOG_INF("rx_en: trigger irq_cb_work"); + cp210x_work_submit(&data->irq_cb_work); + } + + if (!atomic_test_bit(&data->state, CP210X_RX_FIFO_BUSY)) { + LOG_INF("rx_en: trigger rx_fifo_work"); + cp210x_work_submit(&data->rx_fifo_work); + } +} + +static void cp210x_irq_rx_disable(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + atomic_clear_bit(&data->state, CP210X_IRQ_RX_ENABLED); +} + +static int cp210x_fifo_fill(const struct device *dev, + const uint8_t *const tx_data, + const int len) +{ + struct cp210x_uart_data *const data = dev->data; + k_spinlock_key_t key; + uint32_t done; + + if (!check_wq_ctx(dev)) { + LOG_WRN("Invoked by inappropriate context"); + __ASSERT_NO_MSG(false); + return 0; + } + + key = k_spin_lock(&data->lock); + done = ring_buf_put(data->tx_fifo.rb, tx_data, len); + k_spin_unlock(&data->lock, key); + if (done) { + data->tx_fifo.altered = true; + } + + LOG_INF("UART dev %p, len %d, remaining space %u", + dev, len, ring_buf_space_get(data->tx_fifo.rb)); + + return done; +} + +static int cp210x_fifo_read(const struct device *dev, + uint8_t *const rx_data, + const int size) +{ + struct cp210x_uart_data *const data = dev->data; + uint32_t len; + + LOG_INF("UART dev %p size %d length %u", + dev, size, ring_buf_size_get(data->rx_fifo.rb)); + + if (!check_wq_ctx(dev)) { + LOG_WRN("Invoked by inappropriate context"); + __ASSERT_NO_MSG(false); + return 0; + } + + len = ring_buf_get(data->rx_fifo.rb, rx_data, size); + if (len) { + data->rx_fifo.altered = true; + } + + return len; +} + +static int cp210x_irq_tx_ready(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + if (check_wq_ctx(dev)) { + if (data->tx_fifo.irq) { + return ring_buf_space_get(data->tx_fifo.rb); + } + } else { + LOG_WRN("Invoked by inappropriate context"); + __ASSERT_NO_MSG(false); + } + + return 0; +} + +static int cp210x_irq_rx_ready(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + if (check_wq_ctx(dev)) { + if (data->rx_fifo.irq) { + return 1; + } + } else { + LOG_WRN("Invoked by inappropriate context"); + __ASSERT_NO_MSG(false); + } + + return 0; +} + +static int cp210x_irq_is_pending(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + if (check_wq_ctx(dev)) { + if (data->tx_fifo.irq || data->rx_fifo.irq) { + return 1; + } + } else { + LOG_WRN("Invoked by inappropriate context"); + __ASSERT_NO_MSG(false); + } + + return 0; +} + +static int cp210x_irq_update(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + if (!check_wq_ctx(dev)) { + LOG_WRN("Invoked by inappropriate context"); + __ASSERT_NO_MSG(false); + return 0; + } + + if (atomic_test_bit(&data->state, CP210X_IRQ_RX_ENABLED) && + !ring_buf_is_empty(data->rx_fifo.rb)) { + data->rx_fifo.irq = true; + } else { + data->rx_fifo.irq = false; + } + + if (atomic_test_bit(&data->state, CP210X_IRQ_TX_ENABLED) && + ring_buf_space_get(data->tx_fifo.rb)) { + data->tx_fifo.irq = true; + } else { + data->tx_fifo.irq = false; + } + + return 1; +} + +/* + * IRQ handler should be conditionally triggered for the TX path at: + * - cp210x_irq_tx_enable() + * - TX transfer completion + * - TX buffer is empty + * - USBD class API enable and resumed calls + * + * for RX path, if enabled, at: + * - cp210x_irq_rx_enable() + * - RX transfer completion + * - RX buffer is not empty + */ +static void cp210x_irq_cb_handler(struct k_work *work) +{ + struct cp210x_uart_data *data; + const struct cp210x_uart_config *cfg; + struct usbd_class_data *c_data; + + data = CONTAINER_OF(work, struct cp210x_uart_data, irq_cb_work); + cfg = data->dev->config; + c_data = cfg->c_data; + + if (!data->cb) { + LOG_ERR("IRQ callback is not set"); + return; + } + + data->tx_fifo.altered = false; + data->rx_fifo.altered = false; + data->rx_fifo.irq = false; + data->tx_fifo.irq = false; + + if (atomic_test_bit(&data->state, CP210X_IRQ_RX_ENABLED) || + atomic_test_bit(&data->state, CP210X_IRQ_TX_ENABLED)) { + data->cb(usbd_class_get_private(c_data), data->cb_data); + } + + if (data->rx_fifo.altered) { + LOG_DBG("rx fifo altered, submit work"); + cp210x_work_submit(&data->rx_fifo_work); + } + + if (!atomic_test_bit(&data->state, CP210X_TX_FIFO_BUSY)) { + if (data->tx_fifo.altered) { + LOG_DBG("tx fifo altered, submit work"); + cp210x_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } else if (data->zlp_needed) { + LOG_DBG("zlp needed, submit work"); + cp210x_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } + } + + if (atomic_test_bit(&data->state, CP210X_IRQ_RX_ENABLED) && + !ring_buf_is_empty(data->rx_fifo.rb)) { + LOG_DBG("rx irq pending, submit irq_cb_work"); + cp210x_work_submit(&data->irq_cb_work); + } + + if (atomic_test_bit(&data->state, CP210X_IRQ_TX_ENABLED) && + ring_buf_space_get(data->tx_fifo.rb)) { + LOG_DBG("tx irq pending, submit irq_cb_work"); + cp210x_work_submit(&data->irq_cb_work); + } +} + +static void cp210x_irq_callback_set(const struct device *dev, + const uart_irq_callback_user_data_t cb, + void *const cb_data) +{ + struct cp210x_uart_data *const data = dev->data; + + data->cb = cb; + data->cb_data = cb_data; +} + +static int cp210x_poll_in(const struct device *dev, unsigned char *const c) +{ + struct cp210x_uart_data *const data = dev->data; + uint32_t len; + int ret = -1; + + if (ring_buf_is_empty(data->rx_fifo.rb)) { + return ret; + } + + len = ring_buf_get(data->rx_fifo.rb, c, 1); + if (len) { + cp210x_work_submit(&data->rx_fifo_work); + ret = 0; + } + + return ret; +} + +static void cp210x_poll_out(const struct device *dev, const unsigned char c) +{ + struct cp210x_uart_data *const data = dev->data; + k_spinlock_key_t key; + uint32_t wrote; + + while (true) { + key = k_spin_lock(&data->lock); + wrote = ring_buf_put(data->tx_fifo.rb, &c, 1); + k_spin_unlock(&data->lock, key); + + if (wrote == 1) { + break; + } + + if (k_is_in_isr() || !data->flow_ctrl) { + LOG_WRN_ONCE("Ring buffer full, discard data"); + break; + } + + k_msleep(1); + } + + /* Schedule with minimal timeout to make it possible to send more than + * one byte per USB transfer. The latency increase is negligible while + * the increased throughput and reduced CPU usage is easily observable. + */ + cp210x_work_schedule(&data->tx_fifo_work, K_MSEC(1)); +} + +#ifdef CONFIG_UART_LINE_CTRL +static int cp210x_line_ctrl_set(const struct device *dev, + const uint32_t ctrl, const uint32_t val) +{ + /* FIXME: add line ctrl set support */ + return -EINVAL; +} + +static int cp210x_line_ctrl_get(const struct device *dev, + const uint32_t ctrl, uint32_t *const val) +{ + struct cp210x_uart_data *const data = dev->data; + + switch (ctrl) { + case UART_LINE_CTRL_BAUD_RATE: + *val = data->uart_cfg.baudrate; + return 0; + case UART_LINE_CTRL_RTS: + *val = data->line_state_rts; + return 0; + case UART_LINE_CTRL_DTR: + *val = data->line_state_dtr; + return 0; + default: + return -ENOTSUP; + } +} +#endif + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE +static int cp210x_configure(const struct device *dev, + const struct uart_config *const cfg) +{ + struct cp210x_uart_data *const data = dev->data; + + switch (cfg->flow_ctrl) { + case UART_CFG_FLOW_CTRL_NONE: + data->flow_ctrl = false; + break; + case UART_CFG_FLOW_CTRL_DTR_DSR: + data->flow_ctrl = true; + break; + default: + return -ENOTSUP; + } + + return 0; +} + +static int cp210x_config_get(const struct device *dev, + struct uart_config *const cfg) +{ + struct cp210x_uart_data *const data = dev->data; + + memcpy(cfg, &data->uart_cfg, sizeof(struct uart_config)); + + return 0; +} +#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ + +static int usbd_cp210x_preinit(const struct device *dev) +{ + struct cp210x_uart_data *const data = dev->data; + + ring_buf_reset(data->tx_fifo.rb); + ring_buf_reset(data->rx_fifo.rb); + + k_work_init_delayable(&data->tx_fifo_work, cp210x_tx_fifo_handler); + k_work_init(&data->rx_fifo_work, cp210x_rx_fifo_handler); + k_work_init(&data->irq_cb_work, cp210x_irq_cb_handler); + + return 0; +} + +static DEVICE_API(uart, cp210x_uart_api) = { + .irq_tx_enable = cp210x_irq_tx_enable, + .irq_tx_disable = cp210x_irq_tx_disable, + .irq_tx_ready = cp210x_irq_tx_ready, + .irq_rx_enable = cp210x_irq_rx_enable, + .irq_rx_disable = cp210x_irq_rx_disable, + .irq_rx_ready = cp210x_irq_rx_ready, + .irq_is_pending = cp210x_irq_is_pending, + .irq_update = cp210x_irq_update, + .irq_callback_set = cp210x_irq_callback_set, + .poll_in = cp210x_poll_in, + .poll_out = cp210x_poll_out, + .fifo_fill = cp210x_fifo_fill, + .fifo_read = cp210x_fifo_read, +#ifdef CONFIG_UART_LINE_CTRL + .line_ctrl_set = cp210x_line_ctrl_set, + .line_ctrl_get = cp210x_line_ctrl_get, +#endif +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + .configure = cp210x_configure, + .config_get = cp210x_config_get, +#endif +}; + +struct usbd_class_api usbd_cp210x_api = { + .request = usbd_cp210x_request, + .update = usbd_cp210x_update, + .enable = usbd_cp210x_enable, + .disable = usbd_cp210x_disable, + .suspended = usbd_cp210x_suspended, + .resumed = usbd_cp210x_resumed, + .control_to_host = usbd_cp210x_cth, + .control_to_dev = usbd_cp210x_ctd, + .init = usbd_cp210x_init, + .get_desc = usbd_cp210x_get_desc, +}; + +#define CP210X_DEFINE_DESCRIPTOR(n) \ + static struct usbd_cp210x_desc cp210x_desc_##n = { \ + .if0 = \ + { \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_DESC_INTERFACE, \ + .bInterfaceNumber = 0, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_BCC_VENDOR, \ + .bInterfaceSubClass = 0, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ + }, \ + \ + .if0_in_ep = \ + { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x81, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64U), \ + .bInterval = 0, \ + }, \ + \ + .if0_out_ep = \ + { \ + .bLength = sizeof(struct usb_ep_descriptor), \ + .bDescriptorType = USB_DESC_ENDPOINT, \ + .bEndpointAddress = 0x01, \ + .bmAttributes = USB_EP_TYPE_BULK, \ + .wMaxPacketSize = sys_cpu_to_le16(64U), \ + .bInterval = 0, \ + }, \ + \ + .nil_desc = \ + { \ + .bLength = 0, \ + .bDescriptorType = 0, \ + }, \ + }; \ + \ + const static struct usb_desc_header *cp210x_fs_desc_##n[] = { \ + (struct usb_desc_header *)&cp210x_desc_##n.if0, \ + (struct usb_desc_header *)&cp210x_desc_##n.if0_in_ep, \ + (struct usb_desc_header *)&cp210x_desc_##n.if0_out_ep, \ + (struct usb_desc_header *)&cp210x_desc_##n.nil_desc, \ + } + +#define USBD_CP210X_DT_DEVICE_DEFINE(n) \ + BUILD_ASSERT(DT_INST_ON_BUS(n, usb), \ + "node " DT_NODE_PATH( \ + DT_DRV_INST(n)) " is not assigned to a USB device controller"); \ + \ + CP210X_DEFINE_DESCRIPTOR(n); \ + USBD_DEFINE_CLASS(cp210x_##n, &usbd_cp210x_api, (void *)DEVICE_DT_GET(DT_DRV_INST(n)), \ + NULL); \ + \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(n, label), ( \ + USBD_DESC_STRING_DEFINE(cp210x_if_desc_data_##n, \ + DT_INST_PROP(n, label), \ + USBD_DUT_STRING_INTERFACE); \ + )) \ + \ + RING_BUF_DECLARE(cp210x_rb_rx_##n, DT_INST_PROP(n, rx_fifo_size)); \ + RING_BUF_DECLARE(cp210x_rb_tx_##n, DT_INST_PROP(n, tx_fifo_size)); \ + \ + static const struct cp210x_uart_config uart_config_##n = { \ + .c_data = &cp210x_##n, \ + IF_ENABLED(DT_INST_NODE_HAS_PROP(n, label), ( \ + .if_desc_data = &cp210x_if_desc_data_##n, \ + )) .desc = &cp210x_desc_##n, \ + .fs_desc = cp210x_fs_desc_##n, \ + }; \ + \ + static struct cp210x_uart_data uart_data_##n = { \ + .dev = DEVICE_DT_GET(DT_DRV_INST(n)), \ + .uart_cfg = {CP210X_DEFAULT_LINECODING}, \ + .rx_fifo.rb = &cp210x_rb_rx_##n, \ + .tx_fifo.rb = &cp210x_rb_tx_##n, \ + .flow_ctrl = DT_INST_PROP(n, hw_flow_control), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, usbd_cp210x_preinit, NULL, &uart_data_##n, &uart_config_##n, \ + PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, &cp210x_uart_api); + +DT_INST_FOREACH_STATUS_OKAY(USBD_CP210X_DT_DEVICE_DEFINE);