refactor(serial): add host serial framework

Signed-off-by: sakumisu <1203593632@qq.com>
This commit is contained in:
sakumisu
2025-12-12 22:45:10 +08:00
parent 707e865627
commit da2263728a
43 changed files with 4649 additions and 3297 deletions

View File

@@ -1,294 +0,0 @@
/*
* Copyright (c) 2022, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_cdc_acm.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_cdc_acm"
#include "usb_log.h"
#define DEV_FORMAT "/dev/ttyACM%d"
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cdc_acm_buf[CONFIG_USBHOST_MAX_CDC_ACM_CLASS][USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
static struct usbh_cdc_acm g_cdc_acm_class[CONFIG_USBHOST_MAX_CDC_ACM_CLASS];
static uint32_t g_devinuse = 0;
static struct usbh_cdc_acm *usbh_cdc_acm_class_alloc(void)
{
uint8_t devno;
for (devno = 0; devno < CONFIG_USBHOST_MAX_CDC_ACM_CLASS; devno++) {
if ((g_devinuse & (1U << devno)) == 0) {
g_devinuse |= (1U << devno);
memset(&g_cdc_acm_class[devno], 0, sizeof(struct usbh_cdc_acm));
g_cdc_acm_class[devno].minor = devno;
return &g_cdc_acm_class[devno];
}
}
return NULL;
}
static void usbh_cdc_acm_class_free(struct usbh_cdc_acm *cdc_acm_class)
{
uint8_t devno = cdc_acm_class->minor;
if (devno < 32) {
g_devinuse &= ~(1U << devno);
}
memset(cdc_acm_class, 0, sizeof(struct usbh_cdc_acm));
}
int usbh_cdc_acm_set_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
if (!cdc_acm_class || !cdc_acm_class->hport) {
return -USB_ERR_INVAL;
}
setup = cdc_acm_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = cdc_acm_class->intf;
setup->wLength = 7;
memcpy(g_cdc_acm_buf[cdc_acm_class->minor], line_coding, sizeof(struct cdc_line_coding));
return usbh_control_transfer(cdc_acm_class->hport, setup, g_cdc_acm_buf[cdc_acm_class->minor]);
}
int usbh_cdc_acm_get_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
int ret;
if (!cdc_acm_class || !cdc_acm_class->hport) {
return -USB_ERR_INVAL;
}
setup = cdc_acm_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = cdc_acm_class->intf;
setup->wLength = 7;
ret = usbh_control_transfer(cdc_acm_class->hport, setup, g_cdc_acm_buf[cdc_acm_class->minor]);
if (ret < 0) {
return ret;
}
memcpy(line_coding, g_cdc_acm_buf[cdc_acm_class->minor], sizeof(struct cdc_line_coding));
return ret;
}
int usbh_cdc_acm_set_line_state(struct usbh_cdc_acm *cdc_acm_class, bool dtr, bool rts)
{
struct usb_setup_packet *setup;
if (!cdc_acm_class || !cdc_acm_class->hport) {
return -USB_ERR_INVAL;
}
setup = cdc_acm_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
setup->wValue = (dtr << 0) | (rts << 1);
setup->wIndex = cdc_acm_class->intf;
setup->wLength = 0;
return usbh_control_transfer(cdc_acm_class->hport, setup, NULL);
}
static int usbh_cdc_acm_connect(struct usbh_hubport *hport, uint8_t intf)
{
struct usb_endpoint_descriptor *ep_desc;
int ret = 0;
struct usbh_cdc_acm *cdc_acm_class = usbh_cdc_acm_class_alloc();
if (cdc_acm_class == NULL) {
USB_LOG_ERR("Fail to alloc cdc_acm_class\r\n");
return -USB_ERR_NOMEM;
}
cdc_acm_class->hport = hport;
cdc_acm_class->intf = intf;
hport->config.intf[intf].priv = cdc_acm_class;
hport->config.intf[intf + 1].priv = NULL;
#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
ep_desc = &hport->config.intf[intf].altsetting[0].ep[0].ep_desc;
USBH_EP_INIT(cdc_acm_class->intin, ep_desc);
#endif
for (uint8_t i = 0; i < hport->config.intf[intf + 1].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &hport->config.intf[intf + 1].altsetting[0].ep[i].ep_desc;
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(cdc_acm_class->bulkin, ep_desc);
} else {
USBH_EP_INIT(cdc_acm_class->bulkout, ep_desc);
}
}
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, cdc_acm_class->minor);
USB_LOG_INFO("Register CDC ACM Class:%s\r\n", hport->config.intf[intf].devname);
#if 0
USB_LOG_INFO("Test cdc acm rx and tx and rx for 5 times, baudrate is 115200\r\n");
struct cdc_line_coding linecoding;
uint8_t count = 5;
linecoding.dwDTERate = 115200;
linecoding.bDataBits = 8;
linecoding.bParityType = 0;
linecoding.bCharFormat = 0;
usbh_cdc_acm_set_line_coding(cdc_acm_class, &linecoding);
usbh_cdc_acm_set_line_state(cdc_acm_class, true, false);
memset(g_cdc_acm_buf, 'a', sizeof(g_cdc_acm_buf));
ret = usbh_cdc_acm_bulk_out_transfer(cdc_acm_class, g_cdc_acm_buf, sizeof(g_cdc_acm_buf), 0xfffffff);
USB_LOG_RAW("out ret:%d\r\n", ret);
while (count--) {
ret = usbh_cdc_acm_bulk_in_transfer(cdc_acm_class, g_cdc_acm_buf, sizeof(g_cdc_acm_buf), 0xfffffff);
USB_LOG_RAW("in ret:%d\r\n", ret);
if (ret > 0) {
for (uint32_t i = 0; i < ret; i++) {
USB_LOG_RAW("%02x ", g_cdc_acm_buf[i]);
}
}
USB_LOG_RAW("\r\n");
}
#endif
usbh_cdc_acm_run(cdc_acm_class);
return ret;
}
static int usbh_cdc_acm_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
int ret = 0;
struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)hport->config.intf[intf].priv;
if (cdc_acm_class) {
if (cdc_acm_class->bulkin) {
usbh_kill_urb(&cdc_acm_class->bulkin_urb);
}
if (cdc_acm_class->bulkout) {
usbh_kill_urb(&cdc_acm_class->bulkout_urb);
}
#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
if (cdc_acm_class->intin) {
usbh_kill_urb(&cdc_acm_class->intin_urb);
}
#endif
if (hport->config.intf[intf].devname[0] != '\0') {
usb_osal_thread_schedule_other();
USB_LOG_INFO("Unregister CDC ACM Class:%s\r\n", hport->config.intf[intf].devname);
usbh_cdc_acm_stop(cdc_acm_class);
}
usbh_cdc_acm_class_free(cdc_acm_class);
}
return ret;
}
int usbh_cdc_acm_bulk_in_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &cdc_acm_class->bulkin_urb;
usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkin, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
int usbh_cdc_acm_bulk_out_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &cdc_acm_class->bulkout_urb;
usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkout, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
static int usbh_cdc_data_connect(struct usbh_hubport *hport, uint8_t intf)
{
(void)hport;
(void)intf;
return 0;
}
static int usbh_cdc_data_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
(void)hport;
(void)intf;
return 0;
}
__WEAK void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class)
{
(void)cdc_acm_class;
}
__WEAK void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class)
{
(void)cdc_acm_class;
}
const struct usbh_class_driver cdc_acm_class_driver = {
.driver_name = "cdc_acm",
.connect = usbh_cdc_acm_connect,
.disconnect = usbh_cdc_acm_disconnect
};
const struct usbh_class_driver cdc_data_class_driver = {
.driver_name = "cdc_data",
.connect = usbh_cdc_data_connect,
.disconnect = usbh_cdc_data_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_none_class_info = {
.match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
.bInterfaceClass = USB_DEVICE_CLASS_CDC,
.bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
.bInterfaceProtocol = CDC_COMMON_PROTOCOL_NONE,
.id_table = NULL,
.class_driver = &cdc_acm_class_driver
};
CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_at_class_info = {
.match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
.bInterfaceClass = USB_DEVICE_CLASS_CDC,
.bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
.bInterfaceProtocol = CDC_COMMON_PROTOCOL_AT_COMMANDS,
.id_table = NULL,
.class_driver = &cdc_acm_class_driver
};
CLASS_INFO_DEFINE const struct usbh_class_info cdc_data_class_info = {
.match_flags = USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = USB_DEVICE_CLASS_CDC_DATA,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = NULL,
.class_driver = &cdc_data_class_driver
};

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 2022, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_CDC_ACM_H
#define USBH_CDC_ACM_H
#include "usb_cdc.h"
struct usbh_cdc_acm {
struct usbh_hubport *hport;
struct usb_endpoint_descriptor *bulkin; /* Bulk IN endpoint */
struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
struct usb_endpoint_descriptor *intin; /* INTR IN endpoint (optional) */
#endif
struct usbh_urb bulkout_urb;
struct usbh_urb bulkin_urb;
#ifdef CONFIG_USBHOST_CDC_ACM_NOTIFY
struct usbh_urb intin_urb;
#endif
struct cdc_line_coding linecoding;
uint8_t intf;
uint8_t minor;
void *user_data;
};
#ifdef __cplusplus
extern "C" {
#endif
int usbh_cdc_acm_set_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding);
int usbh_cdc_acm_get_line_coding(struct usbh_cdc_acm *cdc_acm_class, struct cdc_line_coding *line_coding);
int usbh_cdc_acm_set_line_state(struct usbh_cdc_acm *cdc_acm_class, bool dtr, bool rts);
int usbh_cdc_acm_bulk_in_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
int usbh_cdc_acm_bulk_out_transfer(struct usbh_cdc_acm *cdc_acm_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class);
void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class);
#ifdef __cplusplus
}
#endif
#endif /* USBH_CDC_ACM_H */

266
class/serial/usbh_cdc_acm.c Normal file
View File

@@ -0,0 +1,266 @@
/*
* Copyright (c) 2022 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#include "usbh_cdc_acm.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_cdc_acm"
#include "usb_log.h"
struct usbh_cdc_acm {
struct usb_endpoint_descriptor *intin;
struct usbh_urb intin_urb;
struct usb_osal_timer *modem_timer;
uint16_t modem_status;
};
static int usbh_cdc_acm_attach(struct usbh_serial *serial)
{
struct usb_endpoint_descriptor *ep_desc;
int ret;
struct usbh_cdc_acm *cdc_acm_class = usb_osal_malloc(sizeof(struct usbh_cdc_acm));
if (!cdc_acm_class) {
USB_LOG_ERR("No memory for cdc_acm_class\r\n");
return -USB_ERR_NOMEM;
}
memset(cdc_acm_class, 0, sizeof(struct usbh_cdc_acm));
serial->priv = cdc_acm_class;
for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(cdc_acm_class->intin, ep_desc);
break;
} else {
}
}
}
if (!cdc_acm_class->intin) {
USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
ret = -USB_ERR_NODEV;
goto errout;
}
return 0;
errout:
serial->priv = NULL;
usb_osal_free(cdc_acm_class);
return ret;
}
static void usbh_cdc_acm_detach(struct usbh_serial *serial)
{
struct usbh_cdc_acm *cdc_acm_class;
if (!serial || !serial->priv) {
return;
}
cdc_acm_class = (struct usbh_cdc_acm *)serial->priv;
if (cdc_acm_class->intin) {
usbh_kill_urb(&cdc_acm_class->intin_urb);
}
serial->priv = NULL;
usb_osal_free(cdc_acm_class);
}
static int usbh_cdc_acm_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = serial->intf;
setup->wLength = 7;
memcpy(serial->iobuffer, line_coding, sizeof(struct cdc_line_coding));
return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
}
static int usbh_cdc_acm_get_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
int ret;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = serial->intf;
setup->wLength = 7;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return ret;
}
memcpy(line_coding, serial->iobuffer, sizeof(struct cdc_line_coding));
return ret;
}
static int usbh_cdc_acm_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
setup->wValue = (dtr << 0) | (rts << 1);
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_cdc_acm_get_modem_status(struct usbh_serial *serial)
{
struct usbh_cdc_acm *cdc_acm_class;
uintptr_t flags;
uint16_t status;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
flags = usb_osal_enter_critical_section();
cdc_acm_class = (struct usbh_cdc_acm *)serial->priv;
status = (cdc_acm_class->modem_status & CDC_SERIAL_STATE_TX_CARRIER ? USBH_SERIAL_TIOCM_DSR : 0) |
(cdc_acm_class->modem_status & CDC_SERIAL_STATE_RING ? USBH_SERIAL_TIOCM_RI : 0) |
(cdc_acm_class->modem_status & CDC_SERIAL_STATE_RX_CARRIER ? USBH_SERIAL_TIOCM_CD : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
usb_osal_leave_critical_section(flags);
return status;
}
#ifdef CONFIG_USBH_SERIAL_GET_MODEM_STATUS
static int __usbh_cdc_acm_get_modem_status(struct usbh_serial *serial)
{
struct usbh_cdc_acm *cdc_acm_class;
struct cdc_acm_notification *notification;
uint16_t difference;
uintptr_t flags;
int ret;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
cdc_acm_class = (struct usbh_cdc_acm *)serial->priv;
usbh_int_urb_fill(&cdc_acm_class->intin_urb, serial->hport, cdc_acm_class->intin, &serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET], cdc_acm_class->intin->wMaxPacketSize, 0xffffffff, NULL, NULL);
ret = usbh_submit_urb(&cdc_acm_class->intin_urb);
if (ret < 0) {
return ret;
}
if (cdc_acm_class->intin_urb.actual_length < sizeof(struct cdc_acm_notification)) {
return -USB_ERR_INVAL;
}
notification = (struct cdc_acm_notification *)&serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET];
if (notification->bNotificationType != CDC_NOTIFICATION_SERIAL_STATE) {
return -USB_ERR_INVAL;
}
flags = usb_osal_enter_critical_section();
difference = cdc_acm_class->modem_status ^ notification->data;
cdc_acm_class->modem_status = notification->data;
if (difference & CDC_SERIAL_STATE_TX_CARRIER)
serial->iocount.dsr++;
if (difference & CDC_SERIAL_STATE_RX_CARRIER)
serial->iocount.dsr++;
if (notification->data & CDC_SERIAL_STATE_BREAK)
serial->iocount.brk++;
if (notification->data & CDC_SERIAL_STATE_FRAMING)
serial->iocount.frame++;
if (notification->data & CDC_SERIAL_STATE_PARITY)
serial->iocount.parity++;
if (notification->data & CDC_SERIAL_STATE_OVERRUN)
serial->iocount.overrun++;
usb_osal_leave_critical_section(flags);
return ret;
}
#endif
static const struct usbh_serial_driver cdc_acm_driver = {
.driver_name = "cdc_acm",
.ignore_rx_header = 0,
.ignore_tx_header = 0,
.attach = usbh_cdc_acm_attach,
.detach = usbh_cdc_acm_detach,
.set_flow_control = NULL,
.set_line_coding = usbh_cdc_acm_set_line_coding,
.get_line_coding = usbh_cdc_acm_get_line_coding,
.set_line_state = usbh_cdc_acm_set_line_state,
.get_modem_status = usbh_cdc_acm_get_modem_status,
};
static int usbh_cdc_acm_connect(struct usbh_hubport *hport, uint8_t intf)
{
return usbh_serial_probe(hport, intf, &cdc_acm_driver) ? 0 : -USB_ERR_NOMEM;
}
static int usbh_cdc_acm_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
if (serial) {
usbh_serial_remove(serial);
}
return 0;
}
const struct usbh_class_driver cdc_acm_class_driver = {
.driver_name = "cdc_acm",
.connect = usbh_cdc_acm_connect,
.disconnect = usbh_cdc_acm_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_none_class_info = {
.match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
.bInterfaceClass = USB_DEVICE_CLASS_CDC,
.bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
.bInterfaceProtocol = CDC_COMMON_PROTOCOL_NONE,
.id_table = NULL,
.class_driver = &cdc_acm_class_driver
};
CLASS_INFO_DEFINE const struct usbh_class_info cdc_acm_at_class_info = {
.match_flags = USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
.bInterfaceClass = USB_DEVICE_CLASS_CDC,
.bInterfaceSubClass = CDC_ABSTRACT_CONTROL_MODEL,
.bInterfaceProtocol = CDC_COMMON_PROTOCOL_AT_COMMANDS,
.id_table = NULL,
.class_driver = &cdc_acm_class_driver
};

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) 2022 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_CDC_ACM_H
#define USBH_CDC_ACM_H
#include "usb_cdc.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* USBH_CDC_ACM_H */

410
class/serial/usbh_ch34x.c Normal file
View File

@@ -0,0 +1,410 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#include "usbh_ch34x.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_ch43x"
#include "usb_log.h"
struct usbh_ch34x {
struct usb_endpoint_descriptor *intin;
struct usbh_urb intin_urb;
struct usb_osal_timer *modem_timer;
uint16_t modem_status;
};
/* refer to https://github.com/WCHSoftGroup/ch341ser_linux/blob/main/driver/ch341.c */
static int usbh_ch34x_get_baudrate_div(uint32_t baudrate, uint8_t *factor, uint8_t *divisor)
{
uint8_t a;
uint8_t b;
uint32_t c;
switch (baudrate) {
case 921600:
a = 0xf3;
b = 7;
break;
case 307200:
a = 0xd9;
b = 7;
break;
default:
if (baudrate > 6000000 / 255) {
b = 3;
c = 6000000;
} else if (baudrate > 750000 / 255) {
b = 2;
c = 750000;
} else if (baudrate > 93750 / 255) {
b = 1;
c = 93750;
} else {
b = 0;
c = 11719;
}
a = (uint8_t)(c / baudrate);
if (a == 0 || a == 0xFF) {
return -USB_ERR_INVAL;
}
if ((c / a - baudrate) > (baudrate - c / (a + 1))) {
a++;
}
a = (uint8_t)(256 - a);
break;
}
*factor = a;
*divisor = b;
return 0;
}
static int usbh_ch34x_control_out(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = bRequest;
setup->wValue = wValue;
setup->wIndex = wIndex;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ch34x_control_in(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *data, uint16_t size)
{
struct usb_setup_packet *setup;
int ret;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = bRequest;
setup->wValue = wValue;
setup->wIndex = wIndex;
setup->wLength = size;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return ret;
}
memcpy(data, serial->iobuffer, size);
return ret;
}
static int usbh_ch34x_get_version(struct usbh_serial *serial)
{
int ret;
uint8_t buf[2];
ret = usbh_ch34x_control_in(serial, CH34X_READ_VERSION, 0, 0, buf, 2);
if (ret < 0) {
return ret;
}
USB_LOG_INFO("chip version: 0x%02x\r\n", buf[0]);
return ret;
}
static int usbh_ch34x_attach(struct usbh_serial *serial)
{
struct usb_endpoint_descriptor *ep_desc;
int ret;
struct usbh_ch34x *ch34x_class = usb_osal_malloc(sizeof(struct usbh_ch34x));
if (!ch34x_class) {
USB_LOG_ERR("No memory for ch34x_class\r\n");
return -USB_ERR_NOMEM;
}
memset(ch34x_class, 0, sizeof(struct usbh_ch34x));
serial->priv = ch34x_class;
for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(ch34x_class->intin, ep_desc);
break;
} else {
}
}
}
if (!ch34x_class->intin) {
USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
ret = -USB_ERR_NODEV;
goto errout;
}
ret = usbh_ch34x_get_version(serial);
ret |= usbh_ch34x_control_out(serial, CH34X_SERIAL_INIT, 0, 0);
ret |= usbh_ch34x_control_out(serial, CH34X_WRITE_REG, 0x1312, 0xd982);
ret |= usbh_ch34x_control_out(serial, CH34X_WRITE_REG, 0x0f2c, 0x0007);
if (ret < 0) {
goto errout;
}
return 0;
errout:
serial->priv = NULL;
usb_osal_free(ch34x_class);
return ret;
}
static void usbh_ch34x_detach(struct usbh_serial *serial)
{
struct usbh_ch34x *ch34x_class;
if (!serial || !serial->priv) {
return;
}
ch34x_class = (struct usbh_ch34x *)serial->priv;
if (ch34x_class->intin) {
usbh_kill_urb(&ch34x_class->intin_urb);
}
serial->priv = NULL;
usb_osal_free(ch34x_class);
}
static int usbh_ch34x_set_flow_ctrl(struct usbh_serial *serial, bool hardctrl)
{
return usbh_ch34x_control_out(serial, CH34X_WRITE_REG, 0x2727, hardctrl ? 0x0101 : 0x0000);
}
static int usbh_ch34x_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
uint16_t reg_value = 0;
uint16_t value = 0;
uint16_t index = 0;
uint8_t factor = 0;
uint8_t divisor = 0;
switch (line_coding->bParityType) {
case 0:
break;
case 1:
reg_value |= CH341_L_PO;
break;
case 2:
reg_value |= CH341_L_PE;
break;
case 3:
reg_value |= CH341_L_PM;
break;
case 4:
reg_value |= CH341_L_PS;
break;
default:
return -USB_ERR_INVAL;
}
switch (line_coding->bDataBits) {
case 5:
reg_value |= CH341_L_D5;
break;
case 6:
reg_value |= CH341_L_D6;
break;
case 7:
reg_value |= CH341_L_D7;
break;
case 8:
reg_value |= CH341_L_D8;
break;
default:
return -USB_ERR_INVAL;
}
if (line_coding->bCharFormat == 2) {
reg_value |= CH341_L_SB;
}
reg_value |= 0xC0;
value |= 0x9c;
value |= reg_value << 8;
index |= 0x80 | divisor;
index |= (uint16_t)factor << 8;
usbh_ch34x_get_baudrate_div(line_coding->dwDTERate, &factor, &divisor);
return usbh_ch34x_control_out(serial, CH34X_SERIAL_INIT, value, index);
}
static int usbh_ch34x_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
{
uint16_t value = 0;
uint8_t control = 0;
control = (dtr << 5) | (rts << 6);
value = (uint8_t)~control;
return usbh_ch34x_control_out(serial, CH34X_MODEM_CTRL, value, 0x0000);
}
static int usbh_ch34x_get_modem_status(struct usbh_serial *serial)
{
struct usbh_ch34x *ch34x_class;
uintptr_t flags;
uint16_t status;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
flags = usb_osal_enter_critical_section();
ch34x_class = (struct usbh_ch34x *)serial->priv;
status = (ch34x_class->modem_status & CH341_CTI_DS ? USBH_SERIAL_TIOCM_DSR : 0) |
(ch34x_class->modem_status & CH341_CTI_C ? USBH_SERIAL_TIOCM_CTS : 0) |
(ch34x_class->modem_status & CH341_CTRL_RI ? USBH_SERIAL_TIOCM_RI : 0) |
(ch34x_class->modem_status & CH341_CTI_DC ? USBH_SERIAL_TIOCM_CD : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
usb_osal_leave_critical_section(flags);
return status;
}
#ifdef CONFIG_USBH_SERIAL_GET_MODEM_STATUS
static int __usbh_ch34x_get_modem_status(struct usbh_serial *serial, uint16_t *status)
{
struct usbh_ch34x *ch34x_class;
uint8_t type = 0;
uint8_t data = 0;
uint16_t difference;
uintptr_t flags;
int ret;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
ch34x_class = (struct usbh_ch34x *)serial->priv;
usbh_int_urb_fill(&ch34x_class->intin_urb, serial->hport, ch34x_class->intin, &serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET], ch34x_class->intin->wMaxPacketSize, 0xffffffff, NULL, NULL);
ret = usbh_submit_urb(&ch34x_class->intin_urb);
if (ret < 0) {
return ret;
}
if (ret < 4) {
return -USB_ERR_INVAL;
}
flags = usb_osal_enter_critical_section();
type = serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET];
if (type & CH341_CTT_M) {
data = ~serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET + 2] & CH341_CTI_ST;
difference = data ^ (ch34x_class->modem_status & CH341_CTI_ST);
ch34x_class->modem_status = data;
if (difference) {
if (difference & CH341_CTI_C) {
serial->iocount.cts++;
}
if (difference & CH341_CTI_DS) {
serial->iocount.dsr++;
}
if (difference & CH341_CTRL_RI) {
serial->iocount.rng++;
}
if (difference & CH341_CTI_DC) {
serial->iocount.dcd++;
}
}
}
if (type & CH341_CTT_O) {
serial->iocount.overrun++;
}
if ((type & CH341_CTT_F) == CH341_CTT_F) {
serial->iocount.frame++;
}
if (type & CH341_CTT_P) {
serial->iocount.parity++;
}
usb_osal_leave_critical_section(flags);
return ret;
}
#endif
static const struct usbh_serial_driver ch34x_driver = {
.driver_name = "ch34x",
.ignore_rx_header = 0,
.ignore_tx_header = 0,
.attach = usbh_ch34x_attach,
.detach = usbh_ch34x_detach,
.set_flow_control = usbh_ch34x_set_flow_ctrl,
.set_line_coding = usbh_ch34x_set_line_coding,
.get_line_coding = NULL,
.set_line_state = usbh_ch34x_set_line_state,
.get_modem_status = usbh_ch34x_get_modem_status,
};
static int usbh_ch34x_connect(struct usbh_hubport *hport, uint8_t intf)
{
return usbh_serial_probe(hport, intf, &ch34x_driver) ? 0 : -USB_ERR_NOMEM;
}
static int usbh_ch34x_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
if (serial) {
usbh_serial_remove(serial);
}
return 0;
}
static const uint16_t ch34x_id_table[][2] = {
{ 0x1A86, 0x7523 }, /* ch340 chip */
{ 0x1A86, 0x7522 }, /* ch340k chip */
{ 0x1A86, 0x5523 }, /* ch341 chip */
{ 0x1A86, 0xe523 }, /* ch330 chip */
{ 0x4348, 0x5523 }, /* ch340 custom chip */
{ 0, 0 },
};
const struct usbh_class_driver ch34x_class_driver = {
.driver_name = "ch34x",
.connect = usbh_ch34x_connect,
.disconnect = usbh_ch34x_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info ch34x_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = ch34x_id_table,
.class_driver = &ch34x_class_driver
};

56
class/serial/usbh_ch34x.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_CH34X_H
#define USBH_CH34X_H
#include "usb_cdc.h"
/* Requests */
#define CH34X_READ_VERSION 0x5F
#define CH34X_WRITE_REG 0x9A
#define CH34X_READ_REG 0x95
#define CH34X_SERIAL_INIT 0xA1
#define CH34X_MODEM_CTRL 0xA4
// modem control bits
#define CH34X_BIT_RTS (1 << 6)
#define CH34X_BIT_DTR (1 << 5)
#define CH341_CTO_O 0x10
#define CH341_CTO_D 0x20
#define CH341_CTO_R 0x40
#define CH341_CTI_C 0x01
#define CH341_CTI_DS 0x02
#define CH341_CTRL_RI 0x04
#define CH341_CTI_DC 0x08
#define CH341_CTI_ST 0x0f
#define CH341_CTT_M BIT(3)
#define CH341_CTT_F (BIT(2) | BIT(6))
#define CH341_CTT_P BIT(2)
#define CH341_CTT_O BIT(1)
#define CH341_L_ER 0x80
#define CH341_L_ET 0x40
#define CH341_L_PS 0x38
#define CH341_L_PM 0x28
#define CH341_L_PE 0x18
#define CH341_L_PO 0x08
#define CH341_L_SB 0x04
#define CH341_L_D8 0x03
#define CH341_L_D7 0x02
#define CH341_L_D6 0x01
#define CH341_L_D5 0x00
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* USBH_CH34X_H */

510
class/serial/usbh_cp210x.c Normal file
View File

@@ -0,0 +1,510 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#include "usbh_cp210x.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_cp210x"
#include "usb_log.h"
struct usbh_cp210x {
uint8_t partnum;
uint32_t fw_version;
uint32_t min_speed;
uint32_t max_speed;
bool use_actual_rate;
bool no_flow_control;
bool no_event_mode;
};
struct cp210x_rate {
uint32_t rate;
uint32_t high;
};
static const struct cp210x_rate cp210x_an205_table1[] = {
{ 300, 300 },
{ 600, 600 },
{ 1200, 1200 },
{ 1800, 1800 },
{ 2400, 2400 },
{ 4000, 4000 },
{ 4800, 4803 },
{ 7200, 7207 },
{ 9600, 9612 },
{ 14400, 14428 },
{ 16000, 16062 },
{ 19200, 19250 },
{ 28800, 28912 },
{ 38400, 38601 },
{ 51200, 51558 },
{ 56000, 56280 },
{ 57600, 58053 },
{ 64000, 64111 },
{ 76800, 77608 },
{ 115200, 117028 },
{ 128000, 129347 },
{ 153600, 156868 },
{ 230400, 237832 },
{ 250000, 254234 },
{ 256000, 273066 },
{ 460800, 491520 },
{ 500000, 567138 },
{ 576000, 670254 },
{ 921600, 0xffffffff }
};
/*
* Quantises the baud rate as per AN205 Table 1
*/
static uint32_t cp210x_get_an205_rate(uint32_t baud)
{
int i;
for (i = 0; i < ARRAY_SIZE(cp210x_an205_table1); ++i) {
if (baud <= cp210x_an205_table1[i].high)
break;
}
return cp210x_an205_table1[i].rate;
}
static uint32_t cp210x_get_actual_rate(uint32_t baud)
{
unsigned int prescale = 1;
unsigned int div;
if (baud <= 365)
prescale = 4;
div = DIV_ROUND_CLOSEST(48000000, 2 * prescale * baud);
baud = 48000000 / (2 * prescale * div);
return baud;
}
static void usbh_cp210x_init_max_speed(struct usbh_serial *serial)
{
struct usbh_cp210x *cp210x_class;
if (!serial || !serial->hport || !serial->priv) {
return;
}
cp210x_class = (struct usbh_cp210x *)serial->priv;
bool use_actual_rate = false;
uint32_t min = 300;
uint32_t max;
switch (cp210x_class->partnum) {
case CP210X_PARTNUM_CP2101:
max = 921600;
break;
case CP210X_PARTNUM_CP2102:
case CP210X_PARTNUM_CP2103:
max = 1000000;
break;
case CP210X_PARTNUM_CP2104:
use_actual_rate = true;
max = 2000000;
break;
case CP210X_PARTNUM_CP2108:
max = 2000000;
break;
case CP210X_PARTNUM_CP2105:
if (serial->intf == 0) {
use_actual_rate = true;
max = 2000000; /* ECI */
} else {
min = 2400;
max = 921600; /* SCI */
}
break;
case CP210X_PARTNUM_CP2102N_QFN28:
case CP210X_PARTNUM_CP2102N_QFN24:
case CP210X_PARTNUM_CP2102N_QFN20:
use_actual_rate = true;
max = 3000000;
break;
default:
max = 2000000;
break;
}
cp210x_class->min_speed = min;
cp210x_class->max_speed = max;
cp210x_class->use_actual_rate = use_actual_rate;
}
static int usbh_cp210x_control_out(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *data, uint16_t size)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = bRequest;
setup->wValue = wValue;
setup->wIndex = wIndex;
setup->wLength = size;
if (data && size) {
memcpy(serial->iobuffer, data, size);
return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
} else {
return usbh_control_transfer(serial->hport, setup, NULL);
}
}
static int usbh_cp210x_control_in(struct usbh_serial *serial, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *data, uint16_t size)
{
struct usb_setup_packet *setup;
int ret;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = bRequest;
setup->wValue = wValue;
setup->wIndex = wIndex;
setup->wLength = size;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return ret;
}
memcpy(data, serial->iobuffer, size);
return ret;
}
static int usbh_cp210x_get_partnum(struct usbh_serial *serial)
{
uint8_t version[3];
struct usbh_cp210x *cp210x_class;
int ret;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
cp210x_class = (struct usbh_cp210x *)serial->priv;
ret = usbh_cp210x_control_in(serial, CP210X_VENDOR_SPECIFIC, CP210X_GET_PARTNUM, serial->intf, (uint8_t *)&cp210x_class->partnum, 1);
if (ret < 0) {
return ret;
}
USB_LOG_INFO("chip partnum: 0x%02x\r\n", cp210x_class->partnum);
switch (cp210x_class->partnum) {
case CP210X_PARTNUM_CP2102:
break;
case CP210X_PARTNUM_CP2105:
case CP210X_PARTNUM_CP2108:
ret = usbh_cp210x_control_in(serial, CP210X_VENDOR_SPECIFIC, CP210X_GET_FW_VER_2N, serial->intf, version, 3);
if (ret < 0) {
return ret;
}
cp210x_class->fw_version = version[0] << 16 | version[1] << 8 | version[2];
break;
case CP210X_PARTNUM_CP2102N_QFN28:
case CP210X_PARTNUM_CP2102N_QFN24:
case CP210X_PARTNUM_CP2102N_QFN20:
ret = usbh_cp210x_control_in(serial, CP210X_VENDOR_SPECIFIC, CP210X_GET_FW_VER_2N, serial->intf, version, 3);
if (ret < 0) {
return ret;
}
cp210x_class->fw_version = version[0] << 16 | version[1] << 8 | version[2];
if (cp210x_class->fw_version <= 0x10004)
cp210x_class->no_flow_control = true;
break;
default:
break;
}
return ret;
}
static int usbh_cp210x_enable(struct usbh_serial *serial)
{
return usbh_cp210x_control_out(serial, CP210X_IFC_ENABLE, 1, serial->intf, NULL, 0);
}
static int usbh_cp210x_set_chars(struct usbh_serial *serial)
{
struct cp210x_special_chars chars = { 0 };
return usbh_cp210x_control_out(serial, CP210X_SET_CHARS, 0, serial->intf, (uint8_t *)&chars, sizeof(struct cp210x_special_chars));
}
// static int usbh_cp210x_get_common_status(struct usbh_serial *serial, struct cp210x_comm_status *status)
// {
// return usbh_cp210x_control_in(serial, CP210X_GET_COMM_STATUS, 0, serial->intf, (uint8_t *)status, sizeof(struct cp210x_comm_status));
// }
static int usbh_cp210x_set_baudrate(struct usbh_serial *serial, uint32_t baudrate)
{
struct usb_setup_packet *setup;
struct usbh_cp210x *cp210x_class;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
cp210x_class = (struct usbh_cp210x *)serial->priv;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_BAUDRATE;
setup->wValue = 0;
setup->wIndex = serial->intf;
setup->wLength = 4;
if (cp210x_class->use_actual_rate)
baudrate = cp210x_get_actual_rate(baudrate);
else if (baudrate < 1000000)
baudrate = cp210x_get_an205_rate(baudrate);
memcpy(serial->iobuffer, (uint8_t *)&baudrate, 4);
return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
}
static int usbh_cp210x_set_data_format(struct usbh_serial *serial, uint8_t databits, uint8_t parity, uint8_t stopbits)
{
struct usb_setup_packet *setup;
uint16_t value;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
value = ((databits & 0x0F) << 8) | ((parity & 0x0f) << 4) | ((stopbits & 0x03) << 0);
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_LINE_CTL;
setup->wValue = value;
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_cp210x_attach(struct usbh_serial *serial)
{
struct cp210x_comm_status status = { 0 };
int ret;
struct usbh_cp210x *cp210x_class = usb_osal_malloc(sizeof(struct usbh_cp210x));
if (!cp210x_class) {
return -USB_ERR_NOMEM;
}
memset(cp210x_class, 0, sizeof(struct usbh_cp210x));
serial->priv = cp210x_class;
ret = usbh_cp210x_get_partnum(serial);
usbh_cp210x_init_max_speed(serial);
ret |= usbh_cp210x_enable(serial);
ret |= usbh_cp210x_set_chars(serial);
if (ret < 0) {
goto errout;
}
USB_LOG_INFO("ulAmountInInQueue: %u, ulAmountInOutQueue: %u\r\n", (unsigned int)status.ulAmountInInQueue, (unsigned int)status.ulAmountInOutQueue);
return 0;
errout:
serial->priv = NULL;
usb_osal_free(cp210x_class);
return ret;
}
static void usbh_cp210x_detach(struct usbh_serial *serial)
{
if (serial && serial->priv) {
serial->priv = NULL;
usb_osal_free(serial->priv);
}
}
int usbh_cp210x_set_flow_ctrl(struct usbh_serial *serial, bool enable)
{
struct cp210x_flow_ctl flow_ctl = { 0 };
uint32_t flow_repl;
uint32_t ctl_hs;
int ret;
ret = usbh_cp210x_control_in(serial, CP210X_GET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
if (ret < 0) {
return ret;
}
ctl_hs = flow_ctl.lControlHandshake;
flow_repl = flow_ctl.lFlowReplace;
ctl_hs &= ~CP210X_SERIAL_DSR_HANDSHAKE;
ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE;
ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY;
ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
flow_repl &= ~CP210X_SERIAL_RTS_MASK;
flow_repl &= ~CP210X_SERIAL_AUTO_RECEIVE;
flow_repl &= ~CP210X_SERIAL_AUTO_TRANSMIT;
flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
flow_repl &= ~CP210X_SERIAL_RTS_MASK;
if (enable) {
ctl_hs |= CP210X_SERIAL_CTS_HANDSHAKE;
} else {
ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE;
}
flow_ctl.lControlHandshake = ctl_hs;
flow_ctl.lFlowReplace = flow_repl;
return usbh_cp210x_control_out(serial, CP210X_SET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
}
int usbh_cp210x_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
int ret;
ret = usbh_cp210x_set_baudrate(serial, line_coding->dwDTERate);
if (ret < 0) {
return ret;
}
return usbh_cp210x_set_data_format(serial, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat);
}
int usbh_cp210x_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
{
struct cp210x_flow_ctl flow_ctl = { 0 };
uint32_t flow_repl;
uint32_t ctl_hs;
uint16_t control = 0;
int ret;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
if (serial->rtscts) {
ret = usbh_cp210x_control_in(serial, CP210X_GET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
if (ret < 0) {
return ret;
}
ctl_hs = flow_ctl.lControlHandshake;
flow_repl = flow_ctl.lFlowReplace;
ctl_hs &= ~CP210X_SERIAL_DTR_MASK;
if (dtr)
ctl_hs |= CP210X_SERIAL_DTR_ACTIVE;
else
ctl_hs |= CP210X_SERIAL_DTR_INACTIVE;
flow_repl &= ~CP210X_SERIAL_RTS_MASK;
if (rts)
flow_repl |= CP210X_SERIAL_RTS_FLOW_CTL;
else
flow_repl |= CP210X_SERIAL_RTS_INACTIVE;
flow_ctl.lControlHandshake = ctl_hs;
flow_ctl.lFlowReplace = flow_repl;
return usbh_cp210x_control_out(serial, CP210X_SET_FLOW, 0, serial->intf, (uint8_t *)&flow_ctl, sizeof(struct cp210x_flow_ctl));
} else {
if (dtr) {
control |= CP210X_CONTROL_DTR;
}
if (rts) {
control |= CP210X_CONTROL_RTS;
}
control |= CP210X_CONTROL_WRITE_DTR;
control |= CP210X_CONTROL_WRITE_RTS;
return usbh_cp210x_control_out(serial, CP210X_SET_MHS, control, serial->intf, NULL, 0);
}
}
static int usbh_cp210x_get_modem_status(struct usbh_serial *serial)
{
int ret;
uint8_t control;
uint16_t status;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
ret = usbh_cp210x_control_in(serial, CP210X_GET_MDMSTS, 0, serial->intf, (uint8_t *)&control, 1);
if (ret < 0) {
return ret;
}
status = ((control & CP210X_CONTROL_DTR) ? USBH_SERIAL_TIOCM_DTR : 0) |
((control & CP210X_CONTROL_RTS) ? USBH_SERIAL_TIOCM_RTS : 0) |
((control & CP210X_CONTROL_CTS) ? USBH_SERIAL_TIOCM_CTS : 0) |
((control & CP210X_CONTROL_DSR) ? USBH_SERIAL_TIOCM_DSR : 0) |
((control & CP210X_CONTROL_RING) ? USBH_SERIAL_TIOCM_RI : 0) |
((control & CP210X_CONTROL_DCD) ? USBH_SERIAL_TIOCM_CD : 0);
return status;
}
static const struct usbh_serial_driver cp210x_driver = {
.driver_name = "cp210x",
.ignore_rx_header = 0,
.ignore_tx_header = 0,
.attach = usbh_cp210x_attach,
.detach = usbh_cp210x_detach,
.set_flow_control = usbh_cp210x_set_flow_ctrl,
.set_line_coding = usbh_cp210x_set_line_coding,
.get_line_coding = NULL,
.set_line_state = usbh_cp210x_set_line_state,
.get_modem_status = usbh_cp210x_get_modem_status,
};
static int usbh_cp210x_connect(struct usbh_hubport *hport, uint8_t intf)
{
return usbh_serial_probe(hport, intf, &cp210x_driver) ? 0 : -USB_ERR_NOMEM;
}
static int usbh_cp210x_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
if (serial) {
usbh_serial_remove(serial);
}
return 0;
}
static const uint16_t cp210x_id_table[][2] = {
{ 0x10C4, 0xEA60 },
{ 0, 0 },
};
const struct usbh_class_driver cp210x_class_driver = {
.driver_name = "cp210x",
.connect = usbh_cp210x_connect,
.disconnect = usbh_cp210x_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info cp210x_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = cp210x_id_table,
.class_driver = &cp210x_class_driver
};

187
class/serial/usbh_cp210x.h Normal file
View File

@@ -0,0 +1,187 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_CP210X_H
#define USBH_CP210X_H
#include "usb_cdc.h"
/* Requests */
#define CP210X_IFC_ENABLE 0x00
#define CP210X_SET_BAUDDIV 0x01
#define CP210X_GET_BAUDDIV 0x02
#define CP210X_SET_LINE_CTL 0x03 // Set parity, data bits, stop bits
#define CP210X_GET_LINE_CTL 0x04
#define CP210X_SET_BREAK 0x05
#define CP210X_IMM_CHAR 0x06
#define CP210X_SET_MHS 0x07 // Set DTR, RTS
#define CP210X_GET_MDMSTS 0x08
#define CP210X_SET_XON 0x09
#define CP210X_SET_XOFF 0x0A
#define CP210X_SET_EVENTMASK 0x0B
#define CP210X_GET_EVENTMASK 0x0C
#define CP210X_SET_CHAR 0x0D
#define CP210X_GET_CHARS 0x0E
#define CP210X_GET_PROPS 0x0F
#define CP210X_GET_COMM_STATUS 0x10
#define CP210X_RESET 0x11
#define CP210X_PURGE 0x12
#define CP210X_SET_FLOW 0x13
#define CP210X_GET_FLOW 0x14
#define CP210X_EMBED_EVENTS 0x15
#define CP210X_GET_EVENTSTATE 0x16
#define CP210X_SET_CHARS 0x19
#define CP210X_GET_BAUDRATE 0x1D
#define CP210X_SET_BAUDRATE 0x1E // Set baudrate
#define CP210X_VENDOR_SPECIFIC 0xFF
/* CP210X_VENDOR_SPECIFIC values */
#define CP210X_GET_FW_VER 0x000E
#define CP210X_READ_2NCONFIG 0x000E
#define CP210X_GET_FW_VER_2N 0x0010
#define CP210X_READ_LATCH 0x00C2
#define CP210X_GET_PARTNUM 0x370B
#define CP210X_GET_PORTCONFIG 0x370C
#define CP210X_GET_DEVICEMODE 0x3711
#define CP210X_WRITE_LATCH 0x37E1
/* CP210X_IFC_ENABLE */
#define CP210X_UART_ENABLE 0x0001
#define CP210X_UART_DISABLE 0x0000
/* CP210X_(SET|GET)_BAUDDIV */
#define CP210X_BAUD_RATE_GEN_FREQ 0x384000
/* CP210X_(SET|GET)_LINE_CTL */
#define CP210X_BITS_DATA_MASK 0X0f00
#define CP210X_BITS_DATA_5 0X0500
#define CP210X_BITS_DATA_6 0X0600
#define CP210X_BITS_DATA_7 0X0700
#define CP210X_BITS_DATA_8 0X0800
#define CP210X_BITS_DATA_9 0X0900
#define CP210X_BITS_PARITY_MASK 0x00f0
#define CP210X_BITS_PARITY_NONE 0x0000
#define CP210X_BITS_PARITY_ODD 0x0010
#define CP210X_BITS_PARITY_EVEN 0x0020
#define CP210X_BITS_PARITY_MARK 0x0030
#define CP210X_BITS_PARITY_SPACE 0x0040
#define CP210X_BITS_STOP_MASK 0x000f
#define CP210X_BITS_STOP_1 0x0000
#define CP210X_BITS_STOP_1_5 0x0001
#define CP210X_BITS_STOP_2 0x0002
/* CP210X_SET_BREAK */
#define CP210X_BREAK_ON 0x0001
#define CP210X_BREAK_OFF 0x0000
/* CP210X_(SET_MHS|GET_MDMSTS) */
#define CP210X_CONTROL_DTR 0x0001
#define CP210X_CONTROL_RTS 0x0002
#define CP210X_CONTROL_CTS 0x0010
#define CP210X_CONTROL_DSR 0x0020
#define CP210X_CONTROL_RING 0x0040
#define CP210X_CONTROL_DCD 0x0080
#define CP210X_CONTROL_WRITE_DTR 0x0100
#define CP210X_CONTROL_WRITE_RTS 0x0200
/* CP210X_(GET|SET)_CHARS */
struct cp210x_special_chars {
uint8_t bEofChar;
uint8_t bErrorChar;
uint8_t bBreakChar;
uint8_t bEventChar;
uint8_t bXonChar;
uint8_t bXoffChar;
};
/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */
struct cp210x_comm_status {
uint32_t ulErrors;
uint32_t ulHoldReasons;
uint32_t ulAmountInInQueue;
uint32_t ulAmountInOutQueue;
uint8_t bEofReceived;
uint8_t bWaitForImmediate;
uint8_t bReserved;
} __PACKED;
/*
* CP210X_PURGE - 16 bits passed in wValue of USB request.
* SiLabs app note AN571 gives a strange description of the 4 bits:
* bit 0 or bit 2 clears the transmit queue and 1 or 3 receive.
* writing 1 to all, however, purges cp2108 well enough to avoid the hang.
*/
#define PURGE_ALL 0x000f
/* CP210X_EMBED_EVENTS */
#define CP210X_ESCCHAR 0xec
#define CP210X_LSR_OVERRUN BIT(1)
#define CP210X_LSR_PARITY BIT(2)
#define CP210X_LSR_FRAME BIT(3)
#define CP210X_LSR_BREAK BIT(4)
/* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */
struct cp210x_flow_ctl {
uint32_t lControlHandshake;
uint32_t lFlowReplace;
uint32_t lXonLimit;
uint32_t lXoffLimit;
};
/* cp210x_flow_ctl::ulControlHandshake */
#define CP210X_SERIAL_DTR_MASK (0x03 << 0)
#define CP210X_SERIAL_DTR_INACTIVE (0 << 0)
#define CP210X_SERIAL_DTR_ACTIVE (1 << 0)
#define CP210X_SERIAL_DTR_FLOW_CTL (2 << 0)
#define CP210X_SERIAL_CTS_HANDSHAKE BIT(3)
#define CP210X_SERIAL_DSR_HANDSHAKE BIT(4)
#define CP210X_SERIAL_DCD_HANDSHAKE BIT(5)
#define CP210X_SERIAL_DSR_SENSITIVITY BIT(6)
/* cp210x_flow_ctl::ulFlowReplace */
#define CP210X_SERIAL_AUTO_TRANSMIT BIT(0)
#define CP210X_SERIAL_AUTO_RECEIVE BIT(1)
#define CP210X_SERIAL_ERROR_CHAR BIT(2)
#define CP210X_SERIAL_NULL_STRIPPING BIT(3)
#define CP210X_SERIAL_BREAK_CHAR BIT(4)
#define CP210X_SERIAL_RTS_MASK (0x03 << 6)
#define CP210X_SERIAL_RTS_INACTIVE (0 << 6)
#define CP210X_SERIAL_RTS_ACTIVE (1 << 6)
#define CP210X_SERIAL_RTS_FLOW_CTL (2 << 6)
#define CP210X_SERIAL_XOFF_CONTINUE BIT(31)
/* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */
struct cp210x_pin_mode {
uint8_t eci;
uint8_t sci;
};
#define CP210X_PIN_MODE_MODEM 0
#define CP210X_PIN_MODE_GPIO BIT(0)
/* Part number definitions */
#define CP210X_PARTNUM_CP2101 0x01
#define CP210X_PARTNUM_CP2102 0x02
#define CP210X_PARTNUM_CP2103 0x03
#define CP210X_PARTNUM_CP2104 0x04
#define CP210X_PARTNUM_CP2105 0x05
#define CP210X_PARTNUM_CP2108 0x08
#define CP210X_PARTNUM_CP2102N_QFN28 0x20
#define CP210X_PARTNUM_CP2102N_QFN24 0x21
#define CP210X_PARTNUM_CP2102N_QFN20 0x22
#define CP210X_PARTNUM_UNKNOWN 0xFF
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* USBH_CP210X_H */

407
class/serial/usbh_ftdi.c Normal file
View File

@@ -0,0 +1,407 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#include "usbh_ftdi.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_ftdi"
#include "usb_log.h"
enum ftdi_chip_type {
SIO,
FT232A,
FT232B,
FT2232C,
FT232R,
FT232H,
FT2232H,
FT4232H,
FT4232HA,
FT232HP,
FT233HP,
FT2232HP,
FT2233HP,
FT4232HP,
FT4233HP,
FTX,
};
static const char *ftdi_chip_name[] = {
[SIO] = "SIO", /* the serial part of FT8U100AX */
[FT232A] = "FT232A",
[FT232B] = "FT232B",
[FT2232C] = "FT2232C/D",
[FT232R] = "FT232R",
[FT232H] = "FT232H",
[FT2232H] = "FT2232H",
[FT4232H] = "FT4232H",
[FT4232HA] = "FT4232HA",
[FT232HP] = "FT232HP",
[FT233HP] = "FT233HP",
[FT2232HP] = "FT2232HP",
[FT2233HP] = "FT2233HP",
[FT4232HP] = "FT4232HP",
[FT4233HP] = "FT4233HP",
[FTX] = "FT-X",
};
struct usbh_ftdi {
enum ftdi_chip_type chip_type;
};
static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, int base)
{
static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
uint32_t divisor;
/* divisor shifted 3 bits to the left */
int divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud);
divisor = divisor3 >> 3;
divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
/* Deal with special cases for highest baud rates. */
if (divisor == 1) /* 1.0 */
divisor = 0;
else if (divisor == 0x4001) /* 1.5 */
divisor = 1;
return divisor;
}
static uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud)
{
return ftdi_232bm_baud_base_to_divisor(baud, 48000000);
}
static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, int base)
{
static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
uint32_t divisor;
int divisor3;
/* hi-speed baud rate is 10-bit sampling instead of 16-bit */
divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * baud);
divisor = divisor3 >> 3;
divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
/* Deal with special cases for highest baud rates. */
if (divisor == 1) /* 1.0 */
divisor = 0;
else if (divisor == 0x4001) /* 1.5 */
divisor = 1;
/*
* Set this bit to turn off a divide by 2.5 on baud rate generator
* This enables baud rates up to 12Mbaud but cannot reach below 1200
* baud with this bit set
*/
divisor |= 0x00020000;
return divisor;
}
static uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud)
{
return ftdi_2232h_baud_base_to_divisor(baud, 120000000);
}
int usbh_ftdi_reset(struct usbh_serial *serial)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_RESET;
setup->wValue = 0;
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ftdi_set_baudrate(struct usbh_serial *serial, uint32_t baudrate)
{
struct usb_setup_packet *setup;
struct usbh_ftdi *ftdi_class;
uint32_t div_value;
uint16_t value;
uint8_t baudrate_high;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
ftdi_class = (struct usbh_ftdi *)serial->priv;
switch (ftdi_class->chip_type) {
case FT232B:
case FT2232C:
case FT232R:
if (baudrate > 3000000) {
return -USB_ERR_INVAL;
}
div_value = ftdi_232bm_baud_to_divisor(baudrate);
break;
default:
if ((baudrate <= 12000000) && (baudrate >= 1200)) {
div_value = ftdi_2232h_baud_to_divisor(baudrate);
} else if (baudrate < 1200) {
div_value = ftdi_232bm_baud_to_divisor(baudrate);
} else {
return -USB_ERR_INVAL;
}
break;
}
value = div_value & 0xFFFF;
baudrate_high = (div_value >> 16) & 0xff;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_SET_BAUDRATE;
setup->wValue = value;
setup->wIndex = (baudrate_high << 8) | serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ftdi_set_data_format(struct usbh_serial *serial, uint8_t databits, uint8_t parity, uint8_t stopbits, uint8_t isbreak)
{
/**
* D0-D7 databits BITS_7=7, BITS_8=8
* D8-D10 parity NONE=0, ODD=1, EVEN=2, MARK=3, SPACE=4
* D11-D12 STOP_BIT_1=0, STOP_BIT_15=1, STOP_BIT_2=2
* D14 BREAK_OFF=0, BREAK_ON=1
**/
struct usb_setup_packet *setup;
uint16_t value;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
value = ((isbreak & 0x01) << 14) | ((stopbits & 0x03) << 11) | ((parity & 0x0f) << 8) | (databits & 0x0f);
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_SET_DATA;
setup->wValue = value;
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ftdi_set_latency_timer(struct usbh_serial *serial, uint16_t value)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_SET_LATENCY_TIMER;
setup->wValue = value;
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ftdi_attach(struct usbh_serial *serial)
{
uint16_t version;
uint8_t chip_type;
int ret;
version = serial->hport->device_desc.bcdDevice;
switch (version) {
case 0x400:
chip_type = FT232B;
break;
case 0x500:
chip_type = FT2232C;
break;
case 0x600:
chip_type = FT232R;
break;
case 0x700:
chip_type = FT2232H;
break;
case 0x900:
chip_type = FT232H;
break;
default:
USB_LOG_ERR("Unsupported FTDI chip version: 0x%04x\r\n", version);
return -USB_ERR_NOTSUPP;
}
USB_LOG_INFO("chip name: %s\r\n", ftdi_chip_name[chip_type]);
struct usbh_ftdi *ftdi_class = usb_osal_malloc(sizeof(struct usbh_ftdi));
if (!ftdi_class) {
USB_LOG_ERR("No memory for ftdi_class\r\n");
return -USB_ERR_NOMEM;
}
memset(ftdi_class, 0, sizeof(struct usbh_ftdi));
serial->priv = ftdi_class;
ftdi_class->chip_type = chip_type;
ret = usbh_ftdi_set_latency_timer(serial, 0x10);
if (ret < 0) {
goto errout;
}
return 0;
errout:
serial->priv = NULL;
usb_osal_free(ftdi_class);
return ret;
}
static void usbh_ftdi_detach(struct usbh_serial *serial)
{
if (serial && serial->priv) {
serial->priv = NULL;
usb_osal_free(serial->priv);
}
}
static int usbh_ftdi_set_flow_ctrl(struct usbh_serial *serial, bool hardctrl)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_SET_FLOW_CTRL;
setup->wValue = hardctrl ? FTDI_SIO_RTS_CTS_HS : FTDI_SIO_DISABLE_FLOW_CTRL;
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ftdi_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
int ret = usbh_ftdi_set_baudrate(serial, line_coding->dwDTERate);
if (ret < 0) {
return ret;
}
return usbh_ftdi_set_data_format(serial, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat, 0);
}
static int usbh_ftdi_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
{
struct usb_setup_packet *setup;
uint16_t value = 0;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
value = ((dtr ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW) | (rts ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW));
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_SET_MODEM_CTRL;
setup->wValue = value;
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_ftdi_get_modem_status(struct usbh_serial *serial)
{
struct usb_setup_packet *setup;
uint16_t status = 0;
int ret;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = FTDI_SIO_GET_MODEM_STATUS;
setup->wValue = 0x0000;
setup->wIndex = serial->intf;
setup->wLength = 2;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return 0;
}
status = (serial->iobuffer[0] & FTDI_SIO_DSR_MASK ? USBH_SERIAL_TIOCM_DSR : 0) |
(serial->iobuffer[0] & FTDI_SIO_CTS_MASK ? USBH_SERIAL_TIOCM_CTS : 0) |
(serial->iobuffer[0] & FTDI_SIO_RI_MASK ? USBH_SERIAL_TIOCM_RI : 0) |
(serial->iobuffer[0] & FTDI_SIO_RLSD_MASK ? USBH_SERIAL_TIOCM_CD : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
return status;
}
static const struct usbh_serial_driver ftdi_driver = {
.driver_name = "ftdi",
.ignore_rx_header = 2,
.ignore_tx_header = 0,
.attach = usbh_ftdi_attach,
.detach = usbh_ftdi_detach,
.set_flow_control = usbh_ftdi_set_flow_ctrl,
.set_line_coding = usbh_ftdi_set_line_coding,
.get_line_coding = NULL,
.set_line_state = usbh_ftdi_set_line_state,
.get_modem_status = usbh_ftdi_get_modem_status,
};
static int usbh_ftdi_connect(struct usbh_hubport *hport, uint8_t intf)
{
return usbh_serial_probe(hport, intf, &ftdi_driver) ? 0 : -USB_ERR_NOMEM;
}
static int usbh_ftdi_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
if (serial) {
usbh_serial_remove(serial);
}
return 0;
}
static const uint16_t ftdi_id_table[][2] = {
{ 0x0403, 0x6001 },
{ 0x0403, 0x6010 },
{ 0x0403, 0x6014 },
{ 0, 0 },
};
const struct usbh_class_driver ftdi_class_driver = {
.driver_name = "ftdi",
.connect = usbh_ftdi_connect,
.disconnect = usbh_ftdi_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info ftdi_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = ftdi_id_table,
.class_driver = &ftdi_class_driver
};

341
class/serial/usbh_ftdi.h Normal file
View File

@@ -0,0 +1,341 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_FTDI_H
#define USBH_FTDI_H
#include "usb_cdc.h"
#define FTDI_VID 0x0403 /* Vendor Id */
/* FTDI device PIDs */
#define FTDI_8U232AM_PID 0x6001 /* Similar device to SIO above */
#define FTDI_8U232AM_ALT_PID 0x6006 /* FTDI's alternate PID for above */
#define FTDI_8U2232C_PID 0x6010 /* Dual channel device */
#define FTDI_4232H_PID 0x6011 /* Quad channel hi-speed device */
#define FTDI_232H_PID 0x6014 /* Single channel hi-speed device */
#define FTDI_FTX_PID 0x6015 /* FT-X series (FT201X, FT230X, FT231X, etc) */
#define FTDI_FT2233HP_PID 0x6040 /* Dual channel hi-speed device with PD */
#define FTDI_FT4233HP_PID 0x6041 /* Quad channel hi-speed device with PD */
#define FTDI_FT2232HP_PID 0x6042 /* Dual channel hi-speed device with PD */
#define FTDI_FT4232HP_PID 0x6043 /* Quad channel hi-speed device with PD */
#define FTDI_FT233HP_PID 0x6044 /* Dual channel hi-speed device with PD */
#define FTDI_FT232HP_PID 0x6045 /* Dual channel hi-speed device with PD */
#define FTDI_FT4232HA_PID 0x6048 /* Quad channel automotive grade hi-speed device */
#define FTDI_SIO_PID 0x8372 /* Product Id SIO application of 8U100AX */
#define FTDI_232RL_PID 0xFBFA /* Product ID for FT232RL */
/* Requests */
#define FTDI_SIO_RESET 0x00 /* Reset the port */
#define FTDI_SIO_SET_MODEM_CTRL 0x01 /* Set the modem control register */
#define FTDI_SIO_SET_FLOW_CTRL 0x02 /* Set flow control register */
#define FTDI_SIO_SET_BAUDRATE 0x03 /* Set baud rate */
#define FTDI_SIO_SET_DATA 0x04 /* Set the data characteristics of the port */
#define FTDI_SIO_GET_MODEM_STATUS 0x05
#define FTDI_SIO_SET_EVENT_CHAR 0x06
#define FTDI_SIO_SET_ERROR_CHAR 0x07
#define FTDI_SIO_SET_LATENCY_TIMER 0x09
#define FTDI_SIO_GET_LATENCY_TIMER 0x0A
#define FTDI_SIO_SET_BITMODE 0x0B
#define FTDI_SIO_READ_PINS 0x0C
#define FTDI_SIO_READ_EEPROM 0x90
#define FTDI_SIO_WRITE_EEPROM 0x91
#define FTDI_SIO_ERASE_EEPROM 0x92
/* Channel indices for FT2232, FT2232H and FT4232H devices */
#define FTDI_SIO_CHANNEL_A 1
#define FTDI_SIO_CHANNEL_B 2
#define FTDI_SIO_CHANNEL_C 3
#define FTDI_SIO_CHANNEL_D 4
/*
* BmRequestType: 0100 0000B
* bRequest: FTDI_SIO_RESET
* wValue: Control Value
* 0 = Reset SIO
* 1 = Purge RX buffer
* 2 = Purge TX buffer
* wIndex: Port
* wLength: 0
* Data: None
*
* The Reset SIO command has this effect:
*
* Sets flow control set to 'none'
* Event char = $0D
* Event trigger = disabled
* Purge RX buffer
* Purge TX buffer
* Clear DTR
* Clear RTS
* baud and data format not reset
*
* The Purge RX and TX buffer commands affect nothing except the buffers
*
*/
#define FTDI_SIO_RESET_SIO 0
#define FTDI_SIO_RESET_PURGE_RX 1
#define FTDI_SIO_RESET_PURGE_TX 2
/*
* BmRequestType: 0100 0000B
* bRequest: FTDI_SIO_SET_BAUDRATE
* wValue: BaudDivisor value - see below
* wIndex: Port
* wLength: 0
* Data: None
* The BaudDivisor values are calculated as follows:
* - BaseClock is either 12000000 or 48000000 depending on the device.
* FIXME: I wish I knew how to detect old chips to select proper base clock!
* - BaudDivisor is a fixed point number encoded in a funny way.
* (--WRONG WAY OF THINKING--)
* BaudDivisor is a fixed point number encoded with following bit weighs:
* (-2)(-1)(13..0). It is a radical with a denominator of 4, so values
* end with 0.0 (00...), 0.25 (10...), 0.5 (01...), and 0.75 (11...).
* (--THE REALITY--)
* The both-bits-set has quite different meaning from 0.75 - the chip
* designers have decided it to mean 0.125 instead of 0.75.
* This info looked up in FTDI application note "FT8U232 DEVICES \ Data Rates
* and Flow Control Consideration for USB to RS232".
* - BaudDivisor = (BaseClock / 16) / BaudRate, where the (=) operation should
* automagically re-encode the resulting value to take fractions into
* consideration.
* As all values are integers, some bit twiddling is in order:
* BaudDivisor = (BaseClock / 16 / BaudRate) |
* (((BaseClock / 2 / BaudRate) & 4) ? 0x4000 // 0.5
* : ((BaseClock / 2 / BaudRate) & 2) ? 0x8000 // 0.25
* : ((BaseClock / 2 / BaudRate) & 1) ? 0xc000 // 0.125
* : 0)
*
* For the FT232BM, a 17th divisor bit was introduced to encode the multiples
* of 0.125 missing from the FT8U232AM. Bits 16 to 14 are coded as follows
* (the first four codes are the same as for the FT8U232AM, where bit 16 is
* always 0):
* 000 - add .000 to divisor
* 001 - add .500 to divisor
* 010 - add .250 to divisor
* 011 - add .125 to divisor
* 100 - add .375 to divisor
* 101 - add .625 to divisor
* 110 - add .750 to divisor
* 111 - add .875 to divisor
* Bits 15 to 0 of the 17-bit divisor are placed in the urb value. Bit 16 is
* placed in bit 0 of the urb index.
*
* Note that there are a couple of special cases to support the highest baud
* rates. If the calculated divisor value is 1, this needs to be replaced with
* 0. Additionally for the FT232BM, if the calculated divisor value is 0x4001
* (1.5), this needs to be replaced with 0x0001 (1) (but this divisor value is
* not supported by the FT8U232AM).
*/
enum ftdi_sio_baudrate {
ftdi_sio_b300 = 0,
ftdi_sio_b600 = 1,
ftdi_sio_b1200 = 2,
ftdi_sio_b2400 = 3,
ftdi_sio_b4800 = 4,
ftdi_sio_b9600 = 5,
ftdi_sio_b19200 = 6,
ftdi_sio_b38400 = 7,
ftdi_sio_b57600 = 8,
ftdi_sio_b115200 = 9
};
/*
* BmRequestType: 0100 0000B
* bRequest: FTDI_SIO_SET_DATA
* wValue: Data characteristics (see below)
* wIndex: Port
* wLength: 0
* Data: No
*
* Data characteristics
*
* B0..7 Number of data bits
* B8..10 Parity
* 0 = None
* 1 = Odd
* 2 = Even
* 3 = Mark
* 4 = Space
* B11..13 Stop Bits
* 0 = 1
* 1 = 1.5
* 2 = 2
* B14
* 1 = TX ON (break)
* 0 = TX OFF (normal state)
* B15 Reserved
*
*/
#define FTDI_SIO_SET_DATA_PARITY_NONE (0x0 << 8)
#define FTDI_SIO_SET_DATA_PARITY_ODD (0x1 << 8)
#define FTDI_SIO_SET_DATA_PARITY_EVEN (0x2 << 8)
#define FTDI_SIO_SET_DATA_PARITY_MARK (0x3 << 8)
#define FTDI_SIO_SET_DATA_PARITY_SPACE (0x4 << 8)
#define FTDI_SIO_SET_DATA_STOP_BITS_1 (0x0 << 11)
#define FTDI_SIO_SET_DATA_STOP_BITS_15 (0x1 << 11)
#define FTDI_SIO_SET_DATA_STOP_BITS_2 (0x2 << 11)
#define FTDI_SIO_SET_BREAK (0x1 << 14)
/*
* BmRequestType: 0100 0000B
* bRequest: FTDI_SIO_MODEM_CTRL
* wValue: ControlValue (see below)
* wIndex: Port
* wLength: 0
* Data: None
*
* NOTE: If the device is in RTS/CTS flow control, the RTS set by this
* command will be IGNORED without an error being returned
* Also - you can not set DTR and RTS with one control message
*
* ControlValue
* B0 DTR state
* 0 = reset
* 1 = set
* B1 RTS state
* 0 = reset
* 1 = set
* B2..7 Reserved
* B8 DTR state enable
* 0 = ignore
* 1 = use DTR state
* B9 RTS state enable
* 0 = ignore
* 1 = use RTS state
* B10..15 Reserved
*
*/
#define FTDI_SIO_SET_DTR_MASK 0x1
#define FTDI_SIO_SET_DTR_HIGH ((FTDI_SIO_SET_DTR_MASK << 8) | 1)
#define FTDI_SIO_SET_DTR_LOW ((FTDI_SIO_SET_DTR_MASK << 8) | 0)
#define FTDI_SIO_SET_RTS_MASK 0x2
#define FTDI_SIO_SET_RTS_HIGH ((FTDI_SIO_SET_RTS_MASK << 8) | 2)
#define FTDI_SIO_SET_RTS_LOW ((FTDI_SIO_SET_RTS_MASK << 8) | 0)
/*
* BmRequestType: 0100 0000b
* bRequest: FTDI_SIO_SET_FLOW_CTRL
* wValue: Xoff/Xon
* wIndex: Protocol/Port - hIndex is protocol / lIndex is port
* wLength: 0
* Data: None
*
* hIndex protocol is:
* B0 Output handshaking using RTS/CTS
* 0 = disabled
* 1 = enabled
* B1 Output handshaking using DTR/DSR
* 0 = disabled
* 1 = enabled
* B2 Xon/Xoff handshaking
* 0 = disabled
* 1 = enabled
*
* A value of zero in the hIndex field disables handshaking
*
* If Xon/Xoff handshaking is specified, the hValue field should contain the
* XOFF character and the lValue field contains the XON character.
*/
#define FTDI_SIO_DISABLE_FLOW_CTRL 0x0
#define FTDI_SIO_RTS_CTS_HS (0x1 << 8)
#define FTDI_SIO_DTR_DSR_HS (0x2 << 8)
#define FTDI_SIO_XON_XOFF_HS (0x4 << 8)
/*
* BmRequestType: 1100 0000b
* bRequest: FTDI_SIO_GET_MODEM_STATUS
* wValue: zero
* wIndex: Port
* wLength: 1
* Data: Status
*
* One byte of data is returned
* B0..3 0
* B4 CTS
* 0 = inactive
* 1 = active
* B5 DSR
* 0 = inactive
* 1 = active
* B6 Ring Indicator (RI)
* 0 = inactive
* 1 = active
* B7 Receive Line Signal Detect (RLSD)
* 0 = inactive
* 1 = active
*/
#define FTDI_SIO_CTS_MASK 0x10
#define FTDI_SIO_DSR_MASK 0x20
#define FTDI_SIO_RI_MASK 0x40
#define FTDI_SIO_RLSD_MASK 0x80
/* Possible bitmodes for FTDI_SIO_SET_BITMODE_REQUEST */
#define FTDI_SIO_BITMODE_RESET 0x00
#define FTDI_SIO_BITMODE_CBUS 0x20
/*
* IN Endpoint
*
* The device reserves the first two bytes of data on this endpoint to contain
* the current values of the modem and line status registers. In the absence of
* data, the device generates a message consisting of these two status bytes
* every 40 ms
*
* Byte 0: Modem Status
*
* Offset Description
* B0 Reserved - must be 1
* B1 Reserved - must be 0
* B2 Reserved - must be 0
* B3 Reserved - must be 0
* B4 Clear to Send (CTS)
* B5 Data Set Ready (DSR)
* B6 Ring Indicator (RI)
* B7 Receive Line Signal Detect (RLSD)
*
* Byte 1: Line Status
*
* Offset Description
* B0 Data Ready (DR)
* B1 Overrun Error (OE)
* B2 Parity Error (PE)
* B3 Framing Error (FE)
* B4 Break Interrupt (BI)
* B5 Transmitter Holding Register (THRE)
* B6 Transmitter Empty (TEMT)
* B7 Error in RCVR FIFO
*
*/
#define FTDI_RS0_CTS (1 << 4)
#define FTDI_RS0_DSR (1 << 5)
#define FTDI_RS0_RI (1 << 6)
#define FTDI_RS0_RLSD (1 << 7)
#define FTDI_RS_DR 1
#define FTDI_RS_OE (1 << 1)
#define FTDI_RS_PE (1 << 2)
#define FTDI_RS_FE (1 << 3)
#define FTDI_RS_BI (1 << 4)
#define FTDI_RS_THRE (1 << 5)
#define FTDI_RS_TEMT (1 << 6)
#define FTDI_RS_FIFO (1 << 7)
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* USBH_FTDI_H */

127
class/serial/usbh_gsm.c Normal file
View File

@@ -0,0 +1,127 @@
/*
* Copyright (c) 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_gsm"
#include "usb_log.h"
struct usbh_gsm {
struct usb_endpoint_descriptor *intin;
struct usbh_urb intin_urb;
struct usb_osal_timer *modem_timer;
uint16_t modem_status;
};
static int usbh_gsm_attach(struct usbh_serial *serial)
{
struct usb_endpoint_descriptor *ep_desc;
int ret;
struct usbh_gsm *gsm_class = usb_osal_malloc(sizeof(struct usbh_gsm));
if (!gsm_class) {
USB_LOG_ERR("No memory for gsm_class\r\n");
return -USB_ERR_NOMEM;
}
memset(gsm_class, 0, sizeof(struct usbh_gsm));
serial->priv = gsm_class;
for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(gsm_class->intin, ep_desc);
break;
} else {
}
}
}
if (!gsm_class->intin) {
USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
ret = -USB_ERR_NODEV;
goto errout;
}
return 0;
errout:
serial->priv = NULL;
usb_osal_free(gsm_class);
return ret;
}
static void usbh_gsm_detach(struct usbh_serial *serial)
{
struct usbh_gsm *gsm_class;
if (!serial || !serial->priv) {
return;
}
gsm_class = (struct usbh_gsm *)serial->priv;
if (gsm_class->intin) {
usbh_kill_urb(&gsm_class->intin_urb);
}
serial->priv = NULL;
usb_osal_free(gsm_class);
}
static const struct usbh_serial_driver gsm_driver = {
.driver_name = "gsm",
.ignore_rx_header = 0,
.ignore_tx_header = 0,
.attach = usbh_gsm_attach,
.detach = usbh_gsm_detach,
.set_flow_control = NULL,
.set_line_coding = NULL,
.get_line_coding = NULL,
.set_line_state = NULL,
.get_modem_status = NULL,
};
static int usbh_gsm_connect(struct usbh_hubport *hport, uint8_t intf)
{
return usbh_serial_probe(hport, intf, &gsm_driver) ? 0 : -USB_ERR_NOMEM;
}
static int usbh_gsm_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
if (serial) {
usbh_serial_remove(serial);
}
return 0;
}
const struct usbh_class_driver gsm_class_driver = {
.driver_name = "gsm",
.connect = usbh_gsm_connect,
.disconnect = usbh_gsm_disconnect
};
static const uint16_t gsm_id_table[][2] = {
{ 0x2C7C, 0x0120 }, /* Quectel EC20 */
{ 0x2C7C, 0x0121 }, /* Quectel EC21 */
{ 0x2C7C, 0x0125 }, /* Quectel EC25 */
{ 0x2C7C, 0x0191 }, /* Quectel EG91 */
{ 0x2C7C, 0x0195 }, /* Quectel EG95 */
{ 0x2C7C, 0x6002 }, /* Quectel EC200/EC600/EC800/EG91x */
{ 0x1E0E, 0x9001 }, /* SIMCOM SIM7600 */
{ 0, 0 },
};
CLASS_INFO_DEFINE const struct usbh_class_info gsm_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS | USB_CLASS_MATCH_INTF_SUBCLASS | USB_CLASS_MATCH_INTF_PROTOCOL,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = gsm_id_table,
.class_driver = &gsm_class_driver
};

726
class/serial/usbh_pl2303.c Normal file
View File

@@ -0,0 +1,726 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
* Copyright (c) 2024, Derek Konigsberg
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#include "usbh_pl2303.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_pl2303"
#include "usb_log.h"
#define UART_STATE_INDEX 8
#define UART_STATE_MSR_MASK 0x8b
#define UART_STATE_TRANSIENT_MASK 0x74
#define UART_DCD 0x01
#define UART_DSR 0x02
#define UART_BREAK_ERROR 0x04
#define UART_RING 0x08
#define UART_FRAME_ERROR 0x10
#define UART_PARITY_ERROR 0x20
#define UART_OVERRUN_ERROR 0x40
#define UART_CTS 0x80
struct pl2303_type_data {
const char *name;
uint32_t max_baud_rate;
unsigned long quirks;
unsigned int no_autoxonxoff : 1;
unsigned int no_divisors : 1;
unsigned int alt_divisors : 1;
};
enum pl2303_type {
TYPE_H,
TYPE_HX,
TYPE_TA,
TYPE_TB,
TYPE_HXD,
TYPE_HXN,
TYPE_COUNT
};
struct usbh_pl2303 {
enum pl2303_type chip_type;
uint32_t quirks;
struct usb_endpoint_descriptor *intin;
struct usbh_urb intin_urb;
struct usb_osal_timer *modem_timer;
uint16_t modem_status;
};
static const struct pl2303_type_data pl2303_type_data[TYPE_COUNT] = {
[TYPE_H] = {
.name = "PL2303H",
.max_baud_rate = 1228800,
.quirks = PL2303_QUIRK_LEGACY,
.no_autoxonxoff = true,
},
[TYPE_HX] = {
.name = "PL2303HX",
.max_baud_rate = 6000000,
},
[TYPE_TA] = {
.name = "PL2303TA",
.max_baud_rate = 6000000,
.alt_divisors = true,
},
[TYPE_TB] = {
.name = "PL2303TB",
.max_baud_rate = 12000000,
.alt_divisors = true,
},
[TYPE_HXD] = {
.name = "PL2303HXD",
.max_baud_rate = 12000000,
},
[TYPE_HXN] = {
.name = "PL2303G",
.max_baud_rate = 12000000,
.no_divisors = true,
},
};
/*
* Returns the nearest supported baud rate that can be set directly without
* using divisors.
*/
static uint32_t pl2303_get_supported_baud_rate(uint32_t baud)
{
static const uint32_t baud_sup[] = {
75, 150, 300, 600, 1200, 1800, 2400, 3600, 4800, 7200, 9600,
14400, 19200, 28800, 38400, 57600, 115200, 230400, 460800,
614400, 921600, 1228800, 2457600, 3000000, 6000000
};
unsigned i;
for (i = 0; i < ARRAY_SIZE(baud_sup); ++i) {
if (baud_sup[i] > baud)
break;
}
if (i == ARRAY_SIZE(baud_sup))
baud = baud_sup[i - 1];
else if (i > 0 && (baud_sup[i] - baud) > (baud - baud_sup[i - 1]))
baud = baud_sup[i - 1];
else
baud = baud_sup[i];
return baud;
}
/*
* NOTE: If unsupported baud rates are set directly, the PL2303 seems to
* use 9600 baud.
*/
static uint32_t pl2303_encode_baud_rate_direct(unsigned char buf[4],
uint32_t baud)
{
memcpy(buf, &baud, 4);
return baud;
}
static uint32_t pl2303_encode_baud_rate_divisor_alt(unsigned char buf[4],
uint32_t baud)
{
unsigned int baseline, mantissa, exponent;
/*
* Apparently, for the TA version the formula is:
* baudrate = 12M * 32 / (mantissa * 2^exponent)
* where
* mantissa = buf[10:0]
* exponent = buf[15:13 16]
*/
baseline = 12000000 * 32;
mantissa = baseline / baud;
if (mantissa == 0)
mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
exponent = 0;
while (mantissa >= 2048) {
if (exponent < 15) {
mantissa >>= 1; /* divide by 2 */
exponent++;
} else {
/* Exponent is maxed. Trim mantissa and leave. */
mantissa = 2047;
break;
}
}
buf[3] = 0x80;
buf[2] = exponent & 0x01;
buf[1] = (exponent & ~0x01) << 4 | mantissa >> 8;
buf[0] = mantissa & 0xff;
/* Calculate and return the exact baud rate. */
baud = (baseline / mantissa) >> exponent;
return baud;
}
static uint32_t pl2303_encode_baud_rate_divisor(unsigned char buf[4],
uint32_t baud)
{
unsigned int baseline, mantissa, exponent;
/*
* Apparently the formula is:
* baudrate = 12M * 32 / (mantissa * 4^exponent)
* where
* mantissa = buf[8:0]
* exponent = buf[11:9]
*/
baseline = 12000000 * 32;
mantissa = baseline / baud;
if (mantissa == 0)
mantissa = 1; /* Avoid dividing by zero if baud > 32*12M. */
exponent = 0;
while (mantissa >= 512) {
if (exponent < 7) {
mantissa >>= 2; /* divide by 4 */
exponent++;
} else {
/* Exponent is maxed. Trim mantissa and leave. */
mantissa = 511;
break;
}
}
buf[3] = 0x80;
buf[2] = 0;
buf[1] = exponent << 1 | mantissa >> 8;
buf[0] = mantissa & 0xff;
/* Calculate and return the exact baud rate. */
baud = (baseline / mantissa) >> (exponent << 1);
return baud;
}
static int pl2303_vendor_write(struct usbh_serial *serial, uint16_t wValue, uint16_t wIndex)
{
struct usb_setup_packet *setup;
struct usbh_pl2303 *pl2303_class;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
pl2303_class = (struct usbh_pl2303 *)serial->priv;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = pl2303_class->chip_type == TYPE_HXN ? PL2303_VENDOR_WRITE_NREQUEST : PL2303_VENDOR_WRITE_REQUEST;
setup->wValue = wValue;
setup->wIndex = wIndex;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int pl2303_vendor_read(struct usbh_serial *serial, uint16_t wValue, uint8_t *data)
{
struct usb_setup_packet *setup;
struct usbh_pl2303 *pl2303_class;
int ret;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
pl2303_class = (struct usbh_pl2303 *)serial->priv;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = pl2303_class->chip_type == TYPE_HXN ? PL2303_VENDOR_READ_NREQUEST : PL2303_VENDOR_READ_REQUEST;
setup->wValue = wValue;
setup->wIndex = 0;
setup->wLength = 1;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return ret;
}
memcpy(data, serial->iobuffer, 1);
return ret;
}
static bool pl2303_supports_hx_status(struct usbh_serial *serial)
{
int ret;
uint8_t buf;
ret = pl2303_vendor_read(serial, PL2303_READ_TYPE_HX_STATUS, &buf);
if (ret < 0) {
return false;
}
return true;
}
static bool pl2303_is_hxd_clone(struct usbh_serial *serial)
{
struct usb_setup_packet *setup;
int ret;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = 0;
setup->wLength = 7;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return false;
}
return true;
}
static int pl2303_update_reg(struct usbh_serial *serial, uint8_t reg, uint8_t mask, uint8_t val)
{
int ret;
uint8_t buf[1];
struct usbh_pl2303 *pl2303_class;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
pl2303_class = (struct usbh_pl2303 *)serial->priv;
if (pl2303_class->chip_type == TYPE_HXN)
ret = pl2303_vendor_read(serial, reg, buf);
else
ret = pl2303_vendor_read(serial, reg | 0x80, buf);
if (ret < 0) {
return ret;
}
*buf &= ~mask;
*buf |= val & mask;
return pl2303_vendor_write(serial, reg, *buf);
}
static int usbh_pl2303_get_chiptype(struct usbh_serial *serial)
{
if (serial->hport->device_desc.bDeviceClass == 0x02) {
return TYPE_H; /* variant 0 */
}
if (serial->hport->device_desc.bMaxPacketSize0 != 0x40) {
if (serial->hport->device_desc.bDeviceClass == 0x00 || serial->hport->device_desc.bDeviceClass == 0xff)
return TYPE_H; /* variant 1 */
return TYPE_H; /* variant 0 */
}
switch (serial->hport->device_desc.bcdUSB) {
case 0x101:
/* USB 1.0.1? Let's assume they meant 1.1... */
case 0x110:
switch (serial->hport->device_desc.bcdDevice) {
case 0x300:
return TYPE_HX;
case 0x400:
return TYPE_HXD;
default:
return TYPE_HX;
}
break;
case 0x200:
switch (serial->hport->device_desc.bcdDevice) {
case 0x100: /* GC */
case 0x105:
return TYPE_HXN;
case 0x300: /* GT / TA */
if (pl2303_supports_hx_status(serial))
return TYPE_TA;
__attribute__((fallthrough));
case 0x305:
case 0x400: /* GL */
case 0x405:
return TYPE_HXN;
case 0x500: /* GE / TB */
if (pl2303_supports_hx_status(serial))
return TYPE_TB;
__attribute__((fallthrough));
case 0x505:
case 0x600: /* GS */
case 0x605:
case 0x700: /* GR */
case 0x705:
case 0x905: /* GT-2AB */
case 0x1005: /* GC-Q20 */
return TYPE_HXN;
}
break;
}
USB_LOG_ERR("Unsupported PL2303 Device\r\n");
return -USB_ERR_NOTSUPP;
}
static int usbh_pl2303_attach(struct usbh_serial *serial)
{
struct usbh_pl2303 *pl2303_class;
struct usb_endpoint_descriptor *ep_desc;
uint8_t type;
int ret;
ret = usbh_pl2303_get_chiptype(serial);
if (ret < 0) {
return ret;
}
pl2303_class = usb_osal_malloc(sizeof(struct usbh_pl2303));
if (pl2303_class == NULL) {
USB_LOG_ERR("Fail to alloc pl2303_class\r\n");
return -USB_ERR_NOMEM;
}
memset(pl2303_class, 0, sizeof(struct usbh_pl2303));
serial->priv = pl2303_class;
for (uint8_t i = 0; i < serial->hport->config.intf[serial->intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &serial->hport->config.intf[serial->intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(pl2303_class->intin, ep_desc);
break;
} else {
}
}
}
if (!pl2303_class->intin) {
USB_LOG_ERR("Failed to find interrupt endpoint\r\n");
ret = -USB_ERR_NODEV;
goto errout;
}
type = (uint8_t)ret;
pl2303_class->chip_type = type;
pl2303_class->quirks = pl2303_type_data[pl2303_class->chip_type].quirks;
USB_LOG_INFO("chip type: %s\r\n", pl2303_type_data[pl2303_class->chip_type].name);
if (type == TYPE_HXD && pl2303_is_hxd_clone(serial)) {
pl2303_class->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE;
}
if (type != TYPE_HXN) {
uint8_t buf[1];
ret = pl2303_vendor_read(serial, 0x8484, buf);
ret |= pl2303_vendor_write(serial, 0x0404, 0);
ret |= pl2303_vendor_read(serial, 0x8484, buf);
ret |= pl2303_vendor_read(serial, 0x8383, buf);
ret |= pl2303_vendor_read(serial, 0x8484, buf);
ret |= pl2303_vendor_write(serial, 0x0404, 1);
ret |= pl2303_vendor_read(serial, 0x8484, buf);
ret |= pl2303_vendor_read(serial, 0x8383, buf);
ret |= pl2303_vendor_write(serial, 0, 1);
ret |= pl2303_vendor_write(serial, 1, 0);
if (pl2303_class->quirks & PL2303_QUIRK_LEGACY)
ret |= pl2303_vendor_write(serial, 2, 0x24);
else
ret |= pl2303_vendor_write(serial, 2, 0x44);
} else {
ret = 0;
}
if (ret < 0) {
USB_LOG_ERR("pl2303 init failed\r\n");
goto errout;
}
return 0;
errout:
serial->priv = NULL;
usb_osal_free(pl2303_class);
return ret;
}
static void usbh_pl2303_detach(struct usbh_serial *serial)
{
struct usbh_pl2303 *pl2303_class;
if (!serial || !serial->priv) {
return;
}
pl2303_class = (struct usbh_pl2303 *)serial->priv;
if (pl2303_class->intin) {
usbh_kill_urb(&pl2303_class->intin_urb);
}
serial->priv = NULL;
usb_osal_free(pl2303_class);
}
static int usbh_pl2303_set_flow_ctrl(struct usbh_serial *serial, bool hardctrl)
{
struct usbh_pl2303 *pl2303_class;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
pl2303_class = (struct usbh_pl2303 *)serial->priv;
if (hardctrl) {
if (pl2303_class->quirks & PL2303_QUIRK_LEGACY) {
return pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x40);
} else if (pl2303_class->chip_type == TYPE_HXN) {
return pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
PL2303_HXN_FLOWCTRL_MASK,
PL2303_HXN_FLOWCTRL_RTS_CTS);
} else {
return pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0x60);
}
} else {
if (pl2303_class->chip_type == TYPE_HXN) {
return pl2303_update_reg(serial, PL2303_HXN_FLOWCTRL_REG,
PL2303_HXN_FLOWCTRL_MASK,
PL2303_HXN_FLOWCTRL_NONE);
} else {
return pl2303_update_reg(serial, 0, PL2303_FLOWCTRL_MASK, 0);
}
}
}
static int usbh_pl2303_set_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
struct usbh_pl2303 *pl2303_class;
uint32_t baud;
uint32_t baud_sup;
uint8_t buf[7];
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
pl2303_class = (struct usbh_pl2303 *)serial->priv;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = serial->intf;
setup->wLength = 7;
baud = line_coding->dwDTERate;
if (pl2303_type_data[pl2303_class->chip_type].max_baud_rate) {
baud = MIN(baud, pl2303_type_data[pl2303_class->chip_type].max_baud_rate);
}
/*
* Use direct method for supported baud rates, otherwise use divisors.
* Newer chip types do not support divisor encoding.
*/
if (pl2303_type_data[pl2303_class->chip_type].no_divisors)
baud_sup = baud;
else
baud_sup = pl2303_get_supported_baud_rate(baud);
if (baud == baud_sup)
baud = pl2303_encode_baud_rate_direct(buf, baud);
else if (pl2303_type_data[pl2303_class->chip_type].alt_divisors)
baud = pl2303_encode_baud_rate_divisor_alt(buf, baud);
else
baud = pl2303_encode_baud_rate_divisor(buf, baud);
buf[4] = line_coding->bCharFormat;
buf[5] = line_coding->bParityType;
buf[6] = line_coding->bDataBits;
memcpy(serial->iobuffer, buf, sizeof(struct cdc_line_coding));
return usbh_control_transfer(serial->hport, setup, serial->iobuffer);
}
static int usbh_pl2303_get_line_coding(struct usbh_serial *serial, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
int ret;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = serial->intf;
setup->wLength = 7;
ret = usbh_control_transfer(serial->hport, setup, serial->iobuffer);
if (ret < 0) {
return ret;
}
memcpy(line_coding, serial->iobuffer, sizeof(struct cdc_line_coding));
return ret;
}
static int usbh_pl2303_set_line_state(struct usbh_serial *serial, bool dtr, bool rts)
{
struct usb_setup_packet *setup;
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
setup = serial->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
setup->wValue = (dtr << 0) | (rts << 1);
setup->wIndex = serial->intf;
setup->wLength = 0;
return usbh_control_transfer(serial->hport, setup, NULL);
}
static int usbh_pl2303_get_modem_status(struct usbh_serial *serial)
{
struct usbh_pl2303 *pl2303_class;
uintptr_t flags;
uint16_t status;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
flags = usb_osal_enter_critical_section();
pl2303_class = (struct usbh_pl2303 *)serial->priv;
status = (pl2303_class->modem_status & UART_DSR ? USBH_SERIAL_TIOCM_DSR : 0) |
(pl2303_class->modem_status & UART_CTS ? USBH_SERIAL_TIOCM_CTS : 0) |
(pl2303_class->modem_status & UART_RING ? USBH_SERIAL_TIOCM_RI : 0) |
(pl2303_class->modem_status & UART_DCD ? USBH_SERIAL_TIOCM_CD : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_DTR ? USBH_SERIAL_TIOCM_DTR : 0) |
(serial->line_state & USBH_SERIAL_TIOCM_RTS ? USBH_SERIAL_TIOCM_RTS : 0);
usb_osal_leave_critical_section(flags);
return status;
}
#ifdef CONFIG_USBH_SERIAL_GET_MODEM_STATUS
static int __usbh_pl2303_get_modem_status(struct usbh_serial *serial)
{
struct usbh_pl2303 *pl2303_class;
uint8_t status = 0;
uint16_t difference;
uintptr_t flags;
int ret;
if (!serial || !serial->hport || !serial->priv) {
return -USB_ERR_INVAL;
}
pl2303_class = (struct usbh_pl2303 *)serial->priv;
usbh_int_urb_fill(&pl2303_class->intin_urb, serial->hport, pl2303_class->intin, &serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET], pl2303_class->intin->wMaxPacketSize, 0xffffffff, NULL, NULL);
ret = usbh_submit_urb(&pl2303_class->intin_urb);
if (ret < 0) {
return ret;
}
if (ret < 1) {
return -USB_ERR_INVAL;
}
flags = usb_osal_enter_critical_section();
status = serial->iobuffer[USBH_SERIAL_INT_NOCACHE_OFFSET];
difference = pl2303_class->modem_status ^ status;
pl2303_class->modem_status = status;
if (status & UART_BREAK_ERROR)
serial->iocount.brk++;
if (difference & UART_STATE_MSR_MASK) {
if (difference & UART_CTS)
serial->iocount.cts++;
if (difference & UART_DSR)
serial->iocount.dsr++;
if (difference & UART_RING)
serial->iocount.rng++;
if (difference & UART_DCD) {
serial->iocount.dcd++;
}
}
usb_osal_leave_critical_section(flags);
return ret;
}
#endif
static const struct usbh_serial_driver pl2303_driver = {
.driver_name = "pl2303",
.ignore_rx_header = 0,
.ignore_tx_header = 0,
.attach = usbh_pl2303_attach,
.detach = usbh_pl2303_detach,
.set_flow_control = usbh_pl2303_set_flow_ctrl,
.set_line_coding = usbh_pl2303_set_line_coding,
.get_line_coding = usbh_pl2303_get_line_coding,
.set_line_state = usbh_pl2303_set_line_state,
.get_modem_status = usbh_pl2303_get_modem_status,
};
static int usbh_pl2303_connect(struct usbh_hubport *hport, uint8_t intf)
{
return usbh_serial_probe(hport, intf, &pl2303_driver) ? 0 : -USB_ERR_NOMEM;
}
static int usbh_pl2303_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
struct usbh_serial *serial = (struct usbh_serial *)hport->config.intf[intf].priv;
if (serial) {
usbh_serial_remove(serial);
}
return 0;
}
static const uint16_t pl2303_id_table[][2] = {
{ 0x067B, 0x2303 }, // PL2303 Serial (ATEN/IOGEAR UC232A)
{ 0x067B, 0x2304 }, // PL2303HXN Serial, type TB
{ 0x067B, 0x23A3 }, // PL2303HXN Serial, type GC
{ 0x067B, 0x23B3 }, // PL2303HXN Serial, type GB
{ 0x067B, 0x23C3 }, // PL2303HXN Serial, type GT
{ 0x067B, 0x23D3 }, // PL2303HXN Serial, type GL
{ 0x067B, 0x23E3 }, // PL2303HXN Serial, type GE
{ 0x067B, 0x23F3 }, // PL2303HXN Serial, type GS
{ 0, 0 },
};
const struct usbh_class_driver pl2303_class_driver = {
.driver_name = "pl2303",
.connect = usbh_pl2303_connect,
.disconnect = usbh_pl2303_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info pl2303_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = pl2303_id_table,
.class_driver = &pl2303_class_driver
};

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 ~ 2025, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_PL2303_H
#define USBH_PL2303_H
#include "usb_cdc.h"
#define PL2303_VENDOR_WRITE_REQUEST 0x01
#define PL2303_VENDOR_WRITE_NREQUEST 0x80
#define PL2303_VENDOR_READ_REQUEST 0x01
#define PL2303_VENDOR_READ_NREQUEST 0x81
#define PL2303_FLOWCTRL_MASK 0xf0
#define PL2303_READ_TYPE_HX_STATUS 0x8080
#define PL2303_HXN_RESET_REG 0x07
#define PL2303_HXN_RESET_UPSTREAM_PIPE 0x02
#define PL2303_HXN_RESET_DOWNSTREAM_PIPE 0x01
#define PL2303_HXN_FLOWCTRL_REG 0x0a
#define PL2303_HXN_FLOWCTRL_MASK 0x1c
#define PL2303_HXN_FLOWCTRL_NONE 0x1c
#define PL2303_HXN_FLOWCTRL_RTS_CTS 0x18
#define PL2303_HXN_FLOWCTRL_XON_XOFF 0x0c
#define PL2303_QUIRK_UART_STATE_IDX0 BIT(0)
#define PL2303_QUIRK_LEGACY BIT(1)
#define PL2303_QUIRK_ENDPOINT_HACK BIT(2)
#define PL2303_QUIRK_NO_BREAK_GETLINE BIT(3)
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* USBH_PL2303_H */

719
class/serial/usbh_serial.c Normal file
View File

@@ -0,0 +1,719 @@
/*
* Copyright (c) 2025, sakumisu
* Copyright (c) 2025, MDLZCOOL
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_serial.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_serial"
#include "usb_log.h"
#define DEV_FORMAT_VENDOR "/dev/ttyUSB%d"
#define DEV_FORMAT_CDC_ACM "/dev/ttyACM%d"
#define CONFIG_USBHOST_MAX_SERIAL_CLASS 4
static struct usbh_serial g_serial_class[CONFIG_USBHOST_MAX_SERIAL_CLASS];
static uint32_t g_devinuse = 0;
static uint32_t g_cdcacm_devinuse = 0;
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_serial_iobuffer[CONFIG_USBHOST_MAX_SERIAL_CLASS][USB_ALIGN_UP((USBH_SERIAL_INT_NOCACHE_OFFSET + USBH_SERIAL_INT_NOCACHE_SIZE), CONFIG_USB_ALIGN_SIZE)];
/* refer to cherryrb */
static int usbh_serial_ringbuffer_init(usbh_serial_ringbuf_t *rb, void *pool, uint32_t size)
{
if (NULL == rb) {
return -1;
}
if (NULL == pool) {
return -1;
}
if ((size < 2) || (size & (size - 1))) {
return -1;
}
rb->in = 0;
rb->out = 0;
rb->mask = size - 1;
rb->pool = pool;
return 0;
}
static void usbh_serial_ringbuffer_reset(usbh_serial_ringbuf_t *rb)
{
rb->in = 0;
rb->out = 0;
}
static uint32_t usbh_serial_ringbuffer_get_used(usbh_serial_ringbuf_t *rb)
{
return rb->in - rb->out;
}
static uint32_t usbh_serial_ringbuffer_write(usbh_serial_ringbuf_t *rb, void *data, uint32_t size)
{
uint32_t unused;
uint32_t offset;
uint32_t remain;
unused = (rb->mask + 1) - (rb->in - rb->out);
if (size > unused) {
size = unused;
}
offset = rb->in & rb->mask;
remain = rb->mask + 1 - offset;
remain = remain > size ? size : remain;
memcpy(((uint8_t *)(rb->pool)) + offset, data, remain);
memcpy(rb->pool, (uint8_t *)data + remain, size - remain);
rb->in += size;
return size;
}
static uint32_t usbh_serial_ringbuffer_peek(usbh_serial_ringbuf_t *rb, void *data, uint32_t size)
{
uint32_t used;
uint32_t offset;
uint32_t remain;
used = rb->in - rb->out;
if (size > used) {
size = used;
}
offset = rb->out & rb->mask;
remain = rb->mask + 1 - offset;
remain = remain > size ? size : remain;
memcpy(data, ((uint8_t *)(rb->pool)) + offset, remain);
memcpy((uint8_t *)data + remain, rb->pool, size - remain);
return size;
}
static uint32_t usbh_serial_ringbuffer_read(usbh_serial_ringbuf_t *rb, void *data, uint32_t size)
{
size = usbh_serial_ringbuffer_peek(rb, data, size);
rb->out += size;
return size;
}
static struct usbh_serial *usbh_serial_alloc(bool is_cdcacm)
{
uint8_t devno;
uint8_t devno2;
for (devno = 0; devno < CONFIG_USBHOST_MAX_SERIAL_CLASS; devno++) {
if ((g_devinuse & (1U << devno)) == 0) {
g_devinuse |= (1U << devno);
memset(&g_serial_class[devno], 0, sizeof(struct usbh_serial));
g_serial_class[devno].minor = devno;
g_serial_class[devno].cdc_minor = -1;
g_serial_class[devno].iobuffer = g_serial_iobuffer[devno];
g_serial_class[devno].rx_complete_sem = usb_osal_sem_create(0);
if (is_cdcacm) {
for (devno2 = 0; devno2 < CONFIG_USBHOST_MAX_SERIAL_CLASS; devno2++) {
if ((g_cdcacm_devinuse & (1U << devno2)) == 0) {
g_cdcacm_devinuse |= (1U << devno2);
g_serial_class[devno].cdc_minor = devno2;
return &g_serial_class[devno];
}
}
g_devinuse &= ~(1U << devno);
return NULL;
} else {
return &g_serial_class[devno];
}
}
}
return NULL;
}
static void usbh_serial_free(struct usbh_serial *serial)
{
uint8_t devno = serial->minor;
if (devno < 32) {
g_devinuse &= ~(1U << devno);
}
if (serial->cdc_minor >= 0) {
g_cdcacm_devinuse &= ~(1U << serial->cdc_minor);
}
if (g_serial_class[devno].rx_complete_sem) {
usb_osal_sem_delete(g_serial_class[devno].rx_complete_sem);
}
}
static void usbh_serial_callback(void *arg, int nbytes)
{
struct usbh_serial *serial = (struct usbh_serial *)arg;
int ret;
if (nbytes >= serial->driver->ignore_rx_header) {
usbh_serial_ringbuffer_write(&serial->rx_rb,
&serial->iobuffer[USBH_SERIAL_RX_NOCACHE_OFFSET + serial->driver->ignore_rx_header],
(nbytes - serial->driver->ignore_rx_header));
/* resubmit the read urb */
usbh_bulk_urb_fill(&serial->bulkin_urb, serial->hport, serial->bulkin, &serial->iobuffer[USBH_SERIAL_RX_NOCACHE_OFFSET], serial->bulkin->wMaxPacketSize,
0, usbh_serial_callback, serial);
ret = usbh_submit_urb(&serial->bulkin_urb);
if (ret < 0) {
USB_LOG_ERR("serial submit failed: %d\n", ret);
}
if (serial->rx_complete_callback) {
serial->rx_complete_callback(serial, nbytes - serial->driver->ignore_rx_header);
}
serial->rx_errorcode = 0;
usb_osal_sem_give(serial->rx_complete_sem);
} else {
serial->rx_errorcode = nbytes;
usb_osal_sem_give(serial->rx_complete_sem);
}
}
struct usbh_serial *usbh_serial_probe(struct usbh_hubport *hport, uint8_t intf,
const struct usbh_serial_driver *driver)
{
struct usb_endpoint_descriptor *ep_desc;
struct usbh_serial *serial;
bool is_cdcacm = false;
int ret;
if (strcmp(driver->driver_name, "cdc_acm") == 0) {
is_cdcacm = true;
}
serial = usbh_serial_alloc(is_cdcacm);
if (serial == NULL) {
USB_LOG_ERR("Fail to alloc serial class\r\n");
return NULL;
}
serial->hport = hport;
serial->intf = intf;
serial->driver = driver;
if (driver->attach) {
ret = driver->attach(serial);
if (ret < 0) {
USB_LOG_ERR("Serial attach failed: %d\r\n", ret);
usbh_serial_free(serial);
return NULL;
}
}
if (is_cdcacm) {
intf = intf + 1; /* data interface */
}
for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_BULK) {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(serial->bulkin, ep_desc);
} else {
USBH_EP_INIT(serial->bulkout, ep_desc);
}
}
}
if (is_cdcacm) {
intf = intf - 1; /* data interface */
}
if (!serial->bulkin || !serial->bulkout) {
USB_LOG_ERR("Serial bulk in/out endpoint not found\r\n");
usbh_serial_free(serial);
return NULL;
}
if (is_cdcacm) {
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_CDC_ACM, serial->cdc_minor);
} else {
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_VENDOR, serial->minor);
}
hport->config.intf[intf].priv = serial;
USB_LOG_INFO("Register Serial Class: %s (%s)\r\n", hport->config.intf[intf].devname, driver->driver_name);
usbh_serial_run(serial);
return serial;
}
void usbh_serial_remove(struct usbh_serial *serial)
{
if (!serial || !serial->hport)
return;
usbh_serial_close(serial);
if (serial->driver && serial->driver->detach) {
serial->driver->detach(serial);
}
if (serial->hport->config.intf[serial->intf].priv) {
usb_osal_thread_schedule_other();
USB_LOG_INFO("Unregister Serial Class: %s (%s)\r\n", serial->hport->config.intf[serial->intf].devname, serial->driver->driver_name);
usbh_serial_stop(serial);
}
usbh_serial_free(serial);
}
struct usbh_serial *usbh_serial_open(const char *devname, uint32_t open_flags)
{
struct usbh_serial *serial;
int ret;
serial = usbh_find_class_instance(devname);
if (!serial) {
return NULL;
}
if (serial->ref_count != 0) {
USB_LOG_ERR("Device busy: %s\r\n", devname);
return NULL;
}
if (serial && serial->driver && serial->driver->open) {
ret = serial->driver->open(serial);
if (ret < 0) {
return NULL;
}
}
usbh_serial_ringbuffer_init(&serial->rx_rb, serial->rx_rb_pool, CONFIG_USBHOST_SERIAL_RX_SIZE);
serial->ref_count++;
serial->open_flags = open_flags;
return serial;
}
int usbh_serial_close(struct usbh_serial *serial)
{
if (!serial || !serial->hport) {
return -USB_ERR_INVAL;
}
if (serial->ref_count == 0) {
return 0;
}
if (serial->bulkin) {
usbh_kill_urb(&serial->bulkin_urb);
}
if (serial->bulkout) {
usbh_kill_urb(&serial->bulkout_urb);
}
if (serial && serial->driver && serial->driver->set_flow_control && serial->rtscts) {
serial->driver->set_flow_control(serial, false);
}
if (serial && serial->driver && serial->driver->close) {
serial->driver->close(serial);
}
serial->ref_count--;
serial->rtscts = false;
return 0;
}
static int usbh_serial_tiocmset(struct usbh_serial *serial, uint32_t set, uint32_t clear)
{
int ret;
uint16_t line_state;
bool dtr;
bool rts;
if (!serial || !serial->hport || !serial->hport->connected) {
return -USB_ERR_INVAL;
}
if (serial->ref_count == 0) {
return -USB_ERR_NODEV;
}
line_state = serial->line_state;
clear &= ~set; /* 'set' takes precedence over 'clear' */
if (set & USBH_SERIAL_TIOCM_DTR) {
line_state |= USBH_SERIAL_TIOCM_DTR;
}
if (set & USBH_SERIAL_TIOCM_RTS) {
line_state |= USBH_SERIAL_TIOCM_RTS;
}
if (clear & USBH_SERIAL_TIOCM_DTR) {
line_state &= ~USBH_SERIAL_TIOCM_DTR;
}
if (clear & USBH_SERIAL_TIOCM_RTS) {
line_state &= ~USBH_SERIAL_TIOCM_RTS;
}
dtr = (line_state & USBH_SERIAL_TIOCM_RTS) ? true : false;
rts = (line_state & USBH_SERIAL_TIOCM_RTS) ? true : false;
if (serial && serial->driver && serial->driver->set_line_state) {
ret = serial->driver->set_line_state(serial, dtr, rts);
} else {
return -USB_ERR_NOTSUPP;
}
serial->line_state = line_state;
return ret;
}
int usbh_serial_control(struct usbh_serial *serial, int cmd, void *arg)
{
int ret;
if (!serial || !serial->hport || !serial->hport->connected) {
return -USB_ERR_INVAL;
}
if (serial->ref_count == 0) {
return -USB_ERR_NODEV;
}
switch (cmd) {
case USBH_SERIAL_CMD_SET_ATTR: {
struct usbh_serial_termios *termios = (struct usbh_serial_termios *)arg;
struct cdc_line_coding line_coding;
line_coding.dwDTERate = termios->baudrate;
line_coding.bCharFormat = termios->stopbits;
line_coding.bParityType = termios->parity;
line_coding.bDataBits = termios->databits;
if (serial->bulkin) {
usbh_kill_urb(&serial->bulkin_urb);
}
if (serial->bulkout) {
usbh_kill_urb(&serial->bulkout_urb);
}
if (serial && serial->driver && serial->driver->set_line_coding) {
ret = serial->driver->set_line_coding(serial, &line_coding);
if (ret < 0) {
return ret;
}
} else {
return -USB_ERR_NOTSUPP;
}
memcpy(&serial->line_coding, &line_coding, sizeof(struct cdc_line_coding));
if (serial && serial->driver && serial->driver->set_flow_control) {
ret = serial->driver->set_flow_control(serial, termios->rtscts);
}
serial->rtscts = termios->rtscts;
serial->rx_timeout_ms = termios->rx_timeout;
ret = usbh_serial_tiocmset(serial, USBH_SERIAL_TIOCM_DTR | USBH_SERIAL_TIOCM_RTS, 0);
if (ret < 0) {
return ret;
}
usbh_serial_ringbuffer_reset(&serial->rx_rb);
usb_osal_sem_reset(serial->rx_complete_sem);
usbh_bulk_urb_fill(&serial->bulkin_urb, serial->hport, serial->bulkin, &serial->iobuffer[USBH_SERIAL_RX_NOCACHE_OFFSET], serial->bulkin->wMaxPacketSize,
0, usbh_serial_callback, serial);
ret = usbh_submit_urb(&serial->bulkin_urb);
return ret;
} break;
case USBH_SERIAL_CMD_GET_ATTR: {
struct usbh_serial_termios *termios = (struct usbh_serial_termios *)arg;
struct cdc_line_coding line_coding;
if (serial && serial->driver && serial->driver->get_line_coding) {
return serial->driver->get_line_coding(serial, &line_coding);
} else {
memcpy(&line_coding, &serial->line_coding, sizeof(struct cdc_line_coding));
}
termios->baudrate = line_coding.dwDTERate;
termios->stopbits = line_coding.bCharFormat;
termios->parity = line_coding.bParityType;
termios->databits = line_coding.bDataBits;
termios->rtscts = serial->rtscts;
termios->rx_timeout = serial->rx_timeout_ms;
return 0;
} break;
case USBH_SERIAL_CMD_IOCMBIS: {
uint32_t flags = *(uint32_t *)arg;
return usbh_serial_tiocmset(serial, flags, 0);
} break;
case USBH_SERIAL_CMD_IOCMBIC: {
uint32_t flags = *(uint32_t *)arg;
return usbh_serial_tiocmset(serial, 0, flags);
} break;
case USBH_SERIAL_CMD_TIOCMSET: {
uint32_t flags = *(uint32_t *)arg;
uint32_t set = 0;
uint32_t clear = 0;
set |= (flags & USBH_SERIAL_TIOCM_DTR) ? USBH_SERIAL_TIOCM_DTR : 0;
set |= (flags & USBH_SERIAL_TIOCM_RTS) ? USBH_SERIAL_TIOCM_RTS : 0;
clear |= !(flags & USBH_SERIAL_TIOCM_DTR) ? USBH_SERIAL_TIOCM_DTR : 0;
clear |= !(flags & USBH_SERIAL_TIOCM_RTS) ? USBH_SERIAL_TIOCM_RTS : 0;
return usbh_serial_tiocmset(serial, set, clear);
} break;
case USBH_SERIAL_CMD_TIOCMGET: {
uint32_t *flags = (uint32_t *)arg;
int status;
if (serial && serial->driver && serial->driver->get_modem_status) {
status = serial->driver->get_modem_status(serial);
if (status < 0) {
return status;
}
} else {
return -USB_ERR_NOTSUPP;
}
*flags = status;
} break;
default:
break;
}
return -USB_ERR_NOTSUPP;
}
int usbh_serial_write(struct usbh_serial *serial, const void *buffer, uint32_t buflen)
{
int ret;
struct usbh_urb *urb;
if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkout) {
return -USB_ERR_INVAL;
}
if (serial->ref_count == 0) {
return -USB_ERR_NODEV;
}
urb = &serial->bulkout_urb;
usbh_bulk_urb_fill(urb, serial->hport, serial->bulkout, (uint8_t *)buffer, serial->line_coding.dwDTERate ? MIN(buflen, serial->bulkout->wMaxPacketSize) : buflen, 0xffffffff, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
int usbh_serial_read(struct usbh_serial *serial, void *buffer, uint32_t buflen)
{
int ret;
if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkin || !serial->line_coding.dwDTERate) {
return -USB_ERR_INVAL;
}
if (serial->ref_count == 0) {
return -USB_ERR_NODEV;
}
if (serial->open_flags & USBH_SERIAL_O_NONBLOCK) {
return usbh_serial_ringbuffer_read(&serial->rx_rb, buffer, buflen);
} else {
if (usbh_serial_ringbuffer_get_used(&serial->rx_rb) == 0) {
ret = usb_osal_sem_take(serial->rx_complete_sem, serial->rx_timeout_ms == 0 ? USB_OSAL_WAITING_FOREVER : serial->rx_timeout_ms);
if (ret < 0) {
return ret;
}
if (serial->rx_errorcode < 0) {
return serial->rx_errorcode;
}
}
return usbh_serial_ringbuffer_read(&serial->rx_rb, buffer, buflen);
}
}
int usbh_serial_cdc_write_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg)
{
struct usbh_urb *urb;
if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkout || !complete || serial->line_coding.dwDTERate) {
return -USB_ERR_INVAL;
}
if (serial->ref_count > 0) {
return -USB_ERR_NODEV;
}
urb = &serial->bulkout_urb;
usbh_bulk_urb_fill(urb, serial->hport, serial->bulkout, buffer, buflen,
0, complete, serial);
return usbh_submit_urb(urb);
}
int usbh_serial_cdc_read_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg)
{
struct usbh_urb *urb;
if (!serial || !serial->hport || !serial->hport->connected || !serial->bulkin || !complete || serial->line_coding.dwDTERate) {
return -USB_ERR_INVAL;
}
if (serial->ref_count > 0) {
return -USB_ERR_NODEV;
}
if (buflen % serial->bulkin->wMaxPacketSize) {
return -USB_ERR_INVAL;
}
urb = &serial->bulkin_urb;
usbh_bulk_urb_fill(urb, serial->hport, serial->bulkin, buffer, MIN(buflen, serial->bulkin->wMaxPacketSize),
0, complete, serial);
return usbh_submit_urb(urb);
}
void usbh_serial_help(void)
{
USB_LOG_RAW("USB host serial test\r\n"
"Usage: usbh_serial <ttypath> [options]...\r\n"
"\r\n"
"-b <baud> set serial baudrate\r\n"
"-t <dtr> <rts> set rts and dtr\r\n"
"-w string write string\r\n"
"-r read data and dump\r\n"
"-x close serial device\r\n"
"\r\n");
}
static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_serial_testbuffer[512];
int usbh_serial(int argc, char **argv)
{
static struct usbh_serial *serial;
int ret;
if (argc < 3) {
usbh_serial_help();
return 0;
}
if (!serial) {
serial = usbh_serial_open(argv[1], USBH_SERIAL_O_RDWR | USBH_SERIAL_O_NONBLOCK);
if (!serial) {
USB_LOG_ERR("Fail to open serial device: %s\r\n", argv[1]);
return -USB_ERR_INVAL;
}
}
if (strncmp(argv[2], "-b", 2) == 0 && argc >= 4) {
struct usbh_serial_termios termios;
memset(&termios, 0, sizeof(termios));
termios.baudrate = atoi(argv[3]);
termios.stopbits = 0;
termios.parity = 0;
termios.databits = 8;
termios.rtscts = false;
termios.rx_timeout = 0;
usbh_serial_control(serial, USBH_SERIAL_CMD_SET_ATTR, &termios);
} else if (strncmp(argv[2], "-t", 2) == 0 && argc >= 5) {
uint32_t flags;
flags = atoi(argv[3]) ? USBH_SERIAL_TIOCM_DTR : 0;
flags |= atoi(argv[4]) ? USBH_SERIAL_TIOCM_RTS : 0;
usbh_serial_control(serial, USBH_SERIAL_CMD_TIOCMSET, &flags);
USB_LOG_INFO("Set DTR: %d, RTS: %d\r\n", atoi(argv[3]), atoi(argv[4]));
} else if (strncmp(argv[2], "-w", 2) == 0 && argc >= 4) {
memcpy(g_serial_testbuffer, argv[3], MIN(strlen(argv[3]), sizeof(g_serial_testbuffer)));
uint32_t len = snprintf((char *)g_serial_testbuffer, sizeof(g_serial_testbuffer), "%s\r\n", argv[3]);
ret = usbh_serial_write(serial, g_serial_testbuffer, len);
if (ret >= 0) {
USB_LOG_INFO("Write %d bytes\r\n", ret);
} else {
USB_LOG_ERR("Write failed: %d\r\n", ret);
}
} else if (strncmp(argv[2], "-r", 2) == 0) {
ret = usbh_serial_read(serial, g_serial_testbuffer, sizeof(g_serial_testbuffer));
if (ret >= 0) {
usb_hexdump(g_serial_testbuffer, ret);
USB_LOG_INFO("Read %d bytes\r\n", ret);
} else {
USB_LOG_ERR("Read failed: %d\r\n", ret);
}
} else if (strncmp(argv[2], "-x", 2) == 0) {
usbh_serial_close(serial);
serial = NULL;
} else {
usbh_serial_help();
}
return 0;
}
__WEAK void usbh_serial_run(struct usbh_serial *serial)
{
(void)serial;
}
__WEAK void usbh_serial_stop(struct usbh_serial *serial)
{
(void)serial;
}
static int usbh_cdc_data_connect(struct usbh_hubport *hport, uint8_t intf)
{
(void)hport;
(void)intf;
return 0;
}
static int usbh_cdc_data_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
(void)hport;
(void)intf;
return 0;
}
const struct usbh_class_driver cdc_data_class_driver = {
.driver_name = "cdc_data",
.connect = usbh_cdc_data_connect,
.disconnect = usbh_cdc_data_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info cdc_data_class_info = {
.match_flags = USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = USB_DEVICE_CLASS_CDC_DATA,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = NULL,
.class_driver = &cdc_data_class_driver
};

179
class/serial/usbh_serial.h Normal file
View File

@@ -0,0 +1,179 @@
/*
* Copyright (c) 2025, sakumisu
* Copyright (c) 2025, MDLZCOOL
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_SERIAL_H
#define USBH_SERIAL_H
#include "usb_cdc.h"
#define USBH_SERIAL_CTRL_NOCACHE_SIZE 32
#define USBH_SERIAL_CTRL_NOCACHE_OFFSET 0
#define USBH_SERIAL_INT_NOCACHE_SIZE 32
#define USBH_SERIAL_INT_NOCACHE_OFFSET USB_ALIGN_UP(USBH_SERIAL_CTRL_NOCACHE_SIZE, CONFIG_USB_ALIGN_SIZE)
#define USBH_SERIAL_RX_NOCACHE_OFFSET USB_ALIGN_UP((USBH_SERIAL_INT_NOCACHE_OFFSET + USBH_SERIAL_INT_NOCACHE_SIZE), CONFIG_USB_ALIGN_SIZE)
#define USBH_SERIAL_RX_NOCACHE_SIZE 512
#define USBH_SERIAL_DATABITS_5 5
#define USBH_SERIAL_DATABITS_6 6
#define USBH_SERIAL_DATABITS_7 7
#define USBH_SERIAL_DATABITS_8 8
#define USBH_SERIAL_PARITY_NONE 0
#define USBH_SERIAL_PARITY_ODD 1
#define USBH_SERIAL_PARITY_EVEN 2
#define USBH_SERIAL_PARITY_MARK 3
#define USBH_SERIAL_PARITY_SPACE 4
#define USBH_SERIAL_STOPBITS_1 0
#define USBH_SERIAL_STOPBITS_1_5 1
#define USBH_SERIAL_STOPBITS_2 2
/* modem lines */
#define USBH_SERIAL_TIOCM_LE 0x001 /* line enable */
#define USBH_SERIAL_TIOCM_DTR 0x002 /* data terminal ready */
#define USBH_SERIAL_TIOCM_RTS 0x004 /* request to send */
#define USBH_SERIAL_TIOCM_ST 0x010 /* secondary transmit */
#define USBH_SERIAL_TIOCM_SR 0x020 /* secondary receive */
#define USBH_SERIAL_TIOCM_CTS 0x040 /* clear to send */
#define USBH_SERIAL_TIOCM_CAR 0x100 /* carrier detect */
#define USBH_SERIAL_TIOCM_CD USBH_SERIAL_TIOCM_CAR
#define USBH_SERIAL_TIOCM_RNG 0x200 /* ring */
#define USBH_SERIAL_TIOCM_RI USBH_SERIAL_TIOCM_RNG
#define USBH_SERIAL_TIOCM_DSR 0x400 /* data set ready */
#define USBH_SERIAL_TIOCM_OUT1 0x2000
#define USBH_SERIAL_TIOCM_OUT2 0x4000
#define USBH_SERIAL_TIOCM_LOOP 0x8000
#define USBH_SERIAL_O_RDONLY 0x0000 /* open for reading only */
#define USBH_SERIAL_O_WRONLY 0x0001 /* open for writing only */
#define USBH_SERIAL_O_RDWR 0x0002 /* open for reading and writing */
#define USBH_SERIAL_O_ACCMODE 0x0003 /* mask for above modes, from 4.4BSD https://minnie.tuhs.org/cgi-bin/utree.pl?file=4.4BSD/usr/include/sys/fcntl.h */
#define USBH_SERIAL_O_NONBLOCK 0x0004 /* non blocking I/O, from BSD apple https://opensource.apple.com/source/xnu/xnu-1228.0.2/bsd/sys/fcntl.h */
#define USBH_SERIAL_CMD_SET_ATTR 0
#define USBH_SERIAL_CMD_GET_ATTR 1
#define USBH_SERIAL_CMD_IOCMBIS 2
#define USBH_SERIAL_CMD_IOCMBIC 3
#define USBH_SERIAL_CMD_TIOCMSET 4
#define USBH_SERIAL_CMD_TIOCMGET 5
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t in; /*!< Define the write pointer. */
uint32_t out; /*!< Define the read pointer. */
uint32_t mask; /*!< Define the write and read pointer mask. */
void *pool; /*!< Define the memory pointer. */
} usbh_serial_ringbuf_t;
/*
* Counters of the input lines (CTS, DSR, RI, CD) interrupts
*/
struct usbh_serial_async_icount {
uint32_t cts, dsr, rng, dcd, tx, rx;
uint32_t frame, parity, overrun, brk;
uint32_t buf_overrun;
};
struct usbh_serial_termios {
uint32_t baudrate;
uint8_t databits;
uint8_t parity;
uint8_t stopbits;
bool rtscts; /* hardware flow control */
uint32_t rx_timeout;
};
struct usbh_serial;
typedef void (*usbh_serial_rx_complete_callback_t)(struct usbh_serial *serial, int nbytes);
/**
* @brief Serial Driver Operations
*/
struct usbh_serial_driver {
const char *driver_name;
uint8_t ignore_tx_header;
uint8_t ignore_rx_header;
int (*attach)(struct usbh_serial *serial);
void (*detach)(struct usbh_serial *serial);
int (*open)(struct usbh_serial *serial);
void (*close)(struct usbh_serial *serial);
int (*set_flow_control)(struct usbh_serial *serial, bool enable);
int (*set_line_coding)(struct usbh_serial *serial, struct cdc_line_coding *line_coding);
int (*get_line_coding)(struct usbh_serial *serial, struct cdc_line_coding *line_coding);
int (*set_line_state)(struct usbh_serial *serial, bool dtr, bool rts);
int (*get_modem_status)(struct usbh_serial *serial);
};
/**
* @brief Serial Instance
*/
struct usbh_serial {
struct usbh_hubport *hport;
uint8_t intf; /* Interface Number */
int minor; /* Serial Port Number (/dev/ttyUSBx or /dev/ttyACMx) */
int cdc_minor; /* Serial Port Number (/dev/ttyACMx) */
uint8_t *iobuffer; /* I/O buffer for serial transfers */
uint8_t ref_count; /* Reference Count */
uint32_t open_flags;
uint32_t rx_timeout_ms;
struct cdc_line_coding line_coding;
uint16_t line_state;
bool rtscts; /* hardware flow control */
struct usbh_serial_async_icount iocount;
struct usb_endpoint_descriptor *bulkin; /* Bulk IN endpoint */
struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
struct usbh_urb bulkout_urb;
struct usbh_urb bulkin_urb;
const struct usbh_serial_driver *driver;
usbh_serial_ringbuf_t rx_rb;
uint8_t rx_rb_pool[CONFIG_USBHOST_SERIAL_RX_SIZE];
usb_osal_sem_t rx_complete_sem;
int rx_errorcode;
usbh_serial_rx_complete_callback_t rx_complete_callback;
void *priv; /* Private Data */
void *user_data; /* User Data */
};
/* internal api */
struct usbh_serial *usbh_serial_probe(struct usbh_hubport *hport, uint8_t intf, const struct usbh_serial_driver *driver);
void usbh_serial_remove(struct usbh_serial *serial);
/* public api */
struct usbh_serial *usbh_serial_open(const char *devname, uint32_t open_flags);
int usbh_serial_close(struct usbh_serial *serial);
int usbh_serial_control(struct usbh_serial *serial, int cmd, void *arg);
int usbh_serial_write(struct usbh_serial *serial, const void *buffer, uint32_t buflen);
int usbh_serial_read(struct usbh_serial *serial, void *buffer, uint32_t buflen);
/* cdc only api */
int usbh_serial_cdc_write_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg);
int usbh_serial_cdc_read_async(struct usbh_serial *serial, uint8_t *buffer, uint32_t buflen, usbh_complete_callback_t complete, void *arg);
/* public weak api */
void usbh_serial_run(struct usbh_serial *serial);
void usbh_serial_stop(struct usbh_serial *serial);
int usbh_serial(int argc, char **argv);
#ifdef __cplusplus
}
#endif
#endif /* USBH_SERIAL_H */

View File

@@ -1,379 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_ch34x.h"
#define DEV_FORMAT "/dev/ttyUSB%d"
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_ch34x_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
#define CONFIG_USBHOST_MAX_CP210X_CLASS 1
static struct usbh_ch34x g_ch34x_class[CONFIG_USBHOST_MAX_CP210X_CLASS];
static uint32_t g_devinuse = 0;
static struct usbh_ch34x *usbh_ch34x_class_alloc(void)
{
uint8_t devno;
for (devno = 0; devno < CONFIG_USBHOST_MAX_CP210X_CLASS; devno++) {
if ((g_devinuse & (1U << devno)) == 0) {
g_devinuse |= (1U << devno);
memset(&g_ch34x_class[devno], 0, sizeof(struct usbh_ch34x));
g_ch34x_class[devno].minor = devno;
return &g_ch34x_class[devno];
}
}
return NULL;
}
static void usbh_ch34x_class_free(struct usbh_ch34x *ch34x_class)
{
uint8_t devno = ch34x_class->minor;
if (devno < 32) {
g_devinuse &= ~(1U << devno);
}
memset(ch34x_class, 0, sizeof(struct usbh_ch34x));
}
static int usbh_ch34x_get_baudrate_div(uint32_t baudrate, uint8_t *factor, uint8_t *divisor)
{
uint8_t a;
uint8_t b;
uint32_t c;
switch (baudrate) {
case 921600:
a = 0xf3;
b = 7;
break;
case 307200:
a = 0xd9;
b = 7;
break;
default:
if (baudrate > 6000000 / 255) {
b = 3;
c = 6000000;
} else if (baudrate > 750000 / 255) {
b = 2;
c = 750000;
} else if (baudrate > 93750 / 255) {
b = 1;
c = 93750;
} else {
b = 0;
c = 11719;
}
a = (uint8_t)(c / baudrate);
if (a == 0 || a == 0xFF) {
return -USB_ERR_INVAL;
}
if ((c / a - baudrate) > (baudrate - c / (a + 1))) {
a++;
}
a = (uint8_t)(256 - a);
break;
}
*factor = a;
*divisor = b;
return 0;
}
static int usbh_ch34x_get_version(struct usbh_ch34x *ch34x_class)
{
struct usb_setup_packet *setup;
int ret;
if (!ch34x_class || !ch34x_class->hport) {
return -USB_ERR_INVAL;
}
setup = ch34x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = CH34X_READ_VERSION;
setup->wValue = 0;
setup->wIndex = 0;
setup->wLength = 2;
ret = usbh_control_transfer(ch34x_class->hport, setup, g_ch34x_buf);
if (ret < 0) {
return ret;
}
USB_LOG_INFO("Ch34x chip version %02x:%02x\r\n", g_ch34x_buf[0], g_ch34x_buf[1]);
return ret;
}
static int usbh_ch34x_flow_ctrl(struct usbh_ch34x *ch34x_class)
{
struct usb_setup_packet *setup;
if (!ch34x_class || !ch34x_class->hport) {
return -USB_ERR_INVAL;
}
setup = ch34x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = CH34X_WRITE_REG;
setup->wValue = 0x2727;
setup->wIndex = 0;
setup->wLength = 0;
return usbh_control_transfer(ch34x_class->hport, setup, NULL);
}
int usbh_ch34x_set_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
uint16_t reg_value = 0;
uint16_t value = 0;
uint8_t factor = 0;
uint8_t divisor = 0;
if (!ch34x_class || !ch34x_class->hport) {
return -USB_ERR_INVAL;
}
setup = ch34x_class->hport->setup;
memcpy((uint8_t *)&ch34x_class->line_coding, line_coding, sizeof(struct cdc_line_coding));
/* refer to https://github.com/WCHSoftGroup/ch341ser_linux/blob/main/driver/ch341.c */
switch (line_coding->bParityType) {
case 0:
break;
case 1:
reg_value |= CH341_L_PO;
break;
case 2:
reg_value |= CH341_L_PE;
break;
case 3:
reg_value |= CH341_L_PM;
break;
case 4:
reg_value |= CH341_L_PS;
break;
default:
return -USB_ERR_INVAL;
}
switch (line_coding->bDataBits) {
case 5:
reg_value |= CH341_L_D5;
break;
case 6:
reg_value |= CH341_L_D6;
break;
case 7:
reg_value |= CH341_L_D7;
break;
case 8:
reg_value |= CH341_L_D8;
break;
default:
return -USB_ERR_INVAL;
}
if (line_coding->bCharFormat == 2) {
reg_value |= CH341_L_SB;
}
reg_value |= 0xC0;
value |= 0x9c;
value |= reg_value << 8;
usbh_ch34x_get_baudrate_div(line_coding->dwDTERate, &factor, &divisor);
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = CH34X_SERIAL_INIT;
setup->wValue = value;
setup->wIndex = (factor << 8) | 0x80 | divisor;
setup->wLength = 0;
return usbh_control_transfer(ch34x_class->hport, setup, NULL);
}
int usbh_ch34x_get_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding)
{
memcpy(line_coding, (uint8_t *)&ch34x_class->line_coding, sizeof(struct cdc_line_coding));
return 0;
}
int usbh_ch34x_set_line_state(struct usbh_ch34x *ch34x_class, bool dtr, bool rts)
{
struct usb_setup_packet *setup;
if (!ch34x_class || !ch34x_class->hport) {
return -USB_ERR_INVAL;
}
setup = ch34x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = CH34X_MODEM_CTRL;
setup->wValue = 0x0f | (dtr << 5) | (rts << 6);
setup->wIndex = 0;
setup->wLength = 0;
return usbh_control_transfer(ch34x_class->hport, setup, NULL);
}
static int usbh_ch34x_connect(struct usbh_hubport *hport, uint8_t intf)
{
struct usb_endpoint_descriptor *ep_desc;
int ret = 0;
struct usbh_ch34x *ch34x_class = usbh_ch34x_class_alloc();
if (ch34x_class == NULL) {
USB_LOG_ERR("Fail to alloc ch34x_class\r\n");
return -USB_ERR_NOMEM;
}
ch34x_class->hport = hport;
ch34x_class->intf = intf;
hport->config.intf[intf].priv = ch34x_class;
usbh_ch34x_get_version(ch34x_class);
usbh_ch34x_flow_ctrl(ch34x_class);
for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
continue;
} else {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(ch34x_class->bulkin, ep_desc);
} else {
USBH_EP_INIT(ch34x_class->bulkout, ep_desc);
}
}
}
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, ch34x_class->minor);
USB_LOG_INFO("Register CH34X Class:%s\r\n", hport->config.intf[intf].devname);
#if 0
USB_LOG_INFO("Test ch34x rx and tx and rx for 5 times, baudrate is 115200\r\n");
struct cdc_line_coding linecoding;
uint8_t count = 5;
linecoding.dwDTERate = 115200;
linecoding.bDataBits = 8;
linecoding.bParityType = 0;
linecoding.bCharFormat = 0;
usbh_ch34x_set_line_coding(ch34x_class, &linecoding);
usbh_ch34x_set_line_state(ch34x_class, true, false);
memset(g_ch34x_buf, 'a', sizeof(g_ch34x_buf));
ret = usbh_ch34x_bulk_out_transfer(ch34x_class, g_ch34x_buf, sizeof(g_ch34x_buf), 0xfffffff);
USB_LOG_RAW("out ret:%d\r\n", ret);
while (count--) {
ret = usbh_ch34x_bulk_in_transfer(ch34x_class, g_ch34x_buf, sizeof(g_ch34x_buf), 0xfffffff);
USB_LOG_RAW("in ret:%d\r\n", ret);
if (ret > 0) {
for (uint32_t i = 0; i < ret; i++) {
USB_LOG_RAW("%02x ", g_ch34x_buf[i]);
}
USB_LOG_RAW("\r\n");
}
}
#endif
usbh_ch34x_run(ch34x_class);
return ret;
}
static int usbh_ch34x_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
int ret = 0;
struct usbh_ch34x *ch34x_class = (struct usbh_ch34x *)hport->config.intf[intf].priv;
if (ch34x_class) {
if (ch34x_class->bulkin) {
usbh_kill_urb(&ch34x_class->bulkin_urb);
}
if (ch34x_class->bulkout) {
usbh_kill_urb(&ch34x_class->bulkout_urb);
}
if (hport->config.intf[intf].devname[0] != '\0') {
usb_osal_thread_schedule_other();
USB_LOG_INFO("Unregister CH34X Class:%s\r\n", hport->config.intf[intf].devname);
usbh_ch34x_stop(ch34x_class);
}
usbh_ch34x_class_free(ch34x_class);
}
return ret;
}
int usbh_ch34x_bulk_in_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &ch34x_class->bulkin_urb;
usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkin, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
int usbh_ch34x_bulk_out_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &ch34x_class->bulkout_urb;
usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkout, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
__WEAK void usbh_ch34x_run(struct usbh_ch34x *ch34x_class)
{
(void)ch34x_class;
}
__WEAK void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class)
{
(void)ch34x_class;
}
static const uint16_t ch34x_id_table[][2] = {
{ 0x1A86, 0x7523 },
{ 0, 0 },
};
const struct usbh_class_driver ch34x_class_driver = {
.driver_name = "ch34x",
.connect = usbh_ch34x_connect,
.disconnect = usbh_ch34x_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info ch34x_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = ch34x_id_table,
.class_driver = &ch34x_class_driver
};

View File

@@ -1,76 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_CH34X_H
#define USBH_CH34X_H
#include "usb_cdc.h"
/* Requests */
#define CH34X_READ_VERSION 0x5F
#define CH34X_WRITE_REG 0x9A
#define CH34X_READ_REG 0x95
#define CH34X_SERIAL_INIT 0xA1
#define CH34X_MODEM_CTRL 0xA4
// modem control bits
#define CH34X_BIT_RTS (1 << 6)
#define CH34X_BIT_DTR (1 << 5)
#define CH341_CTO_O 0x10
#define CH341_CTO_D 0x20
#define CH341_CTO_R 0x40
#define CH341_CTI_C 0x01
#define CH341_CTI_DS 0x02
#define CH341_CTRL_RI 0x04
#define CH341_CTI_DC 0x08
#define CH341_CTI_ST 0x0f
#define CH341_L_ER 0x80
#define CH341_L_ET 0x40
#define CH341_L_PS 0x38
#define CH341_L_PM 0x28
#define CH341_L_PE 0x18
#define CH341_L_PO 0x08
#define CH341_L_SB 0x04
#define CH341_L_D8 0x03
#define CH341_L_D7 0x02
#define CH341_L_D6 0x01
#define CH341_L_D5 0x00
struct usbh_ch34x {
struct usbh_hubport *hport;
struct usb_endpoint_descriptor *bulkin; /* Bulk IN endpoint */
struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
struct usbh_urb bulkout_urb;
struct usbh_urb bulkin_urb;
struct cdc_line_coding line_coding;
uint8_t intf;
uint8_t minor;
void *user_data;
};
#ifdef __cplusplus
extern "C" {
#endif
int usbh_ch34x_set_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding);
int usbh_ch34x_get_line_coding(struct usbh_ch34x *ch34x_class, struct cdc_line_coding *line_coding);
int usbh_ch34x_set_line_state(struct usbh_ch34x *ch34x_class, bool dtr, bool rts);
int usbh_ch34x_bulk_in_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
int usbh_ch34x_bulk_out_transfer(struct usbh_ch34x *ch34x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
void usbh_ch34x_run(struct usbh_ch34x *ch34x_class);
void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class);
#ifdef __cplusplus
}
#endif
#endif /* USBH_CH34X_H */

View File

@@ -1,328 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_cp210x.h"
#define DEV_FORMAT "/dev/ttyUSB%d"
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_cp210x_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
#define CONFIG_USBHOST_MAX_CP210X_CLASS 1
static struct usbh_cp210x g_cp210x_class[CONFIG_USBHOST_MAX_CP210X_CLASS];
static uint32_t g_devinuse = 0;
static struct usbh_cp210x *usbh_cp210x_class_alloc(void)
{
uint8_t devno;
for (devno = 0; devno < CONFIG_USBHOST_MAX_CP210X_CLASS; devno++) {
if ((g_devinuse & (1U << devno)) == 0) {
g_devinuse |= (1U << devno);
memset(&g_cp210x_class[devno], 0, sizeof(struct usbh_cp210x));
g_cp210x_class[devno].minor = devno;
return &g_cp210x_class[devno];
}
}
return NULL;
}
static void usbh_cp210x_class_free(struct usbh_cp210x *cp210x_class)
{
uint8_t devno = cp210x_class->minor;
if (devno < 32) {
g_devinuse &= ~(1U << devno);
}
memset(cp210x_class, 0, sizeof(struct usbh_cp210x));
}
static int usbh_cp210x_enable(struct usbh_cp210x *cp210x_class)
{
struct usb_setup_packet *setup;
if (!cp210x_class || !cp210x_class->hport) {
return -USB_ERR_INVAL;
}
setup = cp210x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_IFC_ENABLE;
setup->wValue = 1;
setup->wIndex = cp210x_class->intf;
setup->wLength = 0;
return usbh_control_transfer(cp210x_class->hport, setup, NULL);
}
static int usbh_cp210x_set_flow(struct usbh_cp210x *cp210x_class)
{
struct usb_setup_packet *setup;
if (!cp210x_class || !cp210x_class->hport) {
return -USB_ERR_INVAL;
}
setup = cp210x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_FLOW;
setup->wValue = 0;
setup->wIndex = cp210x_class->intf;
setup->wLength = 16;
memset(g_cp210x_buf, 0, 16);
g_cp210x_buf[13] = 0x20;
return usbh_control_transfer(cp210x_class->hport, setup, g_cp210x_buf);
}
static int usbh_cp210x_set_chars(struct usbh_cp210x *cp210x_class)
{
struct usb_setup_packet *setup;
if (!cp210x_class || !cp210x_class->hport) {
return -USB_ERR_INVAL;
}
setup = cp210x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_CHARS;
setup->wValue = 0;
setup->wIndex = cp210x_class->intf;
setup->wLength = 6;
memset(g_cp210x_buf, 0, 6);
g_cp210x_buf[0] = 0x80;
g_cp210x_buf[4] = 0x88;
g_cp210x_buf[5] = 0x28;
return usbh_control_transfer(cp210x_class->hport, setup, g_cp210x_buf);
}
static int usbh_cp210x_set_baudrate(struct usbh_cp210x *cp210x_class, uint32_t baudrate)
{
struct usb_setup_packet *setup;
if (!cp210x_class || !cp210x_class->hport) {
return -USB_ERR_INVAL;
}
setup = cp210x_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_BAUDRATE;
setup->wValue = 0;
setup->wIndex = cp210x_class->intf;
setup->wLength = 4;
memcpy(g_cp210x_buf, (uint8_t *)&baudrate, 4);
return usbh_control_transfer(cp210x_class->hport, setup, g_cp210x_buf);
}
static int usbh_cp210x_set_data_format(struct usbh_cp210x *cp210x_class, uint8_t databits, uint8_t parity, uint8_t stopbits)
{
struct usb_setup_packet *setup;
uint16_t value;
if (!cp210x_class || !cp210x_class->hport) {
return -USB_ERR_INVAL;
}
setup = cp210x_class->hport->setup;
value = ((databits & 0x0F) << 8) | ((parity & 0x0f) << 4) | ((stopbits & 0x03) << 0);
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_LINE_CTL;
setup->wValue = value;
setup->wIndex = cp210x_class->intf;
setup->wLength = 0;
return usbh_control_transfer(cp210x_class->hport, setup, NULL);
}
static int usbh_cp210x_set_mhs(struct usbh_cp210x *cp210x_class, uint8_t dtr, uint8_t rts, uint8_t dtr_mask, uint8_t rts_mask)
{
struct usb_setup_packet *setup;
uint16_t value;
if (!cp210x_class || !cp210x_class->hport) {
return -USB_ERR_INVAL;
}
setup = cp210x_class->hport->setup;
value = ((dtr & 0x01) << 0) | ((rts & 0x01) << 1) | ((dtr_mask & 0x01) << 8) | ((rts_mask & 0x01) << 9);
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CP210X_SET_MHS;
setup->wValue = value;
setup->wIndex = cp210x_class->intf;
setup->wLength = 0;
return usbh_control_transfer(cp210x_class->hport, setup, NULL);
}
int usbh_cp210x_set_line_coding(struct usbh_cp210x *cp210x_class, struct cdc_line_coding *line_coding)
{
memcpy((uint8_t *)&cp210x_class->line_coding, line_coding, sizeof(struct cdc_line_coding));
usbh_cp210x_set_baudrate(cp210x_class, line_coding->dwDTERate);
return usbh_cp210x_set_data_format(cp210x_class, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat);
}
int usbh_cp210x_get_line_coding(struct usbh_cp210x *cp210x_class, struct cdc_line_coding *line_coding)
{
memcpy(line_coding, (uint8_t *)&cp210x_class->line_coding, sizeof(struct cdc_line_coding));
return 0;
}
int usbh_cp210x_set_line_state(struct usbh_cp210x *cp210x_class, bool dtr, bool rts)
{
return usbh_cp210x_set_mhs(cp210x_class, dtr, rts, 1, 1);
}
static int usbh_cp210x_connect(struct usbh_hubport *hport, uint8_t intf)
{
struct usb_endpoint_descriptor *ep_desc;
int ret = 0;
struct usbh_cp210x *cp210x_class = usbh_cp210x_class_alloc();
if (cp210x_class == NULL) {
USB_LOG_ERR("Fail to alloc cp210x_class\r\n");
return -USB_ERR_NOMEM;
}
cp210x_class->hport = hport;
cp210x_class->intf = intf;
hport->config.intf[intf].priv = cp210x_class;
usbh_cp210x_enable(cp210x_class);
usbh_cp210x_set_flow(cp210x_class);
usbh_cp210x_set_chars(cp210x_class);
for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(cp210x_class->bulkin, ep_desc);
} else {
USBH_EP_INIT(cp210x_class->bulkout, ep_desc);
}
}
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, cp210x_class->minor);
USB_LOG_INFO("Register CP210X Class:%s\r\n", hport->config.intf[intf].devname);
#if 0
USB_LOG_INFO("Test cp2102 rx and tx and rx for 5 times, baudrate is 115200\r\n");
struct cdc_line_coding linecoding;
uint8_t count = 5;
linecoding.dwDTERate = 115200;
linecoding.bDataBits = 8;
linecoding.bParityType = 0;
linecoding.bCharFormat = 0;
usbh_cp210x_set_line_coding(cp210x_class, &linecoding);
usbh_cp210x_set_line_state(cp210x_class, true, false);
memset(g_cp210x_buf, 'a', sizeof(g_cp210x_buf));
ret = usbh_cp210x_bulk_out_transfer(cp210x_class, g_cp210x_buf, sizeof(g_cp210x_buf), 0xfffffff);
USB_LOG_RAW("out ret:%d\r\n", ret);
while (count--) {
ret = usbh_cp210x_bulk_in_transfer(cp210x_class, g_cp210x_buf, sizeof(g_cp210x_buf), 0xfffffff);
USB_LOG_RAW("in ret:%d\r\n", ret);
if (ret > 0) {
for (uint32_t i = 0; i < ret; i++) {
USB_LOG_RAW("%02x ", g_cp210x_buf[i]);
}
USB_LOG_RAW("\r\n");
}
}
#endif
usbh_cp210x_run(cp210x_class);
return ret;
}
static int usbh_cp210x_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
int ret = 0;
struct usbh_cp210x *cp210x_class = (struct usbh_cp210x *)hport->config.intf[intf].priv;
if (cp210x_class) {
if (cp210x_class->bulkin) {
usbh_kill_urb(&cp210x_class->bulkin_urb);
}
if (cp210x_class->bulkout) {
usbh_kill_urb(&cp210x_class->bulkout_urb);
}
if (hport->config.intf[intf].devname[0] != '\0') {
usb_osal_thread_schedule_other();
USB_LOG_INFO("Unregister CP210X Class:%s\r\n", hport->config.intf[intf].devname);
usbh_cp210x_stop(cp210x_class);
}
usbh_cp210x_class_free(cp210x_class);
}
return ret;
}
int usbh_cp210x_bulk_in_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &cp210x_class->bulkin_urb;
usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkin, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
int usbh_cp210x_bulk_out_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &cp210x_class->bulkout_urb;
usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkout, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
__WEAK void usbh_cp210x_run(struct usbh_cp210x *cp210x_class)
{
(void)cp210x_class;
}
__WEAK void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class)
{
(void)cp210x_class;
}
static const uint16_t cp210x_id_table[][2] = {
{ 0x10C4, 0xEA60 },
{ 0, 0 },
};
const struct usbh_class_driver cp210x_class_driver = {
.driver_name = "cp210x",
.connect = usbh_cp210x_connect,
.disconnect = usbh_cp210x_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info cp210x_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = cp210x_id_table,
.class_driver = &cp210x_class_driver
};

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_CP210X_H
#define USBH_CP210X_H
#include "usb_cdc.h"
/* Requests */
#define CP210X_IFC_ENABLE 0x00
#define CP210X_SET_BAUDDIV 0x01
#define CP210X_GET_BAUDDIV 0x02
#define CP210X_SET_LINE_CTL 0x03 // Set parity, data bits, stop bits
#define CP210X_GET_LINE_CTL 0x04
#define CP210X_SET_BREAK 0x05
#define CP210X_IMM_CHAR 0x06
#define CP210X_SET_MHS 0x07 // Set DTR, RTS
#define CP210X_GET_MDMSTS 0x08
#define CP210X_SET_XON 0x09
#define CP210X_SET_XOFF 0x0A
#define CP210X_SET_EVENTMASK 0x0B
#define CP210X_GET_EVENTMASK 0x0C
#define CP210X_SET_CHAR 0x0D
#define CP210X_GET_CHARS 0x0E
#define CP210X_GET_PROPS 0x0F
#define CP210X_GET_COMM_STATUS 0x10
#define CP210X_RESET 0x11
#define CP210X_PURGE 0x12
#define CP210X_SET_FLOW 0x13
#define CP210X_GET_FLOW 0x14
#define CP210X_EMBED_EVENTS 0x15
#define CP210X_GET_EVENTSTATE 0x16
#define CP210X_SET_CHARS 0x19
#define CP210X_GET_BAUDRATE 0x1D
#define CP210X_SET_BAUDRATE 0x1E // Set baudrate
#define CP210X_VENDOR_SPECIFIC 0xFF
struct usbh_cp210x {
struct usbh_hubport *hport;
struct usb_endpoint_descriptor *bulkin; /* Bulk IN endpoint */
struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
struct usbh_urb bulkout_urb;
struct usbh_urb bulkin_urb;
struct cdc_line_coding line_coding;
uint8_t intf;
uint8_t minor;
void *user_data;
};
#ifdef __cplusplus
extern "C" {
#endif
int usbh_cp210x_set_line_coding(struct usbh_cp210x *ftdi_class, struct cdc_line_coding *line_coding);
int usbh_cp210x_get_line_coding(struct usbh_cp210x *ftdi_class, struct cdc_line_coding *line_coding);
int usbh_cp210x_set_line_state(struct usbh_cp210x *ftdi_class, bool dtr, bool rts);
int usbh_cp210x_bulk_in_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
int usbh_cp210x_bulk_out_transfer(struct usbh_cp210x *cp210x_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
void usbh_cp210x_run(struct usbh_cp210x *cp210x_class);
void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class);
#ifdef __cplusplus
}
#endif
#endif /* USBH_CP210X_H */

View File

@@ -1,510 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_ftdi.h"
#define DEV_FORMAT "/dev/ttyUSB%d"
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_ftdi_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
#define CONFIG_USBHOST_MAX_FTDI_CLASS 1
static struct usbh_ftdi g_ftdi_class[CONFIG_USBHOST_MAX_FTDI_CLASS];
static uint32_t g_devinuse = 0;
static const char *ftdi_chip_name[] = {
[SIO] = "SIO", /* the serial part of FT8U100AX */
[FT232A] = "FT232A",
[FT232B] = "FT232B",
[FT2232C] = "FT2232C/D",
[FT232R] = "FT232R",
[FT232H] = "FT232H",
[FT2232H] = "FT2232H",
[FT4232H] = "FT4232H",
[FT4232HA] = "FT4232HA",
[FT232HP] = "FT232HP",
[FT233HP] = "FT233HP",
[FT2232HP] = "FT2232HP",
[FT2233HP] = "FT2233HP",
[FT4232HP] = "FT4232HP",
[FT4233HP] = "FT4233HP",
[FTX] = "FT-X",
};
static struct usbh_ftdi *usbh_ftdi_class_alloc(void)
{
uint8_t devno;
for (devno = 0; devno < CONFIG_USBHOST_MAX_FTDI_CLASS; devno++) {
if ((g_devinuse & (1U << devno)) == 0) {
g_devinuse |= (1U << devno);
memset(&g_ftdi_class[devno], 0, sizeof(struct usbh_ftdi));
g_ftdi_class[devno].minor = devno;
return &g_ftdi_class[devno];
}
}
return NULL;
}
static void usbh_ftdi_class_free(struct usbh_ftdi *ftdi_class)
{
uint8_t devno = ftdi_class->minor;
if (devno < 32) {
g_devinuse &= ~(1U << devno);
}
memset(ftdi_class, 0, sizeof(struct usbh_ftdi));
}
/*
* Divide positive or negative dividend by positive or negative divisor
* and round to closest integer. Result is undefined for negative
* divisors if the dividend variable type is unsigned and for negative
* dividends if the divisor variable type is unsigned.
*/
#define DIV_ROUND_CLOSEST(x, divisor) ( \
{ \
typeof(x) __x = x; \
typeof(divisor) __d = divisor; \
(((typeof(x))-1) > 0 || \
((typeof(divisor))-1) > 0 || \
(((__x) > 0) == ((__d) > 0))) ? \
(((__x) + ((__d) / 2)) / (__d)) : \
(((__x) - ((__d) / 2)) / (__d)); \
})
static uint32_t ftdi_232bm_baud_base_to_divisor(uint32_t baud, int base)
{
static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
uint32_t divisor;
/* divisor shifted 3 bits to the left */
int divisor3 = DIV_ROUND_CLOSEST(base, 2 * baud);
divisor = divisor3 >> 3;
divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
/* Deal with special cases for highest baud rates. */
if (divisor == 1) /* 1.0 */
divisor = 0;
else if (divisor == 0x4001) /* 1.5 */
divisor = 1;
return divisor;
}
static uint32_t ftdi_232bm_baud_to_divisor(uint32_t baud)
{
return ftdi_232bm_baud_base_to_divisor(baud, 48000000);
}
static uint32_t ftdi_2232h_baud_base_to_divisor(uint32_t baud, int base)
{
static const unsigned char divfrac[8] = { 0, 3, 2, 4, 1, 5, 6, 7 };
uint32_t divisor;
int divisor3;
/* hi-speed baud rate is 10-bit sampling instead of 16-bit */
divisor3 = DIV_ROUND_CLOSEST(8 * base, 10 * baud);
divisor = divisor3 >> 3;
divisor |= (uint32_t)divfrac[divisor3 & 0x7] << 14;
/* Deal with special cases for highest baud rates. */
if (divisor == 1) /* 1.0 */
divisor = 0;
else if (divisor == 0x4001) /* 1.5 */
divisor = 1;
/*
* Set this bit to turn off a divide by 2.5 on baud rate generator
* This enables baud rates up to 12Mbaud but cannot reach below 1200
* baud with this bit set
*/
divisor |= 0x00020000;
return divisor;
}
static uint32_t ftdi_2232h_baud_to_divisor(uint32_t baud)
{
return ftdi_2232h_baud_base_to_divisor(baud, 120000000);
}
int usbh_ftdi_reset(struct usbh_ftdi *ftdi_class)
{
struct usb_setup_packet *setup;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_RESET_REQUEST;
setup->wValue = 0;
setup->wIndex = ftdi_class->intf;
setup->wLength = 0;
return usbh_control_transfer(ftdi_class->hport, setup, NULL);
}
static int usbh_ftdi_set_modem(struct usbh_ftdi *ftdi_class, uint16_t value)
{
struct usb_setup_packet *setup;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_SET_MODEM_CTRL_REQUEST;
setup->wValue = value;
setup->wIndex = ftdi_class->intf;
setup->wLength = 0;
return usbh_control_transfer(ftdi_class->hport, setup, NULL);
}
static int usbh_ftdi_set_baudrate(struct usbh_ftdi *ftdi_class, uint32_t baudrate)
{
struct usb_setup_packet *setup;
uint32_t div_value;
uint16_t value;
uint8_t baudrate_high;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
switch (ftdi_class->chip_type) {
case FT232B:
case FT2232C:
case FT232R:
if (baudrate > 3000000) {
return -USB_ERR_INVAL;
}
div_value = ftdi_232bm_baud_to_divisor(baudrate);
break;
default:
if ((baudrate <= 12000000) && (baudrate >= 1200)) {
div_value = ftdi_2232h_baud_to_divisor(baudrate);
} else {
return -USB_ERR_INVAL;
}
break;
}
value = div_value & 0xFFFF;
baudrate_high = (div_value >> 16) & 0xff;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_SET_BAUDRATE_REQUEST;
setup->wValue = value;
setup->wIndex = (baudrate_high << 8) | ftdi_class->intf;
setup->wLength = 0;
return usbh_control_transfer(ftdi_class->hport, setup, NULL);
}
static int usbh_ftdi_set_data_format(struct usbh_ftdi *ftdi_class, uint8_t databits, uint8_t parity, uint8_t stopbits, uint8_t isbreak)
{
/**
* D0-D7 databits BITS_7=7, BITS_8=8
* D8-D10 parity NONE=0, ODD=1, EVEN=2, MARK=3, SPACE=4
* D11-D12 STOP_BIT_1=0, STOP_BIT_15=1, STOP_BIT_2=2
* D14 BREAK_OFF=0, BREAK_ON=1
**/
struct usb_setup_packet *setup;
uint16_t value;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
value = ((isbreak & 0x01) << 14) | ((stopbits & 0x03) << 11) | ((parity & 0x0f) << 8) | (databits & 0x0f);
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_SET_DATA_REQUEST;
setup->wValue = value;
setup->wIndex = ftdi_class->intf;
setup->wLength = 0;
return usbh_control_transfer(ftdi_class->hport, setup, NULL);
}
static int usbh_ftdi_set_latency_timer(struct usbh_ftdi *ftdi_class, uint16_t value)
{
struct usb_setup_packet *setup;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_SET_LATENCY_TIMER_REQUEST;
setup->wValue = value;
setup->wIndex = ftdi_class->intf;
setup->wLength = 0;
return usbh_control_transfer(ftdi_class->hport, setup, NULL);
}
static int usbh_ftdi_set_flow_ctrl(struct usbh_ftdi *ftdi_class, uint16_t value)
{
struct usb_setup_packet *setup;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_SET_FLOW_CTRL_REQUEST;
setup->wValue = value;
setup->wIndex = ftdi_class->intf;
setup->wLength = 0;
return usbh_control_transfer(ftdi_class->hport, setup, NULL);
}
static int usbh_ftdi_read_modem_status(struct usbh_ftdi *ftdi_class)
{
struct usb_setup_packet *setup;
int ret;
if (!ftdi_class || !ftdi_class->hport) {
return -USB_ERR_INVAL;
}
setup = ftdi_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
setup->bRequest = SIO_POLL_MODEM_STATUS_REQUEST;
setup->wValue = 0x0000;
setup->wIndex = ftdi_class->intf;
setup->wLength = 2;
ret = usbh_control_transfer(ftdi_class->hport, setup, g_ftdi_buf);
if (ret < 0) {
return ret;
}
memcpy(ftdi_class->modem_status, g_ftdi_buf, 2);
return ret;
}
int usbh_ftdi_set_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding)
{
memcpy((uint8_t *)&ftdi_class->line_coding, line_coding, sizeof(struct cdc_line_coding));
int ret = usbh_ftdi_set_baudrate(ftdi_class, line_coding->dwDTERate);
if (ret < 0) {
return ret;
}
return usbh_ftdi_set_data_format(ftdi_class, line_coding->bDataBits, line_coding->bParityType, line_coding->bCharFormat, 0);
}
int usbh_ftdi_get_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding)
{
memcpy(line_coding, (uint8_t *)&ftdi_class->line_coding, sizeof(struct cdc_line_coding));
return 0;
}
int usbh_ftdi_set_line_state(struct usbh_ftdi *ftdi_class, bool dtr, bool rts)
{
int ret;
if (dtr) {
usbh_ftdi_set_modem(ftdi_class, SIO_SET_DTR_HIGH);
} else {
usbh_ftdi_set_modem(ftdi_class, SIO_SET_DTR_LOW);
}
if (rts) {
ret = usbh_ftdi_set_modem(ftdi_class, SIO_SET_RTS_HIGH);
} else {
ret = usbh_ftdi_set_modem(ftdi_class, SIO_SET_RTS_LOW);
}
return ret;
}
static int usbh_ftdi_connect(struct usbh_hubport *hport, uint8_t intf)
{
struct usb_endpoint_descriptor *ep_desc;
int ret = 0;
uint16_t version;
struct usbh_ftdi *ftdi_class = usbh_ftdi_class_alloc();
if (ftdi_class == NULL) {
USB_LOG_ERR("Fail to alloc ftdi_class\r\n");
return -USB_ERR_NOMEM;
}
ftdi_class->hport = hport;
ftdi_class->intf = intf;
hport->config.intf[intf].priv = ftdi_class;
version = hport->device_desc.bcdDevice;
switch (version) {
case 0x400:
ftdi_class->chip_type = FT232B;
break;
case 0x500:
ftdi_class->chip_type = FT2232C;
break;
case 0x600:
ftdi_class->chip_type = FT232R;
break;
case 0x700:
ftdi_class->chip_type = FT2232H;
break;
case 0x800:
ftdi_class->chip_type = FT4232H;
break;
case 0x900:
ftdi_class->chip_type = FT232H;
break;
default:
USB_LOG_ERR("Unknown FTDI chip version:%04x\r\n", version);
return -USB_ERR_NOTSUPP;
}
USB_LOG_INFO("FTDI chip name:%s\r\n", ftdi_chip_name[ftdi_class->chip_type]);
usbh_ftdi_reset(ftdi_class);
usbh_ftdi_set_flow_ctrl(ftdi_class, SIO_DISABLE_FLOW_CTRL);
usbh_ftdi_set_latency_timer(ftdi_class, 0x10);
usbh_ftdi_read_modem_status(ftdi_class);
USB_LOG_INFO("modem status:%02x:%02x\r\n", ftdi_class->modem_status[0], ftdi_class->modem_status[1]);
for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(ftdi_class->bulkin, ep_desc);
} else {
USBH_EP_INIT(ftdi_class->bulkout, ep_desc);
}
}
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, ftdi_class->minor);
USB_LOG_INFO("Register FTDI Class:%s\r\n", hport->config.intf[intf].devname);
#if 0
USB_LOG_INFO("Test ftdi rx and tx and rx for 5 times, baudrate is 115200\r\n");
struct cdc_line_coding linecoding;
uint8_t count = 5;
linecoding.dwDTERate = 115200;
linecoding.bDataBits = 8;
linecoding.bParityType = 0;
linecoding.bCharFormat = 0;
usbh_ftdi_set_line_coding(ftdi_class, &linecoding);
usbh_ftdi_set_line_state(ftdi_class, true, false);
memset(g_ftdi_buf, 'a', sizeof(g_ftdi_buf));
ret = usbh_ftdi_bulk_out_transfer(ftdi_class, g_ftdi_buf, sizeof(g_ftdi_buf), 0xfffffff);
USB_LOG_RAW("out ret:%d\r\n", ret);
while (count--) {
ret = usbh_ftdi_bulk_in_transfer(ftdi_class, g_ftdi_buf, sizeof(g_ftdi_buf), 0xfffffff);
USB_LOG_RAW("in ret:%d\r\n", ret);
if (ret > 0) {
for (uint32_t i = 0; i < ret; i++) {
USB_LOG_RAW("%02x ", g_ftdi_buf[i]);
}
}
USB_LOG_RAW("\r\n");
}
#endif
usbh_ftdi_run(ftdi_class);
return ret;
}
static int usbh_ftdi_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
int ret = 0;
struct usbh_ftdi *ftdi_class = (struct usbh_ftdi *)hport->config.intf[intf].priv;
if (ftdi_class) {
if (ftdi_class->bulkin) {
usbh_kill_urb(&ftdi_class->bulkin_urb);
}
if (ftdi_class->bulkout) {
usbh_kill_urb(&ftdi_class->bulkout_urb);
}
if (hport->config.intf[intf].devname[0] != '\0') {
usb_osal_thread_schedule_other();
USB_LOG_INFO("Unregister FTDI Class:%s\r\n", hport->config.intf[intf].devname);
usbh_ftdi_stop(ftdi_class);
}
usbh_ftdi_class_free(ftdi_class);
}
return ret;
}
int usbh_ftdi_bulk_in_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &ftdi_class->bulkin_urb;
usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkin, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
int usbh_ftdi_bulk_out_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &ftdi_class->bulkout_urb;
usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkout, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
__WEAK void usbh_ftdi_run(struct usbh_ftdi *ftdi_class)
{
(void)ftdi_class;
}
__WEAK void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class)
{
(void)ftdi_class;
}
static const uint16_t ftdi_id_table[][2] = {
{ 0x0403, 0x6001 },
{ 0x0403, 0x6010 },
{ 0, 0 },
};
const struct usbh_class_driver ftdi_class_driver = {
.driver_name = "ftdi",
.connect = usbh_ftdi_connect,
.disconnect = usbh_ftdi_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info ftdi_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = ftdi_id_table,
.class_driver = &ftdi_class_driver
};

View File

@@ -1,96 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_FTDI_H
#define USBH_FTDI_H
#include "usb_cdc.h"
/* Requests */
#define SIO_RESET_REQUEST 0x00 /* Reset the port */
#define SIO_SET_MODEM_CTRL_REQUEST 0x01 /* Set the modem control register */
#define SIO_SET_FLOW_CTRL_REQUEST 0x02 /* Set flow control register */
#define SIO_SET_BAUDRATE_REQUEST 0x03 /* Set baud rate */
#define SIO_SET_DATA_REQUEST 0x04 /* Set the data characteristics of the port */
#define SIO_POLL_MODEM_STATUS_REQUEST 0x05
#define SIO_SET_EVENT_CHAR_REQUEST 0x06
#define SIO_SET_ERROR_CHAR_REQUEST 0x07
#define SIO_SET_LATENCY_TIMER_REQUEST 0x09
#define SIO_GET_LATENCY_TIMER_REQUEST 0x0A
#define SIO_SET_BITMODE_REQUEST 0x0B
#define SIO_READ_PINS_REQUEST 0x0C
#define SIO_READ_EEPROM_REQUEST 0x90
#define SIO_WRITE_EEPROM_REQUEST 0x91
#define SIO_ERASE_EEPROM_REQUEST 0x92
#define SIO_DISABLE_FLOW_CTRL 0x0
#define SIO_RTS_CTS_HS (0x1 << 8)
#define SIO_DTR_DSR_HS (0x2 << 8)
#define SIO_XON_XOFF_HS (0x4 << 8)
#define SIO_SET_DTR_MASK 0x1
#define SIO_SET_DTR_HIGH (1 | (SIO_SET_DTR_MASK << 8))
#define SIO_SET_DTR_LOW (0 | (SIO_SET_DTR_MASK << 8))
#define SIO_SET_RTS_MASK 0x2
#define SIO_SET_RTS_HIGH (2 | (SIO_SET_RTS_MASK << 8))
#define SIO_SET_RTS_LOW (0 | (SIO_SET_RTS_MASK << 8))
#define SIO_RTS_CTS_HS (0x1 << 8)
enum ftdi_chip_type {
SIO,
FT232A,
FT232B,
FT2232C,
FT232R,
FT232H,
FT2232H,
FT4232H,
FT4232HA,
FT232HP,
FT233HP,
FT2232HP,
FT2233HP,
FT4232HP,
FT4233HP,
FTX,
};
struct usbh_ftdi {
struct usbh_hubport *hport;
struct usb_endpoint_descriptor *bulkin; /* Bulk IN endpoint */
struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
struct usbh_urb bulkout_urb;
struct usbh_urb bulkin_urb;
struct cdc_line_coding line_coding;
uint8_t intf;
uint8_t minor;
uint8_t modem_status[2];
enum ftdi_chip_type chip_type;
void *user_data;
};
#ifdef __cplusplus
extern "C" {
#endif
int usbh_ftdi_set_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding);
int usbh_ftdi_get_line_coding(struct usbh_ftdi *ftdi_class, struct cdc_line_coding *line_coding);
int usbh_ftdi_set_line_state(struct usbh_ftdi *ftdi_class, bool dtr, bool rts);
int usbh_ftdi_bulk_in_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
int usbh_ftdi_bulk_out_transfer(struct usbh_ftdi *ftdi_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
void usbh_ftdi_run(struct usbh_ftdi *ftdi_class);
void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class);
#ifdef __cplusplus
}
#endif
#endif /* USBH_FTDI_H */

View File

@@ -1,449 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
* Copyright (c) 2024, Derek Konigsberg
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "usbh_core.h"
#include "usbh_pl2303.h"
#undef USB_DBG_TAG
#define USB_DBG_TAG "usbh_pl2303"
#include "usb_log.h"
#define DEV_FORMAT "/dev/ttyUSB%d"
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_pl2303_buf[USB_ALIGN_UP(64, CONFIG_USB_ALIGN_SIZE)];
#define CONFIG_USBHOST_MAX_PL2303_CLASS 1
#define UT_WRITE_VENDOR_DEVICE (USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE)
#define UT_READ_VENDOR_DEVICE (USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE)
static struct usbh_pl2303 g_pl2303_class[CONFIG_USBHOST_MAX_PL2303_CLASS];
static uint32_t g_devinuse = 0;
static struct usbh_pl2303 *usbh_pl2303_class_alloc(void)
{
uint8_t devno;
for (devno = 0; devno < CONFIG_USBHOST_MAX_PL2303_CLASS; devno++) {
if ((g_devinuse & (1U << devno)) == 0) {
g_devinuse |= (1U << devno);
memset(&g_pl2303_class[devno], 0, sizeof(struct usbh_pl2303));
g_pl2303_class[devno].minor = devno;
return &g_pl2303_class[devno];
}
}
return NULL;
}
static void usbh_pl2303_class_free(struct usbh_pl2303 *pl2303_class)
{
uint8_t devno = pl2303_class->minor;
if (devno < 32) {
g_devinuse &= ~(1U << devno);
}
memset(pl2303_class, 0, sizeof(struct usbh_pl2303));
}
static int usbh_pl2303_get_chiptype(struct usbh_pl2303 *pl2303_class)
{
int ret = 0;
switch (pl2303_class->hport->device_desc.bcdDevice) {
case 0x0300:
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HX;
/* or TA, that is HX with external crystal */
break;
case 0x0400:
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HXD;
/* or EA, that is HXD with ESD protection */
/* or RA, that has internal voltage level converter that works only up to 1Mbaud (!) */
break;
case 0x0500:
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HXD;
/* in fact it's TB, that is HXD with external crystal */
break;
default:
/* NOTE: I have no info about the bcdDevice for the base PL2303 (up to 1.2Mbaud,
only fixed rates) and for PL2303SA (8-pin chip, up to 115200 baud */
/* Determine the chip type. This algorithm is taken from Linux. */
if (pl2303_class->hport->device_desc.bDeviceClass == 0x02) {
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303;
} else if (pl2303_class->hport->device_desc.bMaxPacketSize0 == 0x40) {
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HX;
} else {
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303;
}
break;
}
/*
* The new chip revision PL2303HXN is only compatible with the new
* PLCOM_SET_REQUEST_PL2303HXN command. Issuing the old command
* PLCOM_SET_REQUEST to the new chip raises an error. Thus, PL2303HX
* and PL2303HXN can be distinguished by issuing an old-style request
* (on a status register) to the new chip and checking the error.
*/
if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HX) {
struct usb_setup_packet *setup = pl2303_class->hport->setup;
setup->bmRequestType = UT_READ_VENDOR_DEVICE;
setup->bRequest = PL2303_SET_REQUEST;
setup->wValue = PL2303_STATUS_REG_PL2303HX;
setup->wIndex = 0;
setup->wLength = 1;
ret = usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
if (ret == -USB_ERR_STALL) {
pl2303_class->chiptype = USBH_PL2303_TYPE_PL2303HXN;
ret = 0;
} else if (ret < 0) {
USB_LOG_WRN("Error checking chip type: %d\r\n", ret);
return ret;
}
}
switch (pl2303_class->chiptype) {
case USBH_PL2303_TYPE_PL2303:
USB_LOG_INFO("chiptype = 2303\r\n");
break;
case USBH_PL2303_TYPE_PL2303HX:
USB_LOG_INFO("chiptype = 2303HX/TA\r\n");
break;
case USBH_PL2303_TYPE_PL2303HXN:
USB_LOG_INFO("chiptype = 2303HXN\r\n");
break;
case USBH_PL2303_TYPE_PL2303HXD:
USB_LOG_INFO("chiptype = 2303HXD/TB/RA/EA\r\n");
break;
default:
USB_LOG_INFO("chiptype = [%d]\r\n", pl2303_class->chiptype);
break;
}
return ret;
}
static int usbh_pl2303_do(struct usbh_pl2303 *pl2303_class,
uint8_t req_type, uint8_t request, uint16_t value, uint16_t index,
uint16_t length)
{
struct usb_setup_packet *setup;
if (!pl2303_class || !pl2303_class->hport) {
return -USB_ERR_INVAL;
}
setup = pl2303_class->hport->setup;
setup->bmRequestType = req_type;
setup->bRequest = request;
setup->wValue = value;
setup->wIndex = index;
setup->wLength = length;
return usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
}
int usbh_pl2303_set_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
if (!pl2303_class || !pl2303_class->hport) {
return -USB_ERR_INVAL;
}
setup = pl2303_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = pl2303_class->intf;
setup->wLength = 7;
memcpy(g_pl2303_buf, line_coding, sizeof(struct cdc_line_coding));
return usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
}
int usbh_pl2303_get_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding)
{
struct usb_setup_packet *setup;
int ret;
if (!pl2303_class || !pl2303_class->hport) {
return -USB_ERR_INVAL;
}
setup = pl2303_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_GET_LINE_CODING;
setup->wValue = 0;
setup->wIndex = pl2303_class->intf;
setup->wLength = 7;
ret = usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
if (ret < 0) {
return ret;
}
memcpy(line_coding, g_pl2303_buf, sizeof(struct cdc_line_coding));
return ret;
}
int usbh_pl2303_set_line_state(struct usbh_pl2303 *pl2303_class, bool dtr, bool rts)
{
struct usb_setup_packet *setup;
if (!pl2303_class || !pl2303_class->hport) {
return -USB_ERR_INVAL;
}
setup = pl2303_class->hport->setup;
setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_CLASS | USB_REQUEST_RECIPIENT_INTERFACE;
setup->bRequest = CDC_REQUEST_SET_CONTROL_LINE_STATE;
setup->wValue = (dtr << 0) | (rts << 1);
setup->wIndex = pl2303_class->intf;
setup->wLength = 0;
return usbh_control_transfer(pl2303_class->hport, setup, NULL);
}
static int usbh_pl2303_connect(struct usbh_hubport *hport, uint8_t intf)
{
struct usb_endpoint_descriptor *ep_desc;
int ret = 0;
struct usbh_pl2303 *pl2303_class = usbh_pl2303_class_alloc();
if (pl2303_class == NULL) {
USB_LOG_ERR("Fail to alloc pl2303_class\r\n");
return -USB_ERR_NOMEM;
}
pl2303_class->hport = hport;
pl2303_class->intf = intf;
hport->config.intf[intf].priv = pl2303_class;
do {
ret = usbh_pl2303_get_chiptype(pl2303_class);
if (ret < 0) {
break;
}
/* Startup reset sequence, if necessary for the chip type */
if (pl2303_class->chiptype != USBH_PL2303_TYPE_PL2303HXN) {
struct usb_setup_packet *setup = pl2303_class->hport->setup;
setup->bmRequestType = UT_WRITE_VENDOR_DEVICE;
setup->bRequest = PL2303_SET_REQUEST;
setup->wValue = 0;
setup->wIndex = pl2303_class->intf;
setup->wLength = 0;
ret = usbh_control_transfer(pl2303_class->hport, setup, g_pl2303_buf);
if (ret < 0) {
USB_LOG_WRN("Initialization reset failed: %d\r\n", ret);
break;
}
}
if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303) {
/* HX variants seem to lock up after a clear stall request. */
/*
* The FreeBSD code sets the stall flags on the in and out pipes
* here. Have no idea exactly how to do this, or if it is necessary.
* May just leave this code unwritten until test hardware is available.
*/
} else if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HX || pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HXD) {
/* Reset upstream data pipes */
ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 8, 0, 0);
if (ret < 0) {
USB_LOG_WRN("Could not reset upstream data pipes (8,0): %d\r\n", ret);
break;
}
ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 9, 0, 0);
if (ret < 0) {
USB_LOG_WRN("Could not reset upstream data pipes (9,0): %d\r\n", ret);
break;
}
} else if (pl2303_class->chiptype == USBH_PL2303_TYPE_PL2303HXN) {
/* Reset upstream data pipes */
ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST_PL2303HXN, 0x07, 0x03, 0);
if (ret < 0) {
USB_LOG_WRN("Could not reset upstream data pipes (7,3): %d\r\n", ret);
break;
}
}
/* Final device initialization, if necessary for the chip type */
if (pl2303_class->chiptype != USBH_PL2303_TYPE_PL2303HXN) {
if (usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x0404, 0, 0) < 0 ||
usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8383, 0, 1) < 0 ||
usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x0404, 1, 0) < 0 ||
usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8484, 0, 1) < 0 ||
usbh_pl2303_do(pl2303_class, UT_READ_VENDOR_DEVICE, PL2303_SET_REQUEST, 0x8383, 0, 1) < 0 ||
usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 0, 1, 0) < 0 ||
usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 1, 0, 0) < 0) {
USB_LOG_WRN("Could not complete init sequence\r\n");
ret = -USB_ERR_INVAL;
break;
}
if (pl2303_class->chiptype != USBH_PL2303_TYPE_PL2303) {
ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 2, 0x44, 0);
} else {
ret = usbh_pl2303_do(pl2303_class, UT_WRITE_VENDOR_DEVICE, PL2303_SET_REQUEST, 2, 0x24, 0);
}
if (ret < 0) {
USB_LOG_WRN("Could not complete final init request: %d\r\n", ret);
break;
}
}
} while (0);
if (ret < 0) {
USB_LOG_ERR("Failed to initialize PL2303 device: %d\r\n", ret);
return ret;
}
for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;
if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
continue;
} else {
if (ep_desc->bEndpointAddress & 0x80) {
USBH_EP_INIT(pl2303_class->bulkin, ep_desc);
} else {
USBH_EP_INIT(pl2303_class->bulkout, ep_desc);
}
}
}
snprintf(hport->config.intf[intf].devname, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT, pl2303_class->minor);
USB_LOG_INFO("Register PL2303 Class:%s\r\n", hport->config.intf[intf].devname);
#if 0
USB_LOG_INFO("Test pl2303 rx and tx and rx for 5 times, baudrate is 115200\r\n");
struct cdc_line_coding linecoding;
uint8_t count = 5;
linecoding.dwDTERate = 115200;
linecoding.bDataBits = 8;
linecoding.bParityType = 0;
linecoding.bCharFormat = 0;
usbh_pl2303_set_line_coding(pl2303_class, &linecoding);
usbh_pl2303_set_line_state(pl2303_class, true, false);
memset(g_pl2303_buf, 'a', sizeof(g_pl2303_buf));
ret = usbh_pl2303_bulk_out_transfer(pl2303_class, g_pl2303_buf, sizeof(g_pl2303_buf), 0xfffffff);
USB_LOG_RAW("out ret:%d\r\n", ret);
while (count--) {
ret = usbh_pl2303_bulk_in_transfer(pl2303_class, g_pl2303_buf, sizeof(g_pl2303_buf), 0xfffffff);
USB_LOG_RAW("in ret:%d\r\n", ret);
if (ret > 0) {
for (uint32_t i = 0; i < ret; i++) {
USB_LOG_RAW("%02x ", g_pl2303_buf[i]);
}
}
USB_LOG_RAW("\r\n");
}
#endif
usbh_pl2303_run(pl2303_class);
return ret;
}
static int usbh_pl2303_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
int ret = 0;
struct usbh_pl2303 *pl2303_class = (struct usbh_pl2303 *)hport->config.intf[intf].priv;
if (pl2303_class) {
if (pl2303_class->bulkin) {
usbh_kill_urb(&pl2303_class->bulkin_urb);
}
if (pl2303_class->bulkout) {
usbh_kill_urb(&pl2303_class->bulkout_urb);
}
if (hport->config.intf[intf].devname[0] != '\0') {
usb_osal_thread_schedule_other();
USB_LOG_INFO("Unregister PL2303 Class:%s\r\n", hport->config.intf[intf].devname);
usbh_pl2303_stop(pl2303_class);
}
usbh_pl2303_class_free(pl2303_class);
}
return ret;
}
int usbh_pl2303_bulk_in_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &pl2303_class->bulkin_urb;
usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkin, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
int usbh_pl2303_bulk_out_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout)
{
int ret;
struct usbh_urb *urb = &pl2303_class->bulkout_urb;
usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkout, buffer, buflen, timeout, NULL, NULL);
ret = usbh_submit_urb(urb);
if (ret == 0) {
ret = urb->actual_length;
}
return ret;
}
__WEAK void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class)
{
(void)pl2303_class;
}
__WEAK void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class)
{
(void)pl2303_class;
}
static const uint16_t pl2303_id_table[][2] = {
{ 0x067B, 0x2303 }, // PL2303 Serial (ATEN/IOGEAR UC232A)
{ 0x067B, 0x23A3 }, // PL2303HXN Serial, type GC
{ 0x067B, 0x23B3 }, // PL2303HXN Serial, type GB
{ 0x067B, 0x23C3 }, // PL2303HXN Serial, type GT
{ 0x067B, 0x23D3 }, // PL2303HXN Serial, type GL
{ 0x067B, 0x23E3 }, // PL2303HXN Serial, type GE
{ 0x067B, 0x23F3 }, // PL2303HXN Serial, type GS
{ 0, 0 },
};
const struct usbh_class_driver pl2303_class_driver = {
.driver_name = "pl2303",
.connect = usbh_pl2303_connect,
.disconnect = usbh_pl2303_disconnect
};
CLASS_INFO_DEFINE const struct usbh_class_info pl2303_class_info = {
.match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
.bInterfaceClass = 0xff,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.id_table = pl2303_id_table,
.class_driver = &pl2303_class_driver
};

View File

@@ -1,62 +0,0 @@
/*
* Copyright (c) 2024, sakumisu
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef USBH_PL2303_H
#define USBH_PL2303_H
#include "usb_cdc.h"
#define PL2303_SET_REQUEST 0x01
#define PL2303_SET_REQUEST_PL2303HXN 0x80
#define PL2303_SET_CRTSCTS 0x41
#define PL2303_SET_CRTSCTS_PL2303X 0x61
#define PL2303_SET_CRTSCTS_PL2303HXN 0xFA
#define PL2303_CLEAR_CRTSCTS_PL2303HXN 0xFF
#define PL2303_CRTSCTS_REG_PL2303HXN 0x0A
#define PL2303_STATUS_REG_PL2303HX 0x8080
/* Different PL2303 IC types */
#define USBH_PL2303_TYPE_UNKNOWN 0
#define USBH_PL2303_TYPE_PL2303 1
#define USBH_PL2303_TYPE_PL2303HX 2
#define USBH_PL2303_TYPE_PL2303HXD 3
#define USBH_PL2303_TYPE_PL2303HXN 4
struct usbh_pl2303 {
struct usbh_hubport *hport;
struct usb_endpoint_descriptor *bulkin; /* Bulk IN endpoint */
struct usb_endpoint_descriptor *bulkout; /* Bulk OUT endpoint */
struct usbh_urb bulkout_urb;
struct usbh_urb bulkin_urb;
struct cdc_line_coding linecoding;
uint8_t intf;
uint8_t minor;
uint8_t chiptype;
void *user_data;
};
#ifdef __cplusplus
extern "C" {
#endif
int usbh_pl2303_set_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding);
int usbh_pl2303_get_line_coding(struct usbh_pl2303 *pl2303_class, struct cdc_line_coding *line_coding);
int usbh_pl2303_set_line_state(struct usbh_pl2303 *pl2303_class, bool dtr, bool rts);
int usbh_pl2303_bulk_in_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
int usbh_pl2303_bulk_out_transfer(struct usbh_pl2303 *pl2303_class, uint8_t *buffer, uint32_t buflen, uint32_t timeout);
void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class);
void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class);
#ifdef __cplusplus
}
#endif
#endif /* USBH_PL2303_H */