Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: usb host: Add the USB host virtual serial port architecture … #87665

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions dts/bindings/usb/quectel,userial.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright (c) 2025 Quectel Wireless Solutions Co
# SPDX-License-Identifier: Apache-2.0

description: USBH QUECTEL DRIVER

compatible: "quectel,stm32-userial-driver"

80 changes: 80 additions & 0 deletions include/zephyr/userial/quectel/quec_uhc_app.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "stdio.h"
#include "string.h"

#include <zephyr/sys/util.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/irq.h>
#include <zephyr/logging/log.h>


/*===========================================================================
* define
===========================================================================*/
#define QUEC_UHC_DRIVER_NAME "quec_uhc"
#define QUEC_UHC_DRIVER_ID quec_uhc

#define quec_print(format, ...) LOG_DBG("[%d - %d][%s][%d]: "format"\n", (uint32_t)k_uptime_get() / 1000, (uint32_t)k_uptime_get() % 1000 , __func__, __LINE__, ##__VA_ARGS__)


#define UHC_MIN(a,b) (a < b ? a : b)
/*===========================================================================
* enum
===========================================================================*/
typedef enum
{
QUEC_AT_PORT = 1,
QUEC_MODEM_PORT = 2,
QUEC_PORT_MAX,
}quec_cdc_port_e;

typedef enum
{
QUEC_DEVICE_CONNECT = 1 << 0,
QUEC_DEVICE_DISCONNECT = 1 << 1,

QUEC_RX_ARRIVE = 1 << 5,
QUEC_RX_ERROR = 1 << 6,

QUEC_TX_COMPLETE = 1 << 15,
QUEC_TX_ERROR = 1 << 16
}uhc_rx_event_e;

typedef enum
{
QUEC_STATUS_CONNECT = 0,
QUEC_STATUS_DISCONNECT = 1,
QUEC_STATUS_DEBOUNCE,
}uhc_status_e;

typedef enum
{
QUEC_GET_DEVICE_STATUS = 0, /* param: NULL return: Is it connected to a USB device --- uhc_status_e */

QUEC_SET_USER_CALLBACK = 1, /* param: quec_uhc_callback When the USB host receives data/successfully sends data,
disconnects or successfully connects events, this callback function will be called*/
}quec_ioctl_cmd_e;

/*===========================================================================
* typedef
===========================================================================*/
typedef void (*quec_uhc_callback)(uint32_t event, uint8_t port_id, uint32_t size);

typedef struct
{
int (*open)(const struct device *dev, quec_cdc_port_e port);
int (*read)(const struct device *dev, quec_cdc_port_e port, uint8_t *buffer, uint32_t size);
int (*write)(const struct device *dev, quec_cdc_port_e port, uint8_t *buffer, uint32_t size);
int (*close)(const struct device *dev, quec_cdc_port_e port);
int (*read_aviable)(const struct device *dev, quec_cdc_port_e port_id);
int (*write_aviable)(const struct device *dev, quec_cdc_port_e port_id);
int (*ioctl)(const struct device *dev, quec_ioctl_cmd_e cmd, void *param);
}quec_uhc_api_t;


9 changes: 9 additions & 0 deletions samples/subsys/usb/userial/quectel/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2025 Quectel Wireless Solutions Co
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hello_world)

target_sources(app PRIVATE main.c)
11 changes: 11 additions & 0 deletions samples/subsys/usb/userial/quectel/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.. zephyr:code-sample:: quectel usb serial example
:name: quectel usb serial

Check failure on line 2 in samples/subsys/usb/userial/quectel/README.rst

GitHub Actions / Run compliance checks on patch series (PR)

SphinxLint

samples/subsys/usb/userial/quectel/README.rst:2 trailing whitespace (trailing-whitespace)

Print "quectel usb serial example" to the console.

Overview
********

This example describes how to use an MCU such as stm32 as a USB host
and use the USB virtual serial port to interact with

Check failure on line 10 in samples/subsys/usb/userial/quectel/README.rst

GitHub Actions / Run compliance checks on patch series (PR)

SphinxLint

samples/subsys/usb/userial/quectel/README.rst:10 trailing whitespace (trailing-whitespace)
the Quectel module through AT commands.
13 changes: 13 additions & 0 deletions samples/subsys/usb/userial/quectel/app.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 Quectel Wireless Solutions Co
*
* SPDX-License-Identifier: Apache-2.0
*/

/{
usbh_quectel_driver {
compatible = "quectel,stm32-userial-driver";
status = "okay";
};
};

245 changes: 245 additions & 0 deletions samples/subsys/usb/userial/quectel/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#include <zephyr/userial/quectel/quec_uhc_app.h>

/*===========================================================================
* define
===========================================================================*/

#define AT_ATE0 "ate0&w\r\n"
#define AT_QSCLK "at+qsclk=1\r\n"
#define AT_CGDCONT "at+cgdcont?\r\n"
#define AT_CSQ "at+csq\r\n"
#define AT_CREG "at+creg?\r\n"

#define TEST_STASK_SZIE (1024 * 6)
#define TEST_PORT QUEC_AT_PORT
#define REC_BUF_SIZE 1024

LOG_MODULE_REGISTER(quec_uhc_demo, LOG_LEVEL_DBG);

/*===========================================================================
* typedef
===========================================================================*/
typedef struct
{
const struct device *device;
struct k_thread tx_thread;
quec_uhc_api_t *api;
}quec_demo_param_t;

typedef struct
{
uint32_t event;
uint8_t port_id;
uint32_t size;
}quec_sys_event_t;

/*===========================================================================
* variables
===========================================================================*/
static uint8_t tx_task_stack[TEST_STASK_SZIE];
static quec_demo_param_t uhc_manager;

K_MSGQ_DEFINE(quec_demo_msgq, sizeof(quec_sys_event_t), 20, 4);
K_MSGQ_DEFINE(quec_rx_test_msgq, sizeof(quec_sys_event_t), 20, 4);
/*===========================================================================
* function
===========================================================================*/
static void quec_event_process(uint32_t event, uint8_t port_id, uint32_t size)
{
quec_sys_event_t sys_event = {0};
quec_demo_param_t *uhc_ctl = (quec_demo_param_t *)&uhc_manager;
uint8_t rec_buf[REC_BUF_SIZE];
int read_size = 0, total_size = 0;

if(event & QUEC_RX_ARRIVE)
{
memset(rec_buf, 0, sizeof(rec_buf));

total_size = uhc_ctl->api->read_aviable(uhc_ctl->device, port_id);
while(total_size > 0)
{
memset(rec_buf, 0, REC_BUF_SIZE);

read_size = UHC_MIN(total_size, REC_BUF_SIZE);
read_size = uhc_ctl->api->read(uhc_ctl->device, port_id, rec_buf, read_size);
if(read_size > 0)
{
total_size -= read_size;
quec_print("Receive: size %d content %s", size, rec_buf);
}
else
{
quec_print("read failed");
break;
}
}
}

if(event & QUEC_DEVICE_CONNECT)
{
quec_print("qcx216 connect");

if(uhc_ctl->api->open(uhc_ctl->device, TEST_PORT) != 0)
{
quec_print("port open error");
return;
}

sys_event.event = QUEC_DEVICE_CONNECT;
k_msgq_put(&quec_demo_msgq, &sys_event, K_MSEC(0));
}

if(event & QUEC_DEVICE_DISCONNECT)
{
quec_print("qcx216 disconnect");
}

if(event & QUEC_RX_ERROR)
{
quec_print("qcx216 rx error");
}

if(event & QUEC_TX_ERROR)
{
quec_print("qcx216 tx error");
}
}

static int quec_at_cmd_send(uint8_t *buffer, uint32_t size)
{
int total_write = 0;
quec_demo_param_t *uhc_ctl = (quec_demo_param_t *)&uhc_manager;

total_write = uhc_ctl->api->write(uhc_ctl->device, TEST_PORT, buffer, size);
if(total_write <= 0)
{
quec_print("send failed");
return -1;
}

quec_print("send: %s", buffer);
return total_write;
}

void quec_uhc_demo_process(void *ctx1, void *ctx2, void *ctx3)
{
int ret = 0;
quec_sys_event_t sys_event = {0};

while(1)
{
ret = k_msgq_get(&quec_demo_msgq, &sys_event, K_FOREVER);
if(ret != 0)
{
quec_print("message error");
continue;
}

if(sys_event.event == QUEC_DEVICE_CONNECT)
{
while(1)
{
if(quec_at_cmd_send(AT_ATE0, strlen(AT_ATE0)) < 0)
{
quec_print("send ate0 fail");
break;
}

k_sleep(K_MSEC(100));

if(quec_at_cmd_send(AT_QSCLK, strlen(AT_QSCLK)) < 0)
{
quec_print("send qsclk fail");
break;
}

k_sleep(K_MSEC(100));

if(quec_at_cmd_send(AT_CGDCONT, strlen(AT_CGDCONT)) < 0)
{
quec_print("send cgdcont fail");
break;
}

k_sleep(K_MSEC(100));

if(quec_at_cmd_send(AT_CSQ, strlen(AT_CSQ)) < 0)
{
quec_print("send csq fail");
break;
}

k_sleep(K_MSEC(100));

if(quec_at_cmd_send(AT_CREG, strlen(AT_CREG)) < 0)
{
quec_print("send creg fail");
break;
}

k_sleep(K_MSEC(100));
}
}
}
}


int main(void)
{
quec_print("app enter");

quec_demo_param_t *uhc_ctl = (quec_demo_param_t *)&uhc_manager;

uhc_ctl->device = device_get_binding(QUEC_UHC_DRIVER_NAME);
if(uhc_ctl->device == NULL)
{
quec_print("no invalid device");
return -1;
}

uhc_ctl->api = (quec_uhc_api_t *)uhc_ctl->device->api;
if(uhc_ctl->api == NULL)
{
quec_print("device error");
return -1;
}

int ret = uhc_ctl->api->ioctl(uhc_ctl->device, QUEC_SET_USER_CALLBACK, quec_event_process);
if(ret < 0)
{
quec_print("set callback failed");
return -1;
}

k_thread_create(&uhc_ctl->tx_thread,
(k_thread_stack_t *)tx_task_stack,
TEST_STASK_SZIE,
quec_uhc_demo_process,
NULL, NULL, NULL,
K_PRIO_PREEMPT(6), 0, K_MSEC(0));

//为了防止绑定回调函数时,设备已经连接成功了,错过连接成功的事件;因此手动判断一下
ret = uhc_ctl->api->ioctl(uhc_ctl->device, QUEC_GET_DEVICE_STATUS, NULL);
if(ret == QUEC_DEVICE_CONNECT)
{
ret = uhc_ctl->api->open(uhc_ctl->device, TEST_PORT);
if(ret < 0)
{
quec_print("port open failed");
return -1;
}

quec_sys_event_t sys_event = {0};
sys_event.event = QUEC_DEVICE_CONNECT;
k_msgq_put(&quec_demo_msgq, &sys_event, K_NO_WAIT);
}

return 0;
}

1 change: 1 addition & 0 deletions samples/subsys/usb/userial/quectel/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# nothing here
15 changes: 15 additions & 0 deletions samples/subsys/usb/userial/quectel/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
sample:
description: quectel usb serial example
name: usbh quectel driver
common:
tags: introduction
integration_platforms:
- native_sim
harness: console
harness_config:
type: one_line
regex:
- ""
tests:
sample.basic.helloworld:
tags: introduction
1 change: 1 addition & 0 deletions subsys/Kconfig
Original file line number Diff line number Diff line change
@@ -53,9 +53,10 @@
source "subsys/usb/device/Kconfig"
source "subsys/usb/device_next/Kconfig"
source "subsys/usb/host/Kconfig"
source "subsys/usb/userial/quectel/Kconfig"
source "subsys/usb/usb_c/Kconfig"
source "subsys/zbus/Kconfig"
# zephyr-keep-sorted-stop

Check failure on line 59 in subsys/Kconfig

GitHub Actions / Run compliance checks on patch series (PR)

KeepSorted

subsys/Kconfig:59 sorted block has out-of-order line at 57

config MODULES
bool "Make tristate Kconfig options and an 'm' selection available"
1 change: 1 addition & 0 deletions subsys/usb/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK device)
add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK_NEXT device_next)
add_subdirectory_ifdef(CONFIG_USB_HOST_STACK host)
add_subdirectory_ifdef(CONFIG_USBC_STACK usb_c)
add_subdirectory_ifdef(CONFIG_USERIAL_QUECTEL userial/quectel)
16 changes: 16 additions & 0 deletions subsys/usb/userial/quectel/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright (c) 2025 Quectel Corporation
# SPDX-License-Identifier: Apache-2.0

zephyr_library()

zephyr_library_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
zephyr_library_include_directories(driver)

#userial host drivers(for mcu)
zephyr_library_sources(driver/quec_uhc_driver_stm32.c)

#userial common drivers
zephyr_library_sources(driver/quec_ringbuffer.c)
zephyr_library_sources(driver/quec_uhc_memory.c)
zephyr_library_sources(driver/quec_uhc_enumeration.c)
zephyr_library_sources(quec_uhc_userial.c)
12 changes: 12 additions & 0 deletions subsys/usb/userial/quectel/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (c) 2025 Quectel Corporation
# SPDX-License-Identifier: Apache-2.0
# Users can define other mcu to drive quectel moodules here

config USERIAL_QUECTEL
bool "USB serial port using MCU as host and quectel as device"
default y if USERIAL_STM32_DRIVE_QUECTEL
default n
help
USB subsys userial drivers

source "subsys/usb/userial/quectel/driver/Kconfig.stm32"
12 changes: 12 additions & 0 deletions subsys/usb/userial/quectel/driver/Kconfig.stm32
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (c) 2025 Quectel Corporation
# SPDX-License-Identifier: Apache-2.0

config USERIAL_STM32_DRIVE_QUECTEL
bool "USB serial port using stm32 as host and quectel as device"
depends on DT_HAS_QUECTEL_STM32_USERIAL_DRIVER_ENABLED
select USE_STM32_LL_USB
select USE_STM32_HAL_HCD
select PINCTRL
default y
help
STM32 USBH controller driver.
90 changes: 90 additions & 0 deletions subsys/usb/userial/quectel/driver/quec_ringbuffer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "quec_ringbuffer.h"

void ring_buffer_init(ring_buffer_t *ring_buffer, uint8_t *buffer, int buff_size)
{
if(buffer == NULL)
return ;

ring_buffer->buffer = buffer;
ring_buffer->read_offset = 0;
ring_buffer->write_offset = 0;
ring_buffer->valid_size = 0;
ring_buffer->total_size = buff_size;
}

void ring_buffer_reset(ring_buffer_t *ring_buffer)
{
ring_buffer->read_offset = 0;
ring_buffer->write_offset = 0;
ring_buffer->valid_size = 0;
}

void ring_buffer_write(void *buffer_to_write, int size, ring_buffer_t *ring_buffer)
{
int32_t write_offset = ring_buffer->write_offset;
int32_t total_size = ring_buffer->total_size;
int32_t first_write_size = 0;

if (ring_buffer->valid_size + size > total_size)
{
return;
}

if (size + write_offset <= total_size)
{
memcpy(ring_buffer->buffer + write_offset, buffer_to_write, size);
}
else
{
first_write_size = total_size - write_offset;
memcpy(ring_buffer->buffer + write_offset, buffer_to_write, first_write_size);
memcpy(ring_buffer->buffer, (uint8_t *)buffer_to_write + first_write_size, size - first_write_size);
}
ring_buffer->write_offset += size;
ring_buffer->write_offset %= total_size;
ring_buffer->valid_size += size;
}

void ring_buffer_read(ring_buffer_t *ring_buffer, void *buff, int size)
{
int32_t read_offset = ring_buffer->read_offset;
int32_t total_size = ring_buffer->total_size;
int32_t first_read_size = 0;

if (size > ring_buffer->valid_size)
{
return;
}

if (total_size - read_offset >= size)
{
memcpy(buff, ring_buffer->buffer + read_offset, size);
}
else
{
first_read_size = total_size - read_offset;
memcpy(buff, ring_buffer->buffer + read_offset, first_read_size);
memcpy((uint8_t *)buff + first_read_size, ring_buffer->buffer, size - first_read_size);
}

ring_buffer->read_offset += size;
ring_buffer->read_offset %= total_size;
ring_buffer->valid_size -= size;
}

extern inline uint8_t ring_buffer_is_empty(ring_buffer_t *buffer);
extern inline uint8_t ring_buffer_is_full(ring_buffer_t *buffer);
extern inline int ring_buffer_num_items(ring_buffer_t *buffer);
extern inline int ring_buffer_free_size(ring_buffer_t *buffer);


92 changes: 92 additions & 0 deletions subsys/usb/userial/quectel/driver/quec_ringbuffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <inttypes.h>

/**
* @file
* Prototypes and structures for the ring buffer module.
*/

#ifndef RINGBUFFER_H
#define RINGBUFFER_H

/**
* Simplifies the use of <tt>struct ring_buffer_t</tt>.
*/
typedef struct ring_buffer_t ring_buffer_t;

/**
* Structure which holds a ring buffer.
* The buffer contains a buffer array
* as well as metadata for the ring buffer.
*/
struct ring_buffer_t {
uint8_t *buffer;
int32_t read_offset;
int32_t write_offset;
int32_t valid_size;
int32_t total_size;
};

/**
* Initializes the ring buffer pointed to by <em>buffer</em>.
* This function can also be used to empty/reset the buffer.
* @param buffer The ring buffer to initialize.
*/
void ring_buffer_init(ring_buffer_t *ring_buffer, uint8_t *buffer, int buff_size);

void ring_buffer_reset(ring_buffer_t *ring_buffer);

/**
* Adds a byte to a ring buffer.
* @param buffer The buffer in which the data should be placed.
* @param data The byte to place.
*/
void ring_buffer_write(void *buffer_to_write, int size, ring_buffer_t *ring_buffer);

/**
* Adds an array of bytes to a ring buffer.
* @param buffer The buffer in which the data should be placed.
* @param data A pointer to the array of bytes to place in the queue.
* @param size The size of the array.
*/
void ring_buffer_read(ring_buffer_t *ring_buffer, void *buff, int size);



/**
* Returns whether a ring buffer is empty.
* @param buffer The buffer for which it should be returned whether it is empty.
* @return 1 if empty; 0 otherwise.
*/
inline uint8_t ring_buffer_is_empty(ring_buffer_t *buffer) {
return (buffer->valid_size == 0);
}

/**
* Returns whether a ring buffer is full.
* @param buffer The buffer for which it should be returned whether it is full.
* @return 1 if full; 0 otherwise.
*/
inline uint8_t ring_buffer_is_full(ring_buffer_t *buffer) {
return (buffer->valid_size == buffer->total_size);
}

/**
* Returns the number of items in a ring buffer.
* @param buffer The buffer for which the number of items should be returned.
* @return The number of items in the ring buffer.
*/
inline int ring_buffer_num_items(ring_buffer_t *buffer) {
return (buffer->valid_size);
}

inline int ring_buffer_free_size(ring_buffer_t *buffer) {
return (buffer->total_size - buffer->valid_size);
}

#endif /* RINGBUFFER_H */
271 changes: 271 additions & 0 deletions subsys/usb/userial/quectel/driver/quec_uhc_driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <zephyr/usb/usb_ch9.h>
#include <zephyr/userial/quectel/quec_uhc_app.h>

#include "quec_ringbuffer.h"

/*===========================================================================
* define
===========================================================================*/
#define UHC_PORT_INVALID 0xFF

#define QUEC_SYSTEM_PORT 0
#define QUEC_AT_INTF_NUM 2
#define QUEC_MODEM_INTF_NUM 3

#define DEVICE_DESC_PRE_SIZE 8
#define CFG_DESC_MAX_SIZE 512

#define USB_DESC_TYPE_DEVICE 0x01
#define USB_DESC_TYPE_CONFIGURATION 0x02
#define USB_DESC_TYPE_STRING 0x03
#define USB_DESC_TYPE_INTERFACE 0x04
#define USB_DESC_TYPE_ENDPOINT 0x05
#define USB_DESC_TYPE_DEVICEQUALIFIER 0x06
#define USB_DESC_TYPE_OTHERSPEED 0x07
#define USB_DESC_TYPE_IAD 0x0b
#define USB_DESC_TYPE_HID 0x21
#define USB_DESC_TYPE_REPORT 0x22
#define USB_DESC_TYPE_PHYSICAL 0x23
#define USB_DESC_TYPE_HUB 0x29

#define USB_EP_ATTR_CONTROL 0x00
#define USB_EP_ATTR_ISOC 0x01
#define USB_EP_ATTR_BULK 0x02
#define USB_EP_ATTR_INT 0x03
#define USB_EP_ATTR_TYPE_MASK 0x03

#define USB_DIR_OUT 0x00
#define USB_DIR_IN 0x80

#define USB_REQ_TYPE_DIR_OUT 0x00
#define USB_REQ_TYPE_DIR_IN 0x80

#define USB_REQ_TYPE_DEVICE 0x00
#define USB_REQ_TYPE_INTERFACE 0x01
#define USB_REQ_TYPE_ENDPOINT 0x02
#define USB_REQ_TYPE_OTHER 0x03
#define USB_REQ_TYPE_RECIPIENT_MASK 0x1f

#define USB_REQ_TYPE_STANDARD 0x00
#define USB_REQ_TYPE_CLASS 0x20
#define USB_REQ_TYPE_VENDOR 0x40
#define USB_REQ_TYPE_MASK 0x60

#define USB_REQ_GET_STATUS 0x00
#define USB_REQ_CLEAR_FEATURE 0x01
#define USB_REQ_SET_FEATURE 0x03
#define USB_REQ_SET_ADDRESS 0x05
#define USB_REQ_GET_DESCRIPTOR 0x06
#define USB_REQ_SET_DESCRIPTOR 0x07
#define USB_REQ_GET_CONFIGURATION 0x08
#define USB_REQ_SET_CONFIGURATION 0x09
#define USB_REQ_GET_INTERFACE 0x0A
#define USB_REQ_SET_INTERFACE 0x0B
#define USB_REQ_SYNCH_FRAME 0x0C
#define USB_REQ_SET_ENCRYPTION 0x0D
#define USB_REQ_GET_ENCRYPTION 0x0E
#define USB_REQ_RPIPE_ABORT 0x0E
#define USB_REQ_SET_HANDSHAKE 0x0F
#define USB_REQ_RPIPE_RESET 0x0F
#define USB_REQ_GET_HANDSHAKE 0x10
#define USB_REQ_SET_CONNECTION 0x11
#define USB_REQ_SET_SECURITY_DATA 0x12
#define USB_REQ_GET_SECURITY_DATA 0x13
#define USB_REQ_SET_WUSB_DATA 0x14
#define USB_REQ_LOOPBACK_DATA_WRITE 0x15
#define USB_REQ_LOOPBACK_DATA_READ 0x16
#define USB_REQ_SET_INTERFACE_DS 0x17
#define USB_REQ_SET_LINE_STATE 0x22

#define USB_TRANS_ID 11388

#define USBH_PID_SETUP 0x00
#define USBH_PID_DATA 0x01

#define USB_FIFO_SIZE (4 * 1024)
#define USB_RX_TRIG_LEVEL (4 * 1024)

#define USB_RX_TRIG_TIMEOUT (50) //50ms
#define USB_FS_PKT_SIZE 64

#define QUEC_RX_STACK_SIZE (8 * 1024)
#define QUEC_TX_STACK_SIZE (8 * 1024)


/*===========================================================================
* ENUM
===========================================================================*/
typedef enum
{
QUEC_PORT_STATUS_INVALID = 0,
QUEC_PORT_STATUS_FREE,
QUEC_PORT_STATUS_OPEN
}quec_port_status_e;


/*===========================================================================
* Struct
===========================================================================*/
struct usb_intf_ep_desc
{
struct usb_if_descriptor intf_desc;
struct usb_ep_descriptor ctrl_ep_desc;
struct usb_ep_descriptor in_ep_desc;
struct usb_ep_descriptor out_ep_desc;
} __packed;

struct uhc_cfg_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t wTotalLength;
uint8_t bNumInterfaces;
uint8_t bConfigurationValue;
uint8_t iConfiguration;
uint8_t bmAttributes;
uint8_t bMaxPower;
uint8_t data[512];
} __packed;

typedef struct usb_desc_header usb_desc_head_t;
typedef struct usb_device_descriptor usb_device_desc_t;
typedef struct usb_cfg_descriptor usb_cfg_desc_t;
typedef struct usb_if_descriptor usb_intf_desc_t;
typedef struct usb_ep_descriptor usb_endp_desc_t;
typedef struct usb_intf_ep_desc usb_intf_ep_desc_t;
typedef struct uhc_cfg_descriptor uhc_cfg_descriptor_t;

typedef void(*quec_uhc_drv_cb_t)(uint32_t event, void *ctx);

typedef void(*quec_uhc_trans_cb_t)(void *ctx);

typedef struct
{
usb_endp_desc_t *ep_desc;
uint16_t trans_id;
uint8_t cdc_num;
uint8_t port_num;
uint8_t token;
uint8_t *buffer;
int nbytes;
int actual;
int cached;
int timeouts;
int status;
quec_uhc_trans_cb_t callback;
}quec_uhc_xfer_t;

typedef struct
{
usb_endp_desc_t ep_desc;
uint8_t port_num;
uint8_t cache[64];
ring_buffer_t fifo;
quec_uhc_xfer_t xfer;
uint8_t is_busy;

uint8_t *task_stack;
struct k_msgq *msgq;
struct k_thread thread;
}quec_uhc_pmg_t;

typedef struct
{
bool occupied;
quec_uhc_xfer_t *xfer;
}quec_udrv_port_t;

typedef struct
{
HCD_HandleTypeDef hcd;
uint32_t port_index;
uint8_t status;
uint8_t dev_address;
quec_uhc_drv_cb_t callback;
quec_udrv_port_t port[16];
}quec_udrv_mgr_t;

typedef struct
{
uint32_t event_id;
uint32_t param1;
uint32_t param2;
uint32_t param3;
}quec_uhc_msg_t;

typedef struct
{
uint8_t request_type;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
}quec_uhc_req_t;

typedef struct
{
int (*init)(const struct device *dev, quec_uhc_drv_cb_t callback);
int (*deinit)(const struct device *dev);
int (*reset)(const struct device *dev);
int (*set_address)(const struct device *dev, uint8_t address);
int (*ep_enable)(const struct device *dev, usb_endp_desc_t *port);
int (*ep_disable)(const struct device *dev, uint8_t port);
int (*enqueue)(const struct device *dev, quec_uhc_xfer_t *xfer);
}quec_udrv_api_t;

typedef struct
{
uint8_t cdc_num;
uint32_t status;
uint32_t size;
}quec_trans_status_t;

typedef struct
{
uint8_t intf_num;
quec_port_status_e status;
quec_uhc_pmg_t rx_port;
quec_uhc_pmg_t tx_port;
quec_uhc_pmg_t ctl_port;;
}quec_uhc_dev_t;

typedef struct
{
const struct device *device;
quec_udrv_api_t *api;
uint16_t trans;
uint8_t dev_address;
uint8_t status;
quec_uhc_pmg_t sys_port;
quec_uhc_callback user_callback;
quec_uhc_dev_t dev[QUEC_PORT_MAX];
}quec_uhc_mgr_t;


/*===========================================================================
* Function
===========================================================================*/
int quec_uhc_enum_process(quec_uhc_mgr_t *dev, usb_device_desc_t *dev_desc, uhc_cfg_descriptor_t *cfg_desc);

int quec_uhc_parse_config_desc(uhc_cfg_descriptor_t *cfg_desc, int intf_num, usb_intf_ep_desc_t *desc);

int quec_uhc_open(const struct device *dev, quec_cdc_port_e port_id);

int quec_uhc_set_line_state(quec_uhc_mgr_t *udev, uint16_t intf, uint16_t value);

int quec_uhc_set_interface(quec_uhc_mgr_t *udev, uint16_t intf);

void quec_uhc_cdc_memory_init(quec_uhc_dev_t *dev, uint8_t port);

void quec_uhc_sys_memory_init(quec_uhc_pmg_t *port);

int quec_uhc_sio_deinit(quec_uhc_mgr_t *udev);

int quec_uhc_msg_put(struct k_msgq *msgq, uint32_t event_id, uint32_t param1, uint32_t param2);

342 changes: 342 additions & 0 deletions subsys/usb/userial/quectel/driver/quec_uhc_driver_stm32.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#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/drivers/clock_control/stm32_clock_control.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/irq.h>
#include "stm32_hsem.h"
#include "quec_uhc_driver.h"
#include "stm32h5xx_hal_rcc_ex.h"

#define DT_DRV_COMPAT st_stm32_usb
LOG_MODULE_REGISTER(quec_uhc_drv, LOG_LEVEL_DBG);

/*===========================================================================
* variables
===========================================================================*/
static quec_udrv_mgr_t uhc_control = {0};
static void uhc_debounce_callback(struct k_timer *timer);

K_MSGQ_DEFINE(uhc_urb_msgq, sizeof(quec_uhc_msg_t), 10, 4);
K_TIMER_DEFINE(uhc_dec_timer, uhc_debounce_callback, NULL);


/*===========================================================================
* Functions
===========================================================================*/
void HAL_HCD_Connect_Callback(HCD_HandleTypeDef *hhcd)
{
quec_udrv_mgr_t *driver = (quec_udrv_mgr_t *)hhcd->pData;
if(driver == NULL)
{
quec_print("driver err");
return;
}

driver->status = QUEC_STATUS_DEBOUNCE;
k_timer_start(&uhc_dec_timer, K_MSEC(50), K_MSEC(0));
}

void HAL_HCD_Disconnect_Callback(HCD_HandleTypeDef *hhcd)
{
quec_udrv_mgr_t *driver = (quec_udrv_mgr_t *)hhcd->pData;
if(driver == NULL)
{
quec_print("driver err");
return;
}

if(driver->status == QUEC_STATUS_DEBOUNCE)
{
k_timer_stop(&uhc_dec_timer);
}
else if(driver->status != QUEC_STATUS_DISCONNECT)
{
if(driver->callback)
{
driver->callback(QUEC_DEVICE_DISCONNECT, NULL);
}
}

driver->status = QUEC_STATUS_DISCONNECT;
}

void uhc_debounce_callback(struct k_timer *timer)
{
uhc_control.status = QUEC_STATUS_CONNECT;
if(uhc_control.callback)
{
uhc_control.callback(QUEC_DEVICE_CONNECT, NULL);
}
}

void HAL_HCD_HC_NotifyURBChange_Callback(HCD_HandleTypeDef *hhcd, uint8_t chnum, HCD_URBStateTypeDef urb_state)
{
quec_udrv_mgr_t *driver = (quec_udrv_mgr_t *)hhcd->pData;
if(driver == NULL || chnum > 16)
{
quec_print("driver err %p %d", driver, chnum);
return;
}

quec_udrv_port_t *port = &driver->port[chnum];
if(port->xfer == NULL || port->xfer->callback == NULL)
{
quec_print("port err %d %d %p", chnum, urb_state, port->xfer);
return;
}

if(urb_state == URB_DONE || urb_state == URB_STALL)
{
uint32_t irq_hd = irq_lock();
port->xfer->status = (urb_state == URB_DONE ? 0 : -1);
port->xfer->actual = HAL_HCD_HC_GetXferCount(hhcd, chnum);
port->xfer->callback(port->xfer);
irq_unlock(irq_hd);
}
}

static void uhc_stm32_isr(const void *arg)
{
HAL_HCD_IRQHandler(&uhc_control.hcd);
}

static int uhc_stm32_hw_init(HCD_HandleTypeDef *hcd)
{
quec_print("start init uhc...");

hcd->Instance = USB_DRD_FS;
hcd->Init.Host_channels = 8;
hcd->Init.speed = HCD_SPEED_FULL;
hcd->Init.dma_enable = DISABLE;
hcd->Init.phy_itface = HCD_PHY_EMBEDDED;
hcd->Init.Sof_enable = ENABLE;
hcd->pData = (void *)&uhc_control;

if (HAL_HCD_Init(hcd) != HAL_OK)
{
quec_print("HCD init failed");
return -1;
}

if(HAL_HCD_Start(hcd) != HAL_OK)
{
quec_print("HCD start failed");
return -1;
}

IRQ_CONNECT(USB_DRD_FS_IRQn, 0, uhc_stm32_isr, 0, 0);
irq_enable(USB_DRD_FS_IRQn);

quec_print("uhc init ok");
return 0;
}

static int uhc_stm32_clock_enable(void)
{
const struct device *const clk = DEVICE_DT_GET(STM32_CLOCK_CONTROL_NODE);
const struct stm32_pclken pclken[] = STM32_DT_INST_CLOCKS(0);

if (!device_is_ready(clk))
{
quec_print("clock control device not ready");
return -1;
}

LL_PWR_EnableVDDUSB();

if (DT_INST_NUM_CLOCKS(0) > 1)
{
if (clock_control_configure(clk, (clock_control_subsys_t)&pclken[1], NULL) != 0)
{
quec_print("Could not select USB domain clock");
return -1;
}
}

if (clock_control_on(clk, (clock_control_subsys_t)&pclken[0]) != 0)
{
quec_print("Unable to enable USB clock");
return -1;
}

quec_print("uhc clock enabled");
return 0;
}

static int quec_stm32_chip_init(const struct device *dev)
{
if(uhc_stm32_clock_enable() != 0)
{
return -1;
}

return 0;
}

static uint8_t quec_stm32_alloc_port(quec_udrv_mgr_t *uhc_mgr)
{
uint8_t index;

for (index = 1; index <= 16 ; index++)
{
if(uhc_mgr->port[index].occupied == false)
{
uhc_mgr->port[index].occupied = true;
uhc_mgr->port[index].xfer = NULL;

return index;
}
}

return UHC_PORT_INVALID;
}

static void quec_stm32_free_port(quec_udrv_mgr_t *uhc_mgr, uint8_t port_id)
{
uhc_mgr->port[port_id].occupied = false;
uhc_mgr->port[port_id].xfer = NULL;

quec_print("free port %d", port_id);
}

static int quec_stm32_enqueue(const struct device *dev, quec_uhc_xfer_t *xfer)
{
quec_udrv_mgr_t *uhc_cfg = dev->data;
int ret = 0;

uhc_cfg->port[xfer->port_num].xfer = xfer;

ret = HAL_HCD_HC_SubmitRequest(&uhc_cfg->hcd,
xfer->port_num,
(xfer->ep_desc->bEndpointAddress & 0x80) >> 7,
xfer->ep_desc->bmAttributes,
xfer->token,
xfer->buffer,
xfer->nbytes,
0);
if(ret != HAL_OK)
{
uhc_cfg->port[xfer->port_num].xfer = NULL;
quec_print("transfer request fail");
return -1;
}

return 0;
}

static int quec_stm32_reset(const struct device *dev)
{
quec_udrv_mgr_t *uhc_cfg = dev->data;

HAL_HCD_ResetPort(&uhc_cfg->hcd);
quec_print("stm32 reset usb");
return 0;
}

static int quec_stm32_init(const struct device *dev, quec_uhc_drv_cb_t callback)
{
quec_udrv_mgr_t *uhc_cfg = dev->data;

uhc_cfg->callback = callback;
if(uhc_stm32_hw_init(&uhc_cfg->hcd) != 0)
{
return -1;
}

return 0;
}

static int quec_stm32_deinit(const struct device *dev)
{
quec_udrv_mgr_t *uhc_cfg = dev->data;

uhc_cfg->callback = NULL;
irq_disable(USB_DRD_FS_IRQn);
HAL_HCD_Stop(&uhc_cfg->hcd);
HAL_HCD_DeInit(&uhc_cfg->hcd);

return 0;
}

static int quec_stm32_set_address(const struct device *dev, uint8_t address)
{
quec_udrv_mgr_t *uhc_cfg = dev->data;

uhc_cfg->dev_address = address;
return 0;
}

static int quec_stm32_ep_enable(const struct device *dev, usb_endp_desc_t *ep_desc)
{
int ret = 0;
uint8_t port_num = 0;
quec_udrv_mgr_t *uhc_cfg = dev->data;

if(ep_desc->bEndpointAddress == 0x80 || ep_desc->bEndpointAddress == 0)
{
port_num = 0;
}
else
{
port_num = quec_stm32_alloc_port(uhc_cfg);
}

if(port_num == UHC_PORT_INVALID)
{
quec_print("no valid port");
return -1;
}

ret = HAL_HCD_HC_Init(&uhc_cfg->hcd,
port_num,
ep_desc->bEndpointAddress,
uhc_cfg->dev_address,
USB_DRD_SPEED_FS,
ep_desc->bmAttributes,
ep_desc->wMaxPacketSize);
if(ret != 0)
{
quec_print("ep 0x%x enable failed", ep_desc->bEndpointAddress);
return -1;
}

return port_num;
}

static int quec_stm32_ep_disable(const struct device *dev, uint8_t port)
{
quec_udrv_mgr_t *uhc_cfg = dev->data;

HAL_HCD_HC_Halt(&uhc_cfg->hcd, port);
HAL_HCD_HC_Close(&uhc_cfg->hcd, port);
quec_stm32_free_port(uhc_cfg, port);

return 0;
}

static const quec_udrv_api_t uhc_stm32_api = {
.init = quec_stm32_init,
.deinit = quec_stm32_deinit,
.reset = quec_stm32_reset,
.set_address = quec_stm32_set_address,
.ep_enable = quec_stm32_ep_enable,
.ep_disable = quec_stm32_ep_disable,
.enqueue = quec_stm32_enqueue,
};

DEVICE_DEFINE(QCX216, "QCX216", quec_stm32_chip_init,
NULL, &uhc_control, NULL,
POST_KERNEL, 98,
&uhc_stm32_api);

467 changes: 467 additions & 0 deletions subsys/usb/userial/quectel/driver/quec_uhc_enumeration.c

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions subsys/usb/userial/quectel/driver/quec_uhc_memory.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2025 Quectel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <soc.h>
#include <string.h>
#include <zephyr/sys/util.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/irq.h>

#include "quec_uhc_driver.h"

LOG_MODULE_REGISTER(quec_uhc_memory, LOG_LEVEL_DBG);

/*===========================================================================
* variables
===========================================================================*/
K_MSGQ_DEFINE(uhc_sys_msgq, sizeof(quec_uhc_msg_t), 20, 4);
K_MSGQ_DEFINE(uhc_at_rx_msgq, sizeof(quec_trans_status_t), 20, 4);
K_MSGQ_DEFINE(uhc_at_tx_msgq, sizeof(quec_trans_status_t), 20, 4);
K_MSGQ_DEFINE(uhc_modem_rx_msgq, sizeof(quec_trans_status_t), 20, 4);
K_MSGQ_DEFINE(uhc_modem_tx_msgq, sizeof(quec_trans_status_t), 20, 4);


__attribute__((section("SRAM3"))) uint8_t quec_at_rx_buf[USB_FIFO_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_at_tx_buf[USB_FIFO_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_modem_rx_buf[USB_FIFO_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_modem_tx_buf[USB_FIFO_SIZE];

__attribute__((section("SRAM3"))) uint8_t quec_sys_task_stack[QUEC_RX_STACK_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_at_rx_task_stack[QUEC_RX_STACK_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_at_tx_task_stack[QUEC_TX_STACK_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_modem_tx_task_stack[QUEC_TX_STACK_SIZE];
__attribute__((section("SRAM3"))) uint8_t quec_modem_rx_task_stack[QUEC_RX_STACK_SIZE];


/*===========================================================================
* functions
===========================================================================*/
void quec_uhc_cdc_memory_init(quec_uhc_dev_t *dev, uint8_t port)
{
switch(port)
{
case QUEC_AT_PORT:
dev->rx_port.msgq = &uhc_at_rx_msgq;
dev->tx_port.msgq = &uhc_at_tx_msgq;
dev->rx_port.task_stack = quec_at_rx_task_stack;
dev->tx_port.task_stack = quec_at_tx_task_stack;
ring_buffer_init(&dev->rx_port.fifo, quec_at_rx_buf, USB_FIFO_SIZE);
ring_buffer_init(&dev->tx_port.fifo, quec_at_tx_buf, USB_FIFO_SIZE);
break;

case QUEC_MODEM_PORT:
dev->rx_port.msgq = &uhc_modem_rx_msgq;
dev->tx_port.msgq = &uhc_modem_tx_msgq;
dev->rx_port.task_stack = quec_modem_rx_task_stack;
dev->tx_port.task_stack = quec_modem_tx_task_stack;
ring_buffer_init(&dev->rx_port.fifo, quec_modem_rx_buf, USB_FIFO_SIZE);
ring_buffer_init(&dev->tx_port.fifo, quec_modem_tx_buf, USB_FIFO_SIZE);
break;

default:

break;
}

quec_print("cdc port %d memory init ok", port);
}

void quec_uhc_sys_memory_init(quec_uhc_pmg_t *port)
{
port->task_stack = quec_sys_task_stack;
port->msgq = &uhc_sys_msgq;

quec_print("sys port memory init ok");
}

812 changes: 812 additions & 0 deletions subsys/usb/userial/quectel/quec_uhc_userial.c

Large diffs are not rendered by default.