Files
CherryUSB/class/serial/usbh_ch34x.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
};