From e89798b6ec6dd02e189e4ed022897d4e17365947 Mon Sep 17 00:00:00 2001 From: Lorenzooone Date: Wed, 27 Nov 2024 14:23:00 +0100 Subject: [PATCH] Add base ftd2 support (bad performance) --- CMakeLists.txt | 30 +- bin/ftd2_ds2_fw_1.bin | Bin 0 -> 60946 bytes bin/ftd2_ds2_fw_2.bin | Bin 0 -> 60946 bytes .../CaptureDeviceSpecific/dscapture_ftd2.hpp | 2 + include/CaptureDeviceSpecific/usb_generic.hpp | 4 +- include/capture_structs.hpp | 16 +- include/hw_defs.hpp | 2 +- setup_scripts/ftd_shared_symbols.txt | 8 + setup_scripts/libusb_ftd2_shared_symbols.txt | 69 +++ setup_scripts/libusb_ftd3_shared_symbols.txt | 0 .../CaptureDeviceSpecific/3dscapture_ftd3.cpp | 3 +- .../CaptureDeviceSpecific/dscapture_ftd2.cpp | 480 +++++++++++++++++- source/conversions.cpp | 22 +- tools/CMakeLists.txt | 13 + tools/prepare_rename_symbols_list.cpp | 57 +++ usb_rules/50-ftd2xx.rules | 4 + usb_rules/51-ftd2xx.rules | 1 + 17 files changed, 685 insertions(+), 26 deletions(-) create mode 100644 bin/ftd2_ds2_fw_1.bin create mode 100644 bin/ftd2_ds2_fw_2.bin create mode 100644 setup_scripts/ftd_shared_symbols.txt create mode 100644 setup_scripts/libusb_ftd2_shared_symbols.txt create mode 100644 setup_scripts/libusb_ftd3_shared_symbols.txt create mode 100644 tools/prepare_rename_symbols_list.cpp create mode 100644 usb_rules/50-ftd2xx.rules create mode 100644 usb_rules/51-ftd2xx.rules diff --git a/CMakeLists.txt b/CMakeLists.txt index 6257297..5635965 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(TARGET_LANGUAGES CXX C) project(cc3dsfs VERSION 1.0.0 LANGUAGES ${TARGET_LANGUAGES}) include(ExternalProject) -set(N3DSXL_LOOPY_SUPPORT 1) +set(N3DSXL_LOOPY_SUPPORT 0) set(NEW_DS_LOOPY_SUPPORT 1) set(IS_NITRO_SUPPORT 1) set(OLD_DS_3DS_LOOPY_SUPPORT 1) @@ -172,7 +172,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(FTD2XX_SUBFOLDER win) set(FTD2XX_HEADER_PATH ftd2xx.h) if(${WINDOWS_FTD2XX_USE_SHARED_LIB}) - set(FTD2XX_BUILD_COMMAND ${FTD3XX_BUILD_COMMAND}_dll) + set(FTD2XX_BUILD_COMMAND ${FTD2XX_BUILD_COMMAND}_dll) set(FTD2XX_WINDOWS_PATH_SPECIFIER "") if(WINDOWS_ARM64) set(FTD2XX_WINDOWS_PATH_SPECIFIER Release) @@ -266,6 +266,9 @@ if(NEW_DS_LOOPY_SUPPORT) endif() endif() +set(TOOLS_DATA_DIR ${CMAKE_BINARY_DIR}/tools_and_data) +file(MAKE_DIRECTORY ${TOOLS_DATA_DIR}) + set(FETCH_CONTENT_MAKE_AVAILABLE_LIBRARIES "") set(SOURCE_CPP_EXTRA_FILES "") set(EXTRA_DEPENDENCIES "") @@ -283,7 +286,7 @@ if(N3DSXL_LOOPY_SUPPORT) endif() if(NEW_DS_LOOPY_SUPPORT) list(APPEND FETCH_CONTENT_MAKE_AVAILABLE_LIBRARIES FTD2XX) - list(APPEND SOURCE_CPP_EXTRA_FILES ${SOURCE_CPP_DEVICE_FILES_BASE_PATH}/dscapture_ftd2.cpp) + list(APPEND SOURCE_CPP_EXTRA_FILES ${SOURCE_CPP_DEVICE_FILES_BASE_PATH}/dscapture_ftd2.cpp ${TOOLS_DATA_DIR}/ftd2_ds2_fw_1.cpp ${TOOLS_DATA_DIR}/ftd2_ds2_fw_2.cpp) list(APPEND EXTRA_DEPENDENCIES FTD2XX_BUILD_PROJECT) if(MSVC) list(APPEND EXTRA_CXX_FLAGS "/DUSE_FTD2") @@ -360,11 +363,14 @@ else() set(FTD3XX_BUILD_COMMAND_PARAMETERS ${ftd3xx_SOURCE_DIR} ${ftd3xx_BINARY_DIR}/${FTD3XX_SUBFOLDER} ${FTD3XX_LIB}) set(FTD2XX_BUILD_COMMAND_PARAMETERS ${ftd2xx_SOURCE_DIR} ${ftd2xx_BINARY_DIR}/${FTD2XX_SUBFOLDER} build/${FTD2XX_LIB}) endif() + if(N3DSXL_LOOPY_SUPPORT) ExternalProject_Add(FTD3XX_BUILD_PROJECT SOURCE_DIR ${ftd3xx_SOURCE_DIR} BINARY_DIR ${ftd3xx_BINARY_DIR} BUILD_COMMAND ${FTD3XX_BUILD_COMMAND}${SCRIPT_EXTENSION} ${FTD3XX_BUILD_COMMAND_PARAMETERS} + #COMMAND ${TOOLS_DATA_DIR}/CMakePrepareRenameSymbols${HOST_FINAL_EXTENSION} ${SETUP_SCRIPTS_DIR}/ftd_shared_symbols.txt ftd3_ ${TOOLS_DATA_DIR}/bad_ftd3_symbols_list.txt ${TOOLS_DATA_DIR}/ftd3xx_symbols_renames.h + #COMMAND ${CMAKE_OBJCOPY} ARGS --redefine-syms=${TOOLS_DATA_DIR}/bad_ftd3_symbols_list.txt ${ftd3xx_BINARY_DIR}/${FTD3XX_SUBFOLDER}/${FTD3XX_LIB} UPDATE_COMMAND "" CONFIGURE_COMMAND "" INSTALL_COMMAND "" @@ -375,6 +381,8 @@ if(NEW_DS_LOOPY_SUPPORT) SOURCE_DIR ${ftd2xx_SOURCE_DIR} BINARY_DIR ${ftd2xx_BINARY_DIR} BUILD_COMMAND ${FTD2XX_BUILD_COMMAND}${SCRIPT_EXTENSION} ${FTD2XX_BUILD_COMMAND_PARAMETERS} + #COMMAND ${TOOLS_DATA_DIR}/CMakePrepareRenameSymbols${HOST_FINAL_EXTENSION} ${SETUP_SCRIPTS_DIR}/ftd_shared_symbols.txt ftd2_ ${TOOLS_DATA_DIR}/bad_ftd2_symbols_list.txt ${TOOLS_DATA_DIR}/ftd2xx_symbols_renames.h + #COMMAND ${CMAKE_OBJCOPY} ARGS --redefine-syms=${TOOLS_DATA_DIR}/bad_ftd2_symbols_list.txt ${ftd2xx_BINARY_DIR}/${FTD2XX_SUBFOLDER}/${FTD2XX_LIB} UPDATE_COMMAND "" CONFIGURE_COMMAND "" INSTALL_COMMAND "" @@ -386,8 +394,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # search headers and libraries in the target environment set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -set(TOOLS_DATA_DIR ${CMAKE_BINARY_DIR}/tools_and_data) -file(MAKE_DIRECTORY ${TOOLS_DATA_DIR}) execute_process(COMMAND ${CMAKE_COMMAND} -B ${TOOLS_DATA_DIR} ${CMAKE_SOURCE_DIR}/tools) execute_process(COMMAND ${CMAKE_COMMAND} --build ${TOOLS_DATA_DIR}) @@ -426,6 +432,20 @@ add_custom_command( DEPENDS ${CMAKE_SOURCE_DIR}/data/font.ttf ${TOOLS_DATA_DIR}/CMakeBin2C${HOST_FINAL_EXTENSION} ) +add_custom_command( + OUTPUT ${TOOLS_DATA_DIR}/ftd2_ds2_fw_1.cpp + COMMENT "Convert fw binary to C - FTD2 NDS 1" + COMMAND ${TOOLS_DATA_DIR}/CMakeBin2C${HOST_FINAL_EXTENSION} ${CMAKE_SOURCE_DIR}/bin/ftd2_ds2_fw_1.bin ${TOOLS_DATA_DIR} ftd2_ds2_fw_1 ftd2_ds2_fw_1 + DEPENDS ${CMAKE_SOURCE_DIR}/bin/ftd2_ds2_fw_1.bin ${TOOLS_DATA_DIR}/CMakeBin2C${HOST_FINAL_EXTENSION} +) + +add_custom_command( + OUTPUT ${TOOLS_DATA_DIR}/ftd2_ds2_fw_2.cpp + COMMENT "Convert fw binary to C - FTD2 NDS 2" + COMMAND ${TOOLS_DATA_DIR}/CMakeBin2C${HOST_FINAL_EXTENSION} ${CMAKE_SOURCE_DIR}/bin/ftd2_ds2_fw_2.bin ${TOOLS_DATA_DIR} ftd2_ds2_fw_2 ftd2_ds2_fw_2 + DEPENDS ${CMAKE_SOURCE_DIR}/bin/ftd2_ds2_fw_2.bin ${TOOLS_DATA_DIR}/CMakeBin2C${HOST_FINAL_EXTENSION} +) + set(SHADERS_LIST "") list(APPEND SHADERS_LIST ${CMAKE_SOURCE_DIR}/shaders/bit_crusher_fragment_shader.2_to_x_1_7.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_crusher_fragment_shader.2_to_x_1_6.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_crusher_fragment_shader_7.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_fragment_shader.2_to_x_0_6.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_fragment_shader_7.frag ${CMAKE_SOURCE_DIR}/shaders/frame_blending_bit_crusher_fragment_shader.2_to_x_1_7.frag ${CMAKE_SOURCE_DIR}/shaders/frame_blending_fragment_shader.frag ${CMAKE_SOURCE_DIR}/shaders/no_effect_fragment_shader.frag) diff --git a/bin/ftd2_ds2_fw_1.bin b/bin/ftd2_ds2_fw_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..f5052e3dced4e26d664222ccee79d8ff4fff5f85 GIT binary patch literal 60946 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 >!s=r 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 &devices_list, std::vector &no_access_list); bool connect_ftd2(bool print_failed, CaptureData* capture_data, CaptureDevice* device); void ftd2_capture_main_loop(CaptureData* capture_data); diff --git a/include/CaptureDeviceSpecific/usb_generic.hpp b/include/CaptureDeviceSpecific/usb_generic.hpp index cfc47c3..a7901e5 100644 --- a/include/CaptureDeviceSpecific/usb_generic.hpp +++ b/include/CaptureDeviceSpecific/usb_generic.hpp @@ -1,12 +1,14 @@ #ifndef __USB_GENERIC_HPP #define __USB_GENERIC_HPP +#if defined(USE_IS_NITRO_USB) || defined(USE_DS_3DS_USB) #include +libusb_context* get_usb_ctx(); +#endif void usb_init(); void usb_close(); bool usb_is_initialized(); -libusb_context* get_usb_ctx(); int get_usb_total_filtered_devices(const uint16_t valid_vids[], size_t num_vids, const uint16_t valid_pids[], size_t num_pids); #endif diff --git a/include/capture_structs.hpp b/include/capture_structs.hpp index eb464f3..3455be3 100755 --- a/include/capture_structs.hpp +++ b/include/capture_structs.hpp @@ -94,6 +94,12 @@ struct ALIGNED(16) PACKED USBOldDSCaptureReceived { USBOldDSFrameInfo frameinfo; }; +struct ALIGNED(16) PACKED FTD2OldDSCaptureReceived { + USBOldDSVideoInputData video_in; + uint16_t audio_data[DS_SAMPLES_IN]; + uint8_t unused_buffer[EXTRA_DATA_BUFFER_USB_SIZE]; +}; + struct ALIGNED(16) PACKED ISNitroCaptureReceived { ISNitroEmulatorVideoInputData video_in; }; @@ -106,13 +112,15 @@ union CaptureReceived { USB3DSCaptureReceived usb_received_3ds; USB3DSCaptureReceived_3D usb_received_3ds_3d; USBOldDSCaptureReceived usb_received_old_ds; + FTD2OldDSCaptureReceived ftd2_received_old_ds; ISNitroCaptureReceived is_nitro_capture_received; }; struct CaptureDevice { - CaptureDevice(std::string serial_number, std::string name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, int max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type) : serial_number(serial_number), name(name), cc_type(cc_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), path(""), video_data_type(video_data_type) {} - CaptureDevice(std::string serial_number, std::string name, std::string path, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, int max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type) : serial_number(serial_number), name(name), cc_type(cc_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), path(path), video_data_type(video_data_type) {} - CaptureDevice(): serial_number(""), name(""), cc_type(CAPTURE_CONN_USB), descriptor(NULL), is_3ds(false), has_3d(false), has_audio(false), width(0), height(0), max_samples_in(0), base_rotation(0), top_screen_x(0), top_screen_y(0), bot_screen_x(0), bot_screen_y(0), path(""), video_data_type(VIDEO_DATA_RGB) {} + CaptureDevice(std::string serial_number, std::string name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, int max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type) : serial_number(serial_number), name(name), cc_type(cc_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), path(""), video_data_type(video_data_type), firmware_id(0), is_rgb_888(false) {} + CaptureDevice(std::string serial_number, std::string name, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, int max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type, int firmware_id, bool is_rgb_888) : serial_number(serial_number), name(name), cc_type(cc_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), path(""), video_data_type(video_data_type), firmware_id(firmware_id), is_rgb_888(is_rgb_888) {} + CaptureDevice(std::string serial_number, std::string name, std::string path, CaptureConnectionType cc_type, const void* descriptor, bool is_3ds, bool has_3d, bool has_audio, int width, int height, int max_samples_in, int base_rotation, int top_screen_x, int top_screen_y, int bot_screen_x, int bot_screen_y, InputVideoDataType video_data_type) : serial_number(serial_number), name(name), cc_type(cc_type), descriptor(descriptor), is_3ds(is_3ds), has_3d(has_3d), has_audio(has_audio), width(width), height(height), max_samples_in(max_samples_in), base_rotation(base_rotation), top_screen_x(top_screen_x), top_screen_y(top_screen_y), bot_screen_x(bot_screen_x), bot_screen_y(bot_screen_y), path(path), video_data_type(video_data_type), firmware_id(0), is_rgb_888(false) {} + CaptureDevice(): serial_number(""), name(""), cc_type(CAPTURE_CONN_USB), descriptor(NULL), is_3ds(false), has_3d(false), has_audio(false), width(0), height(0), max_samples_in(0), base_rotation(0), top_screen_x(0), top_screen_y(0), bot_screen_x(0), bot_screen_y(0), path(""), firmware_id(0), video_data_type(VIDEO_DATA_RGB), is_rgb_888(false) {} std::string serial_number; std::string name; @@ -131,6 +139,8 @@ struct CaptureDevice { int top_screen_y; int bot_screen_x; int bot_screen_y; + int firmware_id; + bool is_rgb_888; }; struct CaptureStatus { diff --git a/include/hw_defs.hpp b/include/hw_defs.hpp index ddf1224..86455ad 100755 --- a/include/hw_defs.hpp +++ b/include/hw_defs.hpp @@ -71,7 +71,7 @@ // In general, it should be less than * 2, but you never know. For now, go for safety at x16... #define O3DS_SAMPLES_IN (1096 * 16) #define N3DSXL_SAMPLES_IN (1096 * 16) -#define DS_SAMPLES_IN (1096 * 16) +#define DS_SAMPLES_IN (1096) #define MAX_SAMPLES_IN O3DS_SAMPLES_IN #endif diff --git a/setup_scripts/ftd_shared_symbols.txt b/setup_scripts/ftd_shared_symbols.txt new file mode 100644 index 0000000..2cc3caa --- /dev/null +++ b/setup_scripts/ftd_shared_symbols.txt @@ -0,0 +1,8 @@ +FT_ListDevices +FT_GetVIDPID +FT_Close +FT_CreateDeviceInfoList +FT_GetDeviceInfoList +FT_GetDeviceInfoDetail +FT_GetDriverVersion +FT_GetLibraryVersion diff --git a/setup_scripts/libusb_ftd2_shared_symbols.txt b/setup_scripts/libusb_ftd2_shared_symbols.txt new file mode 100644 index 0000000..89f7cd2 --- /dev/null +++ b/setup_scripts/libusb_ftd2_shared_symbols.txt @@ -0,0 +1,69 @@ +usbi_hotplug_notification +usbi_default_context +usbi_log +usbi_alloc_device +usbi_connect_device +usbi_disconnect_device +usbi_sanitize_device +usbi_get_device_by_session_id +usbi_signal_event +usbi_clear_event +active_contexts_lock +discovered_devs_append +libusb_get_version +libusb_error_name +libusb_exit +libusb_init +libusb_set_option +libusb_set_debug +libusb_set_auto_detach_kernel_driver +libusb_attach_kernel_driver +libusb_detach_kernel_driver +libusb_kernel_driver_active +libusb_dev_mem_free +libusb_dev_mem_alloc +libusb_free_streams +libusb_alloc_streams +libusb_reset_device +libusb_clear_halt +libusb_unref_device +libusb_ref_device +libusb_has_capability +libusb_get_device_list +libusb_free_device_list +libusb_get_bus_number +libusb_get_port_number +libusb_get_port_numbers +libusb_get_port_path +libusb_get_parent +libusb_get_device_address +libusb_get_device_speed +libusb_get_max_packet_size +libusb_get_max_iso_packet_size +libusb_open +libusb_open_device_with_vid_pid +libusb_close +libusb_get_device +libusb_get_configuration +libusb_set_configuration +libusb_claim_interface +libusb_release_interface +libusb_set_interface_alt_setting +libusb_hotplug_register_callback +libusb_hotplug_deregister_callback +libusb_get_device_descriptor +libusb_get_active_config_descriptor +libusb_get_config_descriptor +libusb_get_config_descriptor_by_value +libusb_free_config_descriptor +libusb_get_ss_endpoint_companion_descriptor +libusb_free_ss_endpoint_companion_descriptor +libusb_free_bos_descriptor +libusb_get_bos_descriptor +libusb_get_usb_2_0_extension_descriptor +libusb_free_usb_2_0_extension_descriptor +libusb_get_ss_usb_device_capability_descriptor +libusb_free_ss_usb_device_capability_descriptor +libusb_get_container_id_descriptor +libusb_free_container_id_descriptor +libusb_get_string_descriptor_ascii diff --git a/setup_scripts/libusb_ftd3_shared_symbols.txt b/setup_scripts/libusb_ftd3_shared_symbols.txt new file mode 100644 index 0000000..e69de29 diff --git a/source/CaptureDeviceSpecific/3dscapture_ftd3.cpp b/source/CaptureDeviceSpecific/3dscapture_ftd3.cpp index 27249ee..45584db 100644 --- a/source/CaptureDeviceSpecific/3dscapture_ftd3.cpp +++ b/source/CaptureDeviceSpecific/3dscapture_ftd3.cpp @@ -1,5 +1,6 @@ #include "3dscapture_ftd3.hpp" #include "devicecapture.hpp" +//#include "ftd3xx_symbols_renames.h" #ifdef _WIN32 #define FTD3XX_STATIC @@ -103,7 +104,7 @@ void list_devices_ftd3(std::vector &devices_list, std::vector @@ -9,23 +14,492 @@ #include #include +#define FT_FAILED(x) ((x) != FT_OK) + +#define FTD2XX_VID 0x0403 + +#define REAL_SERIAL_NUMBER_SIZE 16 +#define SERIAL_NUMBER_SIZE (REAL_SERIAL_NUMBER_SIZE+1) + +#define LE16(x) ((x) & 0xff), (((x) >> 8) & 0xff) + +#define FTD2XX_IN_SIZE 0x10000 +#define FTD2XX_OUT_SIZE 0x10000 + +#define CLKRATE 6 // 1 - 25MHz +#define CLKDIV ((30 / CLKRATE) - 1) +#define RESETDELAY ((1 * CLKRATE) / 8) //1uS in bytes (clks/8) +#define INITDELAY ((1200 * CLKRATE) / 8) //1200uS + +#define TX_SPI_SIZE (1 << 16) +#define TX_SPI_OFFSET 3 + +#define MAX_PACKET_SIZE_FTD2 510 +#define ENABLE_AUDIO true + +const uint16_t ftd2xx_valid_vids[] = {FTD2XX_VID}; +const uint16_t ftd2xx_valid_pids[] = {0x6014}; + +const uint8_t* ftd2_ds2_fws[] = { +ftd2_ds2_fw_1, +ftd2_ds2_fw_2, +}; + +const size_t ftd2_ds2_sizes[] = { +ftd2_ds2_fw_1_len, +ftd2_ds2_fw_2_len, +}; + void list_devices_ftd2(std::vector &devices_list, std::vector &no_access_list) { + FT_STATUS ftStatus; + DWORD numDevs = 0; + std::string valid_descriptions[] = {"NDS.1", "NDS.2"}; + int descriptions_firmware_ids[] = {1, 2}; + ftStatus = FT_CreateDeviceInfoList(&numDevs); + size_t num_inserted = 0; + if (!FT_FAILED(ftStatus) && numDevs > 0) + { + const int debug_multiplier = 1; + FT_HANDLE ftHandle = NULL; + DWORD Flags = 0; + DWORD Type = 0; + DWORD ID = 0; + char SerialNumber[SERIAL_NUMBER_SIZE] = { 0 }; + char Description[65] = { 0 }; + for (DWORD i = 0; i < numDevs; i++) + { + ftStatus = FT_GetDeviceInfoDetail(i, &Flags, &Type, &ID, NULL, + SerialNumber, Description, &ftHandle); + if((!FT_FAILED(ftStatus)) && (Flags & FT_FLAGS_HISPEED) && (Type == FT_DEVICE_232H)) + { + for(int j = 0; j < sizeof(valid_descriptions) / sizeof(*valid_descriptions); j++) { + if(Description == valid_descriptions[j]) { + for(int u = 0; u < debug_multiplier; u++) + devices_list.emplace_back(std::string(SerialNumber), "DS.2", CAPTURE_CONN_FTD2, (void*)NULL, false, false, ENABLE_AUDIO, WIDTH_DS, HEIGHT_DS + HEIGHT_DS, 0, 0, 0, 0, 0, HEIGHT_DS, VIDEO_DATA_RGB16, descriptions_firmware_ids[j], false); + break; + } + } + } + } + } + if(num_inserted == 0) { + #if defined(USE_IS_NITRO_USB) || defined(USE_DS_3DS_USB) + if(get_usb_total_filtered_devices(ftd2xx_valid_vids, sizeof(ftd2xx_valid_vids) / sizeof(ftd2xx_valid_vids[0]), ftd2xx_valid_pids, sizeof(ftd2xx_valid_pids) / sizeof(ftd2xx_valid_pids[0])) != numDevs) + no_access_list.emplace_back("FTD2XX"); + #endif + } } uint64_t ftd2_get_video_in_size(CaptureData* capture_data) { - return 0; + return sizeof(USBOldDSVideoInputData); } static uint64_t get_capture_size(CaptureData* capture_data) { - return 0; + return (((sizeof(FTD2OldDSCaptureReceived) - EXTRA_DATA_BUFFER_USB_SIZE) + (MAX_PACKET_SIZE_FTD2 - 1)) / MAX_PACKET_SIZE_FTD2) * MAX_PACKET_SIZE_FTD2; // multiple of maxPacketSize +} + +static bool pass_if_FT_queue_empty(FT_HANDLE handle) { + DWORD bytesIn = 0; + FT_STATUS ftStatus; + ftStatus = FT_GetQueueStatus(handle, &bytesIn); + if(FT_FAILED(ftStatus) || (bytesIn != 0)) //should be empty + return false; + return true; +} + +static bool full_ftd2_write(FT_HANDLE handle, const uint8_t* data, size_t size) { + FT_STATUS ftStatus; + if(!pass_if_FT_queue_empty(handle)) // maybe MPSSE error? + return false; + + DWORD sent; + ftStatus = FT_Write(handle, (void*)data, size, &sent); + if(FT_FAILED(ftStatus) || (sent != size)) + return false; + return true; +} + +//verify CDONE==val +static bool check_cdone(FT_HANDLE handle, bool want_active) { + static const uint8_t cmd[] = { + 0x81, //read D + 0x83, //read C + }; + uint8_t buf[2]; + + if(!full_ftd2_write(handle, cmd, sizeof(cmd))) + return false; + + DWORD bytesIn; + FT_STATUS ftStatus = FT_Read(handle, buf, 2, &bytesIn); + if(FT_FAILED(ftStatus) || (bytesIn != 2) || (buf[0] == 0xFA)) + return false; + + if(want_active) + return (buf[0] & 0x40); + return !(buf[0] & 0x40); +} + +static FT_STATUS end_spi_tx(uint8_t *txBuf, FT_STATUS ftStatus) { + delete []txBuf; + return ftStatus; +} + +static FT_STATUS ftd2_spi_tx(FT_HANDLE handle, const uint8_t* buf, int size) { + uint8_t *txBuf = new uint8_t[TX_SPI_SIZE + TX_SPI_OFFSET]; + FT_STATUS ftStatus; + int len; + DWORD sent; + DWORD bytesIn; + DWORD wrote = 0; + + if(!pass_if_FT_queue_empty(handle)) + return end_spi_tx(txBuf, FT_OTHER_ERROR); + + while(size > 0) { + len = size; + if(len > TX_SPI_SIZE) + len = TX_SPI_SIZE; + memcpy(&txBuf[TX_SPI_OFFSET], &buf[wrote], len); + + txBuf[0] = 0x11; + txBuf[1] = (len - 1) & 0xFF; + txBuf[2] = ((len - 1) >> 8) & 0xFF; + + ftStatus = FT_Write(handle, (void*)txBuf, len + TX_SPI_OFFSET, &sent); + if(FT_FAILED(ftStatus)) + return end_spi_tx(txBuf, ftStatus); + if(sent != (len + TX_SPI_OFFSET)) + return end_spi_tx(txBuf, FT_INSUFFICIENT_RESOURCES); + + if(!pass_if_FT_queue_empty(handle)) + return end_spi_tx(txBuf, FT_OTHER_ERROR); + + wrote += sent - TX_SPI_OFFSET; + size -= len; + } + return end_spi_tx(txBuf, FT_OK); +} + +static bool fpga_config(FT_HANDLE handle, const uint8_t* bitstream, int size) { + //D6=CDONE (in) + //D3=SS (out) + //D2=TDO (in) + //D1=TDI (out) + //D0=TCK (out) + //C7=reset (out) + + //Lattice programming: + // hold SS low + // pulse reset 200ns + // wait 1200us + // release SS + // send 8 clocks + // hold SS low + // send configuration MSB first, sampled on +clk + // release SS + // CDONE should go high within 100 clocks + // send >49 clocks after CDONE=1 + + static const uint8_t cmd0[] = { //configure for lattice SPI: + 0x82, 0x7f, 0x80, //set Cx pins {val,dir:1=out}: reset=0 + 0x80, 0xf0, 0x0b, //set Dx pins {val,dir:1=out}: SS=0, clk idle low, TDI out, TCK out + 0x8f, LE16(RESETDELAY), //reset delay + 0x82, 0xff, 0x00, //set Cx pins: reset=PU (does val=1 enable pullups? seems to...) + 0x8f, LE16(INITDELAY), //init delay + 0x80, 0xf8, 0x0b, //set Dx pins: SS=1 + 0x8f, LE16(1), //16 dummy clocks + 0x80, 0xf0, 0x0b, //set Dx pins: SS=0 + }; + + FT_STATUS ftStatus; + ftStatus = FT_SetTimeouts(handle, 300, 300); + if(FT_FAILED(ftStatus)) + return false; + if(!full_ftd2_write(handle, cmd0, sizeof(cmd0))) + return false; + + //verify CDONE=0 + if(!check_cdone(handle, false)) + return false; + + //send configuration + ftStatus = ftd2_spi_tx(handle, bitstream, size); + if(FT_FAILED(ftStatus)) + return false; + + //finish up + static const uint8_t cmd1[] = { + 0x80, 0xf8, 0x0b, //set Dx pins: SS=1 + 0x8f, LE16(20), //>150 dummy clocks + 0x80, 0x00, 0x00, //float Dx + 0x82, 0x00, 0x00, //float Cx + }; + if(!full_ftd2_write(handle, cmd1, sizeof(cmd1))) + return false; + + //verify CDONE=1 + if(!check_cdone(handle, true)) + return false; + + return true; +} + +static bool init_MPSSE(FT_HANDLE handle) { + static const uint8_t cmd[] = { + 0x85, //no loopback + 0x8d, //disable 3-phase clocking + 0x8a, //disable clk divide-by-5 + 0x97, //disable adaptive clocking + 0x86, LE16(CLKDIV), //set clock rate + }; + + FT_STATUS ftStatus; + ftStatus = FT_ResetDevice(handle); + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetUSBParameters(handle, FTD2XX_IN_SIZE, FTD2XX_OUT_SIZE); //Multiple of 64 bytes up to 64k + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetChars(handle, 0, 0, 0, 0); + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetTimeouts(handle, 300, 300); //read,write timeout (ms) + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetLatencyTimer(handle, 3); //time to wait before incomplete packet is sent (default=16ms). MPSSE read seems to fail on 2 sometimes, too fast? + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetFlowControl(handle, FT_FLOW_RTS_CTS, 0, 0); //turn on flow control to synchronize IN requests + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetBitMode(handle, 0x00, FT_BITMODE_RESET); //performs a general reset on MPSSE + if(FT_FAILED(ftStatus)) + return false; + ftStatus = FT_SetBitMode(handle, 0x00, FT_BITMODE_MPSSE); //enable MPSSE mode + if(FT_FAILED(ftStatus)) + return false; + + //MPSSE seems to choke on first write sometimes :/ Send, purge, resend. + DWORD sent; + ftStatus = FT_Write(handle, (void*)cmd, 1, &sent); //enable MPSSE mode + if(FT_FAILED(ftStatus)) + return false; + default_sleep(); + ftStatus = FT_Purge(handle, FT_PURGE_RX | FT_PURGE_TX); + if(FT_FAILED(ftStatus)) + return false; + return full_ftd2_write(handle, cmd, sizeof(cmd)); +} + +static void preemptive_close_connection(CaptureData* capture_data) { + FT_ResetDevice(capture_data->handle); + FT_Close(capture_data->handle); } bool connect_ftd2(bool print_failed, CaptureData* capture_data, CaptureDevice* device) { - return false; + char SerialNumber[SERIAL_NUMBER_SIZE] = { 0 }; + strncpy(SerialNumber, device->serial_number.c_str(), REAL_SERIAL_NUMBER_SIZE); + SerialNumber[REAL_SERIAL_NUMBER_SIZE] = 0; + if(FT_OpenEx(SerialNumber, FT_OPEN_BY_SERIAL_NUMBER, &capture_data->handle)) { + capture_error_print(print_failed, capture_data, "Create failed"); + return false; + } + + if(!init_MPSSE(capture_data->handle)) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "MPSSE init failed"); + return false; + } + + if(!fpga_config(capture_data->handle, ftd2_ds2_fws[device->firmware_id - 1], ftd2_ds2_sizes[device->firmware_id - 1])) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "FPGA config failed"); + return false; + } + + FT_STATUS ftStatus; + WORD val=0; + ftStatus = FT_ReadEE(capture_data->handle, 1, &val); + if(FT_FAILED(ftStatus) || (val != 0x0403)) { //=85A8: something went wrong (fpga is configured but FT chip detected wrong eeprom size) + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "EEPROM read error"); + return false; + } + + /* + WORD Firmware = 0; + WORD Hardware = 0; + ftStatus = FT_ReadEE(capture_data->handle, 0x10, &Firmware); + if(FT_FAILED(ftStatus)) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "Firmware ID read error"); + return false; + } + ftStatus = FT_ReadEE(capture_data->handle, 0x11, &Hardware); + if(FT_FAILED(ftStatus)) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "Hardware ID read error"); + return false; + } + */ + + ftStatus = FT_SetBitMode(capture_data->handle, 0x00, FT_BITMODE_SYNC_FIFO); //to FIFO mode. This takes over port B, pins shouldn't get modified though + if(FT_FAILED(ftStatus)) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "Bitmode setup error"); + return false; + } + + ftStatus = FT_SetTimeouts(capture_data->handle, 50, 50); + if(FT_FAILED(ftStatus)) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "Timeouts setup error"); + return false; + } + + static const uint8_t cmd[]={ 0x80, 0x01 }; //enable capture + if(!full_ftd2_write(capture_data->handle, cmd, sizeof(cmd))) { + preemptive_close_connection(capture_data); + capture_error_print(print_failed, capture_data, "Capture enable error"); + return false; + } + + return true; +} + +/* +//DS frame: +// video (top|bottom interleaved RGB16), 256*192*2*2 bytes +// audio (16bit stereo, approx. 32729 Hz), 0x8F4 bytes +// actual number of samples varies, audio is padded with 0x4321 +FrameResult DS2Device::getFrame(DSFrame *frame) { + enum { + FRAMESIZE = ndsWidth*ndsHeight*2*2, + S_MAX = 0x47A/2, //#audiosamples(dword) aligns frame to 510 bytes.. + READSIZE = FRAMESIZE + S_MAX*4, + MAX = READSIZE/2, //(usbFifoWords) + }; + + FT_STATUS status; + DWORD bytesIn; + + status=FT_Read(handle, frame->raw, READSIZE, &bytesIn); + if(status!=FT_OK) { + DEBUG_TRACE("err %d\n",status); //disconnected = FT_IO_ERROR + usb_close(); + return FRAME_ERROR; + } + if(bytesIn!=READSIZE) { //stream stopped (lid closed / DS is off) + DEBUG_TRACE("."); + return FRAME_SKIP; + } + + //check sync + uint16_t *src=(uint16_t*)frame->raw; + int samples; + if(src[0]==0x4321) { + samples=0; + while(samplesraw, samples*2, &bytesIn); + DEBUG_TRACE("resync(%d)",samples); + return FRAME_SKIP; + } else if(src[MAX-1]!=0x4321) { + samples=0; + while(samplesraw, samples*2, &bytesIn); + DEBUG_TRACE("resync(%d)",samples); + return FRAME_SKIP; + } else { + //DEBUG_TRACE("."); + } + + //count audio samples + samples=0; + uint32_t *a=(uint32_t*)(frame->raw+FRAMESIZE); + while(samples548) + DEBUG_TRACE("%d?",samples); + + frame->audioBuf=a; + frame->audioSamples=samples; + + return FRAME_OK; +} +*/ + +static bool synchronization_check(CaptureData* capture_data, uint16_t* data_buffer) { + //check sync + DWORD bytesIn; + int samples = 0; + int size_words = get_capture_size(capture_data) / 2; + if(data_buffer[0] == FTD2_OLDDS_SYNCH_VALUES) { + while((samples < size_words) && (data_buffer[samples] == FTD2_OLDDS_SYNCH_VALUES)) + samples++; + // Do a read to re-synchronize + FT_Read(capture_data->handle, data_buffer, samples * 2, &bytesIn); + return false; + } else if(data_buffer[size_words - 1] != FTD2_OLDDS_SYNCH_VALUES) { + // Find the first spot the padding is present + while((samples < size_words) && (data_buffer[samples] != FTD2_OLDDS_SYNCH_VALUES)) + samples++; + while((samples < size_words) && (data_buffer[samples] == FTD2_OLDDS_SYNCH_VALUES)) + samples++; + // Do a read to re-synchronize + FT_Read(capture_data->handle, data_buffer, samples * 2, &bytesIn); + return false; + } + return true; +} + +static inline void data_output_update(CaptureReceived* buffer, CaptureData* capture_data, int read_amount, std::chrono::time_point &base_time) { + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - base_time; + base_time = curr_time; + capture_data->data_buffers.WriteToBuffer(buffer, read_amount, diff.count(), &capture_data->status.device); + + if(capture_data->status.cooldown_curr_in) + capture_data->status.cooldown_curr_in = capture_data->status.cooldown_curr_in - 1; + // Signal that there is data available + capture_data->status.video_wait.unlock(); + capture_data->status.audio_wait.unlock(); } void ftd2_capture_main_loop(CaptureData* capture_data) { + int inner_curr_in = 0; + FT_STATUS ftStatus; + auto clock_start = std::chrono::high_resolution_clock::now(); + CaptureReceived* data_buffer = new CaptureReceived[2]; + DWORD bytesIn; + int read_size = get_capture_size(capture_data); + + while (capture_data->status.connected && capture_data->status.running) { + ftStatus = FT_Read(capture_data->handle, data_buffer, read_size, &bytesIn); + if(FT_FAILED(ftStatus)) { + capture_error_print(true, capture_data, "Disconnected: Read failed"); + return; + } + if(bytesIn < read_size) + continue; + if(!synchronization_check(capture_data, (uint16_t*)data_buffer)) + continue; + data_output_update(data_buffer, capture_data, bytesIn, clock_start); + } } void ftd2_capture_cleanup(CaptureData* capture_data) { + if(FT_Close(capture_data->handle)) { + capture_error_print(true, capture_data, "Disconnected: Close failed"); + } } diff --git a/source/conversions.cpp b/source/conversions.cpp index 50aadd7..9ee52ce 100755 --- a/source/conversions.cpp +++ b/source/conversions.cpp @@ -25,7 +25,6 @@ struct deinterleaved_ds_pixels_le { uint64_t fourth : 16; }; -#ifdef USE_FTD3 static inline void convertVideoToOutputChunk(RGB83DSVideoInputData *p_in, VideoOutputData *p_out, int iters, int start_in, int start_out) { memcpy(&p_out->screen_data[start_out], &p_in->screen_data[start_in], iters * 3); } @@ -62,14 +61,7 @@ static void ftd3_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_ convertVideoToOutputChunk_3D(&p_in->ftd3_received_3d.video_in, p_out, IN_VIDEO_WIDTH_3DS_3D, (((i * 3) + 1) * IN_VIDEO_WIDTH_3DS_3D) + IN_VIDEO_NO_BOTTOM_SIZE_3DS_3D, TOP_SIZE_3DS + (i * IN_VIDEO_WIDTH_3DS_3D)); } } -#endif -#ifdef USE_FTD2 -static void ftd2_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out) { -} -#endif - -#ifdef USE_DS_3DS_USB static inline void usb_oldDSconvertVideoToOutputHalfLineDirectOptLE(USBOldDSCaptureReceived *p_in, VideoOutputData *p_out, int input_halfline, int output_halfline) { //de-interleave pixels deinterleaved_ds_pixels_le* out_ptr_top = (deinterleaved_ds_pixels_le*)p_out->screen_data; @@ -162,6 +154,10 @@ static void usb_oldDSconvertVideoToOutput(USBOldDSCaptureReceived *p_in, VideoOu #endif } +static void ftd2_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, const bool is_big_endian) { + usb_oldDSconvertVideoToOutput(&p_in->usb_received_old_ds, p_out, is_big_endian); +} + static void usb_3DSconvertVideoToOutput(USB3DSCaptureReceived *p_in, VideoOutputData *p_out) { memcpy(p_out->screen_data, p_in->video_in.screen_data, IN_VIDEO_HEIGHT_3DS * IN_VIDEO_WIDTH_3DS * 3); } @@ -174,9 +170,7 @@ static void usb_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_o else usb_oldDSconvertVideoToOutput(&p_in->usb_received_old_ds, p_out, is_big_endian); } -#endif -#ifdef USE_IS_NITRO_USB static void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureScreensType capture_type) { int num_pixels = usb_is_nitro_get_video_in_size(capture_type) / 3; int out_start_pos = 0; @@ -189,7 +183,6 @@ static void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutput memset(p_out->screen_data[out_clear_pos], 0, num_pixels * 3); memcpy(p_out->screen_data[out_start_pos], p_in->is_nitro_capture_received.video_in.screen_data, num_pixels * 3); } -#endif bool convertVideoToOutput(VideoOutputData *p_out, const bool is_big_endian, CaptureDataSingleBuffer* data_buffer, CaptureStatus* status) { CaptureReceived *p_in = &data_buffer->capture_buf; @@ -203,7 +196,7 @@ bool convertVideoToOutput(VideoOutputData *p_out, const bool is_big_endian, Capt #endif #ifdef USE_FTD2 if(chosen_device->cc_type == CAPTURE_CONN_FTD2) { - ftd2_convertVideoToOutput(p_in, p_out); + ftd2_convertVideoToOutput(p_in, p_out, is_big_endian); converted = true; } #endif @@ -237,6 +230,11 @@ bool convertAudioToOutput(std::int16_t *p_out, uint64_t n_samples, const bool is #endif #ifdef USE_FTD2 if(status->device.cc_type == CAPTURE_CONN_FTD2) { + base_ptr = (uint8_t*)p_in->ftd2_received_old_ds.audio_data; + int real_samples = 0; + while((real_samples < n_samples) && (((uint32_t*)base_ptr)[real_samples] != (FTD2_OLDDS_SYNCH_VALUES | (FTD2_OLDDS_SYNCH_VALUES << 16)))) + real_samples++; + n_samples = real_samples; } #endif #ifdef USE_DS_3DS_USB diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 4a9abf9..4ab3f44 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -26,3 +26,16 @@ add_custom_command( POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_BINARY_DIR} VERBATIM ) + +set(OUTPUT_NAME CMakePrepareRenameSymbols) +project(${OUTPUT_NAME} VERSION 1.0.0 LANGUAGES CXX) + +add_executable(${OUTPUT_NAME} prepare_rename_symbols_list.cpp) +target_compile_features(${OUTPUT_NAME} PRIVATE cxx_std_17) + +add_custom_command( + TARGET ${OUTPUT_NAME} + COMMENT "Copy Output" + POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${CMAKE_BINARY_DIR} + VERBATIM +) diff --git a/tools/prepare_rename_symbols_list.cpp b/tools/prepare_rename_symbols_list.cpp new file mode 100644 index 0000000..d7a3325 --- /dev/null +++ b/tools/prepare_rename_symbols_list.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +#if (!defined(_MSC_VER)) || (_MSC_VER > 1916) +#include +#else +#include +#endif + +using namespace std; + +int main(int argc, char *argv[]) { + if(argc < 4) { + cout << "Usage: " << argv[0] << " text_file prefix new_file define_file" << endl; + return -1; + } + + ifstream input(argv[1], ios::in); + if(!input) { + cout << "Couldn't open input file!" << endl; + return -2; + } + vector file_lines; + string single_str = ""; + while (getline(input, single_str)) + file_lines.push_back(single_str); + + input.close(); + + string prefix(argv[2]); + + ofstream output(argv[3], ios::out); + if(!output) { + cout << "Couldn't open output file!" << endl; + return -2; + } + for(int i = 0; i < file_lines.size(); i++) + output << file_lines[i] << " " << prefix << file_lines[i] << endl; + + output.close(); + + if(argc > 4) { + ofstream output_define(argv[4], ios::out); + if(!output_define) { + cout << "Couldn't open output define file!" << endl; + return -2; + } + for(int i = 0; i < file_lines.size(); i++) + output_define << "#define " << file_lines[i] << " " << prefix << file_lines[i] << endl; + + output_define.close(); + } + + return 0; +} diff --git a/usb_rules/50-ftd2xx.rules b/usb_rules/50-ftd2xx.rules new file mode 100644 index 0000000..79572b6 --- /dev/null +++ b/usb_rules/50-ftd2xx.rules @@ -0,0 +1,4 @@ +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", PROGRAM="/bin/sh -c '\ + echo -n $id:1.0 > /sys/bus/usb/drivers/ftdi_sio/unbind;\ + echo -n $id:1.1 > /sys/bus/usb/drivers/ftdi_sio/unbind\ +'" diff --git a/usb_rules/51-ftd2xx.rules b/usb_rules/51-ftd2xx.rules new file mode 100644 index 0000000..90848e2 --- /dev/null +++ b/usb_rules/51-ftd2xx.rules @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="0666"