root/src/add-ons/kernel/drivers/ports/usb_serial/WinChipHead.cpp
/*
 * 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;
}