WIP: add support for palette (single channel) mode
This seems to work fine with sample rates up to 100 MS/s, but with higher rates there is an unresolved issue and after a couple of minutes all transfers stop...
This commit is contained in:
parent
197f421c98
commit
3b75777cf9
|
@ -39,6 +39,7 @@ enum fl2k_error {
|
||||||
FL2K_ERROR_BUSY = -6,
|
FL2K_ERROR_BUSY = -6,
|
||||||
FL2K_ERROR_TIMEOUT = -7,
|
FL2K_ERROR_TIMEOUT = -7,
|
||||||
FL2K_ERROR_NO_MEM = -11,
|
FL2K_ERROR_NO_MEM = -11,
|
||||||
|
FL2K_ERROR_OTHER = -9999,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct fl2k_data_info {
|
typedef struct fl2k_data_info {
|
||||||
|
@ -51,11 +52,17 @@ typedef struct fl2k_data_info {
|
||||||
|
|
||||||
/* filled in by application */
|
/* filled in by application */
|
||||||
int sampletype_signed; /* are samples signed or unsigned? */
|
int sampletype_signed; /* are samples signed or unsigned? */
|
||||||
char *r_buf; /* pointer to red buffer */
|
char *r_buf; /* pointer to red buffer (or singlechan buffer) */
|
||||||
char *g_buf; /* pointer to green buffer */
|
char *g_buf; /* pointer to green buffer */
|
||||||
char *b_buf; /* pointer to blue buffer */
|
char *b_buf; /* pointer to blue buffer */
|
||||||
} fl2k_data_info_t;
|
} fl2k_data_info_t;
|
||||||
|
|
||||||
|
typedef enum fl2k_mode {
|
||||||
|
FL2K_MODE_INVALID,
|
||||||
|
FL2K_MODE_SINGLECHAN,
|
||||||
|
FL2K_MODE_MULTICHAN
|
||||||
|
} fl2k_mode_t;
|
||||||
|
|
||||||
typedef struct fl2k_dev fl2k_dev_t;
|
typedef struct fl2k_dev fl2k_dev_t;
|
||||||
|
|
||||||
/** The transfer length was chosen by the following criteria:
|
/** The transfer length was chosen by the following criteria:
|
||||||
|
@ -69,6 +76,8 @@ typedef struct fl2k_dev fl2k_dev_t;
|
||||||
#define FL2K_BUF_LEN (1280 * 1024)
|
#define FL2K_BUF_LEN (1280 * 1024)
|
||||||
#define FL2K_XFER_LEN (FL2K_BUF_LEN * 3)
|
#define FL2K_XFER_LEN (FL2K_BUF_LEN * 3)
|
||||||
|
|
||||||
|
#define FL2K_PALETTE_SIZE 256
|
||||||
|
|
||||||
FL2K_API uint32_t fl2k_get_device_count(void);
|
FL2K_API uint32_t fl2k_get_device_count(void);
|
||||||
|
|
||||||
FL2K_API const char* fl2k_get_device_name(uint32_t index);
|
FL2K_API const char* fl2k_get_device_name(uint32_t index);
|
||||||
|
@ -101,6 +110,8 @@ FL2K_API uint32_t fl2k_get_sample_rate(fl2k_dev_t *dev);
|
||||||
|
|
||||||
typedef void(*fl2k_tx_cb_t)(fl2k_data_info_t *data_info);
|
typedef void(*fl2k_tx_cb_t)(fl2k_data_info_t *data_info);
|
||||||
|
|
||||||
|
FL2K_API int fl2k_set_mode(fl2k_dev_t *dev, fl2k_mode_t mode);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Starts the tx thread. This function will block until
|
* Starts the tx thread. This function will block until
|
||||||
* it is being canceled using fl2k_stop_tx()
|
* it is being canceled using fl2k_stop_tx()
|
||||||
|
|
|
@ -89,6 +89,8 @@ struct fl2k_dev {
|
||||||
enum fl2k_async_status async_status;
|
enum fl2k_async_status async_status;
|
||||||
int async_cancel;
|
int async_cancel;
|
||||||
|
|
||||||
|
fl2k_mode_t mode;
|
||||||
|
uint8_t enabled_chans;
|
||||||
int use_zerocopy;
|
int use_zerocopy;
|
||||||
int terminate;
|
int terminate;
|
||||||
|
|
||||||
|
@ -123,9 +125,13 @@ static fl2k_dongle_t known_devices[] = {
|
||||||
#define CTRL_TIMEOUT 300
|
#define CTRL_TIMEOUT 300
|
||||||
#define BULK_TIMEOUT 0
|
#define BULK_TIMEOUT 0
|
||||||
|
|
||||||
|
#define R_EN (1 << 0)
|
||||||
|
#define G_EN (1 << 1)
|
||||||
|
#define B_EN (1 << 2)
|
||||||
|
|
||||||
static int fl2k_read_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t *val)
|
static int fl2k_read_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t *val)
|
||||||
{
|
{
|
||||||
int r;
|
uint32_t r;
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
|
|
||||||
if (!dev || !val)
|
if (!dev || !val)
|
||||||
|
@ -134,16 +140,19 @@ static int fl2k_read_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t *val)
|
||||||
r = libusb_control_transfer(dev->devh, CTRL_IN, 0x40,
|
r = libusb_control_transfer(dev->devh, CTRL_IN, 0x40,
|
||||||
0, reg, data, 4, CTRL_TIMEOUT);
|
0, reg, data, 4, CTRL_TIMEOUT);
|
||||||
|
|
||||||
if (r < 4)
|
if (r < sizeof(data)) {
|
||||||
fprintf(stderr, "Error, short read from register!\n");
|
fprintf(stderr, "Error, short read from register!\n");
|
||||||
|
return FL2K_ERROR_OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
*val = (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
|
*val = (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | data[0];
|
||||||
|
|
||||||
return r;
|
return FL2K_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fl2k_write_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t val)
|
static int fl2k_write_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t val)
|
||||||
{
|
{
|
||||||
|
uint32_t r;
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
|
|
||||||
if (!dev)
|
if (!dev)
|
||||||
|
@ -154,15 +163,72 @@ static int fl2k_write_reg(fl2k_dev_t *dev, uint16_t reg, uint32_t val)
|
||||||
data[2] = (val >> 16) & 0xff;
|
data[2] = (val >> 16) & 0xff;
|
||||||
data[3] = (val >> 24) & 0xff;
|
data[3] = (val >> 24) & 0xff;
|
||||||
|
|
||||||
return libusb_control_transfer(dev->devh, CTRL_OUT, 0x41,
|
r = libusb_control_transfer(dev->devh, CTRL_OUT, 0x41,
|
||||||
0, reg, data, 4, CTRL_TIMEOUT);
|
0, reg, data, 4, CTRL_TIMEOUT);
|
||||||
|
|
||||||
|
if (r != sizeof(data))
|
||||||
|
return FL2K_ERROR_OTHER;
|
||||||
|
|
||||||
|
return FL2K_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fl2k_load_custom_palette(fl2k_dev_t *dev, uint32_t *palette)
|
||||||
|
{
|
||||||
|
int i, r;
|
||||||
|
uint32_t reg = 0;
|
||||||
|
|
||||||
|
if (!dev)
|
||||||
|
return FL2K_ERROR_INVALID_PARAM;
|
||||||
|
|
||||||
|
/* write palette RAM */
|
||||||
|
for (i = 0; i < FL2K_PALETTE_SIZE; i++) {
|
||||||
|
r = fl2k_write_reg(dev, 0x805c, palette[i] << 8 | (i & 0xff));
|
||||||
|
if (r < 0)
|
||||||
|
fprintf(stderr, "Error writing palette entry %d!\n", i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* verify palette RAM */
|
||||||
|
for (i = 0; i < FL2K_PALETTE_SIZE; i++) {
|
||||||
|
/* for whatever reason there's an address offset of 1
|
||||||
|
* when reading */
|
||||||
|
r = fl2k_write_reg(dev, 0x8060, (i+1) & 0xff );
|
||||||
|
r |= fl2k_read_reg(dev, 0x805c, ®);
|
||||||
|
|
||||||
|
if (r < 0)
|
||||||
|
return FL2K_ERROR_OTHER;
|
||||||
|
|
||||||
|
if (reg != palette[i])
|
||||||
|
fprintf(stderr, "Palette entry %d mismatch: 0x%06x, "
|
||||||
|
"expected 0x%06x\n", i, reg, palette[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FL2K_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fl2k_set_enabled_channels(fl2k_dev_t *dev, uint8_t chan_mask)
|
||||||
|
{
|
||||||
|
uint32_t palette[FL2K_PALETTE_SIZE];
|
||||||
|
uint8_t val;
|
||||||
|
|
||||||
|
/* generate linear 8 bit palette for desired DAC channels */
|
||||||
|
for (int i = 0; i < FL2K_PALETTE_SIZE; i++) {
|
||||||
|
val = i & 0xff;
|
||||||
|
palette[i] = chan_mask & R_EN ? (i << 16) : 0;
|
||||||
|
palette[i] |= chan_mask & G_EN ? (i << 8) : 0;
|
||||||
|
palette[i] |= chan_mask & B_EN ? i : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fl2k_load_custom_palette(dev, palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
int fl2k_init_device(fl2k_dev_t *dev)
|
int fl2k_init_device(fl2k_dev_t *dev)
|
||||||
{
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
if (!dev)
|
if (!dev)
|
||||||
return FL2K_ERROR_INVALID_PARAM;
|
return FL2K_ERROR_INVALID_PARAM;
|
||||||
|
|
||||||
|
|
||||||
/* initialization */
|
/* initialization */
|
||||||
fl2k_write_reg(dev, 0x8020, 0xdf0000cc);
|
fl2k_write_reg(dev, 0x8020, 0xdf0000cc);
|
||||||
|
|
||||||
|
@ -559,6 +625,7 @@ static void LIBUSB_CALL _libusb_callback(struct libusb_transfer *xfer)
|
||||||
r = libusb_submit_transfer(xfer);
|
r = libusb_submit_transfer(xfer);
|
||||||
pthread_cond_signal(&dev->buf_cond);
|
pthread_cond_signal(&dev->buf_cond);
|
||||||
dev->underflow_cnt++;
|
dev->underflow_cnt++;
|
||||||
|
fprintf(stderr, "Resubmitted transfer!\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -798,7 +865,7 @@ static void *fl2k_usb_worker(void *arg)
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buffer format conversion functions for R, G, B DACs */
|
/* Buffer format conversion functions for R, G, B DACs (multichannel mode) */
|
||||||
static inline void fl2k_convert_r(char *out,
|
static inline void fl2k_convert_r(char *out,
|
||||||
char *in,
|
char *in,
|
||||||
uint32_t len,
|
uint32_t len,
|
||||||
|
@ -865,24 +932,59 @@ static inline void fl2k_convert_b(char *out,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Conversion function for single channel (256-color) mode */
|
||||||
|
static inline void fl2k_convert_singlechan(char *out,
|
||||||
|
char *in,
|
||||||
|
uint32_t len,
|
||||||
|
uint8_t offset)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
if (!in || !out)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* swap 32 bit words */
|
||||||
|
for (i = 0; i < len; i += 8) {
|
||||||
|
out[i+0] = in[i+4] + offset;
|
||||||
|
out[i+1] = in[i+5] + offset;
|
||||||
|
out[i+2] = in[i+6] + offset;
|
||||||
|
out[i+3] = in[i+7] + offset;
|
||||||
|
out[i+4] = in[i+0] + offset;
|
||||||
|
out[i+5] = in[i+1] + offset;
|
||||||
|
out[i+6] = in[i+2] + offset;
|
||||||
|
out[i+7] = in[i+3] + offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void *fl2k_sample_worker(void *arg)
|
static void *fl2k_sample_worker(void *arg)
|
||||||
{
|
{
|
||||||
int r = 0;
|
int r = 0;
|
||||||
unsigned int i, j;
|
unsigned int i, callback_cnt;
|
||||||
fl2k_dev_t *dev = (fl2k_dev_t *)arg;
|
fl2k_dev_t *dev = (fl2k_dev_t *)arg;
|
||||||
fl2k_xfer_info_t *xfer_info = NULL;
|
fl2k_xfer_info_t *xfer_info = NULL;
|
||||||
struct libusb_transfer *xfer = NULL;
|
struct libusb_transfer *xfer = NULL;
|
||||||
char *out_buf = NULL;
|
char *out_buf = NULL;
|
||||||
fl2k_data_info_t data_info;
|
fl2k_data_info_t data_info[3];
|
||||||
uint32_t underflows = 0;
|
uint32_t underflows = 0;
|
||||||
uint64_t buf_cnt = 0;
|
uint64_t buf_cnt = 0;
|
||||||
|
|
||||||
while (FL2K_RUNNING == dev->async_status) {
|
while (FL2K_RUNNING == dev->async_status) {
|
||||||
memset(&data_info, 0, sizeof(fl2k_data_info_t));
|
/* for backward API compatibility, the buffer size should
|
||||||
|
* not change between single- and multichannel mode, thus
|
||||||
|
* we call the callback 3 times for singlechannel mode to
|
||||||
|
* get the required amount of data */
|
||||||
|
callback_cnt = (dev->mode == FL2K_MODE_SINGLECHAN) ? 3 : 1;
|
||||||
|
|
||||||
data_info.len = FL2K_BUF_LEN;
|
for (i = 0; i < callback_cnt; i++) {
|
||||||
data_info.underflow_cnt = dev->underflow_cnt;
|
memset(&data_info[i], 0, sizeof(fl2k_data_info_t));
|
||||||
data_info.ctx = dev->cb_ctx;
|
data_info[i].len = FL2K_BUF_LEN;
|
||||||
|
data_info[i].underflow_cnt = dev->underflow_cnt;
|
||||||
|
data_info[i].ctx = dev->cb_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* call application callback to get samples */
|
||||||
|
if (dev->cb)
|
||||||
|
dev->cb(&data_info[0]);
|
||||||
|
|
||||||
if (dev->underflow_cnt > underflows) {
|
if (dev->underflow_cnt > underflows) {
|
||||||
fprintf(stderr, "Underflow! Skipped %d buffers\n",
|
fprintf(stderr, "Underflow! Skipped %d buffers\n",
|
||||||
|
@ -890,10 +992,6 @@ static void *fl2k_sample_worker(void *arg)
|
||||||
underflows = dev->underflow_cnt;
|
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);
|
xfer = fl2k_get_next_xfer(dev, BUF_EMPTY);
|
||||||
|
|
||||||
if (!xfer) {
|
if (!xfer) {
|
||||||
|
@ -914,15 +1012,29 @@ static void *fl2k_sample_worker(void *arg)
|
||||||
xfer_info = (fl2k_xfer_info_t *)xfer->user_data;
|
xfer_info = (fl2k_xfer_info_t *)xfer->user_data;
|
||||||
out_buf = (char *)xfer->buffer;
|
out_buf = (char *)xfer->buffer;
|
||||||
|
|
||||||
/* Re-arrange and copy bytes in buffer for DACs */
|
if (dev->mode == FL2K_MODE_SINGLECHAN) {
|
||||||
fl2k_convert_r(out_buf, data_info.r_buf, dev->xfer_buf_len,
|
/* Convert a buffer to the singlechannel DAC format */
|
||||||
data_info.sampletype_signed ? 128 : 0);
|
for (i = 0; i < callback_cnt; i++) {
|
||||||
|
fl2k_convert_singlechan(&out_buf[i*FL2K_BUF_LEN], data_info[i].r_buf,
|
||||||
|
FL2K_BUF_LEN,
|
||||||
|
data_info[i].sampletype_signed ? 128 : 0);
|
||||||
|
|
||||||
fl2k_convert_g(out_buf, data_info.g_buf, dev->xfer_buf_len,
|
/* We need to fetch two more buffers to get
|
||||||
data_info.sampletype_signed ? 128 : 0);
|
* the same amount of data as in multichannel mode */
|
||||||
|
if (dev->cb && (i < 2))
|
||||||
|
dev->cb(&data_info[i+1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Re-arrange and copy bytes in buffer for DACs */
|
||||||
|
fl2k_convert_r(out_buf, data_info[0].r_buf, dev->xfer_buf_len,
|
||||||
|
data_info[0].sampletype_signed ? 128 : 0);
|
||||||
|
|
||||||
fl2k_convert_b(out_buf, data_info.b_buf, dev->xfer_buf_len,
|
fl2k_convert_g(out_buf, data_info[0].g_buf, dev->xfer_buf_len,
|
||||||
data_info.sampletype_signed ? 128 : 0);
|
data_info[0].sampletype_signed ? 128 : 0);
|
||||||
|
|
||||||
|
fl2k_convert_b(out_buf, data_info[0].b_buf, dev->xfer_buf_len,
|
||||||
|
data_info[0].sampletype_signed ? 128 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
xfer_info->seq = buf_cnt++;
|
xfer_info->seq = buf_cnt++;
|
||||||
xfer_info->state = BUF_FILLED;
|
xfer_info->state = BUF_FILLED;
|
||||||
|
@ -930,13 +1042,55 @@ static void *fl2k_sample_worker(void *arg)
|
||||||
|
|
||||||
/* notify application if we've lost the device */
|
/* notify application if we've lost the device */
|
||||||
if (dev->dev_lost && dev->cb) {
|
if (dev->dev_lost && dev->cb) {
|
||||||
data_info.device_error = 1;
|
data_info[0].device_error = 1;
|
||||||
dev->cb(&data_info);
|
dev->cb(&data_info[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_exit(NULL);
|
pthread_exit(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fl2k_set_mode(fl2k_dev_t *dev, fl2k_mode_t mode)
|
||||||
|
{
|
||||||
|
uint32_t reg;
|
||||||
|
int r = 0;
|
||||||
|
|
||||||
|
if (!dev)
|
||||||
|
return FL2K_ERROR_INVALID_PARAM;
|
||||||
|
|
||||||
|
if (FL2K_RUNNING == dev->async_status)
|
||||||
|
return FL2K_ERROR_BUSY;
|
||||||
|
|
||||||
|
if (dev->mode == mode)
|
||||||
|
return FL2K_SUCCESS;
|
||||||
|
|
||||||
|
r = fl2k_read_reg(dev, 0x8004, ®);
|
||||||
|
|
||||||
|
if (r < 0)
|
||||||
|
return FL2K_ERROR_OTHER;
|
||||||
|
|
||||||
|
if (mode == FL2K_MODE_SINGLECHAN) {
|
||||||
|
/* enable 256 color palette mode */
|
||||||
|
reg |= (1 << 25) | (1 << 26);
|
||||||
|
r |= fl2k_set_enabled_channels(dev, R_EN);
|
||||||
|
} else if (mode == FL2K_MODE_MULTICHAN) {
|
||||||
|
reg &= ~((1 << 25) | (1 << 26));
|
||||||
|
} else {
|
||||||
|
return FL2K_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = fl2k_write_reg(dev, 0x8004, reg );
|
||||||
|
|
||||||
|
if (!r)
|
||||||
|
dev->mode = mode;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
fl2k_mode_t fl2k_get_mode(fl2k_dev_t *dev)
|
||||||
|
{
|
||||||
|
if (dev)
|
||||||
|
return dev->mode;
|
||||||
|
}
|
||||||
|
|
||||||
int fl2k_start_tx(fl2k_dev_t *dev, fl2k_tx_cb_t cb, void *ctx,
|
int fl2k_start_tx(fl2k_dev_t *dev, fl2k_tx_cb_t cb, void *ctx,
|
||||||
uint32_t buf_num)
|
uint32_t buf_num)
|
||||||
|
|
Loading…
Reference in New Issue