From 2d20d6db39547f013b66230f378ceb8e21fa36e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20M=C3=A1rton?= Date: Tue, 30 Jan 2018 20:20:15 +0100 Subject: Add support for National Instruments USB-845x devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I9477b6f0193bfdf20bbe63421a7fb97b597ec549 Signed-off-by: Miklós Márton Reviewed-on: https://review.coreboot.org/c/flashrom/+/25683 Tested-by: build bot (Jenkins) Reviewed-by: Nico Huber --- Makefile | 68 +++++- flashrom.8.tmpl | 64 ++++++ flashrom.c | 11 + ni845x_spi.c | 642 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ programmer.h | 8 + 5 files changed, 792 insertions(+), 1 deletion(-) create mode 100644 ni845x_spi.c diff --git a/Makefile b/Makefile index f0a1b6198..5cf4481b6 100644 --- a/Makefile +++ b/Makefile @@ -206,6 +206,10 @@ FLASHROM_CFLAGS += -Dffs=__builtin_ffs # Some functions provided by Microsoft do not work as described in C99 specifications. This macro fixes that # for MinGW. See http://sourceforge.net/p/mingw-w64/wiki2/printf%20and%20scanf%20family/ */ FLASHROM_CFLAGS += -D__USE_MINGW_ANSI_STDIO=1 + +# National Instruments USB-845x is Windows only for now +CONFIG_NI845X_SPI ?= no + # For now we disable all PCI-based programmers on Windows/MinGW (no libpci). ifeq ($(CONFIG_INTERNAL), yes) UNSUPPORTED_FEATURES += CONFIG_INTERNAL=yes @@ -294,6 +298,15 @@ override CONFIG_SATAMV = no endif endif +ifneq ($(TARGET_OS), MinGW) +# NI USB-845x only supported on Windows at the moment +ifeq ($(CONFIG_NI845X_SPI), yes) +UNSUPPORTED_FEATURES += CONFIG_NI845X_SPI=yes +else +override CONFIG_NI845X_SPI = no +endif +endif + ifeq ($(TARGET_OS), libpayload) ifeq ($(MAKECMDGOALS),) .DEFAULT_GOAL := libflashrom.a @@ -981,6 +994,26 @@ FEATURE_CFLAGS += -D'CONFIG_JLINK_SPI=1' PROGRAMMER_OBJS += jlink_spi.o endif +ifeq ($(CONFIG_NI845X_SPI), yes) +FEATURE_CFLAGS += -D'CONFIG_NI845X_SPI=1' + +ifeq ($(CONFIG_NI845X_LIBRARY_PATH),) +# if the user did not specified the NI-845x headers/lib path +# do a guess for both 32 and 64 bit Windows versions +NI845X_LIBS += -L'${PROGRAMFILES}\National Instruments\NI-845x\MS Visual C' +NI845X_LIBS += -L'${PROGRAMFILES(x86)}\National Instruments\NI-845x\MS Visual C' +NI845X_INCLUDES += -I'${PROGRAMFILES}\National Instruments\NI-845x\MS Visual C' +NI845X_INCLUDES += -I'${PROGRAMFILES(x86)}\National Instruments\NI-845x\MS Visual C' +else +NI845X_LIBS += -L'$(CONFIG_NI845X_LIBRARY_PATH)' +NI845X_INCLUDES += -I'$(CONFIG_NI845X_LIBRARY_PATH)' +endif + +FEATURE_CFLAGS += $(NI845X_INCLUDES) +LIBS += -lni845x +PROGRAMMER_OBJS += ni845x_spi.o +endif + ifneq ($(NEED_SERIAL), ) LIB_OBJS += serial.o custom_baud.o endif @@ -1078,7 +1111,7 @@ ifeq ($(ARCH), x86) endif $(PROGRAM)$(EXEC_SUFFIX): $(OBJS) - $(CC) $(LDFLAGS) -o $(PROGRAM)$(EXEC_SUFFIX) $(OBJS) $(LIBS) $(PCILIBS) $(FEATURE_LIBS) $(USBLIBS) $(USB1LIBS) $(JAYLINKLIBS) + $(CC) $(LDFLAGS) -o $(PROGRAM)$(EXEC_SUFFIX) $(OBJS) $(LIBS) $(PCILIBS) $(FEATURE_LIBS) $(USBLIBS) $(USB1LIBS) $(JAYLINKLIBS) $(NI845X_LIBS) libflashrom.a: $(LIBFLASHROM_OBJS) $(AR) rcs $@ $^ @@ -1390,6 +1423,23 @@ int main(int argc, char **argv) endef export CLOCK_GETTIME_TEST +define NI845X_TEST +#include + +int main(int argc, char **argv) +{ + (void) argc; + (void) argv; + char I2C_Device[256]; + NiHandle Dev_Handle; + uInt32 NumberFound = 0; + ni845xFindDevice(I2C_Device, &Dev_Handle, &NumberFound); + ni845xCloseFindDeviceHandle(Dev_Handle); + return 0; +} +endef +export NI845X_TEST + features: compiler @echo "FEATURES := yes" > .features.tmp ifneq ($(NEED_LIBFTDI), ) @@ -1434,6 +1484,22 @@ ifneq ($(NEED_LINUX_I2C), ) ( echo "yes."; echo "LINUX_I2C_SUPPORT := yes" >> .features.tmp ) || \ ( echo "no."; echo "LINUX_I2C_SUPPORT := no" >> .features.tmp ) } \ 2>>$(BUILD_DETAILS_FILE) | tee -a $(BUILD_DETAILS_FILE) +endif +ifeq ($(CONFIG_NI845X_SPI), yes) + @printf "Checking for NI USB-845x installation... " | tee -a $(BUILD_DETAILS_FILE) + @echo "$$NI845X_TEST" > .featuretest.c + @printf "\nexec: %s\n" "$(CC) $(CPPFLAGS) $(CFLAGS) $(NI845X_INCLUDES) $(LDFLAGS) .featuretest.c -o .featuretest$(EXEC_SUFFIX) $(NI845X_LIBS) $(LIBS)" >>$(BUILD_DETAILS_FILE) + + @ { { { { { $(CC) $(CPPFLAGS) $(CFLAGS) $(NI845X_INCLUDES) $(LDFLAGS) .featuretest.c -o .featuretest$(EXEC_SUFFIX) $(NI845X_LIBS) $(LIBS) >&2 && \ + ( echo "yes."; echo "NI845X_SUPPORT := yes" >> .features.tmp ) || \ + { echo -e "\nUnable to find NI-845x headers or libraries."; \ + echo "Please pass the NI-845x library path to the make with the CONFIG_NI845X_LIBRARY_PATH parameter,"; \ + echo "or disable the NI-845x support by specifying make CONFIG_NI845X_SPI=no"; \ + echo "For the NI-845x 17.0 the library path is:"; \ + echo " On 32 bit systems: C:\Program Files)\National Instruments\NI-845x\MS Visual C"; \ + echo " On 64 bit systems: C:\Program Files (x86)\National Instruments\NI-845x\MS Visual C"; \ + exit 1; }; } \ + 2>>$(BUILD_DETAILS_FILE); echo $? >&3 ; } | tee -a $(BUILD_DETAILS_FILE) >&4; } 3>&1;} | { read rc ; exit ${rc}; } } 4>&1 endif @printf "Checking for utsname support... " | tee -a $(BUILD_DETAILS_FILE) @echo "$$UTSNAME_TEST" > .featuretest.c diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl index eb7fdec45..27f38465a 100644 --- a/flashrom.8.tmpl +++ b/flashrom.8.tmpl @@ -341,6 +341,8 @@ bitbanging adapter) .sp .BR "* jlink_spi" " (for SPI flash ROMs attached to SEGGER J-Link and compatible devices)" .sp +.BR "* ni845x_spi" " (for SPI flash ROMs attached to National Instruments USB-8451 or USB-8452)" +.sp Some programmers have optional or mandatory parameters which are described in detail in the .B PROGRAMMER-SPECIFIC INFORMATION @@ -1126,6 +1128,68 @@ Please also note that the mstarddc_spi driver only works on Linux. The WCH CH341A programmer does not support any parameters currently. SPI frequency is fixed at 2 MHz, and CS0 is used as per the device. .SS +.BR "ni845x_spi " programmer +.IP +An optional +.B voltage +parameter could be used to specify the IO voltage. This parameter is available for the NI USB-8452 device. +The default unit is Volt if no unit is specified. You can use +.BR mV ", " milliVolt ", " V " or " Volt +as unit specifier. +Syntax is +.sp +.B " flashrom \-p ni845x_spi:voltage=value" +.sp +where +.B value +can be +.BR 1.2V ", " 1.5V ", " 1.8V ", " 2.5V ", " 3.3V +or the equivalent in mV. +.sp +In the case if none of the programmer's supported IO voltage is within the supported voltage range of +the detected flash chip the flashrom will abort the operation (to prevent damaging the flash chip). +You can override this behaviour by passing "yes" to the +.B ignore_io_voltage_limits +parameter (for e.g. if you are using an external voltage translator circuit). +Syntax is +.sp +.B " flashrom \-p ni845x_spi:ignore_io_voltage_limits=yes" +.sp +You can use the +.B serial +parameter to explicitly specify which connected NI USB-845x device should be used. +You should use your device's 7 digit hexadecimal serial number. +Usage example to select the device with 1230A12 serial number: +.sp +.B " flashrom \-p ni845x_spi:serial=1230A12" +.sp +An optional +.B spispeed +parameter specifies the frequency of the SPI bus. +Syntax is +.sp +.B " flashrom \-p ni845x_spi:spispeed=frequency" +.sp +where +.B frequency +should a number corresponding to the desired frequency in kHz. +The maximum +.B frequency +is 12 MHz (12000 kHz) for the USB-8451 and 50 MHz (50000 kHz) for the USB-8452. +The default is a frequency of 1 MHz (1000 kHz). +.sp +An optional +.B cs +parameter specifies which target chip select line should be used. Syntax is +.sp +.B " flashrom \-p ni845x_spi:csnumber=value" +.sp +where +.B value +should be between +.BR 0 " and " 7 +By default the CS0 is used. +.SS .BR "digilent_spi " programmer .IP An optional diff --git a/flashrom.c b/flashrom.c index 06b18545d..2534e4a7a 100644 --- a/flashrom.c +++ b/flashrom.c @@ -449,6 +449,17 @@ const struct programmer_entry programmer_table[] = { }, #endif +#if CONFIG_NI845X_SPI == 1 + { + .name = "ni845x_spi", + .type = OTHER, // choose other because NI-845x uses own USB implementation + .devs.note = "National Instruments USB-845x\n", + .init = ni845x_spi_init, + .map_flash_region = fallback_map, + .unmap_flash_region = fallback_unmap, + .delay = internal_delay, + }, +#endif {0}, /* This entry corresponds to PROGRAMMER_INVALID. */ }; diff --git a/ni845x_spi.c b/ni845x_spi.c new file mode 100644 index 000000000..7b2bea3e2 --- /dev/null +++ b/ni845x_spi.c @@ -0,0 +1,642 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2018 Miklós Márton martonmiklosqdev@gmail.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 2 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "flash.h" +#include "programmer.h" +#include "spi.h" + +#define NI845x_FIND_DEVICE_NO_DEVICE_FOUND -301701 + +enum USB845x_type { + USB8451 = 0x7166, + USB8452 = 0x7514, + Unknown_NI845X_Device +}; + +enum voltage_coerce_mode { + USE_LOWER, + USE_HIGHER +}; + +static const struct spi_master spi_programmer_ni845x; + +static unsigned char CS_number; // use chip select 0 as default +static enum USB845x_type device_pid = Unknown_NI845X_Device; + +static uInt32 device_handle; +static NiHandle configuration_handle; +static uint16_t io_voltage_in_mV; +static bool ignore_io_voltage_limits; + +static int ni845x_spi_shutdown(void *data); +static int32 ni845x_spi_open_resource(char *resource_handle, uInt32 *opened_handle); +static void ni845x_spi_print_available_devices(void); + +// USB-8452 supported voltages, keep this array in ascending order! +static const uint8_t usb8452_io_voltages_in_100mV[5] = { + kNi845x12Volts, + kNi845x15Volts, + kNi845x18Volts, + kNi845x25Volts, + kNi845x33Volts +}; + +/* Copied from dediprog.c */ +/* Might be useful for other USB devices as well. static for now. */ +static int parse_voltage(char *voltage) +{ + char *tmp = NULL; + int i; + int millivolt = 0, fraction = 0; + + if (!voltage || !strlen(voltage)) { + msg_perr("Empty voltage= specified.\n"); + return -1; + } + millivolt = (int)strtol(voltage, &tmp, 0); + voltage = tmp; + /* Handle "," and "." as decimal point. Everything after it is assumed + * to be in decimal notation. + */ + if ((*voltage == '.') || (*voltage == ',')) { + voltage++; + for (i = 0; i < 3; i++) { + fraction *= 10; + /* Don't advance if the current character is invalid, + * but continue multiplying. + */ + if ((*voltage < '0') || (*voltage > '9')) + continue; + fraction += *voltage - '0'; + voltage++; + } + /* Throw away remaining digits. */ + voltage += strspn(voltage, "0123456789"); + } + /* The remaining string must be empty or "mV" or "V". */ + tolower_string(voltage); + + /* No unit or "V". */ + if ((*voltage == '\0') || !strncmp(voltage, "v", 1)) { + millivolt *= 1000; + millivolt += fraction; + } else if (!strncmp(voltage, "mv", 2) || !strncmp(voltage, "millivolt", 9)) { + /* No adjustment. fraction is discarded. */ + } else { + /* Garbage at the end of the string. */ + msg_perr("Garbage voltage= specified.\n"); + return -1; + } + return millivolt; +} + +static void ni845x_report_error(const char *const func, const int32 err) +{ + static char buf[1024]; + + ni845xStatusToString(err, sizeof(buf), buf); + msg_perr("%s failed with: %s (%d)\n", func, buf, (int)err); +} + +static void ni845x_report_warning(const char *const func, const int32 err) +{ + static char buf[1024]; + + ni845xStatusToString(err, sizeof(buf), buf); + msg_pwarn("%s failed with: %s (%d)\n", func, buf, (int)err); +} + +/** + * @param serial a null terminated string containing the serial number of the specific device or NULL + * @return the 0 on successful completition, negative error code on failure + */ +static int ni845x_spi_open(const char *serial, uInt32 *return_handle) +{ + char resource_name[256]; + NiHandle device_find_handle; + uInt32 found_devices_count = 0; + int32 tmp = 0; + + unsigned int vid, pid, usb_bus; + unsigned long int serial_as_number; + int ret = -1; + + tmp = ni845xFindDevice(resource_name, &device_find_handle, &found_devices_count); + if (tmp != 0) { + // supress warning if no device found + if (tmp != NI845x_FIND_DEVICE_NO_DEVICE_FOUND) + ni845x_report_error("ni845xFindDevice", tmp); + return -1; + } + + for (; found_devices_count; --found_devices_count) { + // Read the serial number and the PID here + // VISA resource name format example: + // USB0::0x3923::0x7514::DEADBEEF::RAW + // where the 0x7514 is the PID + // and the DEADBEEF is the serial of the device + if (sscanf(resource_name, + "USB%u::0x%04X::0x%04X::%08lX::RAW", + &usb_bus, &vid, &pid, &serial_as_number) != 4) { + // malformed resource string detected + msg_pwarn("Warning: Unable to parse the %s NI-845x resource string.\n", + resource_name); + msg_pwarn("Please report a bug at flashrom@flashrom.org\n"); + continue; + } + + device_pid = pid; + + if (!serial || strtol(serial, NULL, 16) == serial_as_number) + break; + + if (found_devices_count > 1) { + tmp = ni845xFindDeviceNext(device_find_handle, resource_name); + if (tmp) { + ni845x_report_error("ni845xFindDeviceNext", tmp); + goto _close_ret; + } + } + } + + if (found_devices_count) + ret = ni845x_spi_open_resource(resource_name, return_handle); + +_close_ret: + tmp = ni845xCloseFindDeviceHandle(device_find_handle); + if (tmp) { + ni845x_report_error("ni845xCloseFindDeviceHandle", tmp); + return -1; + } + return ret; +} + +/** + * @brief ni845x_spi_open_resource + * @param resource_handle the resource handle returned by the ni845xFindDevice or ni845xFindDeviceNext + * @param opened_handle the opened handle from the ni845xOpen + * @return the 0 on successful competition, negative error code on failure positive code on warning + */ +static int32 ni845x_spi_open_resource(char *resource_handle, uInt32 *opened_handle) +{ + // NI-845x driver loads the FPGA bitfile at the first time + // which can take couple seconds + if (device_pid == USB8452) + msg_pwarn("Opening NI-8452, this might take a while for the first time\n"); + + int32 tmp = ni845xOpen(resource_handle, opened_handle); + + if (tmp < 0) + ni845x_report_error("ni845xOpen", tmp); + else if (tmp > 0) + ni845x_report_warning("ni845xOpen", tmp); + return tmp; +} + +/** + * @brief usb8452_spi_set_io_voltage sets the IO voltage for the USB-8452 devices + * @param requested_io_voltage_mV the desired IO voltage in mVolts + * @param set_io_voltage_mV the IO voltage which was set in mVolts + * @param coerce_mode if set to USE_LOWER the closest supported IO voltage which is lower or equal to + * the requested_io_voltage_mV will be selected. Otherwise the next closest supported voltage will be choosen + * which is higher or equal to the requested_io_voltage_mV. + * @return 0 on success, negative on error, positive on warning + */ +static int usb8452_spi_set_io_voltage(uint16_t requested_io_voltage_mV, + uint16_t *set_io_voltage_mV, + enum voltage_coerce_mode coerce_mode) +{ + int i = 0; + uint8_t selected_voltage_100mV = 0; + uint8_t requested_io_voltage_100mV = 0; + + if (device_pid == USB8451) { + io_voltage_in_mV = 3300; + msg_pwarn("USB-8451 does not support the changing of the SPI IO voltage\n"); + return 0; + } + + // limit the IO voltage to 3.3V + if (requested_io_voltage_mV > 3300) { + msg_pinfo("USB-8452 maximum IO voltage is 3.3V\n"); + return -1; + } + requested_io_voltage_100mV = (requested_io_voltage_mV / 100.0f); + + // usb8452_io_voltages_in_100mV contains the supported voltage levels in increasing order + for (i = (ARRAY_SIZE(usb8452_io_voltages_in_100mV) - 1); i > 0; --i) { + if (requested_io_voltage_100mV >= usb8452_io_voltages_in_100mV[i]) + break; + } + + if (coerce_mode == USE_LOWER) { + if (requested_io_voltage_100mV < usb8452_io_voltages_in_100mV[0]) { + msg_perr("Unable to set the USB-8452 IO voltage below %.1fV " + "(the minimum supported IO voltage is %.1fV)\n", + (float)requested_io_voltage_100mV / 10.0f, + (float)usb8452_io_voltages_in_100mV[0] / 10.0f); + return -1; + } + selected_voltage_100mV = usb8452_io_voltages_in_100mV[i]; + } else { + if (i == ARRAY_SIZE(usb8452_io_voltages_in_100mV) - 1) + selected_voltage_100mV = usb8452_io_voltages_in_100mV[i]; + else + selected_voltage_100mV = usb8452_io_voltages_in_100mV[i + 1]; + } + + if (requested_io_voltage_100mV < usb8452_io_voltages_in_100mV[0]) { + /* unsupported / would have to round up */ + msg_pwarn("The USB-8452 does not support the %.1fV IO voltage\n", + requested_io_voltage_mV / 1000.0f); + selected_voltage_100mV = kNi845x12Volts; + msg_pwarn("The output voltage is set to 1.2V (this is the lowest voltage)\n"); + msg_pwarn("Supported IO voltages:\n"); + for (i = 0; i < ARRAY_SIZE(usb8452_io_voltages_in_100mV); i++) { + msg_pwarn("%.1fV", (float)usb8452_io_voltages_in_100mV[i] / 10.0f); + if (i != ARRAY_SIZE(usb8452_io_voltages_in_100mV) - 1) + msg_pwarn(", "); + } + msg_pwarn("\n"); + } else if (selected_voltage_100mV != requested_io_voltage_100mV) { + /* we rounded down/up */ + msg_pwarn("USB-8452 IO voltage forced to: %.1f V\n", + (float)selected_voltage_100mV / 10.0f); + } else { + /* exact match */ + msg_pinfo("USB-8452 IO voltage set to: %.1f V\n", + (float)selected_voltage_100mV / 10.0f); + } + + if (set_io_voltage_mV) + *set_io_voltage_mV = (selected_voltage_100mV * 100); + + i = ni845xSetIoVoltageLevel(device_handle, selected_voltage_100mV); + if (i != 0) { + ni845x_report_error("ni845xSetIoVoltageLevel", i); + return -1; + } + return 0; +} + +/** + * @brief ni845x_spi_set_speed sets the SPI SCK speed + * @param SCK_freq_in_KHz SCK speed in KHz + * @return + */ +static int ni845x_spi_set_speed(uint16_t SCK_freq_in_KHz) +{ + int32 i = ni845xSpiConfigurationSetClockRate(configuration_handle, SCK_freq_in_KHz); + uInt16 clock_freq_read_KHz; + + if (i != 0) { + ni845x_report_error("ni845xSpiConfigurationSetClockRate", i); + return -1; + } + + // read back the clock frequency and notify the user if it is not the same as it was requested + i = ni845xSpiConfigurationGetClockRate(configuration_handle, &clock_freq_read_KHz); + if (i != 0) { + ni845x_report_error("ni845xSpiConfigurationGetClockRate", i); + return -1; + } + + if (clock_freq_read_KHz != SCK_freq_in_KHz) { + msg_pinfo("SPI clock frequency forced to: %d KHz (requested: %d KHz)\n", + (int)clock_freq_read_KHz, (int)SCK_freq_in_KHz); + } else { + msg_pinfo("SPI clock frequency set to: %d KHz\n", (int)SCK_freq_in_KHz); + } + return 0; +} + +/** + * @brief ni845x_spi_print_available_devices prints a list of the available devices + */ +static void ni845x_spi_print_available_devices(void) +{ + char resource_handle[256], device_type_string[16]; + NiHandle device_find_handle; + uInt32 found_devices_count = 0; + int32 tmp = 0; + unsigned int pid, vid, usb_bus; + unsigned long int serial_as_number; + + tmp = ni845xFindDevice(resource_handle, &device_find_handle, &found_devices_count); + if (tmp != 0) { + // supress warning if no device found + if (tmp != NI845x_FIND_DEVICE_NO_DEVICE_FOUND) + ni845x_report_error("ni845xFindDevice", tmp); + return; + } + + if (found_devices_count) { + msg_pinfo("Available devices:\n"); + do { + tmp = sscanf(resource_handle, "USB%d::0x%04X::0x%04X::%08lX::RAW", + &usb_bus, &vid, &pid, &serial_as_number); + if (tmp == 4) { + switch (pid) { + case USB8451: + snprintf(device_type_string, + ARRAY_SIZE(device_type_string), "USB-8451"); + break; + case USB8452: + snprintf(device_type_string, + ARRAY_SIZE(device_type_string), "USB-8452"); + break; + default: + snprintf(device_type_string, + ARRAY_SIZE(device_type_string), "Unknown device"); + break; + } + msg_pinfo("- %lX (%s)\n", serial_as_number, device_type_string); + + found_devices_count--; + if (found_devices_count) { + tmp = ni845xFindDeviceNext(device_find_handle, resource_handle); + if (tmp) + ni845x_report_error("ni845xFindDeviceNext", tmp); + } + } + } while (found_devices_count); + } + + tmp = ni845xCloseFindDeviceHandle(device_find_handle); + if (tmp) + ni845x_report_error("ni845xCloseFindDeviceHandle", tmp); +} + +int ni845x_spi_init(void) +{ + char *speed_str = NULL; + char *CS_str = NULL; + char *voltage = NULL; + char *endptr = NULL; + int requested_io_voltage_mV = 1200; // default the IO voltage to 1.2V + int spi_speed_KHz = 1000; // selecting 1 MHz SCK is a good bet + char *serial_number = NULL; // by default open the first connected device + char *ignore_io_voltage_limits_str = NULL; + int32 tmp = 0; + + // read the cs parameter (which Chip select should we use) + CS_str = extract_programmer_param("cs"); + if (CS_str) { + CS_number = CS_str[0] - '0'; + free(CS_str); + if (strlen(CS_str) > 1 || CS_number < 0 || 7 < CS_number) { + msg_perr("Only CS 0-7 supported\n"); + return 1; + } + } + + voltage = extract_programmer_param("voltage"); + if (voltage != NULL) { + requested_io_voltage_mV = parse_voltage(voltage); + free(voltage); + if (requested_io_voltage_mV < 0) + return 1; + } + + serial_number = extract_programmer_param("serial"); + + speed_str = extract_programmer_param("spispeed"); + if (speed_str) { + spi_speed_KHz = strtoul(speed_str, &endptr, 0); + if (*endptr) { + msg_perr("The spispeed parameter passed with invalid format: %s\n", + speed_str); + msg_perr("Please pass the parameter with a simple number in kHz\n"); + return 1; + } + free(speed_str); + } + + ignore_io_voltage_limits = false; + ignore_io_voltage_limits_str = extract_programmer_param("ignore_io_voltage_limits"); + if (ignore_io_voltage_limits_str + && strcmp(ignore_io_voltage_limits_str, "yes") == 0) { + ignore_io_voltage_limits = true; + } + + if (ni845x_spi_open(serial_number, &device_handle)) { + if (serial_number) { + msg_pinfo("Could not find any connected NI USB-8451/8452 with serialnumber: %s!\n", + serial_number); + ni845x_spi_print_available_devices(); + msg_pinfo("Check the S/N field on the bottom of the device,\n" + "or use 'lsusb -v -d 3923:7166 | grep Serial' for USB-8451\n" + "or 'lsusb -v -d 3923:7514 | grep Serial' for USB-8452\n"); + free(serial_number); + } else { + msg_pinfo("Could not find any connected NI USB-845x device!\n"); + } + return 1; + } + free(serial_number); + + // open the SPI config handle + tmp = ni845xSpiConfigurationOpen(&configuration_handle); + if (tmp != 0) { + ni845x_report_error("ni845xSpiConfigurationOpen", tmp); + ni845x_spi_shutdown(NULL); + return 1; + } + + if (usb8452_spi_set_io_voltage(requested_io_voltage_mV, &io_voltage_in_mV, USE_LOWER) < 0) { + ni845x_spi_shutdown(NULL); + return 1; // no alert here usb8452_spi_set_io_voltage already printed that + } + + if (ni845x_spi_set_speed(spi_speed_KHz)) { + msg_perr("Unable to set SPI speed\n"); + ni845x_spi_shutdown(NULL); + return 1; + } + + if (register_shutdown(ni845x_spi_shutdown, NULL)) { + ni845x_spi_shutdown(NULL); + return 1; + } + + register_spi_master(&spi_programmer_ni845x); + + return 0; +} + +static int ni845x_spi_shutdown(void *data) +{ + int32 ret = 0; + + if (configuration_handle != 0) { + ret = ni845xSpiConfigurationClose(configuration_handle); + if (ret) + ni845x_report_error("ni845xSpiConfigurationClose", ret); + } + + if (device_handle != 0) { + ret = ni845xClose(device_handle); + if (ret) + ni845x_report_error("ni845xClose", ret); + } + return 0; +} + +static void ni845x_warn_over_max_voltage(const struct flashctx *flash) +{ + if (device_pid == USB8451) { + msg_pwarn("The %s chip maximum voltage is %.1fV, while the USB-8451 " + "IO voltage levels are 3.3V.\n" + "Ignoring this because ignore_io_voltage_limits parameter is set.\n", + flash->chip->name, + flash->chip->voltage.max / 1000.0f); + } else if (device_pid == USB8452) { + msg_pwarn("The %s chip maximum voltage is %.1fV, while the USB-8452 " + "IO voltage is set to %.1fV.\n" + "Ignoring this because ignore_io_voltage_limits parameter is set.\n", + flash->chip->name, + flash->chip->voltage.max / 1000.0f, + io_voltage_in_mV / 1000.0f); + } +} + +static int ni845x_spi_io_voltage_check(const struct flashctx *flash) +{ + static bool first_transmit = true; + + if (first_transmit && flash->chip) { + first_transmit = false; + if (io_voltage_in_mV > flash->chip->voltage.max) { + if (ignore_io_voltage_limits) { + ni845x_warn_over_max_voltage(flash); + return 0; + } + + if (device_pid == USB8451) { + msg_perr("The %s chip maximum voltage is %.1fV, while the USB-8451 " + "IO voltage levels are 3.3V.\nAborting operations\n", + flash->chip->name, + flash->chip->voltage.max / 1000.0f); + return -1; + } else if (device_pid == USB8452) { + msg_perr("Lowering IO voltage because the %s chip maximum voltage is %.1fV, " + "(%.1fV was set)\n", + flash->chip->name, + flash->chip->voltage.max / 1000.0f, + io_voltage_in_mV / 1000.0f); + if (usb8452_spi_set_io_voltage(flash->chip->voltage.max, + &io_voltage_in_mV, + USE_LOWER)) { + msg_perr("Unable to lower the IO voltage below " + "the chip's maximum voltage\n"); + return -1; + } + } + } else if (io_voltage_in_mV < flash->chip->voltage.min) { + if (device_pid == USB8451) { + msg_pwarn("Flash operations might be unreliable, because the %s chip's " + "minimum voltage is %.1fV, while the USB-8451's " + "IO voltage levels are 3.3V.\n", + flash->chip->name, + flash->chip->voltage.min / 1000.0f); + return ignore_io_voltage_limits ? 0 : -1; + } else if (device_pid == USB8452) { + msg_pwarn("Raising the IO voltage because the %s chip's " + "minimum voltage is %.1fV, " + "(%.1fV was set)\n", + flash->chip->name, + flash->chip->voltage.min / 1000.0f, + io_voltage_in_mV / 1000.0f); + if (usb8452_spi_set_io_voltage(flash->chip->voltage.min, + &io_voltage_in_mV, + USE_HIGHER)) { + msg_pwarn("Unable to raise the IO voltage above the chip's " + "minimum voltage\n" + "Flash operations might be unreliable.\n"); + return ignore_io_voltage_limits ? 0 : -1; + } + } + } + } + return 0; +} + +static int ni845x_spi_transmit(struct flashctx *flash, + unsigned int write_cnt, + unsigned int read_cnt, + const unsigned char *write_arr, + unsigned char *read_arr) +{ + uInt32 read_size = 0; + uInt8 *transfer_buffer = NULL; + int32 ret = 0; + + if (ni845x_spi_io_voltage_check(flash)) + return -1; + + transfer_buffer = calloc(write_cnt + read_cnt, sizeof(uInt8)); + if (transfer_buffer == NULL) { + msg_gerr("Memory allocation failed!\n"); + return -1; + } + + memcpy(transfer_buffer, write_arr, write_cnt); + + ret = ni845xSpiWriteRead(device_handle, + configuration_handle, + (write_cnt + read_cnt), transfer_buffer, &read_size, transfer_buffer); + if (ret < 0) { + // Negative specifies an error, meaning the function did not perform the expected behavior. + ni845x_report_error("ni845xSpiWriteRead", ret); + free(transfer_buffer); + return -1; + } else if (ret > 0) { + // Positive specifies a warning, meaning the function performed as expected, + // but a condition arose that might require attention. + ni845x_report_warning("ni845xSpiWriteRead", ret); + } + + if (read_cnt != 0 && read_arr != NULL) { + if ((read_cnt + write_cnt) != read_size) { + msg_perr("%s: expected and returned read count mismatch: %u expected, %ld recieved\n", + __func__, read_cnt, read_size); + free(transfer_buffer); + return -1; + } + memcpy(read_arr, &transfer_buffer[write_cnt], read_cnt); + } + free(transfer_buffer); + return 0; +} + +static const struct spi_master spi_programmer_ni845x = { + .max_data_read = MAX_DATA_READ_UNLIMITED, + .max_data_write = MAX_DATA_WRITE_UNLIMITED, + .command = ni845x_spi_transmit, + .multicommand = default_spi_send_multicommand, + .read = default_spi_read, + .write_256 = default_spi_write_256, + .write_aai = default_spi_write_aai, +}; diff --git a/programmer.h b/programmer.h index e42c6bb58..5a21b2e2f 100644 --- a/programmer.h +++ b/programmer.h @@ -120,6 +120,9 @@ enum programmer { #endif #if CONFIG_JLINK_SPI == 1 PROGRAMMER_JLINK_SPI, +#endif +#if CONFIG_NI845X_SPI == 1 + PROGRAMMER_NI845X_SPI, #endif PROGRAMMER_INVALID /* This must always be the last entry. */ }; @@ -556,6 +559,11 @@ extern const struct dev_entry devs_digilent_spi[]; int jlink_spi_init(void); #endif +/* ni845x_spi.c */ +#if CONFIG_NI845X_SPI == 1 +int ni845x_spi_init(void); +#endif + /* flashrom.c */ struct decode_sizes { uint32_t parallel; -- cgit v1.2.3