blob: cf8ab298ddeb602af9090d336fcd381413d6de5b [file] [log] [blame]
/*
* This file is part of the libsigrok project.
*
* Copyright (C) 2015 Soeren Apel <soeren@apelpie.net>
* Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
*
* 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 3 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/>.
*/
/*
* Usage notes:
* This input module reads .ad files created using the
* following practice commands:
*
* I.SAVE <file> /NoCompress
* IPROBE.SAVE <file> /NoCompress
*
* It currently cannot make use of files that have been
* saved using /QuickCompress, /Compress or /ZIP.
* As a workaround you may load the file in PowerView
* using I.LOAD / IPROBE.LOAD and re-save using /NoCompress.
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
#define LOG_PREFIX "input/trace32_ad"
#define CHUNK_SIZE 10240
#define MAX_POD_COUNT 12
#define HEADER_SIZE 80
#define TIMESTAMP_RESOLUTION ((double)0.000000000078125) /* 0.078125 ns */
/*
* The resolution equals a sampling freq of 12.8 GHz. That's a bit high
* for inter-record sample generation, so we scale it down to 200 MHz
* for now. That way, the scaling factor becomes 32.
*/
#define DEFAULT_SAMPLERATE 200
enum {
AD_FORMAT_BINHDR = 1, /* Binary header, binary data, textual setup info */
AD_FORMAT_TXTHDR /* Textual header, binary data */
};
enum {
AD_DEVICE_PI = 1, /* Data recorded by LA-7940 PowerIntegrator or */
/* LA-394x PowerIntegrator II. */
AD_DEVICE_IPROBE /* Data recorded by LA-769x PowerTrace II IProbe. */
/* Missing file format info for LA-793x ICD PowerProbe */
/* Missing file format info for LA-4530 uTrace analog probe */
};
enum {
AD_MODE_250MHZ = 0,
AD_MODE_500MHZ = 1
};
enum {
AD_COMPR_NONE = 0, /* File created with /NOCOMPRESS */
AD_COMPR_QCOMP = 6, /* File created with /COMPRESS or /QUICKCOMPRESS */
};
struct context {
gboolean meta_sent;
gboolean header_read, records_read, trigger_sent;
char format, device, record_mode, compression;
char pod_status[MAX_POD_COUNT];
struct sr_channel *channels[MAX_POD_COUNT][17]; /* 16 + CLK */
uint64_t trigger_timestamp;
uint32_t record_size, record_count, cur_record;
int32_t last_record;
uint64_t samplerate;
double timestamp_scale;
GString *out_buf;
};
static int process_header(GString *buf, struct context *inc);
static void create_channels(struct sr_input *in);
static char get_pod_name_from_id(int id)
{
switch (id) {
case 0: return 'A';
case 1: return 'B';
case 2: return 'C';
case 3: return 'D';
case 4: return 'E';
case 5: return 'F';
case 6: return 'J';
case 7: return 'K';
case 8: return 'L';
case 9: return 'M';
case 10: return 'N';
case 11: return 'O';
default:
sr_err("get_pod_name_from_id() called with invalid ID %d!", id);
}
return 'X';
}
static int init(struct sr_input *in, GHashTable *options)
{
struct context *inc;
int pod;
char id[17];
in->sdi = g_malloc0(sizeof(struct sr_dev_inst));
in->priv = g_malloc0(sizeof(struct context));
inc = in->priv;
/* Calculate the desired timestamp scaling factor. */
inc->samplerate = 1000000 *
g_variant_get_uint32(g_hash_table_lookup(options, "samplerate"));
inc->timestamp_scale = ((1 / TIMESTAMP_RESOLUTION) / (double)inc->samplerate);
/* Enable the pods the user chose to see. */
for (pod = 0; pod < MAX_POD_COUNT; pod++) {
g_snprintf(id, sizeof(id), "pod%c", get_pod_name_from_id(pod));
if (g_variant_get_boolean(g_hash_table_lookup(options, id)))
inc->pod_status[pod] = 1;
}
create_channels(in);
if (g_slist_length(in->sdi->channels) == 0) {
sr_err("No pods were selected and thus no channels created, aborting.");
g_free(in->priv);
g_free(in->sdi);
return SR_ERR;
}
inc->out_buf = g_string_sized_new(CHUNK_SIZE);
return SR_OK;
}
static int format_match(GHashTable *metadata)
{
GString *buf;
buf = g_hash_table_lookup(metadata, GINT_TO_POINTER(SR_INPUT_META_HEADER));
return process_header(buf, NULL);
}
static int process_header(GString *buf, struct context *inc)
{
char *format_name, *format_name_sig;
int i, record_size, device_id;
/*
* 00-31 (0x00-1F) file format name
* 32-39 (0x20-27) trigger timestamp u64
* 40-47 (0x28-2F) unused
* 48 (0x30) compression
* 49-53 (0x31-35) ??
* 50 (0x32) 0x00 (PI), 0x01 (iprobe)
* 54 (0x36) 0x08 (PI 250/500), 0x0A (iprobe 250)
* 55 (0x37) 0x00 (250), 0x01 (500)
* 56 (0x38) record size u8
* 57-59 (0x39-3B) const 0x00
* 60-63 (0x3C-3F) number of records u32
* 64-67 (0x40-43) id of last record s32
* 68-77 (0x44-4D) ??
* 71 (0x47) const 0x80=128
* 72 (0x48) const 0x01
* 78-79 (0x4E-4F) ??
*/
/* Note: inc is off-limits until we check whether it's a valid pointer. */
format_name = g_strndup(buf->str, 32);
/* File format name ends on 0x20/0x1A, let's remove both. */
for (i = 1; i < 31; i++) {
if (format_name[i] == 0x1A) {
format_name[i - 1] = 0;
format_name[i] = 0;
}
}
g_strchomp(format_name); /* This is for additional padding spaces. */
format_name_sig = g_strndup(format_name, 5);
/* Desired file formats either start with digit+space or "trace32". */
if (g_strcmp0(format_name_sig, "trace32")) {
if (inc)
inc->format = AD_FORMAT_BINHDR;
} else if (g_ascii_isdigit(format_name[0]) && (format_name[1] == 0x20)) {
if (inc)
inc->format = AD_FORMAT_TXTHDR;
g_free(format_name_sig);
g_free(format_name);
sr_err("This format isn't implemented yet, aborting.");
return SR_ERR;
} else {
g_free(format_name_sig);
g_free(format_name);
sr_err("Don't know this file format, aborting.");
return SR_ERR;
}
sr_dbg("File says it's \"%s\"", format_name);
record_size = R8(buf->str + 56);
device_id = 0;
if (g_strcmp0(format_name, "trace32 power integrator data") == 0) {
if (record_size == 28 || record_size == 45)
device_id = AD_DEVICE_PI;
} else if (g_strcmp0(format_name, "trace32 iprobe data") == 0) {
if (record_size == 11)
device_id = AD_DEVICE_IPROBE;
}
if (!device_id) {
g_free(format_name_sig);
g_free(format_name);
sr_err("Don't know how to handle this file with record size %d.",
record_size);
return SR_ERR;
}
g_free(format_name_sig);
g_free(format_name);
/* Stop processing the header if we just want to identify the file. */
if (!inc)
return SR_OK;
inc->device = device_id;
inc->trigger_timestamp = RL64(buf->str + 32);
inc->compression = R8(buf->str + 48); /* Maps to the enum. */
inc->record_mode = R8(buf->str + 55); /* Maps to the enum. */
inc->record_size = record_size;
inc->record_count = RL32(buf->str + 60);
inc->last_record = RL32S(buf->str + 64);
sr_dbg("Trigger occured at %lf s.",
inc->trigger_timestamp * TIMESTAMP_RESOLUTION);
sr_dbg("File contains %d records: first one is %d, last one is %d.",
inc->record_count, (inc->last_record - inc->record_count + 1),
inc->last_record);
/* Check if we can work with this compression. */
if (inc->compression != AD_COMPR_NONE) {
sr_err("File uses unsupported compression (0x%02X), can't continue.",
inc->compression);
return SR_ERR;
}
inc->header_read = TRUE;
return SR_OK;
}
static void create_channels(struct sr_input *in)
{
struct context *inc;
int pod, channel, chan_id;
char name[8];
inc = in->priv;
chan_id = 0;
for (pod = 0; pod < MAX_POD_COUNT; pod++) {
if (!inc->pod_status[pod])
continue;
for (channel = 0; channel < 16; channel++) {
snprintf(name, sizeof(name), "%c%d", get_pod_name_from_id(pod), channel);
inc->channels[pod][channel] =
sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name);
chan_id++;
}
snprintf(name, sizeof(name), "CLK%c", get_pod_name_from_id(pod));
inc->channels[pod][16] =
sr_channel_new(in->sdi, chan_id, SR_CHANNEL_LOGIC, TRUE, name);
chan_id++;
}
}
static void send_metadata(struct sr_input *in)
{
struct sr_datafeed_packet packet;
struct sr_datafeed_meta meta;
struct sr_config *src;
struct context *inc;
inc = in->priv;
packet.type = SR_DF_META;
packet.payload = &meta;
src = sr_config_new(SR_CONF_SAMPLERATE, g_variant_new_uint64(inc->samplerate));
meta.config = g_slist_append(NULL, src);
sr_session_send(in->sdi, &packet);
g_slist_free(meta.config);
sr_config_free(src);
inc->meta_sent = TRUE;
}
static void flush_output_buffer(struct sr_input *in)
{
struct context *inc;
struct sr_datafeed_packet packet;
struct sr_datafeed_logic logic;
inc = in->priv;
if (inc->out_buf->len) {
packet.type = SR_DF_LOGIC;
packet.payload = &logic;
logic.unitsize = (g_slist_length(in->sdi->channels) + 7) / 8;
logic.data = inc->out_buf->str;
logic.length = inc->out_buf->len;
sr_session_send(in->sdi, &packet);
g_string_truncate(inc->out_buf, 0);
}
}
static void process_record_pi(struct sr_input *in, gsize start)
{
struct sr_datafeed_packet packet;
struct context *inc;
uint64_t timestamp, next_timestamp;
uint32_t pod_data;
char single_payload[12 * 3];
GString *buf;
int i, pod_count, clk_offset, packet_count, pod;
int payload_bit, payload_len, value;
inc = in->priv;
buf = in->buf;
/*
* 00-07 timestamp
* 08-09 A15..0
* 10-11 B15..0
* 12-13 C15..0
* 14-15 D15..0
* 16-17 E15..0
* 18-19 F15..0
* 20-23 ??
* 24-25 J15..0 Not present in 500MHz mode
* 26-27 K15..0 Not present in 500MHz mode
* 28-29 L15..0 Not present in 500MHz mode
* 30-31 M15..0 Not present in 500MHz mode
* 32-33 N15..0 Not present in 500MHz mode
* 34-35 O15..0 Not present in 500MHz mode
* 36-39 ?? Not present in 500MHz mode
* 40/24 CLKF..A (32=CLKF, .., 1=CLKA)
* 41 CLKO..J (32=CLKO, .., 1=CLKJ) Not present in 500MHz mode
* 42/25 ??
* 43/26 ??
* 44/27 ??
*/
timestamp = RL64(buf->str + start);
if (inc->record_mode == AD_MODE_500MHZ) {
pod_count = 6;
clk_offset = 24;
} else {
pod_count = 12;
clk_offset = 40;
}
payload_bit = 0;
payload_len = 0;
single_payload[0] = 0;
for (pod = 0; pod < pod_count; pod++) {
if (!inc->pod_status[pod])
continue;
switch (pod) {
case 0: /* A */
pod_data = RL16(buf->str + start + 8);
pod_data |= (RL16(buf->str + start + clk_offset) & 1) << 16;
break;
case 1: /* B */
pod_data = RL16(buf->str + start + 10);
pod_data |= (RL16(buf->str + start + clk_offset) & 2) << 15;
break;
case 2: /* C */
pod_data = RL16(buf->str + start + 12);
pod_data |= (RL16(buf->str + start + clk_offset) & 4) << 14;
break;
case 3: /* D */
pod_data = RL16(buf->str + start + 14);
pod_data |= (RL16(buf->str + start + clk_offset) & 8) << 13;
break;
case 4: /* E */
pod_data = RL16(buf->str + start + 16);
pod_data |= (RL16(buf->str + start + clk_offset) & 16) << 12;
break;
case 5: /* F */
pod_data = RL16(buf->str + start + 18);
pod_data |= (RL16(buf->str + start + clk_offset) & 32) << 11;
break;
case 6: /* J */
pod_data = RL16(buf->str + start + 24);
pod_data |= (RL16(buf->str + start + 41) & 1) << 16;
break;
case 7: /* K */
pod_data = RL16(buf->str + start + 26);
pod_data |= (RL16(buf->str + start + 41) & 2) << 15;
break;
case 8: /* L */
pod_data = RL16(buf->str + start + 28);
pod_data |= (RL16(buf->str + start + 41) & 4) << 14;
break;
case 9: /* M */
pod_data = RL16(buf->str + start + 30);
pod_data |= (RL16(buf->str + start + 41) & 8) << 13;
break;
case 10: /* N */
pod_data = RL16(buf->str + start + 32);
pod_data |= (RL16(buf->str + start + 41) & 16) << 12;
break;
case 11: /* O */
pod_data = RL16(buf->str + start + 34);
pod_data |= (RL16(buf->str + start + 41) & 32) << 11;
break;
default:
sr_err("Don't know how to obtain data for pod %d.", pod);
}
for (i = 0; i < 17; i++) {
value = (pod_data >> i) & 1;
single_payload[payload_len] |= value << payload_bit;
payload_bit++;
if (payload_bit > 7) {
payload_bit = 0;
payload_len++;
single_payload[payload_len] = 0;
}
}
}
/* Make sure that payload_len accounts for any incomplete bytes used. */
if (payload_bit)
payload_len++;
i = (g_slist_length(in->sdi->channels) + 7) / 8;
if (payload_len != i) {
sr_err("Payload unit size is %d but should be %d!", payload_len, i);
return;
}
if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) {
sr_dbg("Trigger @%lf s, record #%d.",
timestamp * TIMESTAMP_RESOLUTION, inc->cur_record);
packet.type = SR_DF_TRIGGER;
packet.payload = NULL;
sr_session_send(in->sdi, &packet);
inc->trigger_sent = TRUE;
}
/* Is this the last record in the file? */
if (inc->cur_record == inc->record_count - 1) {
/* It is, so send the last sample data only once. */
g_string_append_len(inc->out_buf, single_payload, payload_len);
} else {
/* It's not, so fill the time gap by sending lots of data. */
next_timestamp = RL64(buf->str + start + inc->record_size);
packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale;
/* Make sure we send at least one data set. */
if (packet_count == 0)
packet_count = 1;
for (i = 0; i < packet_count; i++)
g_string_append_len(inc->out_buf, single_payload, payload_len);
}
if (inc->out_buf->len >= CHUNK_SIZE)
flush_output_buffer(in);
}
static void process_record_iprobe(struct sr_input *in, gsize start)
{
struct sr_datafeed_packet packet;
struct context *inc;
uint64_t timestamp, next_timestamp;
char single_payload[3];
int i, payload_len, packet_count;
inc = in->priv;
/*
* 00-07 timestamp
* 08-09 IP15..0
* 10 CLK
*/
timestamp = RL64(in->buf->str + start);
single_payload[0] = R8(in->buf->str + start + 8);
single_payload[1] = R8(in->buf->str + start + 9);
single_payload[2] = R8(in->buf->str + start + 10) & 1;
payload_len = 3;
if (timestamp == inc->trigger_timestamp && !inc->trigger_sent) {
sr_dbg("Trigger @%lf s, record #%d.",
timestamp * TIMESTAMP_RESOLUTION, inc->cur_record);
packet.type = SR_DF_TRIGGER;
packet.payload = NULL;
sr_session_send(in->sdi, &packet);
inc->trigger_sent = TRUE;
}
/* Is this the last record in the file? */
if (inc->cur_record == inc->record_count - 1) {
/* It is, so send the last sample data only once. */
g_string_append_len(inc->out_buf, single_payload, payload_len);
} else {
/* It's not, so fill the time gap by sending lots of data. */
next_timestamp = RL64(in->buf->str + start + inc->record_size);
packet_count = (int)(next_timestamp - timestamp) / inc->timestamp_scale;
/* Make sure we send at least one data set. */
if (packet_count == 0)
packet_count = 1;
for (i = 0; i < packet_count; i++)
g_string_append_len(inc->out_buf, single_payload, payload_len);
}
if (inc->out_buf->len >= CHUNK_SIZE)
flush_output_buffer(in);
}
static void process_practice_token(struct sr_input *in, char *cmd_token)
{
struct context *inc;
char **tokens;
char chan_suffix[2], chan_name[33];
char *s1, *s2;
int pod, ch;
struct sr_channel *channel;
inc = in->priv;
/*
* Commands of interest (I may also be IPROBE):
*
* I.TWIDTH
* I.TPREDELAY
* I.TDELAY
* I.TYSNC.SELECT I.A0 HIGH
* NAME.SET <port.chan> <name> <+/-> ...
*/
if (!cmd_token)
return;
if (cmd_token[0] == 0)
return;
tokens = g_strsplit(cmd_token, " ", 0);
if (!tokens)
return;
if (g_strcmp0(tokens[0], "NAME.SET") == 0) {
/* Let the user know when the channel has been inverted. */
/* This *should* be token #3 but there's an additonal space, making it #4. */
chan_suffix[0] = 0;
chan_suffix[1] = 0;
if (tokens[4]) {
if (tokens[4][0] == '-')
chan_suffix[0] = '-'; /* This is the way PowerView shows it. */
}
/*
* Command is using structure "NAME.SET I.A00 I.XYZ" or
* "NAME.SET IP.00 IP.XYZ", depending on the device used.
* Let's get strings with the I./IP. from both tokens removed.
*/
s1 = g_strstr_len(tokens[1], -1, ".") + 1;
s2 = g_strstr_len(tokens[2], -1, ".") + 1;
if (g_strcmp0(s1, "CLK") == 0) {
/* CLK for iprobe */
pod = 0;
ch = 16;
} else if ((strlen(s1) == 4) && g_ascii_isupper(s1[3])) {
/* CLKA/B/J/K for PowerIntegrator */
pod = s1[3] - (char)'A';
ch = 16;
} else if (g_ascii_isupper(s1[0])) {
/* A00 for PowerIntegrator */
pod = s1[0] - (char)'A';
ch = atoi(s1 + 1);
} else {
/* 00 for iprobe */
pod = 0;
ch = atoi(s1);
}
channel = inc->channels[pod][ch];
g_snprintf(chan_name, sizeof(chan_name), "%s%s", s2, chan_suffix);
sr_dbg("Changing channel name for %s to %s.", s1, chan_name);
sr_dev_channel_name_set(channel, chan_name);
}
g_strfreev(tokens);
}
static void process_practice(struct sr_input *in)
{
char delimiter[3];
char **tokens, *token;
int i;
/* Gather all input data until we see the end marker. */
if (in->buf->str[in->buf->len - 1] != 0x29)
return;
delimiter[0] = 0x0A;
delimiter[1] = ' ';
delimiter[2] = 0;
tokens = g_strsplit(in->buf->str, delimiter, 0);
/* Special case: first token contains the start marker, too. Skip it. */
token = tokens[0];
for (i = 0; token[i]; i++) {
if (token[i] == ' ')
process_practice_token(in, token + i + 1);
}
for (i = 1; tokens[i]; i++)
process_practice_token(in, tokens[i]);
g_strfreev(tokens);
g_string_erase(in->buf, 0, in->buf->len);
}
static int process_buffer(struct sr_input *in)
{
struct context *inc;
int i, chunk_size, res;
inc = in->priv;
if (!inc->header_read) {
res = process_header(in->buf, inc);
g_string_erase(in->buf, 0, HEADER_SIZE);
if (res != SR_OK)
return res;
}
if (!inc->meta_sent) {
std_session_send_df_header(in->sdi);
send_metadata(in);
}
if (!inc->records_read) {
/* Cut off at a multiple of the record size. */
chunk_size = ((in->buf->len) / inc->record_size) * inc->record_size;
/* There needs to be at least one more record process_record() can peek into. */
chunk_size -= inc->record_size;
for (i = 0; (i < chunk_size) && (!inc->records_read); i += inc->record_size) {
switch (inc->device) {
case AD_DEVICE_PI:
process_record_pi(in, i);
break;
case AD_DEVICE_IPROBE:
process_record_iprobe(in, i);
break;
default:
sr_err("Trying to process records for unknown device!");
return SR_ERR;
}
inc->cur_record++;
if (inc->cur_record == inc->record_count)
inc->records_read = TRUE;
}
g_string_erase(in->buf, 0, i);
}
if (inc->records_read) {
/* Read practice commands that configure the setup. */
process_practice(in);
}
return SR_OK;
}
static int receive(struct sr_input *in, GString *buf)
{
g_string_append_len(in->buf, buf->str, buf->len);
if (!in->sdi_ready) {
/* sdi is ready, notify frontend. */
in->sdi_ready = TRUE;
return SR_OK;
}
return process_buffer(in);
}
static int end(struct sr_input *in)
{
struct context *inc;
int ret;
inc = in->priv;
if (in->sdi_ready)
ret = process_buffer(in);
else
ret = SR_OK;
flush_output_buffer(in);
if (inc->meta_sent)
std_session_send_df_end(in->sdi);
return ret;
}
static int reset(struct sr_input *in)
{
struct context *inc = in->priv;
inc->meta_sent = FALSE;
inc->header_read = FALSE;
inc->records_read = FALSE;
inc->trigger_sent = FALSE;
inc->cur_record = 0;
g_string_truncate(in->buf, 0);
return SR_OK;
}
static struct sr_option options[] = {
{ "podA", "Import pod A / iprobe",
"Create channels and data for pod A / iprobe", NULL, NULL },
{ "podB", "Import pod B", "Create channels and data for pod B", NULL, NULL },
{ "podC", "Import pod C", "Create channels and data for pod C", NULL, NULL },
{ "podD", "Import pod D", "Create channels and data for pod D", NULL, NULL },
{ "podE", "Import pod E", "Create channels and data for pod E", NULL, NULL },
{ "podF", "Import pod F", "Create channels and data for pod F", NULL, NULL },
{ "podJ", "Import pod J", "Create channels and data for pod J", NULL, NULL },
{ "podK", "Import pod K", "Create channels and data for pod K", NULL, NULL },
{ "podL", "Import pod L", "Create channels and data for pod L", NULL, NULL },
{ "podM", "Import pod M", "Create channels and data for pod M", NULL, NULL },
{ "podN", "Import pod N", "Create channels and data for pod N", NULL, NULL },
{ "podO", "Import pod O", "Create channels and data for pod O", NULL, NULL },
{ "samplerate", "Reduced sample rate (MHz)", "Reduce the original sample rate of 12.8 GHz to the specified sample rate in MHz", NULL, NULL },
ALL_ZERO
};
static const struct sr_option *get_options(void)
{
if (!options[0].def) {
options[0].def = g_variant_ref_sink(g_variant_new_boolean(TRUE));
options[1].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[2].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[3].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[4].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[5].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[6].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[7].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[8].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[9].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[10].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[11].def = g_variant_ref_sink(g_variant_new_boolean(FALSE));
options[12].def = g_variant_ref_sink(g_variant_new_uint32(DEFAULT_SAMPLERATE));
}
return options;
}
SR_PRIV struct sr_input_module input_trace32_ad = {
.id = "trace32_ad",
.name = "Trace32_ad",
.desc = "Lauterbach Trace32 logic analyzer data",
.exts = (const char*[]){"ad", NULL},
.options = get_options,
.metadata = { SR_INPUT_META_HEADER | SR_INPUT_META_REQUIRED },
.format_match = format_match,
.init = init,
.receive = receive,
.end = end,
.reset = reset,
};