diff --git a/share/man/man4/uchcom.4 b/share/man/man4/uchcom.4 --- a/share/man/man4/uchcom.4 +++ b/share/man/man4/uchcom.4 @@ -27,12 +27,12 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd April 26, 2017 +.Dd August 19, 2024 .Dt UCHCOM 4 .Os .Sh NAME .Nm uchcom -.Nd WinChipHead CH341/CH340 serial adapter driver +.Nd WinChipHead CH9102/CH343/CH341/CH340 serial adapter driver .Sh SYNOPSIS To compile this driver into the kernel, place the following lines in your @@ -52,22 +52,12 @@ .Sh DESCRIPTION The .Nm -driver provides support for the WinChipHead CH341/CH340 USB-to-RS-232 -Bridge chip. +driver provides support for the WinChipHead CH9102/CH343/CH341/CH340 +USB-to-RS-232 Bridge chip. .Pp -The device is accessed through the -.Xr ucom 4 -driver which makes it behave like a -.Xr tty 4 . -.Sh HARDWARE -The -.Nm -driver supports the following adapters: -.Pp -.Bl -bullet -compact -.It -HL USB-RS232 -.El +The datasheets for the CH340/CH341 list the maximum +supported baud rate as 2,000,000. +CH9102/CH343 devices support any baud rate up to 6 Mbps. .Sh FILES .Bl -tag -width "/dev/ttyU*.init" -compact .It Pa /dev/ttyU* @@ -95,6 +85,3 @@ .Fx release to include it was .Fx 8.0 . -.Sh BUGS -Actually, this chip seems unable to drive other than 8 data bits and -1 stop bit line. diff --git a/sys/dev/usb/serial/uchcom.c b/sys/dev/usb/serial/uchcom.c --- a/sys/dev/usb/serial/uchcom.c +++ b/sys/dev/usb/serial/uchcom.c @@ -59,8 +59,7 @@ #include /* - * Driver for WinChipHead CH341/340, the worst USB-serial chip in the - * world. + * Driver for WinChipHead CH9102/343/341/340. */ #include @@ -102,17 +101,19 @@ &uchcom_debug, 0, "uchcom debug level"); #endif -#define UCHCOM_IFACE_INDEX 0 -#define UCHCOM_CONFIG_INDEX 0 +#define UCHCOM_IFACE_INDEX 0 +#define UCHCOM_CONFIG_INDEX 0 +#define UCHCOM_SECOND_IFACE_INDEX 1 #define UCHCOM_REV_CH340 0x0250 #define UCHCOM_INPUT_BUF_SIZE 8 -#define UCHCOM_REQ_GET_VERSION 0x5F -#define UCHCOM_REQ_READ_REG 0x95 -#define UCHCOM_REQ_WRITE_REG 0x9A -#define UCHCOM_REQ_RESET 0xA1 -#define UCHCOM_REQ_SET_DTRRTS 0xA4 +#define UCHCOM_REQ_GET_VERSION 0x5F +#define UCHCOM_REQ_READ_REG 0x95 +#define UCHCOM_REQ_WRITE_REG 0x9A +#define UCHCOM_REQ_RESET 0xA1 +#define UCHCOM_REQ_SET_DTRRTS 0xA4 +#define UCHCOM_REQ_CH343_WRITE_REG 0xA8 #define UCHCOM_REG_STAT1 0x06 #define UCHCOM_REG_STAT2 0x07 @@ -135,13 +136,21 @@ #define UCHCOM_RTS_MASK 0x40 #define UCHCOM_BRK_MASK 0x01 +#define UCHCOM_ABRK_MASK 0x10 +#define UCHCOM_CH343_BRK_MASK 0x80 #define UCHCOM_LCR1_MASK 0xAF #define UCHCOM_LCR2_MASK 0x07 #define UCHCOM_LCR1_RX 0x80 #define UCHCOM_LCR1_TX 0x40 #define UCHCOM_LCR1_PARENB 0x08 +#define UCHCOM_LCR1_CS5 0x00 +#define UCHCOM_LCR1_CS6 0x01 +#define UCHCOM_LCR1_CS7 0x02 #define UCHCOM_LCR1_CS8 0x03 +#define UCHCOM_LCR1_STOPB 0x04 +#define UCHCOM_LCR1_PARODD 0x00 +#define UCHCOM_LCR1_PAREVEN 0x10 #define UCHCOM_LCR2_PAREVEN 0x07 #define UCHCOM_LCR2_PARODD 0x06 #define UCHCOM_LCR2_PARMARK 0x05 @@ -151,12 +160,18 @@ #define UCHCOM_INTR_STAT2 0x03 #define UCHCOM_INTR_LEAST 4 -#define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */ +#define UCHCOM_T 0x08 +#define UCHCOM_CL 0x04 +#define UCHCOM_CH343_CT 0x80 +#define UCHCOM_CT 0x90 + +#define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */ + +#define TYPE_CH343 1 enum { UCHCOM_BULK_DT_WR, UCHCOM_BULK_DT_RD, - UCHCOM_INTR_DT_RD, UCHCOM_N_TRANSFER, }; @@ -165,6 +180,7 @@ struct ucom_softc sc_ucom; struct usb_xfer *sc_xfer[UCHCOM_N_TRANSFER]; + struct usb_xfer *sc_intr_xfer; /* Interrupt endpoint */ struct usb_device *sc_udev; struct mtx sc_mtx; @@ -172,39 +188,19 @@ uint8_t sc_rts; /* local copy */ uint8_t sc_version; uint8_t sc_msr; - uint8_t sc_lsr; /* local status register */ -}; - -struct uchcom_divider { - uint8_t dv_prescaler; - uint8_t dv_div; - uint8_t dv_mod; -}; - -struct uchcom_divider_record { - uint32_t dvr_high; - uint32_t dvr_low; - uint32_t dvr_base_clock; - struct uchcom_divider dvr_divider; -}; - -static const struct uchcom_divider_record dividers[] = -{ - {307200, 307200, UCHCOM_BASE_UNKNOWN, {7, 0xD9, 0}}, - {921600, 921600, UCHCOM_BASE_UNKNOWN, {7, 0xF3, 0}}, - {2999999, 23530, 6000000, {3, 0, 0}}, - {23529, 2942, 750000, {2, 0, 0}}, - {2941, 368, 93750, {1, 0, 0}}, - {367, 1, 11719, {0, 0, 0}}, + uint8_t sc_lsr; /* local status register */ + uint8_t sc_chiptype; /* type of chip */ + uint8_t sc_ctrl_iface_no; + uint8_t sc_iface_index; }; -#define NUM_DIVIDERS nitems(dividers) - static const STRUCT_USB_HOST_ID uchcom_devs[] = { {USB_VPI(USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, 0)}, {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER, 0)}, {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER_2, 0)}, {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER_3, 0)}, + {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH343SER, 0)}, + {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH9102SER, 0)}, }; /* protypes */ @@ -226,8 +222,9 @@ static void uchcom_convert_status(struct uchcom_softc *, uint8_t); static void uchcom_update_status(struct uchcom_softc *); static void uchcom_set_dtr_rts(struct uchcom_softc *); -static int uchcom_calc_divider_settings(struct uchcom_divider *, uint32_t); -static void uchcom_set_baudrate(struct uchcom_softc *, uint32_t); +static void uchcom_calc_baudrate(struct uchcom_softc *, uint32_t, uint8_t *, + uint8_t *); +static void uchcom_set_baudrate(struct uchcom_softc *, uint32_t, uint16_t); static void uchcom_poll(struct ucom_softc *ucom); static device_probe_t uchcom_probe; @@ -257,8 +254,10 @@ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, .callback = &uchcom_read_callback, }, +}; - [UCHCOM_INTR_DT_RD] = { +static const struct usb_config uchcom_intr_config_data[1] = { + [0] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, @@ -312,8 +311,9 @@ { struct uchcom_softc *sc = device_get_softc(dev); struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usb_interface *iface; + struct usb_interface_descriptor *id; int error; - uint8_t iface_index; DPRINTFN(11, "\n"); @@ -331,20 +331,49 @@ case USB_PRODUCT_WCH2_CH341SER_3: device_printf(dev, "CH341 detected\n"); break; + case USB_PRODUCT_WCH2_CH343SER: + device_printf(dev, "CH343 detected\n"); + break; + case USB_PRODUCT_WCH2_CH9102SER: + device_printf(dev, "CH9102 detected\n"); + break; default: - device_printf(dev, "New CH340/CH341 product 0x%04x detected\n", - uaa->info.idProduct); + device_printf(dev, "New CH340/CH341/CH343/CH9102 product " + "0x%04x detected\n", uaa->info.idProduct); break; } - iface_index = UCHCOM_IFACE_INDEX; - error = usbd_transfer_setup(uaa->device, - &iface_index, sc->sc_xfer, uchcom_config_data, - UCHCOM_N_TRANSFER, sc, &sc->sc_mtx); + /* CH343/CH9102 has two interfaces. */ + sc->sc_ctrl_iface_no = uaa->info.bIfaceNum; + iface = usbd_get_iface(uaa->device, UCHCOM_SECOND_IFACE_INDEX); + if (iface) { + id = usbd_get_interface_descriptor(iface); + if (id == NULL) { + device_printf(dev, "no interface descriptor\n"); + goto detach; + } + sc->sc_iface_index = UCHCOM_SECOND_IFACE_INDEX; + usbd_set_parent_iface(uaa->device, UCHCOM_SECOND_IFACE_INDEX, + uaa->info.bIfaceIndex); + sc->sc_chiptype = TYPE_CH343; + } else { + sc->sc_iface_index = UCHCOM_IFACE_INDEX; + } + + /* Setup all transfers. */ + error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index, + sc->sc_xfer, uchcom_config_data, UCHCOM_N_TRANSFER, sc, + &sc->sc_mtx); + if (error) { + device_printf(dev, "could not allocate all pipes\n"); + goto detach; + } + error = usbd_transfer_setup(uaa->device, &sc->sc_ctrl_iface_no, + &sc->sc_intr_xfer, uchcom_intr_config_data, 1, sc, &sc->sc_mtx); if (error) { - DPRINTF("one or more missing USB endpoints, " - "error=%s\n", usbd_errstr(error)); + device_printf(dev, "allocating USB transfers failed for " + "interrupt\n"); goto detach; } @@ -450,7 +479,9 @@ (unsigned)reg1, (unsigned)val1, (unsigned)reg2, (unsigned)val2); uchcom_ctrl_write( - sc, UCHCOM_REQ_WRITE_REG, + sc, + (sc->sc_chiptype != TYPE_CH343) ? + UCHCOM_REQ_WRITE_REG : UCHCOM_REQ_CH343_WRITE_REG, reg1 | ((uint16_t)reg2 << 8), val1 | ((uint16_t)val2 << 8)); } @@ -556,78 +587,69 @@ uint8_t brk1; uint8_t brk2; - uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_LCR1, &brk2); - if (onoff) { - /* on - clear bits */ - brk1 &= ~UCHCOM_BRK_MASK; - brk2 &= ~UCHCOM_LCR1_TX; + if (sc->sc_chiptype == TYPE_CH343) { + brk1 = UCHCOM_CH343_BRK_MASK; + if (!onoff) + brk1 |= UCHCOM_ABRK_MASK; + uchcom_write_reg(sc, brk1, 0, 0, 0); } else { - /* off - set bits */ - brk1 |= UCHCOM_BRK_MASK; - brk2 |= UCHCOM_LCR1_TX; + uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_LCR1, + &brk2); + if (onoff) { + /* on - clear bits */ + brk1 &= ~UCHCOM_BRK_MASK; + brk2 &= ~UCHCOM_LCR1_TX; + } else { + /* off - set bits */ + brk1 |= UCHCOM_BRK_MASK; + brk2 |= UCHCOM_LCR1_TX; + } + uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_LCR1, + brk2); } - uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_LCR1, brk2); } -static int -uchcom_calc_divider_settings(struct uchcom_divider *dp, uint32_t rate) -{ - const struct uchcom_divider_record *rp; - uint32_t div; - uint32_t rem; - uint32_t mod; - uint8_t i; - - /* find record */ - for (i = 0; i != NUM_DIVIDERS; i++) { - if (dividers[i].dvr_high >= rate && - dividers[i].dvr_low <= rate) { - rp = ÷rs[i]; - goto found; - } - } - return (-1); - -found: - dp->dv_prescaler = rp->dvr_divider.dv_prescaler; - if (rp->dvr_base_clock == UCHCOM_BASE_UNKNOWN) - dp->dv_div = rp->dvr_divider.dv_div; - else { - div = rp->dvr_base_clock / rate; - rem = rp->dvr_base_clock % rate; - if (div == 0 || div >= 0xFF) - return (-1); - if ((rem << 1) >= rate) - div += 1; - dp->dv_div = (uint8_t)-div; +static void +uchcom_calc_baudrate(struct uchcom_softc *sc, uint32_t rate, uint8_t *divisor, + uint8_t *factor) +{ + uint32_t clk = 12000000; + + if (rate >= 256000 && sc->sc_chiptype == TYPE_CH343) + *divisor = 7; + else if (rate > 23529) { + clk /= 2; + *divisor = 3; + } else if (rate > 2941) { + clk /= 16; + *divisor = 2; + } else if (rate > 367) { + clk /= 128; + *divisor = 1; + } else { + clk = 11719; + *divisor = 0; } - mod = (UCHCOM_BPS_MOD_BASE / rate) + UCHCOM_BPS_MOD_BASE_OFS; - mod = mod + (mod / 2); - - dp->dv_mod = (mod + 0xFF) / 0x100; + *factor = 256 - clk / rate; - return (0); + if (rate == 921600 && sc->sc_chiptype != TYPE_CH343) { + *divisor = 7; + *factor = 243; + } } static void -uchcom_set_baudrate(struct uchcom_softc *sc, uint32_t rate) +uchcom_set_baudrate(struct uchcom_softc *sc, uint32_t rate, uint16_t lcr) { - struct uchcom_divider dv; + uint16_t idx; + uint8_t factor, div; - if (uchcom_calc_divider_settings(&dv, rate)) - return; + uchcom_calc_baudrate(sc, rate, &div, &factor); + div |= (sc->sc_chiptype != TYPE_CH343) ? 0x80 : 0x00; + idx = (factor << 8) | div; - /* - * According to linux code we need to set bit 7 of UCHCOM_REG_BPS_PRE, - * otherwise the chip will buffer data. - */ - uchcom_write_reg(sc, - UCHCOM_REG_BPS_PRE, dv.dv_prescaler | 0x80, - UCHCOM_REG_BPS_DIV, dv.dv_div); - uchcom_write_reg(sc, - UCHCOM_REG_BPS_MOD, dv.dv_mod, - UCHCOM_REG_BPS_PAD, 0); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, lcr, idx); } /* ---------------------------------------------------------------------- @@ -674,6 +696,14 @@ DPRINTF("\n"); + if (sc->sc_chiptype != TYPE_CH343) { + /* Set default configuration. */ + uchcom_get_version(sc, NULL); + uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0); + uchcom_write_reg(sc, UCHCOM_REG_BPS_PRE, 0x82, + UCHCOM_REG_BPS_DIV, 0xd9); + uchcom_write_reg(sc, 0x2c, 0x07, UCHCOM_REG_BPS_PAD, 0); + } uchcom_update_version(sc); uchcom_update_status(sc); } @@ -681,53 +711,69 @@ static int uchcom_pre_param(struct ucom_softc *ucom, struct termios *t) { - struct uchcom_divider dv; + struct uchcom_softc *sc = ucom->sc_parent; - switch (t->c_cflag & CSIZE) { - case CS8: + /* + * Check requested baud rate. + * The CH340/CH341 can set any baud rate up to 2Mb. + * The CH9102/CH343 can set any baud rate up to 6Mb. + */ + switch (sc->sc_chiptype) { + case TYPE_CH343: + if (t->c_ospeed <= 6000000) + return (0); break; default: - return (EIO); + if (t->c_ospeed <= 2000000) + return (0); + break; } - if ((t->c_cflag & CSTOPB) != 0) - return (EIO); - if ((t->c_cflag & PARENB) != 0) - return (EIO); - if (uchcom_calc_divider_settings(&dv, t->c_ospeed)) { - return (EIO); - } - return (0); /* success */ + return (EIO); } static void uchcom_cfg_param(struct ucom_softc *ucom, struct termios *t) { struct uchcom_softc *sc = ucom->sc_parent; + uint8_t lcr; - uchcom_get_version(sc, NULL); - uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0); - uchcom_set_baudrate(sc, t->c_ospeed); - if (sc->sc_version < UCHCOM_VER_30) { - uchcom_read_reg(sc, UCHCOM_REG_LCR1, NULL, - UCHCOM_REG_LCR2, NULL); - uchcom_write_reg(sc, UCHCOM_REG_LCR1, 0x50, - UCHCOM_REG_LCR2, 0x00); - } else { - /* - * Set up line control: - * - enable transmit and receive - * - set 8n1 mode - * To do: support other sizes, parity, stop bits. - */ - uchcom_write_reg(sc, - UCHCOM_REG_LCR1, - UCHCOM_LCR1_RX | UCHCOM_LCR1_TX | UCHCOM_LCR1_CS8, - UCHCOM_REG_LCR2, 0x00); + lcr = UCHCOM_LCR1_RX | UCHCOM_LCR1_TX; + + if (t->c_cflag & CSTOPB) + lcr |= UCHCOM_LCR1_STOPB; + + if (t->c_cflag & PARENB) { + lcr |= UCHCOM_LCR1_PARENB; + if (t->c_cflag & PARODD) + lcr |= UCHCOM_LCR1_PARODD; + else + lcr |= UCHCOM_LCR1_PAREVEN; } - uchcom_update_status(sc); - uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0x501f, 0xd90a); - uchcom_set_baudrate(sc, t->c_ospeed); + + switch (t->c_cflag & CSIZE) { + case CS5: + lcr |= UCHCOM_LCR1_CS5; + break; + case CS6: + lcr |= UCHCOM_LCR1_CS6; + break; + case CS7: + lcr |= UCHCOM_LCR1_CS7; + break; + case CS8: + default: + lcr |= UCHCOM_LCR1_CS8; + break; + } + + if (sc->sc_chiptype == TYPE_CH343) + uchcom_set_baudrate(sc, t->c_ospeed, + UCHCOM_T | UCHCOM_CL | UCHCOM_CH343_CT | lcr << 8); + else + uchcom_set_baudrate(sc, t->c_ospeed, + UCHCOM_T | UCHCOM_CL | UCHCOM_CT | lcr << 8); + uchcom_set_dtr_rts(sc); uchcom_update_status(sc); } @@ -738,7 +784,7 @@ struct uchcom_softc *sc = ucom->sc_parent; /* start interrupt endpoint */ - usbd_transfer_start(sc->sc_xfer[UCHCOM_INTR_DT_RD]); + usbd_transfer_start(sc->sc_intr_xfer); /* start read endpoint */ usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_RD]); @@ -750,7 +796,7 @@ struct uchcom_softc *sc = ucom->sc_parent; /* stop interrupt endpoint */ - usbd_transfer_stop(sc->sc_xfer[UCHCOM_INTR_DT_RD]); + usbd_transfer_stop(sc->sc_intr_xfer); /* stop read endpoint */ usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_RD]); @@ -780,6 +826,7 @@ { struct uchcom_softc *sc = usbd_xfer_softc(xfer); struct usb_page_cache *pc; + uint32_t intrstat; uint8_t buf[UCHCOM_INTR_LEAST]; int actlen; @@ -798,7 +845,10 @@ (unsigned)buf[0], (unsigned)buf[1], (unsigned)buf[2], (unsigned)buf[3]); - uchcom_convert_status(sc, buf[UCHCOM_INTR_STAT1]); + intrstat = (sc->sc_chiptype != TYPE_CH343) ? + actlen - 1 : UCHCOM_INTR_STAT1; + + uchcom_convert_status(sc, buf[intrstat]); ucom_status_change(&sc->sc_ucom); } case USB_ST_SETUP: diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs --- a/sys/dev/usb/usbdevs +++ b/sys/dev/usb/usbdevs @@ -4903,9 +4903,11 @@ /* WCH products */ product WCH CH341SER 0x5523 CH341/CH340 USB-Serial Bridge product WCH2 CH341SER_2 0x5523 CH341/CH340 USB-Serial Bridge +product WCH2 CH343SER 0x55d3 CH343 USB Serial +product WCH2 CH9102SER 0x55d4 CH9102 USB Serial product WCH2 CH341SER_3 0x7522 CH341/CH340 USB-Serial Bridge product WCH2 CH341SER 0x7523 CH341/CH340 USB-Serial Bridge -product WCH2 U2M 0X752d CH345 USB2.0-MIDI +product WCH2 U2M 0x752d CH345 USB2.0-MIDI /* West Mountain Radio products */ product WESTMOUNTAIN RIGBLASTER_ADVANTAGE 0x0003 RIGblaster Advantage