From 87db62b20d56d83de6bd0c04c1cb3ed13b0f6191 Mon Sep 17 00:00:00 2001 From: sys64738 Date: Wed, 23 Jun 2021 20:33:02 +0200 Subject: [PATCH] ikalogic-scanaquad: add basic driver code triggerless captures only for now --- .gitignore | 2 + Makefile.am | 6 + configure.ac | 1 + src/hardware/asix-sigma/protocol.c | 4 +- src/hardware/chronovu-la/api.c | 2 +- src/hardware/chronovu-la/protocol.c | 2 +- src/hardware/ftdi-la/api.c | 2 +- src/hardware/ikalogic-scanaplus/api.c | 2 +- src/hardware/ikalogic-scanaquad/api.c | 495 ++++++++++++ src/hardware/ikalogic-scanaquad/protocol.c | 827 +++++++++++++++++++++ src/hardware/ikalogic-scanaquad/protocol.h | 168 +++++ src/hardware/pipistrello-ols/protocol.c | 2 +- 12 files changed, 1506 insertions(+), 7 deletions(-) create mode 100644 src/hardware/ikalogic-scanaquad/api.c create mode 100644 src/hardware/ikalogic-scanaquad/protocol.c create mode 100644 src/hardware/ikalogic-scanaquad/protocol.h diff --git a/.gitignore b/.gitignore index e943562f..05a0f9bc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ /configure.lineno /m4/libtool.m4 /m4/lt*.m4 +/build +/inst # Editor/IDE cruft *.kate-swp diff --git a/Makefile.am b/Makefile.am index 9bf6eb7d..2805b5fe 100644 --- a/Makefile.am +++ b/Makefile.am @@ -411,6 +411,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/ikalogic-scanaplus/protocol.c \ src/hardware/ikalogic-scanaplus/api.c endif +if HW_IKALOGIC_SCANAQUAD +src_libdrivers_la_SOURCES += \ + src/hardware/ikalogic-scanaquad/protocol.h \ + src/hardware/ikalogic-scanaquad/protocol.c \ + src/hardware/ikalogic-scanaquad/api.c +endif if HW_IPDBG_LA src_libdrivers_la_SOURCES += \ src/hardware/ipdbg-la/protocol.h \ diff --git a/configure.ac b/configure.ac index 85a1af41..fc8b371d 100644 --- a/configure.ac +++ b/configure.ac @@ -289,6 +289,7 @@ SR_DRIVER([hp-59306a], [hp-59306a]) SR_DRIVER([Hung-Chang DSO-2100], [hung-chang-dso-2100], [libieee1284]) SR_DRIVER([Ikalogic Scanalogic-2], [ikalogic-scanalogic2], [libusb]) SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi]) +SR_DRIVER([Ikalogic ScanaQuad], [ikalogic-scanaquad], [libftdi]) SR_DRIVER([IPDBG LA], [ipdbg-la]) SR_DRIVER([ITECH IT8500], [itech-it8500], [serial_comm]) SR_DRIVER([Kecheng KC-330B], [kecheng-kc-330b], [libusb]) diff --git a/src/hardware/asix-sigma/protocol.c b/src/hardware/asix-sigma/protocol.c index df59693d..f97b07f6 100644 --- a/src/hardware/asix-sigma/protocol.c +++ b/src/hardware/asix-sigma/protocol.c @@ -625,7 +625,7 @@ static int sigma_fpga_init_bitbang_once(struct dev_context *devc) if (ret != SR_OK) return ret; g_usleep(10 * 1000); - ftdi_usb_purge_buffers(&devc->ftdi.ctx); + ftdi_tcioflush(&devc->ftdi.ctx); /* * Wait until the FPGA asserts INIT_B. Check in a maximum number @@ -889,7 +889,7 @@ static int upload_firmware(struct sr_context *ctx, struct dev_context *devc, ftdi_get_error_string(&devc->ftdi.ctx)); return SR_ERR; } - ftdi_usb_purge_buffers(&devc->ftdi.ctx); + ftdi_tcioflush(&devc->ftdi.ctx); while (sigma_read_raw(devc, &pins, sizeof(pins)) > 0) ; diff --git a/src/hardware/chronovu-la/api.c b/src/hardware/chronovu-la/api.c index 694bd54a..44d9ae65 100644 --- a/src/hardware/chronovu-la/api.c +++ b/src/hardware/chronovu-la/api.c @@ -243,7 +243,7 @@ static int dev_open(struct sr_dev_inst *sdi) goto err_ftdi_free; } - if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) { + if ((ret = ftdi_tcioflush(devc->ftdic)) < 0) { sr_err("Failed to purge FTDI buffers (%d): %s.", ret, ftdi_get_error_string(devc->ftdic)); goto err_ftdi_free; diff --git a/src/hardware/chronovu-la/protocol.c b/src/hardware/chronovu-la/protocol.c index da772f5b..83d9a223 100644 --- a/src/hardware/chronovu-la/protocol.c +++ b/src/hardware/chronovu-la/protocol.c @@ -202,7 +202,7 @@ static int close_usb_reset_sequencer(struct dev_context *devc) sr_dbg("Purging buffers, resetting+closing FTDI device."); /* Log errors, but ignore them (i.e., don't abort). */ - if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) + if ((ret = ftdi_tcioflush(devc->ftdic)) < 0) sr_err("Failed to purge FTDI buffers (%d): %s.", ret, ftdi_get_error_string(devc->ftdic)); if ((ret = ftdi_usb_reset(devc->ftdic)) < 0) diff --git a/src/hardware/ftdi-la/api.c b/src/hardware/ftdi-la/api.c index 8bb67121..fb795ca4 100644 --- a/src/hardware/ftdi-la/api.c +++ b/src/hardware/ftdi-la/api.c @@ -325,7 +325,7 @@ static int dev_open(struct sr_dev_inst *sdi) goto err_ftdi_free; } - ret = ftdi_usb_purge_buffers(devc->ftdic); + ret = ftdi_tcioflush(devc->ftdic); if (ret < 0) { sr_err("Failed to purge FTDI RX/TX buffers (%d): %s.", ret, ftdi_get_error_string(devc->ftdic)); diff --git a/src/hardware/ikalogic-scanaplus/api.c b/src/hardware/ikalogic-scanaplus/api.c index f29e5c39..be0421b5 100644 --- a/src/hardware/ikalogic-scanaplus/api.c +++ b/src/hardware/ikalogic-scanaplus/api.c @@ -141,7 +141,7 @@ static int dev_open(struct sr_dev_inst *sdi) return SR_ERR; } - if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) { + if ((ret = ftdi_tcioflush(devc->ftdic)) < 0) { sr_err("Failed to purge FTDI RX/TX buffers (%d): %s.", ret, ftdi_get_error_string(devc->ftdic)); goto err_dev_open_close_ftdic; diff --git a/src/hardware/ikalogic-scanaquad/api.c b/src/hardware/ikalogic-scanaquad/api.c new file mode 100644 index 00000000..41bffb21 --- /dev/null +++ b/src/hardware/ikalogic-scanaquad/api.c @@ -0,0 +1,495 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2021 sys64738 , + * haskal + * + * 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 . + */ + +#include +#include "protocol.h" + +#define USB_VENDOR_ID 0x0403 +#define USB_PRODUCT_ID 0x7fd0 + +#define USB_IPRODUCT_25 "ScanaQuad SQ25" +#define USB_IPRODUCT_50 "ScanaQuad SQ50" +#define USB_IPRODUCT_100 "ScanaQuad SQ100" +#define USB_IPRODUCT_200 "ScanaQuad SQ200" + +static struct sr_dev_driver ikalogic_scanaquad_driver_info; + +static const uint32_t drvopts[] = { + SR_CONF_LOGIC_ANALYZER, +}; + +static const uint32_t devopts[] = { + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_VOLTAGE_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_LOGIC_THRESHOLD | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_LOGIC_THRESHOLD_CUSTOM | SR_CONF_GET | SR_CONF_SET, + + /*SR_CONF_TRIGGER_MATCH | SR_CONF_LIST,*/ + + /* list of TODO stuff: + * * VOLTAGE_THRESHOLD vs LOGIC_THRESHOLD? LOGIC_THRESHOLD_CUSTOM? + * * VOLTAGE_THRESHOLD: 2 double values (min, max) with a slider selection GUI + * * LOGIC_THRESHOLD: selection of (max, not thresh value) voltages + * * LOGIC_THRESHOLD_CUSTOM: custom ^, so kinda boils down to VOLTAGE_THRESHOLD? + * * see kingst-la2016/api.c + * * currently implemnted as all of them, pulseview gives a simple slider + * without value indication, which is not that practical to use... + * LOGIC_THRESHOLD(_CUSTOM) seems to give no UI? + * + * * BUFFERSIZE, DATALOG, PATTERN_MODE: should this be used? + * + * * trigger stuff: + * * TRIGGER_MATCH: uncomment when TRIGGER_PATTERN is implemented + * * TRIGGER_PATTERN: freeform string + * * see asix-sigma/protocol.c:1603 , it has some proposed formats, + * however, these aren't sufficient here as the SQ series also allows + * varying pulse widths for each trigger step... + * * TRIGGER_LEVEL, TRIGGER_SOURCE: not applicable here + * + * * the SQ50 accepts 200 MHz samplerate and a buffer size longer than it + * has available physical backing for, so it's not unlikely that modding + * it to have more memory is possible. so maybe memory and frequency + * option shouldn't be strictly checked? + * see also: https://git.lain.faith/BLAHAJ/sq50-re/wiki/Hardware-info + */ +}; + +static const char *channel_names[] = { + "1", "2", "3", "4" +}; + +static const uint64_t samplerates[] = { + /* TODO: can this be an arbitrary value? at least the settings + * blob makes it look like it can */ + /* also, looks like the SQ50 can totally do 100 and 200 MHz... */ + SR_MHZ(200), SR_MHZ(100), + SR_MHZ( 50), SR_MHZ( 25), SR_MHZ( 10), SR_MHZ( 5), SR_MHZ( 1), + SR_KHZ(500), SR_KHZ(250), SR_KHZ(100), SR_KHZ(50), SR_KHZ(10) +}; + +static const char *thresholds[] = { + "1.8V", /* 0x46 0x1e | 1.8 0.8 ~44% */ + "2.8V", /* 0x6e 0x2c | 2.8 1.1 ~40% */ + "3.3V", /* 0x81 0x46 | 3.3 1.8 ~55% */ + "3.6V", /* 0x8d 0x4f | 3.6 2.0 ~55% */ + "5V" , /* 0xc4 0x72 | 5.0 2.9 ~58% */ + "5VTTL",/* 0xc3 0x3b | 5.0 1.5 */ + "User" /* VOLTAGE_THRESHOLD == LOGIC_THRESHOLD_CUSTOM (?) */ +}; + +static const int matches[] = { + SR_TRIGGER_ZERO, + SR_TRIGGER_ONE, + SR_TRIGGER_RISING, + SR_TRIGGER_FALLING, +}; + +static uint64_t dc_samples_to_msec(struct dev_context *dc, uint64_t samples) +{ + float msec_per_sample = 1.0f / ((float)dc->samplerate * 0.001f); + + return (uint64_t)((float)samples * msec_per_sample); +} +static uint64_t dc_msec_to_samples(struct dev_context *dc, uint64_t msec) +{ + float samples_per_msec = ((float)dc->samplerate * 0.001f); + + return (uint64_t)((float)msec * samples_per_msec); +} + +static int dev_clear(const struct sr_dev_driver *di) +{ + return std_dev_clear_with_callback(di, (std_dev_clear_callback)sq_destroy); +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) +{ + struct sr_dev_inst *sdi; + struct dev_context *dc; + struct ftdi_context *ft; + char *manuf, *prod, *serial; + unsigned int i; + int rv; + + (void)options; + + if (!(ft = ftdi_new())) { + sr_err("Failed to initialize libftdi."); + return NULL; + } + + dc = sq_new(ft); + if (dc == NULL) { + sr_err("Failed to initialize ScanaQuad device context."); + ftdi_free(ft); + return NULL; + } + + /* open the FTDI device for a second to 1) check it exists, and + * 2) check which device it is from the SQ series */ + + rv = ftdi_usb_open(ft, USB_VENDOR_ID, USB_PRODUCT_ID); + if (rv < 0) { + if (rv != -3) { /* -3: device not found */ + sr_err("Failed to open device (%d): %s", rv, ftdi_get_error_string(ft)); + sq_destroy(dc); + g_free(dc); + return NULL; + } + } + + rv = ftdi_read_eeprom(ft); + if (rv < 0) { + sr_err("Failted to read FTDI EEPROM (%d): %s", rv, ftdi_get_error_string(ft)); + sq_destroy(dc); + g_free(dc); + return NULL; + } + + rv = ftdi_eeprom_decode(ft, 0); + /* retval -1 means a checksum error, which current libftdi + * is not equipped to deal with yet, as it only knows of the + * FT230X, while the SQ uses an FT240X */ + if (rv < 0) { + sr_err("Failed to decode FTDI EEPROM (%d): %s", rv, ftdi_get_error_string(ft)); + sq_destroy(dc); + g_free(dc); + return NULL; + } + + manuf = g_malloc0(256); + prod = g_malloc0(256); + serial = g_malloc0(256); + rv = ftdi_eeprom_get_strings(ft, manuf, 256, prod, 256, serial, 256); + if (rv < 0) { + sr_err("Failed to get device info strings (%d): %s", rv, ftdi_get_error_string(ft)); + g_free(serial); + g_free(prod ); + g_free(manuf ); + sq_destroy(dc); + g_free(dc); + return NULL; + } + + ftdi_usb_close(ft); /* enough for now */ + + if (strcmp(manuf, "IKALOGIC")) { + sr_warn("Unexpected manufacturer name %s!", manuf); + } + + if (!strcmp(prod, USB_IPRODUCT_25)) { + dc->devtype = 25; + dc->memsize_max = MAX_MEMSIZE_SQ50 / 2; + sr_info("%s", prod); + } else if (!strcmp(prod, USB_IPRODUCT_50)) { + dc->devtype = 50; + dc->memsize_max = MAX_MEMSIZE_SQ50; + sr_info("%s", prod); + } else if (!strcmp(prod, USB_IPRODUCT_100)) { + dc->devtype = 100; + dc->memsize_max = MAX_MEMSIZE_SQ50 * 2; + sr_info("%s", prod); + } else if (!strcmp(prod, USB_IPRODUCT_200)) { + dc->devtype = 200; + dc->memsize_max = MAX_MEMSIZE_SQ50 * 4; + sr_info("%s", prod); + } else { + sr_warn("Unexpected product name %s! Assuming SQ50 behavior...", prod); + dc->devtype = 50; + dc->memsize_max = MAX_MEMSIZE_SQ50; + } + dc->limit_samples = dc->memsize_max; + + sr_info("%s ScanaQuad serial number: %s", manuf, serial); + + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INACTIVE; + sdi->vendor = g_strdup(manuf); + sdi->model = g_strdup(prod); + sdi->priv = dc; + + g_free(serial); + g_free(prod ); + g_free(manuf ); + + for (i = 0; i < ARRAY_SIZE(channel_names); ++i) + sr_channel_new(sdi, i, SR_CHANNEL_LOGIC, TRUE/* enabled */, channel_names[i]); + + return std_scan_complete(di, g_slist_append(NULL, sdi)); +} + +#define CHECK_FTDI_RETVAL(msg, ft, rv) \ + do { \ + if ((rv) < 0) { \ + sr_err(msg " in %s (%d): %s.\n", __func__, (rv), ftdi_get_error_string(ft)); \ + return SR_ERR; \ + } \ + } while (0) \ + +#define CHECK_SQ_RETVAL(rv, msg) \ + do { \ + if ((rv) < 0) { \ + sr_err(msg "in %s (%d).\n", __func__, (rv)); \ + return SR_ERR; \ + } \ + } while (0) \ + +static int dev_open(struct sr_dev_inst *sdi) +{ + struct dev_context *dc; + int rv; + + dc = sdi->priv; + + if (!dc || !dc->ft) return SR_ERR_BUG; + + rv = ftdi_set_interface(dc->ft, INTERFACE_A); + CHECK_FTDI_RETVAL("Failed to set FTDI interface A", dc->ft, rv); + + rv = ftdi_usb_open(dc->ft, USB_VENDOR_ID, USB_PRODUCT_ID); + CHECK_FTDI_RETVAL("Failed to open FTDI device", dc->ft, rv); + + rv = ftdi_tcioflush(dc->ft); + CHECK_FTDI_RETVAL("Failed to purge buffers", dc->ft, rv); + + rv = ftdi_set_latency_timer(dc->ft, 2); + CHECK_FTDI_RETVAL("Failed to set FTDI latency timer", dc->ft, rv); + + /*rv = ftdi_read_data_set_chunksize(dc->ft, 64*1024); + CHECK_FTDI_RETVAL("Failed to set FTDI read data chunk size", dc->ft, rv);*/ + + return scanaquad_init(dc); +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + struct dev_context *dc; + int rv; + + dc = sdi->priv; + + if (!dc || !dc->ft) return SR_ERR_BUG; + + rv = scanaquad_cancel_all(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing ScanaQuad transfers"); + + rv = ftdi_tcioflush(dc->ft); + CHECK_FTDI_RETVAL("Failed to purge buffers", dc->ft, rv); + + rv = ftdi_usb_close(dc->ft); + CHECK_FTDI_RETVAL("Failed to close FTDI device", dc->ft, rv); + + return SR_OK; +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *dc; + double rnded; + int rv; + + dc = sdi->priv; + + (void)cg; + + if (!dc) return SR_ERR_BUG; + + rnded = (int)(dc->voltage.thresh * 10.0 / 39.2) * 0.1; + + rv = SR_OK; + switch (key) { + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(dc->samplerate); + break; + case SR_CONF_LIMIT_MSEC: + *data = g_variant_new_uint64(dc_samples_to_msec(dc, dc->limit_samples)); + break; + case SR_CONF_LIMIT_SAMPLES: + *data = g_variant_new_uint64(dc->limit_samples); + break; + case SR_CONF_CAPTURE_RATIO: + *data = g_variant_new_uint64(dc->capture_ratio); + break; + case SR_CONF_VOLTAGE_THRESHOLD: + *data = std_gvar_tuple_double(rnded, rnded + 0.1); + break; + case SR_CONF_LOGIC_THRESHOLD: + *data = g_variant_new_string(thresholds[dc->voltage_idx]); + break; + case SR_CONF_LOGIC_THRESHOLD_CUSTOM: + *data = g_variant_new_double(rnded); + break; + default: + sr_err("%s no conf key %u", __func__, key); + rv = SR_ERR_NA; + break; + } + + return rv; +} + +static int config_set(uint32_t key, GVariant *data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *dc; + double volt, vlo, vhi; + int rv; + + dc = sdi->priv; + + (void)cg; + + if (!dc) return SR_ERR_BUG; + + if (key == SR_CONF_LOGIC_THRESHOLD_CUSTOM) + volt = g_variant_get_double(data); + + rv = SR_OK; + switch (key) { + case SR_CONF_SAMPLERATE: + dc->samplerate = g_variant_get_uint64(data); + break; + case SR_CONF_LIMIT_MSEC: + dc->limit_samples = dc_msec_to_samples(dc, g_variant_get_uint64(data)); + /* TODO: make this check optional */ + if (dc->limit_samples > dc->memsize_max * 4) + dc->limit_samples = dc->memsize_max * 4; + break; + case SR_CONF_LIMIT_SAMPLES: + dc->limit_samples = g_variant_get_uint64(data); + /* TODO: make this check optional */ + if (dc->limit_samples > dc->memsize_max * 4) + dc->limit_samples = dc->memsize_max * 4; + break; + case SR_CONF_CAPTURE_RATIO: + dc->capture_ratio = g_variant_get_uint64(data); + break; + case SR_CONF_VOLTAGE_THRESHOLD: + g_variant_get(data, "(dd)", &vlo, &vhi); + volt = 0.5 * (vlo + vhi); + /* fallthru! */ + case SR_CONF_LOGIC_THRESHOLD_CUSTOM: + /* in the LOGIC_THRESHOLD_CUSTOM case: see 'if' stmt before switch */ + dc->voltage_idx = 6; /* user */ + dc->voltage.thresh = (int)(volt * 39.2); + /* emulate vendor software values in a naive way. not 100% exact but + * oh well. */ + dc->voltage.level = (dc->voltage.thresh < 0x30) + ? (int)(volt * 39.2 / 0.40) + : (int)(volt * 39.2 / 0.55); + break; + default: + sr_err("%s no conf key %u", __func__, key); + rv = SR_ERR_NA; + break; + } + + return rv; +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + int rv; + + rv = SR_OK; + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, NO_OPTS, drvopts, devopts); + case SR_CONF_SAMPLERATE: + *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates)); + break; + case SR_CONF_VOLTAGE_THRESHOLD: + *data = std_gvar_min_max_step_thresholds(0.8, 2.9, 0.1); + break; + case SR_CONF_LOGIC_THRESHOLD: + *data = g_variant_new_strv(ARRAY_AND_SIZE(thresholds)); + break; + case SR_CONF_TRIGGER_MATCH: + *data = std_gvar_array_i32(ARRAY_AND_SIZE(matches)); + break; + default: + sr_err("%s no conf key %u", __func__, key); + rv = SR_ERR_NA; + break; + } + + return rv; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) +{ + struct dev_context *dc; + int rv; + + dc = sdi->priv; + + if (!dc || !dc->ft) return SR_ERR_BUG; + + rv = scanaquad_acquisition_start(dc); + CHECK_SQ_RETVAL(rv, "Failed to start acquisition"); + + std_session_send_df_header(sdi); + sr_session_source_add(sdi->session, -1, 0, 0, scanaquad_receive_data, (void *)sdi); + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi) +{ + struct dev_context *dc; + int rv; + + dc = sdi->priv; + + if (!dc || !dc->ft) return SR_ERR_BUG; + + sr_session_source_remove(sdi->session, -1); + std_session_send_df_end(sdi); + + rv = scanaquad_acquisition_stop(sdi, dc); + CHECK_SQ_RETVAL(rv, "Failed to stop acquisition"); + + return SR_OK; +} + +static struct sr_dev_driver ikalogic_scanaquad_driver_info = { + .name = "ikalogic-scanaquad", + .longname = "IKALOGIC ScanaQuad", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(ikalogic_scanaquad_driver_info); diff --git a/src/hardware/ikalogic-scanaquad/protocol.c b/src/hardware/ikalogic-scanaquad/protocol.c new file mode 100644 index 00000000..f39ebe1e --- /dev/null +++ b/src/hardware/ikalogic-scanaquad/protocol.c @@ -0,0 +1,827 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2021 sys64738 , + * haskal + * + * 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 . + */ + +#include +#include "protocol.h" + +SR_PRIV struct dev_context *sq_new(struct ftdi_context *ft) +{ + struct dev_context *dc; + + if (!ft) return NULL; + + dc = (struct dev_context *)g_malloc0(sizeof(struct dev_context)); + dc->ft = ft; + + /* some sensible defaults (mostly the vendor software defaults) */ + dc->samplerate = SR_MHZ(25); + dc->capture_ratio = 10; /* 10% in */ + dc->voltage.level = 0x81; /* 3.3v */ + dc->voltage.thresh = 0x46; /* 1.8v (3.3v cmos thresh) */ + dc->voltage_idx = 2; /* "3.3V" */ + + return dc; +} +SR_PRIV void sq_destroy(struct dev_context *dc) +{ + if (!dc) return; + + if (dc->ft) { + ftdi_free(dc->ft); + dc->ft = NULL; + } + /*g_free(dc);*/ +} + +#define CHECK_FTDI_RETVAL(write, ft, rv, expect, ...) \ + do { \ + if ((rv) < 0) { \ + sr_err("Failed to %s FTDI data in %s (%d): %s.\n", (write)?"write":"read", __func__, (rv), \ + ftdi_get_error_string(ft)); \ + return SR_ERR; \ + } else if ((size_t)(rv) != (expect) __VA_OPT__(&& __VA_ARGS__)) { \ + sr_err("FTDI %s error in %s, only %d/%zu bytes %s: %s.", (write)?"write":"read", __func__,\ + (rv), expect, (write)?"written":"read", ftdi_get_error_string(ft)); \ + return SR_ERR; \ + } \ + } while (0) \ + +#define CHECK_SQ_RETVAL(rv, fmt, ...) \ + do { \ + if ((rv) < SR_ERR) { \ + if (FALSE) { \ + sr_err(fmt " in %s (%d)", ##__VA_ARGS__ , __func__, (rv)); \ + } else { \ + sr_err(fmt "in %s", ##__VA_ARGS__, __func__); \ + } \ + return rv; \ + } \ + } while (0) \ + +/* low-level commands */ + +/* this is a macro instead of an inline static fn, because CHECK_FTDI_RETVAL + * uses the __func__ pseudomacro for diagnostic purposes, which would be + * rendered useless if this were a real function */ +#define read_ftdi_blocking(ft, data, s) ({\ + size_t __i = 0; \ + int __rv = 0; \ + do { \ + __rv = ftdi_read_data(ft, (uint8_t *)(data)+__i, (s)-__i); \ + CHECK_FTDI_RETVAL(FALSE, ft, __rv, (s)-__i, FALSE); \ + /*sr_spew("read_blocking: read %d\n", __rv);*/ \ + __i += __rv; \ + } while (__i < s); \ + __rv;}) \ + +SR_PRIV int sq_get_status(struct dev_context *dc) +{ + static const uint8_t get_status_cmd[] = {0xfd,0x00,0x01,0x02,0xfe}; + uint8_t stat[4]; + int rv; + + if (!dc) return SR_ERR; + + rv = ftdi_write_data(dc->ft, get_status_cmd, sizeof get_status_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof get_status_cmd); + + rv = read_ftdi_blocking(dc->ft, stat, sizeof stat); + CHECK_FTDI_RETVAL(FALSE, dc->ft, rv, sizeof stat); + + if (stat[0] != stat[1] || stat[0] != stat[2] || stat[0] != stat[3]) + sr_warn("status: incoherent: %02x %02x %02x %02x", stat[0], stat[1], stat[2], stat[3]); + else + sr_spew("status: %02x", stat[0]); + + return stat[0]; /* array contains the same value 4 times */ +} +SR_PRIV int sq_reset_app(struct dev_context *dc) +{ + static const uint8_t go_app_cmd[] = {0x93}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("reset to app"); + rv = ftdi_write_data(dc->ft, go_app_cmd, sizeof go_app_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof go_app_cmd); + + return SR_OK; +} +SR_PRIV int sq_reset_bl(struct dev_context *dc) +{ + static const uint8_t go_bl_cmd[] = {0x94}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("reset to bl"); + rv = ftdi_write_data(dc->ft, go_bl_cmd, sizeof go_bl_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof go_bl_cmd); + + return SR_OK; + +} +SR_PRIV int sq_get_devid_from_eeprom(struct dev_context *dc, uint8_t *devid) +{ + uint16_t val1, val2; + int rv; + + if (!dc) return SR_ERR; + + if ((rv = ftdi_read_eeprom_location(dc->ft, 0x12, &val1)) < 0) { + sr_err("Failed to read EEPROM index 0x12 (%d): %s.", + rv, ftdi_get_error_string(dc->ft)); + return SR_ERR; + } + if ((rv = ftdi_read_eeprom_location(dc->ft, 0x13, &val2)) < 0) { + sr_err("Failed to read EEPROM index 0x13 (%d): %s.", + rv, ftdi_get_error_string(dc->ft)); + return SR_ERR; + } + + devid[0] = (val1 >> 0) & 0xff; + devid[1] = (val1 >> 8) & 0xff; + devid[2] = (val2 >> 0) & 0xff; + + sr_dbg("got devid %02x %02x %02x", devid[0], devid[1], devid[2]); + + return SR_OK; +} + +SR_PRIV int sq_bl_send_devid(struct dev_context *dc, const uint8_t *devid) +{ + uint8_t send_devid_cmd[27] = {0xf1, 0,0,0, 0}; + int rv; + + if (!dc || !devid) return SR_ERR; + + send_devid_cmd[1] = devid[0]; + send_devid_cmd[2] = devid[1]; + send_devid_cmd[3] = devid[2]; + + sr_spew("auth with devid %02x %02x %02x", devid[0], devid[1], devid[2]); + + rv = ftdi_write_data(dc->ft, send_devid_cmd, sizeof send_devid_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_devid_cmd); + return SR_OK; +} +SR_PRIV int sq_bl_spi_chipsel(struct dev_context *dc) +{ + static const uint8_t send_spi_chipsel_cmd[2] = {0x90,0x00}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("SPI chip select"); + + rv = ftdi_write_data(dc->ft, send_spi_chipsel_cmd, sizeof send_spi_chipsel_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_spi_chipsel_cmd); + return SR_OK; +} +SR_PRIV int sq_bl_spi_chipdesel(struct dev_context *dc) +{ + static const uint8_t send_spi_chipdesel_cmd[2] = {0x91,0x00}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("SPI chip deselect"); + + rv = ftdi_write_data(dc->ft, send_spi_chipdesel_cmd, sizeof send_spi_chipdesel_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_spi_chipdesel_cmd); + return SR_OK; +} +SR_PRIV int sq_bl_spi_xfer(struct dev_context *dc, uint8_t val) +{ + uint8_t send_spi_xfer_cmd[2] = {0x92, 0}; + int rv; + uint8_t read = 0; + + if (!dc) return SR_ERR; + + rv = ftdi_write_data(dc->ft, send_spi_xfer_cmd, sizeof send_spi_xfer_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_spi_xfer_cmd); + + rv = read_ftdi_blocking(dc->ft, &read, 1); + CHECK_FTDI_RETVAL(FALSE, dc->ft, rv, (size_t)1); + + sr_spew("SPI xfer: %02x -> %02x", val, read); + + return read; +} + +SR_PRIV int sq_app_cancel_capture(struct dev_context *dc) +{ + static const uint8_t send_capture_cancel_cmd[] = {0xf0,0x00}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("cancel sc/wft"); + + rv = ftdi_write_data(dc->ft, send_capture_cancel_cmd, sizeof send_capture_cancel_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_capture_cancel_cmd); + + return SR_OK; +} +SR_PRIV int sq_app_start_capture(struct dev_context *dc) +{ + static const uint8_t send_start_capture_cmd[] = {0xf0,0x01}; + int rv; + + if (!dc) return SR_ERR_BUG; + + sr_spew("sc/wft"); + + rv = ftdi_write_data(dc->ft, send_start_capture_cmd, sizeof send_start_capture_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_start_capture_cmd); + + return SR_OK; +} +SR_PRIV int sq_app_try_get_capture_result(struct dev_context *dc, uint32_t *trig_instant) +{ + uint32_t ti; + uint8_t result[4]; + int rv, todo, done; + + if (!dc) return SR_ERR; + + todo = 4; + done = 0; + + rv = ftdi_read_data(dc->ft, &result[done], todo - done); + CHECK_FTDI_RETVAL(FALSE, dc->ft, rv, (size_t)0, FALSE); + + if (rv == 0) return SR_ERR_ARG; + + done += rv; + + while (done < todo) { + rv = ftdi_read_data(dc->ft, &result[done], todo - done); + CHECK_FTDI_RETVAL(FALSE, dc->ft, rv, (size_t)0, FALSE); + done += rv; + } + + ti = (uint32_t)result[0] | ((uint32_t)result[1] << 8) | ((uint32_t)result[2] << 16); + if (trig_instant) *trig_instant = ti; + + sr_spew("sc/wft -> %06x %02x", ti, result[3]); + + return result[3]; +} +SR_PRIV int sq_app_start_generate_inf(struct dev_context *dc) +{ + static const uint8_t send_start_geninf_cmd[] = {0xf0,0x02}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("start generate inf"); + + rv = ftdi_write_data(dc->ft, send_start_geninf_cmd, sizeof send_start_geninf_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_start_geninf_cmd); + + return SR_OK; +} +SR_PRIV int sq_app_start_capgenmix(struct dev_context *dc) +{ + static const uint8_t send_start_capgenmix_cmd[] = {0xf0,0x03}; + int rv; + + if (!dc) return SR_ERR_BUG; + + sr_spew("start mixed sc/g/wft"); + + rv = ftdi_write_data(dc->ft, send_start_capgenmix_cmd, sizeof send_start_capgenmix_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_start_capgenmix_cmd); + + return SR_OK; +} +SR_PRIV int sq_app_upload_genpattern(struct dev_context *dc, const void *data, size_t len) +{ + static const uint8_t send_upl_genpat_cmd[] = {0xf0,0x05,0xf3}; + int rv; + size_t pos = 0; + + if (!dc || !data || !len) return SR_ERR; + + sr_spew("upload genpatt %zx", len); + + rv = ftdi_write_data(dc->ft, send_upl_genpat_cmd, sizeof send_upl_genpat_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_upl_genpat_cmd); + + /* TODO: use async libftdi api? */ + do { + rv = ftdi_write_data(dc->ft, (const uint8_t *)data + pos, len - pos); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, len, FALSE); + + pos += rv; + } while (pos < len); + + return SR_OK; +} +SR_PRIV int sq_app_download_capture(struct dev_context *dc, void *data, size_t len) +{ + static const uint8_t send_dl_capt_cmd[] = {0xf0,0x06}; + int rv; + + if (!dc || !data || !len) return SR_ERR; + + sr_spew("download capture %zx", len); + + rv = ftdi_write_data(dc->ft, send_dl_capt_cmd, sizeof send_dl_capt_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_dl_capt_cmd); + + rv = read_ftdi_blocking(dc->ft, data, len); + CHECK_FTDI_RETVAL(FALSE, dc->ft, rv, len); + + return SR_OK; +} +SR_PRIV int sq_app_start_generate_once(struct dev_context *dc) +{ + static const uint8_t send_start_genonce_cmd[] = {0xf0,0x07}; + int rv; + + if (!dc) return SR_ERR; + + sr_spew("start generate once"); + + rv = ftdi_write_data(dc->ft, send_start_genonce_cmd, sizeof send_start_genonce_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_start_genonce_cmd); + + return SR_OK; +} + +SR_PRIV int sq_app_apply_settings(struct dev_context *dc, const struct sq_app_settings *sett) +{ + uint8_t send_sett_cmd[25] = {0xf1, 0}; + uint8_t *blob; + int rv; + + if (!dc || !sett) return SR_ERR; + + blob = &send_sett_cmd[1]; /* so i wont mess up offsets while writing this code */ + + blob[0x00] = sett->trigscale_us; + blob[0x01] = sett->clockfreq & 0xff; + blob[0x02] = (sett->clockfreq>>8) & 0xff; + blob[0x03] = sett->trigger_pw_scale & 0xff; + blob[0x04] = (sett->trigger_pw_scale>>8) & 0xff; + blob[0x05] = sett->memsetting1[0]; + blob[0x06] = sett->memsetting1[1]; + blob[0x07] = sett->memsetting1[2]; + blob[0x08] = sett->memsetting2[0]; + blob[0x09] = sett->memsetting2[1]; + blob[0x0a] = sett->memsetting2[2]; + blob[0x0b] = sett->memsetting3[0]; + blob[0x0c] = sett->memsetting3[1]; + blob[0x0d] = sett->memsetting3[2]; + blob[0x0e] = 0; + blob[0x0f] = sett->ntrigsteps; + blob[0x10] = 0xf0; + blob[0x11] = 0x0f; + blob[0x12] = sett->chanoutmap; + blob[0x13] = sett->voltage[0]; + blob[0x14] = sett->voltage[1]; + blob[0x15] = 0x32; + blob[0x16] = sett->capture ? 0x01 : 0x00; + blob[0x17] = sett->generate ? 0x01 : 0x00; + + sr_spew("apply settings"); /* TODO: print info about applied settings */ + + rv = ftdi_write_data(dc->ft, send_sett_cmd, sizeof send_sett_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_sett_cmd); + + return SR_OK; +} +SR_PRIV int sq_app_apply_triggers(struct dev_context *dc, const struct sq_app_trigstep *steps, size_t nsteps) +{ + uint8_t *send_trig_cmd; + size_t i, blobsize; + uint32_t bitfield; + int rv; + + if (!dc || !steps || !nsteps) return SR_ERR; + + /* nsteps shouldn't exceed 8 or so */ + blobsize = 1 + nsteps * 4; + send_trig_cmd = (uint8_t *)g_malloc0(blobsize); + send_trig_cmd[0] = 0xf4; + + for (i = 0; i < nsteps; ++i) { + bitfield = 0; + + bitfield |= (steps[i].level ? 1 : 0) << 31; + bitfield |= (steps[i].pw_max & 0x1ff) << 21; + bitfield |= (steps[i].lvloverride ? 1 : 0) << 20; + bitfield |= (steps[i].pw_min & 0x1ff) << 10; + bitfield |= (steps[i].ch_ignore & 0xf) << 6; + bitfield |= (steps[i].nomax ? 1 : 0) << 5; + bitfield |= (steps[i].nomin ? 1 : 0) << 4; + bitfield |= (steps[i].ch_hi_rise_lo_fall & 0xf) << 0; + + send_trig_cmd[1 + i*4 + 0] = (bitfield>> 0)&0xff; + send_trig_cmd[1 + i*4 + 1] = (bitfield>> 8)&0xff; + send_trig_cmd[1 + i*4 + 2] = (bitfield>>16)&0xff; + send_trig_cmd[1 + i*4 + 3] = (bitfield>>14)&0xff; + } + + sr_spew("apply triggers"); /* TODO: print info about applied triggers */ + + rv = ftdi_write_data(dc->ft, send_trig_cmd, blobsize); + g_free(send_trig_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_trig_cmd); + + return SR_OK; +} + +SR_PRIV int sq_app_apply_default_settings(struct dev_context *dc) +{ + /* buffer sizes meant for SQ25, but won't hurt on others I guess */ + static const uint8_t send_sdef_cmd[25] = { + 0xf1, + 0x01, 0x04, 0x00, 0x00, 0x00, 0x48, 0xe8, 0x01, + 0x48, 0xe8, 0x01, 0x08, 0x28, 0xf1, 0x00, 0x00, + 0xf0, 0x0f, 0x0f, 0x81, 0x4b, 0x32, 0x01, 0x00 + }; + int rv; + + sr_spew("apply default settings"); + + rv = ftdi_write_data(dc->ft, send_sdef_cmd, sizeof send_sdef_cmd); + CHECK_FTDI_RETVAL(TRUE, dc->ft, rv, sizeof send_sdef_cmd); + + return SR_OK; +} + + + +/* higher-level routines (finally) */ +SR_PRIV int scanaquad_init(struct dev_context *dc) +{ + size_t i; + int rv; + + sr_info("scanaquad: init!"); + + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "couldn't get status"); + + if (rv == sq_status_app) { + rv = sq_app_cancel_capture(dc); + CHECK_SQ_RETVAL(rv, "couldn't cancel capture/wait-for-trigger"); + } + + rv = sq_reset_bl(dc); + CHECK_SQ_RETVAL(rv, "couldn't reset to bootloader"); + + /* wait until we are in bootloader mode */ + for (i = 0; i < 0x1000; ++i) { + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "couldn't get status"); + if (rv != sq_status_app) break; + } + if (rv == sq_status_app) { + sr_err("Failed to enter bootloader mode (timed out)"); + return SR_ERR; + } + + if (!dc->has_devid) { + rv = sq_get_devid_from_eeprom(dc, &dc->devid[0]); + CHECK_SQ_RETVAL(rv, "couldn't get magic device ID"); + dc->has_devid = TRUE; + } + + rv = sq_bl_send_devid(dc, dc->devid); + CHECK_SQ_RETVAL(rv, "couldn't send magic device ID"); + + for (i = 0; i < 0x1000 /* should be enough */; ++i) { + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "couldn't get status"); + if (rv == sq_status_bl_auth) break; + } + if (rv != sq_status_bl_auth) { + sr_err("Failed to authenticate ScanaQuad device (timed out)... devid=%02x %02x %02x", + dc->devid[0], dc->devid[1], dc->devid[2]); + return SR_ERR; + } + + rv = sq_reset_app(dc); + CHECK_SQ_RETVAL(rv, "couldn't reset to application"); + + for (i = 0; i < 0x1000 /* should be enough */; ++i) { + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "couldn't get status"); + if (rv == sq_status_app) break; + } + if (rv != sq_status_app) { + sr_err("Failed to enter application mode (timed out)"); + return SR_ERR; + } + + return sq_app_apply_default_settings(dc); +} +SR_PRIV int scanaquad_cancel_all(struct dev_context *dc) +{ + int rv; + + sr_spew("cancel everything"); + + dc->acq_started = FALSE; + + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "Failed to get ScanaQuad status"); + + if (rv == sq_status_app) { + /* cancel ongoing capture/wait-for-trigger */ + rv = sq_app_cancel_capture(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing SC/WFT"); + + /* cancel ongoing pattern generation */ + rv = scanaquad_apply_current_settings(dc, TRUE); + CHECK_SQ_RETVAL(rv, "Failed to apply default settings"); + + /* cancel ongoing capture/wait-for-trigger */ + rv = sq_app_cancel_capture(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing SC/WFT"); + + /* cancel ongoing pattern generation */ + rv = scanaquad_apply_current_settings(dc, TRUE); + CHECK_SQ_RETVAL(rv, "Failed to apply default settings"); + } + + return SR_OK; +} + +SR_PRIV int scanaquad_apply_current_settings(struct dev_context *dc, gboolean passive) +{ + /* if passive: + * * no triggers + * * set to capture mode(?) */ + + uint64_t samplim; + uint32_t ms1, ms2, ms3; + struct sq_app_settings sett; + + /* clockfreq == 100000 / samplerate_kHz */ + sett.clockfreq = (uint16_t)((100000uLL*1000) / dc->samplerate); + sett.voltage[0] = dc->voltage.level; + sett.voltage[1] = passive ? 0x4b : dc->voltage.thresh; + sett.chanoutmap = 0x0f; /* TODO: hardcoded: all inputs */ + + /* TODO: more than just capture mode */ + samplim = dc->limit_samples / 4; + /* sample limit vs LA physical memory size check is done in api.c */ + ms1 = samplim; + ms2 = samplim; + ms3 = (uint32_t)(dc->memsize_max * (1 - dc->capture_ratio * 0.01)); + + sett.memsetting1[0] = (ms1>> 0) & 0xff; + sett.memsetting1[1] = (ms1>> 8) & 0xff; + sett.memsetting1[2] = (ms1>>16) & 0xff; + sett.memsetting2[0] = (ms2>> 0) & 0xff; + sett.memsetting2[1] = (ms2>> 8) & 0xff; + sett.memsetting2[2] = (ms2>>16) & 0xff; + sett.memsetting3[0] = (ms3>> 0) & 0xff; + sett.memsetting3[1] = (ms3>> 8) & 0xff; + sett.memsetting3[2] = (ms3>>16) & 0xff; + + /* TODO */ + sett.trigscale_us = 0x01; + sett.ntrigsteps = 0; + sett.trigger_pw_scale = 0; + sett.capture = TRUE; + sett.generate = FALSE; + + dc->MS_capture = ms1; + dc->MS_generate = ms2 - ms1; + + return sq_app_apply_settings(dc, &sett); +} +SR_PRIV int scanaquad_apply_current_triggers(struct dev_context *dc) +{ + (void)dc; + return SR_OK; /* HUGE TODO */ +} + + +/* finally, we can implement the higher-level functions. */ + + +SR_PRIV int scanaquad_acquisition_start(struct dev_context *dc) +{ + int rv; + + if (!dc) return SR_ERR; + + if (dc->acq_started) return SR_ERR_BUG; + + sr_spew("data acquisition start"); + + rv = scanaquad_cancel_all(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing ScanaQuad transfers"); + + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "Failed to get current device state"); + if (rv != sq_status_app) { + sr_err("Device not in application mode."); + return SR_ERR; + } + + rv = scanaquad_apply_current_settings(dc, FALSE); + if (rv == SR_OK) rv = scanaquad_apply_current_triggers(dc); + if (rv < 0) { + /* rollback */ + scanaquad_apply_current_settings(dc, TRUE); + sq_app_cancel_capture(dc); + } + CHECK_SQ_RETVAL(rv, "Failed to apply device settings"); + + rv = sq_get_status(dc); + CHECK_SQ_RETVAL(rv, "Failed to get current device state"); + if (rv != sq_status_app) { + sr_err("Device not in application mode."); + return SR_ERR; + } + + rv = sq_app_cancel_capture(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing ScanaQuad transfers"); + + rv = sq_app_start_capture(dc); + CHECK_SQ_RETVAL(rv, "Failed to start capture"); + + dc->acq_started = TRUE; + + return SR_OK; +} + +static void scanaquad_unpack_samples(uint8_t *rdata, size_t totalunpackbytes) +{ + size_t i; + + for (i = totalunpackbytes / 2; i > 0; --i) { + rdata[i*2-1] = (rdata[i-1] & 0xf0) >> 4; + rdata[i*2-2] = (rdata[i-1] & 0x0f) >> 0; + } +} + +SR_PRIV int scanaquad_acquisition_finish(struct sr_dev_inst *sdi, struct dev_context *dc) +{ + struct sr_datafeed_packet packet1, packet2, packet3; + struct sr_datafeed_logic logic1, logic3; + size_t nbytes; + uint8_t *rdata; + uint32_t trig_instant; + int rv; + + if (!dc || !dc->acq_started) return SR_ERR_BUG; + + sr_spew("gracefully finish acquisition"); + + trig_instant = dc->trig_instant; + dc->acq_started = FALSE; + + sr_spew("trig_instant = %u", trig_instant); + + nbytes = dc->MS_capture * 2; + /* we need 2x the amount of bytes we'll read from the device, as these are + * currently packed per nybbles, while sigrok wants the samples in separate + * bytes */ + rdata = g_try_malloc(nbytes * 2); + if (!rdata) { + sr_err("Out of memory: cannot download captured samples"); + + /* rollback */ + scanaquad_apply_current_settings(dc, TRUE); + sq_app_cancel_capture(dc); + + return SR_ERR; + } + /* convert from trig_instant units (MS1*16) to bytes */ + trig_instant = (trig_instant * 2) / 8; + + rv = sq_app_cancel_capture(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing ScanaQuad transfers"); + + rv = sq_app_download_capture(dc, rdata, nbytes); + CHECK_SQ_RETVAL(rv, "Failed to download captured samples"); + + rv = sq_app_cancel_capture(dc); + CHECK_SQ_RETVAL(rv, "Failed to cancel ongoing ScanaQuad transfers"); + + rv = scanaquad_apply_current_settings(dc, TRUE); + CHECK_SQ_RETVAL(rv, "Failed to reset ScanaQuad settings"); + + scanaquad_unpack_samples(rdata, nbytes * 2); + + /* 3 packets, for trigger indicator */ + logic1.length = trig_instant; + logic1.unitsize = 1; + logic1.data = rdata; + packet1.type = SR_DF_LOGIC; + packet1.payload = &logic1; + + packet2.type = SR_DF_TRIGGER; + packet2.payload = NULL; + + logic3.length = (nbytes * 2) - trig_instant; + logic3.unitsize = 1; + logic3.data = rdata + trig_instant; + packet3.type = SR_DF_LOGIC; + packet3.payload = &logic3; + + sr_spew("data sendoff! 2nb=%zu l1=%zu l3=%zu", nbytes*2, logic1.length, logic3.length); + + sr_session_send(sdi, &packet1); + sr_session_send(sdi, &packet2); + sr_session_send(sdi, &packet3); + + /* i hope this is fine to do? seems like it does work just fine with pulseview */ + g_free(rdata); + + return SR_OK; +} +SR_PRIV int scanaquad_acquisition_stop(struct sr_dev_inst *sdi, struct dev_context *dc) +{ + int rv; + + sr_spew("force-stop acquisition"); + + if (!dc->acq_started) { + sr_spew("already stopped"); + return SR_OK; + } + + rv = sq_app_try_get_capture_result(dc, &dc->trig_instant); + if (rv == SR_ERR_ARG) { /* still busy */ + sr_info("Canceling capture"); + return scanaquad_cancel_all(dc); + } else if (rv < 0) { + sr_err("Cannot get device state / capture result"); + return SR_ERR; + } else if (rv == SQ_APP_START_CAPTURE_SUCCESS) { + return scanaquad_acquisition_finish(sdi, dc); + } else { + sr_err("Error during capture: %02x", rv); + return SR_ERR_BUG; + } +} + +SR_PRIV int scanaquad_receive_data(int fd, int revents, void *cb_data) +{ + struct sr_dev_inst *sdi; + struct dev_context *dc; + int rv; + + (void)fd; + (void)revents; + + if (!(sdi = cb_data)) return TRUE; + if (!(dc = sdi->priv)) return TRUE; + if (!dc->ft) return TRUE; + + /*if (revents != G_IO_IN) return TRUE;*/ + + rv = sq_app_try_get_capture_result(dc, &dc->trig_instant); + if (rv == SR_ERR_ARG) { /* still busy */ + sr_spew("Acquisition still busy..."); + return TRUE; + } else if (rv < 0) { + sr_err("Cannot get device state / capture result"); + sr_dev_acquisition_stop(sdi); + return FALSE; + } else if (rv == SQ_APP_START_CAPTURE_SUCCESS) { + rv = scanaquad_acquisition_finish(sdi, dc); + if (rv == SR_ERR) { + sr_err("acquisition_finish failed"); + sr_dev_acquisition_stop(sdi); + return FALSE; + } + + sr_spew("acquisition finished!"); + sr_dev_acquisition_stop(sdi); + return TRUE; + } else { + sr_err("Error during acquisition: %02x", rv); + sr_dev_acquisition_stop(sdi); + return FALSE; + } +} diff --git a/src/hardware/ikalogic-scanaquad/protocol.h b/src/hardware/ikalogic-scanaquad/protocol.h new file mode 100644 index 00000000..a3f9fdff --- /dev/null +++ b/src/hardware/ikalogic-scanaquad/protocol.h @@ -0,0 +1,168 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2021 sys64738 , + * haskal + * + * 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 . + */ + +#ifndef LIBSIGROK_HARDWARE_IKALOGIC_SCANAQUAD_PROTOCOL_H +#define LIBSIGROK_HARDWARE_IKALOGIC_SCANAQUAD_PROTOCOL_H + +#include +#include +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "ikalogic-scanaquad" + +#define MAX_MEMSIZE_SQ50 0x03d090 + +/* + * For info on how the ScanaQuad works, see our docs here: + * https://git.lain.faith/BLAHAJ/sq50-re/wiki + */ + +struct voltage_setting { + uint8_t level; + uint8_t thresh; +}; + +struct dev_context { + struct ftdi_context *ft; + + /* settings for next capture */ + uint64_t samplerate; + uint64_t limit_samples; /* samples */ + uint64_t capture_ratio; /* 0..100 */ + struct voltage_setting voltage; + int16_t voltage_idx; + + uint32_t trig_instant; + + uint32_t MS_capture; + uint32_t MS_generate; + + uint32_t memsize_max; + int devtype; /* 25, 50, 100 or 200 for SQ25, SQ50, and so on */ + uint8_t devid[3]; + gboolean has_devid; + + gboolean acq_started; +}; + +enum sq_status { + sq_status_bl_auth = 0x01, + sq_status_bl_boot = 0x09, + sq_status_app = 0x22 +}; +struct sq_app_settings { + uint16_t clockfreq; + uint16_t trigger_pw_scale; + uint8_t memsetting1[3]; + uint8_t memsetting2[3]; + uint8_t memsetting3[3]; + uint8_t voltage[2]; + uint8_t trigscale_us; + uint8_t ntrigsteps; + uint8_t chanoutmap; + + gboolean capture; + gboolean generate; +}; +struct sq_app_trigstep { + uint16_t pw_min; + uint16_t pw_max; + + uint8_t ch_ignore; /* set bit in this mask to ignore a channel */ + uint8_t ch_hi_rise_lo_fall; /* 1 in mask => match high/rise for chan, else low/fall */ + + gboolean level; /* false: edge */ + gboolean lvloverride; + gboolean nomax; + gboolean nomin; +}; + +#define SQ_APP_START_CAPTURE_SUCCESS 0xdd + +SR_PRIV struct dev_context *sq_new(struct ftdi_context *ft); +SR_PRIV void sq_destroy(struct dev_context *dc); + +/* low-level wire protocol functions. see + * https://git.lain.faith/BLAHAJ/sq50-re/wiki/USB-protocol for docs on how + * these work. */ + +/* general commands */ +SR_PRIV int sq_get_status(struct dev_context *dc); +SR_PRIV int sq_reset_app(struct dev_context *dc); +SR_PRIV int sq_reset_bl (struct dev_context *dc); +SR_PRIV int sq_get_devid_from_eeprom(struct dev_context *dc, uint8_t *devid); + +/* bootloader mode commands */ +SR_PRIV int sq_bl_send_devid(struct dev_context *dc, const uint8_t *devid); +SR_PRIV int sq_bl_spi_chipsel(struct dev_context *dc); +SR_PRIV int sq_bl_spi_chipdesel(struct dev_context *dc); +SR_PRIV int sq_bl_spi_xfer(struct dev_context *dc, uint8_t val); +/* two aliases for clarity */ +static inline int sq_bl_spi_xfer_write(struct dev_context *dc, uint8_t val) +{ + return sq_bl_spi_xfer(dc, val); +} +static inline int sq_bl_spi_xfer_read(struct dev_context *dc) +{ + return sq_bl_spi_xfer(dc, 0xff); +} + +/* application mode commands */ +/* 0xf0 0x0X commands */ +SR_PRIV int sq_app_cancel_capture(struct dev_context *dc); +SR_PRIV int sq_app_start_capture(struct dev_context *dc); +SR_PRIV int sq_app_try_get_capture_result(struct dev_context *dc, uint32_t *trig_instant); +SR_PRIV int sq_app_start_generate_inf(struct dev_context *dc); +SR_PRIV int sq_app_start_capgenmix(struct dev_context *dc); +static inline int sq_app_try_get_capgenmix_result(struct dev_context *dc, uint32_t *trig_instant) { + return sq_app_try_get_capture_result(dc, trig_instant); +} +/* NOTE: at this point, no checks are done whether the length of the data is + * consistent with the current capture/genpattern memory size in the + * device's settings */ +SR_PRIV int sq_app_upload_genpattern(struct dev_context *dc, const void *data, size_t len); +SR_PRIV int sq_app_download_capture(struct dev_context *dc, void *data, size_t len); +SR_PRIV int sq_app_start_generate_once(struct dev_context *dc); + +/* 0xf1, 0xf4 */ +SR_PRIV int sq_app_apply_settings(struct dev_context *dc, const struct sq_app_settings *sett); +SR_PRIV int sq_app_apply_triggers(struct dev_context *dc, const struct sq_app_trigstep *steps, size_t nsteps); + +/* useful at device init & reset, as it cancels ongoing signal generator stuff */ +SR_PRIV int sq_app_apply_default_settings(struct dev_context *dc); + +/* higher-level stuff used by api.c */ + +SR_PRIV int scanaquad_init(struct dev_context *dc); +SR_PRIV int scanaquad_cancel_all(struct dev_context *dc); +SR_PRIV int scanaquad_apply_current_settings(struct dev_context *dc, gboolean passive); +SR_PRIV int scanaquad_apply_current_triggers(struct dev_context *dc); + +SR_PRIV int scanaquad_acquisition_start(struct dev_context *dc); +/* stuff to do when an acquisition ended successfully */ +SR_PRIV int scanaquad_acquisition_finish(struct sr_dev_inst *sdi, struct dev_context *dc); +/* either finish when possible right now, or abort */ +SR_PRIV int scanaquad_acquisition_stop(struct sr_dev_inst *sdi, struct dev_context *dc); +SR_PRIV int scanaquad_receive_data(int fd, int revents, void *cb_data); + +#endif diff --git a/src/hardware/pipistrello-ols/protocol.c b/src/hardware/pipistrello-ols/protocol.c index f0ef6805..a5a59546 100644 --- a/src/hardware/pipistrello-ols/protocol.c +++ b/src/hardware/pipistrello-ols/protocol.c @@ -90,7 +90,7 @@ SR_PRIV int p_ols_open(struct dev_context *devc) return SR_ERR; } - if ((ret = ftdi_usb_purge_buffers(devc->ftdic)) < 0) { + if ((ret = ftdi_tcioflush(devc->ftdic)) < 0) { sr_err("Failed to purge FTDI RX/TX buffers (%d): %s.", ret, ftdi_get_error_string(devc->ftdic)); goto err_open_close_ftdic;