409 lines
11 KiB
C
409 lines
11 KiB
C
/*
|
|
* 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;
|
|
}
|
|
|
|
usbh_ch34x_get_baudrate_div(line_coding->dwDTERate, &factor, &divisor);
|
|
|
|
reg_value |= 0xC0;
|
|
value |= 0x9c;
|
|
value |= reg_value << 8;
|
|
index |= 0x80 | divisor;
|
|
index |= (uint16_t)factor << 8;
|
|
|
|
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
|
|
}; |