]> sigrok.org Git - libsigrok.git/commitdiff
juntek-jds6600: implement device support, tested with Joy-IT JDS6600
authorGerhard Sittig <redacted>
Thu, 5 Oct 2023 15:21:07 +0000 (17:21 +0200)
committerGerhard Sittig <redacted>
Sat, 7 Oct 2023 13:36:09 +0000 (15:36 +0200)
Implement support for the essential signal generator part of the device,
tested with a 60MHz Joy-IT JDS6600 model: Two channels, enable/disable,
waveform, frequency, amplitude, offset, duty cycle selection, phase
between channels.

This version does not support external signals and frequency measurement
nor counting. Upload/download of arbitrary waveforms could work from the
serial communication's perspective, but sigrok lacks infrastructure/API
for waveform exchange or communication of blobs.

Tested with sigrok-cli and getters/setters. Quickly tested with SmuView.

configure.ac
src/hardware/juntek-jds6600/api.c
src/hardware/juntek-jds6600/protocol.c
src/hardware/juntek-jds6600/protocol.h

index 60e97a65b576994deea4a30991fa3f14de8a2f9b..90a3c1c76c576c5187763e1ca115f322f5dd9865 100644 (file)
@@ -342,7 +342,7 @@ SR_DRIVER([Ikalogic Scanalogic-2], [ikalogic-scanalogic2], [libusb])
 SR_DRIVER([Ikalogic Scanaplus], [ikalogic-scanaplus], [libftdi])
 SR_DRIVER([IPDBG LA], [ipdbg-la])
 SR_DRIVER([ITECH IT8500], [itech-it8500], [serial_comm])
-SR_DRIVER([JUNTEK JDS6600], [juntek-jds6600])
+SR_DRIVER([JUNTEK JDS6600], [juntek-jds6600], [serial_comm])
 SR_DRIVER([Kecheng KC-330B], [kecheng-kc-330b], [libusb])
 SR_DRIVER([KERN scale], [kern-scale], [serial_comm])
 SR_DRIVER([Kingst LA2016], [kingst-la2016], [libusb])
index 54cdfba45974371c31e6d5cb52262fe2f3ae1939..ecc879a4feca6b6e964acedfee3ce0ed063786ed 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+#include "config.h"
+
 #include "protocol.h"
 
-static struct sr_dev_driver juntek_jds6600_driver_info;
+#define DFLT_SERIALCOMM        "115200/8n1"
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
-{
-       struct drv_context *drvc;
-       GSList *devices;
+#define VENDOR_TEXT    "Juntek"
+#define MODEL_TEXT     "JDS6600"
 
-       (void)options;
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
 
-       devices = NULL;
-       drvc = di->context;
-       drvc->instances = NULL;
+static const uint32_t drvopts[] = {
+       SR_CONF_SIGNAL_GENERATOR,
+};
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_ENABLED | SR_CONF_SET,
+       SR_CONF_PHASE | SR_CONF_GET | SR_CONF_SET,
+};
 
-       return devices;
-}
+static const uint32_t devopts_cg[] = {
+       SR_CONF_ENABLED | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_PATTERN_MODE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_OUTPUT_FREQUENCY | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_AMPLITUDE | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_OFFSET | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_DUTY_CYCLE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
 
-static int dev_open(struct sr_dev_inst *sdi)
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
 {
-       (void)sdi;
+       GSList *devices;
+       const char *conn, *serialcomm;
+       struct sr_dev_inst *sdi;
+       struct dev_context *devc;
+       struct sr_serial_dev_inst *ser;
+       int ret;
+       size_t ch_idx, idx, ch_nr;
+       char cg_name[8];
+       struct sr_channel_group *cg;
+       struct sr_channel *ch;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       devices = NULL;
 
-       return SR_OK;
-}
+       conn = NULL;
+       serialcomm = DFLT_SERIALCOMM;
+       (void)sr_serial_extract_options(options, &conn, &serialcomm);
+       if (!conn)
+               return devices;
+
+       ser = sr_serial_dev_inst_new(conn, serialcomm);
+       if (!ser)
+               return devices;
+       ret = serial_open(ser, SERIAL_RDWR);
+       if (ret != SR_OK)
+               return devices;
+
+       sdi = g_malloc0(sizeof(*sdi));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->inst_type = SR_INST_USB;
+       sdi->conn = ser;
+       sdi->connection_id = g_strdup(conn);
+       devc = g_malloc0(sizeof(*devc));
+       sdi->priv = devc;
+
+       ret = jds6600_identify(sdi);
+       if (ret != SR_OK)
+               goto fail;
+       ret = jds6600_setup_devc(sdi);
+       if (ret != SR_OK)
+               goto fail;
+       (void)serial_close(ser);
+
+       sdi->vendor = g_strdup(VENDOR_TEXT);
+       sdi->model = g_strdup(MODEL_TEXT);
+       if (devc->device.serial_number)
+               sdi->serial_num = g_strdup(devc->device.serial_number);
+
+       ch_idx = 0;
+       for (idx = 0; idx < MAX_GEN_CHANNELS; idx++) {
+               ch_nr = idx + 1;
+               snprintf(cg_name, sizeof(cg_name), "CH%zu", ch_nr);
+               cg = sr_channel_group_new(sdi, cg_name, NULL);
+               (void)cg;
+               ch = sr_channel_new(sdi, ch_idx,
+                       SR_CHANNEL_ANALOG, FALSE, cg_name);
+               cg->channels = g_slist_append(cg->channels, ch);
+               ch_idx++;
+       }
 
-static int dev_close(struct sr_dev_inst *sdi)
-{
-       (void)sdi;
+       devices = g_slist_append(devices, sdi);
+       return std_scan_complete(di, devices);
 
-       /* TODO: get handle from sdi->conn and close it. */
+fail:
+       (void)serial_close(ser);
+       sr_serial_dev_inst_free(ser);
+       if (devc) {
+               g_free(devc->device.serial_number);
+               g_free(devc->waveforms.fw_codes);
+               g_free(devc->waveforms.names);
+       }
+       g_free(devc);
+       sr_dev_inst_free(sdi);
 
-       return SR_OK;
+       return devices;
 }
 
 static int config_get(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
+       struct dev_context *devc;
        int ret;
+       size_t cg_idx;
+       struct devc_wave *waves;
+       struct devc_chan *chan;
+       double dvalue;
+       const char *s;
+
+       devc = sdi ? sdi->priv : NULL;
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_CONN:
+                       if (!sdi->connection_id)
+                               return SR_ERR_NA;
+                       *data = g_variant_new_string(sdi->connection_id);
+                       return SR_OK;
+               case SR_CONF_PHASE:
+                       if (!devc)
+                               return SR_ERR_NA;
+                       ret = jds6600_get_phase_chans(sdi);
+                       if (ret != SR_OK)
+                               return SR_ERR_NA;
+                       dvalue = devc->channels_phase;
+                       *data = g_variant_new_double(dvalue);
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       if (!devc)
+               return SR_ERR_NA;
+       ret = g_slist_index(sdi->channel_groups, cg);
+       if (ret < 0)
+               return SR_ERR_NA;
+       cg_idx = (size_t)ret;
+       if (cg_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_NA;
+       chan = &devc->channel_config[cg_idx];
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_ENABLED:
+               ret = jds6600_get_chans_enable(sdi);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               *data = g_variant_new_boolean(chan->enabled);
+               return SR_OK;
+       case SR_CONF_PATTERN_MODE:
+               ret = jds6600_get_waveform(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               waves = &devc->waveforms;
+               s = waves->names[chan->waveform_index];
+               *data = g_variant_new_string(s);
+               return SR_OK;
+       case SR_CONF_OUTPUT_FREQUENCY:
+               ret = jds6600_get_frequency(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->output_frequency;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       case SR_CONF_AMPLITUDE:
+               ret = jds6600_get_amplitude(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->amplitude;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       case SR_CONF_OFFSET:
+               ret = jds6600_get_offset(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->offset;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
+       case SR_CONF_DUTY_CYCLE:
+               ret = jds6600_get_dutycycle(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               dvalue = chan->dutycycle;
+               *data = g_variant_new_double(dvalue);
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int config_set(uint32_t key, GVariant *data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       struct devc_chan *chan;
+       size_t cg_idx;
+       double dvalue;
+       gboolean on;
+       int ret, idx;
+
+       devc = sdi ? sdi->priv : NULL;
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_ENABLED:
+                       /* Enable/disable all channels at the same time. */
+                       on = g_variant_get_boolean(data);
+                       if (!devc)
+                               return SR_ERR_ARG;
+                       cg_idx = devc->device.channel_count_gen;
+                       while (cg_idx) {
+                               chan = &devc->channel_config[--cg_idx];
+                               chan->enabled = on;
+                       }
+                       ret = jds6600_set_chans_enable(sdi);
+                       if (ret != SR_OK)
+                               return SR_ERR_NA;
+                       return SR_OK;
+               case SR_CONF_PHASE:
+                       if (!devc)
+                               return SR_ERR_ARG;
+                       dvalue = g_variant_get_double(data);
+                       devc->channels_phase = dvalue;
+                       ret = jds6600_set_phase_chans(sdi);
+                       if (ret != SR_OK)
+                               return SR_ERR_NA;
+                       return SR_OK;
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       ret = g_slist_index(sdi->channel_groups, cg);
+       if (ret < 0)
+               return SR_ERR_NA;
+       cg_idx = (size_t)ret;
+       if (cg_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_NA;
+       chan = &devc->channel_config[cg_idx];
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+       case SR_CONF_ENABLED:
+               on = g_variant_get_boolean(data);
+               chan->enabled = on;
+               ret = jds6600_set_chans_enable(sdi);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_PATTERN_MODE:
+               waves = &devc->waveforms;
+               idx = std_str_idx(data, waves->names, waves->names_count);
+               if (idx < 0)
+                       return SR_ERR_NA;
+               if ((size_t)idx >= waves->names_count)
+                       return SR_ERR_NA;
+               chan->waveform_index = idx;
+               chan->waveform_code = waves->fw_codes[chan->waveform_index];
+               ret = jds6600_set_waveform(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_OUTPUT_FREQUENCY:
+               dvalue = g_variant_get_double(data);
+               chan->output_frequency = dvalue;
+               ret = jds6600_set_frequency(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_AMPLITUDE:
+               dvalue = g_variant_get_double(data);
+               chan->amplitude = dvalue;
+               ret = jds6600_set_amplitude(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_OFFSET:
+               dvalue = g_variant_get_double(data);
+               chan->offset = dvalue;
+               ret = jds6600_set_offset(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
+       case SR_CONF_DUTY_CYCLE:
+               dvalue = g_variant_get_double(data);
+               chan->dutycycle = dvalue;
+               ret = jds6600_set_dutycycle(sdi, cg_idx);
+               if (ret != SR_OK)
+                       return SR_ERR_NA;
+               return SR_OK;
        default:
-               ret = SR_ERR_NA;
+               return SR_ERR_NA;
        }
-
-       return ret;
 }
 
 static int config_list(uint32_t key, GVariant **data,
        const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
 {
-       int ret;
-
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       double fspec[3];
+
+       if (!cg) {
+               switch (key) {
+               case SR_CONF_SCAN_OPTIONS:
+               case SR_CONF_DEVICE_OPTIONS:
+                       return STD_CONFIG_LIST(key, data, sdi, cg,
+                               scanopts, drvopts, devopts);
+               default:
+                       return SR_ERR_NA;
+               }
+       }
 
-       ret = SR_OK;
+       if (!sdi)
+               return SR_ERR_NA;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_NA;
        switch (key) {
-       /* TODO */
+       case SR_CONF_DEVICE_OPTIONS:
+               *data =std_gvar_array_u32(ARRAY_AND_SIZE(devopts_cg));
+               return SR_OK;
+       case SR_CONF_PATTERN_MODE:
+               waves = &devc->waveforms;
+               *data = std_gvar_array_str(waves->names, waves->names_count);
+               return SR_OK;
+       case SR_CONF_OUTPUT_FREQUENCY:
+               /* Announce range as tupe of min, max, step. */
+               fspec[0] = 0.01;
+               fspec[1] = devc->device.max_output_frequency;
+               fspec[2] = 0.01;
+               *data = std_gvar_min_max_step_array(fspec);
+               return SR_OK;
+       case SR_CONF_DUTY_CYCLE:
+               /* Announce range as tupe of min, max, step. */
+               fspec[0] = 0.0;
+               fspec[1] = 1.0;
+               fspec[2] = 0.001;
+               *data = std_gvar_min_max_step_array(fspec);
+               return SR_OK;
        default:
                return SR_ERR_NA;
        }
-
-       return ret;
 }
 
-static int dev_acquisition_start(const struct sr_dev_inst *sdi)
+static void clear_helper(struct dev_context *devc)
 {
-       /* TODO: configure hardware, reset acquisition state, set up
-        * callbacks and send header packet. */
-
-       (void)sdi;
-
-       return SR_OK;
+       struct devc_wave *waves;
+
+       if (!devc)
+               return;
+
+       g_free(devc->device.serial_number);
+       waves = &devc->waveforms;
+       while (waves->names_count)
+               g_free((char *)waves->names[--waves->names_count]);
+       g_free(waves->names);
+       g_free(waves->fw_codes);
+       if (devc->quick_req)
+               g_string_free(devc->quick_req, TRUE);
 }
 
-static int dev_acquisition_stop(struct sr_dev_inst *sdi)
+static int dev_clear(const struct sr_dev_driver *driver)
 {
-       /* TODO: stop acquisition. */
-
-       (void)sdi;
-
-       return SR_OK;
+       return std_dev_clear_with_callback(driver,
+               (std_dev_clear_callback)clear_helper);
 }
 
 static struct sr_dev_driver juntek_jds6600_driver_info = {
@@ -141,14 +404,14 @@ static struct sr_dev_driver juntek_jds6600_driver_info = {
        .cleanup = std_cleanup,
        .scan = scan,
        .dev_list = std_dev_list,
-       .dev_clear = std_dev_clear,
+       .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,
+       .dev_open = std_serial_dev_open,
+       .dev_close = std_serial_dev_close,
+       .dev_acquisition_start = std_dummy_dev_acquisition_start,
+       .dev_acquisition_stop = std_dummy_dev_acquisition_stop,
        .context = NULL,
 };
 SR_REGISTER_DEV_DRIVER(juntek_jds6600_driver_info);
index c388daed0d164bb480def790d4f128dcc34e5fe2..c6a19f85ec74a041cf958f92002f6afe18a7a86b 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <config.h>
+/*
+ * Juntek JDS6600 is a DDS signal generator.
+ * Often rebranded, goes by different names, among them Joy-IT JDS6600.
+ *
+ * This driver was built using Kristoff Bonne's knowledge as seen in his
+ * MIT licensed Python code for JDS6600 control. For details see the
+ * https://github.com/on1arf/jds6600_python repository.
+ *
+ * Supported features:
+ * - Model detection, which determines the upper output frequency limit
+ *   (15..60MHz models exist).
+ * - Assumes exactly two channels. Other models were not seen out there.
+ * - Per channel configuration of: Waveform, output frequency, amplitude,
+ *   offset, duty cycle.
+ * - Phase between channels is a global property and affects multiple
+ *   channels at the same time (their relation to each other).
+ *
+ * TODO
+ * - Add support for the frequency measurement and/or the counter. This
+ *   feature's availability may depend on or interact with the state of
+ *   other generator channels. Needs consideration of constraints.
+ * - Add support for modulation (sweep, pulse, burst). Add support for
+ *   other "modes"?
+ * - Add support for download/upload of arbitrary waveforms. This needs
+ *   infrastructure in common libsigrok code as well as in applications.
+ *   At the moment "blob transfer" (waveform upload/download) appears to
+ *   not be supported.
+ * - Re-consider parameter value ranges. Frequency depends on the model.
+ *   Amplitude range depends on the model and frequencies and interact
+ *   with the offset offset configuration. Can be -20..+20, or -10..+10,
+ *   or -5..+5 ranges. This implementation caps to the -20..+20 range.
+ *   Many values are passed to the device and may result in capping or
+ *   transformation there in the firwmare.
+ *
+ * Implementation details:
+ * - Communicates via USB CDC at 115200/8n1 (virtual COM port).
+ * - Requests are in text format. Start with a ':' colon, followed by a
+ *   single letter instruction opcode, followed by a number which either
+ *   addresses a parameter (think hardware register) or storage slot for
+ *   an arbitrary waveform. Can be followed by an '=' equals sign and a
+ *   value. Multiple values are comma separated. The line may end in a
+ *   '.' period. Several end-of-line conventions are supported by the
+ *   devices' firmware versions, LF and CR/LF are reported to work.
+ * - Responses also are in text format. Start with a ':' colon, followed
+ *   by an instruction letter, followed by a number (a parameter index,
+ *   or a waveform index), followed by '=' equal sign and one or more
+ *   values. Optionally ending in a '.' period. And ending in the
+ *   firmware's end-of-line. Read requests will have this format.
+ *   Alternatively write requests may just respond with the ":ok"
+ *   text phrase.
+ * - There are four instructions: 'r' to read and 'w' to write parameters
+ *   (think hardware registers, optionaly multi-valued), 'a' to write and
+ *   'b' to read arbitrary waveform data (sequence of sample values).
+ * - Am not aware of a vendor's documentation for the protocol. Joy-IT
+ *   provides the JT-JDS6600-Communication-protocol.pdf document which
+ *   leaves a lot of questions. This sigrok driver implementation used
+ *   a lot of https://github.com/on1arf/jds6600_python knowledge for
+ *   the initial version (MIT licenced Python code by Kristoff Bonne).
+ * - The requests take effect when sent from application code. While
+ *   the requests remain uneffective when typed in interactive terminal
+ *   sessions. Though there are ":ok" responses, the action would not
+ *   happen in the device. It's assumed to be a firmware implementation
+ *   constraint that is essential to keep in mind.
+ * - The right hand side of write requests or read responses can carry
+ *   any number of values, both numbers and text, integers and floats.
+ *   Still some of the parameters (voltages, times, frequencies) come in
+ *   interesting formats. A floating point "mantissa" and an integer code
+ *   for scaling the value. Not an exponent, but some kind of index. In
+ *   addition to an open coded "fixed point" style multiplier that is
+ *   implied and essential, but doesn't show on the wire. Interpretation
+ *   of responses and phrasing of values in requests is arbitrary, this
+ *   "black magic" was found by local experimentation (reading back the
+ *   values which were configured by local UI interaction).
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <math.h>
+#include <string.h>
+
 #include "protocol.h"
 
-SR_PRIV int juntek_jds6600_receive_data(int fd, int revents, void *cb_data)
+#define WITH_ARBWAVE_DOWNLOAD  0 /* Development HACK */
+
+/*
+ * The firmware's maximum response length. Seen when an arbitrary
+ * waveform gets retrieved. Carries 2048 samples in the 0..4095 range.
+ * Plus some decoration around that data.
+ *   :b01=4095,4095,...,4095,<CRLF>
+ */
+#define MAX_RSP_LENGTH (8 + 2048 * 5)
+
+/* Times are in milliseconds. */
+#define DELAY_AFTER_WRITE      10
+#define DELAY_AFTER_FLASH      100
+#define TIMEOUT_READ_CHUNK     20
+#define TIMEOUT_IDENTIFY       200
+
+/* Instruction codes. Read/write parameters/waveforms. */
+#define INSN_WRITE_PARA        'w'
+#define INSN_READ_PARA 'r'
+#define INSN_WRITE_WAVE        'a'
+#define INSN_READ_WAVE 'b'
+
+/* Indices for "register access". */
+enum param_index {
+       IDX_DEVICE_TYPE = 0,
+       IDX_SERIAL_NUMBER = 1,
+       IDX_CHANNELS_ENABLE = 20,
+       IDX_WAVEFORM_CH1 = 21,
+       IDX_WAVEFORM_CH2 = 22,
+       IDX_FREQUENCY_CH1 = 23,
+       IDX_FREQUENCY_CH2 = 24,
+       IDX_AMPLITUDE_CH1 = 25,
+       IDX_AMPLITUDE_CH2 = 26,
+       IDX_OFFSET_CH1 = 27,
+       IDX_OFFSET_CH2 = 28,
+       IDX_DUTYCYCLE_CH1 = 29,
+       IDX_DUTYCYCLE_CH2 = 30,
+       IDX_PHASE_CHANNELS = 31,
+       IDX_ACTION = 32,
+       IDX_MODE = 33,
+       IDX_INPUT_COUPLING = 36,
+       IDX_MEASURE_GATE = 37,
+       IDX_MEASURE_MODE = 38,
+       IDX_COUNTER_RESET = 39,
+       IDX_SWEEP_STARTFREQ = 40,
+       IDX_SWEEP_ENDFREQ = 41,
+       IDX_SWEEP_TIME = 42,
+       IDX_SWEEP_DIRECTION = 43,
+       IDX_SWEEP_MODE = 44,
+       IDX_PULSE_WIDTH = 45,
+       IDX_PULSE_PERIOD = 46,
+       IDX_PULSE_OFFSET = 47,
+       IDX_PULSE_AMPLITUDE = 48,
+       IDX_BURST_COUNT = 49,
+       IDX_BURST_MODE = 50,
+       IDX_SYSTEM_SOUND = 51,
+       IDX_SYSTEM_BRIGHTNESS = 52,
+       IDX_SYSTEM_LANGUAGE = 53,
+       IDX_SYSTEM_SYNC = 54, /* "Tracking" channels? */
+       IDX_SYSTEM_ARBMAX = 55,
+       IDX_PROFILE_SAVE = 70,
+       IDX_PROFILE_LOAD = 71,
+       IDX_PROFILE_CLEAR = 72,
+       IDX_COUNTER_VALUE = 80,
+       IDX_MEAS_VALUE_FREQLOW = 81,
+       IDX_MEAS_VALUE_FREQHI = 82,
+       IDX_MEAS_VALUE_WIDTHHI = 83,
+       IDX_MEAS_VALUE_WIDTHLOW = 84,
+       IDX_MEAS_VALUE_PERIOD = 85,
+       IDX_MEAS_VALUE_DUTYCYCLE = 86,
+       IDX_MEAS_VALUE_U1 = 87,
+       IDX_MEAS_VALUE_U2 = 88,
+       IDX_MEAS_VALUE_U3 = 89,
+};
+
+/* Firmware's codes for waveform selection. */
+enum waveform_index_t {
+       /* 17 pre-defined waveforms. */
+       WAVE_SINE = 0,
+       WAVE_SQUARE = 1,
+       WAVE_PULSE = 2,
+       WAVE_TRIANGLE = 3,
+       WAVE_PARTIAL_SINE = 4,
+       WAVE_CMOS = 5,
+       WAVE_DC = 6,
+       WAVE_HALF_WAVE = 7,
+       WAVE_FULL_WAVE = 8,
+       WAVE_POS_LADDER = 9,
+       WAVE_NEG_LADDER = 10,
+       WAVE_NOISE = 11,
+       WAVE_EXP_RISE = 12,
+       WAVE_EXP_DECAY = 13,
+       WAVE_MULTI_TONE = 14,
+       WAVE_SINC = 15,
+       WAVE_LORENZ = 16,
+       WAVES_COUNT_BUILTIN,
+       /* Up to 60 arbitrary waveforms. */
+       WAVES_ARB_BASE = 100,
+       WAVE_ARB01 = WAVES_ARB_BASE +  1,
+       /* ... */
+       WAVE_ARB60 = WAVES_ARB_BASE + 60,
+       WAVES_PAST_LAST_ARB,
+};
+#define WAVES_COUNT_ARBITRARY  (WAVES_PAST_LAST_ARB - WAVE_ARB01)
+
+static const char *waveform_names[] = {
+       [WAVE_SINE] = "sine",
+       [WAVE_SQUARE] = "square",
+       [WAVE_PULSE] = "pulse",
+       [WAVE_TRIANGLE] = "triangle",
+       [WAVE_PARTIAL_SINE] = "partial-sine",
+       [WAVE_CMOS] = "cmos",
+       [WAVE_DC] = "dc",
+       [WAVE_HALF_WAVE] = "half-wave",
+       [WAVE_FULL_WAVE] = "full-wave",
+       [WAVE_POS_LADDER] = "pos-ladder",
+       [WAVE_NEG_LADDER] = "neg-ladder",
+       [WAVE_NOISE] = "noise",
+       [WAVE_EXP_RISE] = "exp-rise",
+       [WAVE_EXP_DECAY] = "exp-decay",
+       [WAVE_MULTI_TONE] = "multi-tone",
+       [WAVE_SINC] = "sinc",
+       [WAVE_LORENZ] = "lorenz",
+};
+#define WAVEFORM_ARB_NAME_FMT  "arb-%02zu"
+
+/*
+ * Writes a text line to the serial port. Normalizes end-of-line
+ * including trailing period.
+ */
+static int serial_send_textline(const struct sr_dev_inst *sdi,
+       GString *s, unsigned int delay_ms)
+{
+       struct sr_serial_dev_inst *conn;
+       const char *rdptr;
+       size_t padlen, rdlen, wrlen;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       conn = sdi->conn;
+       if (!conn)
+               return SR_ERR_ARG;
+       if (!s)
+               return SR_ERR_ARG;
+
+       /* Trim surrounding whitespace. Normalize end-of-line. */
+       padlen = 4;
+       while (padlen--)
+               g_string_append_c(s, '\0');
+       rdptr = sr_text_trim_spaces(s->str);
+       rdlen = strlen(rdptr);
+       if (rdlen && rdptr[rdlen - 1] == '.')
+               rdlen--;
+       g_string_set_size(s, rdlen);
+       g_string_append_c(s, '.');
+       sr_spew("serial TX data: --> %s", rdptr);
+       g_string_append_c(s, '\r');
+       g_string_append_c(s, '\n');
+       rdlen = strlen(rdptr);
+
+       /* Handle chunked writes, check for transmission errors. */
+       while (rdlen) {
+               ret = serial_write_blocking(conn, rdptr, rdlen, 0);
+               if (ret < 0)
+                       return SR_ERR_IO;
+               wrlen = (size_t)ret;
+               if (wrlen > rdlen)
+                       wrlen = rdlen;
+               rdptr += wrlen;
+               rdlen -= wrlen;
+       }
+
+       if (delay_ms)
+               g_usleep(delay_ms * 1000);
+
+       return SR_OK;
+}
+
+/*
+ * Reads a text line from the serial port. Assumes that only a single
+ * response text line is in flight (does not handle the case of more
+ * receive data following after the first EOL). Transparently deals
+ * with trailing period and end-of-line, so callers need not bother.
+ *
+ * Checks plausibility when the caller specifies conditions to check.
+ * Optionally returns references (and lengths) to the response's RHS.
+ * That's fine because data resides in a caller provided buffer.
+ */
+static int serial_recv_textline(const struct sr_dev_inst *sdi,
+       GString *s, unsigned int delay_ms, unsigned int timeout_ms,
+       gboolean *is_ok, char wants_insn, size_t wants_index,
+       char **rhs_start, size_t *rhs_length)
+{
+       struct sr_serial_dev_inst *ser;
+       char *rdptr;
+       size_t rdlen, got;
+       int ret;
+       guint64 now_us, deadline_us;
+       gboolean has_timedout;
+       char *eol_pos, *endptr;
+       char got_insn;
+       unsigned long got_index;
+
+       if (is_ok)
+               *is_ok = FALSE;
+       if (rhs_start)
+               *rhs_start = NULL;
+       if (rhs_length)
+               *rhs_length = 0;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       ser = sdi->conn;
+       if (!ser)
+               return SR_ERR_ARG;
+
+       g_string_set_size(s, MAX_RSP_LENGTH);
+       g_string_truncate(s, 0);
+
+       /* Arrange for overall receive timeout when caller specified. */
+       now_us = deadline_us = 0;
+       if (timeout_ms) {
+               now_us = g_get_monotonic_time();
+               deadline_us = now_us;
+               deadline_us += timeout_ms * 1000;
+       }
+
+       rdptr = s->str;
+       rdlen = s->allocated_len - 1 - s->len;
+       while (rdlen) {
+               /* Get another chunk of receive data. Check for EOL. */
+               ret = serial_read_blocking(ser, rdptr, rdlen, delay_ms);
+               if (ret < 0)
+                       return SR_ERR_IO;
+               got = (size_t)ret;
+               if (got > rdlen)
+                       got = rdlen;
+               rdptr[got] = '\0';
+               eol_pos = strchr(rdptr, '\n');
+               rdptr += got;
+               rdlen -= got;
+               /* Check timeout expiration upon empty reception. */
+               has_timedout = FALSE;
+               if (timeout_ms && !got) {
+                       now_us = g_get_monotonic_time();
+                       if (now_us >= deadline_us)
+                               has_timedout = TRUE;
+               }
+               if (!eol_pos) {
+                       if (has_timedout)
+                               break;
+                       continue;
+               }
+
+               /* Normalize the received text line. */
+               *eol_pos++ = '\0';
+               rdptr = s->str;
+               (void)sr_text_trim_spaces(rdptr);
+               rdlen = strlen(rdptr);
+               sr_spew("serial RX data: <-- %s", rdptr);
+               if (rdlen && rdptr[rdlen - 1] == '.')
+                       rdptr[--rdlen] = '\0';
+
+               /* Check conditions as requested by the caller. */
+               if (is_ok || wants_insn || rhs_start) {
+                       if (*rdptr != ':') {
+                               sr_dbg("serial read, colon missing");
+                               return SR_ERR_DATA;
+                       }
+                       rdptr++;
+                       rdlen--;
+               }
+               /*
+                * The check for 'ok' is terminal. Does not combine with
+                * responses which carry payload data on their RHS.
+                */
+               if (is_ok) {
+                       *is_ok = strcmp(rdptr, "ok") == 0;
+                       sr_dbg("serial read, 'ok' check %d", *is_ok);
+                       return *is_ok ? SR_OK : SR_ERR_DATA;
+               }
+               /*
+                * Conditional strict checks for caller's expected fields.
+                * Unconditional weaker checks for general structure.
+                */
+               if (wants_insn && *rdptr != wants_insn) {
+                       sr_dbg("serial read, unexpected insn");
+                       return SR_ERR_DATA;
+               }
+               got_insn = *rdptr++;
+               switch (got_insn) {
+               case INSN_WRITE_PARA:
+               case INSN_READ_PARA:
+               case INSN_WRITE_WAVE:
+               case INSN_READ_WAVE:
+                       /* EMPTY */
+                       break;
+               default:
+                       sr_dbg("serial read, unknown insn %c", got_insn);
+                       return SR_ERR_DATA;
+               }
+               endptr = NULL;
+               ret = sr_atoul_base(rdptr, &got_index, &endptr, 10);
+               if (ret != SR_OK || !endptr)
+                       return SR_ERR_DATA;
+               if (wants_index && got_index != wants_index) {
+                       sr_dbg("serial read, unexpected index %zu", got_index);
+                       return SR_ERR_DATA;
+               }
+               rdptr = endptr;
+               if (rhs_start || rhs_length) {
+                       if (*rdptr != '=') {
+                               sr_dbg("serial read, equals sign missing");
+                               return SR_ERR_DATA;
+                       }
+               }
+               if (*rdptr)
+                       rdptr++;
+
+               /* Response is considered plausible here. */
+               if (rhs_start)
+                       *rhs_start = rdptr;
+               if (rhs_length)
+                       *rhs_length = strlen(rdptr);
+               return SR_OK;
+       }
+
+       sr_dbg("serial read, no EOL seen");
+       return SR_ERR_DATA;
+}
+
+/* Formatting helpers for request construction. */
+
+static void append_insn_read_para(GString *s, char insn, size_t idx)
+{
+       g_string_append_printf(s, ":%c%02zu=0", insn, idx);
+}
+
+static void append_insn_write_para_va(GString *s, char insn, size_t idx,
+       const char *fmt, va_list args) ATTR_FMT_PRINTF(4, 0);
+static void append_insn_write_para_va(GString *s, char insn, size_t idx,
+       const char *fmt, va_list args)
+{
+       g_string_append_printf(s, ":%c%02zu=", insn, idx);
+       g_string_append_vprintf(s, fmt, args);
+}
+
+static void append_insn_write_para_dots(GString *s, char insn, size_t idx,
+       const char *fmt, ...) ATTR_FMT_PRINTF(4, 5);
+static void append_insn_write_para_dots(GString *s, char insn, size_t idx,
+       const char *fmt, ...)
+{
+       va_list args;
+
+       va_start(args, fmt);
+       append_insn_write_para_va(s, insn, idx, fmt, args);
+       va_end(args);
+}
+
+/*
+ * Turn comma separators into whitespace. Simplifies the interpretation
+ * of multi-value response payloads. Also replaces any trailing period
+ * in case callers kept one in the receive buffer.
+ */
+static void replace_separators(char *s)
+{
+
+       while (s && *s) {
+               if (s[0] == ',') {
+                       *s++ = ' ';
+                       continue;
+               }
+               if (s[0] == '.' && s[1] == '\0') {
+                       *s++ = ' ';
+                       continue;
+               }
+               s++;
+       }
+}
+
+/*
+ * Convenience to interpret responses' values. Also concentrates the
+ * involved magic and simplifies diagnostics. It's essential to apply
+ * implicit multipliers, and to properly combine multiple fields into
+ * the resulting parameter's value (think scaling and offsetting).
+ */
+
+static const double scales_freq[] = {
+       1, 1, 1, 1e-3, 1e-6,
+};
+
+static int parse_freq_text(char *s, double *value)
+{
+       char *word;
+       int ret;
+       double dvalue;
+       unsigned long scale;
+
+       replace_separators(s);
+
+       /* First word is a mantissa, in centi-Hertz. :-O */
+       word = sr_text_next_word(s, &s);
+       ret = sr_atod(word, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+
+       /* Next word is an encoded scaling factor. */
+       word = sr_text_next_word(s, &s);
+       ret = sr_atoul_base(word, &scale, NULL, 10);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse freq, mant %lf, scale %ld", dvalue, scale);
+       if (scale >= ARRAY_SIZE(scales_freq))
+               return SR_ERR_DATA;
+
+       /* Do scale the mantissa's value. */
+       dvalue /= 100.0;
+       dvalue /= scales_freq[scale];
+       sr_spew("parse freq, value %lf", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_volt_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /* Single value, in units of mV. */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse volt, mant %lf", dvalue);
+       dvalue /= 1000.0;
+       sr_spew("parse volt, value %lf", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_bias_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /*
+        * Single value, in units of 10mV with a 10V offset. Capped to
+        * the +9.99V..-9.99V range. The Joy-IT PDF is a little weird
+        * suggesting that ":w27=9999." translates to 9.99 volts.
+        */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse bias, mant %lf", dvalue);
+       dvalue /= 100.0;
+       dvalue -= 10.0;
+       if (dvalue >= 9.99)
+               dvalue = 9.99;
+       if (dvalue <= -9.99)
+               dvalue = -9.99;
+       sr_spew("parse bias, value %lf", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_duty_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /*
+        * Single value, in units of 0.1% (permille).
+        * Scale to the 0.0..1.0 range.
+        */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse duty, mant %lf", dvalue);
+       dvalue /= 1000.0;
+       sr_spew("parse duty, value %lf", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+static int parse_phase_text(char *s, double *value)
+{
+       int ret;
+       double dvalue;
+
+       /* Single value, in units of deci-degrees. */
+       ret = sr_atod(s, &dvalue);
+       if (ret != SR_OK)
+               return ret;
+       sr_spew("parse phase, mant %lf", dvalue);
+       dvalue /= 10.0;
+       sr_spew("parse phase, value %lf", dvalue);
+
+       if (value)
+               *value = dvalue;
+       return SR_OK;
+}
+
+/*
+ * Convenience to generate request presentations. Also concentrates the
+ * involved magic and simplifies diagnostics. It's essential to apply
+ * implicit multipliers, and to properly create all request fields that
+ * communicate a value to the device's firmware (think scale and offset).
+ */
+
+static void write_freq_text(GString *s, double freq)
+{
+       unsigned long scale_idx;
+       const char *text_pos;
+
+       sr_spew("write freq, value %lf", freq);
+       text_pos = &s->str[s->len];
+
+       /*
+        * First word is mantissa in centi-Hertz. Second word is a
+        * scaling factor code. Keep scaling simple, always scale
+        * by a factor of 1.0.
+        */
+       scale_idx = 0;
+       freq *= scales_freq[scale_idx];
+       freq *= 100.0;
+
+       g_string_append_printf(s, "%.0lf,%lu", freq, scale_idx);
+       sr_spew("write freq, text %s", text_pos);
+}
+
+static void write_volt_text(GString *s, double volt)
+{
+       const char *text_pos;
+
+       sr_spew("write volt, value %lf", volt);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of 1mV.
+        * Limit input values to the 0..+20 range. This writer is only
+        * used by the amplitude setter.
+        */
+       if (volt > 20.0)
+               volt = 20.0;
+       if (volt < 0.0)
+               volt = 0.0;
+       volt *= 1000.0;
+       g_string_append_printf(s, "%.0lf", volt);
+       sr_spew("write volt, text %s", text_pos);
+}
+
+static void write_bias_text(GString *s, double volt)
+{
+       const char *text_pos;
+
+       sr_spew("write bias, value %lf", volt);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of 10mV with a 10V offset. Capped to
+        * the +9.99..-9.99 range.
+        */
+       if (volt > 9.99)
+               volt = 9.99;
+       if (volt < -9.99)
+               volt = -9.99;
+       volt += 10.0;
+       volt *= 100.0;
+
+       g_string_append_printf(s, "%.0lf", volt);
+       sr_spew("write bias, text %s", text_pos);
+}
+
+static void write_duty_text(GString *s, double duty)
+{
+       const char *text_pos;
+
+       sr_spew("write duty, value %lf", duty);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of 0.1% (permille). Capped to the
+        * 0.0..1.0 range.
+        */
+       if (duty < 0.0)
+               duty = 0.0;
+       if (duty > 1.0)
+               duty = 1.0;
+       duty *= 1000.0;
+
+       g_string_append_printf(s, "%.0lf", duty);
+       sr_spew("write duty, text %s", text_pos);
+}
+
+static void write_phase_text(GString *s, double phase)
+{
+       const char *text_pos;
+
+       sr_spew("write phase, value %lf", phase);
+       text_pos = &s->str[s->len];
+
+       /*
+        * Single value in units of deci-degrees.
+        * Kept to the 0..360 range by means of a modulo operation.
+        */
+       phase = fmod(phase, 360.0);
+       phase *= 10.0;
+
+       g_string_append_printf(s, "%.0lf", phase);
+       sr_spew("write phase, text %s", text_pos);
+}
+
+/*
+ * Convenience communication wrapper. Re-uses a buffer in devc, which
+ * simplifies resource handling in error paths. Sends a parameter-less
+ * read-request. Then receives a response which can carry values.
+ */
+static int quick_send_read_then_recv(const struct sr_dev_inst *sdi,
+       char insn, size_t idx,
+       unsigned int read_timeout_ms,
+       char **rhs_start, size_t *rhs_length)
+{
+       struct dev_context *devc;
+       GString *s;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (!devc->quick_req)
+               devc->quick_req = g_string_sized_new(MAX_RSP_LENGTH);
+       s = devc->quick_req;
+
+       g_string_truncate(s, 0);
+       append_insn_read_para(s, insn, idx);
+       ret = serial_send_textline(sdi, s, DELAY_AFTER_WRITE);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = serial_recv_textline(sdi, s,
+               TIMEOUT_READ_CHUNK, read_timeout_ms,
+               NULL, insn, idx, rhs_start, rhs_length);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * Convenience communication wrapper, re-uses a buffer in devc. Sends a
+ * write-request with parameters. Then receives an "ok" style response.
+ * Had to put the request details after the response related parameters
+ * because of the va_list API.
+ */
+static int quick_send_write_then_recv_ok(const struct sr_dev_inst *sdi,
+       unsigned int read_timeout_ms, gboolean *is_ok,
+       char insn, size_t idx, const char *fmt, ...) ATTR_FMT_PRINTF(6, 7);
+static int quick_send_write_then_recv_ok(const struct sr_dev_inst *sdi,
+       unsigned int read_timeout_ms, gboolean *is_ok,
+       char insn, size_t idx, const char *fmt, ...)
+{
+       struct dev_context *devc;
+       GString *s;
+       va_list args;
+       int ret;
+       gboolean ok;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (!devc->quick_req)
+               devc->quick_req = g_string_sized_new(MAX_RSP_LENGTH);
+       s = devc->quick_req;
+
+       g_string_truncate(s, 0);
+       va_start(args, fmt);
+       append_insn_write_para_va(s, insn, idx, fmt, args);
+       va_end(args);
+       ret = serial_send_textline(sdi, s, DELAY_AFTER_WRITE);
+       if (ret != SR_OK)
+               return ret;
+
+       ret = serial_recv_textline(sdi, s,
+               TIMEOUT_READ_CHUNK, read_timeout_ms,
+               &ok, '\0', 0, NULL, NULL);
+       if (is_ok)
+               *is_ok = ok;
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * High level getters/setters for device properties.
+ * To be used by the api.c config get/set infrastructure.
+ */
+
+SR_PRIV int jds6600_get_chans_enable(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr, *word, *endptr;
+       struct devc_dev *device;
+       struct devc_chan *chans;
+       size_t idx;
+       unsigned long on;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_CHANNELS_ENABLE,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get enabled, response text: %s", rdptr);
+
+       /* Interpret the response (multiple values, boolean). */
+       replace_separators(rdptr);
+       device = &devc->device;
+       chans = devc->channel_config;
+       for (idx = 0; idx < device->channel_count_gen; idx++) {
+               word = sr_text_next_word(rdptr, &rdptr);
+               if (!word || !*word)
+                       return SR_ERR_DATA;
+               endptr = NULL;
+               ret = sr_atoul_base(word, &on, &endptr, 10);
+               if (ret != SR_OK || !endptr || *endptr)
+                       return SR_ERR_DATA;
+               chans[idx].enabled = on;
+       }
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_waveform(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr, *endptr;
+       struct devc_wave *waves;
+       struct devc_chan *chan;
+       unsigned long code;
+       size_t idx;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       waves = &devc->waveforms;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_WAVEFORM_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get waveform, response text: %s", rdptr);
+
+       /*
+        * Interpret the response (integer value, waveform code).
+        * Lookup the firmware's code for that waveform in the
+        * list of user perceivable names for waveforms.
+        */
+       endptr = NULL;
+       ret = sr_atoul_base(rdptr, &code, &endptr, 10);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       for (idx = 0; idx < waves->names_count; idx++) {
+               if (code != waves->fw_codes[idx])
+                       continue;
+               chan->waveform_code = code;
+               chan->waveform_index = idx;
+               sr_dbg("get waveform, code %lu, idx %zu, name %s",
+                       code, idx, waves->names[idx]);
+               return SR_OK;
+       }
+
+       return SR_ERR_DATA;
+}
+
+#if WITH_ARBWAVE_DOWNLOAD
+/*
+ * Development HACK. Get a waveform from the device. Uncertain where to
+ * dump it though. Have yet to identify a sigrok API for waveforms.
+ */
+static int jds6600_get_arb_waveform(const struct sr_dev_inst *sdi, size_t idx)
+{
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       int ret;
+       char *rdptr, *word, *endptr;
+       size_t sample_count;
+       unsigned long value;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       waves = &devc->waveforms;
+
+       if (idx >= waves->arbitrary_count)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_WAVE, idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get arb wave, response text: %s", rdptr);
+
+       /* Extract the sequence of samples for the waveform. */
+       replace_separators(rdptr);
+       sample_count = 0;
+       while (rdptr && *rdptr) {
+               word = sr_text_next_word(rdptr, &rdptr);
+               if (!word)
+                       break;
+               endptr = NULL;
+               ret = sr_atoul_base(word, &value, &endptr, 10);
+               if (ret != SR_OK || !endptr || *endptr) {
+                       sr_dbg("get arb wave, conv error: %s", word);
+                       return SR_ERR_DATA;
+               }
+               sample_count++;
+       }
+       sr_dbg("get arb wave, samples count: %zu", sample_count);
+
+       return SR_OK;
+}
+#endif
+
+SR_PRIV int jds6600_get_frequency(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double freq;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_FREQUENCY_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get frequency, response text: %s", rdptr);
+
+       /* Interpret the response (value and scale, frequency). */
+       ret = parse_freq_text(rdptr, &freq);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get frequency, value %lf", freq);
+       chan->output_frequency = freq;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double amp;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_AMPLITUDE_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get amplitude, response text: %s", rdptr);
+
+       /* Interpret the response (single value, a voltage). */
+       ret = parse_volt_text(rdptr, &amp);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get amplitude, value %lf", amp);
+       chan->amplitude = amp;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_offset(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double off;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_OFFSET_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get offset, response text: %s", rdptr);
+
+       /* Interpret the response (single value, an offset). */
+       ret = parse_bias_text(rdptr, &off);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get offset, value %lf", off);
+       chan->offset = off;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx)
 {
-       const struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+       char *rdptr;
+       double duty;
 
-       (void)fd;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= ARRAY_SIZE(devc->channel_config))
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_DUTYCYCLE_CH1 + ch_idx,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get duty cycle, response text: %s", rdptr);
+
+       /* Interpret the response (single value, a percentage). */
+       ret = parse_duty_text(rdptr, &duty);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get duty cycle, value %lf", duty);
+       chan->dutycycle = duty;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_get_phase_chans(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr;
+       double phase;
+
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_PHASE_CHANNELS,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("get phase, response text: %s", rdptr);
+
+       /* Interpret the response (single value, an angle). */
+       ret = parse_phase_text(rdptr, &phase);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       sr_dbg("get phase, value %lf", phase);
+       devc->channels_phase = phase;
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_chans_enable(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct devc_chan *chans;
+       GString *en_text;
+       size_t idx;
+       int ret;
 
-       sdi = cb_data;
        if (!sdi)
-               return TRUE;
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive an "ok" style response. */
+       chans = devc->channel_config;
+       en_text = g_string_sized_new(20);
+       for (idx = 0; idx < devc->device.channel_count_gen; idx++) {
+               if (en_text->len)
+                       g_string_append_c(en_text, ',');
+               g_string_append_c(en_text, chans[idx].enabled ? '1' : '0');
+       }
+       sr_dbg("set enabled, request text: %s", en_text->str);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_CHANNELS_ENABLE, "%s", en_text->str);
+       g_string_free(en_text, 20);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
 
+SR_PRIV int jds6600_set_waveform(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
        devc = sdi->priv;
        if (!devc)
-               return TRUE;
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_WAVEFORM_CH1 + ch_idx,
+               "%" PRIu32, chan->waveform_code);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+#if WITH_ARBWAVE_DOWNLOAD
+/*
+ * Development HACK. Send a waveform to the device. Uncertain where
+ * to get it from though. Just generate some stupid pattern that's
+ * seen on the LCD later.
+ *
+ * Local experiments suggest that writing another waveform after having
+ * written one earlier results in the next waveform to become mangled.
+ * It appears to start with an all-bits-set pattern for a remarkable
+ * number of samples, before the actually written pattern is seen. Some
+ * delay after reception of the ":ok" response may be required to avoid
+ * this corruption.
+ */
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+/* Stupid creation of one sample value. Gets waveform index and sample count. */
+static uint16_t make_sample(size_t wave, size_t curr, size_t total)
+{
+       uint16_t max_value, high_value, low_value;
+       size_t ival, high_width;
+       gboolean is_high;
+
+       /* Get the waveform's amplitudes. */
+       max_value = 4096;
+       high_value = max_value / (wave + 3);
+       high_value = max_value - high_value;
+       low_value = max_value - high_value;
+
+       /* Get pulses' total interval, high and low half-periods. */
+       ival = (total - 10) / wave;
+       high_width = ival / 2;
+
+       /* Check location in the current period. */
+       curr %= ival;
+       is_high = curr <= high_width;
+       return is_high ? high_value : low_value;
+}
+
+/* Creation and download of the sequence of samples. */
+static int jds6600_set_arb_waveform(const struct sr_dev_inst *sdi, size_t idx)
+{
+       struct dev_context *devc;
+       struct devc_wave *waves;
+       GString *wave_text;
+       size_t samples_total, samples_curr;
+       uint16_t value;
+       gboolean ok;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       waves = &devc->waveforms;
+
+       if (idx >= waves->arbitrary_count)
+               return SR_ERR_ARG;
+
+       /* Construct a pattern that depends on the waveform index. */
+       wave_text = g_string_sized_new(MAX_RSP_LENGTH);
+       samples_total = 2048;
+       samples_curr = 0;
+       for (samples_curr = 0; samples_curr < samples_total; samples_curr++) {
+               value = make_sample(idx, samples_curr, samples_total);
+               if (samples_curr)
+                       g_string_append_c(wave_text, ',');
+               g_string_append_printf(wave_text, "%" PRIu16, value);
        }
+       sr_dbg("set arb wave, request text: %s", wave_text->str);
+
+       /* Transmit the request, receive an "ok" style response. */
+       ret = quick_send_write_then_recv_ok(sdi, 0, &ok,
+               INSN_WRITE_WAVE, idx, "%s", wave_text->str);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("set arb wave, response ok: %d", ok);
+
+       if (DELAY_AFTER_FLASH)
+               g_usleep(DELAY_AFTER_FLASH * 1000);
+
+       return SR_OK;
+}
+#endif
+
+SR_PRIV int jds6600_set_frequency(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       double freq;
+       GString *freq_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Limit input values to the range supported by the model. */
+       freq = chan->output_frequency;
+       if (freq < 0.01)
+               freq = 0.01;
+       if (freq > devc->device.max_output_frequency)
+               freq = devc->device.max_output_frequency;
+
+       /* Transmit the request, receive an "ok" style response. */
+       freq_text = g_string_sized_new(32);
+       write_freq_text(freq_text, freq);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_FREQUENCY_CH1 + ch_idx,
+               "%s", freq_text->str);
+       g_string_free(freq_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       GString *volt_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       volt_text = g_string_sized_new(32);
+       write_volt_text(volt_text, chan->amplitude);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_AMPLITUDE_CH1 + ch_idx,
+               "%s", volt_text->str);
+       g_string_free(volt_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_offset(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       GString *volt_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       volt_text = g_string_sized_new(32);
+       write_bias_text(volt_text, chan->offset);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_OFFSET_CH1 + ch_idx,
+               "%s", volt_text->str);
+       g_string_free(volt_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx)
+{
+       struct dev_context *devc;
+       struct devc_chan *chan;
+       GString *duty_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+       if (ch_idx >= devc->device.channel_count_gen)
+               return SR_ERR_ARG;
+       chan = &devc->channel_config[ch_idx];
+
+       /* Transmit the request, receive an "ok" style response. */
+       duty_text = g_string_sized_new(32);
+       write_duty_text(duty_text, chan->dutycycle);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_DUTYCYCLE_CH1 + ch_idx,
+               "%s", duty_text->str);
+       g_string_free(duty_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_set_phase_chans(const struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       GString *phase_text;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit the request, receive an "ok" style response. */
+       phase_text = g_string_sized_new(32);
+       write_phase_text(phase_text, devc->channels_phase);
+       ret = quick_send_write_then_recv_ok(sdi, 0, NULL,
+               INSN_WRITE_PARA, IDX_PHASE_CHANNELS,
+               "%s", phase_text->str);
+       g_string_free(phase_text, TRUE);
+       if (ret != SR_OK)
+               return ret;
+
+       return SR_OK;
+}
+
+/*
+ * High level helpers for the scan/probe phase. Identify the attached
+ * device and synchronize to its current state and its capabilities.
+ */
+
+SR_PRIV int jds6600_identify(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       int ret;
+       char *rdptr, *endptr;
+       unsigned long devtype;
+
+       (void)append_insn_write_para_dots;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /* Transmit "read device type" request, receive the response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_DEVICE_TYPE,
+               TIMEOUT_IDENTIFY, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("identify, device type '%s'", rdptr);
+
+       /* Interpret the response (integer value, max freq). */
+       endptr = NULL;
+       ret = sr_atoul_base(rdptr, &devtype, &endptr, 10);
+       if (ret != SR_OK || !endptr)
+               return SR_ERR_DATA;
+       devc->device.device_type = devtype;
+
+       /* Transmit "read serial number" request. receive response. */
+       ret = quick_send_read_then_recv(sdi,
+               INSN_READ_PARA, IDX_SERIAL_NUMBER,
+               0, &rdptr, NULL);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("identify, serial number '%s'", rdptr);
+
+       /* Keep the response (in string format, some serial number). */
+       devc->device.serial_number = g_strdup(rdptr);
+
+       return SR_OK;
+}
+
+SR_PRIV int jds6600_setup_devc(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       size_t alloc_count, assign_idx, idx;
+       struct devc_dev *device;
+       struct devc_wave *waves;
+       enum waveform_index_t code;
+       char *name;
+       int ret;
+
+       if (!sdi)
+               return SR_ERR_ARG;
+       devc = sdi->priv;
+       if (!devc)
+               return SR_ERR_ARG;
+
+       /*
+        * Derive maximum output frequency from detected device type.
+        * Open coded generator channel count.
+        */
+       device = &devc->device;
+       if (!device->device_type)
+               return SR_ERR_DATA;
+       device->max_output_frequency = device->device_type;
+       device->max_output_frequency *= SR_MHZ(1);
+       device->channel_count_gen = MAX_GEN_CHANNELS;
+
+       /* Construct the list of waveform names and their codes. */
+       waves = &devc->waveforms;
+       waves->builtin_count = WAVES_COUNT_BUILTIN;
+       waves->arbitrary_count = WAVES_COUNT_ARBITRARY;
+       alloc_count = waves->builtin_count;
+       alloc_count += waves->arbitrary_count;
+       waves->names_count = alloc_count;
+       waves->fw_codes = g_malloc0(alloc_count * sizeof(waves->fw_codes[0]));
+       alloc_count++;
+       waves->names = g_malloc0(alloc_count * sizeof(waves->names[0]));
+       if (!waves->names || !waves->fw_codes)
+               return SR_ERR_MALLOC;
+       assign_idx = 0;
+       for (idx = 0; idx < waves->builtin_count; idx++) {
+               code = idx;
+               name = g_strdup(waveform_names[idx]);
+               waves->fw_codes[assign_idx] = code;
+               waves->names[assign_idx] = name;
+               assign_idx++;
+       }
+       for (idx = 0; idx < waves->arbitrary_count; idx++) {
+               code = WAVE_ARB01 + idx;
+               name = g_strdup_printf(WAVEFORM_ARB_NAME_FMT, idx + 1);
+               waves->fw_codes[assign_idx] = code;
+               waves->names[assign_idx] = name;
+               assign_idx++;
+       }
+       waves->names[assign_idx] = NULL;
+
+       /*
+        * Populate internal channel configuration details from the
+        * device's current state. Emit a series of queries which
+        * update internal knowledge.
+        */
+       ret = SR_OK;
+       ret |= jds6600_get_chans_enable(sdi);
+       for (idx = 0; idx < device->channel_count_gen; idx++) {
+               ret |= jds6600_get_waveform(sdi, idx);
+               ret |= jds6600_get_frequency(sdi, idx);
+               ret |= jds6600_get_amplitude(sdi, idx);
+               ret |= jds6600_get_offset(sdi, idx);
+               ret |= jds6600_get_dutycycle(sdi, idx);
+       }
+       ret |= jds6600_get_phase_chans(sdi);
+       ret |= jds6600_get_chans_enable(sdi);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+
+#if WITH_ARBWAVE_DOWNLOAD
+       /*
+        * Development HACK, to see how waveform upload works.
+        * How to forward the data to the application? Or the
+        * sigrok session actually? Provide these as acquisition
+        * results?
+        */
+       ret |= jds6600_get_arb_waveform(sdi, 13);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+       ret |= jds6600_set_arb_waveform(sdi, 12);
+       ret |= jds6600_set_arb_waveform(sdi, 13);
+       if (ret != SR_OK)
+               return SR_ERR_DATA;
+#endif
 
-       return TRUE;
+       return SR_OK;
 }
index ab5dff7dd2b980f6250d7bfe3787b50c0e0d9636..1a7a1469c5fd2e4f07c30bf066e3704081ed6fe0 100644 (file)
 #ifndef LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H
 #define LIBSIGROK_HARDWARE_JUNTEK_JDS6600_PROTOCOL_H
 
-#include <stdint.h>
 #include <glib.h>
 #include <libsigrok/libsigrok.h>
+#include <stdint.h>
+
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "juntek-jds6600"
 
+#define MAX_GEN_CHANNELS       2
+
 struct dev_context {
+       struct devc_dev {
+               unsigned int device_type;
+               char *serial_number;
+               uint64_t max_output_frequency;
+               size_t channel_count_gen;
+       } device;
+       struct devc_wave {
+               size_t builtin_count;
+               size_t arbitrary_count;
+               size_t names_count;
+               const char **names;
+               uint32_t *fw_codes;
+       } waveforms;
+       struct devc_chan {
+               gboolean enabled;
+               uint32_t waveform_code;
+               size_t waveform_index;
+               double output_frequency;
+               double amplitude, offset;
+               double dutycycle;
+       } channel_config[MAX_GEN_CHANNELS];
+       double channels_phase;
+       GString *quick_req;
 };
 
-SR_PRIV int juntek_jds6600_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV int jds6600_identify(struct sr_dev_inst *sdi);
+SR_PRIV int jds6600_setup_devc(struct sr_dev_inst *sdi);
+
+SR_PRIV int jds6600_get_chans_enable(const struct sr_dev_inst *sdi);
+SR_PRIV int jds6600_get_waveform(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_frequency(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_offset(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_get_phase_chans(const struct sr_dev_inst *sdi);
+
+SR_PRIV int jds6600_set_chans_enable(const struct sr_dev_inst *sdi);
+SR_PRIV int jds6600_set_waveform(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_frequency(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_amplitude(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_offset(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_dutycycle(const struct sr_dev_inst *sdi, size_t ch_idx);
+SR_PRIV int jds6600_set_phase_chans(const struct sr_dev_inst *sdi);
 
 #endif