From e4e7a9c4ed785c0104471d7d3a272b4e72f0ae1f Mon Sep 17 00:00:00 2001 From: "Raidurg Javeed Shariff (PSS PCS WCS ASPDP SE 2)" Date: Sat, 15 Mar 2025 01:50:41 +0530 Subject: [PATCH 1/2] FX2G3 replacement for FX2LP LA --- Makefile.am | 6 + configure.ac | 1 + src/hardware/cypress-fx2g3/api.c | 522 +++++++++++++++++ src/hardware/cypress-fx2g3/protocol.c | 772 ++++++++++++++++++++++++++ src/hardware/cypress-fx2g3/protocol.h | 144 +++++ 5 files changed, 1445 insertions(+) create mode 100644 src/hardware/cypress-fx2g3/api.c create mode 100644 src/hardware/cypress-fx2g3/protocol.c create mode 100644 src/hardware/cypress-fx2g3/protocol.h diff --git a/Makefile.am b/Makefile.am index 3b68142f2..56ee2921a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -382,6 +382,12 @@ src_libdrivers_la_SOURCES += \ src/hardware/fx2lafw/protocol.c \ src/hardware/fx2lafw/api.c endif +if HW_CYPRESS_FX2G3 +src_libdrivers_la_SOURCES += \ + src/hardware/cypress-fx2g3/protocol.h \ + src/hardware/cypress-fx2g3/protocol.c \ + src/hardware/cypress-fx2g3/api.c +endif if HW_GMC_MH_1X_2X src_libdrivers_la_SOURCES += \ src/hardware/gmc-mh-1x-2x/protocol.h \ diff --git a/configure.ac b/configure.ac index 194559ed9..383d08c74 100644 --- a/configure.ac +++ b/configure.ac @@ -332,6 +332,7 @@ SR_DRIVER([Fluke 45], [fluke-45]) SR_DRIVER([Fluke DMM], [fluke-dmm], [serial_comm]) SR_DRIVER([FTDI LA], [ftdi-la], [libusb libftdi]) SR_DRIVER([fx2lafw], [fx2lafw], [libusb]) +SR_DRIVER([cypress-fx2g3], [cypress-fx2g3]) SR_DRIVER([GMC MH 1x/2x], [gmc-mh-1x-2x], [serial_comm]) SR_DRIVER([Great Scott Gadgets GreatFET One], [greatfet], [libusb]) SR_DRIVER([GW Instek GDS-800], [gwinstek-gds-800], [serial_comm]) diff --git a/src/hardware/cypress-fx2g3/api.c b/src/hardware/cypress-fx2g3/api.c new file mode 100644 index 000000000..c08d5e972 --- /dev/null +++ b/src/hardware/cypress-fx2g3/api.c @@ -0,0 +1,522 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2025 Javeed Shariff R + * + * 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" +#include + +static const struct cypress_fx2g3_profile supported_fx2g3_dev[] = { + /* + * Cypress FX2G3 + */ + { 0x04b4, 0xF007, "Cypress", "FX2G3", NULL, + "cypress-fx2g3-la.fw", + DEV_CAPS_8BIT, NULL, NULL}, + { 0x04b4, 0xF00F, "Cypress", "FX2G3", NULL, + "cypress-fx2g3-la.fw", + DEV_CAPS_16BIT, NULL, NULL}, + + ALL_ZERO +}; + +static const uint32_t scanopts[] = { + SR_CONF_CONN, +}; + +static const uint32_t drvopts[] = { + SR_CONF_LOGIC_ANALYZER, +}; + +static const uint32_t devopts[] = { + SR_CONF_CONTINUOUS, + SR_CONF_LIMIT_FRAMES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_LIMIT_SAMPLES | SR_CONF_GET | SR_CONF_SET, + SR_CONF_CONN | SR_CONF_GET, + SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST, + SR_CONF_TRIGGER_MATCH | SR_CONF_LIST, + SR_CONF_CAPTURE_RATIO | SR_CONF_GET | SR_CONF_SET, +}; + +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(500), + SR_MHZ(1), + SR_MHZ(5), + SR_MHZ(10), + SR_MHZ(15), + SR_MHZ(20), + SR_MHZ(25), + SR_MHZ(30), + SR_MHZ(35), + SR_MHZ(40), + SR_MHZ(48), +}; + +static gboolean is_plausible(const struct libusb_device_descriptor *des) +{ + int i; + + for (i = 0; supported_fx2g3_dev[i].vid; i++) { + if (des->idVendor != supported_fx2g3_dev[i].vid) + continue; + if (des->idProduct == supported_fx2g3_dev[i].pid) + return TRUE; + } + + return FALSE; +} + +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_usb_dev_inst *usb; + struct sr_channel *ch; + struct sr_channel_group *cg; + struct sr_config *src; + const struct cypress_fx2g3_profile *prof; + GSList *l, *devices, *conn_devices; + gboolean has_firmware; + struct libusb_device_descriptor des; + libusb_device **devlist; + struct libusb_device_handle *hdl; + int ret, i, j; + int num_logic_channels = 0, num_analog_channels = 0; + const char *conn; + char manufacturer[64], product[64], serial_num[64], connection_id[64]; + char channel_name[16]; + + drvc = di->context; + + conn = NULL; + for (l = options; l; l = l->next) { + src = l->data; + switch (src->key) { + case SR_CONF_CONN: + conn = g_variant_get_string(src->data, NULL); + break; + } + } + if (conn) + conn_devices = sr_usb_find(drvc->sr_ctx->libusb_ctx, conn); + else + conn_devices = NULL; + + /* Find all cypress_fx2g3 compatible devices and upload firmware to them. */ + devices = NULL; + libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); + for (i = 0; devlist[i]; i++) { + if (conn) { + usb = NULL; + 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])) + break; + } + if (!l) + /* This device matched none of the ones that + * matched the conn specification. */ + continue; + } + + libusb_get_device_descriptor( devlist[i], &des); + + if (!is_plausible(&des)) + continue; + + if ((ret = libusb_open(devlist[i], &hdl)) < 0) { + sr_warn("Failed to open potential device with " + "VID:PID %04x:%04x: %s.", des.idVendor, + des.idProduct, libusb_error_name(ret)); + continue; + } + + if (des.iManufacturer == 0) { + manufacturer[0] = '\0'; + } else if ((ret = libusb_get_string_descriptor_ascii(hdl, + des.iManufacturer, (unsigned char *) manufacturer, + sizeof(manufacturer))) < 0) { + sr_warn("Failed to get manufacturer string descriptor: %s.", + libusb_error_name(ret)); + continue; + } + + if (des.iProduct == 0) { + product[0] = '\0'; + } else if ((ret = libusb_get_string_descriptor_ascii(hdl, + des.iProduct, (unsigned char *) product, + sizeof(product))) < 0) { + sr_warn("Failed to get product string descriptor: %s.", + libusb_error_name(ret)); + continue; + } + + if (des.iSerialNumber == 0) { + serial_num[0] = '\0'; + } else if ((ret = libusb_get_string_descriptor_ascii(hdl, + des.iSerialNumber, (unsigned char *) serial_num, + sizeof(serial_num))) < 0) { + sr_warn("Failed to get serial number string descriptor: %s.", + libusb_error_name(ret)); + continue; + } + + libusb_close(hdl); + + if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0) + continue; + + prof = NULL; + for (j = 0; supported_fx2g3_dev[j].vid; j++) { + if (des.idVendor == supported_fx2g3_dev[j].vid && + des.idProduct == supported_fx2g3_dev[j].pid && + (!supported_fx2g3_dev[j].usb_manufacturer || + !strcmp(manufacturer, supported_fx2g3_dev[j].usb_manufacturer)) && + (!supported_fx2g3_dev[j].usb_product || + !strcmp(product, supported_fx2g3_dev[j].usb_product))) { + prof = &supported_fx2g3_dev[j]; + break; + } + } + + if (!prof) + continue; + + sdi = g_malloc0(sizeof(struct sr_dev_inst)); + sdi->status = SR_ST_INITIALIZING; + sdi->vendor = g_strdup(prof->vendor); + sdi->model = g_strdup(prof->model); + sdi->version = g_strdup(prof->model_version); + sdi->serial_num = g_strdup(serial_num); + sdi->connection_id = g_strdup(connection_id); + + /* Fill in channellist according to this device's profile. */ + num_logic_channels = prof->dev_caps; + // num_analog_channels = prof->dev_caps & DEV_CAPS_AX_ANALOG ? 1 : 0; + + /* Logic channels, all in one channel group. */ + // cg = g_malloc0(sizeof(struct sr_channel_group)); + // cg->name = g_strdup("Logic"); + cg = sr_channel_group_new(sdi, "Logic", NULL); + for (j = 0; j < num_logic_channels; j++) { + sprintf(channel_name, "D%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); + + // for (j = 0; j < num_analog_channels; j++) { + // snprintf(channel_name, 16, "A%d", j); + // ch = sr_channel_new(sdi, j + num_logic_channels, + // SR_CHANNEL_ANALOG, TRUE, channel_name); + + // /* Every analog channel gets its own channel group. */ + // cg = g_malloc0(sizeof(struct sr_channel_group)); + // cg->name = g_strdup(channel_name); + // cg->channels = g_slist_append(NULL, ch); + // sdi->channel_groups = g_slist_append(sdi->channel_groups, cg); + // } + + devc = cypress_fx2g3_dev_new(); + devc->profile = prof; + sdi->priv = devc; + devices = g_slist_append(devices, sdi); + + devc->samplerates = samplerates; + devc->num_samplerates = ARRAY_SIZE(samplerates); + has_firmware = usb_match_manuf_prod(devlist[i], + "sigrok", "cypress-fx2g3"); + if (has_firmware) { + /* Already has the firmware, so fix the new address. */ + sr_dbg("Found an Cypress fx2g3 device."); + sdi->status = SR_ST_INACTIVE; + sdi->inst_type = SR_INST_USB; + sdi->conn = sr_usb_dev_inst_new(libusb_get_bus_number(devlist[i]), + libusb_get_device_address(devlist[i]), NULL); + } else { + sr_err("fx2g3 Firmware not found (sigrok) (cypress-fx2g3).. "); + // if (ezusb_upload_firmware_fx2g3(drvc->sr_ctx, devlist[i], + // USB_CONFIGURATION, prof->firmware) == SR_OK) { + // /* Store when this device's FW was updated. */ + // devc->fw_updated = g_get_monotonic_time(); + // /*Add delay for the device to re-enumerat in SuperSpeed*/ + // g_usleep(1000 * 1000); + // devices = g_slist_remove(devices, sdi); + // /* Rescan and refresh the device list. + // This is needed as the device renumerates as a SuperSpeed device */ + // libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); + // /* Restart the iteration */ + // i = 0; + // continue; + // } else { + // sr_err("Firmware upload failed for " + // "device %d.%d (logical), name %s.", + // libusb_get_bus_number(devlist[i]), + // libusb_get_device_address(devlist[i]), + // prof->firmware); + // } + sdi->inst_type = SR_INST_USB; + sdi->conn = sr_usb_dev_inst_new(libusb_get_bus_number(devlist[i]), + 0xff, NULL); + } + } + libusb_free_device_list(devlist, 1); + g_slist_free_full(conn_devices, (GDestroyNotify)sr_usb_dev_inst_free); + + return std_scan_complete(di, devices); +} + +static void clear_helper(struct dev_context *devc) +{ + g_slist_free(devc->enabled_analog_channels); +} + +static int dev_clear(const struct sr_dev_driver *di) +{ + return std_dev_clear_with_callback(di, (std_dev_clear_callback)clear_helper); +} + +static int dev_open(struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di = sdi->driver; + struct sr_usb_dev_inst *usb; + struct dev_context *devc; + int ret; + int64_t timediff_us, timediff_ms; + + devc = sdi->priv; + usb = sdi->conn; + + /* + * If the firmware was recently uploaded, wait up to MAX_RENUM_DELAY_MS + * milliseconds for the fx2g3 to renumerate. + */ + ret = SR_ERR; + + if (devc->fw_updated > 0) { + sr_info("Waiting for device to reset."); + /* Takes >= 300ms for the fx2g3 to be gone from the USB bus. */ + g_usleep(300 * 1000); + timediff_ms = 0; + while (timediff_ms < MAX_RENUM_DELAY_MS) { + if ((ret = cypress_fx2g3_dev_open(sdi, di)) == SR_OK) + break; + g_usleep(100 * 1000); + + timediff_us = g_get_monotonic_time() - devc->fw_updated; + timediff_ms = timediff_us / 1000; + sr_spew("Waited %" PRIi64 "ms.", timediff_ms); + } + if (ret != SR_OK) { + sr_err("Device failed to renumerate."); + return SR_ERR; + } + sr_info("Device came back after %" PRIi64 "ms.", timediff_ms); + } else { + sr_info("Firmware upload was not needed."); + ret = cypress_fx2g3_dev_open(sdi, di); + } + + if (ret != SR_OK) { + sr_err("Unable to open device."); + return SR_ERR; + } + + ret = libusb_claim_interface(usb->devhdl, USB_INTERFACE); + if (ret != 0) { + switch (ret) { + case LIBUSB_ERROR_BUSY: + sr_err("Unable to claim USB interface. Another " + "program or driver has already claimed it."); + break; + case LIBUSB_ERROR_NO_DEVICE: + sr_err("Device has been disconnected."); + break; + default: + sr_err("Unable to claim interface: %s.", + libusb_error_name(ret)); + break; + } + + return SR_ERR; + } + + if (devc->cur_samplerate == 0) { + /* Samplerate hasn't been set; default to the slowest one. */ + devc->cur_samplerate = devc->samplerates[0]; + } + + return SR_OK; +} + +static int dev_close(struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb; + + usb = sdi->conn; + + if (!usb->devhdl) + return SR_ERR_BUG; + + sr_info("Closing device on %d.%d (logical) / %s (physical) interface %d.", + usb->bus, usb->address, sdi->connection_id, USB_INTERFACE); + libusb_release_interface(usb->devhdl, USB_INTERFACE); + libusb_close(usb->devhdl); + usb->devhdl = 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; + struct sr_usb_dev_inst *usb; + + (void)cg; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + + switch (key) { + case SR_CONF_CONN: + if (!sdi->conn) + return SR_ERR_ARG; + usb = sdi->conn; + if (usb->address == 255) + /* Device still needs to re-enumerate after firmware + * upload, so we don't know its (future) address. */ + return SR_ERR; + *data = g_variant_new_printf("%d.%d", usb->bus, usb->address); + break; + case SR_CONF_LIMIT_FRAMES: + *data = g_variant_new_uint64(devc->limit_frames); + break; + case SR_CONF_LIMIT_SAMPLES: + *data = g_variant_new_uint64(devc->limit_samples); + break; + case SR_CONF_SAMPLERATE: + *data = g_variant_new_uint64(devc->cur_samplerate); + break; + case SR_CONF_CAPTURE_RATIO: + *data = g_variant_new_uint64(devc->capture_ratio); + break; + default: + return SR_ERR_NA; + } + + 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; + int idx; + + (void)cg; + + if (!sdi) + return SR_ERR_ARG; + + devc = sdi->priv; + + switch (key) { + case SR_CONF_SAMPLERATE: + if ((idx = std_u64_idx(data, devc->samplerates, devc->num_samplerates)) < 0) + return SR_ERR_ARG; + devc->cur_samplerate = devc->samplerates[idx]; + break; + case SR_CONF_LIMIT_FRAMES: + devc->limit_frames = g_variant_get_uint64(data); + break; + case SR_CONF_LIMIT_SAMPLES: + devc->limit_samples = g_variant_get_uint64(data); + break; + case SR_CONF_CAPTURE_RATIO: + devc->capture_ratio = g_variant_get_uint64(data); + break; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + +static int config_list(uint32_t key, GVariant **data, + const struct sr_dev_inst *sdi, const struct sr_channel_group *cg) +{ + struct dev_context *devc; + + devc = (sdi) ? sdi->priv : NULL; + + switch (key) { + case SR_CONF_SCAN_OPTIONS: + case SR_CONF_DEVICE_OPTIONS: + if (cg) + return SR_ERR_NA; + return STD_CONFIG_LIST(key, data, sdi, cg, scanopts, drvopts, devopts); + case SR_CONF_SAMPLERATE: + if (!devc) + return SR_ERR_NA; + *data = std_gvar_samplerates(devc->samplerates, devc->num_samplerates); + break; + case SR_CONF_TRIGGER_MATCH: + *data = std_gvar_array_i32(ARRAY_AND_SIZE(trigger_matches)); + break; + default: + return SR_ERR_NA; + } + + return SR_OK; +} + + +static struct sr_dev_driver cypress_fx2g3_driver_info = { + .name = "Cypress FX2G3", + .longname = "Cypress FX2G3 (generic driver for FX2G3 based LAs)", + .api_version = 1, + .init = std_init, + .cleanup = std_cleanup, + .scan = scan, + .dev_list = std_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 = cypress_fx2g3_start_acquisition, + .dev_acquisition_stop = cypress_fx2g3_acquisition_stop, + .context = NULL, +}; +SR_REGISTER_DEV_DRIVER(cypress_fx2g3_driver_info); diff --git a/src/hardware/cypress-fx2g3/protocol.c b/src/hardware/cypress-fx2g3/protocol.c new file mode 100644 index 000000000..37db20966 --- /dev/null +++ b/src/hardware/cypress-fx2g3/protocol.c @@ -0,0 +1,772 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2025 Javeed Shariff R + * + * 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 +#include +#include "protocol.h" + +#pragma pack(push, 1) + +struct version_info { + uint8_t major; + uint8_t minor; +}; + +struct cmd_start_acquisition { + uint32_t channel_mask; + uint32_t sampling_factor; +}; +struct cmd_stop_acquisition { + uint8_t stop_info; +}; + +#pragma pack(pop) + +#define USB_TIMEOUT 100 + +static int command_get_fw_version(libusb_device_handle *devhdl, + struct version_info *vi) +{ + int ret; + + ret = libusb_control_transfer(devhdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_ENDPOINT_IN, CMD_GET_FW_VERSION, 0x0000, 0x0000, + (unsigned char *)vi, sizeof(struct version_info), USB_TIMEOUT); + + if (ret < 0) { + sr_err("Unable to get version info: %s.", + libusb_error_name(ret)); + return SR_ERR; + } + + return SR_OK; +} + +static int command_get_revid_version(struct sr_dev_inst *sdi, uint8_t *revid) +{ + struct sr_usb_dev_inst *usb = sdi->conn; + libusb_device_handle *devhdl = usb->devhdl; + int ret; + + ret = libusb_control_transfer(devhdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_ENDPOINT_IN, CMD_GET_REVID_VERSION, 0x0000, 0x0000, + revid, 1, USB_TIMEOUT); + + if (ret < 0) { + sr_err("Unable to get REVID: %s.", libusb_error_name(ret)); + return SR_ERR; + } + + return SR_OK; +} + +static int command_start_acquisition(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_usb_dev_inst *usb; + uint64_t samplerate; + struct cmd_start_acquisition cmd; + int ret; + + devc = sdi->priv; + usb = sdi->conn; + samplerate = devc->cur_samplerate; + + /* Compute the sample rate. */ + if (devc->sample_wide && samplerate > MAX_16BIT_SAMPLE_RATE) { + sr_err("Unable to sample at %" PRIu64 "Hz " + "when collecting 16-bit samples.", samplerate); + return SR_ERR; + } + + cmd.sampling_factor = (FX2G3_PIB_CLOCK)/(samplerate); + cmd.channel_mask = devc->channel_mask; + sr_err("cmd.sample rate = %d", samplerate); + sr_err("cmd.sampling_factor = %d",cmd.sampling_factor); + sr_err("cmd.channel_mask = %d",cmd.channel_mask); + + /* Send the control message. */ + ret = libusb_control_transfer(usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_ENDPOINT_OUT, CMD_START, 0x0000, 0x0000, + (unsigned char *)&cmd, sizeof(cmd), USB_TIMEOUT); + if (ret < 0) { + sr_err("Unable to send start command: %s.", + libusb_error_name(ret)); + return SR_ERR; + }else{ + sr_info("CMD_START vendor command sent successfully"); + } + + return SR_OK; +} + +static int command_stop_acquisition(const struct sr_dev_inst *sdi) +{ + struct sr_usb_dev_inst *usb; + uint64_t samplerate; + struct cmd_stop_acquisition cmd; + int ret; + usb = sdi->conn; + cmd.stop_info = 0; + /* Send the control message. */ + ret = libusb_control_transfer(usb->devhdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_ENDPOINT_OUT, CMD_STOP, 0x0000, 0x0000, + (unsigned char *)&cmd, sizeof(cmd), USB_TIMEOUT); + if (ret < 0) { + sr_err("Unable to send start command: %s.", + libusb_error_name(ret)); + return SR_ERR; + }else{ + sr_info("CMD_STOP vendor command sent successfully"); + } + + return SR_OK; +} + +SR_PRIV int cypress_fx2g3_dev_open(struct sr_dev_inst *sdi, struct sr_dev_driver *di) +{ + libusb_device **devlist; + struct sr_usb_dev_inst *usb; + struct libusb_device_descriptor des; + struct dev_context *devc; + struct drv_context *drvc; + struct version_info vi; + int ret = SR_ERR, i, device_count; + uint8_t revid; + char connection_id[64]; + + drvc = di->context; + devc = sdi->priv; + usb = sdi->conn; + + device_count = libusb_get_device_list(drvc->sr_ctx->libusb_ctx, &devlist); + if (device_count < 0) { + sr_err("Failed to get device list: %s.", + libusb_error_name(device_count)); + return SR_ERR; + } + + for (i = 0; i < device_count; i++) { + libusb_get_device_descriptor(devlist[i], &des); + + if (des.idVendor != devc->profile->vid + || des.idProduct != devc->profile->pid) + continue; + + if ((sdi->status == SR_ST_INITIALIZING) || + (sdi->status == SR_ST_INACTIVE)) { + + /* + * Check device by its physical USB bus/port address. + */ + if (usb_get_port_path(devlist[i], connection_id, sizeof(connection_id)) < 0) + { + continue; + } + + if (strcmp(sdi->connection_id, connection_id)) + { + /* This is not the one. */ + continue; + } + } + + if (!(ret = libusb_open(devlist[i], &usb->devhdl))) { + if (usb->address == 0xff) + /* + * First time we touch this device after FW + * upload, so we don't know the address yet. + */ + usb->address = libusb_get_device_address(devlist[i]); + } else { + sr_err("Failed to open device: %s.", + libusb_error_name(ret)); + ret = SR_ERR; + break; + } + + if (libusb_has_capability(LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER)) { + if (libusb_kernel_driver_active(usb->devhdl, USB_INTERFACE) == 1) { + if ((ret = libusb_detach_kernel_driver(usb->devhdl, USB_INTERFACE)) < 0) { + sr_err("Failed to detach kernel driver: %s.", + libusb_error_name(ret)); + ret = SR_ERR; + break; + } + } + } + + ret = command_get_fw_version(usb->devhdl, &vi); + if (ret != SR_OK) { + sr_err("Failed to get firmware version."); + break; + } + + ret = command_get_revid_version(sdi, &revid); + if (ret != SR_OK) { + sr_err("Failed to get REVID."); + break; + } + + /* + * Changes in major version mean incompatible/API changes, so + * bail out if we encounter an incompatible version. + * Different minor versions are OK, they should be compatible. + */ + if (vi.major != FX2G3_REQUIRED_VERSION_MAJOR) { + sr_err("Expected firmware version %d.x, " + "got %d.%d.", FX2G3_REQUIRED_VERSION_MAJOR, + vi.major, vi.minor); + break; + } + + sr_info("Opened device on %d.%d (logical) / %s (physical), " + "interface %d, firmware %d.%d.", + usb->bus, usb->address, connection_id, + USB_INTERFACE, vi.major, vi.minor); + + sr_info("Detected REVID, it's a Cypress FX2G3!\n"); + + ret = SR_OK; + + break; + } + libusb_free_device_list(devlist, 1); + + return ret; +} + +SR_PRIV struct dev_context *cypress_fx2g3_dev_new(void) +{ + struct dev_context *devc; + + devc = g_malloc0(sizeof(struct dev_context)); + devc->profile = NULL; + devc->fw_updated = 0; + devc->cur_samplerate = 0; + devc->limit_frames = 1; + devc->limit_samples = 0; + devc->capture_ratio = 0; + devc->sample_wide = FALSE; + devc->num_frames = 0; + devc->stl = NULL; + + return devc; +} + +SR_PRIV void cypress_fx2g3_abort_acquisition(struct dev_context *devc) +{ + int i; + + devc->acq_aborted = TRUE; + + for (i = devc->num_transfers - 1; i >= 0; i--) { + if (devc->transfers[i]) + libusb_cancel_transfer(devc->transfers[i]); + } +} + +static void finish_acquisition(struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + + devc = sdi->priv; + + std_session_send_df_end(sdi); + + usb_source_remove(sdi->session, devc->ctx); + + devc->num_transfers = 0; + g_free(devc->transfers); + + /* Free the deinterlace buffers if we had them. */ + if (g_slist_length(devc->enabled_analog_channels) > 0) { + g_free(devc->logic_buffer); + g_free(devc->analog_buffer); + } + + if (devc->stl) { + soft_trigger_logic_free(devc->stl); + devc->stl = NULL; + } +} + +static void free_transfer(struct libusb_transfer *transfer) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + unsigned int i; + + sdi = transfer->user_data; + devc = sdi->priv; + + g_free(transfer->buffer); + transfer->buffer = NULL; + libusb_free_transfer(transfer); + + for (i = 0; i < devc->num_transfers; i++) { + if (devc->transfers[i] == transfer) { + devc->transfers[i] = NULL; + break; + } + } + + devc->submitted_transfers--; + if (devc->submitted_transfers == 0) + finish_acquisition(sdi); +} + +static void resubmit_transfer(struct libusb_transfer *transfer) +{ + int ret; + + if ((ret = libusb_submit_transfer(transfer)) == LIBUSB_SUCCESS) + return; + + sr_err("%s: %s", __func__, libusb_error_name(ret)); + free_transfer(transfer); + +} + +static void mso_send_data_proc(struct sr_dev_inst *sdi, + uint8_t *data, size_t length, size_t sample_width) +{ + size_t i; + struct dev_context *devc; + struct sr_datafeed_analog analog; + struct sr_analog_encoding encoding; + struct sr_analog_meaning meaning; + struct sr_analog_spec spec; + + (void)sample_width; + + devc = sdi->priv; + + length /= 2; + + /* Send the logic */ + for (i = 0; i < length; i++) { + devc->logic_buffer[i] = data[i * 2]; + /* Rescale to -10V - +10V from 0-255. */ + devc->analog_buffer[i] = (data[i * 2 + 1] - 128.0f) / 12.8f; + }; + + const struct sr_datafeed_logic logic = { + .length = length, + .unitsize = 1, + .data = devc->logic_buffer + }; + + const struct sr_datafeed_packet logic_packet = { + .type = SR_DF_LOGIC, + .payload = &logic + }; + + sr_session_send(sdi, &logic_packet); + + sr_analog_init(&analog, &encoding, &meaning, &spec, 2); + analog.meaning->channels = devc->enabled_analog_channels; + analog.meaning->mq = SR_MQ_VOLTAGE; + analog.meaning->unit = SR_UNIT_VOLT; + analog.meaning->mqflags = 0 /* SR_MQFLAG_DC */; + analog.num_samples = length; + analog.data = devc->analog_buffer; + + const struct sr_datafeed_packet analog_packet = { + .type = SR_DF_ANALOG, + .payload = &analog + }; + + sr_session_send(sdi, &analog_packet); +} + +static void la_send_data_proc(struct sr_dev_inst *sdi, + uint8_t *data, size_t length, size_t sample_width) +{ + const struct sr_datafeed_logic logic = { + .length = length, + .unitsize = sample_width, + .data = data + }; + + const struct sr_datafeed_packet packet = { + .type = SR_DF_LOGIC, + .payload = &logic + }; + + sr_session_send(sdi, &packet); +} + +static void LIBUSB_CALL receive_transfer(struct libusb_transfer *transfer) +{ + struct sr_dev_inst *sdi; + struct dev_context *devc; + gboolean packet_has_error = FALSE; + unsigned int num_samples; + int trigger_offset, cur_sample_count, unitsize, processed_samples; + int pre_trigger_samples; + + sdi = transfer->user_data; + devc = sdi->priv; + + /* + * If acquisition has already ended, just free any queued up + * transfer that come in. + */ + if (devc->acq_aborted) { + free_transfer(transfer); + return; + } + + sr_dbg("receive_transfer(): status %s received %d bytes.", + libusb_error_name(transfer->status), transfer->actual_length); + + /* Save incoming transfer before reusing the transfer struct. */ + unitsize = devc->sample_wide ? 2 : 1; + cur_sample_count = transfer->actual_length / unitsize; + processed_samples = 0; + + switch (transfer->status) { + case LIBUSB_TRANSFER_NO_DEVICE: + cypress_fx2g3_abort_acquisition(devc); + free_transfer(transfer); + return; + case LIBUSB_TRANSFER_COMPLETED: + case LIBUSB_TRANSFER_TIMED_OUT: /* We may have received some data though. */ + break; + default: + packet_has_error = TRUE; + break; + } + + if (transfer->actual_length == 0 || packet_has_error) { + devc->empty_transfer_count++; + if (devc->empty_transfer_count > MAX_EMPTY_TRANSFERS) { + /* + * The FX2 gave up. End the acquisition, the frontend + * will work out that the samplecount is short. + */ + cypress_fx2g3_abort_acquisition(devc); + free_transfer(transfer); + } else { + resubmit_transfer(transfer); + } + return; + } else { + devc->empty_transfer_count = 0; + } + +check_trigger: + if (devc->trigger_fired) { + if (!devc->limit_samples || devc->sent_samples < devc->limit_samples) { + /* Send the incoming transfer to the session bus. */ + num_samples = cur_sample_count - processed_samples; + if (devc->limit_samples && devc->sent_samples + num_samples > devc->limit_samples) + num_samples = devc->limit_samples - devc->sent_samples; + + devc->send_data_proc(sdi, (uint8_t *)transfer->buffer + processed_samples * unitsize, + num_samples * unitsize, unitsize); + devc->sent_samples += num_samples; + processed_samples += num_samples; + } + } else { + trigger_offset = soft_trigger_logic_check(devc->stl, + transfer->buffer + processed_samples * unitsize, + transfer->actual_length - processed_samples * unitsize, + &pre_trigger_samples); + if (trigger_offset > -1) { + std_session_send_df_frame_begin(sdi); + devc->sent_samples += pre_trigger_samples; + num_samples = cur_sample_count - processed_samples - trigger_offset; + if (devc->limit_samples && + devc->sent_samples + num_samples > devc->limit_samples) + num_samples = devc->limit_samples - devc->sent_samples; + + devc->send_data_proc(sdi, (uint8_t *)transfer->buffer + + processed_samples * unitsize + + trigger_offset * unitsize, + num_samples * unitsize, unitsize); + devc->sent_samples += num_samples; + processed_samples += trigger_offset + num_samples; + + devc->trigger_fired = TRUE; + } + } + + const int frame_ended = devc->limit_samples && (devc->sent_samples >= devc->limit_samples); + const int final_frame = devc->limit_frames && (devc->num_frames >= (devc->limit_frames - 1)); + + if (frame_ended) { + devc->num_frames++; + devc->sent_samples = 0; + devc->trigger_fired = FALSE; + std_session_send_df_frame_end(sdi); + + /* There may be another trigger in the remaining data, go back and check for it */ + if (processed_samples < cur_sample_count) { + /* Reset the trigger stage */ + if (devc->stl) + devc->stl->cur_stage = 0; + else { + std_session_send_df_frame_begin(sdi); + devc->trigger_fired = TRUE; + } + if (!final_frame) + goto check_trigger; + } + } + if (frame_ended && final_frame) { + cypress_fx2g3_abort_acquisition(devc); + free_transfer(transfer); + } else + resubmit_transfer(transfer); +} + +static int configure_channels(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + const GSList *l; + int p; + struct sr_channel *ch; + uint32_t channel_mask = 0, num_analog = 0; + + devc = sdi->priv; + + g_slist_free(devc->enabled_analog_channels); + devc->enabled_analog_channels = NULL; + + for (l = sdi->channels, p = 0; l; l = l->next, p++) { + ch = l->data; + if ((p <= NUM_CHANNELS) && (ch->type == SR_CHANNEL_ANALOG) + && (ch->enabled)) { + num_analog++; + devc->enabled_analog_channels = + g_slist_append(devc->enabled_analog_channels, ch); + } else { + channel_mask |= ch->enabled << p; + } + } + sr_err(" configure_channels:num_analog = %d", num_analog); + sr_err(" configure_channels:channel_mask = %d", channel_mask); + /* + * Use wide sampling as default for now #TODO + */ + devc->sample_wide = 1; + devc->channel_mask = channel_mask; + return SR_OK; +} + +static unsigned int to_bytes_per_ms(unsigned int samplerate) +{ + return samplerate / 1000; +} + +static size_t get_buffer_size(struct dev_context *devc) +{ + size_t s; + + /* + * The buffer should be large enough to hold 10ms of data and + * a multiple of 512. + */ + s = 10 * to_bytes_per_ms(devc->cur_samplerate); + return (s + 1023) & ~1023; +} + +static unsigned int get_number_of_transfers(struct dev_context *devc) +{ + unsigned int n; + + /* Total buffer size should be able to hold about 500ms of data. */ + n = (500 * to_bytes_per_ms(devc->cur_samplerate) / + get_buffer_size(devc)); + + if (n > NUM_SIMUL_TRANSFERS) + return NUM_SIMUL_TRANSFERS; + + return n; +} + +static unsigned int get_timeout(struct dev_context *devc) +{ + size_t total_size; + unsigned int timeout; + + total_size = get_buffer_size(devc) * + get_number_of_transfers(devc); + timeout = total_size / to_bytes_per_ms(devc->cur_samplerate); + return timeout + timeout / 4; /* Leave a headroom of 25% percent. */ +} + +static int receive_data(int fd, int revents, void *cb_data) +{ + struct timeval tv; + struct drv_context *drvc; + + (void)fd; + (void)revents; + + drvc = (struct drv_context *)cb_data; + + tv.tv_sec = tv.tv_usec = 0; + libusb_handle_events_timeout(drvc->sr_ctx->libusb_ctx, &tv); + + return TRUE; +} + +static int start_transfers(const struct sr_dev_inst *sdi) +{ + struct dev_context *devc; + struct sr_usb_dev_inst *usb; + struct sr_trigger *trigger; + struct libusb_transfer *transfer; + unsigned int i, num_transfers; + int timeout, ret; + unsigned char *buf; + size_t size; + + devc = sdi->priv; + usb = sdi->conn; + + devc->sent_samples = 0; + devc->acq_aborted = FALSE; + devc->empty_transfer_count = 0; + + if ((trigger = sr_session_trigger_get(sdi->session))) { + int pre_trigger_samples = 0; + if (devc->limit_samples > 0) + pre_trigger_samples = (devc->capture_ratio * devc->limit_samples) / 100; + devc->stl = soft_trigger_logic_new(sdi, trigger, pre_trigger_samples); + if (!devc->stl) + return SR_ERR_MALLOC; + devc->trigger_fired = FALSE; + } else { + std_session_send_df_frame_begin(sdi); + devc->trigger_fired = TRUE; + } + + num_transfers = get_number_of_transfers(devc); + + size = get_buffer_size(devc); + sr_info("num_transfers: %d, buffer_size: %d", num_transfers,size); + devc->submitted_transfers = 0; + + devc->transfers = g_try_malloc0(sizeof(*devc->transfers) * num_transfers); + if (!devc->transfers) { + sr_err("USB transfers malloc failed."); + return SR_ERR_MALLOC; + } + + timeout = get_timeout(devc); + devc->num_transfers = num_transfers; + for (i = 0; i < num_transfers; i++) { + if (!(buf = g_try_malloc(size))) { + sr_err("USB transfer buffer malloc failed."); + return SR_ERR_MALLOC; + } + transfer = libusb_alloc_transfer(0); + libusb_fill_bulk_transfer(transfer, usb->devhdl, + 2 | LIBUSB_ENDPOINT_IN, buf, size, + receive_transfer, (void *)sdi, timeout); + sr_info("submitting transfer: %d", i); + if ((ret = libusb_submit_transfer(transfer)) != 0) { + sr_err("Failed to submit transfer: %s.", + libusb_error_name(ret)); + libusb_free_transfer(transfer); + g_free(buf); + cypress_fx2g3_abort_acquisition(devc); + return SR_ERR; + } + devc->transfers[i] = transfer; + devc->submitted_transfers++; + } + + /* + * If this device has analog channels and at least one of them is + * enabled, use mso_send_data_proc() to properly handle the analog + * data. Otherwise use la_send_data_proc(). + */ + if (g_slist_length(devc->enabled_analog_channels) > 0) + devc->send_data_proc = mso_send_data_proc; + else + devc->send_data_proc = la_send_data_proc; + + std_session_send_df_header(sdi); + + return SR_OK; +} + +SR_PRIV int cypress_fx2g3_start_acquisition(const struct sr_dev_inst *sdi) +{ + struct sr_dev_driver *di; + struct drv_context *drvc; + struct dev_context *devc; + int timeout, ret; + size_t size; + + di = sdi->driver; + drvc = di->context; + devc = sdi->priv; + + devc->ctx = drvc->sr_ctx; + devc->num_frames = 0; + devc->sent_samples = 0; + devc->empty_transfer_count = 0; + devc->acq_aborted = FALSE; + + if (configure_channels(sdi) != SR_OK) { + sr_err("Failed to configure channels."); + return SR_ERR; + } + + timeout = get_timeout(devc); + + usb_source_add(sdi->session, devc->ctx, timeout, receive_data, drvc); + + size = get_buffer_size(devc); + + + /* Prepare for analog sampling. */ + if (g_slist_length(devc->enabled_analog_channels) > 0) { + /* We need a buffer half the size of a transfer. */ + devc->logic_buffer = g_try_malloc(size / 2); + devc->analog_buffer = g_try_malloc( + sizeof(float) * size / 2); + } + start_transfers(sdi); + if ((ret = command_stop_acquisition(sdi)) != SR_OK) { + return ret; + } + + if ((ret = command_start_acquisition(sdi)) != SR_OK) { + cypress_fx2g3_abort_acquisition(devc); + return ret; + } + + return SR_OK; +} + +SR_PRIV int cypress_fx2g3_acquisition_stop(struct sr_dev_inst *sdi) +{ + command_stop_acquisition(sdi); + cypress_fx2g3_abort_acquisition(sdi->priv); + + return SR_OK; +} diff --git a/src/hardware/cypress-fx2g3/protocol.h b/src/hardware/cypress-fx2g3/protocol.h new file mode 100644 index 000000000..8d755a713 --- /dev/null +++ b/src/hardware/cypress-fx2g3/protocol.h @@ -0,0 +1,144 @@ +/* + * This file is part of the libsigrok project. + * + * Copyright (C) 2025 Javeed Shariff R + * + * 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_CYPRESS_FX2G3_PROTOCOL_H +#define LIBSIGROK_HARDWARE_CYPRESS_FX2G3_PROTOCOL_H + +#include +#include +#include +#include +#include +#include +#include "libsigrok-internal.h" + +#define LOG_PREFIX "cypress-fx2g3" + +#define USB_INTERFACE 0 +#define USB_CONFIGURATION 1 +#define NUM_TRIGGER_STAGES 4 + +#define MAX_RENUM_DELAY_MS 3000 +#define NUM_SIMUL_TRANSFERS 2 +#define MAX_EMPTY_TRANSFERS (NUM_SIMUL_TRANSFERS * 2) + +#define NUM_CHANNELS 32 + +#define FX2G3_REQUIRED_VERSION_MAJOR 1 + +#define MAX_8BIT_SAMPLE_RATE SR_MHZ(40) +#define MAX_16BIT_SAMPLE_RATE SR_MHZ(20) +//#define MAX_32BIT_SAMPLE_RATE SR_MHZ(320) + +#define FX2G3_PIB_CLOCK SR_MHZ(400) + +/* 6 delay states of up to 256 clock ticks */ +#define MAX_SAMPLE_DELAY (6 * 256) + +#define DEV_CAPS_8BIT_POS 3 +#define DEV_CAPS_16BIT_POS 4 +#define DEV_CAPS_32BIT_POS 5 +#define DEV_CAPS_AX_ANALOG_POS 1 + +#define DEV_CAPS_8BIT (1 << DEV_CAPS_8BIT_POS) +#define DEV_CAPS_16BIT (1 << DEV_CAPS_16BIT_POS) +#define DEV_CAPS_32BIT (1 << DEV_CAPS_32BIT_POS) +#define DEV_CAPS_AX_ANALOG (1 << DEV_CAPS_AX_ANALOG_POS) + +/* Protocol commands */ +#define CMD_GET_FW_VERSION (0xb0) +#define CMD_GET_REVID_VERSION (0xb1) +#define CMD_START (0xb2) +#define CMD_STOP (0xb3) + +#define CMD_START_FLAGS_CLK_CTL2_POS 4 +#define CMD_START_FLAGS_WIDE_POS 5 +#define CMD_START_FLAGS_CLK_SRC_POS 6 + +#define CMD_START_FLAGS_CLK_CTL2 (1 << CMD_START_FLAGS_CLK_CTL2_POS) +#define CMD_START_FLAGS_SAMPLE_8BIT (0 << CMD_START_FLAGS_WIDE_POS) +#define CMD_START_FLAGS_SAMPLE_16BIT (1 << CMD_START_FLAGS_WIDE_POS) + +#define CMD_START_FLAGS_CLK_30MHZ (0 << CMD_START_FLAGS_CLK_SRC_POS) +#define CMD_START_FLAGS_CLK_48MHZ (1 << CMD_START_FLAGS_CLK_SRC_POS) +#define CMD_START_FLAGS_CLK_100MHZ (2 << CMD_START_FLAGS_CLK_SRC_POS) + + +struct cypress_fx2g3_profile { + uint16_t vid; + uint16_t pid; + + const char *vendor; + const char *model; + const char *model_version; + + const char *firmware; + + uint32_t dev_caps; + + const char *usb_manufacturer; + const char *usb_product; +}; + +struct dev_context { + const struct cypress_fx2g3_profile *profile; + GSList *enabled_analog_channels; + /* + * Since we can't keep track of an Cypress-FX2G3 device after upgrading + * the firmware (it renumerates into a different device address + * after the upgrade) this is like a global lock. No device will open + * until a proper delay after the last device was upgraded. + */ + int64_t fw_updated; + + const uint64_t *samplerates; + int num_samplerates; + + uint64_t cur_samplerate; + uint64_t limit_frames; + uint64_t limit_samples; + uint64_t capture_ratio; + + gboolean trigger_fired; + gboolean acq_aborted; + gboolean sample_wide; + struct soft_trigger_logic *stl; + + uint64_t num_frames; + uint64_t sent_samples; + int submitted_transfers; + int empty_transfer_count; + + unsigned int num_transfers; + struct libusb_transfer **transfers; + struct sr_context *ctx; + void (*send_data_proc)(struct sr_dev_inst *sdi, + uint8_t *data, size_t length, size_t sample_width); + uint8_t *logic_buffer; + float *analog_buffer; + uint32_t channel_mask; +}; + +SR_PRIV int cypress_fx2g3_dev_open(struct sr_dev_inst *sdi, struct sr_dev_driver *di); +SR_PRIV struct dev_context *cypress_fx2g3_dev_new(void); +SR_PRIV int cypress_fx2g3_start_acquisition(const struct sr_dev_inst *sdi); +SR_PRIV int cypress_fx2g3_acquisition_stop(struct sr_dev_inst *sdi); +SR_PRIV void cypress_fx2g3_abort_acquisition(struct dev_context *devc); + +#endif From 96038b5df40df46f4ec6cb81fa46b2314931bff2 Mon Sep 17 00:00:00 2001 From: "Raidurg Javeed Shariff (PSS PCS WCS ASPDP SE 2)" Date: Tue, 18 Mar 2025 00:42:51 +0530 Subject: [PATCH 2/2] Updated new PID for FX2G3 logic analyzer --- src/hardware/cypress-fx2g3/api.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/hardware/cypress-fx2g3/api.c b/src/hardware/cypress-fx2g3/api.c index c08d5e972..7b53744d1 100644 --- a/src/hardware/cypress-fx2g3/api.c +++ b/src/hardware/cypress-fx2g3/api.c @@ -26,10 +26,7 @@ static const struct cypress_fx2g3_profile supported_fx2g3_dev[] = { /* * Cypress FX2G3 */ - { 0x04b4, 0xF007, "Cypress", "FX2G3", NULL, - "cypress-fx2g3-la.fw", - DEV_CAPS_8BIT, NULL, NULL}, - { 0x04b4, 0xF00F, "Cypress", "FX2G3", NULL, + { 0x04b4, 0x4907, "Cypress", "FX2G3", NULL, "cypress-fx2g3-la.fw", DEV_CAPS_16BIT, NULL, NULL},