| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <string.h> |
| #include <errno.h> |
| |
| #include "log.h" |
| #include "usb_serial.h" |
| #include "usb_ch341.h" |
| |
| struct UsbSerialDataStruct { |
| char version[2]; |
| |
| struct { |
| uint8_t prescaler; |
| uint8_t divisor; |
| } baud; |
| |
| struct { |
| uint8_t lcr1; |
| uint8_t lcr2; |
| uint8_t lsr; |
| } line; |
| |
| struct { |
| uint8_t mcr; |
| uint8_t msr; |
| uint8_t flow; |
| } modem; |
| }; |
| |
| typedef struct { |
| uint16_t factor; |
| uint8_t flags; |
| } CH341_PrescalerEntry; |
| |
| static const CH341_PrescalerEntry CH341_prescalerTable[] = { |
| { .factor = 00001, // 1 |
| .flags = USB_CH341_PSF_BYPASS_2x | USB_CH341_PSF_BYPASS_8x | USB_CH341_PSF_BYPASS_64x |
| }, |
| |
| { .factor = 00002, // 2 |
| .flags = USB_CH341_PSF_BYPASS_8x | USB_CH341_PSF_BYPASS_64x |
| }, |
| |
| { .factor = 00010, // 8 |
| .flags = USB_CH341_PSF_BYPASS_2x | USB_CH341_PSF_BYPASS_64x |
| }, |
| |
| { .factor = 00020, // 16 |
| .flags = USB_CH341_PSF_BYPASS_64x |
| }, |
| |
| { .factor = 00100, // 64 |
| .flags = USB_CH341_PSF_BYPASS_2x | USB_CH341_PSF_BYPASS_8x |
| }, |
| |
| { .factor = 00200, // 128 |
| .flags = USB_CH341_PSF_BYPASS_8x |
| }, |
| |
| { .factor = 01000, // 512 |
| .flags = USB_CH341_PSF_BYPASS_2x |
| }, |
| |
| { .factor = 02000, // 1024 |
| .flags = 0 |
| }, |
| }; |
| |
| static const uint8_t CH341_prescalerCOUNT = ARRAY_COUNT(CH341_prescalerTable); |
| |
| static int |
| usbControlRead_CH341 ( |
| UsbDevice *device, uint8_t request, |
| uint16_t value, uint16_t index, |
| unsigned char *buffer, size_t size |
| ) { |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "CH341 control read: %02X %04X %04X", |
| request, value, index |
| ); |
| |
| ssize_t result = usbControlRead(device, |
| USB_CH341_CONTROL_RECIPIENT, USB_CH341_CONTROL_TYPE, |
| request, value, index, buffer, size, |
| USB_CH341_CONTROL_TIMEOUT |
| ); |
| |
| if (result == -1) return 0; |
| logBytes(LOG_CATEGORY(SERIAL_IO), "CH341 control response", buffer, result); |
| if (result == size) return 1; |
| |
| logMessage(LOG_WARNING, |
| "short CH341 control response: %"PRIsize" < %"PRIssize, |
| result, size |
| ); |
| |
| return 0; |
| } |
| |
| static int |
| usbReadRegisters_CH341 ( |
| UsbDevice *device, |
| uint8_t register1, uint8_t *value1, |
| uint8_t register2, uint8_t *value2 |
| ) { |
| unsigned char buffer[2]; |
| |
| int ok = usbControlRead_CH341( |
| device, USB_CH341_REQ_READ_REGISTERS, |
| (register2 << 8) | register1, |
| 0, buffer, sizeof(buffer) |
| ); |
| |
| if (ok) { |
| *value1 = buffer[0]; |
| *value2 = buffer[1]; |
| } |
| |
| return ok; |
| } |
| |
| static int |
| usbReadRegister_CH341 (UsbDevice *device, uint8_t register1, uint8_t *value1) { |
| uint8_t register2 = register1; |
| uint8_t value2; |
| return usbReadRegisters_CH341(device, register1, value1, register2, &value2); |
| } |
| |
| static int |
| usbControlWrite_CH341 ( |
| UsbDevice *device, uint8_t request, |
| uint16_t value, uint16_t index |
| ) { |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "CH341 control write: %02X %04X %04X", |
| request, value, index |
| ); |
| |
| ssize_t result = usbControlWrite(device, |
| USB_CH341_CONTROL_RECIPIENT, USB_CH341_CONTROL_TYPE, |
| request, value, index, NULL, 0, |
| USB_CH341_CONTROL_TIMEOUT |
| ); |
| |
| return result != -1; |
| } |
| |
| static int |
| usbWriteRegisters_CH341 ( |
| UsbDevice *device, |
| uint8_t register1, uint8_t value1, |
| uint8_t register2, uint8_t value2 |
| ) { |
| return usbControlWrite_CH341( |
| device, USB_CH341_REQ_WRITE_REGISTERS, |
| (register2 << 8) | register1, |
| (value2 << 8) | value1 |
| ); |
| } |
| |
| static int |
| usbWriteRegister_CH341 (UsbDevice *device, uint8_t register1, uint8_t value1) { |
| return usbWriteRegisters_CH341(device, register1, value1, register1, value1); |
| } |
| |
| static void |
| usbLogVersion_CH341 (const UsbSerialData *usd) { |
| logBytes(LOG_CATEGORY(SERIAL_IO), |
| "CH341 version", usd->version, sizeof(usd->version) |
| ); |
| } |
| |
| static int |
| usbReadVersion_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| const size_t size = sizeof(usd->version); |
| unsigned char version[size]; |
| |
| int ok = usbControlRead_CH341( |
| device, USB_CH341_REQ_READ_VERSION, 0, 0, version, size |
| ); |
| |
| if (ok) { |
| memcpy(usd->version, version, size); |
| usbLogVersion_CH341(usd); |
| } |
| |
| return ok; |
| } |
| |
| static void |
| usbLogStatus_CH341 (const UsbSerialData *usd) { |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "CH341 status: MSR:%02X LSR:%02X", |
| usd->modem.msr, usd->line.lsr |
| ); |
| } |
| |
| static int |
| usbReadStatus_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| int ok = usbReadRegisters_CH341(device, |
| USB_CH341_REG_MSR, &usd->modem.msr, |
| USB_CH341_REG_LSR, &usd->line.lsr |
| ); |
| |
| if (ok) { |
| usd->modem.msr ^= UINT8_MAX; |
| usd->line.lsr ^= UINT8_MAX; |
| usbLogStatus_CH341(usd); |
| } |
| |
| return ok; |
| } |
| |
| static inline unsigned long |
| usbTransformValue_CH341 (uint16_t factor, unsigned long value) { |
| return (((2UL * USB_CH341_FREQUENCY) / (factor * value)) + 1UL) / 2UL; |
| } |
| |
| static unsigned int |
| usbCalculateBaud_CH341 (uint8_t prescaler, uint8_t divisor) { |
| const CH341_PrescalerEntry *ps = CH341_prescalerTable; |
| const CH341_PrescalerEntry *const end = ps + CH341_prescalerCOUNT; |
| |
| while (ps < end) { |
| if (ps->flags == prescaler) { |
| return usbTransformValue_CH341( |
| ps->factor, (USB_CH341_DIVISOR_MINUEND - divisor) |
| ); |
| } |
| |
| ps += 1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| usbLogBaud_CH341 (const UsbSerialData *usd) { |
| unsigned int baud = usbCalculateBaud_CH341( |
| usd->baud.prescaler, usd->baud.divisor |
| ); |
| |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "CH341 baud: PS:%02X DIV:%02X Baud:%u", |
| usd->baud.prescaler, usd->baud.divisor, baud |
| ); |
| } |
| |
| static int |
| usbReadBaud_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| int ok = usbReadRegisters_CH341(device, |
| USB_CH341_REG_PRESCALER, &usd->baud.prescaler, |
| USB_CH341_REG_DIVISOR, &usd->baud.divisor |
| ); |
| |
| if (ok) usbLogBaud_CH341(usd); |
| return ok; |
| } |
| |
| static int |
| usbGetBaudParameters ( |
| unsigned int wanted, unsigned int *actual, |
| uint8_t *prescaler, uint8_t *divisor |
| ) { |
| const CH341_PrescalerEntry *ps = CH341_prescalerTable; |
| const CH341_PrescalerEntry *const end = ps + CH341_prescalerCOUNT; |
| |
| const int NOT_FOUND = -1; |
| int nearestDelta = NOT_FOUND; |
| |
| while (ps < end) { |
| unsigned long psDivisor = usbTransformValue_CH341(ps->factor, wanted); |
| |
| if (psDivisor < ((ps->factor == 1)? 9: USB_CH341_DIVISOR_MINIMUM)) { |
| break; |
| } |
| |
| if (psDivisor <= USB_CH341_DIVISOR_MAXIMUM) { |
| unsigned int baud = usbTransformValue_CH341(ps->factor, psDivisor); |
| int delta = baud - wanted; |
| if (delta < 0) delta = -delta; |
| |
| if ((nearestDelta == NOT_FOUND) || (delta <= nearestDelta)) { |
| nearestDelta = delta; |
| *actual = baud; |
| *prescaler = ps->flags; |
| *divisor = USB_CH341_DIVISOR_MINUEND - psDivisor; |
| } |
| } |
| |
| ps += 1; |
| } |
| |
| return nearestDelta != NOT_FOUND; |
| } |
| |
| static int |
| usbSetBaud_CH341 (UsbDevice *device, unsigned int baud) { |
| if ((baud < USB_CH341_BAUD_MINIMUM) || (baud > USB_CH341_BAUD_MAXIMUM)) { |
| logUnsupportedBaud(baud); |
| return 0; |
| } |
| |
| unsigned int actual; |
| uint8_t prescaler; |
| uint8_t divisor; |
| |
| if (!usbGetBaudParameters(baud, &actual, &prescaler, &divisor)) { |
| return 0; |
| } |
| |
| UsbSerialData *usd = usbGetSerialData(device); |
| if ((prescaler == usd->baud.prescaler) && (divisor == usd->baud.divisor)) return 1; |
| |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "changing CH341 baud: %u -> %u", |
| baud, actual |
| ); |
| |
| int ok = usbWriteRegisters_CH341(device, |
| USB_CH341_REG_PRESCALER, (prescaler | USB_CH341_PSF_NO_WAIT), |
| USB_CH341_REG_DIVISOR, divisor |
| ); |
| |
| if (ok) { |
| usd->baud.prescaler = prescaler; |
| usd->baud.divisor = divisor; |
| } |
| |
| return ok; |
| } |
| |
| static void |
| usbLogLineControl_CH341 (const UsbSerialData *usd) { |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "CH341 line control: LCR1:%02X LCR2:%02X", |
| usd->line.lcr1, usd->line.lcr2 |
| ); |
| } |
| |
| static int |
| usbReadLineControl_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| int ok = usbReadRegisters_CH341(device, |
| USB_CH341_REG_LCR1, &usd->line.lcr1, |
| USB_CH341_REG_LCR2, &usd->line.lcr2 |
| ); |
| |
| if (ok) usbLogLineControl_CH341(usd); |
| return ok; |
| } |
| |
| static int |
| usbWriteLineControl_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| return usbWriteRegisters_CH341(device, |
| USB_CH341_REG_LCR1, usd->line.lcr1, |
| USB_CH341_REG_LCR2, usd->line.lcr2 |
| ); |
| } |
| |
| static int |
| usbUpdateLCR1_CH341 (UsbSerialData *usd, uint8_t mask, uint8_t value) { |
| return usbUpdateByte(&usd->line.lcr1, mask, value); |
| } |
| |
| static int |
| usbUpdateDataBits_CH341 (UsbSerialData *usd, unsigned int dataBits) { |
| const uint8_t mask = USB_CH341_LCR1_DATA_BITS_MASK; |
| uint8_t value; |
| |
| switch (dataBits) { |
| case 5: value = USB_CH341_LCR1_DATA_BITS_5; break; |
| case 6: value = USB_CH341_LCR1_DATA_BITS_6; break; |
| case 7: value = USB_CH341_LCR1_DATA_BITS_7; break; |
| case 8: value = USB_CH341_LCR1_DATA_BITS_8; break; |
| |
| default: |
| logUnsupportedDataBits(dataBits); |
| return 0; |
| } |
| |
| return usbUpdateLCR1_CH341(usd, mask, value); |
| } |
| |
| static int |
| usbUpdateStopBits_CH341 (UsbSerialData *usd, SerialStopBits stopBits) { |
| const uint8_t mask = USB_CH341_LCR1_STOP_BITS_MASK; |
| uint8_t value; |
| |
| switch (stopBits) { |
| case SERIAL_STOP_1: |
| value = USB_CH341_LCR1_STOP_BITS_1; |
| break; |
| |
| case SERIAL_STOP_2: |
| value = USB_CH341_LCR1_STOP_BITS_2; |
| break; |
| |
| default: |
| logUnsupportedStopBits(stopBits); |
| return 0; |
| } |
| |
| return usbUpdateLCR1_CH341(usd, mask, value); |
| } |
| |
| static int |
| usbUpdateParity_CH341 (UsbSerialData *usd, SerialParity parity) { |
| const uint8_t mask = USB_CH341_LCR1_PARITY_MASK; |
| uint8_t value; |
| |
| switch (parity) { |
| case SERIAL_PARITY_NONE: |
| value = USB_CH341_LCR1_PARITY_NONE; |
| break; |
| |
| case SERIAL_PARITY_EVEN: |
| value = USB_CH341_LCR1_PARITY_EVEN; |
| break; |
| |
| case SERIAL_PARITY_ODD: |
| value = USB_CH341_LCR1_PARITY_ODD; |
| break; |
| |
| case SERIAL_PARITY_SPACE: |
| value = USB_CH341_LCR1_PARITY_SPACE; |
| break; |
| |
| case SERIAL_PARITY_MARK: |
| value = USB_CH341_LCR1_PARITY_MARK; |
| break; |
| |
| default: |
| logUnsupportedParity(parity); |
| return 0; |
| } |
| |
| return usbUpdateLCR1_CH341(usd, mask, value); |
| } |
| |
| static int |
| usbSetDataFormat_CH341 (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| int changed = 0; |
| if (usbUpdateDataBits_CH341(usd, dataBits)) changed = 1; |
| if (usbUpdateStopBits_CH341(usd, stopBits)) changed = 1; |
| if (usbUpdateParity_CH341(usd, parity)) changed = 1; |
| |
| if (!changed) return 1; |
| return usbWriteLineControl_CH341(device); |
| } |
| |
| static void |
| usbLogFlowControl_CH341 (const UsbSerialData *usd) { |
| logMessage(LOG_CATEGORY(SERIAL_IO), |
| "CH341 flow control: %02X", |
| usd->modem.flow |
| ); |
| } |
| |
| static int |
| usbReadFlowControl_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| int ok = usbReadRegister_CH341(device, |
| USB_CH341_REG_FLOW, &usd->modem.flow |
| ); |
| |
| if (ok) usbLogFlowControl_CH341(usd); |
| return ok; |
| } |
| |
| static int |
| usbWriteFlowControl_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| return usbWriteRegister_CH341(device, USB_CH341_REG_FLOW, usd->modem.flow); |
| } |
| |
| static int |
| usbSetFlowControl_CH341 (UsbDevice *device, SerialFlowControl flowControl) { |
| uint8_t value; |
| |
| switch (flowControl) { |
| case SERIAL_FLOW_NONE: |
| value = 0; |
| break; |
| |
| case SERIAL_FLOW_HARDWARE: |
| value = USB_CH341_FLOW_RTSCTS; |
| break; |
| |
| default: |
| logUnsupportedFlowControl(flowControl); |
| return 0; |
| } |
| |
| UsbSerialData *usd = usbGetSerialData(device); |
| if (value == usd->modem.flow) return 1; |
| |
| usd->modem.flow = value; |
| return usbWriteFlowControl_CH341(device); |
| } |
| |
| static int |
| usbWriteModemControl_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| return usbControlWrite_CH341( |
| device, USB_CH341_REQ_WRITE_MCR, ~usd->modem.mcr, 0 |
| ); |
| } |
| |
| static int |
| usbInitializeSerial_CH341 (UsbDevice *device) { |
| return usbControlWrite_CH341(device, USB_CH341_REQ_INITIALIZE_SERIAL, 0, 0); |
| } |
| |
| static int |
| usbInitializeBaud_CH341 (UsbDevice *device) { |
| if (!usbReadBaud_CH341(device)) return 0; |
| return usbSetBaud_CH341(device, SERIAL_DEFAULT_BAUD); |
| } |
| |
| static int |
| usbInitializeLineControl_CH341 (UsbDevice *device) { |
| if (!usbReadLineControl_CH341(device)) return 0; |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| uint8_t oldLCR1 = usd->line.lcr1; |
| uint8_t oldLCR2 = usd->line.lcr2; |
| |
| usd->line.lcr1 |= USB_CH341_LCR1_RECEIVE_ENABLE; |
| usd->line.lcr1 |= USB_CH341_LCR1_TRANSMIT_ENABLE; |
| |
| usbUpdateDataBits_CH341(usd, SERIAL_DEFAULT_DATA_BITS); |
| usbUpdateStopBits_CH341(usd, SERIAL_DEFAULT_STOP_BITS); |
| usbUpdateParity_CH341(usd, SERIAL_DEFAULT_PARITY); |
| |
| if ((usd->line.lcr1 == oldLCR1) && (usd->line.lcr2 == oldLCR2)) return 1; |
| return usbWriteLineControl_CH341(device); |
| } |
| |
| static int |
| usbInitializeFlowControl_CH341 (UsbDevice *device) { |
| if (!usbReadFlowControl_CH341(device)) return 0; |
| return usbSetFlowControl_CH341(device, SERIAL_DEFAULT_FLOW_CONTROL); |
| } |
| |
| static int |
| usbInitializeModemControl_CH341 (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| usd->modem.mcr = 0; |
| return usbWriteModemControl_CH341(device); |
| } |
| |
| static int |
| usbEnableAdapter_CH341 (UsbDevice *device) { |
| typedef int Function (UsbDevice *device); |
| |
| static Function *const functions[] = { |
| &usbReadVersion_CH341, // should be first |
| &usbInitializeSerial_CH341, |
| &usbInitializeBaud_CH341, |
| &usbInitializeLineControl_CH341, |
| &usbInitializeFlowControl_CH341, |
| &usbInitializeModemControl_CH341, |
| &usbReadStatus_CH341, |
| NULL // must be last |
| }; |
| |
| Function *const *function = functions; |
| |
| while (*function) { |
| if (!(*function)(device)) return 0; |
| function += 1; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| usbMakeData_CH341 (UsbDevice *device, UsbSerialData **serialData) { |
| UsbSerialData *usd; |
| |
| if ((usd = malloc(sizeof(*usd)))) { |
| memset(usd, 0, sizeof(*usd)); |
| *serialData = usd; |
| return 1; |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| usbDestroyData_CH341 (UsbSerialData *usd) { |
| free(usd); |
| } |
| |
| const UsbSerialOperations usbSerialOperations_CH341 = { |
| .name = "CH341", |
| |
| .makeData = usbMakeData_CH341, |
| .destroyData = usbDestroyData_CH341, |
| |
| .enableAdapter = usbEnableAdapter_CH341, |
| .setBaud = usbSetBaud_CH341, |
| .setDataFormat = usbSetDataFormat_CH341, |
| .setFlowControl = usbSetFlowControl_CH341, |
| }; |