diff --git a/Makefile.am b/Makefile.am index 62aca8ac9..9168145f2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -234,6 +234,12 @@ src_libdrivers_tail_la_SOURCES = src/driver_list_stop.c src_libdrivers_la_SOURCES = src/drivers.c +if HW_ADALM2K_DRIVER +src_libdrivers_la_SOURCES += \ + src/hardware/adalm2k-driver/protocol.h \ + src/hardware/adalm2k-driver/protocol.c \ + src/hardware/adalm2k-driver/api.c +endif if HW_AGILENT_DMM src_libdrivers_la_SOURCES += \ src/hardware/agilent-dmm/protocol.h \ diff --git a/configure.ac b/configure.ac index 5c30a8165..82a734d67 100644 --- a/configure.ac +++ b/configure.ac @@ -304,6 +304,7 @@ m4_define([_SR_DRIVER], [ m4_define([SR_DRIVER], [_SR_DRIVER([$1], [$2], m4_expand([AS_TR_CPP([HW_$2])]), [$3])]) +SR_DRIVER([adalm2k-driver], [adalm2k-driver]) SR_DRIVER([Agilent DMM], [agilent-dmm], [serial_comm]) SR_DRIVER([Appa 55II], [appa-55ii], [serial_comm]) SR_DRIVER([Arachnid Labs Re:load Pro], [arachnid-labs-re-load-pro], [serial_comm]) diff --git a/src/hardware/adalm2k-driver/api.c b/src/hardware/adalm2k-driver/api.c new file mode 100644 index 000000000..b096d7f5a --- /dev/null +++ b/src/hardware/adalm2k-driver/api.c @@ -0,0 +1,438 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2024 Sean W Jasin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "libsigrok/libsigrok.h" +#include "protocol.h" +#include +#include +#include + +#define M2K_VID 0x0456 +#define M2K_PID 0xb672 + +static const uint32_t scanopts[] = { + SR_CONF_CONN, +}; + +static const uint32_t drvopts[] = { + SR_CONF_LOGIC_ANALYZER, + SR_CONF_OSCILLOSCOPE, +}; + +static const uint32_t devopts[] = { + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_MSEC | SR_CONF_GET | SR_CONF_SET, + SR_CONF_AVERAGING | SR_CONF_GET | SR_CONF_SET, + SR_CONF_AVG_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_TRIGGER_MATCH | SR_CONF_LIST, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg_analog_group[] = { + SR_CONF_TRIGGER_SOURCE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, +}; + +static const uint32_t devopts_cg_analog_channel[] = { + SR_CONF_TRIGGER_SLOPE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_HIGH_RESOLUTION | SR_CONF_GET | SR_CONF_SET, + SR_CONF_TRIGGER_LEVEL | SR_CONF_GET | SR_CONF_SET, +}; + +static const uint32_t devopts_cg[] = {}; + +static const int32_t trigger_matches[] = { + SR_TRIGGER_ZERO, SR_TRIGGER_ONE, SR_TRIGGER_RISING, + SR_TRIGGER_FALLING, SR_TRIGGER_EDGE, +}; + +static const uint64_t samplerates[] = { + SR_KHZ(1), SR_KHZ(10), SR_KHZ(100), SR_MHZ(1), SR_MHZ(10), SR_MHZ(100), +}; + +static const char *trigger_sources[] = { + "CHANNEL 1", + "CHANNEL 2", + "CHANNEL 1 OR CHANNEL 2", + "CHANNEL 1 AND CHANNEL 2", + "CHANNEL 1 XOR CHANNEL 2", + "NONE", +}; + +static const char *trigger_slopes[] = { + "RISING", + "FALLING", + "LOW", + "HIGH", +}; + +static struct sr_dev_driver adalm2k_driver_driver_info; + +static void scan_device(struct libusb_device *dev, GSList **device) { +} + +static GSList *scan(struct sr_dev_driver *di, GSList *options) { + + struct drv_context *drvc; + struct dev_context *devc; + struct sr_dev_inst *sdi; + struct sr_channel_group *cg; + struct sr_channel *ch; + struct sr_usb_dev_inst *usb; + struct sr_config *src; + + libusb_device **devlist; + char channel_name[16]; + + char *conn; + GSList *l, *conn_devices; + GSList *devices; + + (void)options; + + devices = NULL; + drvc = di->context; + drvc->instances = NULL; + int i; + + // TODO: Remove libiio deps + 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; + } + } + + // Get devices + libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); + for (i = 0; devlist[i]; i++) { + conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn); + for (l = conn_devices; l; l = l->next) { + usb = l->data; + if(usb->bus == libusb_get_bus_number(devlist[i]) + && usb->address == libusb_get_device_address(devlist[i])) { + scan_device(devlist[i], &devices); + } + } + } + libusb_free_device_list(devlist, 1); + + /* TODO: Clean this, very basic concept */ + struct iio_scan *scan = iio_scan(NULL, "usb=0456:b672"); + if (iio_scan_get_results_count(scan) > 0) { + sr_dbg("Found M2K."); + char *uri = g_strdup((char *)iio_scan_get_uri(scan, 0)); + sdi = g_malloc(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INITIALIZING; + sdi->vendor = g_strdup("Analog Devices"); + sdi->model = g_strdup("M2K"); + sdi->connection_id = (void *)uri; + sdi->conn = NULL; + sdi->inst_type = SR_INST_USB; + sdi->driver = NULL; + sdi->session = NULL; + sdi->version = g_strdup("0.0.1"); + sdi->channels = NULL; + sdi->serial_num = NULL; + + cg = g_malloc0(sizeof(struct sr_channel_group)); + cg->name = g_strdup("Logic"); + + for (int j = 0; j < DEFAULT_NUM_LOGIC_CHANNELS; j++) { + snprintf(channel_name, 16, "DIO%d", j); + ch = sr_channel_new(sdi, j, SR_CHANNEL_LOGIC, TRUE, channel_name); + cg->channels = g_slist_append(cg->channels, ch); + } + sdi->channel_groups = g_slist_append(NULL, cg); + + devc = g_malloc(sizeof(struct dev_context)); + devc->m2k = NULL; + devc->mask = iio_create_channels_mask(18); + devc->logic_unitsize = 2; + devc->buffersize = 1 << 16; + devc->meaning.mq = SR_MQ_VOLTAGE; + devc->meaning.unit = SR_UNIT_VOLT; + devc->meaning.mqflags = 0; + + sdi->priv = devc; + sdi->inst_type = SR_INST_USB; + devices = g_slist_append(devices, sdi); + } + iio_scan_destroy(scan); + + return std_scan_complete(di, devices); +} + +static int dev_open(struct sr_dev_inst *sdi) { + int err; + (void)sdi; + struct dev_context *devc; + devc = sdi->priv; + devc->m2k = iio_create_context(NULL, sdi->connection_id); + err = iio_err(devc->m2k); + if (err) { + sr_err("Failed to open device"); + return SR_ERR; + } + sr_dbg("OK"); + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) { + (void)sdi; + struct dev_context *devc; + devc = sdi->priv; + if (devc->m2k) { + iio_context_destroy(devc->m2k); + } + /* NOTE: No valid way of checking if the device has been destroyed properly + */ + /* if (devc->m2k) { */ + /* sr_err("Failed to close device"); */ + /* return SR_ERR; */ + /* } */ + sr_dbg("Successfully closed device"); + + return SR_OK; +} + +static int config_get(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, + const struct sr_channel_group *cg) { + unsigned int idx, capture_ratio, samplerate; + int delay; + gboolean analog_en, digital_en; + struct sr_channel *ch; + struct dev_context *devc; + + int ret; + + (void)sdi; + (void)data; + (void)cg; + + if (!sdi) { + return SR_ERR_ARG; + } + + ret = SR_OK; + + devc = sdi->priv; + sr_dbg("Getting configs"); + + switch (key) { + case SR_CONF_SAMPLERATE: + sr_dbg("SAMPLERATE"); + digital_en = + adalm2k_driver_nb_enabled_channels(sdi, SR_CHANNEL_LOGIC) > 0; + if (digital_en) { + } + /* samplerate = 99999999; */ + *data = g_variant_new_uint64(devc->samplerate); + /* TODO: samplerate = m2k_get_samplerate(sdi); */ + /* TODO: add support for analog */ + break; + case SR_CONF_LIMIT_SAMPLES: + sr_dbg("LIMIT SAMPLES"); + *data = g_variant_new_uint64(devc->limit_samples); + break; + case SR_CONF_LIMIT_MSEC: + sr_dbg("LIMIT MSEC"); + *data = g_variant_new_uint64(devc->limit_msec); + break; + case SR_CONF_AVERAGING: + sr_dbg("AVERAGING"); + *data = g_variant_new_boolean(devc->avg); + break; + case SR_CONF_AVG_SAMPLES: + sr_dbg("AVG SAMPLES"); + *data = g_variant_new_uint64(devc->avg_samples); + break; + case SR_CONF_CAPTURE_RATIO: + *data = g_variant_new_uint64(0); + sr_dbg("CAP RATIO"); + break; + /* TODO */ + 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 sr_channel *ch; + struct dev_context *devc; + + int ret; + + (void)sdi; + (void)data; + (void)cg; + + devc = sdi->priv; + + ret = SR_OK; + sr_dbg("Setting configs"); + switch (key) { + case SR_CONF_SAMPLERATE: + sr_dbg("SAMPLERATE"); + devc->samplerate = g_variant_get_uint64(data); + /* Set samplerate when starting acquisition */ + /* if(adalm2k_driver_set_samplerate(sdi) < 0) { */ + /* sr_err("Failed to set samplerate"); */ + /* ret = SR_ERR_NA; */ + /* break; */ + /* } */ + /* sr_dbg("Successfully changed samplerate"); */ + /* TODO: implement analog */ + break; + case SR_CONF_LIMIT_SAMPLES: + sr_dbg("LIMIT SAMPLES"); + devc->limit_samples = g_variant_get_uint64(data); + devc->limit_msec = 0; + break; + case SR_CONF_LIMIT_MSEC: + sr_dbg("LIMIT MSEC"); + devc->limit_msec = g_variant_get_uint64(data); + devc->limit_samples = 0; + break; + case SR_CONF_CAPTURE_RATIO: + sr_dbg("CAP RATIO"); + break; + case SR_CONF_AVERAGING: + sr_dbg("AVERAGING"); + devc->avg = g_variant_get_boolean(data); + break; + case SR_CONF_AVG_SAMPLES: + sr_dbg("AVG SAMPLES"); + devc->avg_samples = g_variant_get_uint64(data); + break; + /* TODO */ + default: + sr_dbg("ERR"); + 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) { + int ret; + + (void)sdi; + (void)data; + (void)cg; + + ret = SR_OK; + sr_dbg("Listing configs"); + switch (key) { + case SR_CONF_SCAN_OPTIONS: + break; + case SR_CONF_DEVICE_OPTIONS: + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + break; + case SR_CONF_SAMPLERATE: + *data = std_gvar_samplerates(ARRAY_AND_SIZE(samplerates)); + break; + case SR_CONF_TRIGGER_MATCH: + *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches)); + break; + default: + return SR_ERR_NA; + } + + return ret; +} + +static int dev_acquisition_start(const struct sr_dev_inst *sdi) { + /* TODO: configure hardware, reset acquisition state, set up + * callbacks and send header packet. */ + struct dev_context *devc; + gboolean analog_en, digital_en; + struct sr_channel *ch; + GSList *l; + + (void)sdi; + + devc = sdi->priv; + + devc->sent_samples = 0; + analog_en = adalm2k_driver_nb_enabled_channels(sdi, SR_CHANNEL_ANALOG) + ? TRUE + : FALSE; + digital_en = adalm2k_driver_nb_enabled_channels(sdi, SR_CHANNEL_LOGIC) + ? TRUE + : FALSE; + adalm2k_driver_set_samplerate(sdi); + + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + if (ch->type == SR_CHANNEL_LOGIC) { + sr_dbg("Enabling channels"); + adalm2k_driver_enable_channel(sdi, ch->index); + } + } + + std_session_send_df_header(sdi); + sr_session_source_add(sdi->session, -1, G_IO_IN, 0, + adalm2k_driver_receive_data, + (struct sr_dev_inst *)sdi); + + devc->start_time = g_get_monotonic_time(); + devc->spent_us = 0; + + return SR_OK; +} + +static int dev_acquisition_stop(struct sr_dev_inst *sdi) { + /* TODO: stop acquisition. */ + + (void)sdi; + + sr_session_source_remove(sdi->session, -1); + std_session_send_df_end(sdi); + + return SR_OK; +} + +static struct sr_dev_driver adalm2k_driver_driver_info = { + .name = "adalm2k-driver", + .longname = "adalm2k-driver", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_dev_list, + .dev_clear = std_dev_clear, + .config_get = config_get, + .config_set = config_set, + .config_list = config_list, + .dev_open = dev_open, + .dev_close = dev_close, + .dev_acquisition_start = dev_acquisition_start, + .dev_acquisition_stop = dev_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(adalm2k_driver_driver_info); diff --git a/src/hardware/adalm2k-driver/protocol.c b/src/hardware/adalm2k-driver/protocol.c new file mode 100644 index 000000000..2a43dffd8 --- /dev/null +++ b/src/hardware/adalm2k-driver/protocol.c @@ -0,0 +1,175 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2024 Sean W Jasin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "protocol.h" + +SR_PRIV int adalm2k_driver_set_samplerate(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct iio_context *m2k_ctx; + struct iio_device *m2k_dev; + struct iio_attr *attr_sr; + int err; + + devc = sdi->priv; + m2k_ctx = devc->m2k; + + m2k_dev = iio_context_find_device(m2k_ctx, M2K_RX); + if (!m2k_dev) { + return SR_ERR; + } + + attr_sr = (struct iio_attr *)iio_device_find_attr(m2k_dev, "sampling_frequency"); + err = iio_attr_write_longlong(attr_sr, devc->samplerate); + if (err < 0) { + return SR_ERR_SAMPLERATE; + } + + return SR_OK; +} + +SR_PRIV int adalm2k_driver_enable_channel(const struct sr_dev_inst *sdi, int index) { + struct dev_context *devc; + struct iio_context *m2k_ctx; + struct iio_device *m2k_dev; + struct iio_channel *m2k_chn; + + devc = sdi->priv; + m2k_ctx = devc->m2k; + m2k_dev = iio_context_find_device(m2k_ctx, M2K_RX); + if (!m2k_dev) { + return SR_ERR; + } + + m2k_chn = iio_device_get_channel(m2k_dev, index); + if (!m2k_chn) { + return SR_ERR; + } + + iio_channel_enable(m2k_chn, devc->mask); + + return SR_OK; +} + +SR_PRIV int adalm2k_driver_nb_enabled_channels(const struct sr_dev_inst *sdi, int type) +{ + struct sr_channel *ch; + int nb_channels; + GSList *l; + + nb_channels = 0; + + for (l = sdi->channels; l; l = l->next) { + ch = l->data; + if (ch->type == type) { + if (ch->enabled) { + nb_channels++; + } + } + } + return nb_channels; +} + +SR_PRIV int adalm2k_driver_receive_data(int fd, int revents, void *cb_data) +{ + const struct sr_dev_inst *sdi; + struct dev_context *devc; + struct sr_datafeed_packet packet; + struct sr_datafeed_logic logic; + uint64_t samples_todo, logic_done, analog_done, sending_now, analog_sent; + uint16_t *logic_data; + /* float **analog_data; */ + /* GSList *l; */ + + struct iio_context *m2k_ctx; + struct iio_device *m2k_dev; + struct iio_channel *m2k_chn; + struct iio_buffer *m2k_buf; + struct iio_block *m2k_blk; + struct iio_channels_mask *m2k_msk; + uint32_t smp_size; + + (void)fd; + + sdi = cb_data; + if (!sdi) + return TRUE; + + devc = sdi->priv; + if (!devc) + return TRUE; + + if (revents == G_IO_IN) { + return TRUE; + } + + m2k_ctx = devc->m2k; + m2k_dev = iio_context_find_device(m2k_ctx, M2K_RX); + if(!m2k_dev) { + sr_err("Failed to make device"); + return FALSE; + } + + m2k_msk = devc->mask; + m2k_chn = iio_device_get_channel(m2k_dev, 0); + if(!m2k_chn) { + sr_err("Failed to get channel"); + return FALSE; + } + iio_channel_enable(m2k_chn, m2k_msk); + + smp_size= iio_device_get_sample_size(m2k_dev, m2k_msk); + m2k_buf = iio_device_create_buffer(m2k_dev, 0, m2k_msk); + if(iio_err(m2k_buf) < 0) { + sr_err("Failed to make buffer"); + return FALSE; + } + m2k_blk = iio_buffer_create_block(m2k_buf, devc->limit_samples * smp_size); + if(iio_err(m2k_blk) < 0) { + sr_err("Failed to make block"); + return FALSE; + } + + iio_block_enqueue(m2k_blk, 0, false); + iio_buffer_enable(m2k_buf); + iio_block_dequeue(m2k_blk, false); + + logic_data = iio_block_first(m2k_blk, m2k_chn); + + packet.type = SR_DF_LOGIC; + packet.payload = &logic; + logic.unitsize = 2; + + sending_now = devc->limit_samples; + + logic.length = sending_now * logic.unitsize; + logic.data = logic_data; + + iio_buffer_destroy(m2k_buf); + + sr_session_send(sdi, &packet); + + sdi->driver->dev_acquisition_stop((struct sr_dev_inst *)sdi); + return TRUE; +} + +/* SR_PRIV uint8_t * adalm2k_driver_get_samples(struct sr_dev_inst *sdi, long samples) { */ +/* return NULL; */ +/* } */ diff --git a/src/hardware/adalm2k-driver/protocol.h b/src/hardware/adalm2k-driver/protocol.h new file mode 100644 index 000000000..92ed0f92f --- /dev/null +++ b/src/hardware/adalm2k-driver/protocol.h @@ -0,0 +1,74 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2024 Sean W Jasin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBSIGROK_HARDWARE_ADALM2K_DRIVER_PROTOCOL_H +#define LIBSIGROK_HARDWARE_ADALM2K_DRIVER_PROTOCOL_H + +#include +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "adalm2k-driver" + +#define DEFAULT_NUM_LOGIC_CHANNELS 16 +#define DEFAULT_NUM_ANALOG_CHANNELS 2 +#define MAX_NEG_DELAY -8192 +#define M2K_LA "m2k-logic-analyzer" +#define M2K_TX "m2k-logic-analyzer-tx" +#define M2K_RX "m2k-logic-analyzer-rx" + +// NOTE: Credit to @teoperisanu github https://github.com/teoperisanu/libsigrok/blob/master/src/hardware/adalm2000/protocol.h +struct dev_context { + struct iio_context *m2k; + struct iio_channels_mask *mask; + uint64_t samplerate; + uint64_t start_time; + int64_t spent_us; + uint64_t limit_msec; + uint64_t limit_frames; + uint64_t limit_samples; + uint64_t sent_samples; + uint64_t buffersize; + uint32_t logic_unitsize; + gboolean avg; + uint64_t avg_samples; + + // TODO: Learn what this stuff does + struct sr_datafeed_analog packet; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; +}; + +// TODO: And this stuff +SR_PRIV int adalm2k_driver_set_samplerate(const struct sr_dev_inst *sdi); + +SR_PRIV int adalm2k_driver_enable_channel(const struct sr_dev_inst *sdi, int index); + +SR_PRIV int adalm2k_driver_nb_enabled_channels(const struct sr_dev_inst *sdi, int type); + +SR_PRIV int adalm2000_convert_trigger(const struct sr_dev_inst *sdi); + +SR_PRIV int adalm2k_driver_receive_data(int fd, int revents, void *cb_data); + +/* SR_PRIV uint8_t * adalm2k_driver_get_samples(struct sr_dev_inst *sdi, long samples); */ + +#endif