litex/misoclib/com/liteusb/software/ftdi/fastftdi.c

549 lines
14 KiB
C

/*
* fastftdi.c - A minimal FTDI FT2232H interface for which supports bit-bang
* mode, but focuses on very high-performance support for
* synchronous FIFO mode. Requires libusb-1.0
*
* Copyright (C) 2009 Micah Elizabeth Scott
* Copyright (C) 2015 Florent Kermarrec
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "fastftdi.h"
#if defined _WIN32 || defined _WIN64
#include <time.h>
#include <sys/timeb.h>
int gettimeofday (struct timeval *tp, void *tz)
{
struct _timeb timebuffer;
_ftime (&timebuffer);
tp->tv_sec = timebuffer.time;
tp->tv_usec = timebuffer.millitm * 1000;
return 0;
}
#endif
typedef struct {
FTDIStreamCallback *callback;
void *userdata;
int result;
FTDIProgressInfo progress;
} FTDIStreamState;
static int
DeviceInit(FTDIDevice *dev, FTDIInterface interface)
{
int err;
if (libusb_kernel_driver_active(dev->handle, (interface-1)) == 1) {
if ((err = libusb_detach_kernel_driver(dev->handle, (interface-1)))) {
perror("Error detaching kernel driver");
return err;
}
}
if ((err = libusb_set_configuration(dev->handle, 1))) {
perror("Error setting configuration");
return err;
}
if ((err = libusb_claim_interface(dev->handle, (interface-1)))) {
perror("Error claiming interface");
return err;
}
return 0;
}
int
FTDIDevice_Open(FTDIDevice *dev, FTDIInterface interface)
{
int err;
memset(dev, 0, sizeof *dev);
if ((err = libusb_init(&dev->libusb))) {
return err;
}
libusb_set_debug(dev->libusb, 0);
if (!dev->handle) {
dev->handle = libusb_open_device_with_vid_pid(dev->libusb,
FTDI_VENDOR,
FTDI_PRODUCT_FT2232H);
}
if (!dev->handle) {
return LIBUSB_ERROR_NO_DEVICE;
}
return DeviceInit(dev, interface);
}
void
FTDIDevice_Close(FTDIDevice *dev)
{
libusb_close(dev->handle);
libusb_exit(dev->libusb);
}
int
FTDIDevice_Reset(FTDIDevice *dev, FTDIInterface interface)
{
int err;
err = libusb_reset_device(dev->handle);
if (err)
return err;
return DeviceInit(dev, interface);
}
int
FTDIDevice_SetMode(FTDIDevice *dev, FTDIInterface interface,
FTDIBitmode mode, uint8_t pinDirections,
int baudRate)
{
int err;
err = libusb_control_transfer(dev->handle,
LIBUSB_REQUEST_TYPE_VENDOR
| LIBUSB_RECIPIENT_DEVICE
| LIBUSB_ENDPOINT_OUT,
FTDI_SET_BITMODE_REQUEST,
pinDirections | (mode << 8),
interface,
NULL, 0,
FTDI_COMMAND_TIMEOUT);
if (err)
return err;
if (baudRate) {
int divisor;
if (mode == FTDI_BITMODE_BITBANG)
baudRate <<= 2;
divisor = 240000000 / baudRate;
if (divisor < 1 || divisor > 0xFFFF) {
return LIBUSB_ERROR_INVALID_PARAM;
}
err = libusb_control_transfer(dev->handle,
LIBUSB_REQUEST_TYPE_VENDOR
| LIBUSB_RECIPIENT_DEVICE
| LIBUSB_ENDPOINT_OUT,
FTDI_SET_BAUD_REQUEST,
divisor,
interface,
NULL, 0,
FTDI_COMMAND_TIMEOUT);
if (err)
return err;
}
return err;
}
/*
* Internal callback for cleaning up async writes.
*/
static void
WriteAsyncCallback(struct libusb_transfer *transfer)
{
free(transfer->buffer);
libusb_free_transfer(transfer);
}
/*
* Write to an FTDI interface, either synchronously or asynchronously.
* Async writes have no completion callback, they finish 'eventually'.
*/
int
FTDIDevice_Write(FTDIDevice *dev, FTDIInterface interface,
uint8_t *data, size_t length, bool async)
{
int err;
if (async) {
struct libusb_transfer *transfer = libusb_alloc_transfer(0);
if (!transfer) {
return LIBUSB_ERROR_NO_MEM;
}
libusb_fill_bulk_transfer(transfer, dev->handle, FTDI_EP_OUT(interface),
malloc(length), length, (libusb_transfer_cb_fn) WriteAsyncCallback, 0, 0);
if (!transfer->buffer) {
libusb_free_transfer(transfer);
return LIBUSB_ERROR_NO_MEM;
}
memcpy(transfer->buffer, data, length);
err = libusb_submit_transfer(transfer);
} else {
int transferred;
err = libusb_bulk_transfer(dev->handle, FTDI_EP_OUT(interface),
data, length, &transferred,
FTDI_COMMAND_TIMEOUT);
}
if (err < 0)
return err;
else
return 0;
}
int
FTDIDevice_WriteByteSync(FTDIDevice *dev, FTDIInterface interface, uint8_t byte)
{
return FTDIDevice_Write(dev, interface, &byte, sizeof byte, false);
}
int
FTDIDevice_ReadByteSync(FTDIDevice *dev, FTDIInterface interface, uint8_t *byte)
{
/*
* This is a simplified synchronous read, intended for bit-banging mode.
* Ignores the modem/buffer status bytes, returns just the data.
*
*/
uint8_t packet[3];
int transferred, err;
err = libusb_bulk_transfer(dev->handle, FTDI_EP_IN(interface),
packet, sizeof packet, &transferred,
FTDI_COMMAND_TIMEOUT);
if (err < 0) {
return err;
}
if (transferred != sizeof packet) {
return -1;
}
if (byte) {
*byte = packet[sizeof packet - 1];
}
return 0;
}
/*
* Internal callback for one transfer's worth of stream data.
* Split it into packets and invoke the callbacks.
*/
static void
ReadStreamCallback(struct libusb_transfer *transfer)
{
FTDIStreamState *state = transfer->user_data;
if (state->result == 0) {
if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
int i;
uint8_t *ptr = transfer->buffer;
int length = transfer->actual_length;
int numPackets = (length + FTDI_PACKET_SIZE - 1) >> FTDI_LOG_PACKET_SIZE;
for (i = 0; i < numPackets; i++) {
int payloadLen;
int packetLen = length;
if (packetLen > FTDI_PACKET_SIZE)
packetLen = FTDI_PACKET_SIZE;
payloadLen = packetLen - FTDI_HEADER_SIZE;
state->progress.current.totalBytes += payloadLen;
state->result = state->callback(ptr + FTDI_HEADER_SIZE, payloadLen,
NULL, state->userdata);
if (state->result)
break;
ptr += packetLen;
length -= packetLen;
}
} else {
state->result = LIBUSB_ERROR_IO;
}
}
if (state->result == 0) {
transfer->status = -1;
state->result = libusb_submit_transfer(transfer);
}
}
static double
TimevalDiff(const struct timeval *a, const struct timeval *b)
{
return (a->tv_sec - b->tv_sec) + 1e-6 * (a->tv_usec - b->tv_usec);
}
/*
* Use asynchronous transfers in libusb-1.0 for high-performance
* streaming of data from a device interface back to the PC. This
* function continuously transfers data until either an error occurs
* or the callback returns a nonzero value. This function returns
* a libusb error code or the callback's return value.
*
* For every contiguous block of received data, the callback will
* be invoked.
*/
int
FTDIDevice_ReadStream(FTDIDevice *dev, FTDIInterface interface,
FTDIStreamCallback *callback, void *userdata,
int packetsPerTransfer, int numTransfers)
{
struct libusb_transfer **transfers;
FTDIStreamState state = { callback, userdata };
int bufferSize = packetsPerTransfer * FTDI_PACKET_SIZE;
int xferIndex;
int err = 0;
/*
* Set up all transfers
*/
transfers = calloc(numTransfers, sizeof *transfers);
if (!transfers) {
err = LIBUSB_ERROR_NO_MEM;
goto cleanup;
}
for (xferIndex = 0; xferIndex < numTransfers; xferIndex++) {
struct libusb_transfer *transfer;
transfer = libusb_alloc_transfer(0);
transfers[xferIndex] = transfer;
if (!transfer) {
err = LIBUSB_ERROR_NO_MEM;
goto cleanup;
}
libusb_fill_bulk_transfer(transfer, dev->handle, FTDI_EP_IN(interface),
malloc(bufferSize), bufferSize, (libusb_transfer_cb_fn) ReadStreamCallback,
&state, 0);
if (!transfer->buffer) {
err = LIBUSB_ERROR_NO_MEM;
goto cleanup;
}
transfer->status = -1;
err = libusb_submit_transfer(transfer);
if (err)
goto cleanup;
}
/*
* Run the transfers, and periodically assess progress.
*/
gettimeofday(&state.progress.first.time, NULL);
do {
FTDIProgressInfo *progress = &state.progress;
const double progressInterval = 0.1;
struct timeval timeout = { 0, 10000 };
struct timeval now;
int err = libusb_handle_events_timeout(dev->libusb, &timeout);
if (!state.result) {
state.result = err;
}
// If enough time has elapsed, update the progress
gettimeofday(&now, NULL);
if (TimevalDiff(&now, &progress->current.time) >= progressInterval) {
progress->current.time = now;
if (progress->prev.totalBytes) {
// We have enough information to calculate rates
double currentTime;
progress->totalTime = TimevalDiff(&progress->current.time,
&progress->first.time);
currentTime = TimevalDiff(&progress->current.time,
&progress->prev.time);
progress->totalRate = progress->current.totalBytes / progress->totalTime;
progress->currentRate = (progress->current.totalBytes -
progress->prev.totalBytes) / currentTime;
}
state.result = state.callback(NULL, 0, progress, state.userdata);
progress->prev = progress->current;
}
} while (!state.result);
/*
* Cancel any outstanding transfers, and free memory.
*/
cleanup:
if (transfers) {
bool done_cleanup = false;
while (!done_cleanup)
{
done_cleanup = true;
for (xferIndex = 0; xferIndex < numTransfers; xferIndex++) {
struct libusb_transfer *transfer = transfers[xferIndex];
if (transfer) {
// If a transfer is in progress, cancel it
if (transfer->status == -1) {
libusb_cancel_transfer(transfer);
// And we need to wait until we get a clean sweep
done_cleanup = false;
// If a transfer is complete or cancelled, nuke it
} else if (transfer->status == 0 ||
transfer->status == LIBUSB_TRANSFER_CANCELLED) {
free(transfer->buffer);
libusb_free_transfer(transfer);
transfers[xferIndex] = NULL;
}
}
}
// pump events
struct timeval timeout = { 0, 10000 };
libusb_handle_events_timeout(dev->libusb, &timeout);
}
free(transfers);
}
if (err)
return err;
else
return state.result;
}
/* MPSSE mode support -- see
* http://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf
*/
int
FTDIDevice_MPSSE_Enable(FTDIDevice *dev, FTDIInterface interface)
{
int err;
/* Reset interface */
err = FTDIDevice_SetMode(dev, interface, FTDI_BITMODE_RESET, 0, 0);
if (err)
return err;
/* Enable MPSSE mode */
err = FTDIDevice_SetMode(dev, interface, FTDI_BITMODE_MPSSE,
FTDI_SET_BITMODE_REQUEST, 0);
return err;
}
int
FTDIDevice_MPSSE_SetDivisor(FTDIDevice *dev, FTDIInterface interface,
uint8_t ValueL, uint8_t ValueH)
{
uint8_t buf[3] = {FTDI_MPSSE_SETDIVISOR, 0, 0};
buf[1] = ValueL;
buf[2] = ValueH;
return FTDIDevice_Write(dev, interface, buf, 3, false);
}
int
FTDIDevice_MPSSE_SetLowByte(FTDIDevice *dev, FTDIInterface interface, uint8_t data, uint8_t dir)
{
uint8_t buf[3] = {FTDI_MPSSE_SETLOW, 0, 0};
buf[1] = data;
buf[2] = dir;
return FTDIDevice_Write(dev, interface, buf, 3, false);
}
int
FTDIDevice_MPSSE_SetHighByte(FTDIDevice *dev, FTDIInterface interface, uint8_t data, uint8_t dir)
{
uint8_t buf[3] = {FTDI_MPSSE_SETHIGH, 0, 0};
buf[1] = data;
buf[2] = dir;
return FTDIDevice_Write(dev, interface, buf, 3, false);
}
int
FTDIDevice_MPSSE_GetLowByte(FTDIDevice *dev, FTDIInterface interface, uint8_t *byte)
{
int err;
err = FTDIDevice_WriteByteSync(dev, interface, FTDI_MPSSE_GETLOW);
if (err)
return err;
return FTDIDevice_ReadByteSync(dev, interface, byte);
}
int
FTDIDevice_MPSSE_GetHighByte(FTDIDevice *dev, FTDIInterface interface, uint8_t *byte)
{
int err;
err = FTDIDevice_WriteByteSync(dev, interface, FTDI_MPSSE_GETHIGH);
if (err)
return err;
return FTDIDevice_ReadByteSync(dev, interface, byte);
}