blob: 5c0f37aaecb66c63b8b4eb9cdcf1884baa74be89 [file] [log] [blame] [edit]
/*
* 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_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(SERIAL_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:
logUnsupportedBaud(baud);
errno = EINVAL;
return 0;
}
return usbSetBaud_FTDI(device, divisor);
}
static int
usbSetBaud_FTDI_FT8U232AM (UsbDevice *device, unsigned int baud) {
if (baud > 3000000) {
logUnsupportedBaud(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) {
logUnsupportedBaud(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) {
logUnsupportedFlowControl(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) {
logUnsupportedDataBits(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:
logUnsupportedParity(parity);
ok = 0;
break;
}
switch (stopBits) {
case SERIAL_STOP_1: value |= 0X0000; break;
case SERIAL_STOP_2: value |= 0X1000; break;
default:
logUnsupportedStopBits(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
};