]> sigrok.org Git - libsigrok.git/commitdiff
hung-chang-dso-2100: Add driver
authorDaniel Glöckner <redacted>
Mon, 7 Sep 2015 00:18:14 +0000 (02:18 +0200)
committerUwe Hermann <redacted>
Thu, 10 Sep 2015 12:42:12 +0000 (14:42 +0200)
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.

src/hardware/hung-chang-dso-2100/api.c
src/hardware/hung-chang-dso-2100/protocol.c
src/hardware/hung-chang-dso-2100/protocol.h

index d3c3c2865a2fc66af2078c74dc6f10c2d9439c55..1fd4df0b8e9388f346540ca6fe6af80b50e58559 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <ieee1284.h>
+#include <string.h>
 #include "protocol.h"
 
 SR_PRIV struct sr_dev_driver hung_chang_dso_2100_driver_info;
 
+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)
+};
+
+/* must be in sync with readout_steps[] in protocol.c */
+static const uint64_t buffersizes[] = {
+       2 * 500, 3 * 500, 4 * 500, 5 * 500,
+       6 * 500, 7 * 500, 8 * 500, 9 * 500, 10 * 500,
+       12 * 500 - 2, 14 * 500 - 2, 16 * 500 - 2,
+       18 * 500 - 2, 20 * 500 - 2, 10240 - 2
+};
+
+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 int init(struct sr_dev_driver *di, struct sr_context *sr_ctx)
 {
        return std_init(sr_ctx, di, LOG_PREFIX);
 }
 
-static GSList *scan(struct sr_dev_driver *di, GSList *options)
+static GSList *scan_port(GSList *devices, struct sr_dev_driver *di,
+                        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;
-       GSList *devices;
+       int i;
 
-       (void)options;
+       if (ieee1284_open(port, 0, &i) != E1284_OK) {
+               sr_err("Can't open parallel port %s", port->name);
+               goto fail1;
+       }
 
-       devices = NULL;
+       if ((i & (CAP1284_RAW | CAP1284_BYTE)) != (CAP1284_RAW | CAP1284_BYTE)) {
+               sr_err("Parallel port %s does not provide low-level bidirection access",
+                      port->name);
+               goto fail2;
+       }
+
+       if (ieee1284_claim(port) != E1284_OK) {
+               sr_err("Parallel port %s already in use", port->name);
+               goto fail2;
+       }
+
+       if (!hung_chang_dso_2100_check_id(port))
+               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 = di;
        drvc = di->context;
-       drvc->instances = NULL;
+       sdi->inst_type = 0; /* FIXME */
+       sdi->conn = port;
+       ieee1284_ref(port);
+
+       for (i = 0; i < NUM_CHANNELS; 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->channel = 0;
+       devc->rate = 0;
+       devc->probe[0] = 10;
+       devc->probe[1] = 10;
+       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->edge = 0;
+       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->frame_limit = 0;
+       devc->last_step = 0; /* buffersize = 1000 */
+       sdi->priv = devc;
+
+       drvc->instances = g_slist_append(drvc->instances, sdi);
+       devices = g_slist_append(devices, sdi);
+
+fail3:
+       ieee1284_release(port);
+fail2:
+       ieee1284_close(port);
+fail1:
+       return devices;
+}
+
+static GSList *scan(struct sr_dev_driver *di, GSList *options)
+{
+       struct parport_list ports;
+       struct sr_config *src;
+       const char *conn = NULL;
+       GSList *devices, *option;
+       gboolean port_found;
+       int i;
+
+
+       for (option = options; option; option = option->next) {
+               src = option->data;
+               if (src->key == SR_CONF_CONN) {
+                       conn = g_variant_get_string(src->data, NULL);
+                       break;
+               }
+       }
+
+       if (!conn)
+               return NULL;
 
-       /* TODO: scan for devices, either based on a SR_CONF_CONN option
-        * or on a USB scan. */
+       if (ieee1284_find_ports(&ports, 0) != E1284_OK)
+               return NULL;
+
+       devices = NULL;
+       port_found = FALSE;
+       for (i = 0; i < ports.portc; i++)
+               if (!strcmp(ports.portv[i]->name, conn)) {
+                       port_found = TRUE;
+                       devices = scan_port(devices, di, ports.portv[i]);
+               }
+
+       if (!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 devices;
 }
@@ -48,28 +234,77 @@ static GSList *dev_list(const struct sr_dev_driver *di)
        return ((struct drv_context *)(di->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 *di)
 {
-       return std_dev_clear(di, NULL);
+       struct drv_context *drvc = di->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(di, clear_private);
 }
 
 static int dev_open(struct sr_dev_inst *sdi)
 {
-       (void)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;
 
-       /* TODO: get handle from sdi->conn and open it. */
+       if (ieee1284_claim(sdi->conn) != E1284_OK)
+               goto fail2;
+
+       if (ieee1284_data_dir(sdi->conn, 1) != E1284_OK)
+               goto fail3;
+
+       if (hung_chang_dso_2100_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:
+       hung_chang_dso_2100_reset_port(sdi->conn);
+       ieee1284_release(sdi->conn);
+fail2:
+       ieee1284_close(sdi->conn);
+fail1:
+       return SR_ERR;
 }
 
 static int dev_close(struct sr_dev_inst *sdi)
 {
-       (void)sdi;
+       struct dev_context *devc = sdi->priv;
 
-       /* TODO: get handle from sdi->conn and close it. */
+       if (sdi->status != SR_ST_ACTIVE)
+               return SR_OK;
 
+       g_free(devc->samples);
+       hung_chang_dso_2100_reset_port(sdi->conn);
+       ieee1284_release(sdi->conn);
+       ieee1284_close(sdi->conn);
        sdi->status = SR_ST_INACTIVE;
 
        return SR_OK;
@@ -77,27 +312,141 @@ static int dev_close(struct sr_dev_inst *sdi)
 
 static int cleanup(const struct sr_dev_driver *di)
 {
-       dev_clear(di);
+       struct drv_context *drvc = di->context;
+       int ret;
 
-       /* TODO: free other driver resources, if any. */
+       ret = dev_clear(di);
 
-       return SR_OK;
+       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)
 {
-       int ret;
+       struct dev_context *devc = sdi->priv;
+       struct parport *port;
+       int ret, i, ch = -1;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       if (cg) /* sr_config_get will validate cg using config_list */
+               ch = ((struct sr_channel *)cg->channels->data)->index;
 
        ret = SR_OK;
        switch (key) {
-       /* TODO */
+       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)
+                       ret = SR_ERR;
+               else
+                       *data = g_variant_new_string(trigger_sources[i]);
+               break;
+       case SR_CONF_TRIGGER_SLOPE:
+               if (devc->edge >= ARRAY_SIZE(trigger_slopes))
+                       ret = SR_ERR;
+               else
+                       *data = g_variant_new_string(trigger_slopes[devc->edge]);
+               break;
+       case SR_CONF_BUFFERSIZE:
+               *data = g_variant_new_uint64(buffersizes[devc->last_step]);
+               break;
+       case SR_CONF_VDIV:
+               if (ch == -1) {
+                       ret = SR_ERR_CHANNEL_GROUP;
+               } else {
+                       i = reverse_map(devc->cctl[ch] & 0x33, vdivs_map,
+                                       ARRAY_SIZE(vdivs_map));
+                       if (i == -1)
+                               ret = SR_ERR;
+                       else
+                               *data = g_variant_new("(tt)", vdivs[i][0],
+                                                     vdivs[i][1]);
+               }
+               break;
+       case SR_CONF_COUPLING:
+               if (ch == -1) {
+                       ret = SR_ERR_CHANNEL_GROUP;
+               } else {
+                       i = reverse_map(devc->cctl[ch] & 0x0C, coupling_map,
+                                       ARRAY_SIZE(coupling_map));
+                       if (i == -1)
+                               ret = SR_ERR;
+                       else
+                               *data = g_variant_new_string(coupling[i]);
+               }
+               break;
+       case SR_CONF_PROBE_FACTOR:
+               if (ch == -1)
+                       ret = SR_ERR_CHANNEL_GROUP;
+               else
+                       *data = g_variant_new_uint64(devc->probe[ch]);
+               break;
        default:
-               return SR_ERR_NA;
+               ret = SR_ERR_NA;
        }
 
        return ret;
@@ -106,17 +455,95 @@ static int config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *s
 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 = sdi->priv;
+       int ret, i, ch = -1;
+       uint64_t u, v;
 
-       (void)data;
-       (void)cg;
+       if (cg) /* sr_config_set will validate cg using config_list */
+               ch = ((struct sr_channel *)cg->channels->data)->index;
 
        if (sdi->status != SR_ST_ACTIVE)
                return SR_ERR_DEV_CLOSED;
 
        ret = SR_OK;
        switch (key) {
-       /* TODO */
+       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)
+                       ret = SR_ERR_ARG;
+               else
+                       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)
+                       ret = SR_ERR_ARG;
+               else
+                       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)
+                       ret = SR_ERR_ARG;
+               else
+                       devc->edge = i;
+               break;
+       case SR_CONF_BUFFERSIZE:
+               i = find_in_array(data, G_VARIANT_TYPE_UINT64,
+                                 buffersizes, ARRAY_SIZE(buffersizes));
+               if (i == -1)
+                       ret = SR_ERR_ARG;
+               else
+                       devc->last_step = i;
+               break;
+       case SR_CONF_VDIV:
+               if (ch == -1) {
+                       ret = SR_ERR_CHANNEL_GROUP;
+               } else if (!g_variant_is_of_type(data, G_VARIANT_TYPE("(tt)"))) {
+                       ret = SR_ERR_ARG;
+               } else {
+                       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))
+                               ret = SR_ERR_ARG;
+                       else
+                               devc->cctl[ch] = (devc->cctl[ch] & 0xCC)
+                                              | vdivs_map[i];
+               }
+               break;
+       case SR_CONF_COUPLING:
+               if (ch == -1) {
+                       ret = SR_ERR_CHANNEL_GROUP;
+               } else {
+                       i = find_in_array(data, G_VARIANT_TYPE_STRING,
+                                         coupling, ARRAY_SIZE(coupling));
+                       if (i == -1)
+                               ret = SR_ERR_ARG;
+                       else
+                               devc->cctl[ch] = (devc->cctl[ch] & 0xF3)
+                                              | coupling_map[i];
+               }
+               break;
+       case SR_CONF_PROBE_FACTOR:
+               if (ch == -1) {
+                       ret = SR_ERR_CHANNEL_GROUP;
+               } else {
+                       u = g_variant_get_uint64(data);
+                       if (!u)
+                               ret = SR_ERR_ARG;
+                       else
+                               devc->probe[ch] = u;
+               }
+               break;
        default:
                ret = SR_ERR_NA;
        }
@@ -124,52 +551,196 @@ static int config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sd
        return ret;
 }
 
-static int config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi,
-               const struct sr_channel_group *cg)
+static int config_channel_set(const struct sr_dev_inst *sdi,
+                             struct sr_channel *ch,
+                             unsigned int changes)
+{
+       struct dev_context *devc = sdi->priv;
+       uint8_t v;
+
+       if (changes & SR_CHANNEL_SET_ENABLED) {
+               if (ch->enabled) {
+                       v = devc->channel | (1 << ch->index);
+                       if (v & (v - 1))
+                               return SR_ERR;
+                       devc->channel = v;
+                       devc->enabled_channel->data = ch;
+               } else {
+                       devc->channel &= ~(1 << ch->index);
+               }
+       }
+       return SR_OK;
+}
+
+static int config_commit(const struct sr_dev_inst *sdi)
 {
+       uint8_t state = hung_chang_dso_2100_read_mbox(sdi->conn, 0.02);
        int ret;
 
-       (void)sdi;
-       (void)data;
-       (void)cg;
+       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 = hung_chang_dso_2100_move_to(sdi, 1);
+               if (ret != SR_OK)
+                       return ret;
+       case 0x01:
+               hung_chang_dso_2100_write_mbox(sdi->conn, 4);
+       }
+       ret = hung_chang_dso_2100_move_to(sdi, 1);
+       if (ret != SR_OK)
+               return ret;
+       return hung_chang_dso_2100_move_to(sdi, state);
+}
+
+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;
 
-       ret = SR_OK;
        switch (key) {
-       /* TODO */
+               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;
        }
 
-       return ret;
+       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 dev_acquisition_start(const struct sr_dev_inst *sdi,
                void *cb_data)
 {
-       (void)sdi;
-       (void)cb_data;
+       struct dev_context *devc = sdi->priv;
+       int ret;
 
        if (sdi->status != SR_ST_ACTIVE)
                return SR_ERR_DEV_CLOSED;
 
-       /* TODO: configure hardware, reset acquisition state, set up
-        * callbacks and send header packet. */
+       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;
+       devc->state_known = TRUE;
+       devc->step = 0;
+       devc->adc2 = FALSE;
+       devc->retries = MAX_RETRIES;
+
+       ret = hung_chang_dso_2100_move_to(sdi, 0x21);
+       if (ret != SR_OK)
+               return ret;
+
+       std_session_send_df_header(cb_data, LOG_PREFIX);
+
+       sr_session_source_add(sdi->session, 0, 0, 8,
+                             hung_chang_dso_2100_poll, (void *)sdi);
 
        return SR_OK;
 }
 
-static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data)
+SR_PRIV int hung_chang_dso_2100_dev_acquisition_stop(const struct sr_dev_inst *sdi,
+               void *cb_data)
 {
-       (void)cb_data;
+       struct sr_datafeed_packet packet = { .type = SR_DF_END };
 
        if (sdi->status != SR_ST_ACTIVE)
                return SR_ERR_DEV_CLOSED;
 
-       /* TODO: stop acquisition. */
+       sr_session_send(cb_data, &packet);
+       sr_session_source_remove(sdi->session, 0);
+
+       hung_chang_dso_2100_move_to(sdi, 1);
 
        return SR_OK;
 }
 
+static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data)
+{
+       return hung_chang_dso_2100_dev_acquisition_stop(sdi, cb_data);
+}
+
 SR_PRIV struct sr_dev_driver hung_chang_dso_2100_driver_info = {
        .name = "hung-chang-dso-2100",
        .longname = "Hung-Chang DSO-2100",
@@ -181,6 +752,8 @@ SR_PRIV struct sr_dev_driver hung_chang_dso_2100_driver_info = {
        .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,
index 1a5dd33d959583b715c380f644a80f9591470885..6f997db020389ed30d8abdaee0c05651ecc19c84 100644 (file)
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <ieee1284.h>
 #include "protocol.h"
 
-SR_PRIV int hung_chang_dso_2100_receive_data(int fd, int revents, void *cb_data)
+/* The firmware can be in the following states:
+ *  0x00       Temporary state during initialization
+ *             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 artificial 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 and 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
+ *
+ * It takes about 200ns for the data lines to become stable after
+ * the control lines have been changed. This driver assumes that
+ * parallel port access is slow enough to not require additional
+ * delays.
+ *
+ * Channel values in state 0x14 and the mailbox can change their
+ * value while they are selected, the latter of course only from
+ * 0 to a valid state. Beware of intermediate values.
+ *
+ * SRAM N layout (N = 1 or 2):
+ * 0x0000-0x13ff       samples captured from ADC N
+ * 0x4000-0x41f3       bytes extracted from 0x6000 with step1
+ *                     (both ADCs but only channel N)
+ * 0x41f4-0x43e7       bytes extracted from 0x6000+5*step1*shift
+ *                     with step2 (both ADCs but only channel N)
+ * 0x43e8-0x43ea       {0x01, 0xfe, 0x80}
+ * 0x43eb-0x444e       copy of bytes from 0x4320
+ * 0x6000-0x7387       interleaved SRAM 1 and SRAM 2 bytes from
+ *                     0x0001 to 0x09c5 after channel N was captured
+ *
+ * On a trigger event the FPGA directs the ADC samples to the region
+ * at 0x0000. The microcontroller then copies 5000 samples from 0x0001
+ * to 0x6000. Each time state 0x03 is entered, the bytes from 0x4000
+ * to 0x444e are filled and the start address for readout is reset to
+ * 0x4000. Readout will wrap around back to 0x4000 after reaching 0x7fff.
+ *
+ * As you can see from the layout, it was probably intended to capture
+ * 5000 samples for both probes before they are read out. We don't do that
+ * to be able to read the full 10k samples captured by the FPGA. It would
+ * be useless anyway if you don't capture repetitive signals. We're also
+ * not reading the two samples at 0x0000 to save a few milliseconds.
+ */
+
+static const struct {
+       uint16_t num;
+       uint8_t step1;
+       uint8_t shift;
+       uint8_t interleave;
+} readout_steps[] = {
+       { 1000, 1, 100, 0 },
+       { 500, 100, 2, 0 },
+       { 500, 100, 3, 0 },
+       { 500, 100, 4, 0 },
+       { 500, 100, 5, 0 },
+       { 500, 100, 6, 0 },
+       { 500, 100, 7, 0 },
+       { 500, 100, 8, 0 },
+       { 500, 100, 9, 0 },
+       { 499, 212, 41, 1 },
+       { 500, 157, 56, 1 },
+       { 500, 247, 36, 1 },
+       { 500, 232, 180, 1 },
+       { 500, 230, 182, 1 },
+       { 120, 212, 43, 1 }
+};
+
+SR_PRIV void hung_chang_dso_2100_reset_port(struct parport *port)
 {
+       ieee1284_write_control(port,
+                       C1284_NSTROBE | C1284_NAUTOFD | C1284_NSELECTIN);
+       ieee1284_data_dir(port, 0);
+}
+
+SR_PRIV gboolean hung_chang_dso_2100_check_id(struct parport *port)
+{
+       gboolean ret = FALSE;
+
+       if (ieee1284_data_dir(port, 1) != E1284_OK)
+               goto fail;
+
+       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 fail;
+
+       ret = TRUE;
+fail:
+       hung_chang_dso_2100_reset_port(port);
+
+       return ret;
+}
+
+SR_PRIV void hung_chang_dso_2100_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);
+}
+
+SR_PRIV uint8_t hung_chang_dso_2100_read_mbox(struct parport *port, float timeout)
+{
+       GTimer *timer = NULL;
+       uint8_t val;
+
+       ieee1284_write_control(port, C1284_NSTROBE | C1284_NSELECTIN);
+       ieee1284_write_control(port, C1284_NSELECTIN);
+
+       for (;;) {
+               if (ieee1284_read_data(port)) {
+                       /* Always read the value a second time.
+                        * The first one may be unstable. */
+                       val = ieee1284_read_data(port);
+                       break;
+               }
+               if (!timer) {
+                       timer = g_timer_new();
+               } else if (g_timer_elapsed(timer, NULL) > timeout) {
+                       val = 0;
+                       break;
+               }
+       }
+
+       ieee1284_write_control(port, C1284_NSTROBE | C1284_NSELECTIN);
+       ieee1284_write_control(port,
+               C1284_NSTROBE | C1284_NAUTOFD | C1284_NINIT | C1284_NSELECTIN);
+
+       if (timer)
+               g_timer_destroy(timer);
+       sr_dbg("mbox == %X", val);
+       return val;
+}
+
+SR_PRIV int hung_chang_dso_2100_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 = hung_chang_dso_2100_read_mbox(sdi->conn, 0.1);
+               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 = readout_steps[devc->step].step1 + 1; break;
+               case 0x0A: c = readout_steps[devc->step].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:
+                       if (!devc->channel)
+                               c = 1;
+                       else if (readout_steps[devc->step].interleave)
+                               c = devc->adc2 ? 2 : 1;
+                       else
+                               c = devc->channel;
+                       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;
+               }
+               hung_chang_dso_2100_write_mbox(sdi->conn, c);
+       }
+       return SR_ERR_TIMEOUT;
+}
+
+static void 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 read_samples(struct parport *port, uint8_t ctrl, uint8_t *buf, size_t num, size_t stride)
+{
+       while (num--) {
+               ieee1284_write_control(port, ctrl & ~C1284_NSTROBE);
+               *buf = ieee1284_read_data(port);
+               buf += stride;
+               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 read_subframe(const struct sr_dev_inst *sdi, uint8_t *buf)
+{
+       struct dev_context *devc = sdi->priv;
+       uint8_t sig[3], ctrl;
+       unsigned int num;
+       gboolean interleave;
+
+       interleave = readout_steps[devc->step].interleave;
+       ctrl = C1284_NSTROBE;
+       if ((interleave && devc->adc2) || (!interleave && devc->channel == 2))
+               ctrl |= C1284_NAUTOFD;
+
+       ieee1284_write_control(sdi->conn, ctrl);
+       num = readout_steps[devc->step].num;
+       if (num < 1000)
+               skip_samples(sdi->conn, ctrl, 1000 - num);
+       read_samples(sdi->conn, ctrl, buf + (devc->adc2 ? 1 : 0), num,
+                    interleave ? 2 : 1);
+       read_samples(sdi->conn, ctrl, sig, 3, 1);
+       if (sig[0] != 0x01 || sig[1] != 0xfe || sig[2] != 0x80) {
+               if (--devc->retries) {
+                       sr_dbg("Missing signature at end of buffer, %i tries remaining",
+                              devc->retries);
+                       return TRUE;
+               } else {
+                       sr_err("Failed to read frame without transfer errors");
+                       devc->step = 0;
+               }
+       } else {
+               if (interleave && !devc->adc2) {
+                       devc->adc2 = TRUE;
+                       devc->retries = MAX_RETRIES;
+                       return TRUE;
+               } else {
+                       if (interleave)
+                               num *= 2;
+                       if (!devc->step) {
+                               struct sr_datafeed_packet packet = {
+                                       .type = SR_DF_TRIGGER
+                               };
+
+                               push_samples(sdi, buf, 6);
+                               sr_session_send(devc->cb_data, &packet);
+                               buf += 6;
+                               num -= 6;
+                       }
+                       push_samples(sdi, buf, num);
+                       if (++devc->step > devc->last_step)
+                               devc->step = 0;
+               }
+       }
+
+       devc->adc2 = FALSE;
+       devc->retries = MAX_RETRIES;
+
+       return devc->step > 0;
+}
+
+SR_PRIV int hung_chang_dso_2100_poll(int fd, int revents, void *cb_data)
+{
+       struct sr_datafeed_packet packet = { .type = SR_DF_FRAME_BEGIN };
        const struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       uint8_t state, buf[1000];
 
        (void)fd;
+       (void)revents;
 
        if (!(sdi = cb_data))
                return TRUE;
@@ -32,9 +424,40 @@ SR_PRIV int hung_chang_dso_2100_receive_data(int fd, int revents, void *cb_data)
        if (!(devc = sdi->priv))
                return TRUE;
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       if (devc->state_known)
+               hung_chang_dso_2100_write_mbox(sdi->conn, 0x99);
+
+       state = hung_chang_dso_2100_read_mbox(sdi->conn, 0.00025);
+       devc->state_known = (state != 0x00);
+
+       if (!devc->state_known || state == 0x21)
+               return TRUE;
+
+       if (state != 0x03) {
+               sr_err("Unexpected state 0x%X while checking for trigger");
+               return FALSE;
+       }
+
+       sr_session_send(devc->cb_data, &packet);
+
+       if (devc->channel) {
+               while (read_subframe(sdi, buf)) {
+                       if (hung_chang_dso_2100_move_to(sdi, 1) != SR_OK)
+                               break;
+                       hung_chang_dso_2100_write_mbox(sdi->conn, 3);
+                       g_usleep(1700);
+                       if (hung_chang_dso_2100_read_mbox(sdi->conn, 0.02) != 0x03)
+                               break;
+               }
        }
 
+       packet.type = SR_DF_FRAME_END;
+       sr_session_send(devc->cb_data, &packet);
+
+       if (++devc->frame >= devc->frame_limit)
+               hung_chang_dso_2100_dev_acquisition_stop(sdi, devc->cb_data);
+       else
+               hung_chang_dso_2100_move_to(sdi, 0x21);
+
        return TRUE;
 }
index 4a24aebc68edc1a9fe75e72942a1870143350d8d..5636699a87b4126be9c2aeaa7f397bdf5a8e3b26 100644 (file)
 #include "libsigrok-internal.h"
 
 #define LOG_PREFIX "hung-chang-dso-2100"
+#define MAX_RETRIES 4
+#define NUM_CHANNELS 2
 
 /** Private, per-device-instance driver context. */
 struct dev_context {
-       /* Model-specific information */
-
        /* Acquisition settings */
+       GSList *enabled_channel;
+       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];
 
        /* Operational state */
+       uint64_t frame_limit;
+       uint64_t frame;
+       uint64_t probe[2];
+       uint8_t step;
+       uint8_t last_step;
+       uint8_t retries;
+       gboolean adc2;
 
        /* Temporary state across callbacks */
-
+       void *cb_data;
+       float *samples;
+       float factor;
+       gboolean state_known;
 };
 
-SR_PRIV int hung_chang_dso_2100_receive_data(int fd, int revents, void *cb_data);
+SR_PRIV void hung_chang_dso_2100_reset_port(struct parport *port);
+SR_PRIV gboolean hung_chang_dso_2100_check_id(struct parport *port);
+SR_PRIV void hung_chang_dso_2100_write_mbox(struct parport *port, uint8_t val);
+SR_PRIV uint8_t hung_chang_dso_2100_read_mbox(struct parport *port, float timeout);
+SR_PRIV int hung_chang_dso_2100_move_to(const struct sr_dev_inst *sdi, uint8_t target);
+SR_PRIV int hung_chang_dso_2100_poll(int fd, int revents, void *cb_data);
+SR_PRIV int hung_chang_dso_2100_dev_acquisition_stop(const struct sr_dev_inst *sdi, void *cb_data);
 
 #endif