/* * 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-2019 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 . */ #include "prologue.h" #include #include #include "log.h" #include "usb_serial.h" #include "usb_ftdi.h" static int usbInputFilter_FTDI (UsbInputFilterData *data) { return usbSkipInitialBytes(data, 2); } static int usbSetAttribute_FTDI (UsbDevice *device, unsigned char request, unsigned int value, unsigned int index) { logMessage(LOG_CATEGORY(USB_IO), "FTDI request: %02X %04X %04X", request, value, index); return usbControlWrite(device, UsbControlRecipient_Device, UsbControlType_Vendor, request, value, index, NULL, 0, 1000) != -1; } static int usbSetBaud_FTDI (UsbDevice *device, unsigned int divisor) { return usbSetAttribute_FTDI(device, 3, divisor&0XFFFF, divisor>>0X10); } static int usbSetBaud_FTDI_SIO (UsbDevice *device, unsigned int baud) { unsigned int divisor; switch (baud) { case 300: divisor = 0; break; case 600: divisor = 1; break; case 1200: divisor = 2; break; case 2400: divisor = 3; break; case 4800: divisor = 4; break; case 9600: divisor = 5; break; case 19200: divisor = 6; break; case 38400: divisor = 7; break; case 57600: divisor = 8; break; case 115200: divisor = 9; break; default: logMessage(LOG_WARNING, "unsupported FTDI SIO baud: %u", baud); errno = EINVAL; return 0; } return usbSetBaud_FTDI(device, divisor); } static int usbSetBaud_FTDI_FT8U232AM (UsbDevice *device, unsigned int baud) { if (baud > 3000000) { logMessage(LOG_WARNING, "unsupported FTDI FT8U232AM baud: %u", baud); errno = EINVAL; return 0; } { const unsigned int base = 48000000; unsigned int eighths = base / 2 / baud; unsigned int divisor; if ((eighths & 07) == 7) eighths++; divisor = eighths >> 3; divisor |= (eighths & 04)? 0X4000: (eighths & 02)? 0X8000: (eighths & 01)? 0XC000: 0X0000; if (divisor == 1) divisor = 0; return usbSetBaud_FTDI(device, divisor); } } static int usbSetBaud_FTDI_FT232BM (UsbDevice *device, unsigned int baud) { if (baud > 3000000) { logMessage(LOG_WARNING, "unsupported FTDI FT232BM baud: %u", baud); errno = EINVAL; return 0; } { static const unsigned char mask[8] = {00, 03, 02, 04, 01, 05, 06, 07}; const unsigned int base = 48000000; const unsigned int eighths = base / 2 / baud; unsigned int divisor = (eighths >> 3) | (mask[eighths & 07] << 14); if (divisor == 1) { divisor = 0; } else if (divisor == 0X4001) { divisor = 1; } return usbSetBaud_FTDI(device, divisor); } } static int usbSetFlowControl_FTDI (UsbDevice *device, SerialFlowControl flow) { unsigned int index = 0; #define FTDI_FLOW(from,to) if ((flow & (from)) == (from)) flow &= ~(from), index |= (to) FTDI_FLOW(SERIAL_FLOW_OUTPUT_CTS|SERIAL_FLOW_INPUT_RTS, 0X0100); FTDI_FLOW(SERIAL_FLOW_OUTPUT_DSR|SERIAL_FLOW_INPUT_DTR, 0X0200); FTDI_FLOW(SERIAL_FLOW_OUTPUT_XON|SERIAL_FLOW_INPUT_XON, 0X0400); #undef FTDI_FLOW if (flow) { logMessage(LOG_WARNING, "unsupported FTDI flow control: %02X", flow); } return usbSetAttribute_FTDI(device, 2, ((index & 0X0400)? 0X1311: 0), index); } static int usbSetDataFormat_FTDI (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) { int ok = 1; unsigned int value = dataBits & 0XFF; if (dataBits != value) { logMessage(LOG_WARNING, "unsupported FTDI data bits: %u", dataBits); ok = 0; } switch (parity) { case SERIAL_PARITY_NONE: value |= 0X000; break; case SERIAL_PARITY_ODD: value |= 0X100; break; case SERIAL_PARITY_EVEN: value |= 0X200; break; case SERIAL_PARITY_MARK: value |= 0X300; break; case SERIAL_PARITY_SPACE: value |= 0X400; break; default: logMessage(LOG_WARNING, "unsupported FTDI parity: %u", parity); ok = 0; break; } switch (stopBits) { case SERIAL_STOP_1: value |= 0X0000; break; case SERIAL_STOP_2: value |= 0X1000; break; default: logMessage(LOG_WARNING, "unsupported FTDI stop bits: %u", stopBits); ok = 0; break; } if (!ok) { errno = EINVAL; return 0; } return usbSetAttribute_FTDI(device, 4, value, 0); } static int usbSetModemState_FTDI (UsbDevice *device, int state, int shift, const char *name) { if ((state < 0) || (state > 1)) { logMessage(LOG_WARNING, "Unsupported FTDI %s state: %d", name, state); errno = EINVAL; return 0; } return usbSetAttribute_FTDI(device, 1, ((1 << (shift + 8)) | (state << shift)), 0); } static int usbSetDtrState_FTDI (UsbDevice *device, int state) { return usbSetModemState_FTDI(device, state, 0, "DTR"); } static int usbSetRtsState_FTDI (UsbDevice *device, int state) { return usbSetModemState_FTDI(device, state, 1, "RTS"); } const UsbSerialOperations usbSerialOperations_FTDI_SIO = { .name = "FTDI_SIO", .setBaud = usbSetBaud_FTDI_SIO, .setDataFormat = usbSetDataFormat_FTDI, .setFlowControl = usbSetFlowControl_FTDI, .setDtrState = usbSetDtrState_FTDI, .setRtsState = usbSetRtsState_FTDI }; const UsbSerialOperations usbSerialOperations_FTDI_FT8U232AM = { .name = "FTDI_FT8U232AM", .setBaud = usbSetBaud_FTDI_FT8U232AM, .setDataFormat = usbSetDataFormat_FTDI, .setFlowControl = usbSetFlowControl_FTDI, .setDtrState = usbSetDtrState_FTDI, .setRtsState = usbSetRtsState_FTDI, .inputFilter = usbInputFilter_FTDI }; const UsbSerialOperations usbSerialOperations_FTDI_FT232BM = { .name = "FTDI_FT232BM", .setBaud = usbSetBaud_FTDI_FT232BM, .setDataFormat = usbSetDataFormat_FTDI, .setFlowControl = usbSetFlowControl_FTDI, .setDtrState = usbSetDtrState_FTDI, .setRtsState = usbSetRtsState_FTDI, .inputFilter = usbInputFilter_FTDI };