726 lines
21 KiB
C
726 lines
21 KiB
C
/*
|
|
* 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
|
|
}; |