From e511f0c1cb7fea2809d57e7eb79577248f2687e9 Mon Sep 17 00:00:00 2001 From: Gerasim Troeglazov <3dEyes@gmail.com> Date: Sun, 26 Jun 2022 23:57:02 +1000 Subject: [PATCH] usb_serial: add WinChipHead (CH340/CH341) support Fixes #15872. Change-Id: I3b43094574045464ca0d3e024f8b5aac32ce5210 Reviewed-on: https://review.haiku-os.org/c/haiku/+/5401 Reviewed-by: Adrien Destugues Tested-by: Commit checker robot --- .../kernel/drivers/ports/usb_serial/Jamfile | 1 + .../drivers/ports/usb_serial/SerialDevice.cpp | 11 + .../drivers/ports/usb_serial/WinChipHead.cpp | 251 ++++++++++++++++++ .../drivers/ports/usb_serial/WinChipHead.h | 103 +++++++ 4 files changed, 366 insertions(+) create mode 100644 src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.cpp create mode 100644 src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.h diff --git a/src/add-ons/kernel/drivers/ports/usb_serial/Jamfile b/src/add-ons/kernel/drivers/ports/usb_serial/Jamfile index 7fea83c129..5511c0859a 100644 --- a/src/add-ons/kernel/drivers/ports/usb_serial/Jamfile +++ b/src/add-ons/kernel/drivers/ports/usb_serial/Jamfile @@ -17,6 +17,7 @@ KernelAddon usb_serial : Option.cpp Prolific.cpp Silicon.cpp + WinChipHead.cpp ; AddResources usb_serial : usb_serial.rdef ; diff --git a/src/add-ons/kernel/drivers/ports/usb_serial/SerialDevice.cpp b/src/add-ons/kernel/drivers/ports/usb_serial/SerialDevice.cpp index a7100a0a21..e408669950 100644 --- a/src/add-ons/kernel/drivers/ports/usb_serial/SerialDevice.cpp +++ b/src/add-ons/kernel/drivers/ports/usb_serial/SerialDevice.cpp @@ -21,6 +21,7 @@ #include "Option.h" #include "Prolific.h" #include "Silicon.h" +#include "WinChipHead.h" #include @@ -778,6 +779,16 @@ SerialDevice::MakeDevice(usb_device device, uint16 vendorID, } } + // WinChipHead Serial Device + for (uint32 i = 0; i < sizeof(kWCHDevices) + / sizeof(kWCHDevices[0]); i++) { + if (vendorID == kWCHDevices[i].vendorID + && productID == kWCHDevices[i].productID) { + return new(std::nothrow) WCHDevice(device, vendorID, productID, + kWCHDevices[i].deviceName); + } + } + // Option Serial Device for (uint32 i = 0; i < sizeof(kOptionDevices) / sizeof(kOptionDevices[0]); i++) { diff --git a/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.cpp b/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.cpp new file mode 100644 index 0000000000..5bb8df3794 --- /dev/null +++ b/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.cpp @@ -0,0 +1,251 @@ +/* + * Copyright 2022, Gerasim Troeglazov <3dEyes@gmail.com> + * Distributed under the terms of the MIT License. + */ + +#include "WinChipHead.h" + +WCHDevice::WCHDevice(usb_device device, uint16 vendorID, uint16 productID, + const char *description) + : SerialDevice(device, vendorID, productID, description), + fChipVersion(0), + fStatusMCR(0), + fStatusLCR(CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX | CH34X_LCR_CS8), + fDataRate(CH34X_SIO_9600) +{ +} + + +// Called for each configuration of the device. Return B_OK if the given +// configuration sounds like it is the usb serial one. +status_t +WCHDevice::AddDevice(const usb_configuration_info *config) +{ + TRACE_FUNCALLS("> WCHDevice::AddDevice(%08x, %08x)\n", this, config); + + status_t status = ENODEV; + if (config->interface_count > 0) { + int32 pipesSet = 0; + usb_interface_info *interface = config->interface[0].active; + for (size_t i = 0; i < interface->endpoint_count; i++) { + usb_endpoint_info *endpoint = &interface->endpoint[i]; + if (endpoint->descr->attributes == USB_ENDPOINT_ATTR_BULK) { + if (endpoint->descr->endpoint_address & USB_ENDPOINT_ADDR_DIR_IN) { + SetReadPipe(endpoint->handle); + if (++pipesSet >= 3) + break; + } else { + if (endpoint->descr->endpoint_address) { + SetControlPipe(endpoint->handle); + SetWritePipe(endpoint->handle); + pipesSet += 2; + if (pipesSet >= 3) + break; + } + } + } + } + + if (pipesSet >= 3) { + status = B_OK; + } + } + + TRACE_FUNCRET("< WCHDevice::AddDevice() returns: 0x%08x\n", status); + + return status; +} + + +status_t +WCHDevice::ResetDevice() +{ + TRACE_FUNCALLS("> WCHDevice::ResetDevice(0x%08x)\n", this); + size_t length = 0; + + uint8 inputBuffer[CH34X_INPUT_BUF_SIZE]; + status_t status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_IN, + CH34X_REQ_READ_VERSION, 0, 0, sizeof(inputBuffer), inputBuffer, &length); + + if (status == B_OK) { + fChipVersion = inputBuffer[0]; + TRACE_ALWAYS("= WCHDevice::ResetDevice(): Chip version: 0x%02x\n", fChipVersion); + } else { + TRACE_ALWAYS("= WCHDevice::ResetDevice(): Can't get chip version: 0x%08x\n", + status); + return status; + } + + status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, + CH34X_REQ_SERIAL_INIT, 0, 0, 0, NULL, &length); + + if (status != B_OK) { + TRACE_ALWAYS("= WCHDevice::ResetDevice(): init failed\n"); + return status; + } + + status = WriteConfig(fDataRate, fStatusLCR, fStatusMCR); + + TRACE_FUNCRET("< WCHDevice::ResetDevice() returns:%08x\n", status); + return status; +} + +status_t +WCHDevice::SetLineCoding(usb_cdc_line_coding *lineCoding) +{ + TRACE_FUNCALLS("> WCHDevice::SetLineCoding(0x%08x, {%d, 0x%02x, 0x%02x, 0x%02x})\n", + this, lineCoding->speed, lineCoding->stopbits, lineCoding->parity, + lineCoding->databits); + + fStatusLCR = CH34X_LCR_ENABLE_RX | CH34X_LCR_ENABLE_TX; + + switch (lineCoding->stopbits) { + case USB_CDC_LINE_CODING_1_STOPBIT: + break; + case USB_CDC_LINE_CODING_2_STOPBITS: + fStatusLCR |= CH34X_LCR_STOP_BITS_2; + break; + default: + TRACE_ALWAYS("= WCHDevice::SetLineCoding(): Wrong stopbits param: %d\n", + lineCoding->stopbits); + break; + } + + switch (lineCoding->parity) { + case USB_CDC_LINE_CODING_NO_PARITY: + break; + case USB_CDC_LINE_CODING_EVEN_PARITY: + fStatusLCR |= CH34X_LCR_ENABLE_PAR | CH34X_LCR_PAR_EVEN; + break; + case USB_CDC_LINE_CODING_ODD_PARITY: + fStatusLCR |= CH34X_LCR_ENABLE_PAR; + break; + default: + TRACE_ALWAYS("= WCHDevice::SetLineCoding(): Wrong parity param: %d\n", + lineCoding->parity); + break; + } + + switch (lineCoding->databits) { + case 5: + fStatusLCR |= CH34X_LCR_CS5; + break; + case 6: + fStatusLCR |= CH34X_LCR_CS6; + break; + case 7: + fStatusLCR |= CH34X_LCR_CS7; + break; + case 8: + fStatusLCR |= CH34X_LCR_CS8; + break; + default: + fStatusLCR |= CH34X_LCR_CS8; + TRACE_ALWAYS("= WCHDevice::SetLineCoding(): Wrong databits param: %d\n", + lineCoding->databits); + break; + } + + switch (lineCoding->speed) { + case 600: fDataRate = CH34X_SIO_600; break; + case 1200: fDataRate = CH34X_SIO_1200; break; + case 1800: fDataRate = CH34X_SIO_1800; break; + case 2400: fDataRate = CH34X_SIO_2400; break; + case 4800: fDataRate = CH34X_SIO_4800; break; + case 9600: fDataRate = CH34X_SIO_9600; break; + case 19200: fDataRate = CH34X_SIO_19200; break; + case 31250: fDataRate = CH34X_SIO_31250; break; + case 38400: fDataRate = CH34X_SIO_38400; break; + case 57600: fDataRate = CH34X_SIO_57600; break; + case 115200: fDataRate = CH34X_SIO_115200; break; + case 230400: fDataRate = CH34X_SIO_230400; break; + default: + fDataRate = CH34X_SIO_9600; + TRACE_ALWAYS("= WCHDevice::SetLineCoding(): Datarate: %d is not " + "supported by this hardware. Defaulted to %d\n", + lineCoding->speed, CH34X_DEFAULT_BAUD_RATE); + break; + } + + status_t status = WriteConfig(fDataRate, fStatusLCR, fStatusMCR); + + if (status != B_OK) + TRACE_ALWAYS("= WCHDevice::SetLineCoding(): WriteConfig failed\n"); + + TRACE_FUNCRET("< WCHDevice::SetLineCoding() returns: 0x%08x\n", status); + + return status; +} + + +status_t +WCHDevice::SetControlLineState(uint16 state) +{ + TRACE_FUNCALLS("> WCHDevice::SetControlLineState(0x%08x, 0x%04x)\n", + this, state); + + fStatusMCR = 0; + + if (state & USB_CDC_CONTROL_SIGNAL_STATE_RTS) + fStatusMCR |= CH34X_BIT_RTS; + + if (state & USB_CDC_CONTROL_SIGNAL_STATE_DTR) + fStatusMCR |= CH34X_BIT_DTR; + + size_t length = 0; + status_t status = 0; + + if (fChipVersion < CH34X_VER_20) { + status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, + CH34X_REQ_WRITE_REG, CH34X_REG_STAT1 | (CH34X_REG_STAT1 << 8), + ~fStatusMCR | (~fStatusMCR << 8), 0, NULL, &length); + } else { + status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, + CH34X_REQ_MODEM_CTRL, ~fStatusMCR, 0, 0, NULL, &length); + } + + TRACE_FUNCRET("< WCHDevice::SetControlLineState() returns: 0x%08x\n", + status); + + return status; +} + +status_t +WCHDevice::WriteConfig(uint16 dataRate, uint8 lcr, uint8 mcr) +{ + size_t length = 0; + status_t status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, + CH34X_REQ_WRITE_REG, CH34X_REG_BPS_PRE | (CH34X_REG_BPS_DIV << 8), + dataRate | CH34X_BPS_PRE_IMM, 0, NULL, &length); + + if (status != B_OK) { + TRACE_ALWAYS("= WCHDevice::WriteConfig(): datarate request failed: 0x%08x\n", + status); + return status; + } + + status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, + CH34X_REQ_WRITE_REG, CH34X_REG_LCR | (CH34X_REG_LCR2 << 8), + lcr, 0, NULL, &length); + + if (status != B_OK) { + TRACE_ALWAYS("= WCHDevice::WriteConfig(): LCR request failed: 0x%08x\n", + status); + return status; + } + + status = gUSBModule->send_request(Device(), + USB_REQTYPE_VENDOR | USB_REQTYPE_DEVICE_OUT, + CH34X_REQ_MODEM_CTRL, ~mcr, 0, 0, NULL, &length); + + if (status != B_OK) + TRACE_ALWAYS("= WCHDevice::WriteConfig(): handshake failed: 0x%08x\n", status); + + return status; +} diff --git a/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.h b/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.h new file mode 100644 index 0000000000..8172b1f4d4 --- /dev/null +++ b/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.h @@ -0,0 +1,103 @@ +/* + * Copyright 2022, Gerasim Troeglazov <3dEyes@gmail.com> + * Distributed under the terms of the MIT License. + */ + +#ifndef _USB_WINCHIPHEAD_H_ +#define _USB_WINCHIPHEAD_H_ + +#include "SerialDevice.h" + +#define CH34X_DEFAULT_BAUD_RATE 9600 + +#define CH34X_INPUT_BUF_SIZE 8 + +#define CH34X_VER_20 0x20 +#define CH34X_VER_30 0x30 + +#define CH34X_BPS_PRE_IMM 0x80 + +#define CH34X_BIT_CTS 0x01 +#define CH34X_BIT_DSR 0x02 +#define CH34X_BIT_RI 0x04 +#define CH34X_BIT_DCD 0x08 +#define CH34X_BIT_DTR 0x20 +#define CH34X_BIT_RTS 0x40 +#define CH34X_BITS_MODEM_STAT 0x0f + +#define CH34X_MULT_STAT 0x04 + +#define CH34X_REQ_READ_VERSION 0x5F +#define CH34X_REQ_WRITE_REG 0x9A +#define CH34X_REQ_READ_REG 0x95 +#define CH34X_REQ_SERIAL_INIT 0xA1 +#define CH34X_REQ_MODEM_CTRL 0xA4 + +#define CH34X_REG_BREAK 0x05 +#define CH34X_REG_STAT1 0x06 +#define CH34X_REG_STAT2 0x07 +#define CH34X_REG_BPS_PRE 0x12 +#define CH34X_REG_BPS_DIV 0x13 +#define CH34X_REG_LCR 0x18 +#define CH34X_REG_LCR2 0x25 + +#define CH34X_LCR_CS5 0x00 +#define CH34X_LCR_CS6 0x01 +#define CH34X_LCR_CS7 0x02 +#define CH34X_LCR_CS8 0x03 +#define CH34X_LCR_STOP_BITS_2 0x04 +#define CH34X_LCR_ENABLE_PAR 0x08 +#define CH34X_LCR_PAR_EVEN 0x10 +#define CH34X_LCR_MARK_SPACE 0x20 +#define CH34X_LCR_ENABLE_TX 0x40 +#define CH34X_LCR_ENABLE_RX 0x80 + +#define CH34X_SIO_600 0x6401 +#define CH34X_SIO_1200 0xB201 +#define CH34X_SIO_1800 0xCC01 +#define CH34X_SIO_2400 0xD901 +#define CH34X_SIO_4800 0x6402 +#define CH34X_SIO_9600 0xB202 +#define CH34X_SIO_19200 0xD902 +#define CH34X_SIO_31250 0x4003 +#define CH34X_SIO_38400 0x6403 +#define CH34X_SIO_57600 0xF302 +#define CH34X_SIO_115200 0xCC03 +#define CH34X_SIO_230400 0xE603 + +/* supported vendor and product ids */ +#define VENDOR_WCH 0x4348 // WinChipHead +#define VENDOR_QIN_HENG 0x1a86 // QinHeng Electronics +#define VENDOR_GW_INSTEK 0x2184 // GW Instek + +const usb_serial_device kWCHDevices[] = { + {VENDOR_WCH, 0x5523, "CH341 serial converter"}, + {VENDOR_QIN_HENG, 0x5523, "QinHeng CH341A serial converter"}, + {VENDOR_QIN_HENG, 0x7522, "QinHeng CH340 serial converter"}, + {VENDOR_QIN_HENG, 0x7523, "QinHeng CH340 serial converter"}, + {VENDOR_GW_INSTEK, 0x0057, "GW Instek Oscilloscope"} +}; + +class WCHDevice : public SerialDevice { +public: + WCHDevice(usb_device device, uint16 vendorID, + uint16 productID, const char *description); + + virtual status_t AddDevice(const usb_configuration_info *config); + + virtual status_t ResetDevice(); + + virtual status_t SetLineCoding(usb_cdc_line_coding *coding); + virtual status_t SetControlLineState(uint16 state); + +private: + status_t WriteConfig(uint16 dataRate, uint8 lcr, uint8 mcr); + + uint8 fChipVersion; + uint8 fStatusMCR; + uint8 fStatusLCR; + uint16 fDataRate; +}; + + +#endif //_USB_WINCHIPHEAD_H_