The Hung-Chang DSO-2100 is a parallel port PC oscilloscope sold back
in 1999 under brand names like Protek and Voltcraft.

This inital version of the driver has the following limitations:

 - Hardcoded calibration values. All parameters are set to 50%.
 - No support for auto triggering
 - No support for TV sync trigger modes
 - No support for the "scroll acquisition" mode

In scroll acquisition mode the device behaves more like a multimeter
and reports the current voltage of a probe on request. While in this
mode the sample rate is limited by the parallel port interface, it is
the only way to capture both channels at the same time (well, sort of).

Calibration would need auto triggering. The calibration values are very
temperature dependent and the device takes literally hours to reach its
final temperature. Every vdiv setting needs its own set of calibration
values. Without hardware modifications, the calibration settings wear
of in less than a second while waiting for a trigger because the
capacitors storing those values are not recharged in state 0x21.
---
 Makefile.am                                |    4 +
 configure.ac                               |    1 +
 src/drivers.c                              |    6 +
 src/hardware/hung-chang-dso-2100/dso2100.c | 1072 ++++++++++++++++++++++++++++
 4 files changed, 1083 insertions(+)
 create mode 100644 src/hardware/hung-chang-dso-2100/dso2100.c

diff --git a/Makefile.am b/Makefile.am
index 1e79a28..1f4a812 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -264,6 +264,10 @@ libsigrok_la_SOURCES += \
        src/hardware/hameg-hmo/protocol.c \
        src/hardware/hameg-hmo/api.c
 endif
+if HW_HUNG_CHANG_DSO_2100
+libsigrok_la_SOURCES += \
+       src/hardware/hung-chang-dso-2100/dso2100.c
+endif
 if HW_HANTEK_DSO
 libsigrok_la_SOURCES += \
        src/hardware/hantek-dso/dso.h \
diff --git a/configure.ac b/configure.ac
index a647e8b..d9e543b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -221,6 +221,7 @@ SR_DRIVER([fx2lafw], [fx2lafw], [libusb])
 SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [libserialport])
 SR_DRIVER([Hameg HMO], [hameg-hmo], [libserialport])
 SR_DRIVER([Hantek DSO], [hantek-dso], [libusb])
+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([Kecheng KC-330B], [kecheng-kc-330b], [libusb])
diff --git a/src/drivers.c b/src/drivers.c
index 15e1084..27ec35e 100644
--- a/src/drivers.c
+++ b/src/drivers.c
@@ -83,6 +83,9 @@ extern SR_PRIV struct sr_dev_driver hameg_hmo_driver_info;
 #ifdef HAVE_HW_HANTEK_DSO
 extern SR_PRIV struct sr_dev_driver hantek_dso_driver_info;
 #endif
+#ifdef HAVE_HW_HUNG_CHANG_DSO_2100
+extern SR_PRIV struct sr_dev_driver dso2100_driver_info;
+#endif
 #ifdef HAVE_HW_IKALOGIC_SCANALOGIC2
 extern SR_PRIV struct sr_dev_driver ikalogic_scanalogic2_driver_info;
 #endif
@@ -232,6 +235,9 @@ SR_PRIV struct sr_dev_driver **drivers_lists[] = {
 #ifdef HAVE_HW_HANTEK_DSO
        (DRVS) {&hantek_dso_driver_info, NULL},
 #endif
+#ifdef HAVE_HW_HUNG_CHANG_DSO_2100
+       (DRVS) {&dso2100_driver_info, NULL},
+#endif
 #ifdef HAVE_HW_IKALOGIC_SCANALOGIC2
        (DRVS) {&ikalogic_scanalogic2_driver_info, NULL},
 #endif
diff --git a/src/hardware/hung-chang-dso-2100/dso2100.c 
b/src/hardware/hung-chang-dso-2100/dso2100.c
new file mode 100644
index 0000000..15a3f1c
--- /dev/null
+++ b/src/hardware/hung-chang-dso-2100/dso2100.c
@@ -0,0 +1,1072 @@
+/*
+ * This file is part of the libsigrok project.
+ *
+ * Copyright (C) 2015 Daniel Glöckner <daniel...@gmx.net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ieee1284.h>
+#include <string.h>
+#include <glib.h>
+#include <libsigrok/libsigrok.h>
+#include "libsigrok-internal.h"
+
+#define LOG_PREFIX "hung-chang-dso-2100"
+
+/* The firmware can be in the following states:
+ *  0x00       Temporary state during initializazion
+ *             Automatically transitions to state 0x01
+ *  0x01       Idle, this state updates calibration caps
+ *             Send 0x02 to go to state 0x21
+ *             Send 0x03 to go to state 0x03
+ *             Send 0x04 to go to state 0x14
+ *  0x21       Trigger is armed, caps are _not_ updated
+ *             Send 0x99 to check if trigger event occured
+ *                     if triggered, goes to state 0x03
+ *                     else stays in state 0x21
+ *             Send 0xFE to generate artifical trigger event
+ *                     returns to state 0x21
+ *                     but next 0x99 will succeed
+ *             Send 0xFF to go to state 0x03 (abort capture)
+ *  0x03       Extracts two 500 sample subsets from the 5000
+ *             sample capture buffer for readout
+ *             When reading samples, the FPGA starts at the
+ *             first of the 1000 samples an automatically
+ *             advances to the next.
+ *             Send 0x04 to go to state 0x0F
+ *  0x14       Scroll acquisition mode, update calib caps
+ *             When reading samples, the FPGA provides the
+ *             current value of the ADCs
+ *             Send 0xFF to go to state 0x0F
+ *  0x0F       Send channel number (1 or 2) to go to next state
+ *             There are actually two 0x0F states in series
+ *             which both expect the channel number.
+ *             If the values don't match, they are discarded.
+ *             The next state 0x05 is entered anyway
+ *  0x05       Same as state 0x0F but expects sample rate index.
+ *             The next state is 0x08
+ *  0x08       Same as state 0x0F but expects step size + 1 for
+ *             the second 500 sample subset
+ *             The next state is 0x09
+ *  0x09       Same as state 0x0F but expects step size + 1 for
+ *             the first 500 sample subset
+ *             The next state is 0x06
+ *  0x06       Same as state 0x0F but expects vdiv and coupling
+ *             configuration for the first channel and trigger
+ *             source selection.
+ *             (U46 in the schematics)
+ *             The next state is 0x07
+ *  0x07       Same as state 0x0F but expects vdiv and coupling
+ *             configuration for the first channel and trigger
+ *             type (edge, TV hsync, TV vsync).
+ *             (U47 in the schematics)
+ *             The next state is 0x0A
+ *  0x0A       Same as state 0x0F but expects a parameter X + 1
+ *             that determines the offset of the second 500 sample
+ *             subset
+ *             Offset = 5 * X * step size for first subset
+ *             The next state is 0x0B
+ *  0x0B       Same as state 0x0F but expects the type of edge to
+ *             trigger on (rising or falling)
+ *             The next state is 0x0C
+ *  0x0C       Same as state 0x0F but expects the calibration
+ *             value for the first channel's  position
+ *             (POS1 in the schematics)
+ *             The next state is 0x0D
+ *  0x0D       Same as state 0x0F but expects the calibration
+ *             value for the second channel's  position
+ *             (POS2 in the schematics)
+ *             The next state is 0x0E
+ *  0x0E       Same as state 0x0F but expects the trigger level
+ *             (TRIGLEVEL in the schematics)
+ *             Keep in mind that trigger sources are AC coupled
+ *             The next state is 0x10
+ *  0x10       Same as state 0x0F but expects the calibration
+ *             value for the first channel's offset
+ *             (OFFSET1 in the schematics)
+ *             The next state is 0x11
+ *  0x11       Same as state 0x0F but expects the calibration
+ *             value for the first channel's gain
+ *             (GAIN1 in the schematics)
+ *             The next state is 0x12
+ *  0x12       Same as state 0x0F but expects the calibration
+ *             value for the second channel's offset
+ *             (OFFSET2 in the schematics)
+ *             The next state is 0x13
+ *  0x13       Same as state 0x0F but expects the calibration
+ *             value for the second channel's gain
+ *             (GAIN2 in the schematics)
+ *             The next state is 0x01
+ *
+ * The Mailbox appears to be half duplex.
+ * If one side writes a byte into the mailbox, it
+ * reads 0 until the other side has written a byte.
+ * So you can't transfer 0.
+ *
+ * As the status signals are unconnected, the device is not
+ * IEEE1284 compliant and can't make use of EPP or ECP transfers.
+ * It drives the data lines when control is set to:
+ *                0                => Channel A data
+ *          C1284_NAUTOFD          => Channel B data
+ *         C1284_NSELECTIN         => Mailbox
+ * C1284_NSELECTIN | C1284_NAUTOFD => 0x55
+ * */
+
+
+struct dev_context {
+       uint64_t frame_limit;
+       uint64_t frame;
+       uint64_t probe[2];
+       GSList *enabled_channel;
+       void *cb_data;
+       float *samples;
+       float factor;
+       uint16_t buffersize;
+       uint8_t channel;
+       uint8_t rate;
+       uint8_t cctl[2];
+       uint8_t edge;
+       uint8_t tlevel;
+       uint8_t pos[2];
+       uint8_t offset[2];
+       uint8_t gain[2];
+       uint8_t step1;
+       uint8_t shift;
+};
+
+static const uint32_t scanopts[] = {
+       SR_CONF_CONN,
+};
+
+static const uint32_t drvopts[] = {
+       SR_CONF_OSCILLOSCOPE,
+};
+
+static const uint32_t devopts[] = {
+       SR_CONF_CONN | SR_CONF_GET,
+       SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET,
+       SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_TRIGGER_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_TRIGGER_SLOPE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_BUFFERSIZE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+};
+
+static const uint32_t cgopts[] = {
+       SR_CONF_VDIV | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_COUPLING | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
+       SR_CONF_PROBE_FACTOR | SR_CONF_GET | SR_CONF_SET,
+};
+
+static const uint64_t samplerates[] = {
+       SR_MHZ(100), SR_MHZ(50),  SR_MHZ(25),   SR_MHZ(20),
+       SR_MHZ(10),  SR_MHZ(5),   SR_KHZ(2500), SR_MHZ(2),
+       SR_MHZ(1),   SR_KHZ(500), SR_KHZ(250),  SR_KHZ(200),
+       SR_KHZ(100), SR_KHZ(50),  SR_KHZ(25),   SR_KHZ(20),
+       SR_KHZ(10),  SR_KHZ(5),   SR_HZ(2500),  SR_KHZ(2),
+       SR_KHZ(1),   SR_HZ(500),  SR_HZ(250),   SR_HZ(200),
+       SR_HZ(100),  SR_HZ(50),   SR_HZ(25),    SR_HZ(20)
+};
+
+static const uint64_t buffersizes[] = {
+       2 * 500, 3 * 500, 4 * 500, 5 * 500,
+       6 * 500, 7 * 500, 8 * 500, 9 * 500, 10 * 500
+};
+
+static const uint64_t vdivs[][2] = {
+       { 10, 1000 },
+       { 20, 1000 },
+       { 50, 1000 },
+       { 100, 1000 },
+       { 200, 1000 },
+       { 500, 1000 },
+       { 1, 1 },
+       { 2, 1 },
+       { 5, 1 },
+};
+
+/* Bits 4 and 5 enable relays that add /10 filters to the chain
+ * Bits 0 and 1 select an output from a resistor array */
+static const uint8_t vdivs_map[] = {
+       0x01, 0x02, 0x03, 0x21, 0x22, 0x23, 0x31, 0x32, 0x33
+};
+
+
+static const char *trigger_sources[] = {
+       "A", "B", "EXT"
+};
+
+static const uint8_t trigger_sources_map[] = {
+       0x00, 0x80, 0x40
+};
+
+static const char *trigger_slopes[] = {
+       "f", "r"
+};
+
+static const char *coupling[] = {
+       "DC", "AC", "GND"
+};
+
+static const uint8_t coupling_map[] = {
+       0x00, 0x08, 0x04
+};
+
+static void dso2100_write_mbox(struct parport *port, uint8_t val)
+{
+       sr_dbg("mbox <= %X", val);
+       ieee1284_write_control(port,
+                       C1284_NSTROBE | C1284_NINIT | C1284_NSELECTIN);
+       ieee1284_data_dir(port, 0);
+       ieee1284_write_data(port, val);
+       ieee1284_write_control(port, C1284_NINIT | C1284_NSELECTIN);
+       ieee1284_write_control(port,
+                       C1284_NSTROBE | C1284_NINIT | C1284_NSELECTIN);
+       ieee1284_data_dir(port, 1);
+       ieee1284_write_control(port,
+               C1284_NSTROBE | C1284_NAUTOFD | C1284_NINIT | C1284_NSELECTIN);
+}
+
+static uint8_t dso2100_read_mbox_raw(struct parport *port)
+{
+       uint8_t val;
+
+       ieee1284_write_control(port, C1284_NSTROBE | C1284_NSELECTIN);
+       ieee1284_write_control(port, C1284_NSELECTIN);
+       val = ieee1284_read_data(port);
+       ieee1284_write_control(port, C1284_NSTROBE | C1284_NSELECTIN);
+       ieee1284_write_control(port,
+               C1284_NSTROBE | C1284_NAUTOFD | C1284_NINIT | C1284_NSELECTIN);
+       return val;
+}
+
+static uint8_t dso2100_read_mbox(struct parport *port)
+{
+       GTimer *timer = NULL;
+       uint8_t val;
+
+       for (;;) {
+               val = dso2100_read_mbox_raw(port);
+               if (val)
+                       break;
+               if (!timer)
+                       timer = g_timer_new();
+               else if (g_timer_elapsed(timer, NULL) > 0.1)
+                       break;
+       }
+
+       if (timer)
+               g_timer_destroy(timer);
+       sr_dbg("mbox == %X", val);
+       return val;
+}
+
+static GSList *scan_port(GSList *list, struct sr_dev_driver *driver,
+                        struct parport *port)
+{
+       struct sr_dev_inst *sdi;
+       struct sr_channel *ch;
+       struct sr_channel_group *cg;
+       struct dev_context *devc;
+       struct drv_context *drvc;
+       int i;
+
+       if (ieee1284_open(port, 0, &i) != E1284_OK)
+               goto fail1;
+
+       if ((i & (CAP1284_RAW | CAP1284_BYTE)) != (CAP1284_RAW | CAP1284_BYTE))
+               goto fail2;
+
+       if (ieee1284_claim(port) != E1284_OK)
+               goto fail2;
+
+       if (ieee1284_data_dir(port, 1) != E1284_OK)
+               goto fail3;
+
+       ieee1284_write_control(port, C1284_NSTROBE | C1284_NAUTOFD | 
C1284_NSELECTIN);
+       ieee1284_write_control(port, C1284_NAUTOFD | C1284_NSELECTIN);
+
+       if (ieee1284_read_data(port) != 0x55)
+               goto fail3;
+
+       sdi = g_malloc0(sizeof(struct sr_dev_inst));
+       sdi->status = SR_ST_INACTIVE;
+       sdi->vendor = g_strdup("Hung-Chang");
+       sdi->model = g_strdup("DSO-2100");
+       sdi->driver = driver;
+       drvc = driver->context;
+       sdi->inst_type = 0; /* FIXME */
+       sdi->conn = port;
+       ieee1284_ref(port);
+
+       for (i = 0; i < 2; i++) {
+               cg = g_malloc0(sizeof(struct sr_channel_group));
+               cg->name = g_strdup(trigger_sources[i]);
+               ch = sr_channel_new(sdi, i, SR_CHANNEL_ANALOG, FALSE, 
trigger_sources[i]);
+               cg->channels = g_slist_append(cg->channels, ch);
+               sdi->channel_groups = g_slist_append(sdi->channel_groups, cg);
+       }
+
+       devc = g_malloc0(sizeof(struct dev_context));
+       devc->enabled_channel = g_slist_append(NULL, NULL);
+       devc->probe[0] = 10;
+       devc->probe[1] = 10;
+       devc->buffersize = buffersizes[0];
+       devc->cctl[0] = 0x31; /* 1V/div, DC coupling, trigger on channel A*/
+       devc->cctl[1] = 0x31; /* 1V/div, DC coupling, no tv sync trigger */
+       devc->tlevel = 0x80;
+       devc->pos[0] = 0x80;
+       devc->pos[1] = 0x80;
+       devc->offset[0] = 0x80;
+       devc->offset[1] = 0x80;
+       devc->gain[0] = 0x80;
+       devc->gain[1] = 0x80;
+       devc->step1 = 1;
+       devc->shift = 100;
+       sdi->priv = devc;
+
+       drvc->instances = g_slist_append(drvc->instances, sdi);
+       list = g_slist_append(list, sdi);
+
+fail3:
+       ieee1284_write_control(port,
+                       C1284_NSTROBE | C1284_NAUTOFD | C1284_NSELECTIN);
+       ieee1284_data_dir(port, 0);
+       ieee1284_release(port);
+fail2:
+       ieee1284_close(port);
+fail1:
+       return list;
+}
+
+static GSList *scan(struct sr_dev_driver *driver, GSList *options)
+{
+       struct parport_list ports;
+       struct sr_config *src;
+       const char *conn = NULL;
+       GSList *l;
+       gboolean port_found;
+       int i;
+
+       for (l = options; l; l = l->next) {
+               src = l->data;
+               if (src->key == SR_CONF_CONN) {
+                       conn = g_variant_get_string(src->data, NULL);
+                       break;
+               }
+       }
+
+#ifndef SCAN_ALL_PARALLEL_PORTS
+       if (!conn)
+               return NULL;
+#endif
+
+       if (ieee1284_find_ports(&ports, 0) != E1284_OK)
+               return NULL;
+
+       l = NULL;
+       port_found = FALSE;
+       for (i = 0; i < ports.portc; i++)
+               if (!conn || !strcmp(ports.portv[i]->name, conn)) {
+                       port_found = TRUE;
+                       l = scan_port(l, driver, ports.portv[i]);
+               }
+
+       if (conn && !port_found) {
+               sr_err("Parallel port %s not found. Valid names are:", conn);
+               for (i = 0; i < ports.portc; i++)
+                       sr_err("\t%s", ports.portv[i]->name);
+       }
+
+       ieee1284_free_ports(&ports);
+
+       return l;
+}
+
+static GSList *dev_list(const struct sr_dev_driver *driver)
+{
+       return ((struct drv_context *)(driver->context))->instances;
+}
+
+static void clear_private(void *priv)
+{
+       struct dev_context *devc = priv;
+
+       g_slist_free(devc->enabled_channel);
+}
+
+static int dev_clear(const struct sr_dev_driver *driver)
+{
+       struct drv_context *drvc = driver->context;
+       struct sr_dev_inst *sdi;
+       GSList *l;
+
+       if (drvc) {
+               for (l = drvc->instances; l; l = l->next) {
+                       sdi = l->data;
+                       ieee1284_unref(sdi->conn);
+               }
+       }
+
+       return std_dev_clear(driver, clear_private);
+}
+
+static int init(struct sr_dev_driver *driver, struct sr_context *ctx)
+{
+       return std_init(ctx, driver, LOG_PREFIX);
+}
+
+static int cleanup(const struct sr_dev_driver *driver)
+{
+       struct drv_context *drvc = driver->context;
+       int ret;
+       
+       ret = dev_clear(driver);
+
+       g_free(drvc);
+
+       return ret;
+}
+
+static int find_in_array(GVariant *data, const GVariantType *type,
+                        const void *arr, int n)
+{
+       const char * const *sarr;
+       const char *s;
+       const uint64_t *u64arr;
+       const uint8_t *u8arr;
+       uint64_t u64;
+       uint8_t u8;
+       int i;
+
+       if (!g_variant_is_of_type(data, type))
+               return -1;
+
+       switch (g_variant_classify(data)) {
+       case G_VARIANT_CLASS_STRING:
+               s = g_variant_get_string(data, NULL);
+               sarr = arr;
+
+               for (i = 0; i < n; i++)
+                       if (!strcmp(s, sarr[i]))
+                               return i;
+               break;
+       case G_VARIANT_CLASS_UINT64:
+               u64 = g_variant_get_uint64(data);
+               u64arr = arr;
+
+               for (i = 0; i < n; i++)
+                       if (u64 == u64arr[i])
+                               return i;
+               break;
+       case G_VARIANT_CLASS_BYTE:
+               u8 = g_variant_get_byte(data);
+               u8arr = arr;
+
+               for (i = 0; i < n; i++)
+                       if (u8 == u8arr[i])
+                               return i;
+       default:
+               break;
+       }
+       return -1;
+}
+
+static int reverse_map(uint8_t u, const uint8_t *arr, int n)
+{
+       GVariant *v = g_variant_new_byte(u);
+       int i = find_in_array(v, G_VARIANT_TYPE_BYTE, arr, n);
+       g_variant_unref(v);
+       return i;
+}
+
+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 = sdi->priv;
+       struct parport *port;
+       int i, ch = -1;
+
+       if (cg) /* sr_config_get will validate cg using config_list */
+               ch = ((struct sr_channel *)cg->channels->data)->index;
+
+       switch (key) {
+       case SR_CONF_CONN:
+               port = sdi->conn;
+               *data = g_variant_new_string(port->name);
+               break;
+       case SR_CONF_LIMIT_FRAMES:
+               *data = g_variant_new_uint64(devc->frame_limit);
+               break;
+       case SR_CONF_SAMPLERATE:
+               *data = g_variant_new_uint64(samplerates[devc->rate]);
+               break;
+       case SR_CONF_TRIGGER_SOURCE:
+               i = reverse_map(devc->cctl[0] & 0xC0, trigger_sources_map,
+                               ARRAY_SIZE(trigger_sources_map));
+               if (i == -1)
+                       return SR_ERR;
+               *data = g_variant_new_string(trigger_sources[i]);
+               break;
+       case SR_CONF_TRIGGER_SLOPE:
+               if (devc->edge >= ARRAY_SIZE(trigger_slopes))
+                       return SR_ERR;
+               *data = g_variant_new_string(trigger_slopes[devc->edge]);
+               break;
+       case SR_CONF_BUFFERSIZE:
+               *data = g_variant_new_uint64(devc->buffersize);
+               break;
+       case SR_CONF_VDIV:
+               if (ch == -1)
+                       return SR_ERR_CHANNEL_GROUP;
+               i = reverse_map(devc->cctl[ch] & 0x33, vdivs_map,
+                               ARRAY_SIZE(vdivs_map));
+               if (i == -1)
+                       return SR_ERR;
+               *data = g_variant_new("(tt)", vdivs[i][0], vdivs[i][1]);
+               break;
+       case SR_CONF_COUPLING:
+               if (ch == -1)
+                       return SR_ERR_CHANNEL_GROUP;
+               i = reverse_map(devc->cctl[ch] & 0x0C, coupling_map,
+                               ARRAY_SIZE(coupling_map));
+               if (i == -1)
+                       return SR_ERR;
+               *data = g_variant_new_string(coupling[i]);
+               break;
+       case SR_CONF_PROBE_FACTOR:
+               if (ch == -1)
+                       return SR_ERR_CHANNEL_GROUP;
+               *data = g_variant_new_uint64(devc->probe[ch]);
+               break;
+       }
+
+       return SR_OK;
+}
+
+static int config_set(uint32_t key, GVariant *data,
+                     const struct sr_dev_inst *sdi,
+                     const struct sr_channel_group *cg)
+{
+       struct dev_context *devc = sdi->priv;
+       int i, ch = -1;
+       uint64_t u, v;
+
+       if (cg) /* sr_config_set will validate cg using config_list */
+               ch = ((struct sr_channel *)cg->channels->data)->index;
+
+       switch (key) {
+       case SR_CONF_LIMIT_FRAMES:
+               devc->frame_limit = g_variant_get_uint64(data);
+               break;
+       case SR_CONF_SAMPLERATE:
+               i = find_in_array(data, G_VARIANT_TYPE_UINT64,
+                                 samplerates, ARRAY_SIZE(samplerates));
+               if (i == -1)
+                       return SR_ERR_ARG;
+               devc->rate = i;
+               break;
+       case SR_CONF_TRIGGER_SOURCE:
+               i = find_in_array(data, G_VARIANT_TYPE_STRING,
+                                 trigger_sources, ARRAY_SIZE(trigger_sources));
+               if (i == -1)
+                       return SR_ERR_ARG;
+               devc->cctl[0] = (devc->cctl[0] & 0x3F) | trigger_sources_map[i];
+               break;
+       case SR_CONF_TRIGGER_SLOPE:
+               i = find_in_array(data, G_VARIANT_TYPE_STRING,
+                                 trigger_slopes, ARRAY_SIZE(trigger_slopes));
+               if (i == -1)
+                       return SR_ERR_ARG;
+               devc->edge = i;
+               break;
+       case SR_CONF_BUFFERSIZE:
+               i = find_in_array(data, G_VARIANT_TYPE_UINT64,
+                                 buffersizes, ARRAY_SIZE(buffersizes));
+               if (i == -1)
+                       return SR_ERR_ARG;
+               devc->buffersize = buffersizes[i];
+               break;
+       case SR_CONF_VDIV:
+               if (ch == -1)
+                       return SR_ERR_CHANNEL_GROUP;
+               if (!g_variant_is_of_type(data, G_VARIANT_TYPE("(tt)")))
+                       return SR_ERR_ARG;
+               g_variant_get(data, "(tt)", &u, &v);
+               for (i = 0; i < (int)ARRAY_SIZE(vdivs); i++)
+                       if (vdivs[i][0] == u && vdivs[i][1] == v)
+                               break;
+               if (i == ARRAY_SIZE(vdivs))
+                       return SR_ERR_ARG;
+               devc->cctl[ch] = (devc->cctl[ch] & 0xCC) | vdivs_map[i];
+               break;
+       case SR_CONF_COUPLING:
+               if (ch == -1)
+                       return SR_ERR_CHANNEL_GROUP;
+               i = find_in_array(data, G_VARIANT_TYPE_STRING,
+                                 coupling, ARRAY_SIZE(coupling));
+               if (i == -1)
+                       return SR_ERR_ARG;
+               devc->cctl[ch] = (devc->cctl[ch] & 0xF3) | coupling_map[i];
+               break;
+       case SR_CONF_PROBE_FACTOR:
+               if (ch == -1)
+                       return SR_ERR_CHANNEL_GROUP;
+               u = g_variant_get_uint64(data);
+               if (!u)
+                       return SR_ERR_ARG;
+               devc->probe[ch] = u;
+               break;
+       }
+       return SR_OK;
+}
+
+static int config_list(uint32_t key, GVariant **data,
+                      const struct sr_dev_inst *sdi,
+                      const struct sr_channel_group *cg)
+{
+       GVariantBuilder gvb;
+       GVariant *gvar, *rational[2];
+       GSList *l;
+       int i;
+
+       switch (key) {
+       case SR_CONF_SCAN_OPTIONS:
+       case SR_CONF_DEVICE_OPTIONS:
+               break;
+       case SR_CONF_SAMPLERATE:
+       case SR_CONF_TRIGGER_SOURCE:
+       case SR_CONF_TRIGGER_SLOPE:
+       case SR_CONF_BUFFERSIZE:
+               if (!sdi || cg)
+                       return SR_ERR_NA;
+               break;
+       case SR_CONF_VDIV:
+       case SR_CONF_COUPLING:
+               if (!sdi)
+                       return SR_ERR_NA;
+               if (!cg)
+                       return SR_ERR_CHANNEL_GROUP;
+               l = g_slist_find(sdi->channel_groups, cg);
+               if (!l)
+                       return SR_ERR_ARG;
+               break;
+       default:
+               return SR_ERR_NA;
+       }
+
+       switch (key) {
+       case SR_CONF_SCAN_OPTIONS:
+               *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                               scanopts, ARRAY_SIZE(scanopts), 
sizeof(uint32_t));
+               break;
+       case SR_CONF_DEVICE_OPTIONS:
+               if (!sdi)
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       drvopts, ARRAY_SIZE(drvopts), 
sizeof(uint32_t));
+               else if (!cg)
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       devopts, ARRAY_SIZE(devopts), 
sizeof(uint32_t));
+               else
+                       *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
+                                       cgopts, ARRAY_SIZE(cgopts), 
sizeof(uint32_t));
+               break;
+       case SR_CONF_SAMPLERATE:
+               g_variant_builder_init(&gvb, G_VARIANT_TYPE("a{sv}"));
+               gvar = g_variant_new_fixed_array(G_VARIANT_TYPE("t"),
+                               samplerates, ARRAY_SIZE(samplerates), 
sizeof(uint64_t));
+               g_variant_builder_add(&gvb, "{sv}", "samplerates", gvar);
+               *data = g_variant_builder_end(&gvb);
+               break;
+       case SR_CONF_TRIGGER_SOURCE:
+               *data = g_variant_new_strv(trigger_sources, 
ARRAY_SIZE(trigger_sources));
+               break;
+       case SR_CONF_TRIGGER_SLOPE:
+               *data = g_variant_new_strv(trigger_slopes, 
ARRAY_SIZE(trigger_slopes));
+               break;
+       case SR_CONF_BUFFERSIZE:
+               *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT64,
+                               buffersizes, ARRAY_SIZE(buffersizes), 
sizeof(uint64_t));
+               break;
+       case SR_CONF_VDIV:
+               g_variant_builder_init(&gvb, G_VARIANT_TYPE_ARRAY);
+               for (i = 0; i < (int)ARRAY_SIZE(vdivs); i++) {
+                       rational[0] = g_variant_new_uint64(vdivs[i][0]);
+                       rational[1] = g_variant_new_uint64(vdivs[i][1]);
+                       gvar = g_variant_new_tuple(rational, 2);
+                       g_variant_builder_add_value(&gvb, gvar);
+               }
+               *data = g_variant_builder_end(&gvb);
+               break;
+       case SR_CONF_COUPLING:
+               *data = g_variant_new_strv(coupling, ARRAY_SIZE(coupling));
+               break;
+       }
+
+       return SR_OK;
+}
+
+static int config_channel_set(const struct sr_dev_inst *sdi,
+                             struct sr_channel *channel,
+                             unsigned int changes)
+{
+       struct dev_context *devc = sdi->priv;
+       uint8_t v;
+
+       if (changes & SR_CHANNEL_SET_ENABLED) {
+               if (channel->enabled) {
+                       v = devc->channel | (1 << channel->index);
+                       if (v & (v - 1))
+                               return SR_ERR;
+                       devc->channel = v;
+                       devc->enabled_channel->data = channel;
+               } else {
+                       devc->channel &= ~(1 << channel->index);
+               }
+       }
+       return SR_OK;
+}
+
+static int dso2100_move_to(const struct sr_dev_inst *sdi, uint8_t target)
+{
+       struct dev_context *devc = sdi->priv;
+       int timeout = 40;
+       uint8_t c;
+
+       while (timeout--) {
+               c = dso2100_read_mbox(sdi->conn);
+               if (c == target)
+                       return SR_OK;
+
+               switch (c) {
+               case 0x00:
+                       /* Can happen if someone wrote something into
+                        * the mbox that was not expected by the uC.
+                        * Alternating between 0xff and 4 helps in
+                        * all states. */
+                       c = (timeout & 1) ? 0xFF : 0x04;
+                       break;
+               case 0x01:
+                       switch (target) {
+                       case 0x21: c = 2; break;
+                       case 0x03: c = 3; break;
+                       default: c = 4;
+                       }
+                       break;
+               case 0x03: c = 4; break;
+               case 0x05: c = devc->rate + 1; break;
+               case 0x06: c = devc->cctl[0]; break;
+               case 0x07: c = devc->cctl[1]; break;
+               case 0x08: c = 1 /* step 2 */ + 1 ; break;
+               case 0x09: c = devc->step1 + 1; break;
+               case 0x0A: c = devc->shift + 1; break;
+               case 0x0B: c = devc->edge + 1; break;
+               case 0x0C: c = devc->pos[0]; break;
+               case 0x0D: c = devc->pos[1]; break;
+               case 0x0E: c = devc->tlevel; break;
+               case 0x0F: c = devc->channel ? devc->channel : 1; break;
+               case 0x10: c = devc->offset[0]; break;
+               case 0x11: c = devc->gain[0]; break;
+               case 0x12: c = devc->offset[1]; break;
+               case 0x13: c = devc->gain[1]; break;
+               case 0x14:
+               case 0x21: c = 0xFF; break;
+               default:
+                       return SR_ERR_DATA;
+               }
+               dso2100_write_mbox(sdi->conn, c);
+       }
+       return SR_ERR_TIMEOUT;
+}
+
+static int dev_open(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+       int i;
+
+       if (sdi->status != SR_ST_INACTIVE)
+               goto fail1;
+
+       if (ieee1284_open(sdi->conn, 0, &i) != E1284_OK)
+               goto fail1;
+
+       if (ieee1284_claim(sdi->conn) != E1284_OK)
+               goto fail2;
+
+       if (ieee1284_data_dir(sdi->conn, 1) != E1284_OK)
+               goto fail3;
+
+       ieee1284_write_control(sdi->conn, C1284_NSTROBE | C1284_NAUTOFD | 
C1284_NSELECTIN);
+
+       if (ieee1284_data_dir(sdi->conn, 0) != E1284_OK)
+               goto fail3;
+
+       ieee1284_write_data(sdi->conn, 0);
+       ieee1284_write_control(sdi->conn, C1284_NSTROBE | C1284_NINIT | 
C1284_NSELECTIN);
+       ieee1284_write_control(sdi->conn, C1284_NSTROBE | C1284_NAUTOFD | 
C1284_NINIT);
+
+       if (ieee1284_data_dir(sdi->conn, 1) != E1284_OK)
+               goto fail3;
+
+       if (dso2100_move_to(sdi, 1))
+               goto fail3;
+
+       devc->samples = g_try_malloc(1000 * sizeof(*devc->samples));
+       if (!devc->samples)
+               goto fail3;
+
+       sdi->status = SR_ST_ACTIVE;
+       return SR_OK;
+
+fail3:
+       ieee1284_write_control(sdi->conn, C1284_NSTROBE | C1284_NAUTOFD | 
C1284_NSELECTIN);
+       ieee1284_data_dir(sdi->conn, 0);
+       ieee1284_release(sdi->conn);
+fail2:
+       ieee1284_close(sdi->conn);
+fail1:
+       return SR_ERR;
+}
+
+static int dev_close(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc = sdi->priv;
+
+       if (sdi->status != SR_ST_ACTIVE)
+               return SR_OK;
+
+       g_free(devc->samples);
+       ieee1284_write_control(sdi->conn,
+                       C1284_NSTROBE | C1284_NAUTOFD | C1284_NSELECTIN);
+       ieee1284_data_dir(sdi->conn, 0);
+       ieee1284_release(sdi->conn);
+       ieee1284_close(sdi->conn);
+       sdi->status = SR_ST_INACTIVE;
+
+       return SR_OK;
+}
+
+static void dso2100_skip_samples(struct parport *port, uint8_t ctrl, size_t 
num)
+{
+       while(num--) {
+               ieee1284_write_control(port, ctrl & ~C1284_NSTROBE);
+               ieee1284_write_control(port, ctrl);
+       }
+}
+
+static void dso2100_read_samples(struct parport *port, uint8_t ctrl, uint8_t 
*buf, size_t num)
+{
+       size_t i;
+       for (i = 0; i < num; i++) {
+               ieee1284_write_control(port, ctrl & ~C1284_NSTROBE);
+               buf[i] = ieee1284_read_data(port);
+               ieee1284_write_control(port, ctrl);
+       }
+}
+
+static void push_samples(const struct sr_dev_inst *sdi, uint8_t *buf, size_t 
num)
+{
+       struct dev_context *devc = sdi->priv;   
+       float *data = devc->samples;
+       struct sr_datafeed_analog analog = {
+               .channels = devc->enabled_channel,
+               .num_samples = num,
+               .mq = SR_MQ_VOLTAGE,
+               .unit = SR_UNIT_VOLT,
+               .mqflags = 0,
+               .data = data,
+       };
+       struct sr_datafeed_packet packet = {
+               .type = SR_DF_ANALOG,
+               .payload = &analog,
+       };
+       float factor = devc->factor;
+
+       while (num--)
+               data[num] = (buf[num] - 0x80) * factor;
+
+       sr_session_send(devc->cb_data, &packet);
+}
+
+static int dev_acquisition_stop(const struct sr_dev_inst *sdi, void *cb_data)
+{
+       struct sr_datafeed_packet packet = { .type = SR_DF_END };
+
+       sr_session_send(cb_data, &packet);
+       sr_session_source_remove(sdi->session, 0);
+
+       dso2100_move_to(sdi, 1);
+
+       return SR_OK;
+}
+
+static int dev_acquisition_stop_nonconst(struct sr_dev_inst *sdi, void 
*cb_data)
+{
+       return dev_acquisition_stop(sdi, cb_data);
+}
+
+static int dso2100_poll(int fd, int revents, void *cb_data)
+{
+       struct sr_datafeed_packet packet = { .type = SR_DF_FRAME_BEGIN };
+       const struct sr_dev_inst *sdi = cb_data;
+       struct dev_context *devc = sdi->priv;
+       uint8_t v, buf[1003], ctrl;
+       unsigned int offset;
+       int retries;
+
+       (void)fd;
+       (void)revents;
+
+       dso2100_write_mbox(sdi->conn, 0x99);
+       v = dso2100_read_mbox(sdi->conn);
+
+       if (v == 0x21)
+               return TRUE;
+       if (v != 0x03)
+               return FALSE;
+
+       sr_session_send(devc->cb_data, &packet);
+
+       if (devc->channel) {
+               ctrl = C1284_NSTROBE;
+               if (devc->channel == 2)
+                       ctrl |= C1284_NAUTOFD;
+               ieee1284_write_control(sdi->conn, ctrl);
+
+               offset = 0;
+               for (retries = 4; retries > 0;) {
+                       if (!offset) {
+                               dso2100_read_samples(sdi->conn, ctrl, buf, 
1003);
+                       } else {
+                               dso2100_skip_samples(sdi->conn, ctrl, 500);
+                               dso2100_read_samples(sdi->conn, ctrl, buf + 
500, 503);
+                       }
+                       if (buf[1000] == 0x01 && buf[1001] == 0xfe && buf[1002] 
== 0x80) {
+                               if (!offset) {
+                               push_samples(sdi, buf, 6);
+                                               packet.type = SR_DF_TRIGGER;
+                                       sr_session_send(devc->cb_data, &packet);
+                                       push_samples(sdi, buf, 1000 - 6);
+                                       offset += 1000;
+                               } else {
+                                       push_samples(sdi, buf + 500 , 500);
+                                       offset += 500;
+                               }
+                               if (offset >= devc->buffersize) {
+                                       break;
+                               } else {
+                                       devc->step1 = 10;
+                                       devc->shift = offset / (5 * 
devc->step1);
+                                       retries = 4;
+                               }
+                       } else {
+                               retries--;
+                               sr_dbg("Missing signature at end of buffer, %i 
tries remaining", retries);
+                       }
+                       dso2100_move_to(sdi, 1);
+                       dso2100_write_mbox(sdi->conn, 3);
+                       if (dso2100_read_mbox(sdi->conn) != 0x03)
+                               break;
+               }
+               if (!retries)
+                       sr_err("Failed to read frame without transfer errors");
+               devc->step1 = 1;
+               devc->shift = 100;
+       }
+
+       packet.type = SR_DF_FRAME_END;
+       sr_session_send(devc->cb_data, &packet);
+
+       if (++devc->frame >= devc->frame_limit)
+               dev_acquisition_stop(sdi, devc->cb_data);
+       else
+               dso2100_move_to(sdi, 0x21);
+
+       return TRUE;
+}
+
+static int config_commit(const struct sr_dev_inst *sdi)
+{
+       uint8_t state = dso2100_read_mbox(sdi->conn);
+       int ret;
+
+       if (sdi->status != SR_ST_ACTIVE)
+               return SR_ERR_DEV_CLOSED;
+
+       switch (state) {
+       case 0x03:
+       case 0x14:
+       case 0x21:
+               /* we will travel the complete config path on our way to state 
1 */
+               break;
+       case 0x00:
+               state = 0x01;
+       default:
+               ret = dso2100_move_to(sdi, 1);
+               if (ret != SR_OK)
+                       return ret;
+       case 0x01:
+               dso2100_write_mbox(sdi->conn, 4);
+       }
+       ret = dso2100_move_to(sdi, 1);
+       if (ret != SR_OK)
+               return ret;
+       return dso2100_move_to(sdi, state);
+}
+
+static int dev_acquisition_start(const struct sr_dev_inst *sdi, void *cb_data)
+{
+       struct dev_context *devc = sdi->priv;
+
+       if (sdi->status != SR_ST_ACTIVE)
+               return SR_ERR_DEV_CLOSED;
+
+       if (devc->channel) {
+               static const float res_array[] = {0.5, 1, 2, 5};
+               static const uint8_t relays[] = {100, 10, 10, 1};
+               devc->factor = devc->probe[devc->channel - 1] / 32.0;
+               devc->factor *= res_array[devc->cctl[devc->channel - 1] & 0x03];
+               devc->factor /= relays[(devc->cctl[devc->channel - 1] >> 4) & 
0x03];
+       }
+       devc->frame = 0;
+       devc->cb_data = cb_data;
+       dso2100_move_to(sdi, 0x21);
+       std_session_send_df_header(cb_data, LOG_PREFIX);
+
+       sr_session_source_add(sdi->session, 0, 0, 10,
+                             dso2100_poll, (void *)sdi);
+
+       return SR_OK;
+}
+
+SR_PRIV struct sr_dev_driver dso2100_driver_info = {
+       .name = "dso-2100",
+       .longname = "Hung-Chang DSO-2100",
+       .api_version = 1,
+       .init = init,
+       .cleanup = cleanup,
+       .scan = scan,
+       .dev_list = dev_list,
+       .dev_clear = dev_clear,
+       .config_get = config_get,
+       .config_set = config_set,
+       .config_channel_set = config_channel_set,
+       .config_commit = config_commit,
+       .config_list = config_list,
+       .dev_open = dev_open,
+       .dev_close = dev_close,
+       .dev_acquisition_start = dev_acquisition_start,
+       .dev_acquisition_stop = dev_acquisition_stop_nonconst,
+       .context = NULL,
+};
-- 
1.8.3.4


------------------------------------------------------------------------------
_______________________________________________
sigrok-devel mailing list
sigrok-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sigrok-devel

Reply via email to