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
Show file tree
Hide file tree
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

View workflow job for this annotation

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

View workflow job for this annotation

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
Expand Up @@ -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

View workflow job for this annotation

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"
Expand Down
Loading
Loading