⛏️ index : haiku.git

/*
 * 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;
}