Signed-off-by: Bartosz Golaszewski <bgolaszew...@baylibre.com> --- Makefile.am | 4 + configure.ac | 11 + src/drivers.c | 6 + src/hardware/acme/acme.c | 770 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 791 insertions(+) create mode 100644 src/hardware/acme/acme.c
diff --git a/Makefile.am b/Makefile.am index 319f932..9b30e39 100644 --- a/Makefile.am +++ b/Makefile.am @@ -117,6 +117,10 @@ libsigrok_la_SOURCES += \ endif # Hardware drivers +if HW_ACME +libsigrok_la_SOURCES += \ + src/hardware/acme/acme.c +endif if HW_AGILENT_DMM libsigrok_la_SOURCES += \ src/hardware/agilent-dmm/api.c \ diff --git a/configure.ac b/configure.ac index 39766c6..6aba6dd 100644 --- a/configure.ac +++ b/configure.ac @@ -33,6 +33,9 @@ AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([autostuff]) AC_CONFIG_AUX_DIR([autostuff]) +# We require this to know the target settings if we're cross-compiling. +AC_CANONICAL_SYSTEM + # We require at least automake 1.11 (needed for 'silent rules'). AM_INIT_AUTOMAKE([1.11 -Wall -Werror subdir-objects check-news color-tests]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) @@ -102,6 +105,7 @@ AC_DEFUN([DRIVER], [ driver_summary="${driver_summary} - $2${dots} \${m4_join([], [HW_], upperize([$2]))}@" ]) +DRIVER([Another Cute Measurement Equipment], [acme]) DRIVER([Agilent DMM], [agilent-dmm]) DRIVER([Appa 55II], [appa-55ii]) DRIVER([ASIX SIGMA/SIGMA2], [asix-sigma]) @@ -427,6 +431,12 @@ AM_CONDITIONAL(HAVE_CHECK, test x"$have_check" = "xyes") # build it if these headers aren't available. AC_CHECK_HEADERS([sys/mman.h sys/ioctl.h], [], [HW_BEAGLELOGIC="no"]) +# The ACME driver can only be built for Linux. +case "$target_os" in + linux*) ;; + *) HW_ACME="no" ;; +esac + AC_SUBST(SR_PKGLIBS) CFLAGS="$CFLAGS -Iinclude/libsigrok -I${srcdir}/include/libsigrok $LIB_CFLAGS" @@ -466,6 +476,7 @@ AC_DEFUN([DRIVER2], [ if test "x$2" = "xyes"; then AC_DEFINE($3, 1, [.]) fi ]) +DRIVER2([HW_ACME], [$HW_ACME], [HAVE_HW_ACME]) DRIVER2([HW_AGILENT_DMM], [$HW_AGILENT_DMM], [HAVE_HW_AGILENT_DMM]) DRIVER2([HW_APPA_55II], [$HW_APPA_55II], [HAVE_HW_APPA_55II]) DRIVER2([HW_ASIX_SIGMA], [$HW_ASIX_SIGMA], [HAVE_HW_ASIX_SIGMA]) diff --git a/src/drivers.c b/src/drivers.c index 9b970fc..9fcf6a9 100644 --- a/src/drivers.c +++ b/src/drivers.c @@ -21,6 +21,9 @@ #include "libsigrok-internal.h" /** @cond PRIVATE */ +#ifdef HAVE_HW_ACME +extern SR_PRIV struct sr_dev_driver acme_driver_info; +#endif #ifdef HAVE_HW_AGILENT_DMM extern SR_PRIV struct sr_dev_driver agdmm_driver_info; #endif @@ -217,6 +220,9 @@ extern SR_PRIV struct sr_dev_driver zeroplus_logic_cube_driver_info; #endif SR_PRIV struct sr_dev_driver *drivers_list[] = { +#ifdef HAVE_HW_ACME + &acme_driver_info, +#endif #ifdef HAVE_HW_AGILENT_DMM &agdmm_driver_info, #endif diff --git a/src/hardware/acme/acme.c b/src/hardware/acme/acme.c new file mode 100644 index 0000000..4715a14 --- /dev/null +++ b/src/hardware/acme/acme.c @@ -0,0 +1,770 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2015 Bartosz Golaszewski <bgolaszew...@baylibre.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <glib.h> +#include <string.h> +#include <errno.h> +#include "libsigrok.h" +#include "libsigrok-internal.h" +/* We can include these, since we're compiling for Linux anyway. */ +#include <unistd.h> +#include <fcntl.h> + +#define LOG_PREFIX "acme" + +/* We support up to 8 energy/temperature probes. */ +#define MAX_PROBES 8 + +/* + * Temperature probes can be connected to the last four ports on the + * ACME cape. When scanning, first look for temperature probes starting + * from this index. + */ +#define TEMP_PRB_START_INDEX 4 + +#define ENRG_PROBE_NAME "ina226" +#define TEMP_PROBE_NAME "tmp435" + +/* For the user we number the probes starting from 1. */ +#define PROBE_NUM(n) ((n)+1) + +enum probe_type { + PROBE_ENRG = 1, + PROBE_TEMP, +}; + +enum channel_type { + ENRG_PWR = 1, + ENRG_CURR, + ENRG_VOL, + TEMP_IN, + TEMP_OUT, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS | SR_CONF_SET, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +#define MAX_SAMPLE_RATE 500 /* In HZ */ + +static const uint64_t samplerates[] = { + SR_HZ(1), + SR_HZ(MAX_SAMPLE_RATE), + SR_HZ(1), +}; + +static const uint32_t enrg_i2c_addrs[] = { + 0x40, 0x41, 0x44, 0x45, 0x42, 0x43, 0x46, 0x47, +}; + +static const uint32_t temp_i2c_addrs[] = { + 0x0, 0x0, 0x0, 0x0, 0x4c, 0x49, 0x4f, 0x4b, +}; + +struct channel_group_priv { + int prb_type; + int hwmon_num; +}; + +struct channel_priv { + int ch_type; + struct channel_group_priv *probe; +}; + +/** Private, per-device-instance driver context. */ +struct dev_context { + uint64_t samplerate; + uint64_t limit_samples; + uint64_t limit_msec; + + uint64_t samples_read; + int64_t start_time; + int64_t last_sample_fin; + int pipe_fds[2]; + GIOChannel *channel; +}; + +SR_PRIV struct sr_dev_driver acme_driver_info; +static struct sr_dev_driver *di = &acme_driver_info; + +static int init(struct sr_context *sr_ctx) +{ + return std_init(sr_ctx, di, LOG_PREFIX); +} + +/* + * For given address fill buf with the path to the sysfs entry where the name + * of the driver in charge of given address can be found. + */ +static int probe_name_path(unsigned addr, char *buf, size_t buflen) +{ + return snprintf(buf, buflen, + "/sys/class/i2c-adapter/i2c-1/1-00%02x/name", addr); +} + +/* + * For given address fill buf with the path to appropriate hwmon entry. + */ +static int probe_hwmon_path(unsigned addr, char *buf, size_t buflen) +{ + return snprintf(buf, buflen, + "/sys/class/i2c-adapter/i2c-1/1-00%02x/hwmon", addr); +} + +static gboolean detect_probe(unsigned addr, int prb_num, const char *prb_name) +{ + gboolean ret = FALSE, status; + char *buf = NULL; + char path[256]; + GError *err; + gsize size; + + probe_name_path(addr, path, sizeof(path)); + status = g_file_get_contents(path, &buf, &size, &err); + if (!status) { + sr_warn("Name for probe %d can't be read: %s", + prb_num, err->message); + } + + if (strncmp(buf, prb_name, strlen(prb_name)) == 0) { + /* + * Correct driver registered on this address - but is + * there an actual probe connected? + */ + probe_hwmon_path(addr, path, sizeof(path)); + status = g_file_test(path, G_FILE_TEST_IS_DIR); + if (status) { + /* We have found an ACME probe. */ + ret = TRUE; + } + } + + if (buf) + g_free(buf); + + return ret; +} + +static int get_hwmon_index(unsigned addr) +{ + int status, hwmon; + char path[256]; + GError *err; + GDir *dir; + + probe_hwmon_path(addr, path, sizeof(path)); + dir = g_dir_open(path, 0, &err); + if (dir == NULL) { + sr_err("Error opening %s: %s", path, err->message); + return -1; + } + + /* + * The directory should contain a single file named hwmonX where X + * is the hwmon index. + */ + status = sscanf(g_dir_read_name(dir), "hwmon%d", &hwmon); + g_dir_close(dir); + if (status != 1) { + sr_err("Unable to determine the hwmon entry"); + return -1; + } + + return hwmon; +} + +static void append_channel(struct sr_dev_inst *sdi, + struct sr_channel_group *chg, + int index, + int type) +{ + struct channel_priv *cp; + struct sr_channel *ch; + char *name; + + switch (type) { + case ENRG_PWR: + name = g_strdup_printf("P%d_ENRG_PWR", index); + break; + case ENRG_CURR: + name = g_strdup_printf("P%d_ENRG_CURR", index); + break; + case ENRG_VOL: + name = g_strdup_printf("P%d_ENRG_VOL", index); + break; + case TEMP_IN: + name = g_strdup_printf("P%d_TEMP_IN", index); + break; + case TEMP_OUT: + name = g_strdup_printf("P%d_TEMP_OUT", index); + break; + default: + sr_err("Programmer goofed!"); + return; + } + + cp = g_malloc0(sizeof(struct channel_priv)); + cp->ch_type = type; + cp->probe = chg->priv; + + ch = sr_channel_new(index, SR_CHANNEL_ANALOG, TRUE, name); + ch->priv = cp; + chg->channels = g_slist_append(chg->channels, ch); + sdi->channels = g_slist_append(sdi->channels, ch); +} + +static gboolean register_probe(struct sr_dev_inst *sdi, + int type, + unsigned addr, + int prb_num) +{ + struct sr_channel_group *chg; + struct channel_group_priv *cgp; + struct dev_context *devc; + int hwmon; + + devc = sdi->priv; + + /* Obtain the hwmon index. */ + hwmon = get_hwmon_index(addr); + if (hwmon < 0) + return FALSE; + + chg = g_malloc0(sizeof(struct sr_channel_group)); + cgp = g_malloc0(sizeof(struct channel_group_priv)); + cgp->prb_type = type; + cgp->hwmon_num = hwmon; + chg->name = g_strdup_printf("Probe_%d", prb_num); + chg->priv = cgp; + + if (type == PROBE_ENRG) { + append_channel(sdi, chg, prb_num, ENRG_PWR); + append_channel(sdi, chg, prb_num, ENRG_CURR); + append_channel(sdi, chg, prb_num, ENRG_VOL); + } else if (type == PROBE_TEMP) { + append_channel(sdi, chg, prb_num, TEMP_IN); + append_channel(sdi, chg, prb_num, TEMP_OUT); + } else { + sr_err("Programmer goofed!"); + } + + sdi->channel_groups = g_slist_append(sdi->channel_groups, chg); + + return TRUE; +} + +static GSList *scan(GSList *options) +{ + struct drv_context *drvc; + struct dev_context *devc; + struct sr_dev_inst *sdi; + GSList *devices; + gboolean status; + int i; + + (void)options; + + drvc = di->priv; + devices = NULL; + + devc = g_malloc0(sizeof(struct dev_context)); + devc->samplerate = SR_HZ(10); + + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INACTIVE; + sdi->model = g_strdup("Acme cape"); + sdi->driver = di; + sdi->priv = devc; + + /* + * We expect sysfs to be present and mounted at /sys, ina226 and + * tmp435 sensors detected by the system and their appropriate + * drivers loaded and functional. + */ + status = g_file_test("/sys", G_FILE_TEST_IS_DIR); + if (!status) { + sr_err("/sys/ directory not found - sysfs not mounted?"); + goto err_out; + } + + /* + * Iterate over all ACME connectors and check if any probes + * are present. + */ + for (i = 0; i < MAX_PROBES; i++) { + /* + * First check if there's an energy probe on this connector. If + * not, and we're already at the fifth probe - see if we can + * detect a temperature probe. + */ + status = detect_probe(enrg_i2c_addrs[i], + PROBE_NUM(i), + ENRG_PROBE_NAME); + if (status) { + /* Energy probe detected. */ + status = register_probe(sdi, + PROBE_ENRG, + enrg_i2c_addrs[i], + PROBE_NUM(i)); + if (!status) { + sr_err("Error registering power probe %d", + PROBE_NUM(i)); + continue; + } + + } else if (i >= TEMP_PRB_START_INDEX) { + status = detect_probe(temp_i2c_addrs[i], + PROBE_NUM(i), + TEMP_PROBE_NAME); + if (status) { + /* Temperature probe detected. */ + status = register_probe(sdi, + PROBE_TEMP, + temp_i2c_addrs[i], + PROBE_NUM(i)); + if (!status) { + sr_err("Error registering temp " + "probe %d", PROBE_NUM(i)); + continue; + } + } + + } + } + + devices = g_slist_append(devices, sdi); + drvc->instances = g_slist_append(drvc->instances, sdi); + + return devices; + +err_out: + g_free(devc); + sr_dev_inst_free(sdi); + + return NULL; +} + +static GSList *dev_list(void) +{ + return ((struct drv_context *)(di->priv))->instances; +} + +static int dev_clear(void) +{ + return std_dev_clear(di, NULL); +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* Nothing to do here. */ + sdi->status = SR_ST_ACTIVE; + + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + (void)sdi; + + /* Nothing to do here. */ + sdi->status = SR_ST_INACTIVE; + + return SR_OK; +} + +static int cleanup(void) +{ + std_dev_clear(di, NULL); + + return SR_OK; +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) +{ + struct dev_context *devc; + int ret; + + (void)sdi; + (void)data; + (void)cg; + + devc = sdi->priv; + + ret = SR_OK; + switch (key) { + case SR_CONF_LIMIT_SAMPLES: + *data = g_variant_new_uint64(devc->limit_samples); + break; + case SR_CONF_LIMIT_MSEC: + *data = g_variant_new_uint64(devc->limit_msec); + break; + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(devc->samplerate); + break; + 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) +{ + struct dev_context *devc; + uint64_t samplerate; + int ret; + + (void)data; + (void)cg; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + devc = sdi->priv; + + ret = SR_OK; + switch (key) { + case SR_CONF_LIMIT_SAMPLES: + devc->limit_samples = g_variant_get_uint64(data); + devc->limit_msec = 0; + sr_dbg("Setting sample limit to %" PRIu64, + devc->limit_samples); + break; + case SR_CONF_LIMIT_MSEC: + devc->limit_msec = g_variant_get_uint64(data) * 1000; + devc->limit_samples = 0; + sr_dbg("Setting time limit to %" PRIu64"ms", devc->limit_msec); + break; + case SR_CONF_SAMPLERATE: + samplerate = g_variant_get_uint64(data); + if (samplerate > MAX_SAMPLE_RATE) { + sr_err("Maximum sample rate is %d", MAX_SAMPLE_RATE); + ret = SR_ERR_SAMPLERATE; + } + devc->samplerate = samplerate; + sr_dbg("Setting samplerate to %" PRIu64, devc->samplerate); + break; + default: + ret = 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) +{ + GVariant *gvar; + GVariantBuilder gvb; + int ret; + + (void)sdi; + (void)data; + (void)cg; + + ret = SR_OK; + switch (key) { + case SR_CONF_DEVICE_OPTIONS: + *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + devopts, + ARRAY_SIZE(devopts), + 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}", "samplerate-steps", gvar); + *data = g_variant_builder_end(&gvb); + break; + default: + return SR_ERR_NA; + } + + return ret; +} + +static int channel_to_mq(struct sr_channel *ch) +{ + struct channel_priv *chp; + + chp = ch->priv; + + switch (chp->ch_type) { + case ENRG_PWR: + return SR_MQ_POWER; + case ENRG_CURR: + return SR_MQ_CURRENT; + case ENRG_VOL: + return SR_MQ_VOLTAGE; + case TEMP_IN: + case TEMP_OUT: + return SR_MQ_TEMPERATURE; + default: + return -1; + } +} + +static int channel_to_unit(struct sr_channel *ch) +{ + struct channel_priv *chp; + + chp = ch->priv; + + switch (chp->ch_type) { + case ENRG_PWR: + return SR_UNIT_WATT; + case ENRG_CURR: + return SR_UNIT_AMPERE; + case ENRG_VOL: + return SR_UNIT_VOLT; + case TEMP_IN: + case TEMP_OUT: + return SR_UNIT_CELSIUS; + default: + return -1; + } +} + +/* We need to scale measurements down from the units used by the drivers. */ +static float adjust_data(int val, int type) +{ + switch (type) { + case ENRG_PWR: + return ((float)val) / 1000000.0; + case ENRG_CURR: + case ENRG_VOL: + case TEMP_IN: + case TEMP_OUT: + return ((float)val) / 1000.0; + default: + return 0.0; + } +} + +static float read_sample(struct sr_channel *ch) +{ + struct channel_priv *chp; + char path[256], *file, buf[32]; + ssize_t len; + int fd; + + chp = ch->priv; + + switch (chp->ch_type) { + case ENRG_PWR: file = "power1_input"; break; + case ENRG_CURR: file = "curr1_input"; break; + case ENRG_VOL: file = "in1_input"; break; + case TEMP_IN: file = "temp1_input"; break; + case TEMP_OUT: file = "temp2_input"; break; + default: + sr_err("Programmer goofed!"); + return -1.0; + } + + snprintf(path, sizeof(path), + "/sys/class/hwmon/hwmon%d/%s", + chp->probe->hwmon_num, file); + fd = open(path, O_RDONLY); + if (fd < 0) { + sr_err("Error opening %s: %s", path, strerror(errno)); + ch->enabled = FALSE; + return -1.0; + } + + len = read(fd, buf, sizeof(buf)); + close(fd); + if (len < 0) { + sr_err("error reading from %s: %s", path, strerror(errno)); + ch->enabled = FALSE; + return -1.0; + } + + return adjust_data(strtol(buf, NULL, 10), chp->ch_type); +} + +static int read_data(int fd, int revents, void *cb_data) +{ + uint32_t cur_time, elapsed_time, diff_time; + int64_t time_to_sleep; + struct sr_datafeed_packet packet; + struct sr_datafeed_analog analog; + struct sr_dev_inst *sdi; + struct dev_context *devc; + GSList *chl, chonly; + float valf; + + (void)fd; + (void)revents; + + sdi = cb_data; + if (!sdi) + return TRUE; + + devc = sdi->priv; + if (!devc) + return TRUE; + + packet.type = SR_DF_ANALOG; + packet.payload = &analog; + + memset(&analog, 0, sizeof(analog)); + analog.data = &valf; + + /* + * Reading from sysfs takes some time - try to + * keep up with samplerate. + */ + if (devc->samples_read) { + cur_time = g_get_monotonic_time(); + diff_time = cur_time - devc->last_sample_fin; + time_to_sleep = 1000000lu / devc->samplerate - diff_time; + if (time_to_sleep > 0) + g_usleep(time_to_sleep); + } + + /* + * Due to different units used in each channel we're sending + * samples one-by-one. + */ + for (chl = sdi->channels; chl; chl = chl->next) { + chonly.next = NULL; + chonly.data = chl->data; + analog.channels = &chonly; + analog.num_samples = 1; + analog.mq = channel_to_mq(chl->data); + analog.unit = channel_to_unit(chl->data); + + valf = read_sample(chl->data); + + sr_session_send(cb_data, &packet); + + } + + devc->samples_read++; + if (devc->limit_samples > 0 && + devc->samples_read >= devc->limit_samples) { + sr_info("Requested number of samples reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + goto out; + } else if (devc->limit_msec > 0) { + cur_time = g_get_monotonic_time(); + elapsed_time = cur_time - devc->start_time; + + if (elapsed_time >= devc->limit_msec) { + sr_info("Sampling time limit reached."); + sdi->driver->dev_acquisition_stop(sdi, cb_data); + goto out; + } + } + +out: + devc->last_sample_fin = g_get_monotonic_time(); + return TRUE; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi, + void *cb_data) +{ + struct dev_context *devc; + + (void)cb_data; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + devc = sdi->priv; + devc->samples_read = 0; + + if (pipe(devc->pipe_fds)) { + sr_err("Error setting up pipe", strerror(errno)); + return SR_ERR; + } + + devc->channel = g_io_channel_unix_new(devc->pipe_fds[0]); + g_io_channel_set_flags(devc->channel, G_IO_FLAG_NONBLOCK, NULL); + g_io_channel_set_encoding(devc->channel, NULL, NULL); + g_io_channel_set_buffered(devc->channel, FALSE); + + sr_session_source_add_channel(sdi->session, devc->channel, + G_IO_IN | G_IO_ERR, 1, + read_data, (void *)sdi); + + /* Send header packet to the session bus. */ + std_session_send_df_header(sdi, LOG_PREFIX); + devc->start_time = g_get_monotonic_time(); + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi, void *cb_data) +{ + struct sr_datafeed_packet packet; + struct dev_context *devc; + + (void)cb_data; + + devc = sdi->priv; + + if (sdi->status != SR_ST_ACTIVE) + return SR_ERR_DEV_CLOSED; + + sr_session_source_remove_channel(sdi->session, devc->channel); + g_io_channel_shutdown(devc->channel, FALSE, NULL); + g_io_channel_unref(devc->channel); + devc->channel = NULL; + + /* Send last packet. */ + packet.type = SR_DF_END; + sr_session_send(sdi, &packet); + + return SR_OK; +} + +SR_PRIV struct sr_dev_driver acme_driver_info = { + .name = "acme", + .longname = "ACME (Another Cute Measurement Equipment)", + .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_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .priv = NULL, +}; -- 2.1.4 ------------------------------------------------------------------------------ Dive into the World of Parallel Programming. The Go Parallel Website, sponsored by Intel and developed in partnership with Slashdot Media, is your hub for all things parallel software development, from weekly thought leadership blogs to news, videos, case studies, tutorials and more. Take a look and join the conversation now. http://goparallel.sourceforge.net/ _______________________________________________ sigrok-devel mailing list sigrok-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/sigrok-devel