From 65d43393ede4f350b4868b747dac38fddb9ec859 Mon Sep 17 00:00:00 2001
From: Steve Markgraf <steve@steve-m.de>
Date: Tue, 17 Apr 2018 00:23:07 +0200
Subject: initial commit

Signed-off-by: Steve Markgraf <steve@steve-m.de>
---
 src/libosmo-fl2k.c | 969 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 969 insertions(+)
 create mode 100644 src/libosmo-fl2k.c

(limited to 'src/libosmo-fl2k.c')

diff --git a/src/libosmo-fl2k.c b/src/libosmo-fl2k.c
new file mode 100644
index 0000000..7786477
--- /dev/null
+++ b/src/libosmo-fl2k.c
@@ -0,0 +1,969 @@
+/*
+ * osmo-fl2k, turns FL2000-based USB 3.0 to VGA adapters into
+ * low cost DACs
+ *
+ * Copyright (C) 2016-2018 by Steve Markgraf <steve@steve-m.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#include <math.h>
+#include <libusb.h>
+#include <pthread.h>
+
+/*
+ * All libusb callback functions should be marked with the LIBUSB_CALL macro
+ * to ensure that they are compiled with the same calling convention as libusb.
+ *
+ * If the macro isn't available in older libusb versions, we simply define it.
+ */
+#ifndef LIBUSB_CALL
+#define LIBUSB_CALL
+#endif
+
+/* libusb < 1.0.9 doesn't have libusb_handle_events_timeout_completed */
+#ifndef HAVE_LIBUSB_HANDLE_EVENTS_TIMEOUT_COMPLETED
+#define libusb_handle_events_timeout_completed(ctx, tv, c) \
+	libusb_handle_events_timeout(ctx, tv)
+#endif
+
+#include "osmo-fl2k.h"
+
+enum fl2k_async_status {
+	FL2K_INACTIVE = 0,
+	FL2K_CANCELING,
+	FL2K_RUNNING
+};
+
+typedef enum fl2k_buf_state {
+	BUF_EMPTY = 0,
+	BUF_SUBMITTED,
+	BUF_FILLED,
+} fl2k_buf_state_t;
+
+typedef struct fl2k_xfer_info {
+	fl2k_dev_t *dev;
+	uint64_t seq;
+	fl2k_buf_state_t state;
+} fl2k_xfer_info_t;
+
+struct fl2k_dev {
+	libusb_context *ctx;
+	struct libusb_device_handle *devh;
+	uint32_t xfer_num;
+	uint32_t xfer_buf_num;
+	uint32_t xfer_buf_len;
+	struct libusb_transfer **xfer;
+	unsigned char **xfer_buf;
+
+	fl2k_xfer_info_t *xfer_info;
+
+	fl2k_tx_cb_t cb;
+	void *cb_ctx;
+	enum fl2k_async_status async_status;
+	int async_cancel;
+
+	int use_zerocopy;
+	int terminate;
+
+	/* thread related */
+	pthread_t usb_worker_thread;
+	pthread_t sample_worker_thread;
+	pthread_mutex_t buf_mutex;
+	pthread_cond_t buf_cond;
+
+	double rate; /* Hz */
+
+	/* status */
+	int dev_lost;
+	int driver_active;
+	uint32_t underflow_cnt;
+};
+
+typedef struct fl2k_dongle {
+	uint16_t vid;
+	uint16_t pid;
+	const char *name;
+} fl2k_dongle_t;
+
+static fl2k_dongle_t known_devices[] = {
+	{ 0x1d5c, 0x2000, "FL2000DX OEM" },
+};
+
+#define DEFAULT_BUF_NUMBER	4
+
+#define CTRL_IN		(LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_IN)
+#define CTRL_OUT	(LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT)
+#define CTRL_TIMEOUT	300
+#define BULK_TIMEOUT	0
+
+static int fl2k_read_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t *val)
+{
+	int r;
+	uint8_t data[4];
+
+	if (!dev || !val)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	r = libusb_control_transfer(dev->devh, CTRL_IN, 0x40,
+				    0, reg, data, 4, CTRL_TIMEOUT);
+
+	if (r < 4)
+		fprintf(stderr, "Error, short read from register!\n");
+
+	*val = (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
+
+	return r;
+}
+
+static int fl2k_write_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t val)
+{
+	uint8_t data[4];
+
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	data[0] = val & 0xff;
+	data[1] = (val >> 8) & 0xff;
+	data[2] = (val >> 16) & 0xff;
+	data[3] = (val >> 24) & 0xff;
+
+	return libusb_control_transfer(dev->devh, CTRL_OUT, 0x41,
+				       0, reg, data, 4, CTRL_TIMEOUT);
+}
+
+int fl2k_init_device(fl2k_dev_t *dev)
+{
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	/* initialization */
+	fl2k_write_reg(dev, 0x8020, 0xdf0000cc);
+
+	/* set DAC freq to lowest value possible to avoid
+	 * underrun during init */
+	fl2k_write_reg(dev, 0x802c, 0x00416f3f);
+
+	fl2k_write_reg(dev, 0x8048, 0x7ffb8004);
+	fl2k_write_reg(dev, 0x803c, 0xd701004d);
+	fl2k_write_reg(dev, 0x8004, 0x0000031c);
+	fl2k_write_reg(dev, 0x8004, 0x0010039d);
+	fl2k_write_reg(dev, 0x8008, 0x07800898);
+
+	fl2k_write_reg(dev, 0x801c, 0x00000000);
+	fl2k_write_reg(dev, 0x0070, 0x04186085);
+
+	/* blanking magic */
+	fl2k_write_reg(dev, 0x8008, 0xfeff0780);
+	fl2k_write_reg(dev, 0x800c, 0x0000f001);
+
+	/* VSYNC magic */
+	fl2k_write_reg(dev, 0x8010, 0x0400042a);
+	fl2k_write_reg(dev, 0x8014, 0x0010002d);
+
+	fl2k_write_reg(dev, 0x8004, 0x00000002);
+
+	return 0;
+}
+
+int fl2k_deinit_device(fl2k_dev_t *dev)
+{
+	int r = 0;
+
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	/* TODO, power down DACs, PLL, put device in reset */
+
+	return r;
+}
+
+static double fl2k_reg_to_freq(uint32_t reg)
+{
+	double sample_clock, offset, offs_div;
+	uint32_t pll_clock = 160000000;
+	uint8_t div = reg & 0x3f;
+	uint8_t out_div = (reg >> 8) & 0xf;
+	uint8_t frac = (reg >> 16) & 0xf;
+	uint8_t mult = (reg >> 20) & 0xf;
+
+	sample_clock = (pll_clock * mult) / (uint32_t)div;
+	offs_div = (pll_clock / 5.0f ) * mult;
+	offset = ((double)sample_clock/(offs_div/2)) * 1000000.0f;
+	sample_clock += (uint32_t)offset * frac;
+	sample_clock /= out_div;
+
+//	fprintf(stderr, "div: %d\tod: %d\tfrac: %d\tmult %d\tclock: %f\treg "
+//			"%08x\n", div, out_div, frac, mult, sample_clock, reg);
+
+	return sample_clock;
+}
+
+int fl2k_set_sample_rate(fl2k_dev_t *dev, uint32_t target_freq)
+{
+	double sample_clock, error, last_error = 1e20f;
+	uint32_t reg = 0, result_reg = 0;
+	uint8_t div, mult, frac, out_div;
+
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	/* Output divider (accepts value 1-15) 
+	 * works, but adds lots of phase noise, so do not use it */
+	out_div = 1;
+
+	/* Observation: PLL multiplier of 7 works, but has more phase
+	 * noise. Prefer multiplier 6 and 5 */
+	for (mult = 6; mult >= 3; mult--) {
+		for (div = 63; div > 1; div--) {
+			for (frac = 1; frac <= 15; frac++) {
+				reg =  (mult << 20) | (frac << 16) |
+				       (0x60 << 8) | (out_div << 8) | div;
+
+				sample_clock = fl2k_reg_to_freq(reg);
+				error = sample_clock - (double)target_freq;
+
+				/* Keep closest match */
+				if (fabsf(error) < last_error) {
+					result_reg = reg;
+					last_error = fabsf(error);
+				}
+			}
+		}
+	}
+
+	sample_clock = fl2k_reg_to_freq(result_reg);
+	error = sample_clock - (double)target_freq;
+	dev->rate = sample_clock;
+
+	if (fabsf(error) > 1)
+		fprintf(stderr, "Requested sample rate %d not possible, using"
+		                " %f, error is %f\n", target_freq, sample_clock, error); 
+
+	return fl2k_write_reg(dev, 0x802c, result_reg);
+}
+
+uint32_t fl2k_get_sample_rate(fl2k_dev_t *dev)
+{
+	if (!dev)
+		return 0;
+
+	return (uint32_t)dev->rate;
+}
+
+static fl2k_dongle_t *find_known_device(uint16_t vid, uint16_t pid)
+{
+	unsigned int i;
+	fl2k_dongle_t *device = NULL;
+
+	for (i = 0; i < sizeof(known_devices)/sizeof(fl2k_dongle_t); i++ ) {
+		if (known_devices[i].vid == vid && known_devices[i].pid == pid) {
+			device = &known_devices[i];
+			break;
+		}
+	}
+
+	return device;
+}
+
+uint32_t fl2k_get_device_count(void)
+{
+	int i,r;
+	libusb_context *ctx;
+	libusb_device **list;
+	uint32_t device_count = 0;
+	struct libusb_device_descriptor dd;
+	ssize_t cnt;
+
+	r = libusb_init(&ctx);
+	if (r < 0)
+		return 0;
+
+	cnt = libusb_get_device_list(ctx, &list);
+
+	for (i = 0; i < cnt; i++) {
+		libusb_get_device_descriptor(list[i], &dd);
+
+		if (find_known_device(dd.idVendor, dd.idProduct))
+			device_count++;
+	}
+
+	libusb_free_device_list(list, 1);
+
+	libusb_exit(ctx);
+
+	return device_count;
+}
+
+const char *fl2k_get_device_name(uint32_t index)
+{
+	int i,r;
+	libusb_context *ctx;
+	libusb_device **list;
+	struct libusb_device_descriptor dd;
+	fl2k_dongle_t *device = NULL;
+	uint32_t device_count = 0;
+	ssize_t cnt;
+
+	r = libusb_init(&ctx);
+	if (r < 0)
+		return "";
+
+	cnt = libusb_get_device_list(ctx, &list);
+
+	for (i = 0; i < cnt; i++) {
+		libusb_get_device_descriptor(list[i], &dd);
+
+		device = find_known_device(dd.idVendor, dd.idProduct);
+
+		if (device) {
+			device_count++;
+
+			if (index == device_count - 1)
+				break;
+		}
+	}
+
+	libusb_free_device_list(list, 1);
+
+	libusb_exit(ctx);
+
+	if (device)
+		return device->name;
+	else
+		return "";
+}
+
+int fl2k_open(fl2k_dev_t **out_dev, uint32_t index)
+{
+	int r;
+	int i;
+	libusb_device **list;
+	fl2k_dev_t *dev = NULL;
+	libusb_device *device = NULL;
+	uint32_t device_count = 0;
+	struct libusb_device_descriptor dd;
+	uint8_t reg;
+	ssize_t cnt;
+
+	dev = malloc(sizeof(fl2k_dev_t));
+	if (NULL == dev)
+		return -ENOMEM;
+
+	memset(dev, 0, sizeof(fl2k_dev_t));
+
+	r = libusb_init(&dev->ctx);
+	if(r < 0){
+		free(dev);
+		return -1;
+	}
+
+	libusb_set_debug(dev->ctx, 3);
+
+	dev->dev_lost = 1;
+
+	cnt = libusb_get_device_list(dev->ctx, &list);
+
+	for (i = 0; i < cnt; i++) {
+		device = list[i];
+
+		libusb_get_device_descriptor(list[i], &dd);
+
+		if (find_known_device(dd.idVendor, dd.idProduct)) {
+			device_count++;
+		}
+
+		if (index == device_count - 1)
+			break;
+
+		device = NULL;
+	}
+
+	if (!device) {
+		r = -1;
+		goto err;
+	}
+
+	r = libusb_open(device, &dev->devh);
+	libusb_free_device_list(list, 1);
+	if (r < 0) {
+		fprintf(stderr, "usb_open error %d\n", r);
+		if(r == LIBUSB_ERROR_ACCESS)
+			fprintf(stderr, "Please fix the device permissions, e.g. "
+			"by installing the udev rules file\n");
+		goto err;
+	}
+
+	/* If the adapter has an SPI flash for the Windows driver, we
+	 * need to detach the USB mass storage driver first in order to
+	 * open the device */
+	if (libusb_kernel_driver_active(dev->devh, 3) == 1) {
+		fprintf(stderr, "Kernel mass storage driver is attached, "
+				"detaching driver. This may take more than"
+				" 10 seconds!\n");
+		r = libusb_detach_kernel_driver(dev->devh, 3);
+		if (r < 0) {
+			fprintf(stderr, "Failed to detach mass storage "
+					"driver: %d\n", r);
+			goto err;
+		}
+	}
+
+	r = libusb_claim_interface(dev->devh, 0);
+	if (r < 0) {
+		fprintf(stderr, "usb_claim_interface 0 error %d\n", r);
+		goto err;
+	}
+	r = libusb_claim_interface(dev->devh, 1);
+
+	if (r < 0) {
+		fprintf(stderr, "usb_claim_interface 1 error %d\n", r);
+		goto err;
+	}
+
+	r = fl2k_init_device(dev);
+	if (r < 0)
+		goto err;
+
+	dev->dev_lost = 0;
+
+found:
+	*out_dev = dev;
+
+	return 0;
+err:
+	if (dev) {
+		if (dev->ctx)
+			libusb_exit(dev->ctx);
+
+		free(dev);
+	}
+
+	return r;
+}
+
+int fl2k_close(fl2k_dev_t *dev)
+{
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	if(!dev->dev_lost) {
+		/* block until all async operations have been completed (if any) */
+		while (FL2K_INACTIVE != dev->async_status) {
+#ifdef _WIN32
+			Sleep(1);
+#else
+			usleep(1000);
+#endif
+		}
+
+		fl2k_deinit_device(dev);
+	}
+
+	libusb_release_interface(dev->devh, 0);
+	libusb_close(dev->devh);
+	libusb_exit(dev->ctx);
+
+	free(dev);
+
+	return 0;
+}
+
+static struct libusb_transfer *fl2k_get_next_xfer(fl2k_dev_t *dev,
+					          fl2k_buf_state_t state)
+{
+	unsigned int i;
+	int next_buf = -1;
+	uint64_t next_seq = 0;
+	fl2k_xfer_info_t *xfer_info;
+
+	for (i = 0; i < dev->xfer_buf_num; i++) {
+		xfer_info = (fl2k_xfer_info_t *)dev->xfer[i]->user_data;
+		if (!xfer_info)
+			continue;
+
+		if (xfer_info->state == state) {
+			if (state == BUF_EMPTY) {
+				return dev->xfer[i];
+			} else if ((xfer_info->seq < next_seq) || next_buf < 0) {
+				next_seq = xfer_info->seq;
+				next_buf = i;
+			}
+		}
+	}
+
+	if ((state == BUF_FILLED) && (next_buf >= 0))
+		return dev->xfer[next_buf];
+	else
+		return NULL;
+}
+
+static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
+{
+	fl2k_xfer_info_t *xfer_info = (fl2k_xfer_info_t *)xfer->user_data;
+	fl2k_xfer_info_t *next_xfer_info;
+	fl2k_dev_t *dev = (fl2k_dev_t *)xfer_info->dev;
+	struct libusb_transfer *next_xfer = NULL;
+
+	if (LIBUSB_TRANSFER_COMPLETED == xfer->status) {
+		/* resubmit transfer */
+		if (FL2K_RUNNING == dev->async_status) {
+			/* get next transfer */
+			next_xfer = fl2k_get_next_xfer(dev, BUF_FILLED);
+
+			if (next_xfer) {
+				next_xfer_info = (fl2k_xfer_info_t *) next_xfer->user_data;
+
+				/* Submit next filled transfer */
+				next_xfer_info->state = BUF_SUBMITTED;
+				libusb_submit_transfer(next_xfer);
+
+				xfer_info->state = BUF_EMPTY;
+				pthread_cond_signal(&dev->buf_cond);
+			} else {
+				/* We need to re-submit the transfer
+				 * in any case, as otherwise the device
+				 * stops to output data and hangs
+				 * (happens only in the hacked 'gapless'
+				 * mode without HSYNC and VSYNC)  */
+				libusb_submit_transfer(xfer);
+				pthread_cond_signal(&dev->buf_cond);
+				dev->underflow_cnt++;
+			}
+		}
+	} else if (LIBUSB_TRANSFER_CANCELLED != xfer->status) {
+			dev->dev_lost = 1;
+			fl2k_stop_tx(dev);
+			pthread_cond_signal(&dev->buf_cond);
+			fprintf(stderr, "cb transfer status: %d, "
+				"canceling...\n", xfer->status);
+	}
+}
+
+static int fl2k_alloc_submit_transfers(fl2k_dev_t *dev)
+{
+	unsigned int i;
+	int r = 0;
+
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	dev->xfer = malloc(dev->xfer_buf_num * sizeof(struct libusb_transfer *));
+
+	for (i = 0; i < dev->xfer_buf_num; ++i)
+		dev->xfer[i] = libusb_alloc_transfer(0);
+
+	dev->xfer_buf = malloc(dev->xfer_buf_num * sizeof(unsigned char *));
+	dev->xfer_info = malloc(dev->xfer_buf_num * sizeof(fl2k_xfer_info_t));
+
+#if defined (__linux__) && LIBUSB_API_VERSION >= 0x01000105
+	fprintf(stderr, "Using %d zero-copy buffers\n", dev->xfer_buf_num);
+
+	dev->use_zerocopy = 1;
+	for (i = 0; i < dev->xfer_buf_num; ++i) {
+		dev->xfer_buf[i] = libusb_dev_mem_alloc(dev->devh, dev->xfer_buf_len);
+
+		if (!dev->xfer_buf[i]) {
+			fprintf(stderr, "Failed to allocate zerocopy"
+					" buffer for transfer %d\n",
+					 i);
+
+			// TODO: free dev_mem buffers again
+			dev->use_zerocopy = 0;
+			break;
+		}
+	}
+#endif
+
+	if (!dev->use_zerocopy) {
+		for (i = 0; i < dev->xfer_buf_num; ++i) {
+			dev->xfer_buf[i] = malloc(dev->xfer_buf_len);
+
+			if (!dev->xfer_buf[i])
+				return FL2K_ERROR_NO_MEM;
+		}
+	}
+
+	/* fill transfers */
+	for (i = 0; i < dev->xfer_buf_num; ++i) {
+		libusb_fill_bulk_transfer(dev->xfer[i],
+					  dev->devh,
+					  0x01,
+					  dev->xfer_buf[i],
+					  dev->xfer_buf_len,
+					  _libusb_callback,
+					  &dev->xfer_info[i],
+					  0);
+
+		dev->xfer_info[i].dev = dev;
+		dev->xfer_info[i].state = BUF_EMPTY;
+
+		/* if we allocate the memory through the Kernel, it is
+		 * already cleared */
+		if (!dev->use_zerocopy)
+			memset(dev->xfer_buf[i], 0, dev->xfer_buf_len);
+	}
+
+	/* submit transfers */
+	for (i = 0; i < dev->xfer_num; ++i) {
+		r = libusb_submit_transfer(dev->xfer[i]);
+		dev->xfer_info[i].state = BUF_SUBMITTED;
+
+		if (r < 0) {
+			fprintf(stderr, "Failed to submit transfer %i\n"
+					"Please increase your allowed " 
+					"usbfs buffer size with the "
+					"following command:\n"
+					"echo 0 > /sys/module/usbcore"
+					"/parameters/usbfs_memory_mb\n", i);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int _fl2k_free_async_buffers(fl2k_dev_t *dev)
+{
+	unsigned int i;
+
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	if (dev->xfer) {
+		for (i = 0; i < dev->xfer_buf_num; ++i) {
+			if (dev->xfer[i]) {
+				libusb_free_transfer(dev->xfer[i]);
+			}
+		}
+
+		free(dev->xfer);
+		dev->xfer = NULL;
+	}
+
+	if (dev->xfer_buf) {
+		for (i = 0; i < dev->xfer_buf_num; ++i) {
+			if (dev->xfer_buf[i]) {
+				if (dev->use_zerocopy) {
+					libusb_dev_mem_free(dev->devh,
+							    dev->xfer_buf[i],
+							    dev->xfer_buf_len);
+				} else {
+					free(dev->xfer_buf[i]);
+				}
+			}
+		}
+
+		free(dev->xfer_buf);
+		dev->xfer_buf = NULL;
+	}
+
+	return 0;
+}
+
+static void *fl2k_usb_worker(void *arg)
+{
+	fl2k_dev_t *dev = (fl2k_dev_t *)arg;
+	struct timeval tv = { 1, 0 };
+	struct timeval zerotv = { 0, 0 };
+	enum fl2k_async_status next_status = FL2K_INACTIVE;
+	int r = 0;
+	unsigned int i;
+
+	while (FL2K_RUNNING == dev->async_status) {
+		r = libusb_handle_events_timeout_completed(dev->ctx, &tv,
+							   &dev->async_cancel);
+	}
+
+	while (FL2K_INACTIVE != dev->async_status) {
+		r = libusb_handle_events_timeout_completed(dev->ctx, &tv,
+							   &dev->async_cancel);
+		if (r < 0) {
+			/*fprintf(stderr, "handle_events returned: %d\n", r);*/
+			if (r == LIBUSB_ERROR_INTERRUPTED) /* stray signal */
+				continue;
+			break;
+		}
+
+		if (FL2K_CANCELING == dev->async_status) {
+			next_status = FL2K_INACTIVE;
+
+			if (!dev->xfer)
+				break;
+
+			for (i = 0; i < dev->xfer_buf_num; ++i) {
+				if (!dev->xfer[i])
+					continue;
+
+				if (LIBUSB_TRANSFER_CANCELLED !=
+						dev->xfer[i]->status) {
+					r = libusb_cancel_transfer(dev->xfer[i]);
+					/* handle events after canceling
+					 * to allow transfer status to
+					 * propagate */
+					libusb_handle_events_timeout_completed(dev->ctx,
+									       &zerotv, NULL);
+					if (r < 0)
+						continue;
+
+					next_status = FL2K_CANCELING;
+				}
+			}
+
+			if (dev->dev_lost || FL2K_INACTIVE == next_status) {
+				/* handle any events that still need to
+				 * be handled before exiting after we
+				 * just cancelled all transfers */
+				libusb_handle_events_timeout_completed(dev->ctx,
+								       &zerotv, NULL);
+				break;
+			}
+		}
+	}
+
+	_fl2k_free_async_buffers(dev);
+	dev->async_status = next_status;
+
+	pthread_exit(NULL);
+}
+
+/* Buffer format conversion functions for R, G, B DACs */
+static inline void fl2k_convert_r(char *out,
+				  char *in,
+				  uint32_t len,
+				  uint8_t offset)
+{
+	unsigned int i, j = 0;
+
+	if (!in || !out)
+		return;
+
+	for (i = 0; i < len; i += 24) {
+		out[i+ 6] = in[j++] + offset;
+		out[i+ 1] = in[j++] + offset;
+		out[i+12] = in[j++] + offset;
+		out[i+15] = in[j++] + offset;
+		out[i+10] = in[j++] + offset;
+		out[i+21] = in[j++] + offset;
+		out[i+16] = in[j++] + offset;
+		out[i+19] = in[j++] + offset;
+	}
+}
+
+static inline void fl2k_convert_g(char *out,
+				  char *in,
+				  uint32_t len,
+				  uint8_t offset)
+{
+	unsigned int i, j = 0;
+
+	if (!in || !out)
+		return;
+
+	for (i = 0; i < len; i += 24) {
+		out[i+ 5] = in[j++] + offset;
+		out[i+ 0] = in[j++] + offset;
+		out[i+ 3] = in[j++] + offset;
+		out[i+14] = in[j++] + offset;
+		out[i+ 9] = in[j++] + offset;
+		out[i+20] = in[j++] + offset;
+		out[i+23] = in[j++] + offset;
+		out[i+18] = in[j++] + offset;
+	}
+}
+
+static inline void fl2k_convert_b(char *out,
+				  char *in,
+				  uint32_t len,
+				  uint8_t offset)
+{
+	unsigned int i, j = 0;
+
+	if (!in || !out)
+		return;
+
+	for (i = 0; i < len; i += 24) {
+		out[i+ 4] = in[j++] + offset;
+		out[i+ 7] = in[j++] + offset;
+		out[i+ 2] = in[j++] + offset;
+		out[i+13] = in[j++] + offset;
+		out[i+ 8] = in[j++] + offset;
+		out[i+11] = in[j++] + offset;
+		out[i+22] = in[j++] + offset;
+		out[i+17] = in[j++] + offset;
+	}
+}
+
+static void *fl2k_sample_worker(void *arg)
+{
+	int r = 0;
+	unsigned int i, j;
+	fl2k_dev_t *dev = (fl2k_dev_t *)arg;
+	fl2k_xfer_info_t *xfer_info = NULL;
+	struct libusb_transfer *xfer = NULL;
+	char *out_buf = NULL;
+	fl2k_data_info_t data_info;
+	uint32_t underflows = 0;
+	uint64_t buf_cnt = 0;
+
+	while (FL2K_RUNNING == dev->async_status) {
+		memset(&data_info, 0, sizeof(fl2k_data_info_t));
+
+		data_info.len = FL2K_BUF_LEN;
+		data_info.underflow_cnt = dev->underflow_cnt;
+		data_info.ctx = dev->cb_ctx;
+
+		if (dev->underflow_cnt > underflows) {
+			fprintf(stderr, "Underflow! Skipped %d buffers\n",
+					dev->underflow_cnt - underflows);
+			underflows = dev->underflow_cnt;
+		}
+
+		/* call application callback to get samples */
+		if (dev->cb)
+			dev->cb(&data_info);
+
+		xfer = fl2k_get_next_xfer(dev, BUF_EMPTY);
+
+		if (!xfer) {
+			pthread_cond_wait(&dev->buf_cond, &dev->buf_mutex);
+			/* in the meantime, the device might be gone */
+			if (FL2K_RUNNING != dev->async_status)
+				break;
+
+			xfer = fl2k_get_next_xfer(dev, BUF_EMPTY);
+			if (!xfer) {
+				fprintf(stderr, "no free transfer, skipping"
+						" input buffer\n");
+				continue;
+			}
+		}
+
+		/* We have an empty USB transfer buffer */
+		xfer_info = (fl2k_xfer_info_t *)xfer->user_data;
+		out_buf = (char *)xfer->buffer;
+
+		/* Re-arrange and copy bytes in buffer for DACs */
+		fl2k_convert_r(out_buf, data_info.r_buf, dev->xfer_buf_len,
+			       data_info.sampletype_signed ? 128 : 0);
+
+		fl2k_convert_g(out_buf, data_info.g_buf, dev->xfer_buf_len,
+			       data_info.sampletype_signed ? 128 : 0);
+
+		fl2k_convert_b(out_buf, data_info.b_buf, dev->xfer_buf_len,
+			       data_info.sampletype_signed ? 128 : 0);
+
+		xfer_info->seq = buf_cnt++;
+		xfer_info->state = BUF_FILLED;
+	}
+
+	/* notify application if we've lost the device */
+	if (dev->dev_lost && dev->cb) {
+		data_info.device_error = 1;
+		dev->cb(&data_info);
+		fl2k_stop_tx(dev);
+	}
+
+	pthread_exit(NULL);
+}
+
+
+int fl2k_start_tx(fl2k_dev_t *dev, fl2k_tx_cb_t cb, void *ctx,
+		  uint32_t buf_num)
+{
+	int r = 0;
+	int i;
+	pthread_attr_t attr;
+
+	if (!dev || !cb)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	dev->async_status = FL2K_RUNNING;
+	dev->async_cancel = 0;
+
+	dev->cb = cb;
+	dev->cb_ctx = ctx;
+
+	if (buf_num > 0)
+		dev->xfer_num = buf_num;
+	else
+		dev->xfer_num = DEFAULT_BUF_NUMBER;
+
+	/* have two spare buffers that can be filled while the
+	 * others are submitted */
+	dev->xfer_buf_num = dev->xfer_num + 2;
+	dev->xfer_buf_len = FL2K_XFER_LEN;
+
+	r = fl2k_alloc_submit_transfers(dev);
+	if (r < 0)
+		goto cleanup;
+
+	pthread_mutex_init(&dev->buf_mutex, NULL);
+	pthread_cond_init(&dev->buf_cond, NULL);
+	pthread_attr_init(&attr);
+
+	r = pthread_create(&dev->usb_worker_thread, &attr,
+			   fl2k_usb_worker, (void *)dev);
+	if (r < 0) {
+		fprintf(stderr, "Error spawning USB worker thread!\n");
+		goto cleanup;
+	}
+
+	r = pthread_create(&dev->sample_worker_thread, &attr,
+			   fl2k_sample_worker, (void *)dev);
+	if (r < 0) {
+		fprintf(stderr, "Error spawning sample worker thread!\n");
+		goto cleanup;
+	}
+
+	pthread_attr_destroy(&attr);
+
+	return 0;
+
+cleanup:
+	_fl2k_free_async_buffers(dev);
+	return FL2K_ERROR_BUSY;
+
+}
+
+int fl2k_stop_tx(fl2k_dev_t *dev)
+{
+	if (!dev)
+		return FL2K_ERROR_INVALID_PARAM;
+
+	/* if streaming, try to cancel gracefully */
+	if (FL2K_RUNNING == dev->async_status) {
+		dev->async_status = FL2K_CANCELING;
+		dev->async_cancel = 1;
+		return 0;
+	/* if called while in pending state, change the state forcefully */
+	} else if (FL2K_INACTIVE != dev->async_status) {
+		dev->async_status = FL2K_INACTIVE;
+		return 0;
+	}
+
+	return FL2K_ERROR_BUSY;
+}
-- 
cgit v1.2.3