/* -------------------------------------------------------------------------------------------------- */
/* The LongMynd receiver: ftdi_usb.c */
/* - an implementation of the Serit NIM controlling software for the MiniTiouner Hardware */
/* - here we have all the usb interactions with the ftdi module */
/* Copyright 2019 Heather Lomond */
/* -------------------------------------------------------------------------------------------------- */
/*
This file is part of longmynd.
Longmynd is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Longmynd is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with longmynd. If not, see .
*/
/* -------------------------------------------------------------------------------------------------- */
/* ----------------- INCLUDES ----------------------------------------------------------------------- */
/* -------------------------------------------------------------------------------------------------- */
#include
#include
#include
#include
#include
#include
#include "errors.h"
#include "ftdi_usb.h"
#include "ftdi.h"
/* -------------------------------------------------------------------------------------------------- */
/* ----------------- DEFINES ------------------------------------------------------------------------ */
/* -------------------------------------------------------------------------------------------------- */
#define FTDI_USB_READ_RETRIES 2
/* Requests */
#define SIO_RESET_REQUEST SIO_RESET
#define SIO_SET_BAUDRATE_REQUEST SIO_SET_BAUD_RATE
#define SIO_SET_DATA_REQUEST SIO_SET_DATA
#define SIO_SET_FLOW_CTRL_REQUEST SIO_SET_FLOW_CTRL
#define SIO_SET_MODEM_CTRL_REQUEST SIO_MODEM_CTRL
#define SIO_POLL_MODEM_STATUS_REQUEST 0x05
#define SIO_SET_EVENT_CHAR_REQUEST 0x06
#define SIO_SET_ERROR_CHAR_REQUEST 0x07
#define SIO_SET_LATENCY_TIMER_REQUEST 0x09
#define SIO_GET_LATENCY_TIMER_REQUEST 0x0A
#define SIO_SET_BITMODE_REQUEST 0x0B
#define SIO_READ_PINS_REQUEST 0x0C
#define SIO_READ_EEPROM_REQUEST 0x90
#define SIO_WRITE_EEPROM_REQUEST 0x91
#define SIO_ERASE_EEPROM_REQUEST 0x92
#define LATENCY_MS 16
#define SIO_RESET 0 /* Reset the port */
#define SIO_MODEM_CTRL 1 /* Set the modem control register */
#define SIO_SET_FLOW_CTRL 2 /* Set flow control register */
#define SIO_SET_BAUD_RATE 3 /* Set baud rate */
#define SIO_SET_DATA 4 /* Set the data characteristics of the port */
#define SIO_RESET_SIO 0
#define SIO_RESET_PURGE_RX 1
#define SIO_RESET_PURGE_TX 2
#define FTDI_DEVICE_OUT_REQTYPE (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT)
#define FTDI_DEVICE_IN_REQTYPE (LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN)
#define FTDI_RX_CHUNK_SIZE 4096
#define FTDI_TX_CHUNK_SIZE 4096
/* -------------------------------------------------------------------------------------------------- */
/* ----------------- GLOBALS ------------------------------------------------------------------------ */
/* -------------------------------------------------------------------------------------------------- */
uint8_t rx_chunk[FTDI_RX_CHUNK_SIZE];
/* MPSSE bitbang modes */
enum ftdi_mpsse_mode
{
BITMODE_RESET = 0x00, /* switch off bitbang mode, back to regular serial/FIFO */
BITMODE_BITBANG= 0x01, /* classical asynchronous bitbang mode, introduced with B-type chips */
BITMODE_MPSSE = 0x02, /* MPSSE mode, available on 2232x chips */
BITMODE_SYNCBB = 0x04, /* synchronous bitbang mode, available on 2232x and R-type chips */
BITMODE_MCU = 0x08, /* MCU Host Bus Emulation mode, available on 2232x chips */
/* CPU-style fifo mode gets set via EEPROM */
BITMODE_OPTO = 0x10, /* Fast Opto-Isolated Serial Interface Mode, available on 2232x chips */
BITMODE_CBUS = 0x20, /* Bitbang on CBUS pins of R-type chips, configure in EEPROM before */
BITMODE_SYNCFF = 0x40, /* Single Channel Synchronous FIFO mode, available on 2232H chips */
};
static libusb_device_handle *usb_device_handle_i2c; // interface 0, endpoints: 0x81, 0x02
static libusb_device_handle *usb_device_handle_ts; // interface 1, endpoints: 0x83, 0x04
static libusb_context *usb_context_i2c;
static libusb_context *usb_context_ts;
/* -------------------------------------------------------------------------------------------------- */
/* ----------------- ROUTINES ----------------------------------------------------------------------- */
/* -------------------------------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------------------------------- */
uint8_t ftdi_usb_i2c_write( uint8_t *buffer, uint8_t len ){
/* -------------------------------------------------------------------------------------------------- */
/* writes data out to the usb */
/* *buffer: the buffer containing the data to be written out */
/* len: the number of bytes to send */
/* return : error code */
/* -------------------------------------------------------------------------------------------------- */
uint8_t err=ERROR_NONE;
int sent=0;
int res;
res=libusb_bulk_transfer(usb_device_handle_i2c, 0x02, buffer, len, &sent, USB_TIMEOUT);
if (res<0) {
printf("ERROR: USB Cmd Write failure %d\n",res);
err=ERROR_FTDI_USB_CMD;
}
if ((err==ERROR_NONE) && (sent!=len)) {
printf("ERROR: i2c write incorrect num bytes sent=%i, len=%i\n",sent,len);
err=ERROR_FTDI_I2C_WRITE_LEN;
}
return err;
}
/* -------------------------------------------------------------------------------------------------- */
uint8_t ftdi_usb_i2c_read( uint8_t **buffer) {
/* -------------------------------------------------------------------------------------------------- */
/* reads one byte from the usb and returns it. Keeping any other data bytes for later */
/* Note: we only ever need to read one byte of actual data so we can avoid data copying by using the */
/* internal buffers of the usb reads to keep the data */
/* *buffer: iretruned as a pointer the the actual data read into the usb */
/* len: the number of bytes to read */
/* return : error code */
/* -------------------------------------------------------------------------------------------------- */
uint8_t err=ERROR_NONE;
static int rxed=0;
static int posn=0;
int res;
int n;
/* if we have unused characters in the buffer then use them up first */
if (posn!=rxed) {
*buffer=&rx_chunk[posn++];
} else {
/* if we couldn't do it with data we already have then get a new buffer */
/* the data may not be available immediatly so try a few times until it appears (or we error) */
for (n=0; n2) break;
}
/* check we didn't timeout */
if (n==FTDI_USB_READ_RETRIES) err=ERROR_FTDI_I2C_READ_LEN;
else if (err==ERROR_NONE) {
/* once we have good data, use it to fulfil the request */
posn=2;
*buffer=&rx_chunk[posn++];
}
}
return err;
}
/* -------------------------------------------------------------------------------------------------- */
static uint8_t ftdi_usb_set_mpsse_mode(libusb_device_handle *_device_handle){
/* -------------------------------------------------------------------------------------------------- */
/* setup the FTDI USB interface and MPSEE mode */
/* return : error code */
/* -------------------------------------------------------------------------------------------------- */
uint16_t val;
int res;
uint8_t err=ERROR_NONE;
printf("Flow: FTDI set mpsse mode\n");
/* clear out the receive buffers */
if ((res=libusb_control_transfer(_device_handle, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_PURGE_RX, 1, NULL, 0, USB_TIMEOUT))<0) {
printf("ERROR: USB RX Purge failed %d",res);
err=ERROR_MPSSE;
}
/* clear out the transmit buffers */
if ((res=libusb_control_transfer(_device_handle, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_PURGE_TX, 1, NULL, 0, USB_TIMEOUT))<0) {
printf("ERROR: USB TX Purge failed %d",res);
err=ERROR_MPSSE;
}
if ((res=libusb_control_transfer(_device_handle, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_SIO, 1, NULL, 0, USB_TIMEOUT))<0) {
printf("ERROR: USB Reset failed %d",res);
err=ERROR_MPSSE;
}
/* set the latence of the bus */
if ((res=libusb_control_transfer(_device_handle, FTDI_DEVICE_OUT_REQTYPE, SIO_SET_LATENCY_TIMER_REQUEST,
LATENCY_MS, 1, NULL, 0, USB_TIMEOUT))<0) {
printf("ERROR: USB Set Latency failed %d",res);
err=ERROR_MPSSE;
}
/* set the bit modes */
val = (BITMODE_RESET<<8);
if ((res=libusb_control_transfer(_device_handle, FTDI_DEVICE_OUT_REQTYPE, SIO_SET_BITMODE_REQUEST,
val, 1, NULL, 0, USB_TIMEOUT))<0) {
printf("USB Reset Bitmode failed %d\n",res);
err=ERROR_MPSSE;
}
val = (BITMODE_MPSSE<<8);
if ((res=libusb_control_transfer(_device_handle, FTDI_DEVICE_OUT_REQTYPE, SIO_SET_BITMODE_REQUEST,
val, 1, NULL, 0, USB_TIMEOUT))<0) {
printf("USB Set MPSSE failed %d\n",res);
err=ERROR_MPSSE;
}
usleep(1000);
return err;
}
uint8_t ftdi_usb_set_mpsse_mode_i2c(void){
return ftdi_usb_set_mpsse_mode(usb_device_handle_i2c);
}
uint8_t ftdi_usb_set_mpsse_mode_ts(void){
return ftdi_usb_set_mpsse_mode(usb_device_handle_ts);
}
/* -------------------------------------------------------------------------------------------------- */
static uint8_t ftdi_usb_init(libusb_context **usb_context_ptr, libusb_device_handle **usb_device_handle_ptr, int interface_num, uint8_t usb_bus, uint8_t usb_addr, uint16_t vid, uint16_t pid) {
/* -------------------------------------------------------------------------------------------------- */
/* initialise the usb device of choice (or via vid/pid if no USB selected) */
/* return : error code */
/* -------------------------------------------------------------------------------------------------- */
uint8_t err=ERROR_NONE;
ssize_t count;
libusb_device **usb_device_list;
int error_code;
struct libusb_device_descriptor usb_descriptor;
libusb_device *usb_candidate_device;
uint8_t usb_device_count;
printf("Flow: FTDI USB init\n");
if (libusb_init(usb_context_ptr)<0) {
printf("ERROR: Unable to initialise LIBUSB\n");
err=ERROR_FTDI_USB_INIT_LIBUSB;
}
/* turn on debug */
#if LIBUSB_API_VERSION >= 0x01000106
libusb_set_option(*usb_context_ptr, LIBUSB_LOG_LEVEL_INFO);
#else
libusb_set_debug(*usb_context_ptr, LIBUSB_LOG_LEVEL_INFO);
#endif
/* now we need to decide if we are opening by VID and PID or by device number */
if ((err==ERROR_NONE) && (usb_bus==0) && (usb_addr==0)) {
/* if we are using vid and pid it is easy */
if ((*usb_device_handle_ptr = libusb_open_device_with_vid_pid(*usb_context_ptr, vid, pid))==NULL) {
printf("ERROR: Unable to open device with VID and PID\n");
printf(" Is the USB cable plugged in?\n");
err=ERROR_FTDI_USB_VID_PID;
}
/* if we are finding by usb device number then we have to take a look at the IDs to check we are */
/* being asked to open the right one. sTto do this we get a list of all the USB devices on the system */
} else if (err==ERROR_NONE) {
printf("Flow: Searching for bus/device=%i,%i\n",usb_bus,usb_addr);
count=libusb_get_device_list(*usb_context_ptr, &usb_device_list);
if (count<=0) {
printf("ERROR: failed to get the list of devices\n");
err=ERROR_FTDI_USB_DEVICE_LIST;
}
if (err==ERROR_NONE) {
/* we need to find the device we have been told to use */
for (usb_device_count=0; usb_device_count