diff --git a/.github/workflows/build-cmake.yml b/.github/workflows/build-cmake.yml new file mode 100644 index 0000000..869fbbe --- /dev/null +++ b/.github/workflows/build-cmake.yml @@ -0,0 +1,90 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: Build with CMake + +on: + push: + branches: [ "main", "dev*" ] + pull_request: + branches: [ "main", "dev*" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following configurations: + # 1. + # 2. + # 3. + # 4. + ## + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + - os: macos-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + - os: macos-latest + c_compiler: cl + - os: macos-latest + c_compiler: gcc + + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Set Vars + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + echo "artifact-name=libtslitex-${{ matrix.os }}-${{ matrix.c_compiler }}-artifacts" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --clean-first --config ${{ matrix.build_type }} + + # - name: Test + # working-directory: ${{ steps.strings.outputs.build-output-dir }} + # # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + # run: ctest --build-config ${{ matrix.build_type }} + + - uses: actions/upload-artifact@v4 + with: + name: ${{ steps.strings.outputs.artifact-name }} + path: ${{ steps.strings.outputs.build-output-dir }}/artifacts diff --git a/.github/workflows/build-python-bindings.yml b/.github/workflows/build-python-bindings.yml new file mode 100644 index 0000000..b436d53 --- /dev/null +++ b/.github/workflows/build-python-bindings.yml @@ -0,0 +1,71 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: Build Python Bindings with CMake + +on: + push: + branches: [ "main", "dev*" ] + pull_request: + branches: [ "main", "dev*" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following configurations: + # 1. + # 2. + # 3. + # 4. + ## + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + build_type: [Release] # Note, debug builds are not generated as they require the debug version of Python to be installed. + platform: [auto] + arch: [auto64] + + + steps: + - uses: actions/checkout@v4 + with: + fetch-tags: true + fetch-depth: 0 + + - name: Setup Python Environment + uses: actions/setup-python@v5 + with: + python-version: '3.13' + cache: 'pip' + - run: pip install -r ${{ github.workspace }}/requirements.txt + + - name: Set Vars + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build/" >> "$GITHUB_OUTPUT" + echo "build-wheels-dir=${{ github.workspace }}/build/bindings/python/wheelhouse/" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DPY_PLATFORM=${{ matrix.platform }} + -DPY_ARCH=${{ matrix.arch }} + -S ${{ github.workspace }} + + - name: Build Library + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --clean-first --config ${{ matrix.build_type }} -t tslitex_static + + - name: Build Python Wheels + # Build Wheel from Cython code. + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} -t PyWheels + + - uses: actions/upload-artifact@v4 + with: + name: wheel-${{ matrix.os }}-${{ matrix.arch }} + path: ${{ steps.strings.outputs.build-wheels-dir }}/*.whl diff --git a/.gitignore b/.gitignore index 0151929..a811f0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build* +build/* artifacts/* .vscode .venv \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 53781f8..96143c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,65 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.22...4.1) project(libtslitex) +# Set Version +include(version.cmake) + enable_language(C) set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD_REQUIRED ON) # error if compiler doesn't support c17 set(CMAKE_C_EXTENSIONS ON) +if(MSVC) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /std:c17 /experimental:c11atomics") +endif(MSVC) enable_language(CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # error if compiler doesn't support c++17 set(CMAKE_CXX_EXTENSIONS ON) +################################ +# Project Dependencies +################################ +include(FetchContent) + +# json-c Library +FetchContent_Declare( + json_c + GIT_REPOSITORY https://github.com/json-c/json-c.git + GIT_TAG 2372e9518e6ba95b48d37ec162bc7d93b297b52f +) +set(JSON_C_BUILD_APPS OFF) +set(JSON_C_BUILD_SHARED_LIBS OFF) +FetchContent_MakeAvailable(json_c) + +# zlib Library +FetchContent_Declare( + zlib + GIT_REPOSITORY https://github.com/madler/zlib.git + GIT_TAG v1.3.1 +) + +set(ZLIB_BUILD_TESTING OFF) +set(ZLIB_BUILD_SHARED OFF) +set(ZLIB_INSTALL OFF) +FetchContent_MakeAvailable(zlib) + + + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/artifacts/libtslitex") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/artifacts/libtslitex") -add_definitions(-DUNICODE -D_UNICODE) +option(ENABLE_FACTORY_PROVISIONING "Include Factory Provisioning Functions" OFF) + +if(ENABLE_FACTORY_PROVISIONING) + add_compile_definitions(FACTORY_PROVISIONING_API) +endif() -add_definitions(-DEN_LOGGING) +add_compile_definitions(UNICODE _UNICODE) + +add_compile_definitions(EN_LOGGING) add_compile_definitions("$<$:EN_LOGGING_DEBUG>") @@ -33,6 +74,7 @@ set(TS_SOURCES src/spi.c src/afe.c src/adc.c + src/events.c src/hmcad15xx.c src/lmh6518.c src/mcp4728.c @@ -43,6 +85,7 @@ set(TS_SOURCES src/samples.c src/spiflash.c src/ts_channel.c + src/ts_data.c src/ts_fw_manager.c src/thunderscope.c ) @@ -53,6 +96,7 @@ set(TS_HEADERS src/spi.h src/adc.h src/afe.h + src/events.h src/mcp_clkgen.h src/mcp_zl3026x.h src/platform.h @@ -63,6 +107,7 @@ set(TS_HEADERS src/samples.h src/spiflash.h src/ts_channel.h + src/ts_data.h src/ts_fw_manager.h src/util.h ) @@ -80,13 +125,24 @@ add_library(tslitex SHARED ${TS_SOURCES} ${TS_LIB_HEADERS} ) - +set_target_properties(tslitex PROPERTIES VERSION ${${PROJECT_NAME}_VERSION}) set_target_properties(tslitex PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/$<0:> LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/$<0:> ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/$<0:>) -target_link_libraries(tslitex litepcie) +target_link_libraries(tslitex + PUBLIC + "$" + PRIVATE + json-c-static + zlibstatic +) + +if(APPLE) + target_link_libraries(tslitex PRIVATE "$") +endif() + target_include_directories(tslitex PUBLIC include) #### @@ -96,25 +152,37 @@ add_library(tslitex_static STATIC ${TS_SOURCES} ${TS_LIB_HEADERS} ) +set_target_properties(tslitex_static PROPERTIES VERSION ${${PROJECT_NAME}_VERSION}) set_target_properties(tslitex_static PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/$<0:> LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/$<0:> ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/$<0:>) - -#target_compile_definitions(tslitex_static tslitex_static_STATIC) + set_target_properties(tslitex_static PROPERTIES POSITION_INDEPENDENT_CODE 1) +target_link_libraries(tslitex_static + PUBLIC + "$" + PRIVATE + json-c-static + zlibstatic +) -target_link_libraries(tslitex_static litepcie) target_include_directories(tslitex_static PUBLIC include) +if(APPLE) + target_link_libraries(tslitex_static PRIVATE "$") +endif() + file(COPY ${TS_LIB_HEADERS} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/include) +#### +# EXAMPLE APPLICATIONS +#### add_subdirectory(example) #### # BINDINGS #### add_subdirectory(bindings/python) -add_dependencies(PyBindings tslitex_static litepcie) diff --git a/README.md b/README.md index 3dab365..3a5396f 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,11 @@ To build the python bindings, you must first have pipx and cython installed. The > pip install -r requirements.txt ``` -Now you can build the `PyBindings` target with cmake +Now you can build the `PyBindings` target with cmake. If you get an error about some module not being installed, recreate the cmake cache with `cmake --fresh ..` ```bash > cd build/ +> cmake --fresh .. > cmake --build . -t PyBindings ``` diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt index 5d2849c..6083914 100644 --- a/bindings/python/CMakeLists.txt +++ b/bindings/python/CMakeLists.txt @@ -1,11 +1,12 @@ -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.22...4.1) find_package( Python COMPONENTS Interpreter Development.Module - REQUIRED ) +if(Python_FOUND) + add_custom_command( OUTPUT tslitex/tslitex.c COMMAND Python::Interpreter -m cython @@ -14,12 +15,39 @@ add_custom_command( VERBATIM ) -set(PY_BIND_LIBS tslitex_static litepcie) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/lib/include/thunderscope.h + COMMAND ${CMAKE_COMMAND} -E copy_directory + "$" + "$" + "$" + "$" + "${CMAKE_CURRENT_BINARY_DIR}/lib" + DEPENDS tslitex_static +) + +add_custom_target(pydeps + DEPENDS + tslitex/tslitex.c + ${CMAKE_CURRENT_BINARY_DIR}/lib/include/thunderscope.h + tslitex_static +) + +set(PY_BIND_LIBS tslitex_static) + if(WIN32) - list(APPEND PY_BIND_LIBS setupapi) +list(APPEND PY_BIND_LIBS setupapi) +list(APPEND PY_BIND_LIBS litepcie json-c-static zlibstatic) +else() +list(APPEND PY_BIND_LIBS litepcie json-c z) endif() list(JOIN PY_BIND_LIBS "\", \"" PY_EXT_LIBS) +set(PY_WHL_VERSION_STRING "${${PROJECT_NAME}_VERSION}") +if (NOT ${${PROJECT_NAME}_VERSION_AHEAD} EQUAL "0") + set(PY_WHL_VERSION_STRING "${PY_WHL_VERSION_STRING}.dev${${PROJECT_NAME}_VERSION_AHEAD}") +endif() + configure_file(pyproject.toml.in pyproject.toml @ONLY) configure_file(__init__.py.in tslitex/__init__.py COPYONLY) @@ -27,6 +55,22 @@ add_custom_target(PyBindings COMMAND Python::Interpreter -m pipx run build --wheel . WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMENT "Building python whl" - DEPENDS tslitex/tslitex.c + DEPENDS pydeps ) +if (NOT DEFINED PY_PLATFORM) + set(PY_PLATFORM "auto") +endif() +if (NOT DEFINED PY_ARCH) + set(PY_ARCH "auto64") +endif() + +add_custom_target(PyWheels + COMMAND Python::Interpreter -m cibuildwheel --platform ${PY_PLATFORM} --archs ${PY_ARCH} --config-file pyproject.toml + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Building all Python wheels" + DEPENDS pydeps + ) +else() +message(STATUS "Python bindings not available: Python not found") +endif() diff --git a/bindings/python/pyproject.toml.in b/bindings/python/pyproject.toml.in index da621e1..2274bb8 100644 --- a/bindings/python/pyproject.toml.in +++ b/bindings/python/pyproject.toml.in @@ -3,15 +3,15 @@ requires = ["cython", "numpy", "setuptools"] build-backend = "setuptools.build_meta" [project] -name = "pytslitex" -version = "0.0.1" -license = {text = "BSD-2-Clause"} +name = "tslitex" +version = "@PY_WHL_VERSION_STRING@" authors = [ {name = "Nate Meyer", email = "nate.devel@gmail.com"}, ] +dependencies = ["numpy"] [tool.setuptools] packages = ["tslitex"] ext-modules = [ - { name = "tslitex.tslitex", sources = ["tslitex/tslitex.c"], libraries = ["@PY_EXT_LIBS@"], library-dirs = ["@CMAKE_ARCHIVE_OUTPUT_DIRECTORY@"], include-dirs = ["@CMAKE_LIBRARY_OUTPUT_DIRECTORY@/include"]} + { name = "tslitex.tslitex", sources = ["tslitex/tslitex.c"], libraries = ["@PY_EXT_LIBS@"], library-dirs = ["lib"], include-dirs = ["lib/include"]} ] diff --git a/bindings/python/ts_calibration.pxd b/bindings/python/ts_calibration.pxd index b0373a3..0e0905d 100644 --- a/bindings/python/ts_calibration.pxd +++ b/bindings/python/ts_calibration.pxd @@ -13,8 +13,8 @@ cdef extern from "ts_calibration.h": ctypedef void* tsHandle_t cdef struct tsChannelCalibration_s: - int32_t buffer_mV - int32_t bias_mV + int32_t buffer_uV + int32_t bias_uV int32_t attenuatorGain1M_mdB int32_t attenuatorGain50_mdB int32_t bufferGain_mdB @@ -23,31 +23,41 @@ cdef extern from "ts_calibration.h": int32_t preampHighGainError_mdB int32_t preampAttenuatorGain_mdB[11] int32_t preampOutputGainError_mdB - int32_t preampLowOffset_mV - int32_t preampHighOffset_mV + int32_t preampLowOffset_uV + int32_t preampHighOffset_uV int32_t preampInputBias_uA ctypedef tsChannelCalibration_s tsChannelCalibration_t - cdef struct tsChannelCtrl_e: + cdef struct tsAdcCalibration_s: + uint8_t branchFineGain[8] + + ctypedef tsAdcCalibration_s tsAdcCalibration_t + + cdef struct tsChannelCtrl_s: uint8_t atten uint8_t term uint8_t dc_couple - uint16_t dac uint8_t dpot + uint16_t dac uint8_t pga_high_gain uint8_t pga_atten uint8_t pga_bw - ctypedef tsChannelCtrl_e tsChannelCtrl_t + ctypedef tsChannelCtrl_s tsChannelCtrl_t cdef struct tsScopeCalibration_s: tsChannelCalibration_t afeCal[4] + tsAdcCalibration_t adcCal ctypedef tsScopeCalibration_s tsScopeCalibration_t - int32_t thunderscopeCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t* cal) + int32_t thunderscopeChanCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t* cal) + + int32_t thunderscopeChanCalibrationGet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t* cal) + + int32_t thunderscopeAdcCalibrationSet(tsHandle_t ts, tsAdcCalibration_t* cal) - int32_t thunderscopeCalibrationGet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t* cal) + int32_t thunderscopeAdcCalibrationGet(tsHandle_t ts, tsAdcCalibration_t* cal) - int32_t thunderscopeCalibrationManualCtrl(tsHandle_t ts, uint32_t channel, tsChannelCtrl_t ctrl) + int32_t thunderscopeCalibrationManualCtrl(tsHandle_t ts, uint32_t channel, tsChannelCtrl_t* ctrl) diff --git a/bindings/python/tslitex.pxd b/bindings/python/tslitex.pxd index 279e2a7..ce1c3d4 100644 --- a/bindings/python/tslitex.pxd +++ b/bindings/python/tslitex.pxd @@ -6,7 +6,7 @@ # # Copyright (C) 2025 / Nate Meyer / nate.devel@gmail.com -from libc.stdint cimport uint32_t, int32_t, uint8_t +from libc.stdint cimport uint32_t, int32_t, uint8_t, uint64_t cdef extern from "thunderscope.h": @@ -34,15 +34,22 @@ cdef extern from "thunderscope.h": cdef struct tsDeviceInfo_s: uint32_t device_id + uint32_t hw_id + uint32_t gw_id + uint32_t litex + uint32_t board_rev char device_path[256] char identity[256] char serial_number[256] + char build_config[256] + char build_date[256] + char mfg_signature[256] ctypedef tsDeviceInfo_s tsDeviceInfo_t cdef struct tsChannelParam_s: - uint32_t volt_scale_mV - int32_t volt_offset_mV + uint32_t volt_scale_uV + int32_t volt_offset_uV uint32_t bandwidth uint8_t coupling uint8_t term @@ -56,6 +63,8 @@ cdef extern from "thunderscope.h": uint32_t vcc_int uint32_t vcc_aux uint32_t vcc_bram + uint8_t frontend_power_good + uint8_t acq_power_good ctypedef sysHealth_s sysHealth_t @@ -66,16 +75,43 @@ cdef extern from "thunderscope.h": uint32_t adc_lost_buffer_count uint32_t flags uint8_t adc_state + uint8_t adc_sync uint8_t power_state uint8_t pll_state uint8_t afe_state + uint8_t local_osc_clk + uint8_t ref_in_clk + uint8_t pll_lock + uint8_t pll_low + uint8_t pll_high + uint8_t pll_alt sysHealth_t sys_health ctypedef tsScopeState_s tsScopeState_t + cpdef enum tsSyncMode_e: + TS_SYNC_DISABLED + TS_SYNC_OUT + TS_SYNC_IN + + ctypedef tsSyncMode_e tsSyncMode_t + + cpdef enum tsEventType_e: + TS_EVT_NONE + TS_EVT_HOST_SW + TS_EVT_EXT_SYNC + + ctypedef tsEventType_e tsEventType_t + + cdef struct tsEvent_s: + tsEventType_t ID + uint64_t event_sample + + ctypedef tsEvent_s tsEvent_t + int32_t thunderscopeListDevices(uint32_t devIndex, tsDeviceInfo_t* info) - tsHandle_t thunderscopeOpen(uint32_t devIdx) + tsHandle_t thunderscopeOpen(uint32_t devIdx, bint skip_init) int32_t thunderscopeClose(tsHandle_t ts) @@ -87,8 +123,24 @@ cdef extern from "thunderscope.h": int32_t thunderscopeSampleModeSet(tsHandle_t ts, uint32_t rate, uint32_t resolution) + int32_t thunderscopeSampleInterruptRate(tsHandle_t ts, uint32_t interrupt_rate) + int32_t thunderscopeDataEnable(tsHandle_t ts, uint8_t enable) - int32_t thunderscopeRead(tsHandle_t ts, uint8_t* buffer, uint32_t len) + int32_t thunderscopeRead(tsHandle_t ts, uint8_t* buffer, uint32_t len) nogil + + int32_t thunderscopeReadCount(tsHandle_t ts, uint8_t* buffer, uint32_t len, uint64_t* count) nogil + + int32_t thunderscopeExtSyncConfig(tsHandle_t ts, tsSyncMode_t mode) + + int32_t thunderscopeEventSyncAssert(tsHandle_t ts) nogil + + int32_t thunderscopeEventGet(tsHandle_t ts, tsEvent_t* evt) + + int32_t thunderscopeFwUpdate(tsHandle_t ts, const char* bitstream, uint32_t len) nogil + + int32_t thunderscopeUserDataRead(tsHandle_t ts, char* buffer, uint32_t offset, uint32_t readLen) nogil + + int32_t thunderscopeUserDataWrite(tsHandle_t ts, const char* buffer, uint32_t offset, uint32_t writeLen) nogil - int32_t thunderscopeFwUpdate(tsHandle_t ts, char* bitstream, uint32_t len) + int32_t thunderscopeGetFwProgress(tsHandle_t ts, uint32_t* progress) diff --git a/bindings/python/tslitex.pyx b/bindings/python/tslitex.pyx index 6721258..4938de2 100644 --- a/bindings/python/tslitex.pyx +++ b/bindings/python/tslitex.pyx @@ -57,12 +57,12 @@ cdef class Channel: cdef int32_t retVal = tslitex.thunderscopeChannelConfigGet(self.dev, self._channel, &self._params) if retVal != tslitex.TS_STATUS_OK: raise ValueError(f"Failed to retrieve Channel {self._channel} parameters") - return self._params.volt_scale_mV / 1000.0 + return self._params.volt_scale_uV / 1000000.0 @VoltScale.setter def VoltScale(self, volts: float): - self._params.volt_scale_mV = (volts*1000) + self._params.volt_scale_uV = (volts*1000000) cdef int32_t retVal retVal = tslitex.thunderscopeChannelConfigSet(self.dev, self._channel, &self._params) if retVal != tslitex.TS_STATUS_OK: @@ -73,12 +73,12 @@ cdef class Channel: cdef int32_t retVal = tslitex.thunderscopeChannelConfigGet(self.dev, self._channel, &self._params) if retVal != tslitex.TS_STATUS_OK: raise ValueError(f"Failed to retrieve Channel {self._channel} parameters") - return self._params.volt_offset_mV / 1000.0 + return self._params.volt_offset_uV / 1000000.0 @VoltOffset.setter def VoltOffset(self, volts: float): - self._params.volt_offset_mV = (volts*1000) + self._params.volt_offset_uV = (volts*1000000) cdef int32_t retVal retVal = tslitex.thunderscopeChannelConfigSet(self.dev, self._channel, &self._params) if retVal != tslitex.TS_STATUS_OK: @@ -138,20 +138,20 @@ cdef class Channel: def ManualCtrl(self, params: ts_calibration.tsChannelCtrl_t): cdef int32_t retVal - retVal = ts_calibration.thunderscopeCalibrationManualCtrl(self.dev, self._channel, params) + retVal = ts_calibration.thunderscopeCalibrationManualCtrl(self.dev, self._channel, ¶ms) if retVal != tslitex.TS_STATUS_OK: raise ValueError(f"Failed to manually set Channel {self._channel} Parameters {params}") def CalibrationSet(self, calibration: ts_calibration.tsChannelCalibration_t): cdef int32_t retVal - retVal = ts_calibration.thunderscopeCalibrationSet(self.dev, self._channel, &calibration) + retVal = ts_calibration.thunderscopeChanCalibrationSet(self.dev, self._channel, &calibration) if retVal != tslitex.TS_STATUS_OK: raise ValueError(f"Failed to set Channel {self._channel} Calibration {calibration}") def Calibration(self): cdef int32_t retVal cdef ts_calibration.tsChannelCalibration_t calibration - retVal = ts_calibration.thunderscopeCalibrationSet(self.dev, self._channel, &calibration) + retVal = ts_calibration.thunderscopeChanCalibrationSet(self.dev, self._channel, &calibration) if retVal != tslitex.TS_STATUS_OK: raise ValueError(f"Failed to get Channel {self._channel} Calibration ({retVal})") return calibration @@ -162,15 +162,17 @@ cdef class Thunderscope: cdef uint8_t _enable cdef tslitex.tsHandle_t _tsHandle cdef public object channel + cdef tslitex.tsSyncMode_t _ext_sync - def __cinit__(self, dev_idx: int): + def __cinit__(self, dev_idx: int, skip_init:bool = False): self.channel = [] - def __init__(self, dev_idx: int): + def __init__(self, dev_idx: int, skip_init:bool = False): self._sample_rate = 1000000000 self._sample_mode = 256 self._enable = 0 - self._tsHandle = tslitex.thunderscopeOpen(dev_idx) + self._ext_sync = tslitex.tsSyncMode_t.TS_SYNC_DISABLED + self._tsHandle = tslitex.thunderscopeOpen(dev_idx, skip_init) if self._tsHandle == NULL: raise ValueError(f"Failed to Open Thunderscope Device {dev_idx}", dev_idx) for ch in range(4): @@ -187,12 +189,55 @@ cdef class Thunderscope: tslitex.thunderscopeStatusGet(self._tsHandle, &status_vals) return status_vals + def AdcCalibrationSet(self, calibration: ts_calibration.tsAdcCalibration_t): + cdef int32_t retVal + retVal = ts_calibration.thunderscopeAdcCalibrationSet(self._tsHandle, &calibration) + if retVal != tslitex.TS_STATUS_OK: + raise ValueError(f"Failed to set ADC Calibration {calibration}") + + def AdcCalibration(self): + cdef int32_t retVal + cdef ts_calibration.tsAdcCalibration_t calibration + retVal = ts_calibration.thunderscopeAdcCalibrationGet(self._tsHandle, &calibration) + if retVal != tslitex.TS_STATUS_OK: + raise ValueError(f"Failed to get ADC Calibration ({retVal})") + return calibration + def firmwareUpdate(self, bitfile): if type(bitfile) is not bytes: raise TypeError(f"bitfile arg must be 'bytes' type") cdef uint32_t file_len = len(bitfile) cdef char* pFile = bitfile - return tslitex.thunderscopeFwUpdate(self._tsHandle, pFile, file_len) + cdef int32_t status + with nogil: + status = tslitex.thunderscopeFwUpdate(self._tsHandle, pFile, file_len) + return status + + def userDataRead(self, datafile not None, datalen: int, offset: int): + cdef uint32_t max_len = datalen + cdef char* pFile = datafile + cdef int32_t status + cdef uint32_t user_offset = offset + with nogil: + status = tslitex.thunderscopeUserDataRead(self._tsHandle, pFile, user_offset, max_len) + return status + + def userDataWrite(self, datafile, offset:int): + if type(datafile) is not bytes: + raise TypeError(f"bitfile arg must be 'bytes' type") + cdef uint32_t file_len = len(datafile) + cdef char* pFile = datafile + cdef uint32_t offs = offset + cdef int32_t status + with nogil: + status = tslitex.thunderscopeUserDataWrite(self._tsHandle, pFile, offs, file_len) + return status + + @property + def firmwareProgress(self): + cdef uint32_t progress + tslitex.thunderscopeGetFwProgress(self._tsHandle, &progress) + return progress @property def SampleRate(self): @@ -210,17 +255,54 @@ cdef class Thunderscope: @SampleResolution.setter def SampleResolution(self, resolution: int): - assert resolution is 256, f"Support for 8-bit mode only" + assert resolution in [256, 4096], f"Unsupported Resolution" self._sample_resolution = resolution retval = tslitex.thunderscopeSampleModeSet(self._tsHandle, self._sample_rate, self._sample_resolution) + @property + def UpdateRate(self): + return self._interrupt_rate + + @UpdateRate.setter + def UpdateRate(self, rate : int): + assert rate > 0, f"Invalid Update Rate" + self._interrupt_rate = rate + retval = tslitex.thunderscopeSampleInterruptRate(self._tsHandle, + rate) + + @property + def SyncMode(self): + return self._ext_sync + + @SyncMode.setter + def SyncMode(self, mode : tslitex.tsSyncMode_t): + self._ext_sync = mode + tslitex.thunderscopeExtSyncConfig(self._tsHandle, mode) + + def Event(self): + cdef tslitex.tsEvent_t evt + tslitex.thunderscopeEventGet(self._tsHandle, &evt) + return (evt.ID, evt.event_sample) + + def EventTrigger(self): + with nogil: + tslitex.thunderscopeEventSyncAssert(self._tsHandle) + def Enable(self, enable: bool): cdef int32_t retVal = tslitex.thunderscopeDataEnable(self._tsHandle, enable) if retVal != tslitex.TS_STATUS_OK: raise ValueError(f"Unable to set Thunderscope Enable to {enable}") def Read(self, uint8_t[:] data not None, dataLen: int): - readLen = tslitex.thunderscopeRead(self._tsHandle, &data[0], dataLen) + cdef uint32_t dlen = dataLen + with nogil: + readLen = tslitex.thunderscopeRead(self._tsHandle, &data[0], dlen) return readLen + def ReadCount(self, uint8_t[:] data not None, dataLen: int): + cdef uint32_t dlen = dataLen + cdef uint64_t sample + with nogil: + readLen = tslitex.thunderscopeReadCount(self._tsHandle, &data[0], dlen, &sample) + return readLen, sample diff --git a/doc/calibration_data.md b/doc/calibration_data.md new file mode 100644 index 0000000..20e79b6 --- /dev/null +++ b/doc/calibration_data.md @@ -0,0 +1,37 @@ +## Calibration Data + +### API + +- Factory Provision Prepare +- Factory Provision Append Data +- Factory Provision Verify +- Factory Read Item + + +### Factory TLV's + +Each section is dentoted by a 32-bit TAG, 32-bit LENGTH, ASCII Data, and CRC32 Checksum. + +#### HWID + +- Tag: `HWID` +- Format: JSON +- Contents: + - ID Version + - "Serial Number" + - "Board Revision" + - "Build Config" + - "Build Date" + - "Mfg Signature" + + +#### FCAL + +- Tag: `FCAL` +- Format: JSON +- Contents: + - Calibration Version + - Calibration Station + - Calibration Date + - Calibration Data + diff --git a/doc/data/hwid.schema.json b/doc/data/hwid.schema.json new file mode 100644 index 0000000..938bab6 --- /dev/null +++ b/doc/data/hwid.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Hardware Identity Description", + "type": "object", + "properties": { + "version": { + "type": "integer", + "minimum": 1 + }, + "Serial Number": { + "type": "string", + "maxLength": 256 + }, + "Board Revision": { + "type": "number" + }, + "Build Config": { + "type": "string", + "maxLength": 256 + }, + "Build Date": { + "type": "string", + "maxLength": 256 + }, + "Mfg Signature": { + "type": "string", + "maxLength": 256 + } + }, + "required": [ + "version", + "Serial Number" + ] +} \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index da522c6..ca0aee0 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -4,11 +4,13 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/artifacts/example") add_executable(thunderscope_test thunderscope_test.cpp) target_link_libraries(thunderscope_test tslitex_static) - if(WIN32) target_link_libraries(thunderscope_test setupapi) +elseif(APPLE) + target_link_libraries(thunderscope_test "-framework IOKit") endif() + # Thunderscope FW Update CLI add_executable(thunderscope_fw thunderscope_fw.cpp) @@ -16,4 +18,15 @@ target_link_libraries(thunderscope_fw tslitex_static) if(WIN32) target_link_libraries(thunderscope_fw setupapi) +elseif(APPLE) + target_link_libraries(thunderscope_fw "-framework IOKit") +endif() + +# Thunderscope Cal CLI +add_executable(thunderscope_cal thunderscope_cal.cpp) + +target_link_libraries(thunderscope_cal tslitex_static) + +if(WIN32) + target_link_libraries(thunderscope_cal setupapi) endif() diff --git a/example/thunderscope-user.entitlements b/example/thunderscope-user.entitlements new file mode 100644 index 0000000..8b0674b --- /dev/null +++ b/example/thunderscope-user.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.developer.driverkit.userclient-access + + litex.litepcie + + + diff --git a/example/thunderscope_cal.cpp b/example/thunderscope_cal.cpp new file mode 100644 index 0000000..afbb661 --- /dev/null +++ b/example/thunderscope_cal.cpp @@ -0,0 +1,788 @@ +/** + * This file is part of libtslitex. + * + * Copyright (c) 2020-2021 Florent Kermarrec + * Copyright (c) 2022 Franck Jullien + * Copyright (c) 2020 Antmicro + * Copyright (c) 2024 John Simons + * Copyright (c) 2024 Nate Meyer + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#ifdef _WIN32 +#include +#endif + +#define OPTPARSE_IMPLEMENTATION +#include "optparse.h" + +#include "../src/mcp443x.h" +#include "../src/platform.h" +#include "../src/util.h" + +#include "liblitepcie.h" +#include "thunderscope.h" +#include "ts_calibration.h" + +#ifdef _WIN32 +#define FILE_FLAGS (FILE_ATTRIBUTE_NORMAL) +#else +#define FILE_FLAGS (O_RDWR) +#endif + +#define FLUSH() { char c; do { c = getchar(); } while(c != '\n' && c != 'EOF');} + +/* Variables */ +/*-----------*/ +static tsScopeCalibration_t calibration; + +/* Main */ +/*------*/ +// Functioning returning +// current time +auto now() +{ + return std::chrono::steady_clock::now(); +} + +// Function calculating sleep time +// with 500ms delay +auto awake_time() +{ + using std::chrono::operator"" ms; + return now() + 500ms; +} + +static void cal_get_average_value(tsHandle_t pTs, uint8_t chanBitmap, uint32_t numSamples, int8_t* pAvgResult) +{ + //Capture average + //Setup and Enable Channels + uint8_t* sampleBuffer = NULL; + tsChannelParam_t chConfig = {0}; + uint8_t numChan = 0; + uint8_t channel = 0; + while(chanBitmap > 0) + { + if(chanBitmap & 0x1) + { + thunderscopeChannelConfigGet(pTs, channel, &chConfig); + chConfig.active = 1; + thunderscopeChannelConfigSet(pTs, channel, &chConfig); + } + channel++; + chanBitmap >>= 1; + numChan++; + } + if(numChan > 2) + numChan = 4; + + sampleBuffer = (uint8_t*)calloc(((numSamples*numChan) + DMA_BUFFER_SIZE-1)/DMA_BUFFER_SIZE, DMA_BUFFER_SIZE); + uint64_t sampleLen = 0; + + uint64_t data_sum = 0; + //Start Sample capture + thunderscopeDataEnable(pTs, 1); + + if(sampleBuffer != NULL) + { + //Collect Samples + int32_t readRes = thunderscopeRead(pTs, sampleBuffer, numSamples); + if(readRes < 0) + { + printf("ERROR: Sample Get Buffers failed with %" PRIi32"\r\n", readRes); + } + else if(readRes != numSamples) + { + printf("WARN: Read returned different number of bytes, %" PRIu32 " / %" PRIu32 "\r\n", readRes, numSamples); + } + else + { + sampleLen = readRes; + } + } + + //Stop Samples + thunderscopeDataEnable(pTs, 0); + + if(sampleLen > 0) + { + int64_t avg[TS_NUM_CHANNELS] = {0}; + int64_t idx = 0; + while (idx < sampleLen) + { + avg[0] += sampleBuffer[idx++]; + if(numChan > 1) + { + avg[1] += sampleBuffer[idx++]; + if(numChan > 2) + { + avg[2] += sampleBuffer[idx++]; + avg[3] += sampleBuffer[idx++]; + } + } + } + pAvgResult[0] = (int8_t)(avg[0] / sampleLen); + pAvgResult[1] = (int8_t)(avg[1] / sampleLen); + pAvgResult[2] = (int8_t)(avg[2] / sampleLen); + pAvgResult[3] = (int8_t)(avg[3] / sampleLen); + + } + + //Disable channels + for(uint8_t i=0; i < TS_NUM_CHANNELS; i++) + { + thunderscopeChannelConfigGet(pTs, i, &chConfig); + chConfig.active = 0; + thunderscopeChannelConfigSet(pTs, i, &chConfig); + } + free(sampleBuffer); +} + +static void cal_get_interleaved_samples(tsHandle_t pTs, uint32_t numSamples, int64_t branchSum[8]) +{ + //Capture average + //Setup and Enable Channels + int8_t* sampleBuffer = NULL; + + sampleBuffer = (int8_t*)calloc(DMA_BUFFER_SIZE, (numSamples + DMA_BUFFER_SIZE-1)/DMA_BUFFER_SIZE); + int64_t sampleLen = 0; + + //Start Sample capture + thunderscopeDataEnable(pTs, 1); + + if(sampleBuffer != NULL) + { + //Collect Samples + int32_t readRes = thunderscopeRead(pTs, (uint8_t*)sampleBuffer, numSamples); + if(readRes < 0) + { + printf("ERROR: Sample Get Buffers failed with %" PRIi32 "\r\n", readRes); + } + else if(readRes != numSamples) + { + printf("WARN: Read returned different number of bytes, %" PRIu32 " / %" PRIu32 "\r\n", readRes, numSamples); + } + else + { + sampleLen = readRes; + } + } + + //Stop Samples + thunderscopeDataEnable(pTs, 0); + + uint64_t idx = 0; + while (idx < sampleLen) + { + //Branch Order from HMCAD1520 Datasheet, Table 27 + branchSum[0] += sampleBuffer[idx++]; //D1A + branchSum[5] += sampleBuffer[idx++]; //D1B + branchSum[1] += sampleBuffer[idx++]; //D2A + branchSum[4] += sampleBuffer[idx++]; //D2B + branchSum[7] += sampleBuffer[idx++]; //D3A + branchSum[2] += sampleBuffer[idx++]; //D3B + branchSum[6] += sampleBuffer[idx++]; //D4A + branchSum[3] += sampleBuffer[idx++]; //D4B + } + + free(sampleBuffer); +} + +static void cal_step_0(tsHandle_t pTs, uint8_t chanBitmap) +{ + //Set Channels to unity gain, no offset + tsChannelParam_t param = {0}; + param.active = false; + param.volt_scale_uV = 700000; + param.volt_offset_uV = 0; + param.bandwidth = 0; + param.coupling = TS_COUPLE_AC; + param.term = TS_TERM_1M; + + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + thunderscopeChannelConfigSet(pTs, i, ¶m); + } + } + return; +} + +static void cal_step_1(uint8_t chanBitmap) +{ + // Measure +VBIAS + printf("Measure the +VBIAS Test Point\r\n"); + + while(1) + { + double vbias = 0; + printf("Enter value of +VBIAS: "); + if((scanf("%lf", &vbias) > 0) && (vbias < 5.0)) + { + printf("Saving value of %lli uV for +VBIAS\r\n", (int64_t)(vbias * 1000000)); + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + calibration.afeCal[i].bias_uV = (int32_t)(vbias * 1000000); + } + } + break; + } + printf("Invalid Value for VBIAS\r\n"); + } + FLUSH(); + + printf("\r\n"); + while(getchar() != '\n') {;;} + + return; +} + +static void cal_step_2(tsHandle_t pTs, uint8_t chanBitmap) +{ + double vtrim[2][TS_NUM_CHANNELS] = {0}; + tsChannelCtrl_t afe_ctrl = {0}; + afe_ctrl.atten = 0; + // Characterize DPOT + //Set DAC and DPOT + afe_ctrl.dac = 1000; + // afe_ctrl.dac = 1 * 4095 / 5; + afe_ctrl.dpot = MCP4432_MAX; + + printf("Setting V_DAC to 1V and R_TRIM to 50k\r\n"); + + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + thunderscopeCalibrationManualCtrl(pTs, i, &afe_ctrl); + } + } + + // Measure VTRIM + printf("Measure the VTRIM Test Point(s)\r\n"); + + //User input of value and tracking to build calibration + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + while(1) + { + double measure = 0; + printf("Enter value of Channel %d VTRIM: ", i); + if((scanf("%lf", &measure) > 0) && (measure < 5.0)) + { + printf("Storing value of %.03lf V for VTRIM\r\n", measure); + vtrim[0][i] = measure; + break; + } + printf("Invalid Value for VTRIM\r\n"); + } + FLUSH(); + } + } + + printf("\r\n"); + while(getchar() != '\n') {;;} + + afe_ctrl.dpot = MCP4432_MAX/2; + + printf("Setting V_DAC to 1V and R_TRIM to 25k\r\n"); + + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + thunderscopeCalibrationManualCtrl(pTs, i, &afe_ctrl); + } + } + + // Measure VTRIM + printf("Measure the VTRIM Test Point(s)\r\n"); + + //User input of value and tracking to build calibration + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + while(1) + { + double measure = 0; + printf("Enter value of Channel %d VTRIM: ", i); + if((scanf("%lf", &measure) > 0) && (measure < 5.0)) + { + printf("Storing value of %.03lf V for VTRIM\r\n", measure); + vtrim[1][i] = measure; + break; + } + printf("Invalid Value for VTRIM\r\n"); + } + FLUSH(); + } + } + + // Calculate DPOT value + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + calibration.afeCal[i].trimRheostat_range = (500.0 * (2 * vtrim[1][i] - vtrim[0][i] - 1.0)) / (vtrim[0][i] - vtrim[1][i]); + printf("Setting Channel %d Trim DPot to a full range of %d Ohms\r\n", i, calibration.afeCal[i].trimRheostat_range); + } + } + + printf("\r\n"); + while(getchar() != '\n') {;;} +} + +static void cal_step_3(tsHandle_t pTs, uint8_t chanBitmap) +{ + // Characterize Input Bias Current + double vtrim[TS_NUM_CHANNELS] = {0}; + tsChannelCtrl_t afe_ctrl = {0}; + afe_ctrl.atten = 0; + + + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + //Set DAC and DPOT + printf("Setting channel %d V_DAC to %duV and R_TRIM to %d Ohm\r\n", i, calibration.afeCal[i].bias_uV, calibration.afeCal[i].trimRheostat_range); + afe_ctrl.dac = (uint16_t)(calibration.afeCal[i].bias_uV); + // afe_ctrl.dac = (int32_t)(calibration.afeCal[i].bias_mV * 4095 / 5); + afe_ctrl.dpot = ((((500) * calibration.afeCal[i].trimRheostat_range) / MCP4432_MAX) + MCP4432_RWIPER); + thunderscopeCalibrationManualCtrl(pTs, i, &afe_ctrl); + } + } + + // Measure VTRIM + printf("Measure the VTRIM Test Point(s)\r\n"); + + //User input of value and tracking to build calibration + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + while(1) + { + double measure = 0; + printf("Enter value of Channel %d VTRIM: ", i); + if((scanf("%lf", &measure) > 0) && (measure < 5.0)) + { + calibration.afeCal[i].preampInputBias_uA = (int32_t)(((((double)calibration.afeCal[i].bias_uV/1000000) - measure)/250.0) * 1000000.0); + printf("Saving value of %i uA for Channel %d Preamp Input Bias Current\r\n", calibration.afeCal[i].preampInputBias_uA, i); + break; + } + printf("Invalid Value for VTRIM\r\n"); + } + FLUSH(); + } + } + printf("\r\n"); + while(getchar() != '\n') {;;} +} + +static void cal_step_4(tsHandle_t pTs, uint8_t chanBitmap) +{ + // Characterize Preamp Output Offset + // Set V_trim to 2.5V + // Set Low Gain (no attenuator, 1M termination, Low preamp gain) + tsChannelParam_t param = {0}; + param.active = false; + param.volt_scale_uV = 79000; + param.volt_offset_uV = 0; + param.bandwidth = 0; + param.coupling = TS_COUPLE_AC; + param.term = TS_TERM_1M; + + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + thunderscopeChannelConfigSet(pTs, i, ¶m); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Measure Average values + int8_t currentVal[TS_NUM_CHANNELS] = {0}; + cal_get_average_value(pTs, chanBitmap, DMA_BUFFER_SIZE*128, currentVal); + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + // Set average for channel as cal.preampLowOffset_uv + calibration.afeCal[i].preampLowOffset_uV = (int32_t)((((double)currentVal[i]) - 128.0)/700000.0); + printf("Saving value of %i uV for Channel %d Preamp Low-gain Offset\r\n", calibration.afeCal[i].preampLowOffset_uV, i); + } + } + + printf("\r\n"); + while(getchar() != '\n') {;;} + + // Set High Gain (no attenuator, 1M termination, High preamp gain) + param.active = false; + param.volt_scale_uV = 8000; + param.volt_offset_uV = 0; + param.bandwidth = 0; + param.coupling = TS_COUPLE_AC; + param.term = TS_TERM_1M; + + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + thunderscopeChannelConfigSet(pTs, i, ¶m); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Measure Average values + cal_get_average_value(pTs, chanBitmap, DMA_BUFFER_SIZE*128, currentVal); + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((chanBitmap >> i) & 0x1) + { + // Set average for channel as cal.preampLowOffset_uv + calibration.afeCal[i].preampHighOffset_uV = (int32_t)((((double)currentVal[i]) - 128.0)/700000.0); + printf("Saving value of %i uV for Channel %d Preamp High-gain Offset\r\n", calibration.afeCal[i].preampHighOffset_uV, i); + } + } + + printf("\r\n"); + while(getchar() != '\n') {;;} +} + +static void cal_fine_gain(tsHandle_t pTs) +{ + //Zero Fine branch cal + tsAdcCalibration_t cal = {0}; + thunderscopeAdcCalibrationGet(pTs, &cal); + memset(cal.branchFineGain, 0, sizeof(cal.branchFineGain)); + thunderscopeAdcCalibrationSet(pTs, &cal); + + // Characterize ADC Branch Offsets + // Set Offset to 0.4V + // Set Volt scale to 1.0V + // Set AC coupled, 50Ohm + // Set 20MHz bandwidth filter + tsChannelParam_t chan; + chan.active = 1; + chan.bandwidth = 20; + chan.coupling = TS_COUPLE_AC; + chan.term = TS_TERM_50; + chan.volt_offset_uV = -400000; + chan.volt_scale_uV = 1000000; + thunderscopeChannelConfigSet(pTs, 0, &chan); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + printf("Collecting ADC Samples....\r\n"); + + // Measure Average values + int64_t branchVals[8] = {0}; + + //Accumulate many times + cal_get_interleaved_samples(pTs, DMA_BUFFER_SIZE*2000, branchVals); + + int64_t median_sample; + int64_t sum = 0; + double mean = 0, std_dev = 0; + int64_t max_sample = branchVals[0]; + int64_t min_sample = branchVals[0]; + printf("Sampled Branch Sum:\r\n"); + for(uint32_t i=0; i < 8; i++) + { + printf("\t Branch %d: %lld\r\n", i+1, branchVals[i]); + if(branchVals[i] > max_sample) + { + max_sample = branchVals[i]; + } + if(branchVals[i] < min_sample) + { + min_sample = branchVals[i]; + } + sum += branchVals[i]; + } + + // Find median branch val + median_sample = (max_sample + min_sample) / 2; + printf("Branch median value: %lld\r\n", median_sample); + mean = sum / 8.0; + for(uint32_t i = 0; i < 8; i++) + { + std_dev += pow((double)branchVals[i] - mean, 2); + } + std_dev = sqrt(std_dev/8.0); + printf("Uncalibrated STD_Dev: %0.12f\r\n", std_dev); + + //Scale branches to match + for(uint32_t i=0; i < 8; i++) + { + uint8_t config; + /** + * branch * cal_scale = median + * ... cal_scale = median / branch + * we want to find the abs delta from 1, our resolution is 2^-13 + * cal_scale = 1 + x/8192 + * ... x = (cal_scale - 1) * 8192 + * for scale < 1, value is negative + * ... -x = -(cal_scale - 1) * 8192 + * ... x = (1 - cal_scale) * 8192 + */ + double branchScale = (double)median_sample/(double)branchVals[i]; + printf("Branch %d differs from the median by a factor of %.12f\r\n", i+1, branchScale); + if(branchScale > 1) + { + branchScale = (branchScale - 1) * 8192.0; + // Round and limit to 63 + config = branchScale > 63 ? 63 : (uint8_t)(branchScale + 0.5); + } + else + { + branchScale = (1 - branchScale) * 8192.0; + // Round and limit to 63 + config = branchScale > 63 ? 63 : (uint8_t)(branchScale + 0.5); + // Invert + config = ~config; + } + cal.branchFineGain[i] = config & 0x7F; + } + + //Print Calibration + for(int i=0; i<8; i++) + { + printf("Branch %d Fine Gain Cal: %02X\r\n", i+1, cal.branchFineGain[i]); + } + + //Verify Calibration + memset(branchVals, 0, sizeof(branchVals)); + // thunderscopeAdcCalibrationSet(pTs, &cal); + cal_get_interleaved_samples(pTs, DMA_BUFFER_SIZE*2000, branchVals); + chan.active = 0; + thunderscopeChannelConfigSet(pTs, 0, &chan); + + printf("Calibrated Branch Sum:\r\n"); + max_sample = branchVals[0]; + min_sample = branchVals[0]; + sum = 0; + std_dev = 0; + for(uint32_t i=0; i < 8; i++) + { + printf("\t Branch %d: %lld\r\n", i+1, branchVals[i]); + if(branchVals[i] > max_sample) + { + max_sample = branchVals[i]; + } + if(branchVals[i] < min_sample) + { + min_sample = branchVals[i]; + } + sum += branchVals[i]; + } + + // Find median branch val + median_sample = (max_sample + min_sample) / 2; + printf("Branch median value: %lld\r\n", median_sample); + mean = sum / 8.0; + for(uint32_t i = 0; i < 8; i++) + { + std_dev += pow((double)branchVals[i] - mean, 2); + } + std_dev = sqrt(std_dev/8.0); + printf("Calibrated STD_Dev: %0.12f\r\n", std_dev); + + for(uint32_t i=0; i < 8; i++) + { + uint8_t config; + double branchScale = (double)branchVals[i] / median_sample; + printf("Calibrated Branch %d differs from the median by a factor of %.12f\r\n", i+1, branchScale); + } + + printf("\r\n"); + while(getchar() != '\n') {;;} +} + +static void do_calibration(uint32_t idx, uint8_t channelBitmap, uint32_t stepNo) +{ + tsHandle_t tsHdl = thunderscopeOpen(idx, false); + + //Load Starting Config or set Default + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((channelBitmap >> i) & 0x1) + { + calibration.afeCal[i].buffer_uV = TS_VBUFFER_NOMINAL_UV; + calibration.afeCal[i].bias_uV = TS_VBIAS_NOMINAL_UV; + calibration.afeCal[i].attenuatorGain1M_mdB = TS_ATTENUATION_1M_GAIN_mdB; + calibration.afeCal[i].attenuatorGain50_mdB = TS_TERMINATION_50OHM_GAIN_mdB; + calibration.afeCal[i].bufferGain_mdB = TS_BUFFER_GAIN_NOMINAL_mdB; + calibration.afeCal[i].trimRheostat_range = MCP4432_503_FULL_SCALE_OHM; + calibration.afeCal[i].preampLowGainError_mdB = 0; + calibration.afeCal[i].preampHighGainError_mdB = 0; + calibration.afeCal[i].preampOutputGainError_mdB = 0; + calibration.afeCal[i].preampLowOffset_uV = 0; + calibration.afeCal[i].preampHighOffset_uV = 0; + calibration.afeCal[i].preampInputBias_uA = TS_PREAMP_INPUT_BIAS_CURRENT_uA; + + thunderscopeChanCalibrationSet(tsHdl, i, &calibration.afeCal[i]); + } + } + + + + //Intentially fall through each case to do all calibration steps + switch(stepNo) + { + default: + case 0: + cal_step_0(tsHdl, channelBitmap); + case 1: + cal_step_1(channelBitmap); + case 2: + cal_step_2(tsHdl, channelBitmap); + case 3: + cal_step_3(tsHdl, channelBitmap); + case 4: + cal_step_4(tsHdl, channelBitmap); + case 5: + cal_fine_gain(tsHdl); + } + + //Apply Final Calibration + for(uint32_t i=0; i < TS_NUM_CHANNELS; i++) + { + if((channelBitmap >> i) & 0x1) + { + thunderscopeChanCalibrationSet(tsHdl, i, &calibration.afeCal[i]); + } + } + + thunderscopeClose(tsHdl); +} + +static void print_help(void) +{ + printf("TS Calibration Util Usage:\r\n"); + printf("\t -d Device Index\r\n"); + printf("\t -c Channel bitmap\r\n"); + printf("\t -s Skip to step #\r\n"); +} + +/* Main */ +/*------*/ + +int main(int argc, char** argv) +{ + const char* cmd = argv[0]; + unsigned char fpga_identifier[256]; + char devicePath[TS_IDENT_STR_LEN]; + + + file_t fd; + uint32_t idx = 0; + int i; + uint8_t channelBitmap = 0x01; + uint32_t stepNo = 0; + + struct optparse_long argList[] = { + {"dev", 'd', OPTPARSE_REQUIRED}, + {"chan", 'c', OPTPARSE_REQUIRED}, + {"step", 's', OPTPARSE_REQUIRED}, + {0} + }; + + auto argCount = 1; + char *arg = argv[argCount]; + int option; + struct optparse options; + + (void)argc; + optparse_init(&options, argv); + while ((option = optparse_long(&options, argList, NULL)) != -1) + { + switch (option) { + case 'c': + channelBitmap = strtol(options.optarg, NULL, 0); + argCount+=2; + break; + case 'd': + idx = strtol(options.optarg, NULL, 0); + argCount+=2; + break; + case 's': + stepNo = strtol(options.optarg, NULL, 0); + argCount+=2; + break; + case '?': + fprintf(stderr, "%s: %s\n", argv[0], options.errmsg); + print_help(); + exit(EXIT_SUCCESS); + } + } + arg = argv[argCount]; + + snprintf(devicePath, TS_IDENT_STR_LEN, LITEPCIE_CTRL_NAME(%d), idx); + printf("Opening Device %s\n", devicePath); + fd = litepcie_open((const char*)devicePath, FILE_FLAGS); + if(fd == INVALID_HANDLE_VALUE) { + fprintf(stderr, "Could not init driver\n"); + exit(1); + } + + + printf("\x1b[1m[> FPGA/SoC Information:\x1b[0m\n"); + printf("------------------------\n"); + + for (i = 0; i < 256; i++) + { + fpga_identifier[i] = litepcie_readl(fd, CSR_IDENTIFIER_MEM_BASE + 4 * i); + } + printf("FPGA Identifier: %s.\n", fpga_identifier); + +#ifdef CSR_DNA_BASE + printf("FPGA DNA: 0x%08x%08x\n", + litepcie_readl(fd, CSR_DNA_ID_ADDR + 4 * 0), + litepcie_readl(fd, CSR_DNA_ID_ADDR + 4 * 1)); +#endif +#ifdef CSR_XADC_BASE + printf("FPGA Temperature: %0.1f �C\n", + (double)litepcie_readl(fd, CSR_XADC_TEMPERATURE_ADDR) * 503.975 / 4096 - 273.15); + printf("FPGA VCC-INT: %0.2f V\n", + (double)litepcie_readl(fd, CSR_XADC_VCCINT_ADDR) / 4096 * 3); + printf("FPGA VCC-AUX: %0.2f V\n", + (double)litepcie_readl(fd, CSR_XADC_VCCAUX_ADDR) / 4096 * 3); + printf("FPGA VCC-BRAM: %0.2f V\n", + (double)litepcie_readl(fd, CSR_XADC_VCCBRAM_ADDR) / 4096 * 3); +#endif + /* Close LitePCIe device. */ + litepcie_close(fd); + + // Walk the user through calibration steps + do_calibration(idx, channelBitmap, stepNo); + + return 0; +} \ No newline at end of file diff --git a/example/thunderscope_fw.cpp b/example/thunderscope_fw.cpp index 4e15493..b15b29a 100644 --- a/example/thunderscope_fw.cpp +++ b/example/thunderscope_fw.cpp @@ -22,21 +22,83 @@ #include "optparse.h" #include +#include +#define MAX_USER_DATA_SIZE (0x500000) -static void cal_save(tsHandle_t ts, const char* file_path) +static void user_write(tsHandle_t ts, const char* file_path, uint32_t offset) { - + std::error_code err; + auto file_size = std::filesystem::file_size(file_path, err); + if(err) + { + std::cout << "Error opening \"" << file_path << "\" : " << err.message() << "\r\n"; + return; + } + + int32_t status; + // Open data file + std::ifstream file(file_path, std::ios::binary); + if(file) + { + char* bitstream = new char[file_size]; + file.read(bitstream, file_size); + + printf("User Data write in progress...."); + + // Load New bitstream + status = thunderscopeUserDataWrite(ts, bitstream, offset, (uint32_t)file_size); + + printf("Data Write %dB Complete!\r\n", status); + + // Close File + file.close(); + delete[] bitstream; + } + else + { + printf("ERROR User Data Write: Failed to open file %s\r\n", file_path); + } } -static void cal_load(tsHandle_t ts, const char* file_path) +static void user_read(tsHandle_t ts, const char* file_path) { - + int32_t read_len = 0; + // Open file to store data + std::ofstream file(file_path, std::ios::binary); + if(file) + { + char* data_buffer = new char[MAX_USER_DATA_SIZE]; + + printf("Reading User Data ...."); + + // Get data from TS + read_len = thunderscopeUserDataRead(ts, data_buffer, 0, MAX_USER_DATA_SIZE); + file.write(data_buffer, read_len); + + printf("Read %dB Complete!\r\n", read_len); + + // Close File + file.flush(); + file.close(); + delete[] data_buffer; + } + else + { + printf("ERROR User Data Read: Failed to open file %s\r\n", file_path); + } } static void fw_upgrade(tsHandle_t ts, const char* file_path) { - auto file_size = std::filesystem::file_size(file_path); + std::error_code err; + auto file_size = std::filesystem::file_size(file_path, err); + if(err) + { + std::cout << "Error opening \"" << file_path << "\" : " << err.message() << "\r\n"; + return; + } + // Open Bitstream File std::ifstream file(file_path, std::ios::binary); if(file) @@ -53,7 +115,7 @@ static void fw_upgrade(tsHandle_t ts, const char* file_path) // Close File file.close(); - + delete[] bitstream; } else { @@ -70,10 +132,10 @@ static void fw_restore(tsHandle_t ts) static void print_help(void) { printf("TS FW Update Util Usage:\r\n"); - printf("\tcal_save [dest_file] - Save the current calibration to an XML file\r\n"); + printf("\tuser_read [dest_file] - Save user data to a file\r\n"); printf("\t\t[dest_file] - Name of the file to save\r\n"); - printf("\tcal_load [cal_file] - Load user calibration data\r\n"); - printf("\t\t[cal_file] - Name of the XML Calibration file to load\r\n"); + printf("\tuser_write [data_file] - Load user data to the Thunderscope\r\n"); + printf("\t\t[data_file] - Name of the XML Calibration file to load\r\n"); printf("\tfw_upgrade [bitstream_file] - Load a new Bitstream\r\n"); printf("\t\t[bitstream_file] - Name of the Gateware Bitstream file to load\r\n"); printf("\tfactory_restore - Restores the factory bitstream to the primary location\r\n"); @@ -81,6 +143,13 @@ static void print_help(void) printf("\thelp - Print this help message\r\n"); printf("\tCommon Options:\r\n"); printf("\t\t-d Device Index\r\n"); +#if defined(FACTORY_PROVISIONING_API) + printf("--FACTORY USAGE ONLY--\r\n"); + printf("\tfactory_prepare [dna] - Erase the Factory Data Partition\r\n"); + printf("\t\t[dna] - Device DNA value to confirm erase\r\n"); + printf("\tfactory_load [tag] [file] - Load a JSON file with Factory Data\r\n"); + printf("\t\t[tag] - Tag identifier for the data item\r\n"); +#endif } int main(int argc, char** argv) @@ -92,9 +161,11 @@ int main(int argc, char** argv) tsHandle_t ts; tsDeviceInfo_t infos; tsScopeState_t status; + uint32_t offs = 0; struct optparse_long argList[] = { {"dev", 'd', OPTPARSE_REQUIRED}, + {"offs", 'o', OPTPARSE_REQUIRED}, {0} }; @@ -112,6 +183,10 @@ int main(int argc, char** argv) idx = strtol(options.optarg, NULL, 0); argCount+=2; break; + case 'o': + offs = strtol(options.optarg, NULL, 0); + argCount += 2; + break; case '?': fprintf(stderr, "%s: %s\n", argv[0], options.errmsg); print_help(); @@ -132,6 +207,28 @@ int main(int argc, char** argv) exit(0); } + if(0 == strcmp(arg, "list")) + { + uint32_t i = 0; + while(TS_STATUS_OK == thunderscopeListDevices(i, &infos)) + { + if(i==0) + { + printf("Found ThunderScope(s):\n"); + } + printf("\t%3d | Serial Number: %s\n", i, infos.serial_number); + printf("\t | HW Rev: 0x%x\n", infos.hw_id); + printf("\t | GW Rev: 0x%x\n", infos.gw_id); + printf("\t | LiteX Rev: 0x%x\n", infos.litex); + i++; + } + if(i == 0) + { + printf("No devices present\n"); + } + exit(EXIT_SUCCESS); + } + result = thunderscopeListDevices(idx, &infos); if(result == TS_STATUS_ERROR) { @@ -139,24 +236,26 @@ int main(int argc, char** argv) exit(EXIT_FAILURE); } - ts = thunderscopeOpen(idx); + ts = thunderscopeOpen(idx, true); if(ts == NULL) { fprintf(stderr, "Could not init driver\n"); exit(EXIT_FAILURE); } - printf("\x1b[1m[> FPGA/SoC Information:\x1b[0m\n"); + printf("\x1b[1m[> Device Information:\x1b[0m\n"); printf("------------------------\n"); printf("FPGA Identifier: %s.\n", infos.identity); - - result = thunderscopeStatusGet(ts, &status); - if(result == TS_STATUS_OK) + if(infos.hw_id & TS_HW_ID_VALID_MASK) { - printf("FPGA Temperature: %0.1f \u00B0C\n", (float_t)status.sys_health.temp_c / 1000); - printf("FPGA VCC-INT: %0.2f V\n", (float_t)status.sys_health.vcc_int / 1000); - printf("FPGA VCC-AUX: %0.2f V\n", (float_t)status.sys_health.vcc_aux / 1000); - printf("FPGA VCC-BRAM: %0.2f V\n", (float_t)status.sys_health.vcc_bram / 1000); + printf("HW Rev %02d - %s\n", infos.hw_id & TS_HW_ID_REV_MASK, + (infos.hw_id & TS_HW_ID_VARIANT_MASK) ? "TB" : "PCIe" ); } + else + { + printf("HW Rev Beta\n"); + } + printf("Gateware Rev 0x%08X\n", infos.gw_id); + printf("LiteX Release 0x%08X\n", infos.litex); if(0 == strcmp(arg, "factory_restore")) { @@ -167,6 +266,85 @@ int main(int argc, char** argv) //Nothing else to do ;; } +#if defined(FACTORY_PROVISIONING_API) + else if(0 == strcmp(arg, "factory_prepare")) + { + if(argc != 3) + { + printf("Error: Missing args\r\n"); + print_help(); + } + else + { + uint64_t op_dna = 0; + if(!sscanf(argv[2], "%llx", &op_dna)) + { + printf("Error: Invalid DNA\r\n"); + print_help(); + } + else if(TS_STATUS_OK == thunderscopeFactoryProvisionPrepare(ts, op_dna)) + { + printf("Factory Data Partition Erased \r\n"); + } + else + { + printf("Failed to prepare Factory Data Partition\r\n"); + } + } + } + else if(0 == strcmp(arg, "factory_load")) + { + char tag[5]; + if(argc != 4) + { + printf("Error: Missing args\r\n"); + print_help(); + } + else if(!sscanf(argv[2], "%4s", tag)) + { + printf("Error: Invalid TAG arg\r\n"); + print_help(); + } + else + { + file_path = argv[3]; + std::error_code err; + auto file_size = std::filesystem::file_size(file_path, err); + if(err) + { + std::cout << "Error opening \"" << file_path << "\" : " << err.message() << "\r\n"; + thunderscopeClose(ts); + return -1; + } + + // Open Data File + std::ifstream file(file_path, std::ios::binary); + if(file) + { + char* data_content = new char[file_size]; + file.read(data_content, file_size); + + if(TS_STATUS_OK == thunderscopeFactoryProvisionAppendTLV(ts, (uint32_t)((tag[0] << 24) + (tag[1] << 16) + (tag[2] << 8) + tag[3]), + file_size, (const char*)data_content)) + { + printf("Finished writing the %s item\r\n", tag); + } + else + { + printf("Failed to write factory data item\r\n"); + } + + // Close File + file.close(); + delete data_content; + } + else + { + printf("ERROR Factory TLV: Failed to open file %s\r\n", file_path); + } + } + } +#endif else if(argCount == argc) { printf("**Missing File path argument**\r\n"); @@ -176,14 +354,14 @@ int main(int argc, char** argv) { file_path = argv[argCount]; - if(0 == strcmp(arg, "cal_save")) + if(0 == strcmp(arg, "user_write")) { - cal_save(ts, file_path); + user_write(ts, file_path, offs); } // Setup Channel, record samples to buffer, save buffer to file - else if(0 == strcmp(arg, "cal_load")) + else if(0 == strcmp(arg, "user_read")) { - cal_load(ts, file_path); + user_read(ts, file_path); } else if(0 == strcmp(arg, "fw_upgrade")) { diff --git a/example/thunderscope_test.cpp b/example/thunderscope_test.cpp index f4377f5..ca4ea94 100644 --- a/example/thunderscope_test.cpp +++ b/example/thunderscope_test.cpp @@ -27,6 +27,8 @@ #ifdef _WIN32 #include +#else +#include #endif #define OPTPARSE_IMPLEMENTATION @@ -48,6 +50,8 @@ #include "../src/ts_channel.h" #include "../src/samples.h" +#include "../src/platform.h" +#include "../src/events.h" #include "thunderscope.h" @@ -94,10 +98,28 @@ uint32_t _SPI_CONTROL_START = (1 << 0); uint32_t _SPI_CONTROL_LENGTH = (1 << 8); uint32_t _SPI_STATUS_DONE = (1 << 0); +static volatile bool g_program_loop = true; /* Main */ /*------*/ +#ifdef _WIN32 +BOOL WINAPI SigHandler(DWORD ctrlType) +{ + if(ctrlType == CTRL_C_EVENT) + { + g_program_loop = false; + return TRUE; + } + return FALSE; +} +#else +extern "C" void SigHandler(int s) +{ + g_program_loop = false; +} +#endif + void configure_frontend_ldo(file_t fd, uint32_t enable) { uint32_t control_value = litepcie_readl(fd, CSR_FRONTEND_CONTROL_ADDR); control_value &= ~(1 * AFE_CONTROL_LDO_EN); @@ -105,6 +127,11 @@ void configure_frontend_ldo(file_t fd, uint32_t enable) { litepcie_writel(fd, CSR_FRONTEND_CONTROL_ADDR, control_value); } +bool query_frontend_pg(file_t fd) { + uint32_t status_value = litepcie_readl(fd, CSR_FRONTEND_STATUS_ADDR); + return (status_value & (1 << CSR_FRONTEND_STATUS_FE_PG_OFFSET)); +} + void configure_adc_ldo(file_t fd, uint32_t enable) { uint32_t control_value = litepcie_readl(fd, CSR_ADC_CONTROL_ADDR); control_value &= ~(1 * ADC_CONTROL_LDO_EN); @@ -112,6 +139,11 @@ void configure_adc_ldo(file_t fd, uint32_t enable) { litepcie_writel(fd, CSR_ADC_CONTROL_ADDR, control_value); } +bool query_adc_pg(file_t fd) { + uint32_t status_value = litepcie_readl(fd, CSR_ADC_STATUS_ADDR); + return (status_value & (1 << CSR_ADC_STATUS_ACQ_PG_OFFSET)); +} + void configure_pll_en(file_t fd, uint32_t enable) { uint32_t control_value = litepcie_readl(fd, CSR_ADC_CONTROL_ADDR); control_value &= ~(1 * ADC_CONTROL_PLL_EN); @@ -119,11 +151,8 @@ void configure_pll_en(file_t fd, uint32_t enable) { litepcie_writel(fd, CSR_ADC_CONTROL_ADDR, control_value); } -void control_led(file_t fd, uint32_t enable) { - uint32_t control_value = litepcie_readl(fd, CSR_LEDS_OUT_ADDR); - control_value &= ~(1 * AFE_STATUS_LDO_PWR_GOOD); - control_value |= (enable * AFE_STATUS_LDO_PWR_GOOD); - litepcie_writel(fd, CSR_LEDS_OUT_ADDR, control_value); +void control_led(file_t fd, uint8_t val) { + litepcie_writel(fd, CSR_DEV_STATUS_LEDS_ADDR, (uint32_t)val); } static uint32_t read_flash_word(file_t fd, uint32_t flash_addr) @@ -151,7 +180,135 @@ auto awake_time() return now() + 500ms; } -static void test_io(file_t fd) +void save_8bit_wav(int8_t* sampleBuffer, uint64_t sampleLen, uint8_t numChan, uint32_t sampleRate) +{ + AudioFile outWav; + outWav.setBitDepth(8); + outWav.setNumChannels(numChan); + if(numChan > 2) + { + outWav.setSampleRate(sampleRate/4); + } + else + { + outWav.setSampleRate(sampleRate/numChan); + } + + AudioFile::AudioBuffer wavBuffer; + wavBuffer.resize(numChan); + if(numChan == 1) + { + wavBuffer[0].resize(sampleLen); + } + else if(numChan == 2) + { + wavBuffer[0].resize(sampleLen/numChan); + wavBuffer[1].resize(sampleLen/numChan); + } + else + { + wavBuffer[0].resize(sampleLen/4); + wavBuffer[1].resize(sampleLen/4); + wavBuffer[2].resize(sampleLen/4); + + if(numChan == 4) + { + wavBuffer[3].resize(sampleLen/4); + } + } + uint64_t sample = 0; + uint64_t idx = 0; + while (idx < sampleLen) + { + wavBuffer[0][sample] = sampleBuffer[idx++]; + if(numChan > 1) + { + wavBuffer[1][sample] = sampleBuffer[idx++]; + if(numChan > 2) + { + wavBuffer[2][sample] = sampleBuffer[idx++]; + if(numChan == 4) + { + wavBuffer[3][sample] = sampleBuffer[idx++]; + } + else + { + idx++; + } + } + } + sample++; + } + outWav.setAudioBuffer(wavBuffer); + outWav.printSummary(); + outWav.save(TS_TEST_WAV_FILE); +} + +void save_16bit_wav(int16_t* sampleBuffer, uint64_t sampleLen, uint8_t numChan, uint32_t sampleRate) +{ + AudioFile outWav; + outWav.setBitDepth(16); + outWav.setNumChannels(numChan); + if(numChan > 2) + { + outWav.setSampleRate(sampleRate/4); + } + else + { + outWav.setSampleRate(sampleRate/numChan); + } + + AudioFile::AudioBuffer wavBuffer; + wavBuffer.resize(numChan); + if(numChan == 1) + { + wavBuffer[0].resize(sampleLen); + } + else if(numChan == 2) + { + wavBuffer[0].resize(sampleLen/numChan); + wavBuffer[1].resize(sampleLen/numChan); + } + else + { + wavBuffer[0].resize(sampleLen/4); + wavBuffer[1].resize(sampleLen/4); + wavBuffer[2].resize(sampleLen/4); + + if(numChan == 4) + { + wavBuffer[3].resize(sampleLen/4); + } + } + uint64_t sample = 0; + uint64_t idx = 0; + while (idx < sampleLen) + { + wavBuffer[0][sample] = sampleBuffer[idx++]; + if(numChan > 1) + { + wavBuffer[1][sample] = sampleBuffer[idx++]; + if(numChan > 2) + { + wavBuffer[2][sample] = sampleBuffer[idx++]; + if(numChan == 4) + { + wavBuffer[3][sample] = sampleBuffer[idx++]; + } + else + { + idx++; + } + } + } + sample++; + } + outWav.setAudioBuffer(wavBuffer); + outWav.printSummary(); + outWav.save(TS_TEST_WAV_FILE); +} + +static void test_io(file_t fd, bool isBeta) { printf("\x1b[1m[> Scratch register test:\x1b[0m\n"); printf("-------------------------\n"); @@ -159,7 +316,7 @@ static void test_io(file_t fd) /* Write to scratch register. */ printf("Write 0x12345678 to Scratch register:\n"); - litepcie_writel(fd, CSR_CTRL_SCRATCH_ADDR, 0x0); + litepcie_writel(fd, CSR_CTRL_SCRATCH_ADDR, 0x12345678); printf("Read: 0x%08x\n", litepcie_readl(fd, CSR_CTRL_SCRATCH_ADDR)); /* Read from scratch register. */ @@ -170,9 +327,21 @@ static void test_io(file_t fd) printf("Enabling frontend LDO:\n"); configure_frontend_ldo(fd, 1); + std::this_thread::sleep_until(awake_time()); + if(query_frontend_pg(fd)) + { + printf("\tFrontend Power Good\n"); + } + printf("Enabling ADC LDO:\n"); configure_adc_ldo(fd, 1); + std::this_thread::sleep_until(awake_time()); + if(query_adc_pg(fd)) + { + printf("\tADC Power Good\n"); + } + printf("Disabling PLL EN & waiting 500ms:\n"); configure_pll_en(fd, 0); @@ -181,10 +350,37 @@ static void test_io(file_t fd) printf("Enabling PLL EN:\n"); configure_pll_en(fd, 1); + //Cycle LEDs + const led_signals_t *leds; + if(isBeta) + { + leds = &ts_beta_leds; + } + else + { + leds = &ts_dev_leds; + } + + printf("Set READY LED\n"); + control_led(fd, leds->ready); + using std::chrono::operator"" s; + std::this_thread::sleep_for(3s); + printf("Set ERROR LED\n"); + control_led(fd, leds->error); + using std::chrono::operator"" s; + std::this_thread::sleep_for(3s); + printf("Set ACTIVE LED\n"); + control_led(fd, leds->active); + using std::chrono::operator"" s; + std::this_thread::sleep_for(3s); + + control_led(fd, leds->disabled); + i2c_t i2cDev; i2cDev.fd = fd; + i2cDev.peripheral_baseaddr = CSR_I2CBUS_I2C0_PHY_SPEED_MODE_ADDR; i2c_rate_set(i2cDev, I2C_400KHz); - + printf("\nScan I2C Bus 0:\n"); for (unsigned char addr = 0; addr < 0x80; addr++) { i2cDev.devAddr = addr; bool result = i2c_poll(i2cDev); @@ -198,41 +394,89 @@ static void test_io(file_t fd) printf(" --"); } } + + if(!isBeta) + { + i2cDev.peripheral_baseaddr = CSR_I2CBUS_I2C1_PHY_SPEED_MODE_ADDR; + i2c_rate_set(i2cDev, I2C_400KHz); + printf("\nScan I2C Bus 1:\n"); + for (unsigned char addr = 0; addr < 0x80; addr++) { + i2cDev.devAddr = addr; + bool result = i2c_poll(i2cDev); + if (addr % 0x10 == 0) { + printf("\n0x%02X", addr); + } + if (result) { + printf(" %02X", addr); + } + else { + printf(" --"); + } + } + } - spi_bus_t spimaster; - spi_bus_init(&spimaster, fd, CSR_MAIN_SPI_BASE, CSR_MAIN_SPI_CS_SEL_SIZE); + spi_bus_t spibus0; + spi_bus_init(&spibus0, fd, CSR_SPIBUS_BASE, CSR_SPIBUS_SPI0_CS_SEL_SIZE); uint8_t data[2] = {0x01, 0x02}; - for (int i = 0; i < CSR_MAIN_SPI_CS_SEL_SIZE; i++) { + for (int i = 0; i < CSR_SPIBUS_SPI0_CS_SEL_SIZE; i++) { spi_dev_t spiDev; - spi_dev_init(&spiDev, &spimaster, i); + spi_dev_init(&spiDev, &spibus0, i); for (int reg = 0; reg < 10; reg++) { spi_write(spiDev, reg, data, 2); spi_busy_wait(spiDev); } } + + if(!isBeta) + { + spi_bus_t spibus1; + spi_bus_init(&spibus1, fd, CSR_SPIBUS_SPI1_CONTROL_ADDR, CSR_SPIBUS_SPI1_CS_SEL_SIZE); + + uint8_t data[2] = {0x01, 0x02}; + + for (int i = 0; i < CSR_SPIBUS_SPI1_CS_SEL_SIZE; i++) { + spi_dev_t spiDev; + spi_dev_init(&spiDev, &spibus1, i); + for (int reg = 0; reg < 10; reg++) { + spi_write(spiDev, reg, data, 2); + spi_busy_wait(spiDev); + } + } + } + + printf("\nPress ENTER to continue...\n"); + std::cin.ignore(1); + + // Cleanup + configure_frontend_ldo(fd, 0); + configure_adc_ldo(fd, 0); } static void test_capture(file_t fd, uint32_t idx, uint8_t channelBitmap, uint16_t bandwidth, - uint32_t volt_scale_mV, int32_t offset_mV, uint8_t ac_couple, uint8_t term) + uint32_t volt_scale_uV, int32_t offset_uV, uint8_t ac_couple, uint8_t term, bool watch_bitslip, + bool is12bit, bool is14bit, bool inRefClk, bool outRefClk, uint32_t refclkFreq, bool wait_for_sync, bool sync_out) { uint8_t numChan = 0; - tsHandle_t tsHdl = thunderscopeOpen(idx); - - // tsChannelHdl_t channels; - // ts_channel_init(&channels, fd); - // if(channels == NULL) - // { - // printf("Failed to create channels handle"); - // return; - // } - - // sampleStream_t samp; - // samples_init(&samp, 0, 0); + tsHandle_t tsHdl = thunderscopeOpen(idx, false); + uint32_t bitslip_count = 0; + uint32_t dbg_monitor; uint8_t* sampleBuffer = (uint8_t*)calloc(TS_SAMPLE_BUFFER_SIZE, 0x1000); uint64_t sampleLen = 0; + uint32_t sampleRate = 1000000000; + + if(inRefClk) + { + printf("Setting Ref In Clock @ %u Hz\n", refclkFreq); + printf("\t Result: %i\n", thunderscopeRefClockSet(tsHdl, TS_REFCLK_IN, refclkFreq)); + } + else if(outRefClk) + { + printf("Setting Ref Out Clock @ %u Hz\n", refclkFreq); + printf("\t Result: %i\n", thunderscopeRefClockSet(tsHdl, TS_REFCLK_OUT, refclkFreq)); + } //Setup and Enable Channels tsChannelParam_t chConfig = {0}; @@ -242,13 +486,12 @@ static void test_capture(file_t fd, uint32_t idx, uint8_t channelBitmap, uint16_ if(channelBitmap & 0x1) { thunderscopeChannelConfigGet(tsHdl, channel, &chConfig); - chConfig.volt_scale_mV = volt_scale_mV; - chConfig.volt_offset_mV = offset_mV; + chConfig.volt_scale_uV = volt_scale_uV; + chConfig.volt_offset_uV = offset_uV; chConfig.bandwidth = bandwidth; chConfig.coupling = ac_couple ? TS_COUPLE_AC : TS_COUPLE_DC; chConfig.term = term ? TS_TERM_50 : TS_TERM_1M; chConfig.active = 1; - // ts_channel_params_set(channels, channel, &chConfig); thunderscopeChannelConfigSet(tsHdl, channel, &chConfig); numChan++; } @@ -257,33 +500,83 @@ static void test_capture(file_t fd, uint32_t idx, uint8_t channelBitmap, uint16_ } // Uncomment to use Test Pattern - // ts_channel_set_adc_test(channels, HMCAD15_TEST_SYNC, 0, 0); + // thunderscopeCalibrationAdcTest(tsHdl, TS_ADC_TEST_RAMP, 0); + + if(is12bit) + { + sampleRate = 660000000; + thunderscopeSampleModeSet(tsHdl, sampleRate/numChan, TS_12_BIT_MSB); + } + else if(is14bit) + { + sampleRate = 125000000; + thunderscopeSampleModeSet(tsHdl, sampleRate, TS_14_BIT); + } + else + { + thunderscopeSampleModeSet(tsHdl, sampleRate/numChan, TS_8_BIT); + } printf("- Checking HMCAD1520 Sample Rate..."); - litepcie_writel(fd, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_STAT_RST_OFFSET); + litepcie_writel(fd, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_STAT_RST_OFFSET); NS_DELAY(500000000); - uint32_t rate = litepcie_readl(fd, CSR_ADC_HAD1511_SAMPLE_COUNT_ADDR) * 2; + uint32_t rate = litepcie_readl(fd, CSR_ADC_HMCAD1520_SAMPLE_COUNT_ADDR) * 2; printf(" %d Samples/S\r\n", rate); + if(watch_bitslip) + { + bitslip_count = litepcie_readl(fd, CSR_ADC_HMCAD1520_BITSLIP_COUNT_ADDR); + printf("Bitslip Snapshot: %lu\r\n", bitslip_count); + dbg_monitor = litepcie_readl(fd, CSR_ADC_HMCAD1520_FRAME_DEBUG_ADDR); + printf("FRAME Debug: 0x%08x\r\n", dbg_monitor); + dbg_monitor = litepcie_readl(fd, CSR_ADC_HMCAD1520_RANGE_ADDR); + printf("RANGE: 0x%08x\r\n", dbg_monitor); + } //Only start taking samples if the rate is non-zero if(rate > 0) { + if(wait_for_sync) + { + thunderscopeExtSyncConfig(tsHdl, TS_SYNC_IN); + } + else if(sync_out) + { + thunderscopeExtSyncConfig(tsHdl, TS_SYNC_OUT); + } + + printf("Capturing data buffers:\r\n"); uint64_t data_sum = 0; + uint64_t sample_number = 0; + uint64_t event_sample = 0; + tsEvent_t event; + bool sync_found = false; //Start Sample capture - // samples_enable_set(&samp, 1); - // ts_channel_run(channels, 1); thunderscopeDataEnable(tsHdl, 1); + litepcie_writel(fd, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_STAT_RST_OFFSET); auto startTime = std::chrono::steady_clock::now(); if(sampleBuffer != NULL) { - for(uint32_t loop=0; loop < 100; loop++) + uint32_t loop=0; + while(!sync_found) { + ++loop; + if(sync_out && loop == 50) + { + // events_set_periodic(fd, 1000000); + thunderscopeEventSyncAssert(tsHdl); + } + + //Force Event at 100 + if(loop == 100) + { + sync_found = true; + } + uint32_t readReq = (TS_SAMPLE_BUFFER_SIZE * 0x100); //Collect Samples - // int32_t readRes = samples_get_buffers(&samp, sampleBuffer, readReq); - int32_t readRes = thunderscopeRead(tsHdl, sampleBuffer, readReq); + int32_t readRes = thunderscopeReadCount(tsHdl, sampleBuffer, readReq, &sample_number); if(readRes < 0) { printf("ERROR: Sample Get Buffers failed with %" PRIi32, readRes); @@ -292,15 +585,49 @@ static void test_capture(file_t fd, uint32_t idx, uint8_t channelBitmap, uint16_ { printf("WARN: Read returned different number of bytes for loop %" PRIu32 ", %" PRIu32 " / %" PRIu32 "\r\n", loop, readRes, readReq); } + printf("Buffer %d Starts with Sample %lld\r\n", loop, sample_number); + data_sum += readReq; sampleLen = readRes; + + if(wait_for_sync || sync_out) + { + thunderscopeEventGet(tsHdl, &event); + while(event.ID != TS_EVT_NONE) + { + printf("Found EXT Event %d at sample %lld\r\n", event.ID, event.event_sample); + event_sample = event.event_sample; + thunderscopeEventGet(tsHdl, &event); + } + if(event_sample != 0 && event_sample < (sample_number + sampleLen)) + { + sync_found = true; + } + } + + if(watch_bitslip) + { + tsScopeState_t scopeState = {0}; + bitslip_count = litepcie_readl(fd, CSR_ADC_HMCAD1520_BITSLIP_COUNT_ADDR); + printf("Bitslip Snapshot: %lu\r\n", bitslip_count); + dbg_monitor = litepcie_readl(fd, CSR_ADC_HMCAD1520_FRAME_DEBUG_ADDR); + printf("FRAME Debug: 0x%08x\r\n", dbg_monitor); + dbg_monitor = litepcie_readl(fd, CSR_ADC_HMCAD1520_RANGE_ADDR); + printf("RANGE: 0x%08x\r\n", dbg_monitor); + thunderscopeStatusGet(tsHdl, &scopeState); + printf("Scope State Flags: 0x%08x\r\n", scopeState.flags); + } + } + + if(wait_for_sync || sync_out) + { + // events_set_periodic(fd, 0); + thunderscopeExtSyncConfig(tsHdl, TS_SYNC_DISABLED); } } auto endTime = std::chrono::steady_clock::now(); //Stop Samples - // samples_enable_set(&samp, 0); - // ts_channel_run(channels, 0); thunderscopeDataEnable(tsHdl, 0); auto deltaNs = std::chrono::duration_cast(endTime - startTime); @@ -311,15 +638,11 @@ static void test_capture(file_t fd, uint32_t idx, uint8_t channelBitmap, uint16_ //Disable channels for(uint8_t i=0; i < TS_NUM_CHANNELS; i++) { - // ts_channel_params_get(channels, i, &chConfig); thunderscopeChannelConfigGet(tsHdl, i, &chConfig); chConfig.active = 0; - // ts_channel_params_set(channels, i, &chConfig); thunderscopeChannelConfigSet(tsHdl, i, &chConfig); } - // ts_channel_destroy(channels); - // samples_teardown(&samp); thunderscopeClose(tsHdl); if(sampleLen > 0) @@ -329,67 +652,16 @@ static void test_capture(file_t fd, uint32_t idx, uint8_t channelBitmap, uint16_ outFile.flush(); outFile.close(); - AudioFile outWav; - outWav.setBitDepth(8); - outWav.setNumChannels(numChan); - if(numChan > 2) - { - outWav.setSampleRate(1000000000/4); - } - else - { - outWav.setSampleRate(1000000000/numChan); - } - - AudioFile::AudioBuffer wavBuffer; - wavBuffer.resize(numChan); - if(numChan == 1) - { - wavBuffer[0].resize(sampleLen); - } - else if(numChan == 2) + if(is12bit || is14bit) { - wavBuffer[0].resize(sampleLen/numChan); - wavBuffer[1].resize(sampleLen/numChan); + save_16bit_wav((int16_t*)sampleBuffer, sampleLen/2, numChan, sampleRate); } else { - wavBuffer[0].resize(sampleLen/4); - wavBuffer[1].resize(sampleLen/4); - wavBuffer[2].resize(sampleLen/4); - - if(numChan == 4) - { - wavBuffer[3].resize(sampleLen/4); - } + save_8bit_wav((int8_t*)sampleBuffer, sampleLen, numChan, sampleRate); } - uint64_t sample = 0; - uint64_t idx = 0; - while (idx < sampleLen) - { - wavBuffer[0][sample] = sampleBuffer[idx++]; - if(numChan > 1) - { - wavBuffer[1][sample] = sampleBuffer[idx++]; - if(numChan > 2) - { - wavBuffer[2][sample] = sampleBuffer[idx++]; - if(numChan == 4) - { - wavBuffer[3][sample] = sampleBuffer[idx++]; - } - else - { - idx++; - } - } - } - sample++; - } - outWav.setAudioBuffer(wavBuffer); - outWav.printSummary(); - outWav.save(TS_TEST_WAV_FILE); } + free(sampleBuffer); } static void flash_test(char* arg, file_t fd) @@ -411,7 +683,7 @@ static void flash_test(char* arg, file_t fd) auto outFile = std::fstream(TS_FLASH_DUMP_FILE, std::ios::out | std::ios::binary | std::ios::trunc); uint8_t *flash_data = (uint8_t*) malloc(0x40000); printf("Dumping Flash to file.\nProgress: "); - for(uint32_t address = 0x0000000; address < 0x2000000; address+=0x40000) + for(uint32_t address = 0x0000000; address < 0x800000; address+=0x40000) { spiflash_read(&spiflash, address, flash_data, 0x40000); printf("|"); @@ -420,6 +692,7 @@ static void flash_test(char* arg, file_t fd) printf("Done!\n"); outFile.flush(); outFile.close(); + free(flash_data); } else if(0 == strcmp(arg, "test")) { @@ -471,18 +744,83 @@ static void flash_test(char* arg, file_t fd) } } +static void test_clock(uint32_t idx, bool refoutclk, bool refinclk, uint32_t refclkfreq) +{ + tsHandle_t tsHdl = thunderscopeOpen(idx, false); + bool firstStatus = false; + + if(tsHdl) + { +#ifdef _WIN32 + SetConsoleCtrlHandler(SigHandler, TRUE); +#else + struct sigaction signalHandler; + signalHandler.sa_handler = SigHandler; + sigemptyset(&signalHandler.sa_mask); + signalHandler.sa_flags = 0; + sigaction(SIGINT, &signalHandler, NULL); +#endif + if(refinclk) + { + printf("Setting Ref In Clock @ %u Hz\n", refclkfreq); + printf("\t Result: %i\n", thunderscopeRefClockSet(tsHdl, TS_REFCLK_IN, refclkfreq)); + } + else if(refoutclk) + { + printf("Setting Ref Out Clock @ %u Hz\n", refclkfreq); + printf("\t Result: %i\n", thunderscopeRefClockSet(tsHdl, TS_REFCLK_OUT, refclkfreq)); + } + + while (g_program_loop) + { + tsScopeState_t state = {0}; + thunderscopeStatusGet(tsHdl, &state); + // if(!firstStatus) + // { + // printf("\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A\x1b[A"); + // firstStatus = true; + // } + printf("-------\r\n"); + printf("PLL Status:\r\n"); + printf("\tPLL LOCK - %01x\r\n", state.pll_lock); + printf("\tPLL HIGH - %01x\r\n", state.pll_high); + printf("\tPLL LOW - %01x\r\n", state.pll_low); + printf("\tPLL ALT - %01x\r\n", state.pll_alt); + printf("INPUT Clock Status:\r\n"); + printf("\tLocal Valid - %01x\r\n", state.local_osc_clk); + printf("\tREF IN Valid - %01x\r\n", state.ref_in_clk); + printf("\r\n-- PRESS CTRL+C TO STOP --\r\n"); + + std::cout.flush(); + std::this_thread::sleep_until(awake_time()); + } + + thunderscopeClose(tsHdl); + } +} + static void print_help(void) { printf("TS Test Util Usage:\r\n"); printf("\t io - run I/O Test\r\n"); + printf("\t flash - Test Flash device\r\n"); + printf("\t\t read Read a word from flash\r\n"); + printf("\t\t dump Dump the contents of flash to a file\r\n"); + printf("\t\t test Destructive Flash erase/read/write test\r\n"); printf("\t capture - run Sample Capture Test\r\n"); printf("\t\t -d Device Index\r\n"); printf("\t\t -c Channel bitmap\r\n"); printf("\t\t -b Channel Bandwidth [MHz]\r\n"); - printf("\t\t -v Channel Full Scale Volts [millivolt]\r\n"); - printf("\t\t -o Channel Offset [millivolt]\r\n"); + printf("\t\t -v Channel Full Scale Volts [microvolt]\r\n"); + printf("\t\t -o Channel Offset [microvolt]\r\n"); printf("\t\t -a AC Couple\r\n"); printf("\t\t -t 50 Ohm termination\r\n"); + printf("\t\t -m 12-bit mode\r\n"); + printf("\t\t -e External Sync Input\r\n"); + printf("\t\t -y External Sync Output\r\n"); + printf("\t refclk - run the PLL source with different clock configurations\r\n"); + printf("\t\t -i Set the Ref IN Clock frequency\r\n"); + printf("\t\t -r Set the Ref OUT Clock frequency\r\n"); } /* Main */ @@ -500,19 +838,32 @@ int main(int argc, char** argv) int i; uint8_t channelBitmap = 0x0F; uint16_t bandwidth = 350; - uint32_t volt_scale_mV = 10000; - int32_t offset_mV = 0; + uint32_t volt_scale_uV = 10000000; + int32_t offset_uV = 0; uint8_t ac_couple = 0; uint8_t term = 0; + bool bitslip = false; + bool mode12bit = false, mode14bit = false; + bool refInClk = false; + bool refOutClk = false; + uint32_t refclkFreq = 0; + bool extTrigger = false; + bool syncOut = false; struct optparse_long argList[] = { {"dev", 'd', OPTPARSE_REQUIRED}, {"chan", 'c', OPTPARSE_REQUIRED}, {"bw", 'b', OPTPARSE_REQUIRED}, - {"voltsmv", 'v', OPTPARSE_REQUIRED}, - {"offsetmv", 'o', OPTPARSE_REQUIRED}, + {"voltsuv", 'v', OPTPARSE_REQUIRED}, + {"offsetuv", 'o', OPTPARSE_REQUIRED}, + {"refinclk", 'i', OPTPARSE_REQUIRED}, + {"refoutclk",'r', OPTPARSE_REQUIRED}, {"ac", 'a', OPTPARSE_NONE}, {"term", 't', OPTPARSE_NONE}, + {"bits", 's', OPTPARSE_NONE}, + {"12bit", 'm', OPTPARSE_NONE}, + {"ext", 'e', OPTPARSE_NONE}, + {"sync", 'y', OPTPARSE_NONE}, {0} }; @@ -539,11 +890,21 @@ int main(int argc, char** argv) argCount+=2; break; case 'v': - volt_scale_mV = strtol(options.optarg, NULL, 0); + volt_scale_uV = strtol(options.optarg, NULL, 0); argCount+=2; break; case 'o': - offset_mV = strtol(options.optarg, NULL, 0); + offset_uV = strtol(options.optarg, NULL, 0); + argCount+=2; + break; + case 'i': + refInClk = true; + refclkFreq = strtol(options.optarg, NULL, 0); + argCount+=2; + break; + case 'r': + refOutClk = true; + refclkFreq = strtol(options.optarg, NULL, 0); argCount+=2; break; case 'a': @@ -554,6 +915,26 @@ int main(int argc, char** argv) term = 1; argCount++; break; + case 's': + bitslip = true; + argCount++; + break; + case 'm': + mode12bit = true; + argCount++; + break; + case 'p': + mode14bit = true; + argCount++; + break; + case 'e': + extTrigger = true; + argCount++; + break; + case 'y': + syncOut = true; + argCount++; + break; case '?': fprintf(stderr, "%s: %s\n", argv[0], options.errmsg); print_help(); @@ -568,6 +949,29 @@ int main(int argc, char** argv) exit(EXIT_FAILURE); } + if(0 == strcmp(arg, "list")) + { + uint32_t i = 0; + tsDeviceInfo_t infos; + while(TS_STATUS_OK == thunderscopeListDevices(i, &infos)) + { + if(i==0) + { + printf("Found ThunderScope(s):\n"); + } + printf("\t%3d | Serial Number: %s\n", i, infos.serial_number); + printf("\t | HW Rev: 0x%x\n", infos.hw_id); + printf("\t | GW Rev: 0x%x\n", infos.gw_id); + printf("\t | LiteX Rev: 0x%x\n", infos.litex); + i++; + } + if(i == 0) + { + printf("No devices present\n"); + } + exit(EXIT_SUCCESS); + } + if(0 == strcmp(arg, "clk")) { mcp_clkgen_conf_t test_conf[1024]; @@ -620,7 +1024,7 @@ int main(int argc, char** argv) } - printf("\x1b[1m[> FPGA/SoC Information:\x1b[0m\n"); + printf("\x1b[1m[> Device Information:\x1b[0m\n"); printf("------------------------\n"); for (i = 0; i < 256; i++) @@ -628,7 +1032,21 @@ int main(int argc, char** argv) fpga_identifier[i] = litepcie_readl(fd, CSR_IDENTIFIER_MEM_BASE + 4 * i); } printf("FPGA Identifier: %s.\n", fpga_identifier); - + uint32_t hw_info = litepcie_readl(fd, CSR_DEV_STATUS_HW_ID_ADDR); + if(hw_info & TS_HW_ID_VALID_MASK) + { + printf("HW Rev %02d - %s\n", hw_info & TS_HW_ID_REV_MASK, + (hw_info & TS_HW_ID_VARIANT_MASK) ? "TB" : "PCIe" ); + } + else + { + printf("HW Rev Beta\n"); + } + printf("Gateware Rev: 0x%08X\n", + litepcie_readl(fd, CSR_DEV_STATUS_GW_REV_ADDR)); + printf("LiteX Release: 0x%08X\n", + litepcie_readl(fd, CSR_DEV_STATUS_LITEX_REL_ADDR)); + #ifdef CSR_DNA_BASE printf("FPGA DNA: 0x%08x%08x\n", litepcie_readl(fd, CSR_DNA_ID_ADDR + 4 * 0), @@ -648,17 +1066,27 @@ int main(int argc, char** argv) // Run Example IO if(0 == strcmp(arg, "io")) { - test_io(fd); + test_io(fd, ((hw_info & TS_HW_ID_VALID_MASK) == 0)); } // Setup Channel, record samples to buffer, save buffer to file else if(0 == strcmp(arg, "capture")) { - test_capture(fd, idx, channelBitmap, bandwidth, volt_scale_mV, offset_mV, ac_couple, term); + test_capture(fd, idx, channelBitmap, bandwidth, + volt_scale_uV, offset_uV, ac_couple, term, + bitslip, mode12bit, mode14bit, + refInClk, refOutClk, refclkFreq, + extTrigger, syncOut); } + // Flash test else if(0 == strcmp(arg, "flash")) { flash_test(argv[argCount+1], fd); } + // Test REF Clock Modes + else if(0 == strcmp(arg, "clock")) + { + test_clock(idx, refOutClk, refInClk, refclkFreq); + } //Print Help else { diff --git a/example/tsControl.py b/example/tsControl.py new file mode 100755 index 0000000..11c93fa --- /dev/null +++ b/example/tsControl.py @@ -0,0 +1,329 @@ +#!/usr/bin/env python3 +""" SPDX-License-Identifier: BSD-2-Clause +@file tsControl.py + +@brief Thunderscope Control GUI + +@details +A graphical user interface (GUI) application for controlling and monitoring a Thunderscope device. +This script provides functionality to connect to a Thunderscope, configure its channels, and view its status. +It uses the Tkinter library for the GUI and interacts with the Thunderscope through the tslitex library. + +Features: +- Device connection and disconnection management. +- Channel configuration with adjustable parameters. +- Real-time status monitoring of the Thunderscope device. + +@copyright (C) 2025 / Nate Meyer / nate.devel@gmail.com +""" + +import tslitex +import tkinter as tk +from tkinter import ttk + +class TsDelegate: + """ + A delegate class to handle events and actions for the TsControl. + """ + + def __init__(self, parent): + self.parent = parent + self.ts_inst = None + self.dev_list = [] + + def on_channel_apply(self, channel, params): + print(f"Applying settings for Channel {channel}: {params}") + if self.ts_inst: + self.ts_inst.channel[channel-1].ManualCtrl(params) + + def get_devices(self): + print("Retrieving devices...") + # Return a list of devices (this can be replaced with actual logic to fetch devices) + self.dev_list = [] + idx = 0 + while True: + res, dev_info = tslitex.ThunderscopeListDevs(idx) + if res == -1: + break + else: + print(f"Found device {idx}: {dev_info}") + self.dev_list.append(f"{idx}: Thunderscope {dev_info['device_path'].decode('utf-8')}") + idx += 1 + return self.dev_list + + def connect_device(self, device): + # Handle the connect event + print(f"Connecting to device: {device}") + self.ts_inst = tslitex.Thunderscope(dev_idx=device) + self.update_status(f"Connected: Thunderscope {device}") + # Change the connect button to say disconnect + connect_button = self.parent.nametowidget('top_frame.connect_button') + connect_button.config(text="Disconnect", command=self.on_close) + + # Reset fields for all ChannelCtrl instances + for tab in self.parent.nametowidget('!notebook').winfo_children(): + if isinstance(tab, ChannelCtrl): + tab.reset_fields() + + def on_close(self): + # Handle the window close event + if self.ts_inst: + print("Deleting ts_inst...") + del self.ts_inst + self.ts_inst = None + # Change the connect button to say connect + connect_button = self.parent.nametowidget('top_frame.connect_button') + connect_button.config(text="Connect", command=lambda: self.connect_device(self.parent.nametowidget('top_frame.device_combobox').current())) + self.update_status("Disconnected") + + def update_status(self, message): + status_bar = self.parent.nametowidget('status_bar') + status_bar.config(text=message) + + +class ChannelCtrl(ttk.Frame): + """ + A class to create a channel control interface using Tkinter. + """ + + def __init__(self, parent, channel, on_channel_apply_callback): + super().__init__(parent) + self.channel = channel + self.on_channel_apply_callback = on_channel_apply_callback + self.enable = tk.BooleanVar(value=False) + self.atten = tk.BooleanVar(value=False) + self.dc_couple = tk.BooleanVar(value=False) + self.termination = tk.BooleanVar(value=False) + self.pga_high_gain = tk.BooleanVar(value=False) + self.pga_bw = tk.IntVar(value=6) + self.pga_atten = tk.IntVar(value=10) + self.trim_dac = tk.IntVar(value=2048) + self.trim_pot = tk.IntVar(value=0x40) + self.create_widgets() + + def create_widgets(self): + # Create a frame + frame = ttk.Frame(self, padding=10) + frame.pack(expand=True, fill="both") + + # Add Checkboxes + tk.Checkbutton(frame, text="Attenuation", variable=self.atten).grid(row=2, column=0, sticky="w", pady=5) + tk.Checkbutton(frame, text="Termination", variable=self.termination).grid(row=3, column=0, sticky="w", pady=5) + tk.Checkbutton(frame, text="DC Coupling", variable=self.dc_couple).grid(row=4, column=0, sticky="w", pady=5) + tk.Checkbutton(frame, text="PGA High-Gain", variable=self.pga_high_gain).grid(row=5, column=0, sticky="w", pady=5) + + # Add Text Boxes with Range Labels + ttk.Label(frame, text="Trim DAC:").grid(row=2, column=1, sticky="w", pady=5) + trim_dac_entry = ttk.Entry(frame, textvariable=self.trim_dac, width=10) + trim_dac_entry.grid(row=2, column=2, pady=5, padx=5) + ttk.Label(frame, text="(Range: 0 - 4095)").grid(row=2, column=3, sticky="w", padx=5) + + ttk.Label(frame, text="Trim Pot:").grid(row=3, column=1, sticky="w", pady=5) + trim_pot_entry = ttk.Entry(frame, textvariable=self.trim_pot, width=10) + trim_pot_entry.grid(row=3, column=2, pady=5, padx=5) + ttk.Label(frame, text="(Range: 0 - 255)").grid(row=3, column=3, sticky="w", padx=5) + + ttk.Label(frame, text="PGA Attenuation:").grid(row=4, column=1, sticky="w", pady=5) + pga_atten_entry = ttk.Entry(frame, textvariable=self.pga_atten, width=10) + pga_atten_entry.grid(row=4, column=2, pady=5, padx=5) + ttk.Label(frame, text="(Range: 0 - 10)").grid(row=4, column=3, sticky="w", padx=5) + + ttk.Label(frame, text="PGA Bandwidth:").grid(row=5, column=1, sticky="w", pady=5) + pga_bw_entry = ttk.Entry(frame, textvariable=self.pga_bw, width=10) + pga_bw_entry.grid(row=5, column=2, pady=5, padx=5) + ttk.Label(frame, text="(Range: 0 - 6)").grid(row=5, column=3, sticky="w", padx=5) + + # Add a Button + ttk.Button(frame, text="Apply", command=self.submit).grid(row=6, column=0, pady=10) + + def submit(self): + # Validate and adjust entry values + dac = int(self.trim_dac.get()) + if dac < 0: + dac = 0 + self.trim_dac.set(dac) + elif dac > 4095: + dac = 4095 + self.trim_dac.set(dac) + + dpot = int(self.trim_pot.get()) + if dpot < 0: + dpot = 0 + self.trim_pot.set(dpot) + elif dpot > 255: + dpot = 255 + self.trim_pot.set(dpot) + + pga_atten = int(self.pga_atten.get()) + if pga_atten < 0: + pga_atten = 0 + self.pga_atten.set(pga_atten) + elif pga_atten > 10: + pga_atten = 10 + self.pga_atten.set(pga_atten) + + pga_bw = int(self.pga_bw.get()) + if pga_bw < 0: + pga_bw = 0 + self.pga_bw.set(pga_bw) + elif pga_bw > 6: + pga_bw = 6 + self.pga_bw.set(pga_bw) + + # Collect parameters from the checkbuttons and entry fields + params = { + "atten": self.atten.get(), + "term": self.termination.get(), + "dc_couple": self.dc_couple.get(), + "dac": dac, + "dpot": dpot, + "pga_high_gain": self.pga_high_gain.get(), + "pga_atten": pga_atten, + "pga_bw": pga_bw + } + + # Call the provided callback with the channel and parameters + if self.on_channel_apply_callback: + self.on_channel_apply_callback(self.channel, params) + + def reset_fields(self): + # Reset checkboxes + self.atten.set(False) + self.dc_couple.set(False) + self.termination.set(False) + self.pga_high_gain.set(False) + + # Reset entry fields to their default values + self.trim_dac.set(2048) + self.trim_pot.set(0x40) + self.pga_atten.set(10) + self.pga_bw.set(6) + + +# Create the main application window +root = tk.Tk() +root.title("Thunderscope Channel Tester") +root.geometry("650x650") # Set the window size + +# Create an instance of TsDelegate +delegate = TsDelegate(root) +root.protocol("WM_DELETE_WINDOW", lambda: (delegate.on_close(), root.destroy())) + + +# Create a frame for the drop-down menu and button +top_frame = ttk.Frame(root, padding=10, name="top_frame") +top_frame.pack(side="top", fill="x") + +# Add a drop-down menu (Combobox) to the top frame +ttk.Label(top_frame, text="Select Device:").pack(side="left", padx=5) +device_combobox = ttk.Combobox(top_frame, values=delegate.get_devices(), state="readonly", name="device_combobox") +device_combobox.pack(side="left", padx=5) + +# Set the width of the combobox to increase its size +device_combobox.config(width=40) + +# Add a callback to refresh the device list when the dropdown menu is clicked +def refresh_device_list(event): + device_combobox["values"] = delegate.get_devices() + +device_combobox.bind("", refresh_device_list) + +# Add a "Connect" button to the top frame +connect_button = ttk.Button( + top_frame, + text="Connect", + command=lambda: delegate.connect_device(device_combobox.current()), + name="connect_button" +) +connect_button.pack(side="left", padx=5) + + +# Create a style for the notebook +style = ttk.Style() +style.configure("TNotebook.Tab", padding=[10, 5]) # Add padding to tabs + +# Update the notebook style to position tabs on the left side +style.configure("TNotebook", tabposition="wn") +style.configure("TNotebook", expand=True) # Configure the notebook tabs to evenly fill the width of the window +style.map("TNotebook.Tab", expand=[("selected", 1), ("!selected", 1)]) # Stretch tabs evenly + + +# Create a Notebook widget (for tabs) +notebook = ttk.Notebook(root, style="TNotebook") + + +# Modify the status tab to display labels and number fields for each key in tsScopeState_t +def create_status_tab(notebook, delegate): + status_tab = ttk.Frame(notebook) + + # Create a dictionary to hold labels for each key in tsScopeState_t + status_labels = {} + + # Define the keys from tsScopeState_t + keys = [ + "adc_sample_rate", "adc_sample_bits", "adc_sample_resolution", "adc_lost_buffer_count", + "flags", "adc_state", "adc_sync", "power_state", "pll_state", "afe_state", + "sys_health.temp_c", "sys_health.vcc_int", "sys_health.vcc_aux", + "sys_health.vcc_bram", "sys_health.frontend_power_good", "sys_health.acq_power_good", + "local_osc_clk", "ref_in_clk", "pll_lock", "pll_low", "pll_high", "pll_alt" + ] + + # Create labels and fields for each key + for idx, key in enumerate(keys): + ttk.Label(status_tab, text=key + ":").grid(row=idx, column=0, sticky="w", padx=10, pady=2) + status_labels[key] = ttk.Label(status_tab, text="N/A") + status_labels[key].grid(row=idx, column=1, sticky="w", padx=10, pady=2) + + def update_status(): + if delegate.ts_inst: + status = delegate.ts_inst.Status() + for key in keys: + # Handle nested keys like sys_health.temp_c + value = status + for part in key.split('.'): + value = value.get(part, "N/A") + status_labels[key].config(text=value) + else: + for key in keys: + status_labels[key].config(text="N/A") + + # Periodically update the status only when the tab is selected + def periodic_update(): + if notebook.index(notebook.select()) == 0: # Check if tab0 is selected + update_status() + status_tab.after(1000, periodic_update) + + # Bind events to start and stop updates + def on_tab_changed(event): + if notebook.index(notebook.select()) == 0: # Tab0 selected + periodic_update() + + notebook.bind("<>", on_tab_changed) + return status_tab + +# Add the status tab to the notebook +tab0 = create_status_tab(notebook, delegate) +notebook.add(tab0, text="Status") + + +# Create frames for each tab +tab1 = ChannelCtrl(notebook, 1, delegate.on_channel_apply) +tab2 = ChannelCtrl(notebook, 2, delegate.on_channel_apply) +tab3 = ChannelCtrl(notebook, 3, delegate.on_channel_apply) +tab4 = ChannelCtrl(notebook, 4, delegate.on_channel_apply) + +# Add tabs to the notebook +notebook.add(tab1, text="Chan 1") +notebook.add(tab2, text="Chan 2") +notebook.add(tab3, text="Chan 3") +notebook.add(tab4, text="Chan 4") + +# Pack the notebook widget into the main window +notebook.pack(expand=True, fill="both") + +# Add a status bar to the bottom of the window +status_bar = ttk.Label(root, text="Disconnected", relief="sunken", anchor="w", name="status_bar") +status_bar.pack(side="bottom", fill="x") + +# Start the Tkinter event loop +root.mainloop() diff --git a/include/thunderscope.h b/include/thunderscope.h index 56b1986..51b4b68 100644 --- a/include/thunderscope.h +++ b/include/thunderscope.h @@ -33,9 +33,11 @@ int32_t thunderscopeListDevices(uint32_t devIndex, tsDeviceInfo_t *info); * @brief Open a new Thunderscope device instance * * @param devIdx Device index to open + * @param skip_init Do not initialize device peripherals. Useful when doing gateware upgrades + * or other maintenance. * @return tsHandle_t Handle to the Thunderscope device */ -tsHandle_t thunderscopeOpen(uint32_t devIdx); +tsHandle_t thunderscopeOpen(uint32_t devIdx, bool skip_init); /** * @brief Close the Thunderscope device @@ -65,6 +67,16 @@ int32_t thunderscopeChannelConfigGet(tsHandle_t ts, uint32_t channel, tsChannelP */ int32_t thunderscopeChannelConfigSet(tsHandle_t ts, uint32_t channel, tsChannelParam_t* conf); +/** + * @brief Set the mode and frequency for the external Reference Clock + * + * @param ts Handle to the Thunderscope device + * @param mode Set the Clock IN/OUT mode + * @param refclk_freq Set the input clock frequency if in IN mode, or output frequency if in OUT mode + * @return int32_t TS_STATUS_OK if the reference clock was configured + */ +int32_t thunderscopeRefClockSet(tsHandle_t ts, tsRefClockMode_t mode, uint32_t refclk_freq); + /** * @brief Get the status for the Thunderscope device * @@ -79,10 +91,24 @@ int32_t thunderscopeStatusGet(tsHandle_t ts, tsScopeState_t* conf); * * @param ts Handle to the Thunderscope device * @param rate Sample Rate to collect (samples per second) - * @param resolution Resolution to sample at. 256 or 4096 + * @param mode Sample Capture Mode * @return int32_t TS_STATUS_OK if the Thunderscope was configured */ -int32_t thunderscopeSampleModeSet(tsHandle_t ts, uint32_t rate, uint32_t resolution); +int32_t thunderscopeSampleModeSet(tsHandle_t ts, uint32_t rate, tsSampleFormat_t mode); + +/** + * @brief Set the approximate rate at which interrupts will fire + * + * This method sets the target frequency for interrupts in the driver. A higher value will trigger + * more often, zero is invalid. The default update rate is 100Hz, or every 10ms. The driver + * configuration will be updated as the sample mode is changed. This setting will take affect the + * next time the ADC is enabled. + * + * @param ts Handle to the Thunderscope device + * @param rate Sample Interrupt Rate (interrupts per second) + * @return int32_t TS_STATUS_OK if the Thunderscope was configured + */ +int32_t thunderscopeSampleInterruptRate(tsHandle_t ts, uint32_t interrupt_rate); /** * @brief Enable or Disable @@ -103,6 +129,45 @@ int32_t thunderscopeDataEnable(tsHandle_t ts, uint8_t enable); */ int32_t thunderscopeRead(tsHandle_t ts, uint8_t* buffer, uint32_t len); +/** + * @brief Read data into a buffer and retrieve sample count + * + * @param ts Handle to the Thunderscope device + * @param buffer Pointer to a buffer to store data samples in + * @param len Length of the data buffer available + * @param count Pointer to store the sample count number of the first sample in the buffer + * @return int32_t Length of the data read into the buffer, or a negative error code + */ +int32_t thunderscopeReadCount(tsHandle_t ts, uint8_t* buffer, uint32_t len, uint64_t* count); + +/** + * @brief Configure External Sync Interface + * + * @param ts Handle to the Thunderscope device + * @param mode Set the sync interface to in, out, or disabled + * @return int32_t TS_STATUS_OK if the mode was set successfully, or a negative error code + */ +int32_t thunderscopeExtSyncConfig(tsHandle_t ts, tsSyncMode_t mode); + +/** + * @brief Assert a Sync Event on the External Sync Interface + * + * @param ts Handle to the Thunderscope device + * @return int32_t TS_STATUS_OK if the sync event was asserted set successfully, or a negative error code + */ +int32_t thunderscopeEventSyncAssert(tsHandle_t ts); + +/** + * @brief Poll for a Event + * + * Event struct will be populated with TS_EVT_NONE if there is no event available. + * + * @param ts Handle to the Thunderscope device + * @param evt Pointer to an Event struct to fill + * @return int32_t TS_STATUS_OK if polled successfully, or a negative error code + */ +int32_t thunderscopeEventGet(tsHandle_t ts, tsEvent_t* evt); + /** * @brief Load a new user firmware onto the Thunderscope * @@ -111,7 +176,43 @@ int32_t thunderscopeRead(tsHandle_t ts, uint8_t* buffer, uint32_t len); * @param len Length of the data buffer available * @return int32_t TS_STATUS_OK if the firmware was updated successfully, or a negative error code */ -int32_t thunderscopeFwUpdate(tsHandle_t ts, char* bitstream, uint32_t len); +int32_t thunderscopeFwUpdate(tsHandle_t ts, const char* bitstream, uint32_t len); + +/** + * @brief Read user-defined data blob from the Thunderscope + * + * @param ts Handle to the Thunderscope device + * @param buffer Pointer to a buffer to store the data as read. + * @param offset Offset to begin reading within the user data section + * @param readLen Length of the data buffer + * @return int32_t Length of the data read if successfull, or a negative error code + */ +int32_t thunderscopeUserDataRead(tsHandle_t ts, char* buffer, uint32_t offset, uint32_t readLen); + +/** + * @brief Write a user-defined data blob to the Thunderscope + * + * This function writes from a section of SPI Flash reserved for application-defined data, such as + * calibration data, persistent settings, etc. Because the SPI flash needs to erase on a 4k page, + * any offset or length not aligned with a 4k boundary will be read to a temporary buffer, modified, + * and written back. + * + * @param ts Handle to the Thunderscope device + * @param buffer Pointer to a buffer of data to be stored. + * @param offset Offset to begin writing within the user data section + * @param writeLen Length of the data buffer + * @return int32_t Length of the data written if successfull, or a negative error code + */ +int32_t thunderscopeUserDataWrite(tsHandle_t ts, const char* buffer, uint32_t offset, uint32_t writeLen); + +/** + * @brief Get the current progress of the firmware update + * + * @param ts Handle to the Thunderscope device + * @param progress Pointer to a variable to store the progress percentage + * @return int32_t TS_STATUS_OK if the progress was retrieved successfully, or a negative error code + */ +int32_t thunderscopeGetFwProgress(tsHandle_t ts, uint32_t* progress); #ifdef __cplusplus } diff --git a/include/ts_calibration.h b/include/ts_calibration.h index 93237cf..ae89161 100644 --- a/include/ts_calibration.h +++ b/include/ts_calibration.h @@ -17,10 +17,20 @@ extern "C" { #include #include "ts_common.h" +typedef enum tsCalAdcTest_e +{ + TS_ADC_TEST_DISABLE, + TS_ADC_TEST_SINGLE, + TS_ADC_TEST_DUAL, + TS_ADC_TEST_RAMP, + TS_ADC_TEST_DESKEW, + TS_ADC_TEST_SYNC +} tsCalAdcTest_t; + typedef struct tsChannelCalibration_s { - int32_t buffer_mV; - int32_t bias_mV; + int32_t buffer_uV; + int32_t bias_uV; int32_t attenuatorGain1M_mdB; int32_t attenuatorGain50_mdB; int32_t bufferGain_mdB; @@ -29,12 +39,18 @@ typedef struct tsChannelCalibration_s int32_t preampHighGainError_mdB; int32_t preampAttenuatorGain_mdB[11]; int32_t preampOutputGainError_mdB; - int32_t preampLowOffset_mV; - int32_t preampHighOffset_mV; + int32_t preampLowOffset_uV; + int32_t preampHighOffset_uV; int32_t preampInputBias_uA; } tsChannelCalibration_t; -typedef struct tsChannelCtrl_e +typedef struct tsAdcCalibration_s +{ + // Fine Gain Branch Adjustment + uint8_t branchFineGain[8]; +} tsAdcCalibration_t; + +typedef struct tsChannelCtrl_s { // FE Attenuator uint8_t atten; @@ -42,10 +58,10 @@ typedef struct tsChannelCtrl_e uint8_t term; // DC Coupling uint8_t dc_couple; - // Trim DAC - uint16_t dac; // Trim DPOT uint8_t dpot; + // Trim DAC + uint16_t dac; //Preamp Control uint8_t pga_high_gain; uint8_t pga_atten; @@ -55,7 +71,7 @@ typedef struct tsChannelCtrl_e typedef struct tsScopeCalibration_s { tsChannelCalibration_t afeCal[TS_NUM_CHANNELS]; - + tsAdcCalibration_t adcCal; } tsScopeCalibration_t; @@ -64,10 +80,10 @@ typedef struct tsScopeCalibration_s * * @param ts Handle to the Thunderscope device * @param channel Channel number - * @param cal TBD Calibration data + * @param cal Channel Calibration data * @return int32_t TS_STATUS_OK if the calibration was accepted -*/ -int32_t thunderscopeCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal); + */ +int32_t thunderscopeChanCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal); /** * @brief Get the calibration data for a channel on the Thunderscope device @@ -77,17 +93,90 @@ int32_t thunderscopeCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCal * @param cal Calibration Data Pointer * @return int32_t TS_STATUS_OK if the calibration was retrieved */ -int32_t thunderscopeCalibrationGet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal); +int32_t thunderscopeChanCalibrationGet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal); + +/** + * @brief Set the calibration data for the ADC on the Thunderscope device + * + * @param ts Handle to the Thunderscope device + * @param cal ADC Calibration data + * @return int32_t TS_STATUS_OK if the calibration was accepted + */ +int32_t thunderscopeAdcCalibrationSet(tsHandle_t ts, tsAdcCalibration_t *cal); + +/** + * @brief Get the calibration data for the ADC on the Thunderscope device + * + * @param ts Handle to the Thunderscope device + * @param cal ADC Calibration Data Pointer + * @return int32_t TS_STATUS_OK if the calibration was retrieved + */ +int32_t thunderscopeAdcCalibrationGet(tsHandle_t ts, tsAdcCalibration_t *cal); /** * @brief Manually set parameters for the devices in a channel to aid in calibration. * * @param ts Handle to the Thunderscope device * @param channel Channel number - * @param ctrl AFE Control parameters + * @param ctrl AFE Control Parameters Pointer * @return int32_t TS_STATUS_OK if the parameters were applied */ -int32_t thunderscopeCalibrationManualCtrl(tsHandle_t ts, uint32_t channel, tsChannelCtrl_t ctrl); +int32_t thunderscopeCalibrationManualCtrl(tsHandle_t ts, uint32_t channel, tsChannelCtrl_t *ctrl); + +/** + * @brief Manually set the ADC test pattern mode. + * + * @param ts Handle to the Thunderscope device + * @param test_mode Test Pattern (see HMCAD15xx Documentation) + * @param test_pattern Value used for Single/Dual test modes + * @return int32_t TS_STATUS_OK if the parameters were applied + */ +int32_t thunderscopeCalibrationAdcTest(tsHandle_t ts, tsCalAdcTest_t test_mode, uint32_t test_pattern); + +/****************************************************************************** + * Factory Provisioning API + * WARNING: May cause data corruption + ******************************************************************************/ +/** + * @brief Erase the entire Factory data partition + * + * @param ts Handle to the Thunderscope device + * @param dna Device DNA value for confirmation + * @return int32_t TS_STATUS_OK if the partition is erased successfully + */ +int32_t thunderscopeFactoryProvisionPrepare(tsHandle_t ts, uint64_t dna); + +/** + * @brief Append an arbitrary data item to the Factory data partition + * + * @param ts Handle to the Thunderscope device + * @param tag 32-bit Tag to identify the data item + * @param length Length of the content string + * @param content ASCII string containing a JSON object + * @return int32_t TS_STATUS_OK if the object was written successfully + */ +int32_t thunderscopeFactoryProvisionAppendTLV(tsHandle_t ts, const uint32_t tag, uint32_t length, const char* content); + +/** + * @brief Test the Factory data items are valid + * + * @param ts Handle to the Thunderscope device + * @return int32_t TS_STATUS_OK if all data item checks pass + */ +int32_t thunderscopeFactoryProvisionVerify(tsHandle_t ts); + +/** + * @brief Retrieve an item from the Factory data partition. If a NULL buffer is provided, only + * return the length of the item. + * + * @param ts Handle to the Thunderscope device + * @param tag 32-bit Tag value to lookup + * @param content_buffer Pointer for a buffer to store the read value string + * @param item_max_len Maximum size of value string that the provided buffer can hold + * @return int32_t Length of the item value or a negative error code if the Item could not be read + */ +int32_t thunderscopeFactoryReadItem(tsHandle_t ts, const uint32_t tag, char* content_buffer, uint32_t item_max_len); + #ifdef __cplusplus } #endif diff --git a/include/ts_common.h b/include/ts_common.h index d51c4ea..a9d284c 100644 --- a/include/ts_common.h +++ b/include/ts_common.h @@ -25,11 +25,23 @@ extern "C" { #define TS_IDENT_STR_LEN (256) -#define TS_MAX_SAMPLE_RATE (1000000000) -#define TS_MAX_DUAL_CH_RATE (500000000) -#define TS_MAX_QUAD_CH_RATE (250000000) +#define TS_MAX_8BIT_SAMPLE_RATE (1000000000) +#define TS_MAX_12BIT_SAMPLE_RATE (660000000) +#define TS_MAX_14BIT_SAMPLE_RATE (125000000) #define TS_MIN_SAMPLE_RATE (15000000) +#define TS_HW_ID_REV_MASK (7) +#define TS_HW_ID_VARIANT_MASK (1 << 8) +#define TS_HW_ID_VALID_MASK (1 << 9) + +#define TS_DEFUALT_CLKOUT_FREQ (10000000) //10 MHz + +#define TS_GW_VERSION(major, minor, patch) ((((major) & 0xFFFF) << 16) + \ + (((minor) & 0xFF) << 8) + \ + (((patch) & 0x3F) << 1)) + +#define LITEX_VERSION(major, minor) ((((major) & 0xFFFF) << 16) + ((minor) & 0xFFFF)) + /** * @brief Opaque Handle to a Thunderscope device instance * @@ -49,18 +61,41 @@ typedef enum tsChannelTerm_e TS_TERM_50 = 1, } tsChannelTerm_t; +typedef enum tsSampleFormat_e +{ + TS_8_BIT = 0, + TS_12_BIT_LSB, + TS_12_BIT_MSB, + TS_14_BIT +}tsSampleFormat_t; + +typedef enum tsRefClockMode_e +{ + TS_REFCLK_NONE = 0, + TS_REFCLK_OUT = 1, + TS_REFCLK_IN = 2 +} tsRefClockMode_t; + typedef struct tsDeviceInfo_s { uint32_t device_id; - char device_path[TS_IDENT_STR_LEN]; - char identity[TS_IDENT_STR_LEN]; - char serial_number[TS_IDENT_STR_LEN]; + uint32_t hw_id; /**< hw_id[9] - ID Valid, hw_id[8] - PCIe/USB, hw_id[7:4] - Reserved, hw_id[3:0] - Revision */ + uint32_t gw_id; /**< 32-bit version ID: gw_id[31:16] - Major, gw_id[15:8] - Minor, gw_id[7:1] - Patch, gw_id[0] - next */ + uint32_t litex; /**< 32-bit LiteX version: litex[31:16] - Year, litex[15:0] - Month */ + uint32_t board_rev; /**< 32-bit Board revision counter. */ + char device_path[TS_IDENT_STR_LEN]; /**< Device Path where the instance is opened */ + char identity[TS_IDENT_STR_LEN]; /**< Device Identity String */ + char serial_number[TS_IDENT_STR_LEN]; /**< Serial Number */ + char build_config[TS_IDENT_STR_LEN]; /**< Build Configuration description */ + char build_date[TS_IDENT_STR_LEN]; /**< Date of Manufacture */ + char mfg_signature[TS_IDENT_STR_LEN]; /**< Device Unique Manufacturing Signature */ + } tsDeviceInfo_t; typedef struct tsChannelParam_s { - uint32_t volt_scale_mV; /**< Set full scale voltage in millivolts */ - int32_t volt_offset_mV; /**< Set offset voltage in millivolts */ + uint32_t volt_scale_uV; /**< Set full scale voltage in microvolts */ + int32_t volt_offset_uV; /**< Set offset voltage in microvolts */ uint32_t bandwidth; /**< Set Bandwidth Filter in MHz. Next highest filter will be selected */ uint8_t coupling; /**< Select AD/DC coupling for channel. Use tsChannelCoupling_t enum */ uint8_t term; /**< Select Termination mode for channel. Use tsChannelTerm_t enum */ @@ -73,6 +108,8 @@ typedef struct sysHealth_s { uint32_t vcc_int; uint32_t vcc_aux; uint32_t vcc_bram; + uint8_t frontend_power_good; + uint8_t acq_power_good; } sysHealth_t; typedef struct tsScopeState_s @@ -86,14 +123,40 @@ typedef struct tsScopeState_s uint32_t flags; struct { uint8_t adc_state:1; + uint8_t adc_sync:1; uint8_t power_state:1; uint8_t pll_state:1; uint8_t afe_state:1; + uint8_t local_osc_clk:1; + uint8_t ref_in_clk:1; + uint8_t pll_lock:1; + uint8_t pll_low:1; + uint8_t pll_high:1; + uint8_t pll_alt:1; }; }; sysHealth_t sys_health; } tsScopeState_t; +typedef enum tsSyncMode_e +{ + TS_SYNC_DISABLED, + TS_SYNC_OUT, + TS_SYNC_IN +} tsSyncMode_t; + +typedef enum tsEventType_e +{ + TS_EVT_NONE = 0, + TS_EVT_HOST_SW, + TS_EVT_EXT_SYNC, +} tsEventType_t; + +typedef struct tsEvent_s +{ + tsEventType_t ID; + uint64_t event_sample; +} tsEvent_t; #ifdef __cplusplus } diff --git a/litepcie/CMakeLists.txt b/litepcie/CMakeLists.txt index 1ec593f..2d6c073 100644 --- a/litepcie/CMakeLists.txt +++ b/litepcie/CMakeLists.txt @@ -11,10 +11,14 @@ if(WIN32) set(litepcie_PLATFORM_HEADERS public_h/litepcie_win.h ) -else() +elseif(LINUX) set(litepcie_PLATFORM_HEADERS public_h/litepcie_linux.h ) +elseif(APPLE) +set(litepcie_PLATFORM_HEADERS + public_h/litepcie_mac.h + ) endif() set(litepcie_HEADERS @@ -44,5 +48,8 @@ set_target_properties(litepcie PROPERTIES POSITION_INDEPENDENT_CODE 1) target_include_directories(litepcie PUBLIC public_h include) if(WIN32) - target_link_libraries(litepcie setupapi) + target_link_libraries(litepcie PRIVATE setupapi) +elseif(APPLE) + target_link_libraries(litepcie "$") + target_link_libraries(litepcie "$") endif() \ No newline at end of file diff --git a/litepcie/include/litepcie_dma.h b/litepcie/include/litepcie_dma.h index 4cdc7cf..c5baf43 100644 --- a/litepcie/include/litepcie_dma.h +++ b/litepcie/include/litepcie_dma.h @@ -12,25 +12,27 @@ #define LITEPCIE_LIB_DMA_H #include +#include #include "litepcie_helpers.h" #include "litepcie.h" -#if defined(_WIN32) +#if defined(__linux__) +#include +typedef struct pollfd pollfd_t; +#else typedef struct pollfd_s { file_t fd; } pollfd_t; -#else -#include -typedef struct pollfd pollfd_t; #endif struct litepcie_dma_ctrl { uint8_t use_reader, use_writer, loopback, zero_copy; pollfd_t fds; uint32_t channel; - char *buf_rd, *buf_wr; + uint8_t *buf_rd, *buf_wr; + size_t buf_wr_size, buf_rd_size; int64_t reader_hw_count, reader_sw_count, reader_dropped_count; int64_t writer_hw_count, writer_sw_count, writer_dropped_count; unsigned buffers_available_read, buffers_available_write; @@ -39,12 +41,12 @@ struct litepcie_dma_ctrl { struct litepcie_ioctl_mmap_dma_update mmap_dma_update; }; -void litepcie_dma_set_loopback(file_t fd, uint8_t loopback_enable); -void litepcie_dma_reader(file_t fd, uint8_t enable, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count); -void litepcie_dma_writer(file_t fd, uint8_t enable, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count); +void litepcie_dma_set_loopback(struct litepcie_dma_ctrl *dma, uint8_t loopback_enable); +void litepcie_dma_reader(struct litepcie_dma_ctrl *dma, uint8_t enable, uint32_t intr_count, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count); +void litepcie_dma_writer(struct litepcie_dma_ctrl *dma, uint8_t enable, uint32_t intr_count, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count); -uint8_t litepcie_request_dma(file_t fd, uint8_t reader, uint8_t writer); -void litepcie_release_dma(file_t fd, uint8_t reader, uint8_t writer); +uint8_t litepcie_request_dma(struct litepcie_dma_ctrl *dma, uint8_t reader, uint8_t writer); +void litepcie_release_dma(struct litepcie_dma_ctrl *dma, uint8_t reader, uint8_t writer); int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *device_name, uint8_t zero_copy); void litepcie_dma_cleanup(struct litepcie_dma_ctrl *dma); diff --git a/litepcie/include/litepcie_helpers.h b/litepcie/include/litepcie_helpers.h index 86a17eb..f379438 100644 --- a/litepcie/include/litepcie_helpers.h +++ b/litepcie/include/litepcie_helpers.h @@ -26,6 +26,19 @@ void _check_ioctl(int status, const char* file, int line); #define LITEPCIE_CTRL_NAME(ID) "\\CTRL" #ID #define LITEPCIE_DMA_NAME(ID, CHAN) "\\DMA" #ID #CHAN +#elif defined(__APPLE__) +#include +#include +#include +#include +typedef io_connect_t file_t; + +#define ioctl_args(fd, op, data) fd, op, &(data), (size_t)sizeof(data), &(data), &outlen +#define checked_ioctl(...) _check_ioctl((int)IOConnectCallStructMethod(__VA_ARGS__), __FILE__, __LINE__) +void _check_ioctl(int status, const char* file, int line); + +#define LITEPCIE_CTRL_NAME(ID) "thunderscope" #ID +#define LITEPCIE_DMA_NAME(ID, CHAN) "thunderscope" #ID #else #include typedef int file_t; diff --git a/litepcie/public_h/csr.h b/litepcie/public_h/csr.h index c5facdb..33c3153 100644 --- a/litepcie/public_h/csr.h +++ b/litepcie/public_h/csr.h @@ -1,5 +1,5 @@ //-------------------------------------------------------------------------------- -// Auto-generated by LiteX (f63d4a833) on 2024-12-26 19:07:59 +// Auto-generated by LiteX (a1ea5a2f6) on 2025-12-31 15:49:53 //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- @@ -267,26 +267,53 @@ /* XADC Fields */ +/* DEV_STATUS Registers */ +#define CSR_DEV_STATUS_BASE (CSR_BASE + 0x6000L) +#define CSR_DEV_STATUS_LEDS_ADDR (CSR_BASE + 0x6000L) +#define CSR_DEV_STATUS_LEDS_SIZE 1 +#define CSR_DEV_STATUS_HW_ID_ADDR (CSR_BASE + 0x6004L) +#define CSR_DEV_STATUS_HW_ID_SIZE 1 +#define CSR_DEV_STATUS_GW_REV_ADDR (CSR_BASE + 0x6008L) +#define CSR_DEV_STATUS_GW_REV_SIZE 1 +#define CSR_DEV_STATUS_LITEX_REL_ADDR (CSR_BASE + 0x600cL) +#define CSR_DEV_STATUS_LITEX_REL_SIZE 1 + +/* DEV_STATUS Fields */ +#define CSR_DEV_STATUS_HW_ID_HW_REV_OFFSET 0 +#define CSR_DEV_STATUS_HW_ID_HW_REV_SIZE 3 +#define CSR_DEV_STATUS_HW_ID_HW_VARIANT_OFFSET 8 +#define CSR_DEV_STATUS_HW_ID_HW_VARIANT_SIZE 1 +#define CSR_DEV_STATUS_HW_ID_HW_VALID_OFFSET 9 +#define CSR_DEV_STATUS_HW_ID_HW_VALID_SIZE 1 +#define CSR_DEV_STATUS_HW_ID_NUM_LEDS_OFFSET 16 +#define CSR_DEV_STATUS_HW_ID_NUM_LEDS_SIZE 4 + /* ADC Registers */ -#define CSR_ADC_BASE (CSR_BASE + 0x6000L) -#define CSR_ADC_CONTROL_ADDR (CSR_BASE + 0x6000L) +#define CSR_ADC_BASE (CSR_BASE + 0x6800L) +#define CSR_ADC_CONTROL_ADDR (CSR_BASE + 0x6800L) #define CSR_ADC_CONTROL_SIZE 1 -#define CSR_ADC_TRIGGER_CONTROL_ADDR (CSR_BASE + 0x6004L) +#define CSR_ADC_STATUS_ADDR (CSR_BASE + 0x6804L) +#define CSR_ADC_STATUS_SIZE 1 +#define CSR_ADC_TRIGGER_CONTROL_ADDR (CSR_BASE + 0x6808L) #define CSR_ADC_TRIGGER_CONTROL_SIZE 1 -#define CSR_ADC_HAD1511_CONTROL_ADDR (CSR_BASE + 0x6008L) -#define CSR_ADC_HAD1511_CONTROL_SIZE 1 -#define CSR_ADC_HAD1511_STATUS_ADDR (CSR_BASE + 0x600cL) -#define CSR_ADC_HAD1511_STATUS_SIZE 1 -#define CSR_ADC_HAD1511_DOWNSAMPLING_ADDR (CSR_BASE + 0x6010L) -#define CSR_ADC_HAD1511_DOWNSAMPLING_SIZE 1 -#define CSR_ADC_HAD1511_RANGE_ADDR (CSR_BASE + 0x6014L) -#define CSR_ADC_HAD1511_RANGE_SIZE 1 -#define CSR_ADC_HAD1511_BITSLIP_COUNT_ADDR (CSR_BASE + 0x6018L) -#define CSR_ADC_HAD1511_BITSLIP_COUNT_SIZE 1 -#define CSR_ADC_HAD1511_SAMPLE_COUNT_ADDR (CSR_BASE + 0x601cL) -#define CSR_ADC_HAD1511_SAMPLE_COUNT_SIZE 1 -#define CSR_ADC_HAD1511_DATA_CHANNELS_ADDR (CSR_BASE + 0x6020L) -#define CSR_ADC_HAD1511_DATA_CHANNELS_SIZE 1 +#define CSR_ADC_HMCAD1520_CONTROL_ADDR (CSR_BASE + 0x680cL) +#define CSR_ADC_HMCAD1520_CONTROL_SIZE 1 +#define CSR_ADC_HMCAD1520_STATUS_ADDR (CSR_BASE + 0x6810L) +#define CSR_ADC_HMCAD1520_STATUS_SIZE 1 +#define CSR_ADC_HMCAD1520_DOWNSAMPLING_ADDR (CSR_BASE + 0x6814L) +#define CSR_ADC_HMCAD1520_DOWNSAMPLING_SIZE 1 +#define CSR_ADC_HMCAD1520_RANGE_ADDR (CSR_BASE + 0x6818L) +#define CSR_ADC_HMCAD1520_RANGE_SIZE 1 +#define CSR_ADC_HMCAD1520_BITSLIP_COUNT_ADDR (CSR_BASE + 0x681cL) +#define CSR_ADC_HMCAD1520_BITSLIP_COUNT_SIZE 1 +#define CSR_ADC_HMCAD1520_SAMPLE_COUNT_ADDR (CSR_BASE + 0x6820L) +#define CSR_ADC_HMCAD1520_SAMPLE_COUNT_SIZE 1 +#define CSR_ADC_HMCAD1520_DATA_CHANNELS_ADDR (CSR_BASE + 0x6824L) +#define CSR_ADC_HMCAD1520_DATA_CHANNELS_SIZE 1 +#define CSR_ADC_HMCAD1520_SAMPLE_BITS_ADDR (CSR_BASE + 0x6828L) +#define CSR_ADC_HMCAD1520_SAMPLE_BITS_SIZE 1 +#define CSR_ADC_HMCAD1520_FRAME_DEBUG_ADDR (CSR_BASE + 0x682cL) +#define CSR_ADC_HMCAD1520_FRAME_DEBUG_SIZE 1 /* ADC Fields */ #define CSR_ADC_CONTROL_ACQ_EN_OFFSET 0 @@ -297,33 +324,45 @@ #define CSR_ADC_CONTROL_RST_SIZE 1 #define CSR_ADC_CONTROL_PWR_DOWN_OFFSET 3 #define CSR_ADC_CONTROL_PWR_DOWN_SIZE 1 +#define CSR_ADC_STATUS_ACQ_PG_OFFSET 0 +#define CSR_ADC_STATUS_ACQ_PG_SIZE 1 +#define CSR_ADC_STATUS_FRAME_SYNC_OFFSET 1 +#define CSR_ADC_STATUS_FRAME_SYNC_SIZE 1 #define CSR_ADC_TRIGGER_CONTROL_ENABLE_OFFSET 0 #define CSR_ADC_TRIGGER_CONTROL_ENABLE_SIZE 1 -#define CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET 0 -#define CSR_ADC_HAD1511_CONTROL_FRAME_RST_SIZE 1 -#define CSR_ADC_HAD1511_CONTROL_DELAY_RST_OFFSET 1 -#define CSR_ADC_HAD1511_CONTROL_DELAY_RST_SIZE 1 -#define CSR_ADC_HAD1511_CONTROL_DELAY_INC_OFFSET 2 -#define CSR_ADC_HAD1511_CONTROL_DELAY_INC_SIZE 1 -#define CSR_ADC_HAD1511_CONTROL_STAT_RST_OFFSET 3 -#define CSR_ADC_HAD1511_CONTROL_STAT_RST_SIZE 1 -#define CSR_ADC_HAD1511_RANGE_MIN01_OFFSET 0 -#define CSR_ADC_HAD1511_RANGE_MIN01_SIZE 8 -#define CSR_ADC_HAD1511_RANGE_MAX01_OFFSET 8 -#define CSR_ADC_HAD1511_RANGE_MAX01_SIZE 8 -#define CSR_ADC_HAD1511_RANGE_MIN23_OFFSET 16 -#define CSR_ADC_HAD1511_RANGE_MIN23_SIZE 8 -#define CSR_ADC_HAD1511_RANGE_MAX23_OFFSET 24 -#define CSR_ADC_HAD1511_RANGE_MAX23_SIZE 8 -#define CSR_ADC_HAD1511_DATA_CHANNELS_SHUFFLE_OFFSET 0 -#define CSR_ADC_HAD1511_DATA_CHANNELS_SHUFFLE_SIZE 2 -#define CSR_ADC_HAD1511_DATA_CHANNELS_RUN_LENGTH_OFFSET 2 -#define CSR_ADC_HAD1511_DATA_CHANNELS_RUN_LENGTH_SIZE 6 +#define CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_OFFSET 0 +#define CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_SIZE 1 +#define CSR_ADC_HMCAD1520_CONTROL_DELAY_RST_OFFSET 1 +#define CSR_ADC_HMCAD1520_CONTROL_DELAY_RST_SIZE 1 +#define CSR_ADC_HMCAD1520_CONTROL_DELAY_INC_OFFSET 2 +#define CSR_ADC_HMCAD1520_CONTROL_DELAY_INC_SIZE 1 +#define CSR_ADC_HMCAD1520_CONTROL_STAT_RST_OFFSET 3 +#define CSR_ADC_HMCAD1520_CONTROL_STAT_RST_SIZE 1 +#define CSR_ADC_HMCAD1520_RANGE_MIN01_OFFSET 0 +#define CSR_ADC_HMCAD1520_RANGE_MIN01_SIZE 8 +#define CSR_ADC_HMCAD1520_RANGE_MAX01_OFFSET 8 +#define CSR_ADC_HMCAD1520_RANGE_MAX01_SIZE 8 +#define CSR_ADC_HMCAD1520_RANGE_MIN23_OFFSET 16 +#define CSR_ADC_HMCAD1520_RANGE_MIN23_SIZE 8 +#define CSR_ADC_HMCAD1520_RANGE_MAX23_OFFSET 24 +#define CSR_ADC_HMCAD1520_RANGE_MAX23_SIZE 8 +#define CSR_ADC_HMCAD1520_DATA_CHANNELS_SHUFFLE_OFFSET 0 +#define CSR_ADC_HMCAD1520_DATA_CHANNELS_SHUFFLE_SIZE 4 +#define CSR_ADC_HMCAD1520_DATA_CHANNELS_RUN_LENGTH_OFFSET 8 +#define CSR_ADC_HMCAD1520_DATA_CHANNELS_RUN_LENGTH_SIZE 6 +#define CSR_ADC_HMCAD1520_SAMPLE_BITS_DATA_WIDTH_OFFSET 0 +#define CSR_ADC_HMCAD1520_SAMPLE_BITS_DATA_WIDTH_SIZE 2 +#define CSR_ADC_HMCAD1520_FRAME_DEBUG_FRAME_CLK_OFFSET 0 +#define CSR_ADC_HMCAD1520_FRAME_DEBUG_FRAME_CLK_SIZE 8 +#define CSR_ADC_HMCAD1520_FRAME_DEBUG_FRAME_VALID_OFFSET 8 +#define CSR_ADC_HMCAD1520_FRAME_DEBUG_FRAME_VALID_SIZE 1 /* FRONTEND Registers */ -#define CSR_FRONTEND_BASE (CSR_BASE + 0x6800L) -#define CSR_FRONTEND_CONTROL_ADDR (CSR_BASE + 0x6800L) +#define CSR_FRONTEND_BASE (CSR_BASE + 0x7000L) +#define CSR_FRONTEND_CONTROL_ADDR (CSR_BASE + 0x7000L) #define CSR_FRONTEND_CONTROL_SIZE 1 +#define CSR_FRONTEND_STATUS_ADDR (CSR_BASE + 0x7004L) +#define CSR_FRONTEND_STATUS_SIZE 1 /* FRONTEND Fields */ #define CSR_FRONTEND_CONTROL_FE_EN_OFFSET 0 @@ -334,93 +373,181 @@ #define CSR_FRONTEND_CONTROL_ATTENUATION_SIZE 4 #define CSR_FRONTEND_CONTROL_TERMINATION_OFFSET 24 #define CSR_FRONTEND_CONTROL_TERMINATION_SIZE 4 - -/* I2C Registers */ -#define CSR_I2C_BASE (CSR_BASE + 0x7000L) -#define CSR_I2C_PHY_SPEED_MODE_ADDR (CSR_BASE + 0x7000L) -#define CSR_I2C_PHY_SPEED_MODE_SIZE 1 -#define CSR_I2C_MASTER_ACTIVE_ADDR (CSR_BASE + 0x7004L) -#define CSR_I2C_MASTER_ACTIVE_SIZE 1 -#define CSR_I2C_MASTER_SETTINGS_ADDR (CSR_BASE + 0x7008L) -#define CSR_I2C_MASTER_SETTINGS_SIZE 1 -#define CSR_I2C_MASTER_ADDR_ADDR (CSR_BASE + 0x700cL) -#define CSR_I2C_MASTER_ADDR_SIZE 1 -#define CSR_I2C_MASTER_RXTX_ADDR (CSR_BASE + 0x7010L) -#define CSR_I2C_MASTER_RXTX_SIZE 1 -#define CSR_I2C_MASTER_STATUS_ADDR (CSR_BASE + 0x7014L) -#define CSR_I2C_MASTER_STATUS_SIZE 1 - -/* I2C Fields */ -#define CSR_I2C_MASTER_SETTINGS_LEN_TX_OFFSET 0 -#define CSR_I2C_MASTER_SETTINGS_LEN_TX_SIZE 3 -#define CSR_I2C_MASTER_SETTINGS_LEN_RX_OFFSET 8 -#define CSR_I2C_MASTER_SETTINGS_LEN_RX_SIZE 3 -#define CSR_I2C_MASTER_SETTINGS_RECOVER_OFFSET 16 -#define CSR_I2C_MASTER_SETTINGS_RECOVER_SIZE 1 -#define CSR_I2C_MASTER_STATUS_TX_READY_OFFSET 0 -#define CSR_I2C_MASTER_STATUS_TX_READY_SIZE 1 -#define CSR_I2C_MASTER_STATUS_RX_READY_OFFSET 1 -#define CSR_I2C_MASTER_STATUS_RX_READY_SIZE 1 -#define CSR_I2C_MASTER_STATUS_NACK_OFFSET 8 -#define CSR_I2C_MASTER_STATUS_NACK_SIZE 1 -#define CSR_I2C_MASTER_STATUS_TX_UNFINISHED_OFFSET 16 -#define CSR_I2C_MASTER_STATUS_TX_UNFINISHED_SIZE 1 -#define CSR_I2C_MASTER_STATUS_RX_UNFINISHED_OFFSET 17 -#define CSR_I2C_MASTER_STATUS_RX_UNFINISHED_SIZE 1 - -/* LEDS Registers */ -#define CSR_LEDS_BASE (CSR_BASE + 0x7800L) -#define CSR_LEDS_OUT_ADDR (CSR_BASE + 0x7800L) -#define CSR_LEDS_OUT_SIZE 1 -#define CSR_LEDS_PWM_ENABLE_ADDR (CSR_BASE + 0x7804L) -#define CSR_LEDS_PWM_ENABLE_SIZE 1 -#define CSR_LEDS_PWM_WIDTH_ADDR (CSR_BASE + 0x7808L) -#define CSR_LEDS_PWM_WIDTH_SIZE 1 -#define CSR_LEDS_PWM_PERIOD_ADDR (CSR_BASE + 0x780cL) -#define CSR_LEDS_PWM_PERIOD_SIZE 1 - -/* LEDS Fields */ - -/* MAIN_SPI Registers */ -#define CSR_MAIN_SPI_BASE (CSR_BASE + 0x8000L) -#define CSR_MAIN_SPI_CONTROL_ADDR (CSR_BASE + 0x8000L) -#define CSR_MAIN_SPI_CONTROL_SIZE 1 -#define CSR_MAIN_SPI_STATUS_ADDR (CSR_BASE + 0x8004L) -#define CSR_MAIN_SPI_STATUS_SIZE 1 -#define CSR_MAIN_SPI_MOSI_ADDR (CSR_BASE + 0x8008L) -#define CSR_MAIN_SPI_MOSI_SIZE 1 -#define CSR_MAIN_SPI_MISO_ADDR (CSR_BASE + 0x800cL) -#define CSR_MAIN_SPI_MISO_SIZE 1 -#define CSR_MAIN_SPI_CS_ADDR (CSR_BASE + 0x8010L) -#define CSR_MAIN_SPI_CS_SIZE 1 -#define CSR_MAIN_SPI_LOOPBACK_ADDR (CSR_BASE + 0x8014L) -#define CSR_MAIN_SPI_LOOPBACK_SIZE 1 - -/* MAIN_SPI Fields */ -#define CSR_MAIN_SPI_CONTROL_START_OFFSET 0 -#define CSR_MAIN_SPI_CONTROL_START_SIZE 1 -#define CSR_MAIN_SPI_CONTROL_LENGTH_OFFSET 8 -#define CSR_MAIN_SPI_CONTROL_LENGTH_SIZE 8 -#define CSR_MAIN_SPI_STATUS_DONE_OFFSET 0 -#define CSR_MAIN_SPI_STATUS_DONE_SIZE 1 -#define CSR_MAIN_SPI_STATUS_MODE_OFFSET 1 -#define CSR_MAIN_SPI_STATUS_MODE_SIZE 1 -#define CSR_MAIN_SPI_CS_SEL_OFFSET 0 -#define CSR_MAIN_SPI_CS_SEL_SIZE 5 -#define CSR_MAIN_SPI_CS_MODE_OFFSET 16 -#define CSR_MAIN_SPI_CS_MODE_SIZE 1 -#define CSR_MAIN_SPI_LOOPBACK_MODE_OFFSET 0 -#define CSR_MAIN_SPI_LOOPBACK_MODE_SIZE 1 +#define CSR_FRONTEND_STATUS_FE_PG_OFFSET 0 +#define CSR_FRONTEND_STATUS_FE_PG_SIZE 1 /* PROBE_COMPENSATION Registers */ -#define CSR_PROBE_COMPENSATION_BASE (CSR_BASE + 0x8800L) -#define CSR_PROBE_COMPENSATION_ENABLE_ADDR (CSR_BASE + 0x8800L) +#define CSR_PROBE_COMPENSATION_BASE (CSR_BASE + 0x7800L) +#define CSR_PROBE_COMPENSATION_ENABLE_ADDR (CSR_BASE + 0x7800L) #define CSR_PROBE_COMPENSATION_ENABLE_SIZE 1 -#define CSR_PROBE_COMPENSATION_WIDTH_ADDR (CSR_BASE + 0x8804L) +#define CSR_PROBE_COMPENSATION_WIDTH_ADDR (CSR_BASE + 0x7804L) #define CSR_PROBE_COMPENSATION_WIDTH_SIZE 1 -#define CSR_PROBE_COMPENSATION_PERIOD_ADDR (CSR_BASE + 0x8808L) +#define CSR_PROBE_COMPENSATION_PERIOD_ADDR (CSR_BASE + 0x7808L) #define CSR_PROBE_COMPENSATION_PERIOD_SIZE 1 /* PROBE_COMPENSATION Fields */ +/* I2CBUS Registers */ +#define CSR_I2CBUS_BASE (CSR_BASE + 0x8000L) +#define CSR_I2CBUS_I2C0_PHY_SPEED_MODE_ADDR (CSR_BASE + 0x8000L) +#define CSR_I2CBUS_I2C0_PHY_SPEED_MODE_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_ACTIVE_ADDR (CSR_BASE + 0x8004L) +#define CSR_I2CBUS_I2C0_MASTER_ACTIVE_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_ADDR (CSR_BASE + 0x8008L) +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_ADDR_ADDR (CSR_BASE + 0x800cL) +#define CSR_I2CBUS_I2C0_MASTER_ADDR_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_RXTX_ADDR (CSR_BASE + 0x8010L) +#define CSR_I2CBUS_I2C0_MASTER_RXTX_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_ADDR (CSR_BASE + 0x8014L) +#define CSR_I2CBUS_I2C0_MASTER_STATUS_SIZE 1 +#define CSR_I2CBUS_I2C1_PHY_SPEED_MODE_ADDR (CSR_BASE + 0x8018L) +#define CSR_I2CBUS_I2C1_PHY_SPEED_MODE_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_ACTIVE_ADDR (CSR_BASE + 0x801cL) +#define CSR_I2CBUS_I2C1_MASTER_ACTIVE_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_ADDR (CSR_BASE + 0x8020L) +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_ADDR_ADDR (CSR_BASE + 0x8024L) +#define CSR_I2CBUS_I2C1_MASTER_ADDR_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_RXTX_ADDR (CSR_BASE + 0x8028L) +#define CSR_I2CBUS_I2C1_MASTER_RXTX_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_ADDR (CSR_BASE + 0x802cL) +#define CSR_I2CBUS_I2C1_MASTER_STATUS_SIZE 1 + +/* I2CBUS Fields */ +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_LEN_TX_OFFSET 0 +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_LEN_TX_SIZE 3 +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_LEN_RX_OFFSET 8 +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_LEN_RX_SIZE 3 +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_RECOVER_OFFSET 16 +#define CSR_I2CBUS_I2C0_MASTER_SETTINGS_RECOVER_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_TX_READY_OFFSET 0 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_TX_READY_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_RX_READY_OFFSET 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_RX_READY_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_NACK_OFFSET 8 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_NACK_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_TX_UNFINISHED_OFFSET 16 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_TX_UNFINISHED_SIZE 1 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_RX_UNFINISHED_OFFSET 17 +#define CSR_I2CBUS_I2C0_MASTER_STATUS_RX_UNFINISHED_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_LEN_TX_OFFSET 0 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_LEN_TX_SIZE 3 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_LEN_RX_OFFSET 8 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_LEN_RX_SIZE 3 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_RECOVER_OFFSET 16 +#define CSR_I2CBUS_I2C1_MASTER_SETTINGS_RECOVER_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_TX_READY_OFFSET 0 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_TX_READY_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_RX_READY_OFFSET 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_RX_READY_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_NACK_OFFSET 8 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_NACK_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_TX_UNFINISHED_OFFSET 16 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_TX_UNFINISHED_SIZE 1 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_RX_UNFINISHED_OFFSET 17 +#define CSR_I2CBUS_I2C1_MASTER_STATUS_RX_UNFINISHED_SIZE 1 + +/* SPIBUS Registers */ +#define CSR_SPIBUS_BASE (CSR_BASE + 0x8800L) +#define CSR_SPIBUS_SPI0_CONTROL_ADDR (CSR_BASE + 0x8800L) +#define CSR_SPIBUS_SPI0_CONTROL_SIZE 1 +#define CSR_SPIBUS_SPI0_STATUS_ADDR (CSR_BASE + 0x8804L) +#define CSR_SPIBUS_SPI0_STATUS_SIZE 1 +#define CSR_SPIBUS_SPI0_MOSI_ADDR (CSR_BASE + 0x8808L) +#define CSR_SPIBUS_SPI0_MOSI_SIZE 1 +#define CSR_SPIBUS_SPI0_MISO_ADDR (CSR_BASE + 0x880cL) +#define CSR_SPIBUS_SPI0_MISO_SIZE 1 +#define CSR_SPIBUS_SPI0_CS_ADDR (CSR_BASE + 0x8810L) +#define CSR_SPIBUS_SPI0_CS_SIZE 1 +#define CSR_SPIBUS_SPI0_LOOPBACK_ADDR (CSR_BASE + 0x8814L) +#define CSR_SPIBUS_SPI0_LOOPBACK_SIZE 1 +#define CSR_SPIBUS_SPI1_CONTROL_ADDR (CSR_BASE + 0x8818L) +#define CSR_SPIBUS_SPI1_CONTROL_SIZE 1 +#define CSR_SPIBUS_SPI1_STATUS_ADDR (CSR_BASE + 0x881cL) +#define CSR_SPIBUS_SPI1_STATUS_SIZE 1 +#define CSR_SPIBUS_SPI1_MOSI_ADDR (CSR_BASE + 0x8820L) +#define CSR_SPIBUS_SPI1_MOSI_SIZE 1 +#define CSR_SPIBUS_SPI1_MISO_ADDR (CSR_BASE + 0x8824L) +#define CSR_SPIBUS_SPI1_MISO_SIZE 1 +#define CSR_SPIBUS_SPI1_CS_ADDR (CSR_BASE + 0x8828L) +#define CSR_SPIBUS_SPI1_CS_SIZE 1 +#define CSR_SPIBUS_SPI1_LOOPBACK_ADDR (CSR_BASE + 0x882cL) +#define CSR_SPIBUS_SPI1_LOOPBACK_SIZE 1 + +/* SPIBUS Fields */ +#define CSR_SPIBUS_SPI0_CONTROL_START_OFFSET 0 +#define CSR_SPIBUS_SPI0_CONTROL_START_SIZE 1 +#define CSR_SPIBUS_SPI0_CONTROL_LENGTH_OFFSET 8 +#define CSR_SPIBUS_SPI0_CONTROL_LENGTH_SIZE 8 +#define CSR_SPIBUS_SPI0_STATUS_DONE_OFFSET 0 +#define CSR_SPIBUS_SPI0_STATUS_DONE_SIZE 1 +#define CSR_SPIBUS_SPI0_STATUS_MODE_OFFSET 1 +#define CSR_SPIBUS_SPI0_STATUS_MODE_SIZE 1 +#define CSR_SPIBUS_SPI0_CS_SEL_OFFSET 0 +#define CSR_SPIBUS_SPI0_CS_SEL_SIZE 4 +#define CSR_SPIBUS_SPI0_CS_MODE_OFFSET 16 +#define CSR_SPIBUS_SPI0_CS_MODE_SIZE 1 +#define CSR_SPIBUS_SPI0_LOOPBACK_MODE_OFFSET 0 +#define CSR_SPIBUS_SPI0_LOOPBACK_MODE_SIZE 1 +#define CSR_SPIBUS_SPI1_CONTROL_START_OFFSET 0 +#define CSR_SPIBUS_SPI1_CONTROL_START_SIZE 1 +#define CSR_SPIBUS_SPI1_CONTROL_LENGTH_OFFSET 8 +#define CSR_SPIBUS_SPI1_CONTROL_LENGTH_SIZE 8 +#define CSR_SPIBUS_SPI1_STATUS_DONE_OFFSET 0 +#define CSR_SPIBUS_SPI1_STATUS_DONE_SIZE 1 +#define CSR_SPIBUS_SPI1_STATUS_MODE_OFFSET 1 +#define CSR_SPIBUS_SPI1_STATUS_MODE_SIZE 1 +#define CSR_SPIBUS_SPI1_CS_SEL_OFFSET 0 +#define CSR_SPIBUS_SPI1_CS_SEL_SIZE 1 +#define CSR_SPIBUS_SPI1_CS_MODE_OFFSET 16 +#define CSR_SPIBUS_SPI1_CS_MODE_SIZE 1 +#define CSR_SPIBUS_SPI1_LOOPBACK_MODE_OFFSET 0 +#define CSR_SPIBUS_SPI1_LOOPBACK_MODE_SIZE 1 + +/* EVENTS Registers */ +#define CSR_EVENTS_BASE (CSR_BASE + 0x9000L) +#define CSR_EVENTS_ENGINE_CONTROL_ADDR (CSR_BASE + 0x9000L) +#define CSR_EVENTS_ENGINE_CONTROL_SIZE 1 +#define CSR_EVENTS_ENGINE_STATUS_ADDR (CSR_BASE + 0x9004L) +#define CSR_EVENTS_ENGINE_STATUS_SIZE 1 +#define CSR_EVENTS_ENGINE_EVENT_ADDR (CSR_BASE + 0x9008L) +#define CSR_EVENTS_ENGINE_EVENT_SIZE 1 +#define CSR_EVENTS_ENGINE_FIFO_READSOURCE_ADDR (CSR_BASE + 0x900cL) +#define CSR_EVENTS_ENGINE_FIFO_READSOURCE_SIZE 1 +#define CSR_EVENTS_ENGINE_FIFO_READMARKER_ADDR (CSR_BASE + 0x9010L) +#define CSR_EVENTS_ENGINE_FIFO_READMARKER_SIZE 2 +#define CSR_EVENTS_GENERATOR_CONTROL_ADDR (CSR_BASE + 0x9018L) +#define CSR_EVENTS_GENERATOR_CONTROL_SIZE 1 +#define CSR_EVENTS_GENERATOR_TIMEOUT_ADDR (CSR_BASE + 0x901cL) +#define CSR_EVENTS_GENERATOR_TIMEOUT_SIZE 1 +#define CSR_EVENTS_EXT_SYNC_CONTROL_ADDR (CSR_BASE + 0x9020L) +#define CSR_EVENTS_EXT_SYNC_CONTROL_SIZE 1 +#define CSR_EVENTS_EXT_SYNC_PULSE_LEN_ADDR (CSR_BASE + 0x9024L) +#define CSR_EVENTS_EXT_SYNC_PULSE_LEN_SIZE 1 +#define CSR_EVENTS_EXT_SYNC_STATUS_ADDR (CSR_BASE + 0x9028L) +#define CSR_EVENTS_EXT_SYNC_STATUS_SIZE 1 + +/* EVENTS Fields */ +#define CSR_EVENTS_ENGINE_CONTROL_IN_EN_MASK_OFFSET 0 +#define CSR_EVENTS_ENGINE_CONTROL_IN_EN_MASK_SIZE 12 +#define CSR_EVENTS_ENGINE_CONTROL_OUT_EN_MASK_OFFSET 16 +#define CSR_EVENTS_ENGINE_CONTROL_OUT_EN_MASK_SIZE 12 +#define CSR_EVENTS_ENGINE_CONTROL_EVENT_FLUSH_OFFSET 31 +#define CSR_EVENTS_ENGINE_CONTROL_EVENT_FLUSH_SIZE 1 +#define CSR_EVENTS_ENGINE_STATUS_IN_STAT_OFFSET 0 +#define CSR_EVENTS_ENGINE_STATUS_IN_STAT_SIZE 12 +#define CSR_EVENTS_ENGINE_EVENT_PENDING_OFFSET 0 +#define CSR_EVENTS_ENGINE_EVENT_PENDING_SIZE 1 +#define CSR_EVENTS_ENGINE_FIFO_READSOURCE_SOURCE_OFFSET 0 +#define CSR_EVENTS_ENGINE_FIFO_READSOURCE_SOURCE_SIZE 4 +#define CSR_EVENTS_GENERATOR_CONTROL_IMMEDIATE_OFFSET 0 +#define CSR_EVENTS_GENERATOR_CONTROL_IMMEDIATE_SIZE 1 +#define CSR_EVENTS_GENERATOR_CONTROL_PERIODIC_OFFSET 1 +#define CSR_EVENTS_GENERATOR_CONTROL_PERIODIC_SIZE 1 +#define CSR_EVENTS_EXT_SYNC_STATUS_EVT_IN_OFFSET 0 +#define CSR_EVENTS_EXT_SYNC_STATUS_EVT_IN_SIZE 1 +#define CSR_EVENTS_EXT_SYNC_STATUS_EVT_OUT_OFFSET 8 +#define CSR_EVENTS_EXT_SYNC_STATUS_EVT_OUT_SIZE 1 + #endif /* ! __GENERATED_CSR_H */ diff --git a/litepcie/public_h/litepcie.h b/litepcie/public_h/litepcie.h index ed28e42..b1e6e44 100644 --- a/litepcie/public_h/litepcie.h +++ b/litepcie/public_h/litepcie.h @@ -13,8 +13,12 @@ extern "C" { #ifdef _WIN32 #include "litepcie_win.h" -#else +#elif __linux__ #include "litepcie_linux.h" +#elif __APPLE__ +#include "litepcie_mac.h" +#else +#error "UNSUPPORTED PLATFORM" #endif #ifdef __cplusplus diff --git a/litepcie/public_h/litepcie_linux.h b/litepcie/public_h/litepcie_linux.h index d284165..96ee48f 100644 --- a/litepcie/public_h/litepcie_linux.h +++ b/litepcie/public_h/litepcie_linux.h @@ -39,6 +39,7 @@ struct litepcie_ioctl_dma { struct litepcie_ioctl_dma_writer { uint8_t enable; + uint32_t interrupt_count; int64_t hw_count; int64_t sw_count; int64_t lost_count; @@ -46,6 +47,7 @@ struct litepcie_ioctl_dma_writer { struct litepcie_ioctl_dma_reader { uint8_t enable; + uint32_t interrupt_count; int64_t hw_count; int64_t sw_count; int64_t lost_count; diff --git a/litepcie/public_h/litepcie_mac.h b/litepcie/public_h/litepcie_mac.h new file mode 100644 index 0000000..028e59f --- /dev/null +++ b/litepcie/public_h/litepcie_mac.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: BSD-2-Clause + * + * LitePCIe driver + * + * This file is part of LitePCIe. + * + * Copyright (C) 2018-2023 / EnjoyDigital / florent@enjoy-digital.fr + * + */ + +#ifndef _MAC_LITEPCIE_H +#define _MAC_LITEPCIE_H + +#include + +#include "csr.h" +#include "config.h" + + +struct litepcie_ioctl_dma { + uint8_t loopback_enable; + uint8_t channel; +} __attribute__((packed)); + +struct litepcie_ioctl_dma_writer { + uint32_t enable; + uint32_t channel; + uint32_t interrupt_count; + int64_t hw_count; + int64_t sw_count; + int64_t lost_count; +} __attribute__((packed)); + +struct litepcie_ioctl_dma_reader { + uint32_t enable; + uint32_t channel; + uint32_t interrupt_count; + int64_t hw_count; + int64_t sw_count; + int64_t lost_count; +} __attribute__((packed)); + +struct litepcie_ioctl_lock { + uint8_t dma_reader_request; + uint8_t dma_writer_request; + uint8_t dma_reader_release; + uint8_t dma_writer_release; + uint8_t dma_reader_status; + uint8_t dma_writer_status; +}__attribute__((packed)); + +struct litepcie_ioctl_mmap_dma_info { + uint64_t dma_tx_buf_offset; + uint64_t dma_tx_buf_size; + uint64_t dma_tx_buf_count; + + uint64_t dma_rx_buf_offset; + uint64_t dma_rx_buf_size; + uint64_t dma_rx_buf_count; +}; + +struct litepcie_ioctl_mmap_dma_update { + int64_t sw_count; +}; + +enum LitePCIeMessageType { + LITEPCIE_CONFIG_DMA_READER_CHANNEL, + LITEPCIE_CONFIG_DMA_WRITER_CHANNEL, + LITEPCIE_READ_CSR, + LITEPCIE_WRITE_CSR, + LITEPCIE_ICAP, + LITEPCIE_FLASH, + LITEPCIE_CONFIG_DMA, + LITEPCIE_CONFIG_DMA_LOCK, + LITEPCIE_DMA_READ, + LITEPCIE_DMA_WRITE +}; + +enum LitePCIeMemoryType { + LITEPCIE_DMA_READER = 0x00010000, + LITEPCIE_DMA_WRITER = 0x00020000, + LITEPCIE_DMA_COUNTS = 0x00040000, +}; + +typedef struct DMACounts { + uint64_t hwReaderCountTotal; + uint64_t hwReaderCountPrev; + uint64_t hwReaderLost; + uint64_t hwWriterCountTotal; + uint64_t hwWriterCountPrev; + uint64_t hwWriterLost; +} __attribute__((packed)) DMACounts; + +typedef struct litepcie_ioctl_flash { + uint32_t tx_len; /* 8 to 40 */ + uint64_t tx_data; /* 8 to 40 bits */ + uint64_t rx_data; /* 40 bits */ +} __attribute__((packed)) LitePCIeFlashCallData; + +typedef struct litepcie_ioctl_icap { + uint8_t addr; + uint32_t data; +} __attribute__((packed)) LitePCIeICAPCallData; + +typedef struct litepcie_ioctl_dma_transfer_s { + uint32_t channel; + uint32_t length; + void* buffer_addr; +} __attribute__((packed)) litepcie_ioctl_dma_transfer_t; + + +#define LITEPCIE_IOCTL_FLASH LITEPCIE_FLASH +#define LITEPCIE_IOCTL_ICAP LITEPCIE_ICAP + +#define LITEPCIE_IOCTL_DMA LITEPCIE_CONFIG_DMA +#define LITEPCIE_IOCTL_DMA_WRITER LITEPCIE_CONFIG_DMA_WRITER_CHANNEL +#define LITEPCIE_IOCTL_DMA_READER LITEPCIE_CONFIG_DMA_READER_CHANNEL +#define LITEPCIE_IOCTL_MMAP_DMA_INFO LITEPCIE_CONFIG_DMA_WRITER_CHANNEL +#define LITEPCIE_IOCTL_LOCK LITEPCIE_CONFIG_DMA_LOCK +// #define LITEPCIE_IOCTL_MMAP_DMA_WRITER_UPDATE //TBD +// #define LITEPCIE_IOCTL_MMAP_DMA_READER_UPDATE //TBD +#define LITEPCIE_IOCTL_DMA_READ LITEPCIE_DMA_READ +#define LITEPCIE_IOCTL_DMA_WRITE LITEPCIE_DMA_WRITE + +#endif /* _LINUX_LITEPCIE_H */ diff --git a/litepcie/public_h/litepcie_win.h b/litepcie/public_h/litepcie_win.h index 47bec48..04d3130 100644 --- a/litepcie/public_h/litepcie_win.h +++ b/litepcie/public_h/litepcie_win.h @@ -53,6 +53,7 @@ struct litepcie_ioctl_dma { struct litepcie_ioctl_dma_writer { uint8_t enable; + uint32_t interrupt_count; int64_t hw_count; int64_t sw_count; int64_t lost_count; @@ -60,6 +61,7 @@ struct litepcie_ioctl_dma_writer { struct litepcie_ioctl_dma_reader { uint8_t enable; + uint32_t interrupt_count; int64_t hw_count; int64_t sw_count; int64_t lost_count; diff --git a/litepcie/public_h/soc.h b/litepcie/public_h/soc.h index 992025c..4641054 100644 --- a/litepcie/public_h/soc.h +++ b/litepcie/public_h/soc.h @@ -1,5 +1,5 @@ //-------------------------------------------------------------------------------- -// Auto-generated by LiteX (f63d4a833) on 2024-12-26 19:07:59 +// Auto-generated by LiteX (a1ea5a2f6) on 2025-12-31 15:49:53 //-------------------------------------------------------------------------------- #ifndef __GENERATED_SOC_H #define __GENERATED_SOC_H @@ -10,15 +10,16 @@ #define CONFIG_CPU_FAMILY #define CONFIG_CPU_NAME #define CONFIG_CPU_HUMAN_NAME "Unknown" -#define CONFIG_IDENTIFIER "LitePCIe SoC on ThunderScope A200T (dca96645) 2024-12-26 19:07:58" +#define CONFIG_IDENTIFIER "ThunderScope DEV (0.4.0) built with LiteX 2024.12 2025-12-31 15:49:52" #define DMA_CHANNELS 1 #define DMA_ADDR_WIDTH 64 #define PCIE_DMA0_READER_INTERRUPT 0 #define PCIE_DMA0_WRITER_INTERRUPT 1 -#define SPIFLASH_PHY_FREQUENCY 37500000 -#define SPIFLASH_MODULE_NAME "s25fl256s" -#define SPIFLASH_MODULE_TOTAL_SIZE 33554432 +#define SPIFLASH_PHY_FREQUENCY 18750000 +#define SPIFLASH_MODULE_NAME "mx25u6435e" +#define SPIFLASH_MODULE_TOTAL_SIZE 8388608 #define SPIFLASH_MODULE_PAGE_SIZE 256 +#define SPIFLASH_MODULE_QUAD_CAPABLE #define CONFIG_CSR_DATA_WIDTH 32 #define CONFIG_CSR_ALIGNMENT 32 #define CONFIG_BUS_STANDARD "wishbone" diff --git a/litepcie/src/litepcie_dma.c b/litepcie/src/litepcie_dma.c index 6dfb40b..bc31d49 100644 --- a/litepcie/src/litepcie_dma.c +++ b/litepcie/src/litepcie_dma.c @@ -29,25 +29,45 @@ #include "litepcie_helpers.h" -void litepcie_dma_set_loopback(file_t fd, uint8_t loopback_enable) { +void litepcie_dma_set_loopback(struct litepcie_dma_ctrl *dma, uint8_t loopback_enable) { struct litepcie_ioctl_dma m; m.loopback_enable = loopback_enable; - checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_DMA, m)); +#if defined(__APPLE__) + size_t outlen = sizeof(m); + m.channel = dma->channel; +#endif + checked_ioctl(ioctl_args(dma->fds.fd, LITEPCIE_IOCTL_DMA, m)); } -void litepcie_dma_writer(file_t fd, uint8_t enable, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count) { +void litepcie_dma_writer(struct litepcie_dma_ctrl *dma, uint8_t enable, uint32_t intr_count, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count) { struct litepcie_ioctl_dma_writer m; m.enable = enable; - checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_DMA_WRITER, m)); + m.interrupt_count = intr_count; +#if defined(__APPLE__) + size_t outlen = sizeof(m); + m.channel = dma->channel; + m.sw_count = *sw_count; +#endif + + checked_ioctl(ioctl_args(dma->fds.fd, LITEPCIE_IOCTL_DMA_WRITER, m)); + *hw_count = m.hw_count; *sw_count = m.sw_count; *lost_count = m.lost_count; } -void litepcie_dma_reader(file_t fd, uint8_t enable, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count) { +void litepcie_dma_reader(struct litepcie_dma_ctrl *dma, uint8_t enable, uint32_t intr_count, int64_t *hw_count, int64_t *sw_count, int64_t *lost_count) { struct litepcie_ioctl_dma_reader m; m.enable = enable; - checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_DMA_READER, m)); + m.interrupt_count = intr_count; +#if defined(__APPLE__) + size_t outlen = sizeof(m); + m.channel = dma->channel; + m.sw_count = *sw_count; +#endif + + checked_ioctl(ioctl_args(dma->fds.fd, LITEPCIE_IOCTL_DMA_READER, m)); + *hw_count = m.hw_count; *sw_count = m.sw_count; *lost_count = m.lost_count; @@ -55,23 +75,29 @@ void litepcie_dma_reader(file_t fd, uint8_t enable, int64_t *hw_count, int64_t * /* lock */ -uint8_t litepcie_request_dma(file_t fd, uint8_t reader, uint8_t writer) { +uint8_t litepcie_request_dma(struct litepcie_dma_ctrl *dma, uint8_t reader, uint8_t writer) { struct litepcie_ioctl_lock m; m.dma_reader_request = reader > 0; m.dma_writer_request = writer > 0; m.dma_reader_release = 0; m.dma_writer_release = 0; - checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_LOCK, m)); +#if defined(__APPLE__) + size_t outlen = sizeof(m); +#endif + checked_ioctl(ioctl_args(dma->fds.fd, LITEPCIE_IOCTL_LOCK, m)); return m.dma_reader_status; } -void litepcie_release_dma(file_t fd, uint8_t reader, uint8_t writer) { +void litepcie_release_dma(struct litepcie_dma_ctrl *dma, uint8_t reader, uint8_t writer) { struct litepcie_ioctl_lock m; m.dma_reader_request = 0; m.dma_writer_request = 0; m.dma_reader_release = reader > 0; m.dma_writer_release = writer > 0; - checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_LOCK, m)); +#if defined(__APPLE__) + size_t outlen = sizeof(m); +#endif + checked_ioctl(ioctl_args(dma->fds.fd, LITEPCIE_IOCTL_LOCK, m)); } int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *device_name, uint8_t zero_copy) @@ -95,7 +121,7 @@ int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *device_name, ui FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED); //Last char is channel ID. Zero remove char before opening file -#else +#elif defined(__linux__) if (dma->use_reader) dma->fds.events |= POLLOUT; if (dma->use_writer) @@ -104,18 +130,18 @@ int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *device_name, ui #endif dma->fds.fd = litepcie_open(devName, flags); - if (dma->fds.fd < 0) { + if (dma->fds.fd <= 0) { fprintf(stderr, "Could not open device\n"); return -1; } /* request dma reader and writer */ - if ((litepcie_request_dma(dma->fds.fd, dma->use_reader, dma->use_writer) == 0)) { + if ((litepcie_request_dma(dma, dma->use_reader, dma->use_writer) == 0)) { fprintf(stderr, "DMA not available\n"); return -1; } - litepcie_dma_set_loopback(dma->fds.fd, dma->loopback); + litepcie_dma_set_loopback(dma, dma->loopback); if (dma->zero_copy) { #if defined(_WIN32) @@ -123,6 +149,9 @@ int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *device_name, ui return -1; #else /* if mmap: get it from the kernel */ +#if defined(__APPLE__) + size_t outlen = sizeof(struct litepcie_ioctl_mmap_dma_info); +#endif checked_ioctl(ioctl_args(dma->fds.fd, LITEPCIE_IOCTL_MMAP_DMA_INFO, dma->mmap_dma_info)); if (dma->use_writer) { dma->buf_rd = mmap(NULL, DMA_BUFFER_TOTAL_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, @@ -166,11 +195,11 @@ int litepcie_dma_init(struct litepcie_dma_ctrl *dma, const char *device_name, ui void litepcie_dma_cleanup(struct litepcie_dma_ctrl *dma) { if (dma->use_reader) - litepcie_dma_reader(dma->fds.fd, 0, &dma->reader_hw_count, &dma->reader_sw_count, &dma->reader_dropped_count); + litepcie_dma_reader(dma, 0, 0, &dma->reader_hw_count, &dma->reader_sw_count, &dma->reader_dropped_count); if (dma->use_writer) - litepcie_dma_writer(dma->fds.fd, 0, &dma->writer_hw_count, &dma->writer_sw_count, &dma->writer_dropped_count); + litepcie_dma_writer(dma, 0, 0, &dma->writer_hw_count, &dma->writer_sw_count, &dma->writer_dropped_count); - litepcie_release_dma(dma->fds.fd, dma->use_reader, dma->use_writer); + litepcie_release_dma(dma, dma->use_reader, dma->use_writer); if (dma->zero_copy) { #if !defined(_WIN32) @@ -194,9 +223,9 @@ void litepcie_dma_process(struct litepcie_dma_ctrl *dma) /* set / get dma */ if (dma->use_writer) - litepcie_dma_writer(dma->fds.fd, 1, &dma->writer_hw_count, &dma->writer_sw_count, &dma->writer_dropped_count); + litepcie_dma_writer(dma, 1, 0, &dma->writer_hw_count, &dma->writer_sw_count, &dma->writer_dropped_count); if (dma->use_reader) - litepcie_dma_reader(dma->fds.fd, 1, &dma->reader_hw_count, &dma->reader_sw_count, &dma->reader_dropped_count); + litepcie_dma_reader(dma, 1, 0, &dma->reader_hw_count, &dma->reader_sw_count, &dma->reader_dropped_count); #if defined(_WIN32) uint32_t retLen = 0; @@ -287,6 +316,8 @@ void litepcie_dma_process(struct litepcie_dma_ctrl *dma) dma->usr_write_buf_offset = 0; } +#elif defined(__APPLE__) +//TODO #else /* polling */ retVal = poll(&dma->fds, 1, 100); @@ -353,7 +384,7 @@ char *litepcie_dma_next_read_buffer(struct litepcie_dma_ctrl *dma) if (!dma->buffers_available_read) return NULL; dma->buffers_available_read--; - char *ret = dma->buf_rd + dma->usr_read_buf_offset * DMA_BUFFER_SIZE; + char *ret = (char*)dma->buf_rd + dma->usr_read_buf_offset * DMA_BUFFER_SIZE; dma->usr_read_buf_offset = (dma->usr_read_buf_offset + 1) % DMA_BUFFER_COUNT; return ret; } @@ -363,7 +394,7 @@ char *litepcie_dma_next_write_buffer(struct litepcie_dma_ctrl *dma) if (!dma->buffers_available_write) return NULL; dma->buffers_available_write--; - char *ret = dma->buf_wr + dma->usr_write_buf_offset * DMA_BUFFER_SIZE; + char *ret = (char*)dma->buf_wr + dma->usr_write_buf_offset * DMA_BUFFER_SIZE; dma->usr_write_buf_offset = (dma->usr_write_buf_offset + 1) % DMA_BUFFER_COUNT; return ret; } diff --git a/litepcie/src/litepcie_helpers.c b/litepcie/src/litepcie_helpers.c index 953ce17..be8be0f 100644 --- a/litepcie/src/litepcie_helpers.c +++ b/litepcie/src/litepcie_helpers.c @@ -13,6 +13,11 @@ #include #include #include +#elif defined(__APPLE__) +#include +#include +#include +#include #else #include #include @@ -38,10 +43,9 @@ static void getDeviceName(PWCHAR devName, DWORD maxLen, DWORD devIdx) HDEVINFO hwDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_litepciedrv, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); devData.cbSize = sizeof(devData); - if (!SetupDiEnumDeviceInterfaces(hwDevInfo, NULL, &GUID_DEVINTERFACE_litepciedrv, 0, &devData)) + if (!SetupDiEnumDeviceInterfaces(hwDevInfo, NULL, &GUID_DEVINTERFACE_litepciedrv, devIdx, &devData)) { - //Print Error - fprintf(stderr, "No Devices Found\n"); + //Device Not Found goto cleanup; } @@ -68,7 +72,6 @@ static void getDeviceName(PWCHAR devName, DWORD maxLen, DWORD devIdx) if (SetupDiGetDeviceInterfaceDetail(hwDevInfo, &devData, pDetail, detailLen, NULL, NULL)) { wcsncpy_s(devName, maxLen, pDetail->DevicePath, _TRUNCATE); - fwprintf(stdout, L"Found device: %s\n", pDetail->DevicePath); } else { @@ -81,30 +84,71 @@ static void getDeviceName(PWCHAR devName, DWORD maxLen, DWORD devIdx) SetupDiDestroyDeviceInfoList(hwDevInfo); return; } +#elif defined(__APPLE__) + +void _print_kerr_details(kern_return_t ret) +{ + printf("\tSystem: 0x%02x\n", err_get_system(ret)); + printf("\tSubsystem: 0x%03x\n", err_get_sub(ret)); + printf("\tCode: 0x%04x\n", err_get_code(ret)); +} #endif uint32_t litepcie_readl(file_t fd, uint32_t addr) { +#if defined(__APPLE__) + kern_return_t ret = kIOReturnSuccess; + + uint32_t olen = 1; + uint64_t output = 0; + uint64_t input = addr; + + ret = IOConnectCallScalarMethod(fd, LITEPCIE_READ_CSR, &input, 1, &output, &olen); + + if (ret != kIOReturnSuccess) { + printf("LITEPCIE_READ_CSR failed with error: 0x%08x.\n", ret); + _print_kerr_details(ret); + } + + return (uint32_t)output; +#else struct litepcie_ioctl_reg regData = { 0 }; regData.addr = addr; regData.is_write = 0; checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_REG, regData)); return regData.val; +#endif } void litepcie_writel(file_t fd, uint32_t addr, uint32_t val) { +#if defined(__APPLE__) + kern_return_t ret = kIOReturnSuccess; + + uint32_t olen = 0; + uint64_t input[2] = { addr, val }; + ret = IOConnectCallScalarMethod(fd, LITEPCIE_WRITE_CSR, input, 2, NULL, &olen); + + if (ret != kIOReturnSuccess) { + printf("LITEPCIE_WRITE_CSR failed with error: 0x%08x.\n", ret); + _print_kerr_details(ret); + } +#else struct litepcie_ioctl_reg regData; regData.addr = addr; regData.val = val; regData.is_write = 1; checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_REG, regData)); +#endif } void litepcie_reload(file_t fd) { struct litepcie_ioctl_icap m; m.addr = 0x4; m.data = 0xf; +#if defined(__APPLE__) + size_t outlen = sizeof(struct litepcie_ioctl_icap); +#endif checked_ioctl(ioctl_args(fd, LITEPCIE_IOCTL_ICAP, m)); } @@ -115,6 +159,9 @@ void _check_ioctl(int status, const char *file, int line) { #if defined(_WIN32) fprintf(stderr, "Failed ioctl at %s:%d: %d\n", file, line, GetLastError()); + +#elif defined(__APPLE__) + fprintf(stderr, "Failed ioctl at %s:%d: %08x\n", file, line, status); #else fprintf(stderr, "Failed ioctl at %s:%d: %s\n", file, line, strerror(errno)); #endif @@ -135,6 +182,47 @@ file_t litepcie_open(const char* name, int32_t flags) devName[devLen + strlen(name) - 1] = '\0'; fd = CreateFile(devName, (GENERIC_READ | GENERIC_WRITE), 0, NULL, OPEN_EXISTING, flags, NULL); +#elif defined(__APPLE__) + uint32_t idx; + kern_return_t ret = kIOReturnSuccess; + io_iterator_t iterator = IO_OBJECT_NULL; + io_service_t service = IO_OBJECT_NULL; + io_connect_t connection = IO_OBJECT_NULL; + CFStringRef matchServ = CFSTR("IOUserServerName"); + CFStringRef matchVal = CFSTR("eevengers.thunderscope"); + fd = IO_OBJECT_NULL; + + if(1 == sscanf(name, LITEPCIE_CTRL_NAME(%u), &idx)) + { + CFMutableDictionaryRef matchingDict = IOServiceNameMatching("litepcie"); + CFDictionaryAddValue(matchingDict, matchServ, matchVal); + ret = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &iterator); + if (ret != kIOReturnSuccess) { + fprintf(stderr, "Unable to find service for identifier with error: 0x%08x.\n", ret); + _print_kerr_details(ret); + } + else + { + while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { + if(idx-- > 0) + { + continue; + } + // Open a connection to this user client as a server to that client, and store the instance in "service" + ret = IOServiceOpen(service, mach_task_self_, kIOHIDServerConnectType, &connection); + + if (ret == kIOReturnSuccess) { + fd = connection; + break; + } else { + fprintf(stderr, "\tFailed opening service with error: 0x%08x.\n", ret); + } + + IOObjectRelease(service); + } + } + IOObjectRelease(iterator); + } #else fd = open(name, flags); #endif @@ -145,6 +233,8 @@ void litepcie_close(file_t fd) { #if defined(_WIN32) CloseHandle(fd); +#elif defined(__APPLE__) + IOServiceClose(fd); #else close(fd); #endif diff --git a/requirements.txt b/requirements.txt index cf5a04b..7c1b358 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ cython -pipx \ No newline at end of file +pipx +cibuildwheel \ No newline at end of file diff --git a/src/adc.c b/src/adc.c index 954264e..7c3bc47 100644 --- a/src/adc.c +++ b/src/adc.c @@ -13,13 +13,22 @@ #include "platform.h" #include "util.h" #include "liblitepcie.h" +#include "ts_calibration.h" +#define TS_ADC_DATA_FRAMING_8BIT (0) +#define TS_ADC_DATA_FRAMING_12BIT_MSB (1 << CSR_ADC_HMCAD1520_SAMPLE_BITS_DATA_WIDTH_OFFSET); +#define TS_ADC_DATA_FRAMING_12BIT_LSB (2 << CSR_ADC_HMCAD1520_SAMPLE_BITS_DATA_WIDTH_OFFSET); + typedef enum adc_shuffle_e { - ADC_SHUFFLE_1CH = 0, - ADC_SHUFFLE_2CH = 1, - ADC_SHUFFLE_4CH = 2, + ADC_8B_SHUFFLE_1CH = 0, + ADC_8B_SHUFFLE_2CH = 1, + ADC_8B_SHUFFLE_4CH = 2, + ADC_12B_SHUFFLE_1CH = 3, + ADC_12B_SHUFFLE_2CH = 4, + ADC_12B_SHUFFLE_4CH = 5, + ADC_DUAL_8B_SHUFFLE_4CH = 6, } adc_shuffle_t; @@ -35,8 +44,8 @@ int32_t ts_adc_init(ts_adc_t* adc, spi_dev_t spi, file_t fd) if(retVal == TS_STATUS_OK) { - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET); - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_DOWNSAMPLING_ADDR, 1); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_OFFSET); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_DOWNSAMPLING_ADDR, 1); retVal = hmcad15xx_full_scale_adjust(&adc->adcDev, TS_ADC_FULL_SCALE_ADJUST_DEFAULT); } @@ -64,14 +73,14 @@ int32_t ts_adc_set_channel_conf(ts_adc_t* adc, uint8_t channel, uint8_t input, u } } retVal = hmcad15xx_set_channel_config(&adc->adcDev); - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_OFFSET); } } return retVal; } -int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse, int32_t gainFine) +int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse) { int32_t retVal = TS_STATUS_OK; @@ -82,19 +91,17 @@ int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse, int3 else { adc->tsChannels[channel].coarse = gainCoarse; - adc->tsChannels[channel].fine = gainFine; for(uint8_t i = 0; i < TS_NUM_CHANNELS; i++) { if(adc->tsChannels[channel].input == adc->adcDev.channelCfg[i].input) { adc->adcDev.channelCfg[i].coarse = gainCoarse; - adc->adcDev.channelCfg[i].fine = gainFine; break; } } retVal = hmcad15xx_set_channel_config(&adc->adcDev); - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_OFFSET); } if(retVal == TS_STATUS_OK) @@ -106,13 +113,24 @@ int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse, int3 } int32_t ts_adc_channel_enable(ts_adc_t* adc, uint8_t channel, uint8_t enable) +{ + if(adc == NULL || channel >= TS_NUM_CHANNELS) + { + return TS_STATUS_ERROR; + } + else + { + adc->tsChannels[channel].active = enable; + return ts_adc_update_channels(adc); + } +} + +int32_t ts_adc_update_channels(ts_adc_t* adc) { int32_t retVal; uint8_t activeCount = 0; uint8_t inactiveCount = 0; - adc_shuffle_t shuffleMode = ADC_SHUFFLE_1CH; - - adc->tsChannels[channel].active = enable; + adc_shuffle_t shuffleMode = ADC_8B_SHUFFLE_1CH; for(uint8_t i=0; i < TS_NUM_CHANNELS; i++) { @@ -127,9 +145,9 @@ int32_t ts_adc_channel_enable(ts_adc_t* adc, uint8_t channel, uint8_t enable) { //Disable Unused channels in config LOG_DEBUG("Disable CH %d", i); + inactiveCount++; adc->adcDev.channelCfg[TS_NUM_CHANNELS - inactiveCount] = adc->tsChannels[i]; adc->adcDev.channelCfg[TS_NUM_CHANNELS - inactiveCount].active = 0; - inactiveCount++; } } @@ -140,26 +158,33 @@ int32_t ts_adc_channel_enable(ts_adc_t* adc, uint8_t channel, uint8_t enable) } else { - - if(activeCount == 1) + if(adc->adcDev.width == HMCAD15_14_BIT) + { + //Set Quad Channel, Dual-8 + adc->adcDev.mode = HMCAD15_PREC_QUAD_CHANNEL; + shuffleMode = ADC_DUAL_8B_SHUFFLE_4CH; + } + else if(activeCount == 1) { adc->adcDev.mode = HMCAD15_SINGLE_CHANNEL; - shuffleMode = ADC_SHUFFLE_1CH; + shuffleMode = ((adc->adcDev.width == HMCAD15_8_BIT) ? ADC_8B_SHUFFLE_1CH : ADC_12B_SHUFFLE_1CH); } else if(activeCount == 2) { adc->adcDev.mode = HMCAD15_DUAL_CHANNEL; - shuffleMode = ADC_SHUFFLE_2CH; + shuffleMode = ((adc->adcDev.width == HMCAD15_8_BIT) ? ADC_8B_SHUFFLE_2CH : ADC_12B_SHUFFLE_2CH); } else { adc->adcDev.mode = HMCAD15_QUAD_CHANNEL; - shuffleMode = ADC_SHUFFLE_4CH; + shuffleMode = ((adc->adcDev.width == HMCAD15_8_BIT) ? ADC_8B_SHUFFLE_4CH : ADC_12B_SHUFFLE_4CH); } + + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_DATA_CHANNELS_ADDR, + shuffleMode << CSR_ADC_HMCAD1520_DATA_CHANNELS_SHUFFLE_OFFSET); + retVal = hmcad15xx_set_channel_config(&adc->adcDev); - - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET); - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_DATA_CHANNELS_ADDR, shuffleMode << CSR_ADC_HAD1511_DATA_CHANNELS_SHUFFLE_OFFSET); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_OFFSET); } return retVal; @@ -177,7 +202,7 @@ int32_t ts_adc_shutdown(ts_adc_t* adc) if(retVal == TS_STATUS_OK) { hmcad15xx_power_mode(&adc->adcDev, HMCAD15_CH_POWERDN); - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_CONTROL_ADDR, 1 << CSR_ADC_HMCAD1520_CONTROL_FRAME_RST_OFFSET); } return retVal; @@ -194,28 +219,70 @@ int32_t ts_adc_run(ts_adc_t* adc, uint8_t en) return TS_STATUS_OK; } -int32_t ts_adc_set_sample_mode(ts_adc_t* adc, uint32_t sample_rate, uint32_t resolution) +int32_t ts_adc_set_sample_mode(ts_adc_t* adc, uint32_t sample_rate, tsSampleFormat_t mode) { if(!adc) { return TS_STATUS_ERROR; } hmcad15xxDataWidth_t data_mode = HMCAD15_8_BIT; - if(resolution == 4096) + uint32_t sample_bits = TS_ADC_DATA_FRAMING_8BIT; + if(mode == TS_12_BIT_LSB) { data_mode = HMCAD15_12_BIT; + sample_bits = TS_ADC_DATA_FRAMING_12BIT_LSB; + } + else if(mode == TS_12_BIT_MSB) + { + data_mode = HMCAD15_12_BIT; + sample_bits = TS_ADC_DATA_FRAMING_12BIT_MSB; + } + else if(mode == TS_14_BIT) + { + //Precision mode uses Dual-8 LVDS + data_mode = HMCAD15_14_BIT; + sample_bits = TS_ADC_DATA_FRAMING_8BIT; } - //else support 14-bit precise mode? if(TS_STATUS_OK == hmcad15xx_set_sample_mode(&adc->adcDev, sample_rate, data_mode)) { - hmcad15xx_set_channel_config(&adc->adcDev); - litepcie_writel(adc->ctrl, CSR_ADC_HAD1511_CONTROL_ADDR, 1 << CSR_ADC_HAD1511_CONTROL_FRAME_RST_OFFSET); + litepcie_writel(adc->ctrl, CSR_ADC_HMCAD1520_SAMPLE_BITS_ADDR, sample_bits); + + ts_adc_update_channels(adc); return TS_STATUS_OK; } else { - LOG_ERROR("Failed to set the ADC Sample Mode %d/%d", sample_rate, resolution); + LOG_ERROR("Failed to set the ADC Sample Mode %d/%d", sample_rate, mode); return TS_STATUS_ERROR; } } + +int32_t ts_adc_cal_set(ts_adc_t* adc, tsAdcCalibration_t *cal) +{ + if(!adc) + { + return TS_STATUS_ERROR; + } + + for(int i=0; i < HMCAD15_NUM_BRANCHES; i++) + { + adc->adcDev.fineCal[i] = cal->branchFineGain[i]; + } + + return hmcad15xx_fine_gain_set(&adc->adcDev, true); +} + +int32_t ts_adc_cal_get(ts_adc_t* adc, tsAdcCalibration_t *cal) +{ + if(!adc) + { + return TS_STATUS_ERROR; + } + + for(int i=0; i < HMCAD15_NUM_BRANCHES; i++) + { + cal->branchFineGain[i] = adc->adcDev.fineCal[i]; + } + return TS_STATUS_OK; +} \ No newline at end of file diff --git a/src/adc.h b/src/adc.h index e0ecc49..b52ecf9 100644 --- a/src/adc.h +++ b/src/adc.h @@ -14,6 +14,7 @@ extern "C" { #endif #include "ts_common.h" +#include "ts_calibration.h" #include "hmcad15xx.h" #include "platform.h" #include "spi.h" @@ -53,10 +54,9 @@ int32_t ts_adc_set_channel_conf(ts_adc_t* adc, uint8_t channel, uint8_t input, u * @param adc Pointer to a ADC instance * @param channel Thunderscope Channel number to configure * @param gainCoarse Coarse Gain parameter for the ADC Input - * @param gainFine Fine Gain parameter for the ADC Input * @return int32_t TS_STATUS_OK if the gain was set successfully */ -int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse, int32_t gainFine); +int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse); /** * @brief Enable or Disable a Thunderscope Channel ADC @@ -68,6 +68,14 @@ int32_t ts_adc_set_gain(ts_adc_t* adc, uint8_t channel, int32_t gainCoarse, int3 */ int32_t ts_adc_channel_enable(ts_adc_t* adc, uint8_t channel, uint8_t enable); +/** + * @brief Update the ADC configuration with the current active channels + * + * @param adc Pointer to a ADC instance + * @return int32_t TS_STATUS_OK if the channels were updated successfully + */ +int32_t ts_adc_update_channels(ts_adc_t* adc); + /** * @brief Shutdown the Thunderscope ADC * @@ -90,10 +98,28 @@ int32_t ts_adc_run(ts_adc_t* adc, uint8_t en); * * @param adc Pointer to a ADC instance * @param sample_rate Rate of the ADC Sample clock (Hz) - * @param resolution Resolution mode setting for the ADC. + * @param mode Resolution mode setting for the ADC. * @return int32_t TS_STATUS_OK if the mode was applied successfully */ -int32_t ts_adc_set_sample_mode(ts_adc_t* adc, uint32_t sample_rate, uint32_t resolution); +int32_t ts_adc_set_sample_mode(ts_adc_t* adc, uint32_t sample_rate, tsSampleFormat_t mode); + +/** + * @brief Set the calibration on the ADC + * + * @param adc Pointer to a ADC instance + * @param cal Pointer to a ADC Calibration structure + * @return int32_t TS_STATUS_OK if the calibration was applied successfully + */ +int32_t ts_adc_cal_set(ts_adc_t* adc, tsAdcCalibration_t *cal); + +/** + * @brief Set the calibration on the ADC + * + * @param adc Pointer to a ADC instance + * @param cal Pointer to a ADC Calibration structure + * @return int32_t TS_STATUS_OK if the calibration was applied successfully + */ +int32_t ts_adc_cal_get(ts_adc_t* adc, tsAdcCalibration_t *cal); #ifdef __cplusplus } diff --git a/src/afe.c b/src/afe.c index 40c2882..31a019a 100644 --- a/src/afe.c +++ b/src/afe.c @@ -44,6 +44,19 @@ int32_t ts_afe_init(ts_afe_t* afe, uint8_t channel, spi_dev_t afe_amp, i2c_t tri afe->trimDacCh = dacCh; afe->trimPot = trimPot; afe->trimPotCh = potCh; + if(isBetaDevice(trimPot.fd)) + { + afe->trimPotBits = MCP4432_NUM_BITS; + } + else + { + afe->trimPotBits = MCP4452_NUM_BITS; + + if((litepcie_readl(coupling.fd, CSR_DEV_STATUS_HW_ID_ADDR) & TS_HW_ID_REV_MASK) > 0) + { + afe->couplingInverted = true; + } + } afe->termPin = termination; afe->attenuatorPin = attenuator; afe->couplingPin = coupling; @@ -54,18 +67,29 @@ int32_t ts_afe_init(ts_afe_t* afe, uint8_t channel, spi_dev_t afe_amp, i2c_t tri afe->isAttenuated = true; // Default calibration - afe->cal.buffer_mV = TS_VBUFFER_NOMINAL_MV; - afe->cal.bias_mV = TS_VBIAS_NOMINAL_MV; + afe->cal.buffer_uV = TS_VBUFFER_NOMINAL_UV; + afe->cal.bias_uV = TS_VBIAS_NOMINAL_UV; afe->cal.attenuatorGain1M_mdB = TS_ATTENUATION_1M_GAIN_mdB; afe->cal.attenuatorGain50_mdB = TS_TERMINATION_50OHM_GAIN_mdB; afe->cal.bufferGain_mdB = TS_BUFFER_GAIN_NOMINAL_mdB; - afe->cal.trimRheostat_range = MCP4432_FULL_SCALE_OHM; + afe->cal.trimRheostat_range = MCP4452_104_FULL_SCALE_OHM; afe->cal.preampLowGainError_mdB = 0; afe->cal.preampHighGainError_mdB = 0; afe->cal.preampOutputGainError_mdB = 0; - afe->cal.preampLowOffset_mV = 0; - afe->cal.preampHighOffset_mV = 0; + afe->cal.preampLowOffset_uV = 0; + afe->cal.preampHighOffset_uV = 0; afe->cal.preampInputBias_uA = TS_PREAMP_INPUT_BIAS_CURRENT_uA; + afe->cal.preampAttenuatorGain_mdB[0] = TS_AFE_PREAMP_ATTEN_0_mdB; + afe->cal.preampAttenuatorGain_mdB[1] = TS_AFE_PREAMP_ATTEN_1_mdB; + afe->cal.preampAttenuatorGain_mdB[2] = TS_AFE_PREAMP_ATTEN_2_mdB; + afe->cal.preampAttenuatorGain_mdB[3] = TS_AFE_PREAMP_ATTEN_3_mdB; + afe->cal.preampAttenuatorGain_mdB[4] = TS_AFE_PREAMP_ATTEN_4_mdB; + afe->cal.preampAttenuatorGain_mdB[5] = TS_AFE_PREAMP_ATTEN_5_mdB; + afe->cal.preampAttenuatorGain_mdB[6] = TS_AFE_PREAMP_ATTEN_6_mdB; + afe->cal.preampAttenuatorGain_mdB[7] = TS_AFE_PREAMP_ATTEN_7_mdB; + afe->cal.preampAttenuatorGain_mdB[8] = TS_AFE_PREAMP_ATTEN_8_mdB; + afe->cal.preampAttenuatorGain_mdB[9] = TS_AFE_PREAMP_ATTEN_9_mdB; + afe->cal.preampAttenuatorGain_mdB[10] = TS_AFE_PREAMP_ATTEN_10_mdB; Mcp4728ChannelConfig_t trimConf = {0}; trimConf.vref = MCP4728_VREF_VDD; @@ -101,15 +125,24 @@ int32_t ts_afe_set_gain(ts_afe_t* afe, int32_t gain_mdB) //Remove Preamp Output gain calibration value gain_request -= afe->cal.preampOutputGainError_mdB; - // If 50-Ohm mode in use, limit gain to TBD - if(afe->termination == TS_TERM_50) + if(isBetaDevice(afe->termPin.fd)) { - gain_request -= afe->cal.attenuatorGain50_mdB; + // If 50-Ohm mode in use, limit gain to TBD + if(afe->termination == TS_TERM_50) + { + gain_request -= afe->cal.attenuatorGain50_mdB; + } + else if(gain_request < LMH6518_MIN_GAIN_mdB) + { + // Update Attenuation if needed + afe->isAttenuated = true; + ts_afe_attenuation_control(afe, true); + gain_request -= afe->cal.attenuatorGain1M_mdB; + } } else if(gain_request < LMH6518_MIN_GAIN_mdB) { // Update Attenuation if needed - afe->isAttenuated = true; ts_afe_attenuation_control(afe, true); gain_request -= afe->cal.attenuatorGain1M_mdB; } @@ -129,7 +162,7 @@ int32_t ts_afe_set_gain(ts_afe_t* afe, int32_t gain_mdB) else { ts_afe_attenuation_control(afe, false); - if(afe->termination == TS_TERM_50) + if(isBetaDevice(afe->termPin.fd) && afe->termination == TS_TERM_50) { gain_actual += afe->cal.attenuatorGain50_mdB; } @@ -152,14 +185,14 @@ int32_t ts_afe_set_gain(ts_afe_t* afe, int32_t gain_mdB) return gain_actual; } -int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_mV, int32_t* offset_actual) +int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_uV, int32_t* offset_actual) { uint16_t offsetVal = TS_TRIM_DAC_DEFAULT; - int32_t V_dac = 0; - int32_t R_trim = 0; - int32_t gain_afe = 0; - int32_t V_zero = 0; - int32_t gain_preamp = 0; + int64_t V_dac = 0; + int64_t R_trim = 0; + int64_t gain_afe = 0; + int64_t V_zero = 0; + int64_t gain_preamp = 0; if(NULL == afe || NULL == offset_actual) { @@ -177,31 +210,30 @@ int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_mV, int32_t* offset_actu gain_afe += afe->cal.attenuatorGain1M_mdB; } - V_zero = afe->cal.buffer_mV; + V_zero = afe->cal.buffer_uV; gain_preamp = lmh6518_gain_from_config(afe->ampConf); if(afe->ampConf.preamp == PREAMP_LG) { gain_preamp += afe->cal.preampLowGainError_mdB; - V_zero += (int32_t)((double)afe->cal.preampLowOffset_mV / pow(10, ((double)gain_preamp/20000.0))); + V_zero += (int32_t)((double)afe->cal.preampLowOffset_uV / pow(10, ((double)gain_preamp/20000.0))); } else { gain_preamp += afe->cal.preampHighGainError_mdB; - V_zero += (int32_t)((double)afe->cal.preampHighOffset_mV / pow(10, ((double)gain_preamp/20000.0))); + V_zero += (int32_t)((double)afe->cal.preampHighOffset_uV / pow(10, ((double)gain_preamp/20000.0))); } // Desired Trim Voltage - LOG_DEBUG("AFE Offset Request %d mv with %d mdB Input Gain", offset_mV, gain_afe); - int32_t V_trim = V_zero + (uint32_t)((double)offset_mV * pow(10.0, (double)gain_afe/20000.0)); - LOG_DEBUG("AFE Offset target V_trim %d mV compared to V_zero of %d mV", V_trim, V_zero); + LOG_DEBUG("AFE Offset Request %d uv with %lld mdB Input Gain", offset_uV, gain_afe); + int64_t V_trim = V_zero + (int64_t)((double)offset_uV * pow(10.0, (double)gain_afe/20000.0)); + LOG_DEBUG("AFE Offset target V_trim %lld uV compared to V_zero of %lld uV", V_trim, V_zero); // Progressively reduce R_trim until V_dac is within range of 0-VDD - uint8_t trimPotVal = MCP4432_MAX; + uint8_t trimPotVal = (1 << afe->trimPotBits); do { - // R_trim = MCP4432_503_OHM(trimPotVal); - R_trim = ((((trimPotVal) * afe->cal.trimRheostat_range) / MCP4432_MAX) + MCP4432_RWIPER); + R_trim = ((((trimPotVal) * afe->cal.trimRheostat_range) / ((1 << afe->trimPotBits))) + MCP4432_RWIPER); /** * Preamp sinks an input bias current (I_trim), so need to add this factor when comparing current @@ -212,18 +244,18 @@ int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_mV, int32_t* offset_actu * Solved for V_dac becomes: * V_dac = V_trim + R_trim * ((V_trim - V_bias)/R_bias + I_trim) */ - V_dac = V_trim + (R_trim * ((1000 * (V_trim - afe->cal.bias_mV)/TS_BIAS_RESISTOR_NOMINAL) + afe->cal.preampInputBias_uA)) / 1000; - if(V_dac > 0 && V_dac < TS_AFE_TRIM_VDD_NOMINAL) + V_dac = V_trim + (R_trim * (((V_trim - (int64_t)afe->cal.bias_uV)/TS_BIAS_RESISTOR_NOMINAL) + afe->cal.preampInputBias_uA)); + if(V_dac >= 0 && V_dac <= TS_AFE_TRIM_VDD_NOMINAL) { - LOG_DEBUG("Setting Vdac to %d mV, Rtrim to %d Ohm", V_dac, R_trim); + LOG_DEBUG("Setting Vdac to %lld uV, Rtrim to %lld Ohm", V_dac, R_trim); offsetVal = (V_dac * MCP4728_FULL_SCALE_VAL) / TS_AFE_TRIM_VDD_NOMINAL; break; } if(trimPotVal == 0) { - LOG_ERROR("AFE Unable to produce Trim voltage %d for requested offset %d", V_trim, offset_mV); - if(V_trim > afe->cal.bias_mV) + LOG_ERROR("AFE Unable to produce Trim voltage %lld for requested offset %d", V_trim, offset_uV); + if(V_trim > afe->cal.bias_uV) { V_dac = TS_AFE_TRIM_VDD_NOMINAL; offsetVal = MCP4728_FULL_SCALE_VAL; @@ -254,11 +286,11 @@ int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_mV, int32_t* offset_actu // Reverse offset calc V_dac = (offsetVal * TS_AFE_TRIM_VDD_NOMINAL) / MCP4728_FULL_SCALE_VAL; - V_trim = (int32_t) ((((double)TS_BIAS_RESISTOR_NOMINAL * (double)V_dac) + ((double)afe->cal.bias_mV * R_trim) - - ((double)TS_BIAS_RESISTOR_NOMINAL * (double)R_trim * (double)afe->cal.preampInputBias_uA/1000.0)) + V_trim = (int64_t) ((((double)TS_BIAS_RESISTOR_NOMINAL * (double)V_dac) + ((double)afe->cal.bias_uV * R_trim) + - ((double)TS_BIAS_RESISTOR_NOMINAL * (double)R_trim * (double)afe->cal.preampInputBias_uA)) /(TS_BIAS_RESISTOR_NOMINAL + R_trim)); *offset_actual = (int32_t)(((double)V_trim - ((double)V_zero)) / pow(10.0, (double)gain_afe/20000.0)); - LOG_DEBUG("AFE Offset actual V_trim %d mv, Offset %d mV", V_trim, *offset_actual); + LOG_DEBUG("AFE Offset actual V_trim %lld uv, Offset %d uV", V_trim, *offset_actual); return TS_STATUS_OK; } @@ -356,13 +388,20 @@ int32_t ts_afe_coupling_control(ts_afe_t* afe, tsChannelCoupling_t coupled) case TS_COUPLE_DC: { LOG_DEBUG("Clear Coupling %x", afe->couplingPin.bit_mask); - gpio_clear(afe->couplingPin); + if(afe->couplingInverted) + gpio_set(afe->couplingPin); + else + gpio_clear(afe->couplingPin); + break; } case TS_COUPLE_AC: { LOG_DEBUG("Set Coupling %x", afe->couplingPin.bit_mask); - gpio_set(afe->couplingPin); + if(afe->couplingInverted) + gpio_clear(afe->couplingPin); + else + gpio_set(afe->couplingPin); break; } default: diff --git a/src/afe.h b/src/afe.h index 9ed4eb3..6ad1d9c 100644 --- a/src/afe.h +++ b/src/afe.h @@ -31,11 +31,13 @@ typedef struct ts_afe_s uint8_t trimDacCh; i2c_t trimPot; uint8_t trimPotCh; + uint8_t trimPotBits; gpio_t termPin; tsChannelTerm_t termination; gpio_t attenuatorPin; bool isAttenuated; gpio_t couplingPin; + bool couplingInverted; tsChannelCoupling_t coupling; tsChannelCalibration_t cal; }ts_afe_t; @@ -72,11 +74,11 @@ int32_t ts_afe_set_gain(ts_afe_t* afe, int32_t gain_mdB); * @brief Set the AFE input gain for a channel * * @param afe Pointer to an AFE instance - * @param offset_mV Offset in mV - * @param offset_mV Applied Offset in mV + * @param offset_uV Offset in uV + * @param offset_uV Applied Offset in uV * @return int32_t TS_STATUS_OK on success, else error */ -int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_mV, int32_t* offset_actual); +int32_t ts_afe_set_offset(ts_afe_t* afe, int32_t offset_uV, int32_t* offset_actual); /** * @brief Set the AFE Bandwidth filter for a channel diff --git a/src/events.c b/src/events.c new file mode 100644 index 0000000..9746da6 --- /dev/null +++ b/src/events.c @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of libtslitex. + * Event Subsystem for the Thunderscope LiteX design + * + * Copyright (C) 2025 / Nate Meyer / nate.devel@gmail.com + * + */ + +#include "events.h" +#include "csr.h" +#include "util.h" + + +#define EVENT_SOURCE_SW (0) +#define EVENT_SOURCE_EXT_SYNC (1) + +#define EVENT_OUTPUT_EXT_SYNC (16) + +#define EXT_SYNC_DISABLED (0x00) +#define EXT_SYNC_INPUT (0x01) +#define EXT_SYNC_OUTPUT (0x02) + +#define EVENT_FIFO_FLUSH (1UL << 31) + + +// Defaults +#define EXT_SYNC_PULSE_WIDTH_DEFAULT (50) +#define EVENT_SOURCE_ENABLE_DEFAULT (1UL << EVENT_SOURCE_SW) + + +static const tsEvent_t g_EventNone = { + .ID = TS_EVT_NONE, + .event_sample = 0 +}; + + +int32_t events_initialize(file_t handle) +{ + int32_t status = TS_STATUS_OK; + uint32_t evt_data; + + // Default Sync pin to Disabled + status = events_set_ext_sync(handle, TS_SYNC_DISABLED); + + // Enable Default Event Sources and disable all Outputs + evt_data = EVENT_SOURCE_ENABLE_DEFAULT; + // Flush the Event queue + evt_data |= EVENT_FIFO_FLUSH; + LOG_DEBUG("Set Event Control 0x%X", evt_data); + litepcie_writel(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR, evt_data); + + // Default Ext Pulse Width + litepcie_writel(handle, CSR_EVENTS_EXT_SYNC_PULSE_LEN_ADDR, EXT_SYNC_PULSE_WIDTH_DEFAULT); + + return status; +} + +bool events_available(file_t handle) +{ + if(handle != INVALID_HANDLE_VALUE && + litepcie_readl(handle, CSR_EVENTS_ENGINE_EVENT_ADDR) & (1UL << CSR_EVENTS_ENGINE_EVENT_PENDING_OFFSET)) + { + return true; + } + else + { + return false; + } +} + +int32_t events_get_next(file_t handle, tsEvent_t *pEvent) +{ + if(handle == INVALID_HANDLE_VALUE) + { + LOG_ERROR("Invalid Event Handle"); + return TS_STATUS_ERROR; + } + else if(pEvent == NULL) + { + LOG_ERROR("Invalid Event pointer"); + return TS_STATUS_ERROR; + } + + uint32_t evt_data = 0; + + // Read Sample, then Type + pEvent->event_sample = (uint64_t)litepcie_readl(handle, CSR_EVENTS_ENGINE_FIFO_READMARKER_ADDR) << 32; + pEvent->event_sample += (uint64_t)litepcie_readl(handle, CSR_EVENTS_ENGINE_FIFO_READMARKER_ADDR + 4); + + evt_data = litepcie_readl(handle, CSR_EVENTS_ENGINE_FIFO_READSOURCE_ADDR); + if((evt_data & 0xF) == EVENT_SOURCE_SW) + { + pEvent->ID = TS_EVT_HOST_SW; + } + else if((evt_data & 0xF) == EVENT_SOURCE_EXT_SYNC) + { + pEvent->ID = TS_EVT_EXT_SYNC; + } + else + { + LOG_ERROR("Unknown Event Source: %x", evt_data); + return TS_STATUS_ERROR; + } + + return TS_STATUS_OK; +} + +int32_t events_set_periodic(file_t handle, uint32_t period_us) +{ + if(handle == INVALID_HANDLE_VALUE) + { + LOG_ERROR("Invalid Event Handle"); + return TS_STATUS_ERROR; + } + + if(period_us == 0) + { + //Disable periodic event generation + litepcie_writel(handle, CSR_EVENTS_GENERATOR_CONTROL_ADDR, 0); + } + else + { + litepcie_writel(handle, CSR_EVENTS_GENERATOR_TIMEOUT_ADDR, period_us); + litepcie_writel(handle, CSR_EVENTS_GENERATOR_CONTROL_ADDR, (1 << CSR_EVENTS_GENERATOR_CONTROL_PERIODIC_OFFSET)); + } + + return TS_STATUS_OK; +} + +int32_t events_set_immediate(file_t handle) +{ + if(handle == INVALID_HANDLE_VALUE) + { + LOG_ERROR("Invalid Event Handle"); + return TS_STATUS_ERROR; + } + + litepcie_writel(handle, CSR_EVENTS_GENERATOR_CONTROL_ADDR, (1 << CSR_EVENTS_GENERATOR_CONTROL_IMMEDIATE_OFFSET)); + return TS_STATUS_OK; +} + +uint32_t events_get_source_status(file_t handle) +{ + return litepcie_readl(handle, CSR_EVENTS_ENGINE_STATUS_ADDR) & 0xFFFF; +} + +int32_t events_set_ext_sync(file_t handle, tsSyncMode_t mode) +{ + if(handle == INVALID_HANDLE_VALUE) + { + LOG_ERROR("Invalid Event Handle"); + return TS_STATUS_ERROR; + } + + int32_t status = TS_STATUS_ERROR; + uint32_t evt_ctrl = litepcie_readl(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR); + evt_ctrl &= ~(EVENT_FIFO_FLUSH); + + switch (mode) + { + case TS_SYNC_DISABLED: + { + //Disable Ext Output + evt_ctrl &= ~(1UL << EVENT_OUTPUT_EXT_SYNC); + //Disable Ext Input + evt_ctrl &= ~(1UL << EVENT_SOURCE_EXT_SYNC); + LOG_DEBUG("Set Event Control 0x%X", evt_ctrl); + litepcie_writel(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR, evt_ctrl); + litepcie_writel(handle, CSR_EVENTS_EXT_SYNC_CONTROL_ADDR, EXT_SYNC_DISABLED); + status = TS_STATUS_OK; + break; + } + case TS_SYNC_IN: + { + //Disable Ext Output + evt_ctrl &= ~(1UL << EVENT_OUTPUT_EXT_SYNC); + litepcie_writel(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR, evt_ctrl); + + //Set SYNC IN + litepcie_writel(handle, CSR_EVENTS_EXT_SYNC_CONTROL_ADDR, EXT_SYNC_INPUT); + + //Enable Ext Input + evt_ctrl |= (1UL << EVENT_SOURCE_EXT_SYNC); + LOG_DEBUG("Set Event Control 0x%X", evt_ctrl); + litepcie_writel(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR, evt_ctrl); + status = TS_STATUS_OK; + break; + } + case TS_SYNC_OUT: + { + //Disable Ext Input + evt_ctrl &= ~(1UL << EVENT_SOURCE_EXT_SYNC); + litepcie_writel(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR, evt_ctrl); + + //Set SYNC OUT + litepcie_writel(handle, CSR_EVENTS_EXT_SYNC_CONTROL_ADDR, EXT_SYNC_OUTPUT); + + //Enable Ext Output + evt_ctrl |= (1UL << EVENT_OUTPUT_EXT_SYNC); + LOG_DEBUG("Set Event Control 0x%X", evt_ctrl); + litepcie_writel(handle, CSR_EVENTS_ENGINE_CONTROL_ADDR, evt_ctrl); + + status = TS_STATUS_OK; + break; + } + default: + { + LOG_ERROR("Invalid Sync Mode: %x", mode); + break; + } + } + + return status; +} diff --git a/src/events.h b/src/events.h new file mode 100644 index 0000000..5646515 --- /dev/null +++ b/src/events.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of libtslitex. + * Event Subsystem for the Thunderscope LiteX design + * + * Copyright (C) 2025 / Nate Meyer / nate.devel@gmail.com + * + */ + +#ifndef EVENTS_H_ +#define EVENTS_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ts_common.h" +#include "liblitepcie.h" + +#define TS_BYTES_PER_SAMPLE_COUNT (128/8) // 128-bit sample bus + +/** + * @brief Set Initial conditions of the Event Controls + * + * @param handle File handle + * @return int32_t TS_STATUS_OK if the Event controller is initialized successfully + */ +int32_t events_initialize(file_t handle); + +/** + * @brief Check status bit if an Event is pending in the queue + * + * @param handle File handle + * @return bool True if an event can be read, False otherwise + */ +bool events_available(file_t handle); + +/** + * @brief Pull an event off the queue + * + * @param handle File handle + * @param pEvent Pointer to an event structure to populate + * @return int32_t TS_STATUS_OK if the event was retrieved successfully + */ +int32_t events_get_next(file_t handle, tsEvent_t *pEvent); + +/** + * @brief Configure the Event Generator to produce a timed event. The event will repeat with the + * given period. + * + * @param handle File handle + * @param period_us Event period (microseconds) + * @return int32_t TS_STATUS_OK if the event was configured successfully + */ +int32_t events_set_periodic(file_t handle, uint32_t period_us); + +/** + * @brief Immediately trigger an event with the Event Generator + * + * @param handle File handle + * @return int32_t TS_STATUS_OK if the event was configured successfully + */ +int32_t events_set_immediate(file_t handle); + +/** + * @brief Retrieve the current state of the Event Source Inputs to the Event Engine + * + * @param handle File handle + * @return uint32_t Value of the Source Input status + */ +uint32_t events_get_source_status(file_t handle); + +/** + * @brief Configure the External Sync Pin direction + * + * @param handle File handle + * @param mode Sync I/O Mode + * @return int32_t TS_STATUS_OK if the External Sync Pin was configured successfully + */ +int32_t events_set_ext_sync(file_t handle, tsSyncMode_t mode); + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/gpio.c b/src/gpio.c index b7d85e9..8ab9a03 100644 --- a/src/gpio.c +++ b/src/gpio.c @@ -27,3 +27,9 @@ void gpio_clear(gpio_t gpio) uint32_t value = litepcie_readl(gpio.fd, gpio.reg); litepcie_writel(gpio.fd, gpio.reg, (value & ~gpio.bit_mask)); } + +void gpio_group_set(gpio_t gpio, uint32_t set) +{ + uint32_t value = litepcie_readl(gpio.fd, gpio.reg) & ~gpio.bit_mask; + litepcie_writel(gpio.fd, gpio.reg, (value | (set & gpio.bit_mask))); +} diff --git a/src/gpio.h b/src/gpio.h index fc02610..b3575f0 100644 --- a/src/gpio.h +++ b/src/gpio.h @@ -28,6 +28,7 @@ typedef struct gpio_s uint32_t gpio_get(gpio_t gpio); void gpio_set(gpio_t gpio); void gpio_clear(gpio_t gpio); +void gpio_group_set(gpio_t gpio, uint32_t set); #ifdef __cplusplus } diff --git a/src/hmcad15xx.c b/src/hmcad15xx.c index c30ef92..dd61073 100644 --- a/src/hmcad15xx.c +++ b/src/hmcad15xx.c @@ -39,10 +39,10 @@ int32_t hmcad15xx_init(hmcad15xxADC_t* adc, spi_dev_t dev) adc->channelCfg[0].input = HMCAD15_ADC_IN1; adc->channelCfg[0].invert = 1; adc->channelCfg[0].coarse = 0; - adc->channelCfg[0].fine = 0; adc->clockDiv = HMCAD15_CLK_DIV_1; adc->fullScale_x10 = HMCAD15_FULL_SCALE_DEFAULT; adc->drive = HMCAD15_LVDS_DS_15; + adc->lvdsTerm = HMCAD15_LVDS_TERM_94; adc->lvdsPhase = HMCAD15_LVDS_PHASE_DEFAULT; adc->low_clk = 0; @@ -58,6 +58,9 @@ int32_t hmcad15xx_init(hmcad15xxADC_t* adc, spi_dev_t dev) //Gain dB mode hmcad15xxRegWrite(adc, HMCAD15_REG_GAIN_SEL, 0); + //Set Jitter Ctrl + hmcad15xxRegWrite(adc, HMCAD15_REG_CLK_JITTER, 0xFF); + //Channel Conf hmcad15xxApplySampleMode(adc); hmcad15xxApplyChannelMap(adc); @@ -104,6 +107,7 @@ int32_t hmcad15xx_power_mode(hmcad15xxADC_t* adc, hmcad15xxPower_t power) HMCAD15_DUAL_CH_1_SLP : 0; break; case HMCAD15_QUAD_CHANNEL: + case HMCAD15_PREC_QUAD_CHANNEL: data |= adc->channelCfg[0].active == 0 ? HMCAD15_QUAD_CH_0_SLP : 0; data |= adc->channelCfg[1].active == 0 ? @@ -175,6 +179,40 @@ int32_t hmcad15xx_full_scale_adjust(hmcad15xxADC_t* adc, int8_t adjustment) return TS_STATUS_OK; } +int32_t hmcad15xx_fine_gain_set(hmcad15xxADC_t* adc, bool enable) +{ + uint16_t gainVals = 0; + if(!adc) + { + return TS_STATUS_ERROR; + } + + if(enable) + { + gainVals = adc->fineCal[0]; + gainVals |= adc->fineCal[1] << 8; + hmcad15xxRegWrite(adc, HMCAD15_REG_FINE_GAIN_1_2, gainVals); + gainVals = adc->fineCal[2]; + gainVals |= adc->fineCal[3] << 8; + hmcad15xxRegWrite(adc, HMCAD15_REG_FINE_GAIN_3_4, gainVals); + gainVals = adc->fineCal[4]; + gainVals |= adc->fineCal[5] << 8; + hmcad15xxRegWrite(adc, HMCAD15_REG_FINE_GAIN_5_6, gainVals); + gainVals = adc->fineCal[6]; + gainVals |= adc->fineCal[7] << 8; + hmcad15xxRegWrite(adc, HMCAD15_REG_FINE_GAIN_7_8, gainVals); + //Enable Fine Gain Control + hmcad15xxRegWrite(adc, HMCAD15_REG_GAIN_SEL, HMCAD15_FGAIN_EN); + } + else + { + //Disable Fine Gain Control + hmcad15xxRegWrite(adc, HMCAD15_REG_GAIN_SEL, 0); + } + return TS_STATUS_OK; + +} + int32_t hmcad15xx_set_test_pattern(hmcad15xxADC_t* adc, hmcad15xxTestMode_t mode, uint16_t testData1, uint16_t testData2) { if(!adc) @@ -226,7 +264,8 @@ int32_t hmcad15xx_set_sample_mode(hmcad15xxADC_t* adc, uint32_t sample_rate, hmc if(((adc->mode == HMCAD15_SINGLE_CHANNEL) && (sample_rate < HMCAD15_SINGLE_LOW_CLK_THRESHOLD)) || ((adc->mode == HMCAD15_DUAL_CHANNEL) && (sample_rate < HMCAD15_DUAL_LOW_CLK_THRESHOLD)) || - ((adc->mode == HMCAD15_QUAD_CHANNEL) && (sample_rate < HMCAD15_QUAD_LOW_CLK_THRESHOLD))) + ((adc->mode == HMCAD15_QUAD_CHANNEL) && (sample_rate < HMCAD15_QUAD_LOW_CLK_THRESHOLD)) || + ((adc->mode == HMCAD15_PREC_QUAD_CHANNEL) && (sample_rate < HMCAD15_PREC_LOW_CLK_THRESHOLD))) { adc->low_clk = 1; } @@ -260,6 +299,14 @@ static void hmcad15xxApplyLvdsMode(hmcad15xxADC_t* adc) HMCAD15_LVDS_DS_DATA(adc->drive)); hmcad15xxRegWrite(adc, HMCAD15_REG_LVDS_CURRENT, data); + // Set LVDS Termination + data = adc->lvdsTerm == 0 ? 0 : + (HMCAD15_LVDS_DS_LCLK(adc->lvdsTerm) | + HMCAD15_LVDS_DS_FRAME(adc->lvdsTerm) | + HMCAD15_LVDS_DS_DATA(adc->lvdsTerm) | + HMCAD15_LVDS_TERM_EN_MASK); + hmcad15xxRegWrite(adc, HMCAD15_REG_TERM, data); + // Set LVDS Data Width (bits per sample) data = (HMCAD15_DATA_WIDTH(adc->width) | HMCAD15_LOW_CLK(adc->low_clk)); @@ -290,10 +337,15 @@ static void hmcad15xxApplySampleMode(hmcad15xxADC_t* adc) HMCAD15_CLK_DIV_SET(adc->clockDiv); break; case HMCAD15_QUAD_CHANNEL: - case HMCAD15_14BIT_QUAD_CHANNEL: adc->clockDiv = HMCAD15_CLK_DIV_4; data = HMCAD15_SAMPLE_MODE_SET(adc->mode) | - HMCAD15_CLK_DIV_SET(adc->clockDiv); + HMCAD15_CLK_DIV_SET(adc->clockDiv); + break; + case HMCAD15_PREC_QUAD_CHANNEL: + adc->clockDiv = HMCAD15_CLK_DIV_1; + data = HMCAD15_SAMPLE_MODE_SET(adc->mode) | + HMCAD15_SAMPLE_MODE_PREC | + HMCAD15_CLK_DIV_SET(adc->clockDiv); break; } hmcad15xxRegWrite(adc, HMCAD15_REG_CHAN_MODE, data); @@ -321,6 +373,7 @@ static void hmcad15xxApplyChannelMap(hmcad15xxADC_t* adc) HMCAD15_CH_INVERT_D2(adc->channelCfg[1].invert); break; case HMCAD15_QUAD_CHANNEL: + case HMCAD15_PREC_QUAD_CHANNEL: in12 = HMCAD15_SEL_CH_1(adc->channelCfg[0].input); in12 |= HMCAD15_SEL_CH_2(adc->channelCfg[1].input); in34 = HMCAD15_SEL_CH_3(adc->channelCfg[2].input); @@ -329,7 +382,10 @@ static void hmcad15xxApplyChannelMap(hmcad15xxADC_t* adc) HMCAD15_CH_INVERT_Q2(adc->channelCfg[1].invert) | HMCAD15_CH_INVERT_Q3(adc->channelCfg[2].invert) | HMCAD15_CH_INVERT_Q4(adc->channelCfg[3].invert)); - break; + break; + default: + LOG_ERROR("ADC Mode Not Supported: %d", adc->mode); + return; } hmcad15xxRegWrite(adc, HMCAD15_REG_IN_SEL_1_2, in12); @@ -353,11 +409,15 @@ static void hmcad15xxApplyChannelGain(hmcad15xxADC_t* adc) hmcad15xxRegWrite(adc, HMCAD15_REG_COARSE_GAIN_2, cgain); break; case HMCAD15_QUAD_CHANNEL: + case HMCAD15_PREC_QUAD_CHANNEL: cgain = HMCAD15_CGAIN_Q1(adc->channelCfg[0].coarse); cgain |= HMCAD15_CGAIN_Q2(adc->channelCfg[1].coarse); cgain |= HMCAD15_CGAIN_Q3(adc->channelCfg[2].coarse); cgain |= HMCAD15_CGAIN_Q4(adc->channelCfg[3].coarse); hmcad15xxRegWrite(adc, HMCAD15_REG_COARSE_GAIN_1, cgain); - break; + break; + default: + LOG_ERROR("ADC Mode Not Supported: %d", adc->mode); + break; } } \ No newline at end of file diff --git a/src/hmcad15xx.h b/src/hmcad15xx.h index 8b05147..2676c7d 100644 --- a/src/hmcad15xx.h +++ b/src/hmcad15xx.h @@ -53,6 +53,7 @@ extern "C" { #define HMCAD15_NUM_ADC (4) #define HMCAD15_NUM_CHANNELS (4) +#define HMCAD15_NUM_BRANCHES (8) #define HMCAD15_FULL_SCALE_SET(x) (((((x+2) - HMCAD15_FULL_SCALE_MIN) * 0x3F) / \ (HMCAD15_FULL_SCALE_MAX - HMCAD15_FULL_SCALE_MIN)) & 0x3F) @@ -62,6 +63,8 @@ extern "C" { #define HMCAD15_SAMPLE_MODE_SET(x) ((x) & 0x0F) +#define HMCAD15_SAMPLE_MODE_PREC (1 << 3) + #define HMCAD15_CLK_DIV_SET(x) (((x) & 0x03) << 8) #define HMCAD15_CLK_DIV_1 (0) #define HMCAD15_CLK_DIV_2 (1) @@ -78,6 +81,9 @@ extern "C" { #define HMCAD15_SEL_CH_3(x) (((x) & 0x0F) << 1) #define HMCAD15_SEL_CH_4(x) (((x) & 0x0F) << 9) +#define HMCAD15_CGAIN_MODE_X (0x1 << 0) +#define HMCAD15_FGAIN_EN (0x1 << 1) + #define HMCAD15_CGAIN_Q1(x) ((x) & 0xF) #define HMCAD15_CGAIN_Q2(x) (((x) & 0xF) << 4) #define HMCAD15_CGAIN_Q3(x) (((x) & 0xF) << 8) @@ -104,6 +110,11 @@ extern "C" { #define HMCAD15_LVDS_DS_FRAME(x) (((x) & 0x07) << 4) #define HMCAD15_LVDS_DS_DATA(x) (((x) & 0x07) << 8) +#define HMCAD15_LVDS_TERM_LCLK(x) ((x) & 0x07) +#define HMCAD15_LVDS_TERM_FRAME(x) (((x) & 0x07) << 4) +#define HMCAD15_LVDS_TERM_DATA(x) (((x) & 0x07) << 8) +#define HMCAD15_LVDS_TERM_EN_MASK (1 << 14) + #define HMCAD15_DATA_WIDTH(x) ((x) & 0x07) #define HMCAD15_LOW_CLK(x) (((x) & 0x01) << 3) @@ -143,6 +154,14 @@ extern "C" { #define HMCAD15_LVDS_DS_55 (6) #define HMCAD15_LVDS_DS_45 (7) +#define HMCAD15_LVDS_TERM_DEFAULT (0) +#define HMCAD15_LVDS_TERM_260 (1) +#define HMCAD15_LVDS_TERM_150 (2) +#define HMCAD15_LVDS_TERM_94 (3) +#define HMCAD15_LVDS_TERM_125 (4) +#define HMCAD15_LVDS_TERM_80 (5) +#define HMCAD15_LVDS_TERM_66 (6) +#define HMCAD15_LVDS_TERM_55 (7) #define HMCAD15_CLK_DIV_DEFAULT (1) #define HMCAD15_LVDS_PHASE_DEFAULT (HMCAD15_LVDS_PHASE_0DEG) @@ -157,14 +176,14 @@ typedef enum hmcad15xxMode_e HMCAD15_SINGLE_CHANNEL = 1, HMCAD15_DUAL_CHANNEL = 2, HMCAD15_QUAD_CHANNEL = 4, - HMCAD15_14BIT_QUAD_CHANNEL = 8 + HMCAD15_PREC_QUAD_CHANNEL = 8 } hmcad15xxMode_t; typedef enum hmcad15xxDataWidth_e { - HMCAD15_8_BIT, - HMCAD15_12_BIT, - HMCAD15_14_BIT + HMCAD15_8_BIT = 0, + HMCAD15_12_BIT = 1, + HMCAD15_14_BIT = 4 } hmcad15xxDataWidth_t; typedef enum hmcad15xxPower_e @@ -195,7 +214,6 @@ typedef struct hmcad15xxChCfg_s uint8_t active; uint8_t input; uint8_t coarse; - uint8_t fine; uint8_t invert; } hmcad15xxChCfg_t; @@ -207,9 +225,11 @@ typedef struct hmcad15xxADC_s hmcad15xxBtcFmt_t format; hmcad15xxDataWidth_t width; int32_t fullScale_x10; + uint8_t fineCal[HMCAD15_NUM_BRANCHES]; uint8_t clockDiv; uint8_t lvdsPhase; uint8_t drive; + uint8_t lvdsTerm; uint8_t low_clk; } hmcad15xxADC_t; @@ -259,6 +279,16 @@ int32_t hmcad15xx_set_channel_config(hmcad15xxADC_t* adc); */ int32_t hmcad15xx_full_scale_adjust(hmcad15xxADC_t* adc, int8_t adjustment); +/** + * @brief Update settings for the branch fine gain control + * + * @param adc Pointer to an ADC instance + * @param enable Enable or Disable the fine gain adjustments + * @return int32_t TS_STATUS_OK if successful, TS_INVALID_PARAM if the gain settings + * could not be applied. + */ +int32_t hmcad15xx_fine_gain_set(hmcad15xxADC_t* adc, bool enable); + /** * @brief Put the HMCAD15xx into a test mode that sends test data out the LVDS * interface diff --git a/src/i2c.c b/src/i2c.c index d422ebd..d6ebdb2 100644 --- a/src/i2c.c +++ b/src/i2c.c @@ -14,19 +14,45 @@ #include "i2c.h" #include "util.h" +#define I2C_PHY_SPEED_MODE_REG (0x0000) +#define I2C_MASTER_ACTIVE_REG (0x0004) +#define I2C_MASTER_SETTINGS_REG (0x0008) +#define I2C_MASTER_ADDR_REG (0x000C) +#define I2C_MASTER_RXTX_REG (0x0010) +#define I2C_MASTER_STATUS_REG (0x0014) + +#define I2C_MASTER_SETTINGS_LEN_TX_OFFSET (0) +#define I2C_MASTER_SETTINGS_LEN_RX_OFFSET (8) +#define I2C_MASTER_SETTINGS_RECOVER_OFFSET (16) +#define I2C_MASTER_STATUS_TX_READY_OFFSET (0) +#define I2C_MASTER_STATUS_RX_READY_OFFSET (1) +#define I2C_MASTER_STATUS_NACK_OFFSET (8) +#define I2C_MASTER_STATUS_TX_UNFINISHED_OFFSET (16) +#define I2C_MASTER_STATUS_RX_UNFINISHED_OFFSET (17) + #define I2C_WAIT_TIMEOUT (10000) //I2C Transfer Settings -#define I2C_TX_LEN(x) (((x) & 0x7) << CSR_I2C_MASTER_SETTINGS_LEN_TX_OFFSET) -#define I2C_RX_LEN(x) (((x) & 0x7) << CSR_I2C_MASTER_SETTINGS_LEN_RX_OFFSET) -#define I2C_RECOVER(x) (((x) & 0x1) << CSR_I2C_MASTER_SETTINGS_RECOVER_OFFSET) +#define I2C_TX_LEN(x) (((x) & 0x7) << I2C_MASTER_SETTINGS_LEN_TX_OFFSET) +#define I2C_RX_LEN(x) (((x) & 0x7) << I2C_MASTER_SETTINGS_LEN_RX_OFFSET) +#define I2C_RECOVER(x) (((x) & 0x1) << I2C_MASTER_SETTINGS_RECOVER_OFFSET) //I2C Status -#define I2C_TX_READY(x) (((x) >> CSR_I2C_MASTER_STATUS_TX_READY_OFFSET) & 0x1) -#define I2C_RX_READY(x) (((x) >> CSR_I2C_MASTER_STATUS_RX_READY_OFFSET) & 0x1) -#define I2C_NACK(x) (((x) >> CSR_I2C_MASTER_STATUS_NACK_OFFSET) & 0x1) -#define I2C_TX_PEND(x) (((x) >> CSR_I2C_MASTER_STATUS_TX_UNFINISHED_OFFSET) & 0x1) -#define I2C_RX_PEND(x) (((x) >> CSR_I2C_MASTER_STATUS_RX_UNFINISHED_OFFSET) & 0x1) +#define I2C_TX_READY(x) (((x) >> I2C_MASTER_STATUS_TX_READY_OFFSET) & 0x1) +#define I2C_RX_READY(x) (((x) >> I2C_MASTER_STATUS_RX_READY_OFFSET) & 0x1) +#define I2C_NACK(x) (((x) >> I2C_MASTER_STATUS_NACK_OFFSET) & 0x1) +#define I2C_TX_PEND(x) (((x) >> I2C_MASTER_STATUS_TX_UNFINISHED_OFFSET) & 0x1) +#define I2C_RX_PEND(x) (((x) >> I2C_MASTER_STATUS_RX_UNFINISHED_OFFSET) & 0x1) + +static inline void i2c_reg_write(i2c_t dev, uint32_t reg, uint32_t val) +{ + litepcie_writel(dev.fd, (dev.peripheral_baseaddr + reg), val); +} + +static inline uint32_t i2c_reg_read(i2c_t dev, uint32_t reg) +{ + return litepcie_readl(dev.fd, (dev.peripheral_baseaddr + reg)); +} /** * @brief Enable the I2C core @@ -35,11 +61,11 @@ static inline void i2c_activate(i2c_t device, bool active) { if(active) { - litepcie_writel(device.fd, CSR_I2C_MASTER_ACTIVE_ADDR, 1); + i2c_reg_write(device, I2C_MASTER_ACTIVE_REG, 1); } else { - litepcie_writel(device.fd, CSR_I2C_MASTER_ACTIVE_ADDR, 0); + i2c_reg_write(device, I2C_MASTER_ACTIVE_REG, 0); } } @@ -48,7 +74,7 @@ static inline void i2c_activate(i2c_t device, bool active) */ static inline void i2c_set_addr(i2c_t device) { - litepcie_writel(device.fd, CSR_I2C_MASTER_ADDR_ADDR, device.devAddr); + i2c_reg_write(device, I2C_MASTER_ADDR_REG, device.devAddr); } /** @@ -57,7 +83,7 @@ static inline void i2c_set_addr(i2c_t device) static inline void i2c_wait_tx_ready(i2c_t device) { uint32_t timeout = I2C_WAIT_TIMEOUT; - while(timeout && (I2C_TX_READY(litepcie_readl(device.fd, CSR_I2C_MASTER_STATUS_ADDR)) == 0)) + while(timeout && (I2C_TX_READY(i2c_reg_read(device, I2C_MASTER_STATUS_REG)) == 0)) { NS_DELAY(1000); timeout--; @@ -70,7 +96,7 @@ static inline void i2c_wait_tx_ready(i2c_t device) static inline void i2c_wait_rx_ready(i2c_t device) { uint32_t timeout = I2C_WAIT_TIMEOUT; - while(timeout && (I2C_RX_READY(litepcie_readl(device.fd, CSR_I2C_MASTER_STATUS_ADDR)) == 0)) + while(timeout && (I2C_RX_READY(i2c_reg_read(device, I2C_MASTER_STATUS_REG)) == 0)) { NS_DELAY(1000); timeout--; @@ -89,23 +115,23 @@ static bool i2c_tx(i2c_t device, uint32_t data, uint8_t len) uint32_t i2cStatus; //Write Settings - litepcie_writel(device.fd, CSR_I2C_MASTER_SETTINGS_ADDR, I2C_TX_LEN(len)); + i2c_reg_write(device, I2C_MASTER_SETTINGS_REG, I2C_TX_LEN(len)); //Wait for TX ready i2c_wait_tx_ready(device); //Write TX word - litepcie_writel(device.fd, CSR_I2C_MASTER_RXTX_ADDR, data); + i2c_reg_write(device, I2C_MASTER_RXTX_REG, data); //Wait for RX ready i2c_wait_rx_ready(device); //Get ACK - i2cStatus = litepcie_readl(device.fd, CSR_I2C_MASTER_STATUS_ADDR); + i2cStatus = i2c_reg_read(device, I2C_MASTER_STATUS_REG); ack = I2C_NACK(i2cStatus) ? false : true; //Read the RX word - i2cStatus = litepcie_readl(device.fd, CSR_I2C_MASTER_RXTX_ADDR); + i2cStatus = i2c_reg_read(device, I2C_MASTER_RXTX_REG); return ack; } @@ -122,23 +148,23 @@ static bool i2c_rx(i2c_t device, uint8_t len, uint32_t* pData) bool ack; //Write Settings - litepcie_writel(device.fd, CSR_I2C_MASTER_SETTINGS_ADDR, I2C_RX_LEN(len)); + i2c_reg_write(device, I2C_MASTER_SETTINGS_REG, I2C_RX_LEN(len)); //Wait for TX ready i2c_wait_tx_ready(device); //Write TX word - litepcie_writel(device.fd, CSR_I2C_MASTER_RXTX_ADDR, 0); + i2c_reg_write(device, I2C_MASTER_RXTX_REG, 0); //Wait for RX Ready i2c_wait_rx_ready(device); //Get ACK - status = litepcie_readl(device.fd, CSR_I2C_MASTER_STATUS_ADDR); + status = i2c_reg_read(device, I2C_MASTER_STATUS_REG); ack = I2C_NACK(status) ? false : true; //Read Data word - *pData = litepcie_readl(device.fd, CSR_I2C_MASTER_RXTX_ADDR); + *pData = i2c_reg_read(device, I2C_MASTER_RXTX_REG); return ack; } @@ -162,22 +188,22 @@ void i2c_rate_set(i2c_t device, i2c_rate_t rate) i2c_activate(device, false); //Set Clock Rate - litepcie_writel(device.fd, CSR_I2C_PHY_SPEED_MODE_ADDR, rate); + i2c_reg_write(device, I2C_PHY_SPEED_MODE_REG, rate); } void i2c_reset(i2c_t device) { i2c_activate(device, true); - litepcie_writel(device.fd, CSR_I2C_MASTER_SETTINGS_ADDR, I2C_RECOVER(1)); + i2c_reg_write(device, I2C_MASTER_SETTINGS_REG, I2C_RECOVER(1)); i2c_wait_tx_ready(device); - litepcie_writel(device.fd, CSR_I2C_MASTER_RXTX_ADDR, 0); + i2c_reg_write(device, I2C_MASTER_RXTX_REG, 0); i2c_wait_rx_ready(device); - litepcie_readl(device.fd, CSR_I2C_MASTER_RXTX_ADDR); + i2c_reg_read(device, I2C_MASTER_RXTX_REG); i2c_activate(device, false); } diff --git a/src/i2c.h b/src/i2c.h index 8a8c44f..43a8dab 100644 --- a/src/i2c.h +++ b/src/i2c.h @@ -25,6 +25,7 @@ extern "C" { typedef struct i2c_s { file_t fd; + uint32_t peripheral_baseaddr; uint8_t devAddr; } i2c_t; diff --git a/src/mcp443x.h b/src/mcp443x.h index 1b7be58..7743165 100644 --- a/src/mcp443x.h +++ b/src/mcp443x.h @@ -21,14 +21,25 @@ extern "C" { #define MCP4432_NUM_CH (4) -#define MCP4432_RWIPER (75) -#define MCP4432_MAX (1 << 7) -#define MCP4432_MIN (0) -#define MCP4432_DEFAULT (0x40) -#define MCP4432_FULL_SCALE_OHM (50000) - -#define MCP4432_503_SET(x) ((((x) - MCP4432_RWIPER) * MCP4432_MAX) / MCP4432_FULL_SCALE_OHM) -#define MCP4432_503_OHM(x) ((((x) * MCP4432_FULL_SCALE_OHM) / MCP4432_MAX) + MCP4432_RWIPER) +#define MCP4432_RWIPER (75) +#define MCP4432_NUM_BITS (7) +#define MCP4432_MAX (1 << 7) +#define MCP4432_MIN (0) +#define MCP4432_DEFAULT (0x40) + +#define MCP4432_503_FULL_SCALE_OHM (50000) +#define MCP4432_503_SET(x) ((((x) - MCP4432_RWIPER) * MCP4432_MAX) / MCP4432_503_FULL_SCALE_OHM) +#define MCP4432_503_OHM(x) ((((x) * MCP4432_503_FULL_SCALE_OHM) / MCP4432_MAX) + MCP4432_RWIPER) + +#define MCP4452_RWIPER (75) +#define MCP4452_NUM_BITS (8) +#define MCP4452_MAX (1 << 8) +#define MCP4452_MIN (0) +#define MCP4452_DEFAULT (0x40) + +#define MCP4452_104_FULL_SCALE_OHM (100000) +#define MCP4452_104_SET(x) ((((x) - MCP4452_RWIPER) * MCP4452_MAX) / MCP4452_104_FULL_SCALE_OHM) +#define MCP4452_104_OHM(x) ((((x) * MCP4452_104_FULL_SCALE_OHM) / MCP4452_MAX) + MCP4452_RWIPER) int32_t mcp443x_set_wiper(i2c_t dev, uint8_t channel, uint8_t val); diff --git a/src/mcp4728.h b/src/mcp4728.h index 7d91356..e9444a3 100644 --- a/src/mcp4728.h +++ b/src/mcp4728.h @@ -6,6 +6,12 @@ * Copyright (C) 2024 / Nate Meyer / nate.devel@gmail.com * */ +#ifndef _MCP4728_H_ +#define _MCP4728_H_ + +#ifdef __cplusplus +extern "C" { +#endif #include #include "i2c.h" @@ -49,4 +55,9 @@ typedef struct Mcp4728ChannelConfig_s * * @return int32_t TS_STATUS_OK on success, else error */ -int32_t mcp4728_channel_set(i2c_t dev, uint8_t channel, Mcp4728ChannelConfig_t conf); \ No newline at end of file +int32_t mcp4728_channel_set(i2c_t dev, uint8_t channel, Mcp4728ChannelConfig_t conf); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/mcp_clkgen.c b/src/mcp_clkgen.c index 637bee8..f9a34ca 100644 --- a/src/mcp_clkgen.c +++ b/src/mcp_clkgen.c @@ -59,6 +59,35 @@ int32_t mcp_clkgen_config(i2c_t device, const mcp_clkgen_conf_t* confData, uint3 return TS_STATUS_OK; } +int32_t mcp_clkgen_status(i2c_t device, const mcp_clkgen_status_t* statusData, uint32_t statusLen) +{ + int32_t status = 0; + + if(NULL == statusData) + { + //Error + return TS_STATUS_ERROR; + } + LOG_DEBUG("Status Regs:"); + + for(uint32_t i = 0; i < statusLen; i++) + { + uint8_t data[1] = {0}; + if(!i2c_read(device, (uint32_t)ZL302XX_READ_REG(statusData[i].addr), + data, 1, ZL302XX_ADDR_LEN)) + { + LOG_DEBUG("\t%06X (NACK!)", (uint32_t)ZL302XX_READ_REG(statusData[i].addr)); + return TS_STATUS_ERROR; + } + LOG_DEBUG("\t%06X : %02X", (uint32_t)ZL302XX_WRITE_REG(statusData[i].addr), data[0]); + + status |= (data[0] & statusData[i].mask) ? (1 << i) : 0; + } + + return status; +} + + void mcp_clkgen_regdump(i2c_t device, const mcp_clkgen_conf_t* confData, uint32_t confLen) { diff --git a/src/mcp_clkgen.h b/src/mcp_clkgen.h index 396631b..9baca15 100644 --- a/src/mcp_clkgen.h +++ b/src/mcp_clkgen.h @@ -33,6 +33,11 @@ typedef struct mcp_clkgen_conf_s { }; }mcp_clkgen_conf_t; +typedef struct mcp_clkgen_status_s { + uint16_t addr; + uint8_t mask; +} mcp_clkgen_status_t; + /** * @brief Configure the PLL with a list of registers. * @@ -43,6 +48,15 @@ typedef struct mcp_clkgen_conf_s { */ int32_t mcp_clkgen_config(i2c_t device, const mcp_clkgen_conf_t* confData, uint32_t confLen); +/** + * @brief Get a set of status flags for the PLL + * @param device I2C device for the zl30260/zl30250 + * @param statusData Array of status registers to read + * @param statusLen Length of the status array + * @return int32_t Bitfield of status flags, or TS_STATUS_ERROR on error + */ +int32_t mcp_clkgen_status(i2c_t device, const mcp_clkgen_status_t* statusData, uint32_t statusLen); + /** * @brief Debug function to print all register values and compare them to written values * diff --git a/src/mcp_zl3026x.c b/src/mcp_zl3026x.c index 9c9c861..c758152 100644 --- a/src/mcp_zl3026x.c +++ b/src/mcp_zl3026x.c @@ -32,7 +32,6 @@ static uint64_t mcp_zl3026x_selected_input_freq(zl3026x_clk_config_t *conf); int32_t mcp_zl3026x_build_config(mcp_clkgen_conf_t* confData, uint32_t len, zl3026x_clk_config_t conf) { int32_t calLen = 0; - // Reference App Note ZLAN-590 // https://ww1.microchip.com/downloads/aemDocuments/documents/TCG/ApplicationNotes/ApplicationNotes/ConfigurationSequenceZLAN-590.pdf @@ -82,10 +81,10 @@ int32_t mcp_zl3026x_build_config(mcp_clkgen_conf_t* confData, uint32_t len, zl30 { if(conf.in_clks[ch].enable) { - MCP_ADD_REG_WRITE(&confData[calLen], (0x0303+ch), (uint8_t)conf.in_clks[ch].input_divider); + LOG_DEBUG("Input Clock %d: Freq %llu Hz, Div %d", ch, conf.in_clks[ch].input_freq, conf.in_clks[ch].input_divider); + MCP_ADD_REG_WRITE(&confData[calLen], (0x0303+ch), (uint8_t)conf.in_clks[ch].input_divider | ZL3026X_VALTIME_DEFAULT); calLen++; in_ch_bitmap |= (1 << ch); - break; } } if(in_ch_bitmap == 0) @@ -197,6 +196,8 @@ int32_t mcp_zl3026x_build_config(mcp_clkgen_conf_t* confData, uint32_t len, zl30 } } + LOG_DEBUG("Calculated APLL Output Freq: %llu Hz", pll_out); + uint64_t pll_vco = 4200000000; uint32_t pll_int_div = pll_vco/pll_out; //validate pll_int_div 4-15 @@ -236,7 +237,12 @@ int32_t mcp_zl3026x_build_config(mcp_clkgen_conf_t* confData, uint32_t len, zl30 MCP_ADD_REG_WRITE(&confData[calLen], 0x0101, pll_int_div & 0x0F); //PLL Output Integer Divide calLen++; - MCP_ADD_REG_WRITE(&confData[calLen], 0x0102, (conf.input_select & 0x7)); //PLL Input + uint8_t apll_cr3 = (conf.input_select & 0x7); //PLL Input + if(conf.alternate_select != ZL3026X_INPUT_NONE) + { + apll_cr3 |= 0x80 | ((conf.alternate_select & 0x07) << 3); //Enable Input Monitoring and Alternate PLL Input + } + MCP_ADD_REG_WRITE(&confData[calLen], 0x0102, apll_cr3); //PLL Input calLen++; //PLL AFBDIV @@ -369,6 +375,7 @@ int32_t mcp_zl3026x_build_config(mcp_clkgen_conf_t* confData, uint32_t len, zl30 { if(conf.out_clks[ch].enable) { + LOG_DEBUG("Setting Output CLK %d to %llu Hz", ch, conf.out_clks[ch].output_freq); /** * The frequency of the clock from the medium-speed divider is divided by LSDIV+1. * @@ -382,32 +389,42 @@ int32_t mcp_zl3026x_build_config(mcp_clkgen_conf_t* confData, uint32_t len, zl30 uint8_t out_div, out_div_med = 1; out_div = 0x80; //Phase align output + uint32_t clksrc_freq = 0; if(conf.out_clks[ch].output_pll_select == ZL3026X_PLL_INT_DIV) { - if(conf.out_clks[ch].output_freq != pll_out) + clksrc_freq = pll_out; + } + else if(conf.out_clks[ch].output_pll_select == ZL3026X_PLL_BYPASS) + { + clksrc_freq = mcp_zl3026x_selected_input_freq(&conf); + } + + if(conf.out_clks[ch].output_freq != clksrc_freq) + { + out_divide = clksrc_freq / conf.out_clks[ch].output_freq; + // Note: output_freq = clksrc_freq / (div_med * div_low) + while(out_divide >= (1 << 7)) { - out_divide = pll_out / conf.out_clks[ch].output_freq; - // Note: output_freq = pll_out / (div_med * div_low) - while(out_divide >= (1 << 7)) + if(out_divide & 0x1) { - if(out_divide & 0x1) - { - LOG_ERROR("======== TODO: FIX OUTPUT DIVIDE ======="); - return TS_STATUS_ERROR; - } - out_divide >>= 1; - out_div_low <<= 1; + LOG_ERROR("======== TODO: FIX OUTPUT DIVIDE ======="); + return TS_STATUS_ERROR; } - - out_div_med = out_divide; - - out_div |= (out_div_med-1) & 0x7F; + out_divide >>= 1; + out_div_low <<= 1; } + + out_div_med = out_divide; + + out_div |= (out_div_med-1) & 0x7F; } + LOG_DEBUG("Setting Output CLK %d LDIV to %02X", ch, out_div_low); + LOG_DEBUG("Setting Output CLK %d CR1 to %02X", ch, out_div); MCP_ADD_REG_WRITE(&confData[calLen], 0x0200+(ch*0x10), out_div); calLen++; + LOG_DEBUG("Setting Output CLK %d mode to %02X", ch, conf.out_clks[ch].output_mode); MCP_ADD_REG_WRITE(&confData[calLen], 0x0201+(ch*0x10), conf.out_clks[ch].output_mode); calLen++; diff --git a/src/mcp_zl3026x.h b/src/mcp_zl3026x.h index 7111f6a..8bd40ca 100644 --- a/src/mcp_zl3026x.h +++ b/src/mcp_zl3026x.h @@ -27,12 +27,19 @@ extern "C" { #define ZL3026X_OUT_MDIV_MIN_CLK (375000000) +#define ZL3026X_INPUT_CLK_MIN (9720000) +#define ZL3026X_INPUT_CLK_MAX (156250000) + +#define ZL3026X_VALTIME_DEFAULT (0x0C) + + typedef enum zl3026x_input_e { ZL3026X_INPUT_IC1, ZL3026X_INPUT_IC2, ZL3026X_INPUT_IC3, ZL3026X_INPUT_XO, - ZL3026X_INPUT_XO_DBL + ZL3026X_INPUT_XO_DBL, + ZL3026X_INPUT_NONE } zl3026x_input_t; typedef enum zl3026x_input_div_e { @@ -85,6 +92,7 @@ typedef struct zl3026x_clk_config_s { zl3026x_input_clk_t in_clks[ZL3026X_NUM_INPUT_CLK]; zl3026x_output_clk_t out_clks[ZL3026X_NUM_OUTPUT_CLK]; zl3026x_input_t input_select; + zl3026x_input_t alternate_select; //TODO Add GPIO Config } zl3026x_clk_config_t; diff --git a/src/platform.c b/src/platform.c index 1cabfba..53fab5c 100644 --- a/src/platform.c +++ b/src/platform.c @@ -52,6 +52,19 @@ const mcp_clkgen_conf_t ZL30260_CONF[] = { const uint32_t ZL30260_CONF_SIZE = sizeof(ZL30260_CONF)/sizeof(ZL30260_CONF[0]); +const mcp_clkgen_status_t ZL30260_STATUS[] = { + {.addr=0x004C, .mask=0x02}, //XA.XAV Status + {.addr=0x004D, .mask=0x02}, //IC1.ICV Status + {.addr=0x004E, .mask=0x02}, //IC2.ICV Status + {.addr=0x004F, .mask=0x02}, //IC3.ICV Status + {.addr=0x0048, .mask=0x04}, //APLL.ALK Status + {.addr=0x0048, .mask=0x40}, //APLL.AIFL Status + {.addr=0x0048, .mask=0x10}, //APLL.AIFH Status + {.addr=0x0048, .mask=0x01}, //APLL.SELREF Status +}; + +const uint32_t ZL30260_STATUS_SIZE = sizeof(ZL30260_STATUS)/sizeof(ZL30260_STATUS[0]); + const mcp_clkgen_conf_t ZL30250_CONF[] = { {.action=MCP_CLKGEN_WRITE_REG, .addr=0x0009, .value=0x02}, {.action=MCP_CLKGEN_WRITE_REG, .addr=0x0621, .value=0x08}, @@ -86,6 +99,25 @@ const mcp_clkgen_conf_t ZL30250_CONF[] = { const uint32_t ZL30250_CONF_SIZE = sizeof(ZL30250_CONF)/sizeof(ZL30250_CONF[0]); +// +// Bitmasks for LED indicators +// +//Beta hardware has a single LED that is active low +const led_signals_t ts_beta_leds = { + .error = 1, + .ready = 1, + .active = 0, + .disabled = 1 +}; + +//Production hardware has 3 active high LEDs (RGB) +const led_signals_t ts_dev_leds = { + .error = 1, + .ready = 2, + .active = 4, + .disabled = 0 +}; + // A35T/A50T (0x80_0000): @@ -119,6 +151,7 @@ const flash_layout_t ts_64Mb_layout = { // | 0x1000000 - 0x1AFFFFF | Primary Bitstream | // | 0x1B00000 - 0x1B0FFFF | Barrier Image B | // | 0x1B10000 - 0x1FFFFFF | Available for User Data | + const flash_layout_t ts_256Mb_layout = { .factory_bitstream_start = 0x0000000, .factory_bitstream_end = 0x0B00000, diff --git a/src/platform.h b/src/platform.h index d6abd06..c97b290 100644 --- a/src/platform.h +++ b/src/platform.h @@ -8,12 +8,19 @@ #ifndef _PLATFORM_H_ #define _PLATFORM_H_ +#ifdef __cplusplus +extern "C" { +#endif + #include "ts_common.h" #include "mcp_clkgen.h" #include "mcp_zl3026x.h" #include "i2c.h" #include "csr.h" +#define TS_STATUS_LED_ADDR (CSR_DEV_STATUS_LEDS_ADDR) +#define TS_STATUS_LED_COUNT (3) +#define TS_STATUS_LED_MASK ((1 << TS_STATUS_LED_COUNT) - 1) #define TS_ADC_FULL_SCALE_ADJUST_DEFAULT (0x20) /**< Full Scale Adjust set 2V */ #define TS_ADC_CH_COARSE_GAIN_DEFAULT (9) @@ -22,28 +29,45 @@ #define TS_ADC_CH_NO_INVERT (0) #define TS_ADC_CH_INVERT (1) -#define TS_AFE_OUTPUT_NOMINAL_mVPP (700.0) +#define TS_AFE_OUTPUT_NOMINAL_uVPP (700000.0) #define TS_ATTENUATION_1M_GAIN_mdB (-33979) /**< 50x Attenuation = 20 * log(1/50) * 1000 */ #define TS_TERMINATION_50OHM_GAIN_mdB (-13979) /**< 5x Attenuation from 50Ohm mode. 20 * log(1/5) * 1000 */ -#define TS_VBUFFER_NOMINAL_MV (2500) -#define TS_VBIAS_NOMINAL_MV (2500) -#define TS_AFE_TRIM_VDD_NOMINAL (5000) +#define TS_VBUFFER_NOMINAL_UV (2500000) +#define TS_VBIAS_NOMINAL_UV (2500000) +#define TS_AFE_TRIM_VDD_NOMINAL (5000000) #define TS_BUFFER_GAIN_NOMINAL_mdB (-250) #define TS_BIAS_RESISTOR_NOMINAL (500) #define TS_PREAMP_INPUT_BIAS_CURRENT_uA (40) -#define TS_SPI_BUS_BASE_ADDR CSR_MAIN_SPI_BASE -#define TS_SPI_BUS_CS_NUM (CSR_MAIN_SPI_CS_SEL_SIZE) +#define TS_AFE_PREAMP_ATTEN_0_mdB (0) +#define TS_AFE_PREAMP_ATTEN_1_mdB (-2000) +#define TS_AFE_PREAMP_ATTEN_2_mdB (-4000) +#define TS_AFE_PREAMP_ATTEN_3_mdB (-6000) +#define TS_AFE_PREAMP_ATTEN_4_mdB (-8000) +#define TS_AFE_PREAMP_ATTEN_5_mdB (-10000) +#define TS_AFE_PREAMP_ATTEN_6_mdB (-12000) +#define TS_AFE_PREAMP_ATTEN_7_mdB (-14000) +#define TS_AFE_PREAMP_ATTEN_8_mdB (-16000) +#define TS_AFE_PREAMP_ATTEN_9_mdB (-18000) +#define TS_AFE_PREAMP_ATTEN_10_mdB (-20000) + +#define TS_SPI_BUS_BASE_ADDR (CSR_SPIBUS_SPI0_CONTROL_ADDR) +#define TS_SPI_BUS_BETA_CS_NUM (5) +#define TS_SPI_BUS_DEV_CS_NUM (4) #define TS_AFE_0_AMP_CS (0) #define TS_AFE_1_AMP_CS (1) #define TS_AFE_2_AMP_CS (2) #define TS_AFE_3_AMP_CS (3) -#define TS_ADC_CS (4) +#define TS_BETA_ADC_CS (4) + +#define TS_ADC_SPI_BUS_BASE_ADDR (CSR_SPIBUS_SPI1_CONTROL_ADDR) +#define TS_ADC_SPI_BUS_CS_NUM (1) +#define TS_ADC_CS (0) -#define TS_I2C_BASE_ADDR CSR_I2C_BASE #define TS_I2C_CLK_RATE I2C_400KHz +#define TS_TRIM_DAC_BUS (CSR_I2CBUS_I2C0_PHY_SPEED_MODE_ADDR) #define TS_TRIM_DAC_I2C_ADDR (0x60) #define TS_TRIM_DAC_DEFAULT (0x800) @@ -52,6 +76,7 @@ #define TS_AFE_2_TRIM_DAC (2) #define TS_AFE_3_TRIM_DAC (3) +#define TS_TRIM_DPOT_BUS (CSR_I2CBUS_I2C0_PHY_SPEED_MODE_ADDR) #define TS_TRIM_DPOT_I2C_ADDR (0x2C) #define TS_TRIM_DPOT_DEFAULT (0x40) @@ -69,20 +94,29 @@ extern const uint32_t ZL30250_CONF_SIZE; extern const mcp_clkgen_conf_t ZL30260_CONF[]; extern const uint32_t ZL30260_CONF_SIZE; +extern const mcp_clkgen_status_t ZL30260_STATUS[]; +extern const uint32_t ZL30260_STATUS_SIZE; + #ifdef TS_REV_3 #define TS_PLL_I2C_ADDR ZL30250_I2C_ADDR #define TS_PLL_CONF ZL30250_CONF #define TS_PLL_CONF_SIZE ZL30250_CONF_SIZE #else -#define TS_PLL_I2C_ADDR ZL30260_I2C_ADDR -#define TS_PLL_CONF ZL30260_CONF -#define TS_PLL_CONF_SIZE ZL30260_CONF_SIZE -#define TS_PLL_INPUT_IDX (1) -#define TS_PLL_INPUT_RATE (10000000) -#define TS_PLL_INPUT_SEL (ZL3026X_INPUT_IC2) - -#define TS_PLL_REFIN_IDX (0) -#define TS_PLL_REFIN_SEL (ZL3026X_INPUT_IC1) +#define TS_PLL_BUS_BETA CSR_I2CBUS_I2C0_PHY_SPEED_MODE_ADDR +#define TS_PLL_BUS_DEV CSR_I2CBUS_I2C1_PHY_SPEED_MODE_ADDR +#define TS_PLL_I2C_ADDR ZL30260_I2C_ADDR +#define TS_PLL_CONF ZL30260_CONF +#define TS_PLL_CONF_SIZE ZL30260_CONF_SIZE +#define TS_PLL_STATUS ZL30260_STATUS +#define TS_PLL_STATUS_LEN ZL30260_STATUS_SIZE +#define TS_PLL_LOCAL_OSC_IDX (1) +#define TS_PLL_LOCAL_OSC_RATE (10000000) +#define TS_PLL_LOCAL_OSC_SEL (ZL3026X_INPUT_IC2) + +#define TS_PLL_REFIN_IDX (0) +#define TS_PLL_REFIN_SEL (ZL3026X_INPUT_IC1) + +#define TS_PLL_INPUT_NONE_SEL (ZL3026X_INPUT_NONE) #define TS_PLL_REFOUT_CLK_IDX (0) #define TS_PLL_REFOUT_RATE_DEFAULT (10000000) @@ -93,6 +127,15 @@ extern const uint32_t ZL30260_CONF_SIZE; #define TS_PLL_SAMPLE_RATE_DEFAULT (1000000000) #define TS_PLL_SAMPLE_CLK_MODE (ZL3026X_OUT_DIFF) #define TS_PLL_SAMPLE_PLL_MODE (ZL3026X_PLL_INT_DIV) + +#define TS_PLL_STATUS_XA_VALID (0) +#define TS_PLL_STATUS_IC1_VALID (1) +#define TS_PLL_STATUS_IC2_VALID (2) +#define TS_PLL_STATUS_IC3_VALID (3) +#define TS_PLL_STATUS_APLL_LOCK (4) +#define TS_PLL_STATUS_APLL_LOW (5) +#define TS_PLL_STATUS_APLL_HIGH (6) +#define TS_PLL_STATUS_APLL_ALT (7) #endif #define TS_PLL_NRST_ADDR CSR_ADC_CONTROL_ADDR @@ -141,6 +184,17 @@ extern const uint32_t ZL30260_CONF_SIZE; #define TS_AFE_3_COUPLING_MASK (1 << (CSR_FRONTEND_CONTROL_COUPLING_OFFSET + 3)) +typedef struct led_signals_s { + uint32_t error; + uint32_t ready; + uint32_t active; + // Others TBD + uint32_t disabled; +} led_signals_t; + +extern const led_signals_t ts_beta_leds; +extern const led_signals_t ts_dev_leds; + typedef struct flash_layout_s { uint32_t factory_bitstream_start; uint32_t factory_bitstream_end; @@ -160,4 +214,9 @@ extern const flash_layout_t ts_64Mb_layout; #define TS_FLASH_64M_MFG (0xC2) //Macronix #define TS_FLASH_64M_ID (0x2537) //64Mb SPI Flash +static inline bool isBetaDevice(file_t ts) { return (0 == (litepcie_readl(ts, CSR_DEV_STATUS_HW_ID_ADDR) & (1 << CSR_DEV_STATUS_HW_ID_HW_VALID_OFFSET)));} + +#ifdef __cplusplus +} +#endif #endif \ No newline at end of file diff --git a/src/samples.c b/src/samples.c index 4eb4344..fa59f94 100644 --- a/src/samples.c +++ b/src/samples.c @@ -36,7 +36,13 @@ #define TS_DMA_NAME_ARGS(chan, dev) (chan), (dev) #define TS_DMA_OS_FLAGS (FILE_ATTRIBUTE_NORMAL | \ FILE_FLAG_NO_BUFFERING) -#else +#elif defined(__APPLE__) +#define TS_DMA_NAME "thunderscope%u" +#define TS_DMA_NAME_LEN (24) +#define TS_DMA_NAME_ARGS(chan, dev) (dev) +#define TS_DMA_OS_FLAGS (O_CLOEXEC) +#define APPLE_MMAP_DMA +#elif defined(__linux__) #define TS_DMA_NAME "/dev/thunderscope%u" #define TS_DMA_NAME_LEN (24) #define TS_DMA_NAME_ARGS(chan, dev) (dev) @@ -57,13 +63,26 @@ int32_t samples_init(sampleStream_t* inst, uint8_t devIdx, uint8_t channel) snprintf(devName, TS_DMA_NAME_LEN, TS_DMA_NAME, TS_DMA_NAME_ARGS(channel, devIdx)); - inst->dma = litepcie_open(devName, TS_DMA_OS_FLAGS); + inst->dma.fds.fd = litepcie_open(devName, TS_DMA_OS_FLAGS); + inst->dma.channel = channel; - if((INVALID_HANDLE_VALUE != inst->dma) && - litepcie_request_dma(inst->dma, 0, 1)) + if((INVALID_HANDLE_VALUE != inst->dma.fds.fd) && + litepcie_request_dma(&inst->dma, 0, 1)) { - litepcie_dma_set_loopback(inst->dma, 0); + litepcie_dma_set_loopback(&inst->dma, 0); retVal = TS_STATUS_OK; +#if defined(APPLE_MMAP_DMA) + mach_vm_address_t writerAddress = 0; + mach_vm_size_t writerSize = 0; + + IOConnectMapMemory64(inst->dma.fds.fd, LITEPCIE_DMA_WRITER | 0, mach_task_self(), &writerAddress, &writerSize, kIOMapAnywhere); + + if (writerAddress != 0 && writerSize > 0) { + inst->dma.buf_rd = (uint8_t*)writerAddress; + } else { + printf("failed to acquire writer mapped buffer"); + } +#endif //APPLE_MMAP_DMA } //else, DMA Unavailable } @@ -81,7 +100,8 @@ int32_t samples_enable_set(sampleStream_t* inst, uint8_t en) inst->active = en; //Start/Stop DMA - litepcie_dma_writer(inst->dma, en, + litepcie_dma_writer(&inst->dma, en, + inst->interrupt_rate, &inst->dma_buffer_count, &inst->driver_buffer_count, &inst->dropped_buffer_count); @@ -105,7 +125,7 @@ int32_t samples_get_buffers(sampleStream_t* inst, uint8_t* sampleBuffer, uint32_ #if defined(_WIN32) uint32_t len = 0; - if (!ReadFile(inst->dma, sampleBuffer, bufferLen, &len, NULL)) + if (!ReadFile(inst->dma.fds.fd, sampleBuffer, bufferLen, &len, NULL)) { LOG_ERROR("Sample Read failed: %d", GetLastError()); LOG_ERROR("Sample Read args: 0x%p - 0x%lx - 0x%x", sampleBuffer, bufferLen, len); @@ -115,10 +135,50 @@ int32_t samples_get_buffers(sampleStream_t* inst, uint8_t* sampleBuffer, uint32_ { retVal = (int32_t)len; } +#elif defined(__APPLE__) + while(retVal < (int32_t)bufferLen) + { + #if defined (APPLE_MMAP_DMA) //POLLING + litepcie_dma_writer(&inst->dma, inst->active, + inst->interrupt_rate, + &inst->dma_buffer_count, + &inst->driver_buffer_count, + &inst->dropped_buffer_count); + int64_t buff_available = inst->dma_buffer_count - inst->driver_buffer_count; + if(buff_available > (DMA_BUFFER_COUNT - inst->interrupt_rate)) + { + inst->driver_buffer_count = inst->dma_buffer_count - (DMA_BUFFER_COUNT - inst->interrupt_rate); + inst->dropped_buffer_count++; + } + if(buff_available > 0) + { + memcpy(sampleBuffer, &(inst->dma.buf_rd[(inst->driver_buffer_count % DMA_BUFFER_COUNT) * TS_SAMPLE_BUFFER_SIZE]), TS_SAMPLE_BUFFER_SIZE); + inst->driver_buffer_count++; + retVal += TS_SAMPLE_BUFFER_SIZE; + } + else + { + usleep(5000); //5ms + } + #else + size_t readLen = bufferLen - retVal; + litepcie_ioctl_dma_transfer_t sampleTransfer = {.channel=0, .length=readLen, .buffer_addr=&sampleBuffer[retVal]}; + size_t outlen = sizeof(litepcie_ioctl_dma_transfer_t); + checked_ioctl(ioctl_args(inst->dma.fds.fd, LITEPCIE_IOCTL_DMA_READ, sampleTransfer)); + if(sampleTransfer.length == 0) + { + LOG_ERROR("Failed to acquire sample buffer"); + retVal = TS_STATUS_ERROR; + break; + } + //Actual Read Length gets returned in the transfer struct + retVal += sampleTransfer.length; + #endif //APPLE_MMAP_DMA + } #else while (retVal < bufferLen) { - int32_t readLen = (int32_t)read(inst->dma, &sampleBuffer[retVal], (bufferLen - (uint32_t)retVal)); + int32_t readLen = (int32_t)read(inst->dma.fds.fd, &sampleBuffer[retVal], (bufferLen - (uint32_t)retVal)); if(readLen < 0) { LOG_ERROR("Sample Read failed: %d", readLen); @@ -142,7 +202,8 @@ int32_t samples_update_status(sampleStream_t* inst) return TS_STATUS_ERROR; } - litepcie_dma_writer(inst->dma, inst->active, + litepcie_dma_writer(&inst->dma, inst->active, + inst->interrupt_rate, &inst->dma_buffer_count, &inst->driver_buffer_count, &inst->dropped_buffer_count); @@ -160,13 +221,18 @@ int32_t samples_teardown(sampleStream_t* inst) else { inst->active = 0; - litepcie_dma_writer(inst->dma, 0, + litepcie_dma_writer(&inst->dma, 0, 0, &inst->dma_buffer_count, &inst->driver_buffer_count, &inst->dropped_buffer_count); +#if defined(APPLE_MMAP_DMA) + IOConnectUnmapMemory(inst->dma.fds.fd, + LITEPCIE_DMA_WRITER | 0, mach_task_self(), + (mach_vm_address_t)inst->dma.buf_rd); +#endif - litepcie_release_dma(inst->dma, 0, 1); - litepcie_close(inst->dma); + litepcie_release_dma(&inst->dma, 0, 1); + litepcie_close(inst->dma.fds.fd); } return retVal; diff --git a/src/samples.h b/src/samples.h index 61a4658..a53271c 100644 --- a/src/samples.h +++ b/src/samples.h @@ -27,10 +27,11 @@ extern "C" { typedef struct sampleStream_s { - file_t dma; + struct litepcie_dma_ctrl dma; int64_t dma_buffer_count; int64_t driver_buffer_count; int64_t dropped_buffer_count; + uint32_t interrupt_rate; uint8_t active; } sampleStream_t; diff --git a/src/spiflash.c b/src/spiflash.c index 05e756d..32ca298 100644 --- a/src/spiflash.c +++ b/src/spiflash.c @@ -17,12 +17,9 @@ #include #include -#define SPI_FLASH_PROG_SIZE 256 -#define SPI_FLASH_ERASE_SIZE (64*1024) - #define SPI_FLASH_WINDOW_SIZE 0x10000 -#define SPI_FLASH_CLK_DIV_DEFAULT (1) +#define SPI_FLASH_CLK_DIV_DEFAULT (3) #define SPI_FLASH_CLK_DIV_MAX (10) #define SPI_FLASH_JEDEC_READ_ID_CMD (0x9F) @@ -30,10 +27,60 @@ #define SPI_FLASH_WRITE_ENABLE_CMD (0x06) #define SPI_FLASH_WRITE_DISABLE_CMD (0x04) +/** + * LiteSPI Register Maps + */ +/** pre-2025 liteSPI **/ +#ifndef CSR_SPIFLASH_CORE_BASE +#define CSR_SPIFLASH_CORE_BASE CSR_SPIFLASH_BASE +#endif + +/** post 2025 liteSPI **/ +#ifndef CSR_SPIFLASH_BASE +#define CSR_SPIFLASH_BASE CSR_SPIFLASH_CORE_BASE +#endif +#define SPIFLASH_PHYCONFIG_LEN_OFFSET 0 +#define SPIFLASH_PHYCONFIG_LEN_SIZE 8 +#define SPIFLASH_PHYCONFIG_WIDTH_OFFSET 8 +#define SPIFLASH_PHYCONFIG_WIDTH_SIZE 4 +#define SPIFLASH_PHYCONFIG_MASK_OFFSET 16 +#define SPIFLASH_PHYCONFIG_MASK_SIZE 8 + +#define SPIFLASH_STATUS_TX_READY_OFFSET 0 +#define SPIFLASH_STATUS_RX_READY_OFFSET 1 + #ifndef min #define min(x, y) (((x) < (y)) ? (x) : (y)) #endif +typedef struct spiflash_regs_s { + uint32_t PHY_CLK_DIV; + uint32_t MASTER_CS; + uint32_t MASTER_RXTX; + uint32_t MASTER_STATUS; + uint32_t MASTER_PHYCONFIG; + uint32_t DUMMY_BITS; +} spiflash_regs_t; + +static const spiflash_regs_t spiflash_regs[] = { + { + .PHY_CLK_DIV = (CSR_SPIFLASH_CORE_BASE + 0x800L), + .MASTER_CS = (CSR_SPIFLASH_CORE_BASE + 0x08L), + .MASTER_RXTX = (CSR_SPIFLASH_CORE_BASE + 0x10L), + .MASTER_STATUS = (CSR_SPIFLASH_CORE_BASE + 0x14L), + .MASTER_PHYCONFIG = (CSR_SPIFLASH_CORE_BASE + 0x0cL), + .DUMMY_BITS = (CSR_SPIFLASH_CORE_BASE + 0x00L) + }, + { + .PHY_CLK_DIV = (CSR_SPIFLASH_BASE + 0x00L), + .MASTER_CS = (CSR_SPIFLASH_BASE + 0x0cL), + .MASTER_RXTX = (CSR_SPIFLASH_BASE + 0x14L), + .MASTER_STATUS = (CSR_SPIFLASH_BASE + 0x18L), + .MASTER_PHYCONFIG = (CSR_SPIFLASH_BASE + 0x10L), + .DUMMY_BITS = (CSR_SPIFLASH_BASE + 0x04L) + } +}; + static const spiflash_ops_t s25fl256s_ops = { .read = 0x6C, //4QOR .program = 0x12, //4PP @@ -48,21 +95,21 @@ static const spiflash_ops_t mx25u6432f_ops = { .cmd_addr_len = 3 }; -static uint32_t get_flash_data(file_t fd, uint32_t flash_addr, uint8_t* pData, uint32_t data_len) +static uint32_t get_flash_data(spiflash_dev_t* dev, uint32_t flash_addr, uint8_t* pData, uint32_t data_len) { const uint32_t flash_base = 0x10000; uint32_t index = 0; uint32_t flash_data = 0; - uint32_t flash_window = litepcie_readl(fd, CSR_FLASH_ADAPTER_WINDOW0_ADDR); + uint32_t flash_window = litepcie_readl(dev->fd, CSR_FLASH_ADAPTER_WINDOW0_ADDR); while(index < data_len) { if(flash_window != ((flash_addr+index) >> 16)) { flash_window = (flash_addr+index) >> 16; - litepcie_writel(fd, CSR_FLASH_ADAPTER_WINDOW0_ADDR, flash_window); + litepcie_writel(dev->fd, CSR_FLASH_ADAPTER_WINDOW0_ADDR, flash_window); } - flash_data = litepcie_readl(fd, (flash_base + ((flash_addr+index) & 0xFFFF))); + flash_data = litepcie_readl(dev->fd, (flash_base + ((flash_addr+index) & 0xFFFF))); *(uint32_t*)&pData[index] = flash_data; index += 4; } @@ -70,67 +117,78 @@ static uint32_t get_flash_data(file_t fd, uint32_t flash_addr, uint8_t* pData, u return index; } -#ifdef CSR_SPIFLASH_CORE_MMAP_DUMMY_BITS_ADDR -static void spiflash_dummy_bits_setup(file_t fd, unsigned int dummy_bits) + +static void spiflash_dummy_bits_setup(spiflash_dev_t* dev, unsigned int dummy_bits) { - litepcie_writel(fd, CSR_SPIFLASH_CORE_MMAP_DUMMY_BITS_ADDR, dummy_bits); - LOG_DEBUG("Dummy bits set to: %" PRIx32 , litepcie_readl(fd, CSR_SPIFLASH_CORE_MMAP_DUMMY_BITS_ADDR)); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].DUMMY_BITS, dummy_bits); + LOG_DEBUG("Dummy bits set to: %" PRIx32 , litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].DUMMY_BITS)); } -#endif -static void spiflash_len_mask_width_write(file_t fd, uint32_t len, uint32_t width, uint32_t mask) +static void spiflash_len_mask_width_write(spiflash_dev_t* dev, uint32_t len, uint32_t width, uint32_t mask) { - uint32_t tmp = len & ((1 << CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_LEN_SIZE) - 1); - uint32_t word = tmp << CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_LEN_OFFSET; - tmp = width & ((1 << CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_WIDTH_SIZE) - 1); - word |= tmp << CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_WIDTH_OFFSET; - tmp = mask & ((1 << CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_MASK_SIZE) - 1); - word |= tmp << CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_MASK_OFFSET; - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_PHYCONFIG_ADDR, word); + uint32_t tmp = len & ((1 << SPIFLASH_PHYCONFIG_LEN_SIZE) - 1); + uint32_t word = tmp << SPIFLASH_PHYCONFIG_LEN_OFFSET; + tmp = width & ((1 << SPIFLASH_PHYCONFIG_WIDTH_SIZE) - 1); + word |= tmp << SPIFLASH_PHYCONFIG_WIDTH_OFFSET; + tmp = mask & ((1 << SPIFLASH_PHYCONFIG_MASK_SIZE) - 1); + word |= tmp << SPIFLASH_PHYCONFIG_MASK_OFFSET; + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_PHYCONFIG, word); } -static bool spiflash_tx_ready(file_t fd) +static bool spiflash_tx_ready(spiflash_dev_t* dev) { - return (litepcie_readl(fd, CSR_SPIFLASH_CORE_MASTER_STATUS_ADDR) >> CSR_SPIFLASH_CORE_MASTER_STATUS_TX_READY_OFFSET) & 1; + return (litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].MASTER_STATUS) >> SPIFLASH_STATUS_TX_READY_OFFSET) & 1; } -static bool spiflash_rx_ready(file_t fd) +static bool spiflash_rx_ready(spiflash_dev_t* dev) { - return (litepcie_readl(fd, CSR_SPIFLASH_CORE_MASTER_STATUS_ADDR) >> CSR_SPIFLASH_CORE_MASTER_STATUS_RX_READY_OFFSET) & 1; + return (litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].MASTER_STATUS) >> SPIFLASH_STATUS_RX_READY_OFFSET) & 1; } -static void spiflash_master_write(file_t fd, uint32_t val, size_t len, size_t width, uint32_t mask) +static void spiflash_master_write(spiflash_dev_t* dev, uint32_t val, size_t len, size_t width, uint32_t mask) { /* Be sure to empty RX queue before doing Xfer. */ - while (spiflash_rx_ready(fd)) - litepcie_readl(fd, CSR_SPIFLASH_CORE_MASTER_RXTX_ADDR); + while (spiflash_rx_ready(dev)) + litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].MASTER_RXTX); /* Configure Master */ - spiflash_len_mask_width_write(fd, 8*len, width, mask); + spiflash_len_mask_width_write(dev, 8*len, width, mask); /* Set CS. */ - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_CS_ADDR, 1UL); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_CS, 1UL); /* Do Xfer. */ - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_RXTX_ADDR, val); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_RXTX, val); - while (!spiflash_rx_ready(fd)); + while (!spiflash_rx_ready(dev)); /* Clear CS. */ - // spiflash_core_master_cs_write(0); - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_CS_ADDR, 0UL); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_CS, 0UL); } static uint8_t w_buf[SPI_FLASH_PROG_SIZE + 5]; static uint8_t r_buf[SPI_FLASH_PROG_SIZE + 5]; -static void transfer_cmd(file_t fd, const uint8_t *bs, uint8_t *resp, int len) +static void transfer_cmd(spiflash_dev_t* dev, const uint8_t *bs, uint8_t *resp, int len) { uint32_t xfer_word = 0; uint32_t xfer_num_bytes = 0; - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_CS_ADDR, 1UL); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_CS, 1UL); + + /* Flush RX */ + while (spiflash_rx_ready(dev)) + { + litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].MASTER_RXTX); + } + + /* wait for tx ready */ + while (!spiflash_tx_ready(dev)) + { + litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].MASTER_RXTX); + } + for (int i=0; i < len; i+=4) { @@ -142,21 +200,18 @@ static void transfer_cmd(file_t fd, const uint8_t *bs, uint8_t *resp, int len) xfer_word = (xfer_word << 8) | (uint32_t)bs[i+2]; if(xfer_num_bytes > 3) xfer_word = (xfer_word << 8) | (uint32_t)bs[i+3]; + + spiflash_len_mask_width_write(dev, (8*xfer_num_bytes), 1, 1); - - spiflash_len_mask_width_write(fd, (8*xfer_num_bytes), 1, 1); - - /* wait for tx ready */ - while (!spiflash_tx_ready(fd)); - - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_RXTX_ADDR, (uint32_t)xfer_word); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_RXTX, (uint32_t)xfer_word); /* wait for rx ready */ - while (!spiflash_rx_ready(fd)) + while (!spiflash_rx_ready(dev)) { NS_DELAY(250); } - xfer_word = litepcie_readl(fd, CSR_SPIFLASH_CORE_MASTER_RXTX_ADDR); + + xfer_word = litepcie_readl(dev->fd, spiflash_regs[dev->ip_rev].MASTER_RXTX); resp[i] = (uint8_t)((xfer_word >> (8*(xfer_num_bytes-1))) & 0xFF); if(xfer_num_bytes > 1) resp[i+1] = (uint8_t)((xfer_word >> (8*(xfer_num_bytes-2))) & 0xFF); @@ -166,17 +221,17 @@ static void transfer_cmd(file_t fd, const uint8_t *bs, uint8_t *resp, int len) resp[i+3] = (uint8_t)((xfer_word) & 0xFF); } - litepcie_writel(fd, CSR_SPIFLASH_CORE_MASTER_CS_ADDR, 0UL); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].MASTER_CS, 0UL); } -static uint32_t spiflash_read_id_register(file_t fd) +static uint32_t spiflash_read_id_register(spiflash_dev_t* dev) { uint8_t buf[4]; w_buf[0] = SPI_FLASH_JEDEC_READ_ID_CMD; w_buf[1] = 0x00; w_buf[2] = 0x00; w_buf[3] = 0x00; - transfer_cmd(fd, w_buf, buf, 4); + transfer_cmd(dev, w_buf, buf, 4); LOG_DEBUG("[ID: %02x %02x %02x %02x]", buf[0], buf[1], buf[2], buf[3]); @@ -184,30 +239,30 @@ static uint32_t spiflash_read_id_register(file_t fd) return flash_id; } -static uint32_t spiflash_read_status_register(file_t fd) +static uint32_t spiflash_read_status_register(spiflash_dev_t* dev) { uint8_t buf[2]; w_buf[0] = SPI_FLASH_JEDEC_READ_STATUS_REG_1_CMD; w_buf[1] = 0x00; - transfer_cmd(fd, w_buf, buf, 2); + transfer_cmd(dev, w_buf, buf, 2); LOG_DEBUG("[SR: %02x %02x]", buf[0], buf[1]); return buf[1]; } -static void spiflash_write_enable(file_t fd) +static void spiflash_write_enable(spiflash_dev_t* dev) { uint8_t buf[1]; w_buf[0] = SPI_FLASH_WRITE_ENABLE_CMD; - transfer_cmd(fd, w_buf, buf, 1); + transfer_cmd(dev, w_buf, buf, 1); } -static void spiflash_write_disable(file_t fd) +static void spiflash_write_disable(spiflash_dev_t* dev) { uint8_t buf[1]; w_buf[0] = SPI_FLASH_WRITE_DISABLE_CMD; - transfer_cmd(fd, w_buf, buf, 1); + transfer_cmd(dev, w_buf, buf, 1); } static void page_program(spiflash_dev_t* dev, uint32_t addr, uint8_t *data, int len) @@ -222,7 +277,7 @@ static void page_program(spiflash_dev_t* dev, uint32_t addr, uint8_t *data, int w_buf[cmd_idx++] = addr>>8; w_buf[cmd_idx++] = addr>>0; memcpy((void *)(&w_buf[cmd_idx]), (void *)data, len); - transfer_cmd(dev->fd, w_buf, r_buf, len+cmd_idx); + transfer_cmd(dev, w_buf, r_buf, len+cmd_idx); } static void spiflash_sector_erase(spiflash_dev_t* dev, uint32_t addr) @@ -236,7 +291,7 @@ static void spiflash_sector_erase(spiflash_dev_t* dev, uint32_t addr) w_buf[cmd_idx++] = addr>>16; w_buf[cmd_idx++] = addr>>8; w_buf[cmd_idx++] = addr>>0; - transfer_cmd(dev->fd, w_buf, r_buf, cmd_idx); + transfer_cmd(dev, w_buf, r_buf, cmd_idx); } int32_t spiflash_erase(spiflash_dev_t* dev, uint32_t addr, uint32_t len) @@ -244,7 +299,7 @@ int32_t spiflash_erase(spiflash_dev_t* dev, uint32_t addr, uint32_t len) uint32_t i = 0; uint32_t j = 0; //Check Address is aligned to the erase sector - if(addr & 0xffff) + if((addr % SPI_FLASH_ERASE_SIZE) != 0) { LOG_ERROR("Error: Flash Erase address must be 64K-aligned (0x%08X)", addr); return TS_STATUS_ERROR; @@ -252,24 +307,30 @@ int32_t spiflash_erase(spiflash_dev_t* dev, uint32_t addr, uint32_t len) for (i=0; ifd); + spiflash_write_enable(dev); spiflash_sector_erase(dev, addr+i); - while (spiflash_read_status_register(dev->fd) & 1) { - NS_DELAY(250000000); + while (spiflash_read_status_register(dev) & 1) { + NS_DELAY(10000000); } /* check if region was really erased */ for (j = 0; j < SPI_FLASH_ERASE_SIZE; j+=4) { uint32_t flash_word; - get_flash_data(dev->fd, (addr+i+j), (uint8_t*)&flash_word, sizeof(uint32_t)); + get_flash_data(dev, (addr+i+j), (uint8_t*)&flash_word, sizeof(uint32_t)); if (flash_word != 0xffffffff) { - LOG_ERROR("Error: location 0x%08lx not erased (0x%08x)", addr+i+j, flash_word); + LOG_ERROR("Error: location 0x%08x not erased (0x%08x)", addr+i+j, flash_word); return TS_STATUS_ERROR; } } + + //Report progress + if(dev->op_progress != NULL) + { + dev->op_progress(dev->op_progress_ctx, SPI_FLASH_ERASE_SIZE, len); + } } - spiflash_write_disable(dev->fd); + spiflash_write_disable(dev); return TS_STATUS_OK; } @@ -284,27 +345,33 @@ int32_t spiflash_write(spiflash_dev_t* dev, uint32_t addr, const uint8_t *pData, LOG_DEBUG("Write SPI Flash @0x%08lx", ((uint32_t)addr)); while(w_len) { - spiflash_write_enable(dev->fd); + spiflash_write_enable(dev); page_program(dev, addr+offset, (uint8_t*)pData+offset, w_len); - while(spiflash_read_status_register(dev->fd) & 1) { + while(spiflash_read_status_register(dev) & 1) { NS_DELAY(10000); } - get_flash_data(dev->fd, addr+offset, (uint8_t*)r_buf, w_len); + spiflash_read(dev, addr+offset, (uint8_t*)r_buf, w_len); for (j = 0; j < w_len; j++) { if (r_buf[j] != pData[offset+j]) { - LOG_ERROR("Error: verify failed at 0x%08lx (0x%02x should be 0x%02x)", (uint32_t)(addr+offset+j), r_buf[j], pData[offset+j]); - spiflash_write_disable(dev->fd); + LOG_ERROR("Error: verify failed at 0x%08x (0x%02x should be 0x%02x)", (uint32_t)(addr+offset+j), r_buf[j], pData[offset+j]); + spiflash_write_disable(dev); return TS_STATUS_ERROR; } } + //Report progress + if(dev->op_progress != NULL) + { + dev->op_progress(dev->op_progress_ctx, w_len, len); + } + offset += w_len; w_len = min(len-offset, SPI_FLASH_PROG_SIZE); res = offset; } - spiflash_write_disable(dev->fd); + spiflash_write_disable(dev); return res; } @@ -313,20 +380,31 @@ int32_t spiflash_init(file_t fd, spiflash_dev_t* dev) { uint32_t flash_id = 0; uint32_t divisor = SPI_FLASH_CLK_DIV_DEFAULT; + uint32_t ip_version = 0; dev->fd = fd; -#ifdef CSR_SPIFLASH_CORE_MMAP_DUMMY_BITS_ADDR - spiflash_dummy_bits_setup(fd, 8); -#endif + // Get IP Version + ip_version = litepcie_readl(fd, CSR_DEV_STATUS_LITEX_REL_ADDR); + if(ip_version >= LITEX_VERSION(2025, 4)) // 2025.4+ + { + dev->ip_rev = 1; + } + else + { + dev->ip_rev = 0; + } + + spiflash_dummy_bits_setup(dev, 8); while(divisor <= SPI_FLASH_CLK_DIV_MAX) { - litepcie_writel(fd, CSR_SPIFLASH_PHY_CLK_DIVISOR_ADDR, divisor); - flash_id = spiflash_read_id_register(dev->fd); //First ID read returns garbage? - flash_id = spiflash_read_id_register(dev->fd); + litepcie_writel(dev->fd, spiflash_regs[dev->ip_rev].PHY_CLK_DIV, divisor); + flash_id = spiflash_read_id_register(dev); //First ID read returns garbage? + flash_id = spiflash_read_id_register(dev); if((flash_id == 0x010219) || + (flash_id == 0xC22017) || (flash_id == 0xC22537) || (flash_id == 0xC22B27)) { @@ -350,6 +428,7 @@ int32_t spiflash_init(file_t fd, spiflash_dev_t* dev) dev->ops = s25fl256s_ops; break; } + case 0xC22017: case 0xC22537: case 0xC22B27: // Test SPI device MX25S6433F { @@ -381,13 +460,51 @@ int32_t spiflash_init(file_t fd, spiflash_dev_t* dev) int32_t spiflash_read(spiflash_dev_t* dev, uint32_t addr, uint8_t* pData, uint32_t len) { + uint32_t read_len = 0; + uint32_t remaining = len; + uint32_t tempAddr = (addr/4)*4; //Round address down + uint8_t flashBytes[4]; //Validate Address and Lengths - if((len % 4) || (addr % 4)) + if(len == 0) { - //Only supports word-reads currently + LOG_ERROR("Failed to read with length 0"); return TS_STATUS_ERROR; } - int32_t read_len = get_flash_data(dev->fd, addr, pData, len); + + if(addr % 4 != 0) + { + read_len = get_flash_data(dev, tempAddr, flashBytes, 4); + if( 4 != read_len ) + { + LOG_ERROR("Failed to read with length 0"); + return TS_STATUS_ERROR; + } + read_len = (4 - (addr%4)); + memcpy(pData, &flashBytes[addr%4], read_len); + addr += read_len; + pData += read_len; + remaining -= read_len; + } + + read_len = get_flash_data(dev, addr, pData, (remaining/4)*4); + addr += read_len; + pData += read_len; + remaining -= read_len; + + if(remaining >= 4) + { + LOG_ERROR("SPIFLASH READ FAILED (%d - %d)", read_len, remaining); + } + else if(0 != remaining) + { + //Should only be 1-3 bytes left + if( 4 != get_flash_data(dev, addr, flashBytes, 4)) + { + LOG_ERROR("Failed to read end of data (%d / %d)", read_len, len); + return TS_STATUS_ERROR; + } + memcpy(pData, flashBytes, remaining); + } - return len; + return (int32_t)len; } diff --git a/src/spiflash.h b/src/spiflash.h index bcd11c3..f279148 100644 --- a/src/spiflash.h +++ b/src/spiflash.h @@ -18,6 +18,12 @@ extern "C" { #include "ts_common.h" #include "liblitepcie.h" + +#define SPI_FLASH_PROG_SIZE 256 +#define SPI_FLASH_ERASE_SIZE (64*1024) + +typedef void (*spiflash_progress_cb_t)(void* ctx, uint32_t work_done, uint32_t work_total); + typedef struct spiflash_ops_s { uint8_t read; uint8_t program; @@ -27,9 +33,12 @@ typedef struct spiflash_ops_s { typedef struct spiflash_dev_s { file_t fd; + uint32_t ip_rev; uint8_t mfg_code; uint16_t part_id; spiflash_ops_t ops; + spiflash_progress_cb_t op_progress; + void* op_progress_ctx; } spiflash_dev_t; /** diff --git a/src/thunderscope.c b/src/thunderscope.c index c7f109d..4a456ed 100644 --- a/src/thunderscope.c +++ b/src/thunderscope.c @@ -16,25 +16,35 @@ #include "ts_calibration.h" #include "ts_channel.h" #include "samples.h" +#include "events.h" +#include "gpio.h" #include "ts_fw_manager.h" +#include "ts_data.h" #include "util.h" #include "litepcie.h" -#ifdef _WIN32 +#if defined(_WIN32) #define FILE_FLAGS (FILE_ATTRIBUTE_NORMAL) #else #define FILE_FLAGS (O_RDWR) #endif +#define DEFAULT_DATA_INTR_RATE (100) + typedef struct ts_inst_s { file_t ctrl; tsDeviceInfo_t identity; tsChannelHdl_t pChannel; sampleStream_t samples; + gpio_t status_leds; + const led_signals_t *signals; + bool initialized; ts_fw_manager_t fw; + uint32_t interrupt_rate; + uint8_t bytes_per_sample; //TBD - Other Instance Data } ts_inst_t; @@ -43,6 +53,7 @@ int32_t thunderscopeListDevices(uint32_t devIndex, tsDeviceInfo_t *info) { int32_t retVal = TS_STATUS_ERROR; char testPath[TS_IDENT_STR_LEN]; + ts_fw_manager_t fwmngr; // Find device path by index snprintf(testPath, TS_IDENT_STR_LEN, LITEPCIE_CTRL_NAME(%d), devIndex); @@ -52,14 +63,19 @@ int32_t thunderscopeListDevices(uint32_t devIndex, tsDeviceInfo_t *info) if(testDev != INVALID_HANDLE_VALUE) { info->device_id = devIndex; + info->hw_id = litepcie_readl(testDev, CSR_DEV_STATUS_HW_ID_ADDR); + info->gw_id = litepcie_readl(testDev, CSR_DEV_STATUS_GW_REV_ADDR); + info->litex = litepcie_readl(testDev, CSR_DEV_STATUS_LITEX_REL_ADDR); //Copy device identifier for (uint32_t i = 0; i < TS_IDENT_STR_LEN; i++) { info->identity[i] = (char)litepcie_readl(testDev, CSR_IDENTIFIER_MEM_BASE + 4 * i); } - //TODO Implement Serial Number - snprintf(info->serial_number, TS_IDENT_STR_LEN, "TS00##"); strncpy(info->device_path, testPath, TS_IDENT_STR_LEN); + // Get Factory Build Info + ts_fw_manager_init(testDev, &fwmngr); + ts_data_factory_id_get(&fwmngr, info); + litepcie_close(testDev); retVal = TS_STATUS_OK; } @@ -67,46 +83,67 @@ int32_t thunderscopeListDevices(uint32_t devIndex, tsDeviceInfo_t *info) return retVal; } -tsHandle_t thunderscopeOpen(uint32_t devIdx) +tsHandle_t thunderscopeOpen(uint32_t devIdx, bool skip_init) { ts_inst_t* pInst = calloc(sizeof(ts_inst_t), 1); char devName[TS_IDENT_STR_LEN] = {0}; if(pInst) { + pInst->initialized = false; snprintf(devName, TS_IDENT_STR_LEN, LITEPCIE_CTRL_NAME(%d), devIdx); pInst->ctrl = litepcie_open(devName, FILE_FLAGS); } else { - LOG_ERROR("Litepcie Failed to open device %s", devName); + LOG_ERROR("Litepcie Failed to allocate device"); return NULL; } if(pInst->ctrl == INVALID_HANDLE_VALUE) { - LOG_ERROR("litepcie_open returned Invalid File Handle for device %s", devName); + LOG_ERROR("litepcie_open returned Invalid File Handle for device %d (%s)", devIdx, devName); free(pInst); return NULL; } - - if(TS_STATUS_OK != ts_channel_init(&pInst->pChannel, pInst->ctrl)) + + //Get Device Info + pInst->identity.device_id = devIdx; + pInst->identity.hw_id = litepcie_readl(pInst->ctrl, CSR_DEV_STATUS_HW_ID_ADDR); + pInst->identity.gw_id = litepcie_readl(pInst->ctrl, CSR_DEV_STATUS_GW_REV_ADDR); + pInst->identity.litex = litepcie_readl(pInst->ctrl, CSR_DEV_STATUS_LITEX_REL_ADDR); + //Copy device identifier + for (uint32_t i = 0; i < TS_IDENT_STR_LEN; i++) { - LOG_ERROR("Failed to initialize channels"); - litepcie_close(pInst->ctrl); - free(pInst); - return NULL; + pInst->identity.identity[i] = (char)litepcie_readl(pInst->ctrl, CSR_IDENTIFIER_MEM_BASE + 4 * i); } + + + strncpy(pInst->identity.device_path, devName, TS_IDENT_STR_LEN); - if(TS_STATUS_OK != samples_init(&pInst->samples, devIdx, 0)) + if(!skip_init) { - LOG_ERROR("Failed to initialize samples"); - ts_channel_destroy(pInst->pChannel); - litepcie_close(pInst->ctrl); - free(pInst); - return NULL; + pInst->interrupt_rate = DEFAULT_DATA_INTR_RATE; + if(TS_STATUS_OK != ts_channel_init(&pInst->pChannel, pInst->ctrl)) + { + LOG_ERROR("Failed to initialize channels"); + litepcie_close(pInst->ctrl); + free(pInst); + return NULL; + } + + if(TS_STATUS_OK != samples_init(&pInst->samples, devIdx, 0)) + { + LOG_ERROR("Failed to initialize samples"); + ts_channel_destroy(pInst->pChannel); + litepcie_close(pInst->ctrl); + free(pInst); + return NULL; + } + pInst->bytes_per_sample = 1; + pInst->initialized = true; } - + if(TS_STATUS_OK != ts_fw_manager_init(pInst->ctrl, &pInst->fw)) { LOG_ERROR("Failed to initialize spiflash"); @@ -117,6 +154,24 @@ tsHandle_t thunderscopeOpen(uint32_t devIdx) return NULL; } + events_initialize(pInst->ctrl); + + // Get Factory Build Info + ts_data_factory_id_get(&pInst->fw, &pInst->identity); + + pInst->status_leds.fd = pInst->ctrl; + pInst->status_leds.reg = TS_STATUS_LED_ADDR; + pInst->status_leds.bit_mask = TS_STATUS_LED_MASK; + if(pInst->identity.hw_id & TS_HW_ID_VALID_MASK) + { + pInst->signals = &ts_dev_leds; + } + else + { + pInst->signals = &ts_beta_leds; + } + gpio_group_set(pInst->status_leds, pInst->signals->ready); + LOG_DEBUG("Opened TS idx %d with handle %p", devIdx, pInst); return (tsHandle_t)pInst; } @@ -130,8 +185,14 @@ int32_t thunderscopeClose(tsHandle_t ts) ts_inst_t* pInst = (ts_inst_t*)ts; - samples_teardown(&pInst->samples); - ts_channel_destroy(pInst->pChannel); + if(pInst->initialized) + { + samples_teardown(&pInst->samples); + ts_channel_destroy(pInst->pChannel); + } + + thunderscopeExtSyncConfig(ts, TS_SYNC_DISABLED); + gpio_group_set(pInst->status_leds, pInst->signals->disabled); litepcie_close(pInst->ctrl); free(pInst); @@ -142,7 +203,7 @@ int32_t thunderscopeChannelConfigGet(tsHandle_t ts, uint32_t channel, tsChannelP { ts_inst_t* pInst = (ts_inst_t*)ts; - if(pInst) + if(pInst && pInst->initialized) { return ts_channel_params_get(pInst->pChannel, channel, conf); } @@ -154,7 +215,7 @@ int32_t thunderscopeChannelConfigSet(tsHandle_t ts, uint32_t channel, tsChannelP { ts_inst_t* pInst = (ts_inst_t*)ts; - if(pInst) + if(pInst && pInst->initialized) { return ts_channel_params_set(pInst->pChannel, channel, conf); } @@ -162,10 +223,22 @@ int32_t thunderscopeChannelConfigSet(tsHandle_t ts, uint32_t channel, tsChannelP return TS_STATUS_ERROR; } +int32_t thunderscopeRefClockSet(tsHandle_t ts, tsRefClockMode_t mode, uint32_t refclk_freq) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + + if(pInst && pInst->initialized) + { + return ts_channel_ext_clock_config(pInst->pChannel, mode, refclk_freq); + } + + return TS_STATUS_ERROR; +} + int32_t thunderscopeStatusGet(tsHandle_t ts, tsScopeState_t* state) { ts_inst_t* pInst = (ts_inst_t*)ts; - if(!state || !pInst) + if(!state || !pInst || !pInst->initialized) { return TS_STATUS_ERROR; } @@ -176,76 +249,387 @@ int32_t thunderscopeStatusGet(tsHandle_t ts, tsScopeState_t* state) return TS_STATUS_OK; } -int32_t thunderscopeSampleModeSet(tsHandle_t ts, uint32_t rate, uint32_t resolution) +int32_t thunderscopeSampleModeSet(tsHandle_t ts, uint32_t rate, tsSampleFormat_t mode) { ts_inst_t* pInst = (ts_inst_t*)ts; - if(pInst) + if(pInst && pInst->initialized) + { + int32_t status = ts_channel_sample_rate_set(pInst->pChannel, rate, mode); + if(status == TS_STATUS_OK) + { + //Target 100Hz interrupt rate + pInst->samples.interrupt_rate = 1 + ((((mode == TS_8_BIT) ? rate : 2*rate) / + (DMA_BUFFER_SIZE)) / pInst->interrupt_rate); + + LOG_DEBUG("DMA Interrupt Rate is 1/%d MB", pInst->samples.interrupt_rate); + } + return status; + } + + return TS_STATUS_ERROR; +} + +int32_t thunderscopeSampleInterruptRate(tsHandle_t ts, uint32_t interrupt_rate) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + + if(pInst && pInst->initialized) { - return ts_channel_sample_rate_set(pInst->pChannel, rate, resolution); + if(interrupt_rate == 0) + { + interrupt_rate = DEFAULT_DATA_INTR_RATE; + } + pInst->interrupt_rate = interrupt_rate; + + LOG_DEBUG("Targeting Interrupt frequency of %d Hz", pInst->interrupt_rate); + + return TS_STATUS_OK; } return TS_STATUS_ERROR; } -int32_t thunderscopeCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal) +int32_t thunderscopeChanCalibrationSet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal) { ts_inst_t* pInst = (ts_inst_t*)ts; - if(!pInst) + if(pInst && pInst->initialized) { - return TS_STATUS_ERROR; + return ts_channel_calibration_set(pInst->pChannel, channel, cal); } + + return TS_STATUS_ERROR; +} + +int32_t thunderscopeChanCalibrationGet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; - return ts_channel_calibration_set(pInst->pChannel, channel, cal); + if(pInst && pInst->initialized) + { + return ts_channel_calibration_get(pInst->pChannel, channel, cal); + } + + return TS_STATUS_ERROR; } -int32_t thunderscopeCalibrationGet(tsHandle_t ts, uint32_t channel, tsChannelCalibration_t *cal) +int32_t thunderscopeAdcCalibrationSet(tsHandle_t ts, tsAdcCalibration_t *cal) { ts_inst_t* pInst = (ts_inst_t*)ts; - if(!pInst) + if(pInst && pInst->initialized) { - return TS_STATUS_ERROR; + return ts_channel_adc_calibration_set(pInst->pChannel, cal); } + + return TS_STATUS_ERROR; +} + +int32_t thunderscopeAdcCalibrationGet(tsHandle_t ts, tsAdcCalibration_t *cal) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; - return ts_channel_calibration_get(pInst->pChannel, channel, cal); + if(pInst && pInst->initialized) + { + return ts_channel_adc_calibration_get(pInst->pChannel, cal); + } + + return TS_STATUS_ERROR; } -int32_t thunderscopeCalibrationManualCtrl(tsHandle_t ts, uint32_t channel, tsChannelCtrl_t ctrl) +int32_t thunderscopeCalibrationManualCtrl(tsHandle_t ts, uint32_t channel, tsChannelCtrl_t *ctrl) { ts_inst_t* pInst = (ts_inst_t*)ts; - if(!pInst) + if(pInst && pInst->initialized) { - return TS_STATUS_ERROR; + return ts_channel_calibration_manual(pInst->pChannel, channel, *ctrl); } + + return TS_STATUS_ERROR; +} - return ts_channel_calibration_manual(pInst->pChannel, channel, ctrl); +int32_t thunderscopeCalibrationAdcTest(tsHandle_t ts, tsCalAdcTest_t test_mode, uint32_t test_pattern) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + + if(pInst && pInst->initialized) + { + return ts_channel_set_adc_test(pInst->pChannel, (hmcad15xxTestMode_t)test_mode, + (uint16_t)(test_pattern & 0xFFFF), (uint16_t)(test_pattern >> 16)); + } + + return TS_STATUS_ERROR; } int32_t thunderscopeDataEnable(tsHandle_t ts, uint8_t enable) { int32_t status = TS_STATUS_OK; ts_inst_t* pInst = (ts_inst_t*)ts; + + if(!pInst || !pInst->initialized) + { + return TS_STATUS_ERROR; + } + status = ts_channel_run(pInst->pChannel, enable); if(status != TS_STATUS_OK) { + gpio_group_set(pInst->status_leds, pInst->signals->error); return status; } + if(enable) + { + gpio_group_set(pInst->status_leds, pInst->signals->active); + //Get current sample increment + pInst->bytes_per_sample = ts_channel_scope_status(pInst->pChannel).adc_sample_bits / 8; + uint8_t active_channels = 0; + for(uint8_t ch=0; ch < TS_NUM_CHANNELS; ch++) + { + tsChannelParam_t params; + ts_channel_params_get(pInst->pChannel, ch, ¶ms); + if(params.active) + { + active_channels++; + } + } + if(active_channels == 2) + { + pInst->bytes_per_sample *= 2; + } + else if(active_channels > 2) + { + pInst->bytes_per_sample *= 4; + } + } + else + { + gpio_group_set(pInst->status_leds, pInst->signals->ready); + } return samples_enable_set(&pInst->samples, enable); } int32_t thunderscopeRead(tsHandle_t ts, uint8_t* buffer, uint32_t len) { ts_inst_t* pInst = (ts_inst_t*)ts; - return samples_get_buffers(&pInst->samples, buffer, len); + if(pInst && pInst->initialized) + { + return samples_get_buffers(&pInst->samples, buffer, len); + } + else + { + return TS_STATUS_ERROR; + } } +int32_t thunderscopeReadCount(tsHandle_t ts, uint8_t* buffer, uint32_t len, uint64_t* count) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + int32_t sample_result = TS_STATUS_ERROR; + if(pInst && pInst->initialized) + { + sample_result = samples_get_buffers(&pInst->samples, buffer, len); + if(sample_result > 0) + { + samples_update_status(&pInst->samples); + *count = ((pInst->samples.driver_buffer_count * DMA_BUFFER_SIZE) - sample_result) / pInst->bytes_per_sample; + } + else + { + *count = 0; + } + } -int32_t thunderscopeFwUpdate(tsHandle_t ts, char* bitstream, uint32_t len) + return sample_result; +} + +int32_t thunderscopeExtSyncConfig(tsHandle_t ts, tsSyncMode_t mode) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + int32_t status = TS_STATUS_ERROR; + if(pInst) + { + status = events_set_ext_sync(pInst->ctrl, mode); + } + + return status; +} + +int32_t thunderscopeEventSyncAssert(tsHandle_t ts) { ts_inst_t* pInst = (ts_inst_t*)ts; - return ts_fw_manager_user_fw_update(&pInst->fw, bitstream, len); -} \ No newline at end of file + int32_t status = TS_STATUS_ERROR; + if(pInst) + { + status = events_set_immediate(pInst->ctrl); + } + + return status; +} + +int32_t thunderscopeEventGet(tsHandle_t ts, tsEvent_t* evt) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + int32_t status = TS_STATUS_ERROR; + if(pInst && evt) + { + if( !events_available(pInst->ctrl) ) + { + evt->ID = TS_EVT_NONE; + evt->event_sample = 0; + status = TS_STATUS_OK; + } + else + { + status = events_get_next(pInst->ctrl, evt); + + if(status == TS_STATUS_OK) + { + //Adjust Sample Count by Samples/increment + evt->event_sample = evt->event_sample * TS_BYTES_PER_SAMPLE_COUNT / pInst->bytes_per_sample; + } + else + { + evt->event_sample = 0; + } + } + + #if 0 + //Debug + static uint64_t lastBuffer = 0; + if(pInst->samples.driver_buffer_count > (lastBuffer + 950)) + { + evt->ID = TS_EVT_EXT_SYNC; + evt->event_sample = ((pInst->samples.driver_buffer_count * DMA_BUFFER_SIZE)) / pInst->bytes_per_sample + 200; + lastBuffer = pInst->samples.driver_buffer_count; + } + #endif + } + + return status; +} + +int32_t thunderscopeFwUpdate(tsHandle_t ts, const char* bitstream, uint32_t len) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + int32_t status = TS_STATUS_ERROR; + if(pInst) + { + status = ts_fw_manager_user_fw_update(&pInst->fw, bitstream, len); + if(status == TS_STATUS_OK) + { + LOG_DEBUG("Bitstream Update Complete"); + } + else + { + LOG_ERROR("Bitstream Update Failed: %d", status); + } + } + + return status; +} + +int32_t thunderscopeUserDataRead(tsHandle_t ts, char* buffer, uint32_t offset, uint32_t readLen) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + int32_t status = TS_STATUS_ERROR; + if(pInst) + { + status = ts_fw_manager_user_data_read(&pInst->fw, buffer, offset, readLen); + } + + return status; +} + +int32_t thunderscopeUserDataWrite(tsHandle_t ts, const char* buffer, uint32_t offset, uint32_t writeLen) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + int32_t status = TS_STATUS_ERROR; + if(pInst) + { + status = ts_fw_manager_user_data_write(&pInst->fw, buffer, offset, writeLen); + } + + return status; +} + +int32_t thunderscopeGetFwProgress(tsHandle_t ts, uint32_t* progress) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + if(pInst) + { + return ts_fw_manager_get_progress(&pInst->fw, progress); + } + else + { + return TS_STATUS_ERROR; + } +} + +#if defined(FACTORY_PROVISIONING_API) +int32_t thunderscopeFactoryProvisionPrepare(tsHandle_t ts, uint64_t dna) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + if(pInst) + { + return ts_fw_manager_factory_data_erase(&pInst->fw, dna); + } + else + { + return TS_STATUS_ERROR; + } +} + +int32_t thunderscopeFactoryProvisionAppendTLV(tsHandle_t ts, const uint32_t tag, uint32_t length, const char* content) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + if(pInst) + { + return ts_fw_manager_factory_data_append(&pInst->fw, tag, length, content); + } + else + { + return TS_STATUS_ERROR; + } +} +#else +int32_t thunderscopeFactoryProvisionPrepare(tsHandle_t ts, uint64_t dna) +{ + LOG_ERROR("Factory API Not Enabled"); + return TS_STATUS_ERROR; +} + +int32_t thunderscopeFactoryProvisionAppendTLV(tsHandle_t ts, const uint32_t tag, uint32_t length, const char* content) +{ + LOG_ERROR("Factory API Not Enabled"); + return TS_STATUS_ERROR; +} +#endif + +int32_t thunderscopeFactoryProvisionVerify(tsHandle_t ts) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + if(pInst) + { + return ts_fw_manager_factory_data_verify(&pInst->fw); + } + else + { + return TS_STATUS_ERROR; + } +} + +int32_t thunderscopeFactoryReadItem(tsHandle_t ts, const uint32_t tag, char* content_buffer, uint32_t item_max_len) +{ + ts_inst_t* pInst = (ts_inst_t*)ts; + if(pInst) + { + return ts_fw_manager_factory_data_retreive(&pInst->fw, tag, content_buffer, item_max_len); + } + else + { + return TS_STATUS_ERROR; + } +} diff --git a/src/ts_channel.c b/src/ts_channel.c index 48e27ed..de96293 100644 --- a/src/ts_channel.c +++ b/src/ts_channel.c @@ -40,6 +40,7 @@ typedef struct ts_channel_s { tsChannelCalibration_t cal; } chan[TS_NUM_CHANNELS]; ts_adc_t adc; + spi_bus_t adcSpibus; spi_bus_t spibus; struct { i2c_t clkGen; @@ -49,6 +50,7 @@ typedef struct ts_channel_s { gpio_t afe_power; gpio_t acq_power; file_t ctrl_handle; + tsSampleFormat_t sampleMode; tsScopeState_t status; } ts_channel_t; @@ -109,8 +111,8 @@ const static tsChannelParam_t g_tsParamsDefault = {.active = false, .bandwidth = 0, .coupling = TS_COUPLE_DC, .term = TS_TERM_1M, - .volt_offset_mV = 0, - .volt_scale_mV = 700}; + .volt_offset_uV = 0, + .volt_scale_uV = 700000}; static int32_t ts_channel_update_params(ts_channel_t* pTsHdl, uint32_t chanIdx, tsChannelParam_t* param, bool force); static int32_t ts_channel_health_update(ts_channel_t* pTsHdl); @@ -118,6 +120,7 @@ static int32_t ts_channel_health_update(ts_channel_t* pTsHdl); int32_t ts_channel_init(tsChannelHdl_t* pTsChannels, file_t ts) { int32_t retVal = TS_STATUS_OK; + bool betaDevice = false; if(pTsChannels == NULL) { @@ -133,6 +136,12 @@ int32_t ts_channel_init(tsChannelHdl_t* pTsChannels, file_t ts) return retVal; } + uint32_t id = litepcie_readl(ts, CSR_DEV_STATUS_HW_ID_ADDR); + if(0 == (id & (1 << CSR_DEV_STATUS_HW_ID_HW_VALID_OFFSET))) + { + betaDevice = true; + } + //Initialize Status pChan->ctrl_handle = ts; pChan->status.adc_lost_buffer_count = 0; @@ -167,13 +176,25 @@ int32_t ts_channel_init(tsChannelHdl_t* pTsChannels, file_t ts) pChan->pll.clkGen.fd = ts; pChan->pll.clkGen.devAddr = TS_PLL_I2C_ADDR; + if(betaDevice) + { + pChan->pll.clkGen.peripheral_baseaddr = TS_PLL_BUS_BETA; + } + else + { + pChan->pll.clkGen.peripheral_baseaddr = TS_PLL_BUS_DEV; + } //Set I2C Clock i2c_rate_set(pChan->pll.clkGen, TS_I2C_CLK_RATE); - pChan->pll.clkConf.in_clks[TS_PLL_INPUT_IDX].enable = 1; - pChan->pll.clkConf.in_clks[TS_PLL_INPUT_IDX].input_freq = TS_PLL_INPUT_RATE; - pChan->pll.clkConf.input_select = TS_PLL_INPUT_SEL; + pChan->pll.clkConf.in_clks[TS_PLL_LOCAL_OSC_IDX].enable = 1; + pChan->pll.clkConf.in_clks[TS_PLL_LOCAL_OSC_IDX].input_freq = TS_PLL_LOCAL_OSC_RATE; + pChan->pll.clkConf.in_clks[TS_PLL_LOCAL_OSC_IDX].input_divider = 0; + pChan->pll.clkConf.input_select = TS_PLL_LOCAL_OSC_SEL; + pChan->pll.clkConf.alternate_select = TS_PLL_INPUT_NONE_SEL; + pChan->pll.clkConf.in_clks[TS_PLL_REFIN_IDX].enable = 0; + pChan->pll.clkConf.in_clks[TS_PLL_REFIN_IDX].input_divider = 0; pChan->pll.clkConf.out_clks[TS_PLL_REFOUT_CLK_IDX].enable = 1; pChan->pll.clkConf.out_clks[TS_PLL_REFOUT_CLK_IDX].output_freq = TS_PLL_REFOUT_RATE_DEFAULT; pChan->pll.clkConf.out_clks[TS_PLL_REFOUT_CLK_IDX].output_mode = TS_PLL_REFOUT_CLK_MODE; @@ -199,18 +220,44 @@ int32_t ts_channel_init(tsChannelHdl_t* pTsChannels, file_t ts) goto channel_init_error; } - i2c_t trimDac = {ts, TS_TRIM_DAC_I2C_ADDR}; - i2c_t trimPot = {ts, TS_TRIM_DPOT_I2C_ADDR}; + i2c_t trimDac = {ts, TS_TRIM_DAC_BUS, TS_TRIM_DAC_I2C_ADDR}; + i2c_t trimPot = {ts, TS_TRIM_DPOT_BUS, TS_TRIM_DPOT_I2C_ADDR}; - retVal = spi_bus_init(&pChan->spibus, ts, - TS_SPI_BUS_BASE_ADDR, TS_SPI_BUS_CS_NUM); + if(betaDevice) + { + retVal = spi_bus_init(&pChan->spibus, ts, + TS_SPI_BUS_BASE_ADDR, TS_SPI_BUS_BETA_CS_NUM); + } + else + { + retVal = spi_bus_init(&pChan->spibus, ts, + TS_SPI_BUS_BASE_ADDR, TS_SPI_BUS_DEV_CS_NUM); + } if(retVal != TS_STATUS_OK) { goto channel_init_error; } + if(!betaDevice) + { + retVal = spi_bus_init(&pChan->adcSpibus, ts, + TS_ADC_SPI_BUS_BASE_ADDR, TS_ADC_SPI_BUS_CS_NUM); + if(retVal != TS_STATUS_OK) + { + goto channel_init_error; + } + } + spi_dev_t adcDev; - retVal = spi_dev_init(&adcDev, &pChan->spibus, TS_ADC_CS); + if(betaDevice) + { + retVal = spi_dev_init(&adcDev, &pChan->spibus, TS_BETA_ADC_CS); + } + else + { + retVal = spi_dev_init(&adcDev, &pChan->adcSpibus, TS_ADC_CS); + } + if(retVal != TS_STATUS_OK) { goto channel_init_error; @@ -224,7 +271,7 @@ int32_t ts_channel_init(tsChannelHdl_t* pTsChannels, file_t ts) for(uint32_t chanIdx = 0; chanIdx < TS_NUM_CHANNELS; chanIdx++) { pChan->chan[chanIdx].channelNo = chanIdx; - ts_adc_set_gain(&pChan->adc, chanIdx, TS_ADC_CH_COARSE_GAIN_DEFAULT, TS_ADC_CH_FINE_GAIN_DEFAULT); + ts_adc_set_gain(&pChan->adc, chanIdx, TS_ADC_CH_COARSE_GAIN_DEFAULT); retVal = ts_adc_set_channel_conf(&pChan->adc, chanIdx, g_channelConf[chanIdx].adc_input, g_channelConf[chanIdx].adc_invert); if(retVal != TS_STATUS_OK) @@ -387,46 +434,45 @@ static int32_t ts_channel_update_params(ts_channel_t* pTsHdl, uint32_t chanIdx, } //Set Voltage Scale - if(needUpdateGain || (param->volt_scale_mV != pTsHdl->chan[chanIdx].params.volt_scale_mV) || force) + if(needUpdateGain || (param->volt_scale_uV != pTsHdl->chan[chanIdx].params.volt_scale_uV) || force) { //Calculate dB gain value //TODO: Set both AFE and ADC gain? - int32_t afe_gain_mdB = (int32_t)(20000 * log10(TS_AFE_OUTPUT_NOMINAL_mVPP / (double)param->volt_scale_mV)); + int32_t afe_gain_mdB = (int32_t)(20000 * log10(TS_AFE_OUTPUT_NOMINAL_uVPP / (double)param->volt_scale_uV)); LOG_DEBUG("Channel %d AFE request %i mdB gain", chanIdx, afe_gain_mdB); retVal = ts_afe_set_gain(&pTsHdl->chan[chanIdx].afe, afe_gain_mdB); if(TS_STATUS_ERROR == retVal) { - LOG_ERROR("Unable to set Channel %d voltage scale: %x", chanIdx, param->volt_scale_mV); + LOG_ERROR("Unable to set Channel %d voltage scale: %x", chanIdx, param->volt_scale_uV); return TS_INVALID_PARAM; } else { LOG_DEBUG("Channel %d AFE set to %i mdB gain", chanIdx, retVal); - retVal = (int32_t)(TS_AFE_OUTPUT_NOMINAL_mVPP / pow(10.0, (double)retVal / 20000.0)); - LOG_DEBUG("Channel %d voltage scale Request: %d Actual: %d", chanIdx, param->volt_scale_mV, retVal); - pTsHdl->chan[chanIdx].params.volt_scale_mV = retVal; + retVal = (int32_t)(TS_AFE_OUTPUT_NOMINAL_uVPP / pow(10.0, (double)retVal / 20000.0)); + LOG_DEBUG("Channel %d voltage scale Request: %d Actual: %d", chanIdx, param->volt_scale_uV, retVal); + pTsHdl->chan[chanIdx].params.volt_scale_uV = retVal; needUpdateOffset = true; } } //Set Voltage Offset - if(needUpdateOffset || (param->volt_offset_mV != pTsHdl->chan[chanIdx].params.volt_offset_mV) || force) + if(needUpdateOffset || (param->volt_offset_uV != pTsHdl->chan[chanIdx].params.volt_offset_uV) || force) { //Adjust Trim DAC int32_t offset_actual = 0; - retVal = ts_afe_set_offset(&pTsHdl->chan[chanIdx].afe, param->volt_offset_mV, &offset_actual); + retVal = ts_afe_set_offset(&pTsHdl->chan[chanIdx].afe, param->volt_offset_uV, &offset_actual); if(TS_STATUS_OK != retVal) { - LOG_ERROR("Unable to set Channel %d voltage offset: %i", chanIdx, param->volt_offset_mV); + LOG_ERROR("Unable to set Channel %d voltage offset: %i", chanIdx, param->volt_offset_uV); return TS_INVALID_PARAM; } else { - LOG_DEBUG("Channel %d AFE set to %i mV offset", chanIdx, offset_actual); - // pTsHdl->chan[chanIdx].params.volt_offset_mV = offset_actual; - pTsHdl->chan[chanIdx].params.volt_offset_mV = param->volt_offset_mV; + LOG_DEBUG("Channel %d AFE set to %i uV offset", chanIdx, offset_actual); + pTsHdl->chan[chanIdx].params.volt_offset_uV = param->volt_offset_uV; } } @@ -450,7 +496,7 @@ static int32_t ts_channel_update_params(ts_channel_t* pTsHdl, uint32_t chanIdx, } //Update Sample Rate - retVal = ts_channel_sample_rate_set((tsChannelHdl_t)pTsHdl, pTsHdl->status.adc_sample_rate, pTsHdl->status.adc_sample_resolution); + retVal = ts_channel_sample_rate_set((tsChannelHdl_t)pTsHdl, pTsHdl->status.adc_sample_rate, pTsHdl->sampleMode); } return retVal; @@ -480,14 +526,32 @@ tsScopeState_t ts_channel_scope_status(tsChannelHdl_t tsChannels) tsScopeState_t state = {0}; return state; } + ts_channel_t* pTsHdl = (ts_channel_t*)tsChannels; + + pTsHdl->status.adc_sync = (litepcie_readl(pTsHdl->ctrl_handle, CSR_ADC_STATUS_ADDR) & (1 << CSR_ADC_STATUS_FRAME_SYNC_OFFSET)) ? 1 : 0; //Update XADC values - ts_channel_health_update((ts_channel_t*)tsChannels); + ts_channel_health_update(pTsHdl); - return ((ts_channel_t*)tsChannels)->status; + //Update Clock Status + int32_t clock_status = mcp_clkgen_status(pTsHdl->pll.clkGen, TS_PLL_STATUS, TS_PLL_STATUS_LEN); + if(clock_status < 0) + { + LOG_ERROR("Failed to read PLL Clock Status %d", clock_status); + } + else + { + pTsHdl->status.local_osc_clk = (clock_status & (1 << TS_PLL_STATUS_IC2_VALID)) ? 1:0; + pTsHdl->status.ref_in_clk = (clock_status & (1 << TS_PLL_STATUS_IC1_VALID)) ? 1:0; + pTsHdl->status.pll_lock = (clock_status & (1 << TS_PLL_STATUS_APLL_LOCK)) ? 1:0; + pTsHdl->status.pll_low = (clock_status & (1 << TS_PLL_STATUS_APLL_LOW)) ? 1:0; + pTsHdl->status.pll_high = (clock_status & (1 << TS_PLL_STATUS_APLL_HIGH)) ? 1:0; + pTsHdl->status.pll_alt = (clock_status & (1 << TS_PLL_STATUS_APLL_ALT)) ? 1:0; + } + return pTsHdl->status; } -int32_t ts_channel_sample_rate_set(tsChannelHdl_t tsChannels, uint32_t rate, uint32_t resolution) +int32_t ts_channel_sample_rate_set(tsChannelHdl_t tsChannels, uint32_t rate, tsSampleFormat_t mode) { if(tsChannels == NULL) { @@ -495,42 +559,72 @@ int32_t ts_channel_sample_rate_set(tsChannelHdl_t tsChannels, uint32_t rate, uin } ts_channel_t* ts = (ts_channel_t*)tsChannels; uint64_t actual_rate = 0; + uint64_t max_rate = 0; + - //TODO - Support valid rate/resolution combinations - if((rate < TS_MIN_SAMPLE_RATE) || (rate > TS_MAX_SAMPLE_RATE) - || (resolution != 256)) + switch(mode) { + case TS_8_BIT: + { + max_rate = TS_MAX_8BIT_SAMPLE_RATE; + ts->status.adc_sample_resolution = 256; + break; + } + case TS_12_BIT_LSB: + { + max_rate = TS_MAX_12BIT_SAMPLE_RATE; + ts->status.adc_sample_resolution = 4096; + break; + } + case TS_12_BIT_MSB: + { + max_rate = TS_MAX_12BIT_SAMPLE_RATE; + ts->status.adc_sample_resolution = 65536; + break; + } + case TS_14_BIT: + { + max_rate = TS_MAX_14BIT_SAMPLE_RATE; + ts->status.adc_sample_resolution = 65536; + break; + } + default: return TS_INVALID_PARAM; } - //Input validation - if(ts->adc.adcDev.mode == HMCAD15_SINGLE_CHANNEL) + if((rate < TS_MIN_SAMPLE_RATE) || (rate > max_rate)) + { + return TS_INVALID_PARAM; + } + + ts->sampleMode = mode; + + // Use 1:1 rate for precision mode (14_bit) + if((ts->adc.adcDev.mode == HMCAD15_SINGLE_CHANNEL) || (ts->sampleMode == TS_14_BIT)) { actual_rate = rate; } else if(ts->adc.adcDev.mode == HMCAD15_DUAL_CHANNEL) { - //Limit upper rate - if(rate > TS_MAX_DUAL_CH_RATE) + if(rate > (max_rate/2)) { - rate = TS_MAX_DUAL_CH_RATE; + rate = (max_rate/2); } actual_rate = rate * 2; } else { - //Limit upper rate - if(rate > TS_MAX_QUAD_CH_RATE) + if(rate > (max_rate/4)) { - rate = TS_MAX_QUAD_CH_RATE; + rate = (max_rate/4); } actual_rate = rate * 4; } + ts_adc_run(&ts->adc, 0); + if(actual_rate != ts->pll.clkConf.out_clks[TS_PLL_SAMPLE_CLK_IDX].output_freq) { - ts_adc_run(&ts->adc, 0); - // Apply resolution,rate configuration zl3026x_clk_config_t newConf = ts->pll.clkConf; newConf.out_clks[TS_PLL_SAMPLE_CLK_IDX].output_freq = actual_rate; @@ -550,18 +644,118 @@ int32_t ts_channel_sample_rate_set(tsChannelHdl_t tsChannels, uint32_t rate, uin LOG_ERROR("Failed to generate PLL Configuration: %d", clk_len); return clk_len; } + } - ts->status.adc_sample_rate = rate; - ts->status.adc_sample_resolution = resolution; - ts->status.adc_sample_bits = resolution == 256 ? 8 : 16; + ts->status.adc_sample_rate = rate; + ts->status.adc_sample_bits = (mode == TS_8_BIT) ? 8 : 16; - ts_adc_set_sample_mode(&ts->adc, rate, resolution); - ts_adc_run(&ts->adc, ts->status.adc_state); - } + ts_adc_set_sample_mode(&ts->adc, rate, mode); + ts_adc_run(&ts->adc, ts->status.adc_state); return TS_STATUS_OK; } +int32_t ts_channel_ext_clock_config(tsChannelHdl_t tsChannels, tsRefClockMode_t mode, uint32_t refclk_freq) +{ + if(tsChannels == NULL) + { + return TS_STATUS_ERROR; + } + ts_channel_t* ts = (ts_channel_t*)tsChannels; + zl3026x_clk_config_t newConf = ts->pll.clkConf; + bool clkout_en = (mode == TS_REFCLK_OUT); + bool clkin_en = (mode == TS_REFCLK_IN); + uint8_t clkin_divider = 0; + uint32_t input_freq = TS_PLL_LOCAL_OSC_RATE; + + //Validate Settings + if (clkout_en && refclk_freq == 0) + { + LOG_ERROR("Invalid Clock Out Frequency, cannot be 0"); + return TS_INVALID_PARAM; + } + else if(clkin_en && refclk_freq < ZL3026X_INPUT_CLK_MIN) + { + LOG_ERROR("Invalid Clock Input Frequency %d Hz. Must be a minimum of %d Hz", refclk_freq, ZL3026X_INPUT_CLK_MIN); + return TS_INVALID_PARAM; + } + + if(clkin_en) + { + //Divide Input Clock Frequency if needed + while((refclk_freq / (1UL << clkin_divider)) > ZL3026X_INPUT_CLK_MAX) + { + clkin_divider++; + if(clkin_divider > ZL3026X_IN_DIV_8) + { + LOG_ERROR("Unable to configure external clock input frequency %d Hz", refclk_freq); + return TS_INVALID_PARAM; + } + } + + //Set Input Clock Configuration + newConf.in_clks[TS_PLL_REFIN_IDX].enable = 1; + newConf.in_clks[TS_PLL_REFIN_IDX].input_freq = refclk_freq / (1 << clkin_divider); + newConf.in_clks[TS_PLL_REFIN_IDX].input_divider = (zl3026x_input_div_t)clkin_divider; + newConf.input_select = TS_PLL_REFIN_SEL; + newConf.alternate_select = TS_PLL_LOCAL_OSC_SEL; + + //Input frequency on bypass path + input_freq = newConf.in_clks[TS_PLL_REFIN_IDX].input_freq; + } + else + { + //Use Internal Reference Clock + newConf.in_clks[TS_PLL_REFIN_IDX].enable = 0; + newConf.input_select = TS_PLL_LOCAL_OSC_SEL; + newConf.alternate_select = TS_PLL_INPUT_NONE_SEL; + } + + //Set Output Clock Configuration + if(clkout_en) + { + //Validate Output Clock Frequency + if(refclk_freq > ZL3026X_MAX_PLL_OUT) + { + LOG_ERROR("Invalid Clock Output Frequency %d Hz. Must be a maximum of %d Hz", refclk_freq, ZL3026X_MAX_PLL_OUT); + return TS_INVALID_PARAM; + } + + newConf.out_clks[TS_PLL_REFOUT_CLK_IDX].enable = 1; + newConf.out_clks[TS_PLL_REFOUT_CLK_IDX].output_freq = refclk_freq; + if(refclk_freq > input_freq) + { + newConf.out_clks[TS_PLL_REFOUT_CLK_IDX].output_pll_select = ZL3026X_PLL_INT_DIV; + } + else + { + newConf.out_clks[TS_PLL_REFOUT_CLK_IDX].output_pll_select = ZL3026X_PLL_BYPASS; + } + } + else + { + //Disable Output Ref Clock + newConf.out_clks[TS_PLL_REFOUT_CLK_IDX].enable = 0; + } + + mcp_clkgen_conf_t clk_regs[MCP_CLKGEN_ARR_MAX_LEN] = {0}; + int32_t clk_len = mcp_zl3026x_build_config(clk_regs, MCP_CLKGEN_ARR_MAX_LEN, newConf); + if(clk_len > 0) + { + if(TS_STATUS_OK != mcp_clkgen_config(ts->pll.clkGen, clk_regs, clk_len)) + { + return TS_STATUS_ERROR; + } + ts->pll.clkConf = newConf; + } + else + { + LOG_ERROR("Failed to generate PLL Configuration: %d", clk_len); + return clk_len; + } + + return TS_STATUS_OK; +} int32_t ts_channel_calibration_set(tsChannelHdl_t tsChannels, uint32_t chanIdx, tsChannelCalibration_t* cal) { @@ -581,8 +775,8 @@ int32_t ts_channel_calibration_set(tsChannelHdl_t tsChannels, uint32_t chanIdx, ts->chan[chanIdx].afe.cal = *cal; LOG_DEBUG("Received Calibration for channel %d", chanIdx); - LOG_DEBUG("\tBuffer Output: %d mV", cal->buffer_mV); - LOG_DEBUG("\t+VBIAS: %d mV", cal->bias_mV); + LOG_DEBUG("\tBuffer Output: %d uV", cal->buffer_uV); + LOG_DEBUG("\t+VBIAS: %d uV", cal->bias_uV); LOG_DEBUG("\t1M Attenuator Gain: %d mdB", cal->attenuatorGain1M_mdB); LOG_DEBUG("\t50 Ohm Terminator Gain: %d mdB", cal->attenuatorGain50_mdB); LOG_DEBUG("\tBuffer Output Gain: %d mdB", cal->bufferGain_mdB); @@ -590,8 +784,8 @@ int32_t ts_channel_calibration_set(tsChannelHdl_t tsChannels, uint32_t chanIdx, LOG_DEBUG("\tPreamp Low Input Gain Error: %d mdB", cal->preampLowGainError_mdB); LOG_DEBUG("\tPreamp High Input Gain Error: %d mdB", cal->preampHighGainError_mdB); LOG_DEBUG("\tPreamp High Input Gain Error: %d mdB", cal->preampOutputGainError_mdB); - LOG_DEBUG("\tPreamp Low Output Offset: %d mV", cal->preampLowOffset_mV); - LOG_DEBUG("\tPreamp High Output Offset: %d mV", cal->preampHighOffset_mV); + LOG_DEBUG("\tPreamp Low Output Offset: %d uV", cal->preampLowOffset_uV); + LOG_DEBUG("\tPreamp High Output Offset: %d uV", cal->preampHighOffset_uV); LOG_DEBUG("\tPreamp Input Bias Current: %d uA", cal->preampInputBias_uA); //Force afe to recalculate gain/offsets @@ -619,35 +813,51 @@ int32_t ts_channel_calibration_get(tsChannelHdl_t tsChannels, uint32_t chanIdx, return TS_STATUS_OK; } -int32_t ts_channel_calibration_manual(tsChannelHdl_t tsChannels, uint32_t chanIdx, tsChannelCtrl_t ctrl) +int32_t ts_channel_adc_calibration_set(tsChannelHdl_t tsChannels, tsAdcCalibration_t* cal) { - int32_t retVal = TS_STATUS_OK; ts_channel_t* ts = (ts_channel_t*)tsChannels; - if(tsChannels == NULL) + if(tsChannels == NULL || cal == NULL) { LOG_ERROR("Invalid handle"); return TS_STATUS_ERROR; } - if(chanIdx >= TS_NUM_CHANNELS) + LOG_DEBUG("Received Calibration for ADC"); + LOG_DEBUG("\tFine 2-1: %02X %02X", cal->branchFineGain[1], cal->branchFineGain[0]); + LOG_DEBUG("\tFine 4-3: %02X %02X", cal->branchFineGain[3], cal->branchFineGain[2]); + LOG_DEBUG("\tFine 6-5: %02X %02X", cal->branchFineGain[5], cal->branchFineGain[4]); + LOG_DEBUG("\tFine 8-7: %02X %02X", cal->branchFineGain[7], cal->branchFineGain[6]); + + return ts_adc_cal_set(&ts->adc, cal); +} + +int32_t ts_channel_adc_calibration_get(tsChannelHdl_t tsChannels, tsAdcCalibration_t* cal) +{ + ts_channel_t* ts = (ts_channel_t*)tsChannels; + if(tsChannels == NULL || cal == NULL) { - return TS_INVALID_PARAM; + LOG_ERROR("Invalid handle"); + return TS_STATUS_ERROR; } + return ts_adc_cal_get(&ts->adc, cal); +} - //Set AFE Bandwidth - retVal = ts_afe_set_bw_filter(&ts->chan[chanIdx].afe, ctrl.pga_bw); - if(retVal > 0) +int32_t ts_channel_calibration_manual(tsChannelHdl_t tsChannels, uint32_t chanIdx, tsChannelCtrl_t ctrl) +{ + int32_t retVal = TS_STATUS_OK; + ts_channel_t* ts = (ts_channel_t*)tsChannels; + if(tsChannels == NULL) { - LOG_DEBUG("Channel %d AFE BW set to %i MHz", chanIdx, retVal); + LOG_ERROR("Invalid handle"); + return TS_STATUS_ERROR; } - else + + if(chanIdx >= TS_NUM_CHANNELS) { - LOG_ERROR("Unable to set Channel %d bandwidth %d", chanIdx, retVal); return TS_INVALID_PARAM; } - //Set AC/DC Coupling if(TS_STATUS_OK == ts_afe_coupling_control(&ts->chan[chanIdx].afe, ctrl.dc_couple == 1 ? TS_COUPLE_DC : TS_COUPLE_AC )) @@ -688,7 +898,7 @@ int32_t ts_channel_calibration_manual(tsChannelHdl_t tsChannels, uint32_t chanId lmh6518Config_t preamp = LMH6518_CONFIG_INIT; preamp.atten = ctrl.pga_atten; preamp.filter = ctrl.pga_bw; - preamp.preamp = ctrl.pga_high_gain = 0 ? PREAMP_LG : PREAMP_HG; + preamp.preamp = ctrl.pga_high_gain == 0 ? PREAMP_LG : PREAMP_HG; preamp.pm = PM_AUX_HIZ; retVal = lmh6518_apply_config(ts->chan[chanIdx].afe.amp, preamp); @@ -737,6 +947,8 @@ static int32_t ts_channel_health_update(ts_channel_t* pTsHdl) pTsHdl->status.sys_health.vcc_int = (uint32_t)(((double)litepcie_readl(pTsHdl->ctrl_handle, CSR_XADC_VCCINT_ADDR) / 4096 * 3)*1000); pTsHdl->status.sys_health.vcc_aux = (uint32_t)(((double)litepcie_readl(pTsHdl->ctrl_handle, CSR_XADC_VCCAUX_ADDR) / 4096 * 3)*1000); pTsHdl->status.sys_health.vcc_bram = (uint32_t)(((double)litepcie_readl(pTsHdl->ctrl_handle, CSR_XADC_VCCBRAM_ADDR) / 4096 * 3)*1000); + pTsHdl->status.sys_health.frontend_power_good = (uint8_t)litepcie_readl(pTsHdl->ctrl_handle, CSR_FRONTEND_STATUS_ADDR) & (1 << CSR_FRONTEND_STATUS_FE_PG_OFFSET); + pTsHdl->status.sys_health.acq_power_good = (uint8_t)litepcie_readl(pTsHdl->ctrl_handle, CSR_ADC_STATUS_ADDR) & (1 << CSR_ADC_STATUS_ACQ_PG_OFFSET); return TS_STATUS_OK; } \ No newline at end of file diff --git a/src/ts_channel.h b/src/ts_channel.h index f0a6bc8..6575d91 100644 --- a/src/ts_channel.h +++ b/src/ts_channel.h @@ -81,10 +81,20 @@ tsScopeState_t ts_channel_scope_status(tsChannelHdl_t tsChannels); * * @param tsChannels Thunderscope Channel handle * @param rate Samples per Second - * @param resolution Number of bits in each sample. Valid values are 2^8 (256), 2^12 (4096), and 2^14 (16384). + * @param mode Sample Mode to set the resolution of each sample. * @return int32_t TS_STATUS_OK on success, else TS_STATUS_ERROR */ -int32_t ts_channel_sample_rate_set(tsChannelHdl_t tsChannels, uint32_t rate, uint32_t resolution); +int32_t ts_channel_sample_rate_set(tsChannelHdl_t tsChannels, uint32_t rate, tsSampleFormat_t mode); + +/** + * @brief Configure the Clock Generator Reference Clock In/Out + * + * @param tsChannels Thunderscope Channel handle + * @param mode Set the Clock IN/OUT mode + * @param refclk_freq Set the input clock frequency if in IN mode, or output frequency if in OUT mode + * @return int32_t TS_STATUS_OK on success, else TS_STATUS_ERROR + */ +int32_t ts_channel_ext_clock_config(tsChannelHdl_t tsChannels, tsRefClockMode_t mode, uint32_t refclk_freq); /** * @brief Set the calibration parameters for a channel @@ -106,6 +116,24 @@ int32_t ts_channel_calibration_set(tsChannelHdl_t tsChannels, uint32_t chanIdx, */ int32_t ts_channel_calibration_get(tsChannelHdl_t tsChannels, uint32_t chanIdx, tsChannelCalibration_t* cal); +/** + * @brief Set the calibration parameters for the adc + * + * @param tsChannels Thunderscope Channel handle + * @param cal Pointer to the calibration structure to apply + * @return int32_t TS_STATUS_OK on success, else TS_STATUS_ERROR + */ +int32_t ts_channel_adc_calibration_set(tsChannelHdl_t tsChannels, tsAdcCalibration_t* cal); + +/** + * @brief Get the calibration parameters for the adc + * + * @param tsChannels Thunderscope Channel handle + * @param cal Pointer to the calibration structure + * @return int32_t TS_STATUS_OK on success, else TS_STATUS_ERROR + */ + int32_t ts_channel_adc_calibration_get(tsChannelHdl_t tsChannels, tsAdcCalibration_t* cal); + /** * @brief Manually set the AFE controls for a channel * diff --git a/src/ts_data.c b/src/ts_data.c new file mode 100644 index 0000000..d7effad --- /dev/null +++ b/src/ts_data.c @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of libtslitex. + * Provide methods to fetch and store content in flash + * + * Copyright (c) 2024 Nate Meyer + */ + +#include +#include + +#include "ts_data.h" +#include "ts_fw_manager.h" +#include "util.h" + + +int32_t ts_data_factory_cal_get(ts_fw_manager_t* mngr, tsScopeCalibration_t *fcal) +{ + // struct json_object * fcal; + + return TS_STATUS_OK; +} + +int32_t ts_data_factory_id_get(ts_fw_manager_t* mngr, tsDeviceInfo_t* infos) +{ + struct json_object *fid; + struct json_object *item; + uint32_t hwid_version = 0; + uint32_t hwid_len = 0; + uint8_t* hwid_buffer = NULL; + + // Check HWID tag in factory partition + hwid_len = ts_fw_manager_factory_data_get_length(mngr, TAG_HWID); + + if(hwid_len <= 0) + { + LOG_ERROR("Could not read mfg data"); + return TS_STATUS_ERROR; + } + + // Read HWID to buffer + hwid_buffer = malloc(hwid_len + 1); + if(TS_STATUS_ERROR == ts_fw_manager_factory_data_retreive(mngr, TAG_HWID, hwid_buffer, hwid_len)) + { + LOG_ERROR("Failed to read HWID"); + free(hwid_buffer); + return TS_STATUS_ERROR; + } + + // Parse json + hwid_buffer[hwid_len] = '\0'; + fid = json_tokener_parse(hwid_buffer); + + // Store HWID version + if(json_object_object_get_ex(fid,"version", &item)) + { + if(json_object_get_type(item) == json_type_int) + { + hwid_version = json_object_get_int(item); + } + else + { + LOG_ERROR("HWID Version bad value"); + } + } + else + { + LOG_ERROR("HWID Version key not found"); + } + + // Fill struct with info + if(json_object_object_get_ex(fid,"Serial Number", &item)) + { + if(json_object_get_type(item) == json_type_string) + { + strncpy(infos->serial_number, json_object_get_string(item), TS_IDENT_STR_LEN); + } + else + { + LOG_ERROR("Serial Number bad value"); + } + } + else + { + LOG_ERROR("Serial Number key not found"); + } + + if(json_object_object_get_ex(fid,"Board Revision", &item)) + { + if(json_object_get_type(item) == json_type_int) + { + infos->board_rev = json_object_get_int(item); + } + else + { + LOG_ERROR("Board Revision bad value"); + } + } + else + { + LOG_ERROR("Board Revision key not found"); + } + + if(json_object_object_get_ex(fid,"Build Config", &item)) + { + if(json_object_get_type(item) == json_type_string) + { + strncpy(infos->build_config, json_object_get_string(item), TS_IDENT_STR_LEN); + } + else + { + LOG_ERROR("Build Config bad value"); + } + } + else + { + LOG_ERROR("Build Config key not found"); + } + + // Build Date + if(json_object_object_get_ex(fid,"Build Date", &item)) + { + if(json_object_get_type(item) == json_type_string) + { + strncpy(infos->build_date, json_object_get_string(item), TS_IDENT_STR_LEN); + } + else + { + LOG_ERROR("Build Date bad value"); + } + } + else + { + LOG_ERROR("Build Date key not found"); + } + + // Signature + if(json_object_object_get_ex(fid,"Mfg Signature", &item)) + { + if(json_object_get_type(item) == json_type_string) + { + strncpy(infos->mfg_signature, json_object_get_string(item), TS_IDENT_STR_LEN); + } + else + { + LOG_ERROR("Mfg Signature bad value"); + } + } + else + { + LOG_ERROR("Mfg Signature key not found"); + } + + free(hwid_buffer); + return TS_STATUS_OK; +} diff --git a/src/ts_data.h b/src/ts_data.h new file mode 100644 index 0000000..7b50aa7 --- /dev/null +++ b/src/ts_data.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of libtslitex. + * Library configuration definitions + * + * Copyright (C) 2025 / Nate Meyer / nate.devel@gmail.com + * + */ +#ifndef _TS_DATA_H_ +#define _TS_DATA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "ts_common.h" +#include "ts_calibration.h" +#include "ts_fw_manager.h" + + +#define TAGSTR(x) (uint32_t)((x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3]) +#define TAG_HWID TAGSTR("HWID") +#define TAG_FCAL TAGSTR("FCAL") + +/** + * @brief Read and parse the Factory Calibration from flash + * + * @param mngr Pointer to the FW Manager instance + * @param fcal Pointer to the calibration data struct + * + * @return TS_STATUS_OK if the calibration was parsed successfully + */ +int32_t ts_data_factory_cal_get(ts_fw_manager_t* mngr, tsScopeCalibration_t *fcal); + +/** + * @brief Read and parse the Factory Device Information + * + * @param mngr Pointer to the FW Manager instance + * @param infos Pointer to the device information structf + * + * @return TS_STATUS_OK if the Device information data was parsed + */ +int32_t ts_data_factory_id_get(ts_fw_manager_t* mngr, tsDeviceInfo_t* infos); + + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/ts_fw_manager.c b/src/ts_fw_manager.c index 8e2899e..0c5e542 100644 --- a/src/ts_fw_manager.c +++ b/src/ts_fw_manager.c @@ -8,12 +8,23 @@ #include +#if defined(_WIN32) +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#endif + +#include + #include "ts_fw_manager.h" #include "platform.h" #include "spiflash.h" #include "liblitepcie.h" #include "util.h" +#define FLASH_PAGE_LEN (SPI_FLASH_ERASE_SIZE) + // From AMD UG470 #define IDCODE_7A200T (0x03636093) #define IDCODE_7A100T (0x03631093) @@ -23,6 +34,8 @@ #define ICAP_REG_IDCODE (0x0C) +#define INVALID_TAG (0xFFFFFFFF) + static const struct { uint32_t code; char name[16]; @@ -34,6 +47,8 @@ static const struct { {0, ""} }; +static void ts_fw_progress_update(void* ctx, uint32_t work_done, uint32_t work_total); + static uint32_t ts_fw_get_idcode(file_t fd) { uint32_t code; @@ -134,7 +149,16 @@ int32_t ts_fw_manager_init(file_t fd, ts_fw_manager_t* mngr) return TS_STATUS_ERROR; } //Initialize SPI Flash - spiflash_init(fd, &mngr->flash_dev); + if(TS_STATUS_OK == spiflash_init(fd, &mngr->flash_dev)) + { + mngr->flash_dev.op_progress = ts_fw_progress_update; + mngr->flash_dev.op_progress_ctx = mngr; + } + else + { + LOG_ERROR("Failed to initialize SPI Flash"); + return TS_STATUS_ERROR; + } //Set partition Table if( mngr->flash_dev.mfg_code == TS_FLASH_256M_MFG) @@ -157,12 +181,16 @@ int32_t ts_fw_manager_user_fw_update(ts_fw_manager_t* mngr, const char* file_str // Verify Bitstream and Strip Header Info uint32_t bin_length = 0; const char* part_name = NULL; + atomic_store(&mngr->fw_progress, 0); const char* bin_start = ts_fw_parse_bit_header(file_stream, &part_name, &bin_length); if(bin_length > len) { LOG_ERROR("INVALID length in bitstream header (%u > %u)", bin_length, len); return TS_STATUS_ERROR; } + LOG_DEBUG("Bitstream Length: %u", bin_length); + + mngr->fw_progress_max = bin_length*2; // Verify FPGA Part // Get IDCODE from TS @@ -210,37 +238,618 @@ int32_t ts_fw_manager_user_fw_update(ts_fw_manager_t* mngr, const char* file_str return TS_STATUS_ERROR; } + atomic_store(&mngr->fw_progress, bin_length); + // Program New Bitstream - if(bin_length != spiflash_write(&mngr->flash_dev, mngr->partition_table->user_bitstream_start, bin_start, bin_length)) + // TODO: AMD Recommends programming the SYNC word last, so program the bitstream from end to start + // Ref: AMD AR 58090 [https://adaptivesupport.amd.com/s/article/58090] + // Ref: AMD AR 58249 [https://adaptivesupport.amd.com/s/article/58249] + if(bin_length != spiflash_write(&mngr->flash_dev, mngr->partition_table->user_bitstream_start, (const uint8_t*)bin_start, bin_length)) { LOG_ERROR("Failed to write user bitstream partition"); return TS_STATUS_ERROR; } + atomic_store(&mngr->fw_progress, mngr->fw_progress_max); + + return TS_STATUS_OK; +} + +int32_t ts_fw_manager_get_progress(ts_fw_manager_t* mngr, uint32_t* progress) +{ + uint32_t current_progress = 0; + + // Get Progress of Flash Write + if(mngr == NULL || progress == NULL) + { + LOG_ERROR("Invalid manager handle"); + return TS_STATUS_ERROR; + } + + if(mngr->fw_progress_max == 0) + { + LOG_ERROR("Firmware Update not in progress"); + } + else if (current_progress < mngr->fw_progress_max) + { + current_progress = (mngr->fw_progress * 100) / mngr->fw_progress_max; + } + else + { + current_progress = 100; + } + + *progress = current_progress; return TS_STATUS_OK; } -int32_t ts_fw_manager_user_cal_get(ts_fw_manager_t* mngr, const char* file_stream, uint32_t max_len) +int32_t ts_fw_manager_user_data_read(ts_fw_manager_t* mngr, char* buffer, uint32_t offset, uint32_t max_len) { + if(mngr == NULL) + { + LOG_ERROR("Invalid manager handle"); + return TS_STATUS_ERROR; + } + + uint32_t max_offset = mngr->partition_table->user_config_end - mngr->partition_table->user_config_start; + uint32_t readLen = max_len; + atomic_store(&mngr->fw_progress, 0); + mngr->fw_progress_max = max_len; + + if(buffer == NULL) + { + LOG_ERROR("User Data Read Invalid Buffer Address"); + return TS_STATUS_ERROR; + } + if (offset > max_offset) + { + LOG_ERROR("User Data Read Invalid Offset (%d is beyond range of %d)", offset, max_offset); + return TS_STATUS_ERROR; + } + if(max_len == 0) + { + LOG_ERROR("User Data Read Invalid Len (%d)", max_len); + return TS_STATUS_ERROR; + } + + if((offset + max_len) > (max_offset)) + { + readLen = max_offset - offset; + } + // Read File from SPI Flash + if(readLen != spiflash_read(&mngr->flash_dev, (mngr->partition_table->user_config_start + offset), buffer, readLen)) + { + LOG_ERROR("Failed to read user data partition (%d)", readLen); + return TS_STATUS_ERROR; + } - return TS_STATUS_OK; + return readLen; } -int32_t ts_fw_manager_user_cal_update(ts_fw_manager_t* mngr, const char* file_stream, uint32_t len) +int32_t ts_fw_manager_user_data_write(ts_fw_manager_t* mngr, const char* buffer, uint32_t offset, uint32_t len) { - // Verify File Good + if(mngr == NULL) + { + LOG_ERROR("Invalid manager handle"); + return TS_STATUS_ERROR; + } + + int32_t status; + uint8_t start_buffer[FLASH_PAGE_LEN]; + uint8_t end_buffer[FLASH_PAGE_LEN]; + uint32_t max_offset = mngr->partition_table->user_config_end - mngr->partition_table->user_config_start; + uint32_t start_addr = mngr->partition_table->user_config_start + (offset & ~(FLASH_PAGE_LEN - 1)); + uint32_t op_len = ((len + (offset % FLASH_PAGE_LEN) //Add prepend page len + + (FLASH_PAGE_LEN - 1)) / FLASH_PAGE_LEN) //Round up to total number of pages + * FLASH_PAGE_LEN; // Multiply to get number of bytes + + atomic_store(&mngr->fw_progress, 0); + mngr->fw_progress_max = op_len*2; + + + // Verify Parameters Good + if(buffer == NULL) + { + LOG_ERROR("User Data Write Invalid Buffer Address"); + return TS_STATUS_ERROR; + } + if (offset >= max_offset) + { + LOG_ERROR("User Data Write Invalid Offset (%d is beyond range of %d)", offset, max_offset); + return TS_STATUS_ERROR; + } + if(len == 0) + { + LOG_ERROR("User Data Write Invalid Len (%d)", len); + return TS_STATUS_ERROR; + } + else if((len + offset) > max_offset) + { + LOG_ERROR("User Data Write Invalid Offset 0x%06X Len %d exceeds end of User Data Region (0x%06X)", offset, len, max_offset); + return TS_STATUS_ERROR; + } + + //Save initial page if offset not 4k-aligned + if((offset % FLASH_PAGE_LEN) != 0) + { + LOG_DEBUG("Read %d bytes at address %08X", (FLASH_PAGE_LEN), (start_addr + op_len - FLASH_PAGE_LEN)); + status = spiflash_read(&mngr->flash_dev, start_addr, start_buffer, FLASH_PAGE_LEN); + if(status != FLASH_PAGE_LEN) + { + LOG_ERROR("Failed to read SPI Flash page 0x%06X (%d)", start_addr, status); + return status; + } + } + + //Save final page + LOG_DEBUG("Read %d bytes at address %08X", (FLASH_PAGE_LEN), (start_addr + op_len - FLASH_PAGE_LEN)); + status = spiflash_read(&mngr->flash_dev, (start_addr + op_len - FLASH_PAGE_LEN), end_buffer, FLASH_PAGE_LEN); + if(status != FLASH_PAGE_LEN) + { + LOG_ERROR("Failed to read SPI Flash page 0x%06X (%d)", (start_addr + op_len - FLASH_PAGE_LEN), status); + return status; + } + // Erase User Flash Partition + LOG_DEBUG("Erase %d bytes at address %08X", (op_len), (start_addr)); + if(TS_STATUS_OK != spiflash_erase(&mngr->flash_dev, start_addr, op_len)) + { + LOG_ERROR("Failed to erase user data partition (0x%06X 0x%06X)", start_addr, op_len); + return TS_STATUS_ERROR; + } + + atomic_store(&mngr->fw_progress, op_len); // Program New User Data + if((offset % FLASH_PAGE_LEN) != 0) + { + //Copy beginning of user data over saved first page + uint32_t start_page_len = ((offset % FLASH_PAGE_LEN) + len) > FLASH_PAGE_LEN ? + (FLASH_PAGE_LEN - (offset % FLASH_PAGE_LEN)) : + len; + memcpy(&start_buffer[offset % FLASH_PAGE_LEN], buffer, start_page_len); + LOG_DEBUG("Copy %d bytes at offset %08X to start buffer", start_page_len, (offset % FLASH_PAGE_LEN)); + + LOG_DEBUG("Write %d bytes at address %08X", FLASH_PAGE_LEN, (start_addr)); + if(FLASH_PAGE_LEN != spiflash_write(&mngr->flash_dev, start_addr, start_buffer, FLASH_PAGE_LEN)) + { + LOG_ERROR("Failed to erase user bitstream partition"); + return TS_STATUS_ERROR; + } + + start_addr += FLASH_PAGE_LEN; + op_len -= FLASH_PAGE_LEN; + } + + if(op_len > FLASH_PAGE_LEN) + { + if(start_addr + op_len > + mngr->partition_table->user_config_end) + { + LOG_ERROR("Failed to write %d bytes at 0x%06X", op_len, start_addr); + return TS_STATUS_ERROR; + } + + LOG_DEBUG("Write %d bytes at address %08X", (op_len - FLASH_PAGE_LEN), (start_addr)); + if(TS_STATUS_ERROR == spiflash_write(&mngr->flash_dev, (start_addr), (uint8_t*)buffer, + (op_len - FLASH_PAGE_LEN))) + { + LOG_ERROR("Failed to write user bitstream partition"); + return TS_STATUS_ERROR; + } + start_addr += op_len - FLASH_PAGE_LEN; + op_len = FLASH_PAGE_LEN; + } + + if(op_len != 0) + { + //Copy end of user data over saved final page + uint32_t remainder = (len+offset) % FLASH_PAGE_LEN; + if(remainder == 0) + { + remainder = FLASH_PAGE_LEN; + } + memcpy(end_buffer, &buffer[len - remainder], remainder); + LOG_DEBUG("Copy %d bytes at src offset %08X to final buffer", (remainder), (len - remainder)); + + if(start_addr + FLASH_PAGE_LEN > mngr->partition_table->user_config_end) + { + LOG_ERROR("Failed to write final page at 0x%06X", start_addr + FLASH_PAGE_LEN); + return TS_STATUS_ERROR; + } + + LOG_DEBUG("Write %d bytes at address %08X", (FLASH_PAGE_LEN), (start_addr)); + if(FLASH_PAGE_LEN != spiflash_write(&mngr->flash_dev, start_addr, end_buffer, FLASH_PAGE_LEN)) + { + LOG_ERROR("Failed to erase user bitstream partition"); + return TS_STATUS_ERROR; + } + } + + atomic_store(&mngr->fw_progress, mngr->fw_progress_max); return TS_STATUS_OK; } -int32_t ts_fw_manager_factory_cal_get(ts_fw_manager_t* mngr, const char* file_stream, uint32_t len) +static void ts_fw_progress_update(void* ctx, uint32_t work_done, uint32_t work_total) { - // Read File from SPI Flash + ts_fw_manager_t* mngr = (ts_fw_manager_t*)ctx; + if(mngr != NULL) + { + atomic_fetch_add(&mngr->fw_progress, work_done); + } +} - return TS_STATUS_OK; +int32_t ts_fw_manager_factory_data_erase(ts_fw_manager_t* mngr, uint64_t dna) +{ + uint64_t dna_actual= (uint64_t)litepcie_readl(mngr->flash_dev.fd, CSR_DNA_ID_ADDR) << 32; + dna_actual |= litepcie_readl(mngr->flash_dev.fd, CSR_DNA_ID_ADDR + 4); + + if(dna == dna_actual) + { + return spiflash_erase(&mngr->flash_dev, mngr->partition_table->factory_config_start, + (mngr->partition_table->factory_config_end - mngr->partition_table->factory_config_start)); + } + return TS_STATUS_ERROR; +} + +int32_t ts_fw_manager_factory_data_append(ts_fw_manager_t* mngr, uint32_t tag, uint32_t length, const uint8_t *content) +{ + int32_t retVal = TS_STATUS_ERROR; + uint32_t offset = 0; + uint32_t current_tag = INVALID_TAG; + uint32_t next_len = 0; + uint32_t tlv_crc = crc32(0, Z_NULL, 0); + + // Append the new TLV + while (offset < mngr->partition_table->factory_config_end) + { + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), + (uint8_t*)¤t_tag, sizeof(current_tag)); + if(retVal < 0) + { + LOG_ERROR("Failed to read tag at offset 0x%x", offset); + break; + } + + if(tag == htonl(current_tag)) + { + LOG_ERROR("ERROR: Duplicate TAG %08X", tag); + retVal = TS_STATUS_ERROR; + break; + } + + // Check if empty + if(current_tag == INVALID_TAG) + { + //Test for length + if((mngr->partition_table->factory_bitstream_start + offset + sizeof(uint32_t)*3 + length) > + mngr->partition_table->factory_config_end) + { + LOG_ERROR("Not enough space in Factory Partition to store object of %dB",length); + retVal = TS_STATUS_ERROR; + break; + } + + //Write Tag, and Length + uint64_t tl = ((uint64_t)htonl(length) << 32) + htonl(tag); + retVal = spiflash_write(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), (uint8_t*)&tl, sizeof(uint64_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to write tag %08x at offset 0x%x", tag, offset); + break; + } + + offset += sizeof(uint64_t); + + //Write Value + retVal = spiflash_write(&mngr->flash_dev, (mngr->partition_table->factory_config_start + offset), content, length); + if(retVal < 0) + { + LOG_ERROR("Failed to write value for tag %08x at offset 0x%x", tag, offset); + break; + } + offset += length; + + //Write CRC + tlv_crc = crc32(tlv_crc, content, length); + LOG_DEBUG("Completing TLV with CRC %08X", tlv_crc); + tlv_crc = htonl(tlv_crc); + retVal = spiflash_write(&mngr->flash_dev, (mngr->partition_table->factory_config_start + offset), (uint8_t*)&tlv_crc, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to write CRC32 for tag %08X at offset 0x%x", tag, offset); + break; + } + retVal = TS_STATUS_OK; + break; + } + + //Read Len and increment for next tag + offset += sizeof(uint32_t); + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start + offset), (uint8_t*)&next_len, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to read tag at offset 0x%x", offset); + break; + } + + // Increment size of Length, Value, and CRC + offset += sizeof(uint32_t)*2 + ntohl(next_len); + } + return retVal; +} + +int32_t ts_fw_manager_factory_data_verify(ts_fw_manager_t* mngr) +{ + int32_t retVal = TS_STATUS_OK; + uint32_t offset = 0; + uint32_t tag = INVALID_TAG; + uint32_t next_len = 0; + uint8_t val_buffer[4096]; + uint32_t stored_crc; + uint32_t new_crc = crc32(0, Z_NULL, 0); + uint32_t element_count = 0; + + // Read and Verify each TLV CRC32 + while (offset < mngr->partition_table->factory_config_end) + { + //Read the TAG + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), + (uint8_t*)&tag, sizeof(tag)); + if(retVal < 0) + { + LOG_ERROR("Failed to read tag at offset 0x%x", offset); + break; + } + + offset += sizeof(tag); + + // Check if empty, no more tags to verify + if(tag == INVALID_TAG) + { + LOG_DEBUG("Factory data partition verified"); + retVal = TS_STATUS_OK; + break; + } + + //Read the Length + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), + (uint8_t*)&next_len, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to write tag %08x at offset 0x%x", tag, offset); + break; + } + offset += sizeof(uint32_t); + + if((mngr->partition_table->factory_config_start + offset + ntohl(next_len) + sizeof(uint32_t)) > + mngr->partition_table->factory_config_end) + { + LOG_ERROR("Tag %08X Exceeds Length (%d)", tag, next_len); + retVal = TS_STATUS_ERROR; + break; + } + + //Read the CRC + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset + ntohl(next_len)), + (uint8_t*)&stored_crc, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to write tag %08x at offset 0x%x", tag, offset); + break; + } + + //Read Value and Compute CRC + while(next_len > 0) + { + uint32_t segment_len = next_len > sizeof(val_buffer) ? sizeof(val_buffer) : next_len; + next_len -= segment_len; + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start + offset), + val_buffer, segment_len); + if(retVal < 0) + { + LOG_ERROR("Failed to write value for tag %08x at offset 0x%x", tag, offset); + break; + } + offset += segment_len; + + //Calculate the CRC + new_crc = crc32(new_crc, val_buffer, segment_len); + } + + if(ntohl(stored_crc) != new_crc) + { + LOG_ERROR(); + retVal = TS_STATUS_ERROR; + break; + } + + // Increment size of CRC + offset += sizeof(uint32_t); + element_count++; + } + return retVal; } + +int32_t ts_fw_manager_factory_data_get_length(ts_fw_manager_t* mngr, uint32_t tag) +{ + int32_t retVal = TS_STATUS_OK; + uint32_t offset = 0; + uint32_t read_tag = INVALID_TAG; + uint32_t next_len = 0; + + // Search for a specific Tag and copy it to the provided Content buffer + while (offset < mngr->partition_table->factory_config_end) + { + //Read the TAG + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), (uint8_t*)&read_tag, sizeof(read_tag)); + if(retVal < 0) + { + LOG_ERROR("Failed to read tag at offset 0x%x", offset); + break; + } + + offset += sizeof(read_tag); + + // Check if empty, no more tags to try + if(read_tag == INVALID_TAG) + { + LOG_DEBUG("Tag 0x%08X not found in Factory data partition", tag); + retVal = 0; + break; + } + + //Read the Length + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), (uint8_t*)&next_len, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to write tag %08x at offset 0x%x", tag, offset); + break; + } + next_len = ntohl(next_len); + offset += sizeof(uint32_t); + + if((mngr->partition_table->factory_config_start + offset + next_len + sizeof(uint32_t)) > + mngr->partition_table->factory_config_end) + { + LOG_ERROR("Tag %08X Bad Length (%d)", tag, next_len); + retVal = TS_STATUS_ERROR; + break; + } + + //Tag matches, return length + if(ntohl(read_tag) == tag) + { + retVal = next_len; + break; + } + else + { + offset += (sizeof(uint32_t) + next_len); + } + } + return retVal; +} + +int32_t ts_fw_manager_factory_data_retreive(ts_fw_manager_t* mngr, uint32_t tag, uint8_t* content, uint32_t max_len) +{ + int32_t retVal = TS_STATUS_OK; + uint32_t offset = 0; + uint32_t check_offset = 0; + uint32_t read_tag = INVALID_TAG; + uint32_t next_len = 0; + uint32_t val_len = 0; + uint8_t val_buffer[4096]; + uint32_t stored_crc; + uint32_t new_crc = crc32(0, Z_NULL, 0); + + // Search for a specific Tag and copy it to the provided Content buffer + while (offset < mngr->partition_table->factory_config_end) + { + //Read the TAG + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), (uint8_t*)&read_tag, sizeof(read_tag)); + if(retVal < 0) + { + LOG_ERROR("Failed to read tag at offset 0x%x", offset); + break; + } + + offset += sizeof(read_tag); + + // Check if empty, no more tags to try + if(tag == INVALID_TAG) + { + LOG_DEBUG("Tag 0x%08X not found in Factory data partition", tag); + retVal = TS_STATUS_OK; + break; + } + + //Read the Length + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset), (uint8_t*)&next_len, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to read tag %08x length at offset 0x%x", ntohl(read_tag), offset); + break; + } + next_len = ntohl(next_len); + offset += sizeof(uint32_t); + + if((mngr->partition_table->factory_config_start + offset + next_len + sizeof(uint32_t)) > + mngr->partition_table->factory_config_end) + { + LOG_ERROR("Tag %08X Bad Length (%d)", ntohl(read_tag), next_len); + retVal = TS_STATUS_ERROR; + break; + } + + //Find next if tag doesn't match + if(ntohl(read_tag) != tag) + { + offset += (sizeof(uint32_t) + next_len); + continue; + } + + //Read the CRC + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start+offset + next_len), (uint8_t*)&stored_crc, sizeof(uint32_t)); + if(retVal < 0) + { + LOG_ERROR("Failed to write tag %08x at offset 0x%x", tag, offset); + break; + } + + //Read Value and Compute CRC + val_len = next_len; + check_offset = offset; + while(next_len > 0) + { + uint32_t segment_len = next_len > sizeof(val_buffer) ? sizeof(val_buffer) : next_len; + next_len -= segment_len; + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start + check_offset), val_buffer, segment_len); + if(retVal < 0) + { + LOG_ERROR("Failed to read value for tag %08x at offset 0x%x : %d", tag, offset, retVal); + break; + } + check_offset += segment_len; + + //Calculate the CRC + new_crc = crc32(new_crc, val_buffer, segment_len); + } + + if(ntohl(stored_crc) != new_crc) + { + LOG_ERROR("Failed to read Tag %08X, bad CRC (expected %08X, calculated %08X)", tag, + ntohl(stored_crc), new_crc); + retVal = TS_STATUS_ERROR; + break; + } + + //CRC Passes, read to user buffer + new_crc = crc32(0, Z_NULL, 0); + retVal = spiflash_read(&mngr->flash_dev, (mngr->partition_table->factory_config_start + offset), content, val_len); + if(retVal < 0) + { + LOG_ERROR("Failed to read value for tag %08x at offset 0x%x : %d", tag, offset, retVal); + break; + } + + //Calculate the CRC one last time + new_crc = crc32(new_crc, content, val_len); + + if(ntohl(stored_crc) == new_crc) + { + retVal = val_len; + } + else + { + LOG_ERROR("Failed CRC check for tag %08X (expected 0x%08X, calculated 0x%08X)", + tag, ntohl(stored_crc), new_crc); + retVal = TS_STATUS_ERROR; + } + break; + } + return retVal; +} \ No newline at end of file diff --git a/src/ts_fw_manager.h b/src/ts_fw_manager.h index 0d4e194..10bfdd2 100644 --- a/src/ts_fw_manager.h +++ b/src/ts_fw_manager.h @@ -16,9 +16,13 @@ extern "C" { #include "spiflash.h" #include "platform.h" +#include + typedef struct ts_fw_manager_s { spiflash_dev_t flash_dev; const flash_layout_t* partition_table; + _Atomic uint32_t fw_progress; + uint32_t fw_progress_max; } ts_fw_manager_t; /** @@ -43,37 +47,36 @@ int32_t ts_fw_manager_init(file_t fd, ts_fw_manager_t* mngr); int32_t ts_fw_manager_user_fw_update(ts_fw_manager_t* mngr, const char* file_stream, uint32_t len); /** - * @brief Read the User Calibration data from flash + * @brief Get the current progress of the firmware update * * @param mngr Pointer to a manager instance - * @param file_stream Pointer to a buffer to store the User Calibration data - * @param len Available space in the calibration data buffer + * @param progress Pointer to a variable to store the progress percentage * * @return TS_STATUS_OK on success. */ -int32_t ts_fw_manager_user_cal_get(ts_fw_manager_t* mngr, const char* file_stream, uint32_t max_len); +int32_t ts_fw_manager_get_progress(ts_fw_manager_t* mngr, uint32_t* progress); /** * @brief Load User Calibration data to flash * * @param mngr Pointer to a manager instance - * @param file_stream Pointer to a buffer containing User calibration data + * @param buffer Pointer to a buffer containing User data * @param len Length of the calibration data buffer * * @return TS_STATUS_OK on success. */ -int32_t ts_fw_manager_user_cal_update(ts_fw_manager_t* mngr, const char* file_stream, uint32_t len); +int32_t ts_fw_manager_user_data_write(ts_fw_manager_t* mngr, const char* buffer, uint32_t offset, uint32_t len); /** * @brief Save the Factory calibration data to a file * * @param mngr Pointer to a manager instance - * @param file_stream Pointer to a buffer to store the factory calibration + * @param buffer Pointer to a buffer to store the user data * @param max_len Available space in the buffer * * @return TS_STATUS_OK on success. */ -int32_t ts_fw_manager_factory_cal_get(ts_fw_manager_t* mngr, const char* file_stream, uint32_t max_len); +int32_t ts_fw_manager_user_data_read(ts_fw_manager_t* mngr, char* buffer, uint32_t offset, uint32_t max_len); /** * @brief Reset the user flash partitions with the Factory image @@ -86,6 +89,59 @@ int32_t ts_fw_manager_factory_cal_get(ts_fw_manager_t* mngr, const char* file_st */ int32_t ts_fw_manager_factory_reset(ts_fw_manager_t* mngr, bool reset_config, bool reset_bitstream); +/** + * @brief Erase the Factory Data partition + * + * @param mngr Pointer to a manager instance + * @param dna Unit PORT_DNA value + * + * @return int32_t TS_STATUS_SUCCESS if the factory partition is erased. + */ +int32_t ts_fw_manager_factory_data_erase(ts_fw_manager_t* mngr, uint64_t dna); + +/** + * @brief Append a TLV to the Factory data + * + * @param mngr Pointer to a manager instance + * @param tag 32-bit Tag + * @param length Length of the value data + * @param content Pointer to the TLV value data array + * + * @return + */ +int32_t ts_fw_manager_factory_data_append(ts_fw_manager_t* mngr, uint32_t tag, uint32_t length, const uint8_t *content); + + +/** + * @brief Retrieve the length of a specific Tag from the Factory data partition + * + * @param mngr Pointer to a manager instance + * @param tag Tag to retrieve the length of + * + * @return Length of the item if successful, zero if the item is not found, else TS_STATUS_ERROR + */ +int32_t ts_fw_manager_factory_data_get_length(ts_fw_manager_t* mngr, uint32_t tag); + +/** + * @brief Read every item and confirm good checksum for each value + * + * @param mngr Pointer to a manager instance + * + * @return TS_STATUS_OK if every item has a good checksum + */ +int32_t ts_fw_manager_factory_data_verify(ts_fw_manager_t* mngr); + +/** + * @brief Read a specific Tag from the Factory data partition + * + * @param mngr Pointer to a manager instance + * @param tag Tag to retrieve + * @param content Pointer to a buffer to store the TLV value + * @param max_len Not-to-exceed length of the content buffer + * + * @return Length of the content retrieved if successful, else TS_STATUS_ERROR + */ +int32_t ts_fw_manager_factory_data_retreive(ts_fw_manager_t* mngr, uint32_t tag, uint8_t* content, uint32_t max_len); #ifdef __cplusplus } diff --git a/src/util.h b/src/util.h index d63185d..478e00c 100644 --- a/src/util.h +++ b/src/util.h @@ -17,7 +17,9 @@ extern "C" { #include -#if !defined(_WIN32) +#if defined(__APPLE__) +#define INVALID_HANDLE_VALUE (IO_OBJECT_NULL) +#elif defined(__linux__) #define INVALID_HANDLE_VALUE (-1) #endif diff --git a/version.cmake b/version.cmake new file mode 100644 index 0000000..c0eb998 --- /dev/null +++ b/version.cmake @@ -0,0 +1,114 @@ +# +# This cmake module sets the project version and partial version +# variables by analysing the git tag and commit history. It expects git +# tags defined with semantic versioning 2.0.0 (http://semver.org/). +# +# The module expects the PROJECT_NAME variable to be set, and recognizes +# the GIT_FOUND, GIT_EXECUTABLE and VERSION_UPDATE_FROM_GIT variables. +# If Git is found and VERSION_UPDATE_FROM_GIT is set to boolean TRUE, +# the project version will be updated using information fetched from the +# most recent git tag and commit. Otherwise, the module will try to read +# a VERSION file containing the full and partial versions. The module +# will update this file each time the project version is updated. +# +# Once done, this module will define the following variables: +# +# ${PROJECT_NAME}_VERSION_STRING - Version string without metadata +# such as "v2.0.0" or "v1.2.41-beta.1". This should correspond to the +# most recent git tag. +# ${PROJECT_NAME}_VERSION_STRING_FULL - Version string with metadata +# such as "v2.0.0+3.a23fbc" or "v1.3.1-alpha.2+4.9c4fd1" +# ${PROJECT_NAME}_VERSION - Same as ${PROJECT_NAME}_VERSION_STRING, +# without the preceding 'v', e.g. "2.0.0" or "1.2.41-beta.1" +# ${PROJECT_NAME}_VERSION_MAJOR - Major version integer (e.g. 2 in v2.3.1-RC.2+21.ef12c8) +# ${PROJECT_NAME}_VERSION_MINOR - Minor version integer (e.g. 3 in v2.3.1-RC.2+21.ef12c8) +# ${PROJECT_NAME}_VERSION_PATCH - Patch version integer (e.g. 1 in v2.3.1-RC.2+21.ef12c8) +# ${PROJECT_NAME}_VERSION_TWEAK - Tweak version string (e.g. "RC.2" in v2.3.1-RC.2+21.ef12c8) +# ${PROJECT_NAME}_VERSION_AHEAD - How many commits ahead of last tag (e.g. 21 in v2.3.1-RC.2+21.ef12c8) +# ${PROJECT_NAME}_VERSION_GIT_SHA - The git sha1 of the most recent commit (e.g. the "ef12c8" in v2.3.1-RC.2+21.ef12c8) +# +# This module is public domain, use it as it fits you best. +# +# Author: Nuno Fachada + +find_package(Git) + +# Check if git is found... +if (GIT_FOUND) + + # Get last tag from git + execute_process(COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --tags + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE ${PROJECT_NAME}_VERSION_STRING + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE GIT_DESCRIBE_RESULT) + + if (NOT GIT_DESCRIBE_RESULT EQUAL 0) + set(${PROJECT_NAME}_VERSION_STRING "v0.0.0") + set(${PROJECT_NAME}_VERSION_MAJOR 0) + set(${PROJECT_NAME}_VERSION_MINOR 0) + set(${PROJECT_NAME}_VERSION_PATCH 0) + set(${PROJECT_NAME}_VERSION_TWEAK "") + set(${PROJECT_NAME}_VERSION_AHEAD 0) + set(${PROJECT_NAME}_VERSION_GIT_SHA "unknown") + else() + #How many commits since last tag + execute_process(COMMAND ${GIT_EXECUTABLE} rev-list main ${${PROJECT_NAME}_VERSION_STRING}^..HEAD --count + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE ${PROJECT_NAME}_VERSION_AHEAD + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE GIT_REVLIST_RESULT) + + if (NOT GIT_REVLIST_RESULT EQUAL 0) + set(${PROJECT_NAME}_VERSION_AHEAD 0) + endif() + + # Get current commit SHA from git + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE ${PROJECT_NAME}_VERSION_GIT_SHA + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # Get partial versions into a list + string(REGEX MATCHALL "-.*$|[0-9]+" ${PROJECT_NAME}_PARTIAL_VERSION_LIST + ${${PROJECT_NAME}_VERSION_STRING}) + + # Set the version numbers + list(GET ${PROJECT_NAME}_PARTIAL_VERSION_LIST + 0 ${PROJECT_NAME}_VERSION_MAJOR) + list(GET ${PROJECT_NAME}_PARTIAL_VERSION_LIST + 1 ${PROJECT_NAME}_VERSION_MINOR) + list(GET ${PROJECT_NAME}_PARTIAL_VERSION_LIST + 2 ${PROJECT_NAME}_VERSION_PATCH) + + # The tweak part is optional, so check if the list contains it + list(LENGTH ${PROJECT_NAME}_PARTIAL_VERSION_LIST + ${PROJECT_NAME}_PARTIAL_VERSION_LIST_LEN) + if (${PROJECT_NAME}_PARTIAL_VERSION_LIST_LEN GREATER 3) + list(GET ${PROJECT_NAME}_PARTIAL_VERSION_LIST 3 ${PROJECT_NAME}_VERSION_TWEAK) + string(SUBSTRING ${${PROJECT_NAME}_VERSION_TWEAK} 1 -1 ${PROJECT_NAME}_VERSION_TWEAK) + endif() + + # Unset the list + unset(${PROJECT_NAME}_PARTIAL_VERSION_LIST) + endif() + + # Set full project version string + set(${PROJECT_NAME}_VERSION_STRING_FULL + ${${PROJECT_NAME}_VERSION_STRING}+${${PROJECT_NAME}_VERSION_AHEAD}.${${PROJECT_NAME}_VERSION_GIT_SHA}) + +else() + + # Git not available, get version from file + set(${PROJECT_NAME}_VERSION_STRING_FULL "unknown-version") + +endif() + + +# Set project version (without the preceding 'v') +message(STATUS "Setting ${PROJECT_NAME} version to: ${${PROJECT_NAME}_VERSION_STRING_FULL}") +set(${PROJECT_NAME}_VERSION ${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}.${${PROJECT_NAME}_VERSION_PATCH}) +if (${PROJECT_NAME}_VERSION_TWEAK) + set(${PROJECT_NAME}_VERSION ${${PROJECT_NAME}_VERSION}-${${PROJECT_NAME}_VERSION_TWEAK}) +endif() \ No newline at end of file