litex/misoclib/com/liteusb/software/ftdi/fastftdi.c
Florent Kermarrec da0fe2ecfb liteusb: refactor software (use python instead of libftdicom in C) and provide simple example.
small modifications to fastftdi.c are also done to select our interface (A or B) and mode (synchronous, asynchronous)
2015-05-01 16:22:26 +02:00

548 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);
}