From 82ff925001c214b9b341af5ae92c028087be45df Mon Sep 17 00:00:00 2001 From: sys64738 Date: Sun, 18 Jul 2021 22:03:58 +0200 Subject: [PATCH] SUMP logic analyzer mode, stolen from piocprobe-sump (mode 4) --- CMakeLists.txt | 7 +- LICENSE.picoprobe-sump | 34 ++ README.md | 2 + bsp/rp2040/m_sump/bsp-feature.h | 28 ++ bsp/rp2040/m_sump/sump_hw.c | 367 ++++++++++++++ bsp/rp2040/m_sump/sump_hw.h | 19 + dmctl2.py | 11 +- src/m_sump/_sump.c | 170 +++++++ src/m_sump/cdc_sump.c | 817 ++++++++++++++++++++++++++++++++ src/m_sump/sump.h | 88 ++++ src/modeset.c | 6 +- 11 files changed, 1539 insertions(+), 10 deletions(-) create mode 100644 LICENSE.picoprobe-sump create mode 100644 bsp/rp2040/m_sump/bsp-feature.h create mode 100644 bsp/rp2040/m_sump/sump_hw.c create mode 100644 bsp/rp2040/m_sump/sump_hw.h create mode 100644 src/m_sump/_sump.c create mode 100644 src/m_sump/cdc_sump.c create mode 100644 src/m_sump/sump.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 94a2332..c909e28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,11 +72,13 @@ target_sources(${PROJECT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/m_default/cdc_serprog.c ${CMAKE_CURRENT_SOURCE_DIR}/src/m_default/tempsensor.c ${CMAKE_CURRENT_SOURCE_DIR}/src/m_default/vnd_i2ctinyusb.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/m_sump/_sump.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/m_sump/cdc_sump.c ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/cdc_uart.c ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/i2c_tinyusb.c ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/spi_serprog.c ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/tempsensor.c -# ${CMAKE_CURRENT_SOURCE_DIR}/src/m_default2/0def.c + ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_sump/sump_hw.c ) if(USE_USBCDC_FOR_STDIO) target_sources(${PROJECT} PUBLIC @@ -100,7 +102,8 @@ add_custom_target(fix_db ALL WORKING_DIRECTORY ${OUTPUT_DIR} if(FAMILY STREQUAL "rp2040") target_link_libraries(${PROJECT} pico_stdlib pico_unique_id hardware_spi - pico_fix_rp2040_usb_device_enumeration hardware_i2c hardware_adc + hardware_i2c hardware_adc hardware_pio hardware_dma hardware_pwm + pico_fix_rp2040_usb_device_enumeration tinyusb_device tinyusb_board tinyusb_additions) if(USE_USBCDC_FOR_STDIO) diff --git a/LICENSE.picoprobe-sump b/LICENSE.picoprobe-sump new file mode 100644 index 0000000..9232656 --- /dev/null +++ b/LICENSE.picoprobe-sump @@ -0,0 +1,34 @@ +The below copyright and permission notice applies to portions of the following +files, which have been modified from their original versions in + (the "picoprobe-sump repository") + + - TODO + +The below notice does not apply to any modifications made to the above files +since the versions present in the picoprobe-usb repository, nor to any files +not present in the picoprobe-usb repository. + + + + +The MIT License (MIT) + +Copyright (c) 2021 Jaroslav Kysela + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index 47b87b7..9059edc 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,7 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC) - [ ] A name - [ ] A (VID and) PID, and maybe better subclass & protocol IDs for the vnd cfg itf +- [ ] More Pico SDK meta/buildinfo - [x] CMSIS-DAP JTAG implementation - [x] Flashrom/SPI support using Serprog - Parallel ROM flashing support, too, by having the device switch into a @@ -258,6 +259,7 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC) - [x] A proper interface for sending commands etc. instead of shoehorning it into Serprog. - Can probably be included in the "Better USB interface stuff". +- [ ] make modes persistent? - [ ] JTAG pinout detector - https://github.com/cyphunk/JTAGenum - https://github.com/travisgoodspeed/goodfet/blob/master/firmware/apps/jscan/jscan.c diff --git a/bsp/rp2040/m_sump/bsp-feature.h b/bsp/rp2040/m_sump/bsp-feature.h new file mode 100644 index 0000000..48a127e --- /dev/null +++ b/bsp/rp2040/m_sump/bsp-feature.h @@ -0,0 +1,28 @@ + +#ifndef BSP_FEATURE_M_SUMP_H_ +#define BSP_FEATURE_M_SUMP_H_ + +#define DBOARD_HAS_SUMP + +#include "bsp-info.h" + +enum { + HID_N__NITF = 0 +}; +enum { + CDC_N_SUMP = 0, +#ifdef USE_USBCDC_FOR_STDIO + CDC_N_STDIO, +#endif + + CDC_N__NITF +}; +enum { +#if CFG_TUD_VENDOR > 0 + VND_N_CFG = 0, +#endif + + VND_N__NITF +}; + +#endif diff --git a/bsp/rp2040/m_sump/sump_hw.c b/bsp/rp2040/m_sump/sump_hw.c new file mode 100644 index 0000000..676eb5e --- /dev/null +++ b/bsp/rp2040/m_sump/sump_hw.c @@ -0,0 +1,367 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bsp-info.h" +#include "m_sump/sump.h" + + +#include "m_sump/sump_hw.h" + +#define SAMPLING_GPIO_MASK (((1 << SAMPLING_BITS) - 1) << SAMPLING_GPIO_FIRST) + +#define SAMPLING_GPIO_TEST 22 + +#define SAMPLING_PIO pio1 +#define SAMPLING_PIO_SM 0u + +#define SAMPLING_DMA_IRQ DMA_IRQ_1 +#define sump_dma_ints (dma_hw->ints1) + +#define sump_dma_set_irq_channel_mask_enabled dma_set_irq1_channel_mask_enabled + +#define SUMP_SAMPLE_MASK ((1 << SAMPLING_BITS) - 1) +#define SUMP_BYTE0_OR ((~SUMP_SAMPLE_MASK) & 0xff) +#define SUMP_BYTE1_OR ((~SUMP_SAMPLE_MASK >> 8) & 0xff) + +#define SUMP_DMA_CH_FIRST 0 +#define SUMP_DMA_CH_LAST 7 +#define SUMP_DMA_CHANNELS (SUMP_DMA_CH_LAST - SUMP_DMA_CH_FIRST + 1) +#define SUMP_DMA_MASK (((1 << SUMP_DMA_CHANNELS) - 1) << SUMP_DMA_CH_FIRST) + +#define sump_irq_debug(format, ...) ((void)0) +#define picoprobe_info(format, ...) ((void)0) +#define picoprobe_debug(format, ...) ((void)0) +#define picoprobe_dump(format, ...) ((void)0) + +static uint16_t prog[2]; +// clang-format off +static const struct pio_program program = { + .instructions = prog, + .length = count_of(prog), + .origin = -1 +}; +// clang-format on + +static uint32_t pio_prog_offset; +static uint32_t dma_curr_idx = 0; +static uint32_t oldprio; + +uint32_t sump_hw_get_sysclk(void) { return clock_get_hz(clk_sys); } + +void sump_hw_get_cpu_name(char cpu[32]) { + snprintf(cpu, 32, INFO_BOARDNAME " @ %lu MHz", + sump_hw_get_sysclk() / (ONE_MHZ * SAMPLING_DIVIDER)); +} +void sump_hw_get_hw_name(char hw[32]) { + snprintf(hw, 32, INFO_BOARDNAME " rev%hhu, ROM v%hhu", rp2040_chip_version(), + rp2040_rom_version()); +} + +static void sump_pio_init(uint8_t width, bool nogr0) { + uint32_t gpio = SAMPLING_GPIO_FIRST; + +#if SAMPLING_BITS > 8 + if (width == 1 && nogr0) gpio += 8; +#endif + // loop the IN instruction forewer (8-bit and 16-bit version) + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_in_pins(&c, gpio); + uint32_t off = pio_prog_offset + (width - 1); + sm_config_set_wrap(&c, off, off); + + uint32_t divider = sump_calc_sysclk_divider(); + sm_config_set_clkdiv_int_frac(&c, divider >> 8, divider & 0xff); + sm_config_set_in_shift(&c, true, true, 32); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); + + pio_sm_init(SAMPLING_PIO, SAMPLING_PIO_SM, off, &c); + picoprobe_debug("%s(): pc=0x%02x [0x%02x], gpio=%u\n", __func__, off, pio_prog_offset, gpio); +} + +static void sump_pio_program(void) { + prog[0] = pio_encode_in(pio_pins, 8); + prog[1] = pio_encode_in(pio_pins, 16); + + picoprobe_debug("%s(): 0x%04x 0x%04x len=%u\n", __func__, prog[0], prog[1], program.length); + pio_prog_offset = pio_add_program(SAMPLING_PIO, &program); +} + +static uint32_t sump_pwm_slice_init(uint32_t gpio, uint32_t clock, bool swap_levels) { + uint32_t clksys = sump_hw_get_sysclk(); + uint16_t top = 5, level_a = 1, level_b = 4; + + // correction for low speed PWM + while ((clksys / clock / top) & ~0xff) { + top *= 1000; + level_a *= 1000; + level_b *= 1000; + } + + uint32_t clkdiv = clksys / clock / top; + + // pwm setup + uint32_t slice = pwm_gpio_to_slice_num(gpio); + gpio_set_function(gpio, GPIO_FUNC_PWM); + gpio_set_function(gpio + 1, GPIO_FUNC_PWM); + pwm_config c = pwm_get_default_config(); + pwm_config_set_wrap(&c, top - 1); + pwm_config_set_clkdiv_int(&c, clkdiv); + pwm_init(slice, &c, false); + + if (swap_levels) { + uint16_t tmp = level_a; + level_a = level_b; + level_b = tmp; + } + + pwm_set_both_levels(slice, level_a, level_b); + picoprobe_debug("%s(): gpio=%u clkdiv=%u top=%u level=%u/%u freq=%.4fMhz (req %.4fMhz)\n", + __func__, gpio, clkdiv, top, level_a, level_b, + (float)clksys / (float)clkdiv / (float)top / 1000000.0, (float)clock / 1000000.0); + + return 1u << slice; +} + +static uint32_t sump_calib_init(void) { + uint32_t clksys = sump_hw_get_sysclk(), clkdiv, slice; + const uint32_t clock = 5 * ONE_MHZ; + const uint16_t top = 10, level_a = 5; + + // set 5Mhz PWM on test pin + + // should not go beyond 255! + clkdiv = clksys / clock / top; + + // pwm setup + slice = pwm_gpio_to_slice_num(SAMPLING_GPIO_TEST); + gpio_set_function(SAMPLING_GPIO_TEST, GPIO_FUNC_PWM); + pwm_config c = pwm_get_default_config(); + pwm_config_set_wrap(&c, top - 1); + pwm_config_set_clkdiv_int(&c, clkdiv); + pwm_init(slice, &c, false); + pwm_set_both_levels(slice, level_a, level_a); + picoprobe_debug("%s(): gpio=%u clkdiv=%u top=%u level=%u/%u freq=%.4fMhz (req %.4fMhz)\n", + __func__, SAMPLING_GPIO_TEST, clkdiv, top, level_a, level_a, + (float)clksys / (float)clkdiv / (float)top / 1000000.0, (float)clock / 1000000.0); + return 1u << slice; +} + +static uint32_t sump_test_init(void) { + // Initialize test PWMs + const uint32_t gpio = SAMPLING_GPIO_FIRST; + uint32_t mask; + // 10Mhz PWM + mask = sump_pwm_slice_init(gpio, 10000000, false); + // 1Mhz PWM + mask |= sump_pwm_slice_init(gpio + 2, 1000000, false); + // 1kHz PWM + mask |= sump_pwm_slice_init(gpio + 4, 1000, false); +#if SAMPLING_BITS > 8 + // 1kHz PWM (second byte) + mask |= sump_pwm_slice_init(gpio + 8, 1000, true); +#endif + return mask; +} + +static void sump_test_done(void) { + const uint32_t gpio = SAMPLING_GPIO_FIRST; + + pwm_set_enabled(pwm_gpio_to_slice_num(gpio), false); + pwm_set_enabled(pwm_gpio_to_slice_num(gpio + 2), false); + pwm_set_enabled(pwm_gpio_to_slice_num(gpio + 4), false); +#if SAMPLING_BITS > 8 + pwm_set_enabled(pwm_gpio_to_slice_num(gpio + 8), false); +#endif + for (uint32_t i = SAMPLING_GPIO_FIRST; i <= SAMPLING_GPIO_LAST; i++) + gpio_set_function(i, GPIO_FUNC_NULL); + // test pin + pwm_set_enabled(SAMPLING_GPIO_TEST, false); +} + +static void sump_dma_chain_to_self(uint32_t ch) { + dma_channel_config cfg; + + ch += SUMP_DMA_CH_FIRST; + cfg = dma_get_channel_config(ch); + channel_config_set_chain_to(&cfg, ch); + dma_channel_set_config(ch, &cfg, false); +} + +void sump_hw_capture_setup_next( + uint32_t ch, uint32_t mask, uint32_t chunk_size, uint32_t next_count, uint8_t width) { + if ((next_count % chunk_size) == 0) { + ch = (mask + dma_curr_idx - 1) % SUMP_DMA_CHANNELS; + sump_dma_chain_to_self(ch); + ch = (ch + 1) % SUMP_DMA_CHANNELS; + } else { + ch = (mask + dma_curr_idx) % SUMP_DMA_CHANNELS; + dma_channel_set_trans_count( + ch + SUMP_DMA_CH_FIRST, (next_count % chunk_size) / width, false); + } + sump_irq_debug("%s(): %u: t=0x%08x\n", __func__, ch + SUMP_DMA_CH_FIRST, + (next_count % chunk_size) / width); + + // break chain, reset unused DMA chunks + // clear all chains for high-speed DMAs + mask = SUMP_DMA_CHANNELS - ((next_count + chunk_size - 1) / chunk_size); + while (mask > 0) { + sump_dma_chain_to_self(ch); + sump_irq_debug( + "%s(): %u -> %u\n", __func__, ch + SUMP_DMA_CH_FIRST, ch + SUMP_DMA_CH_FIRST); + ch = (ch + 1) % SUMP_DMA_CHANNELS; + mask--; + } +} +static void __isr sump_hw_dma_irq_handler(void) { + uint32_t loop = 0; + + while (1) { + uint32_t ch = SUMP_DMA_CH_FIRST + dma_curr_idx; + uint32_t mask = 1u << ch; + + if ((sump_dma_ints & mask) == 0) break; + + // acknowledge interrupt + sump_dma_ints = mask; + + dma_curr_idx = (dma_curr_idx + 1) % SUMP_DMA_CHANNELS; + + dma_channel_set_write_addr(ch, sump_capture_get_next_dest(SUMP_DMA_CHANNELS), false); + // sump_irq_debug("%s(): %u: w=0x%08x, state=%u\n", __func__, ch, sump_dma_get_next_dest(), + // sump.state); + + sump_capture_callback(ch, SUMP_DMA_CHANNELS); + + // are we slow? + if (++loop == SUMP_DMA_CHANNELS) { + sump_capture_callback_cancel(); + break; + } + } +} + +static void sump_dma_program( + uint32_t ch, uint32_t pos, uint8_t width, uint32_t chunk_size, uint8_t* destbuf) { + dma_channel_config cfg = dma_channel_get_default_config(SUMP_DMA_CH_FIRST + ch); + channel_config_set_read_increment(&cfg, false); + channel_config_set_write_increment(&cfg, true); + channel_config_set_dreq(&cfg, pio_get_dreq(SAMPLING_PIO, SAMPLING_PIO_SM, false)); + channel_config_set_chain_to(&cfg, SUMP_DMA_CH_FIRST + ((ch + 1) % SUMP_DMA_CHANNELS)); + channel_config_set_transfer_data_size(&cfg, width == 1 ? DMA_SIZE_8 : DMA_SIZE_16); + + dma_channel_configure(SUMP_DMA_CH_FIRST + ch, &cfg, destbuf + pos, + &SAMPLING_PIO->rxf[SAMPLING_PIO_SM], chunk_size / width, false); + + picoprobe_debug("%s() %u: w=0x%08x r=0x%08x t=0x%08x -> %u\n", __func__, SUMP_DMA_CH_FIRST + ch, + destbuf + pos, &SAMPLING_PIO->rxf[SAMPLING_PIO_SM], chunk_size / width, + SUMP_DMA_CH_FIRST + ((ch + 1) % SUMP_DMA_CHANNELS)); +} + +/*uint64_t*/ void sump_hw_capture_start( + uint8_t width, int flags, uint32_t chunk_size, uint8_t* destbuf) { + sump_pio_init(width, flags & SUMP_FLAG1_GR0_DISABLE); + + dma_curr_idx = 0; + + uint32_t pwm_mask = sump_calib_init(); + if (flags & SUMP_FLAG1_EXT_TEST) { + pwm_mask |= sump_test_init(); + } else { + sump_test_done(); + } + + for (uint32_t i = 0; i < SUMP_DMA_CHANNELS; i++) + sump_dma_program(i, i * chunk_size, width, chunk_size, destbuf); + + // let's go + uint32_t irq_state = save_and_disable_interrupts(); + pio_sm_set_enabled(SAMPLING_PIO, SAMPLING_PIO_SM, true); + + if (pwm_mask) pwm_set_mask_enabled(pwm_mask); + + dma_channel_start(SUMP_DMA_CH_FIRST); + irq_set_enabled(SAMPLING_DMA_IRQ, true); + restore_interrupts(irq_state); + + // return time_us_64(); +} +void sump_hw_capture_stop(void) { + pio_sm_set_enabled(SAMPLING_PIO, SAMPLING_PIO_SM, false); + irq_set_enabled(SAMPLING_DMA_IRQ, false); +} + +void sump_hw_init(void) { + // claim DMA channels + dma_claim_mask(SUMP_DMA_MASK); + + // claim PIO state machine and add program + pio_claim_sm_mask(SAMPLING_PIO, 1u << SAMPLING_PIO_SM); + sump_pio_program(); + + // high bus priority to the DMA + oldprio = bus_ctrl_hw->priority; + bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS; + + // GPIO init + gpio_set_dir_in_masked(SAMPLING_GPIO_MASK); + gpio_put_masked(SAMPLING_GPIO_MASK, 0); + for (uint32_t i = SAMPLING_GPIO_FIRST; i <= SAMPLING_GPIO_LAST; i++) { + gpio_set_function(i, GPIO_FUNC_NULL); + gpio_set_pulls(i, false, false); + } + + // test GPIO pin + gpio_set_dir(SAMPLING_GPIO_TEST, true); + gpio_put(SAMPLING_GPIO_TEST, true); + gpio_set_function(SAMPLING_GPIO_TEST, GPIO_FUNC_PWM); + + // set exclusive interrupt handler + irq_set_enabled(SAMPLING_DMA_IRQ, false); + irq_set_exclusive_handler(SAMPLING_DMA_IRQ, sump_hw_dma_irq_handler); + sump_dma_set_irq_channel_mask_enabled(SUMP_DMA_MASK, true); +} + +void sump_hw_stop(void) { + // IRQ and PIO fast stop + irq_set_enabled(SAMPLING_DMA_IRQ, false); + pio_sm_set_enabled(SAMPLING_PIO, SAMPLING_PIO_SM, false); + + // DMA abort + for (uint32_t i = SUMP_DMA_CH_FIRST; i <= SUMP_DMA_CH_LAST; i++) dma_channel_abort(i); + + // IRQ status cleanup + sump_dma_ints = SUMP_DMA_MASK; + + // PIO cleanup + pio_sm_clear_fifos(SAMPLING_PIO, SAMPLING_PIO_SM); + pio_sm_restart(SAMPLING_PIO, SAMPLING_PIO_SM); + + // test + sump_test_done(); +} + +void sump_hw_deinit(void) { + sump_hw_stop(); + + sump_dma_set_irq_channel_mask_enabled(SUMP_DMA_MASK, false); + + gpio_set_dir(SAMPLING_GPIO_TEST, false); + gpio_set_function(SAMPLING_GPIO_TEST, GPIO_FUNC_NULL); + + bus_ctrl_hw->priority = oldprio; + + pio_remove_program(SAMPLING_PIO, &program, pio_prog_offset); + pio_sm_unclaim(SAMPLING_PIO, SAMPLING_PIO_SM); + + for (uint32_t i = SUMP_DMA_CH_FIRST; i <= SUMP_DMA_CH_LAST; ++i) dma_channel_unclaim(i); +} + diff --git a/bsp/rp2040/m_sump/sump_hw.h b/bsp/rp2040/m_sump/sump_hw.h new file mode 100644 index 0000000..3904d3c --- /dev/null +++ b/bsp/rp2040/m_sump/sump_hw.h @@ -0,0 +1,19 @@ +#ifndef BSP_SUMP_HW_PICO_H +#define BSP_SUMP_HW_PICO_H + +#define SAMPLING_DIVIDER 4 // minimal sysclk sampling divider + +#define SAMPLING_GPIO_FIRST 6 +#define SAMPLING_GPIO_LAST 21 + +#define SAMPLING_BITS (SAMPLING_GPIO_LAST-SAMPLING_GPIO_FIRST+1) +#define SAMPLING_BYTES ((SAMPLING_BITS+7)/8) + +#if PICO_NO_FLASH +#define SUMP_MEMORY_SIZE 102400 // 100kB +#else +#define SUMP_MEMORY_SIZE 204800 // 200kB +#endif +#define SUMP_MAX_CHUNK_SIZE 4096 + +#endif diff --git a/dmctl2.py b/dmctl2.py index a258b22..36d237f 100755 --- a/dmctl2.py +++ b/dmctl2.py @@ -87,25 +87,24 @@ epout.write(b'\x12') # get mode1 features print("stat=%d"%stat) print(res) -print("echo time!") -epout.write(b'\x14\x00\x42') # I2C echo! +epout.write(b'\x40') # get mode4 name (stat, res) = rdresp(epin) print("stat=%d"%stat) print(res) -epout.write(b'\x1f\x14\x00\x43') +epout.write(b'\x41') # get mode4 version (stat, res) = rdresp(epin) print("stat=%d"%stat) print(res) -epout.write(b'\x14\x00\x44') # I2C echo! +epout.write(b'\x42') # get mode4 features (stat, res) = rdresp(epin) print("stat=%d"%stat) print(res) ### ATTEMPT A MODESET ### -#epout.write(b'\x03\x02') # set cur mode -#print('[%s]'%(', '.join(hex(x) for x in epin.read(3)))) # result: status, payload len, mode +epout.write(b'\x03\x04') # set cur mode +print('[%s]'%(', '.join(hex(x) for x in epin.read(3)))) # result: status, payload len, mode diff --git a/src/m_sump/_sump.c b/src/m_sump/_sump.c new file mode 100644 index 0000000..0bb181f --- /dev/null +++ b/src/m_sump/_sump.c @@ -0,0 +1,170 @@ +// vim: set et: + +#include + +#include "mode.h" +#include "thread.h" +#include "vnd_cfg.h" + +#include "m_sump/bsp-feature.h" + +/* CDC SUMP */ +#include "m_sump/sump.h" + +enum m_default_feature { + msump_feat_sump = 1<<0, +}; + +#ifdef DBOARD_HAS_SUMP +static cothread_t sumpthread; +static uint8_t sumpstack[THREAD_STACK_SIZE]; + +static void sump_thread_fn(void) { + cdc_sump_init(); + thread_yield(); + while (1) { + cdc_sump_task(); + thread_yield(); + } +} +#endif + +void stdio_usb_set_itf_num(int itf); // TODO: move to a header! + +static void enter_cb(void) { + stdio_usb_set_itf_num(CDC_N_STDIO); + vnd_cfg_set_itf_num(VND_N_CFG); + +#ifdef DBOARD_HAS_SUMP + sumpthread = co_derive(sumpstack, sizeof sumpstack, sump_thread_fn); + thread_enter(sumpthread); +#endif +} +static void leave_cb(void) { +#ifdef DBOARD_HAS_SUMP + cdc_sump_deinit(); +#endif +} + +static void task_cb(void) { +#ifdef DBOARD_HAS_SUMP + tud_task(); + thread_enter(sumpthread); +#endif +} + +static void handle_cmd_cb(uint8_t cmd) { + uint8_t resp = 0; + + switch (cmd) { + case mode_cmd_get_features: +#ifdef DBOARD_HAS_SUMP + resp |= msump_feat_sump; +#endif + vnd_cfg_write_resp(cfg_resp_ok, 1, &resp); + break; + default: + vnd_cfg_write_strf(cfg_resp_illcmd, "unknown mode4 command %02x", cmd); + break; + } +} + +enum { + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + + STRID_CONFIG, + + STRID_IF_VND_CFG, + STRID_IF_CDC_SUMP, + STRID_IF_CDC_STDIO, +}; +enum { +#if CFG_TUD_VENDOR > 0 + ITF_NUM_VND_CFG, +#endif +#ifdef DBOARD_HAS_SUMP + ITF_NUM_CDC_SUMP_COM, + ITF_NUM_CDC_SUMP_DATA, +#endif +#ifdef USE_USBCDC_FOR_STDIO + ITF_NUM_CDC_STDIO_COM, + ITF_NUM_CDC_STDIO_DATA, +#endif + + ITF_NUM__TOTAL +}; +enum { + CONFIG_TOTAL_LEN + = TUD_CONFIG_DESC_LEN +#if CFG_TUD_VENDOR > 0 + + TUD_VENDOR_DESC_LEN +#endif +#ifdef DBOARD_HAS_SUMP + + TUD_CDC_DESC_LEN +#endif +#ifdef USE_USBCDC_FOR_STDIO + + TUD_CDC_DESC_LEN +#endif +}; + +#define EPNUM_VND_CFG_OUT 0x01 +#define EPNUM_VND_CFG_IN 0x81 +#define EPNUM_CDC_SUMP_OUT 0x02 +#define EPNUM_CDC_SUMP_IN 0x82 +#define EPNUM_CDC_SUMP_NOTIF 0x83 +#define EPNUM_CDC_STDIO_OUT 0x04 +#define EPNUM_CDC_STDIO_IN 0x84 +#define EPNUM_CDC_STDIO_NOTIF 0x85 + +// clang-format off +// TODO: replace magic 64s by actual buffer size macros +static const uint8_t desc_configuration[] = { + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM__TOTAL, STRID_CONFIG, CONFIG_TOTAL_LEN, + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + +#if CFG_TUD_VENDOR > 0 + TUD_VENDOR_DESCRIPTOR_EX(ITF_NUM_VND_CFG, STRID_IF_VND_CFG, EPNUM_VND_CFG_OUT, + EPNUM_VND_CFG_IN, CFG_TUD_VENDOR_RX_BUFSIZE, VND_CFG_SUBCLASS, VND_CFG_PROTOCOL), +#endif + +#ifdef DBOARD_HAS_SUMP + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_SUMP_COM, STRID_IF_CDC_SUMP, EPNUM_CDC_SUMP_NOTIF, + CFG_TUD_CDC_RX_BUFSIZE, EPNUM_CDC_SUMP_OUT, EPNUM_CDC_SUMP_IN, CFG_TUD_CDC_RX_BUFSIZE), +#endif + +#ifdef USE_USBCDC_FOR_STDIO + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_STDIO_COM, STRID_IF_CDC_STDIO, EPNUM_CDC_STDIO_NOTIF, + CFG_TUD_CDC_RX_BUFSIZE, EPNUM_CDC_STDIO_OUT, EPNUM_CDC_STDIO_IN, CFG_TUD_CDC_RX_BUFSIZE), +#endif +}; +static const char* string_desc_arr[] = { + NULL, + + [STRID_CONFIG] = "Configuration descriptor", + // max string length check: ||||||||||||||||||||||||||||||| + [STRID_IF_VND_CFG ] = "Device cfg/ctl interface", + [STRID_IF_CDC_SUMP ] = "SUMP LA CDC interface", + [STRID_IF_CDC_STDIO] = "stdio CDC interface (debug)", +}; +// clang-format on + +extern struct mode m_04_sump; +// clang-format off +struct mode m_04_sump = { + .name = "SUMP logic analyzer mode", + .version = 0x0010, + .n_string_desc = sizeof(string_desc_arr)/sizeof(string_desc_arr[0]), + + .usb_desc = desc_configuration, + .string_desc = string_desc_arr, + + .enter = enter_cb, + .leave = leave_cb, + .task = task_cb, + .handle_cmd = handle_cmd_cb, +}; +// clang-format on + diff --git a/src/m_sump/cdc_sump.c b/src/m_sump/cdc_sump.c new file mode 100644 index 0000000..e4c5e29 --- /dev/null +++ b/src/m_sump/cdc_sump.c @@ -0,0 +1,817 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021 Jaroslav Kysela + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * + * Protocol link: https://www.sump.org/projects/analyzer/protocol + * + */ + +#include +#include + +#include "info.h" +#include "m_sump/bsp-feature.h" +#include "m_sump/sump.h" +#include "m_sump/sump_hw.h" + +#define picoprobe_debug(format, ...) ((void)0) +#define picoprobe_info(format, ...) ((void)0) + +#define CDC_INTF CDC_N_SUMP + +#if SAMPLING_BITS != 8 && SAMPLING_BITS != 16 +#error "Correct sampling width (8 or 16 bits)" +#endif + +#if (SUMP_MEMORY_SIZE % SUMP_MAX_CHUNK_SIZE) != 0 +#error "Invalid maximal chunk size!" +#endif + +#if (SUMP_MEMORY_SIZE / SUMP_MAX_CHUNK_SIZE) < SUMP_DMA_CHANNELS +#error "DMA buffer and DMA channels out of sync!" +#endif + +#define SUMP_STATE_CONFIG 0 +#define SUMP_STATE_INIT 1 +#define SUMP_STATE_TRIGGER 2 +#define SUMP_STATE_SAMPLING 3 +#define SUMP_STATE_DUMP 4 +#define SUMP_STATE_ERROR 5 + +#define AS_16P(a) (*(uint16_t*)(a)) + +struct _trigger { + uint32_t mask; + uint32_t value; + uint16_t delay; + uint8_t channel; + uint8_t level; + bool serial; + bool start; +}; + +static struct _sump { + /* internal states */ + bool cdc_connected; + uint8_t cmd[5]; // command + uint8_t cmd_pos; // command buffer position + uint8_t state; // SUMP_STATE_* + uint8_t width; // in bytes, 1 = 8 bits, 2 = 16 bits + uint8_t trigger_index; + // uint32_t pio_prog_offset; + uint32_t read_start; + // uint64_t timestamp_start; + + /* protocol config */ + uint32_t divider; // clock divider + uint32_t read_count; + uint32_t delay_count; + uint32_t flags; + + struct _trigger trigger[4]; + + /* DMA buffer */ + uint32_t chunk_size; // in bytes + uint32_t dma_start; + uint32_t dma_count; + // uint32_t dma_curr_idx; // current DMA channel (index) + uint32_t dma_pos; + uint32_t next_count; + uint8_t buffer[SUMP_MEMORY_SIZE]; +} sump; + +/* utility functions ======================================================= */ + +/*static void picoprobe_debug_hexa(uint8_t *buf, uint32_t len) { + uint32_t l; + for (l = 0; len > 0; len--, l++) { + if (l != 0) + putchar(':'); + printf("%02x", *buf++); + } +}*/ + +static uint8_t* sump_add_metas(uint8_t* buf, uint8_t tag, const char* str) { + *buf++ = tag; + while (*str) *buf++ = (uint8_t)(*str++); + *buf++ = '\0'; + + return buf; +} + +static uint8_t* sump_add_meta1(uint8_t* buf, uint8_t tag, uint8_t val) { + buf[0] = tag; + buf[1] = val; + return buf + 2; +} + +static uint8_t* sump_add_meta4(uint8_t* buf, uint8_t tag, uint32_t val) { + buf[0] = tag; + // this is a bit weird, but libsigrok decodes Big-Endian words here + // the commands use Little-Endian +#if 0 + buf[1] = val; + buf[2] = val >> 8; + buf[3] = val >> 16; + buf[4] = val >> 24; +#else + buf[1] = val >> 24; + buf[2] = val >> 16; + buf[3] = val >> 8; + buf[4] = val; +#endif + return buf + 5; +} + +static void* sump_analyze_trigger8(void* ptr, uint32_t size) { + (void)size; + + uint8_t* src = ptr; + uint8_t tmask = sump.trigger[sump.trigger_index].mask; + uint8_t tvalue = sump.trigger[sump.trigger_index].value; + uint32_t count = sump.chunk_size; + + for (count = sump.chunk_size; count > 0; count--) { + uint32_t v = *src++; + if ((v & tmask) != tvalue) continue; + + while (1) { + if (sump.trigger[sump.trigger_index].start) return src; + + sump.trigger_index++; + tmask = sump.trigger[sump.trigger_index].mask; + tvalue = sump.trigger[sump.trigger_index].value; + + if (tmask != 0 || tvalue != 0) break; + } + } + return NULL; +} + +static void* sump_analyze_trigger16(void* ptr, uint32_t size) { + (void)size; + + uint16_t* src = ptr; + uint16_t tmask = sump.trigger[0].mask; + uint16_t tvalue = sump.trigger[0].value; + uint32_t count = sump.chunk_size; + + for (count = sump.chunk_size / 2; count > 0; count--) { + uint32_t v = *src++; + if ((v & tmask) != tvalue) continue; + + while (1) { + if (sump.trigger[sump.trigger_index].start) return src; + + sump.trigger_index++; + tmask = sump.trigger[sump.trigger_index].mask; + tvalue = sump.trigger[sump.trigger_index].value; + + if (tmask != 0 || tvalue != 0) break; + } + } + return NULL; +} + +static void* sump_analyze_trigger(void* ptr, uint32_t size) { + if (sump.width == 1) + return sump_analyze_trigger8(ptr, size); + else + return sump_analyze_trigger16(ptr, size); +} + +uint32_t sump_calc_sysclk_divider() { + const uint32_t common_divisor = 4; + uint32_t divider = sump.divider; + + if (divider > 65535) divider = 65535; + + // return the fractional part in lowest byte (8 bits) + if (sump.flags & SUMP_FLAG1_DDR) { + // 125Mhz support + divider *= 128 / common_divisor; + } else { + divider *= 256 / common_divisor; + } + + uint32_t v = sump_hw_get_sysclk(); + assert((v % ONE_MHZ) == 0); + // conversion from 100Mhz to sysclk + v = ((v / ONE_MHZ) * divider) / ((100 / common_divisor) * SAMPLING_DIVIDER); + v *= sump.width; + + if (v > 65535 * 256) + v = 65535 * 256; + else if (v <= 255) + v = 256; + + picoprobe_debug("%s(): %u %u -> %u (%.4f)\n", __func__, sump_hw_get_sysclk(), sump.divider, v, + (float)v / 256.0); + return v; +} + +static void sump_set_chunk_size(void) { + uint32_t clk_hz = sump_hw_get_sysclk() / (sump_calc_sysclk_divider() / 256); + // the goal is to transfer around 125 DMA chunks per second + // for slow sampling rates + sump.chunk_size = 1; + + while (clk_hz > 125 && sump.chunk_size < SUMP_MAX_CHUNK_SIZE) { + sump.chunk_size *= 2; + clk_hz /= 2; + } + + picoprobe_debug("%s(): 0x%04x\n", __func__, sump.chunk_size); +} + +/* data capture ============================================================ */ + +static void sump_capture_done(void) { + sump_hw_capture_stop(); + /*uint64_t us = time_us_64() - sump.timestamp_start; + picoprobe_debug("%s(): sampling time = %llu.%llu\n", __func__, us / 1000000ull, us % + 1000000ull);*/ + sump.state = SUMP_STATE_DUMP; +} + +static int sump_capture_next(uint32_t pos) { + if (sump.state != SUMP_STATE_TRIGGER) { + sump_capture_done(); + return 0; + } + + // waiting for the trigger samples + uint8_t* ptr = sump_analyze_trigger(sump.buffer + pos, sump.chunk_size); + if (ptr == NULL) { + // call this routine again right after next chunk + return sump.chunk_size; + } + + sump.state = SUMP_STATE_SAMPLING; + + // calculate read start + uint32_t tmp = (sump.read_count - sump.delay_count) * sump.width; + pos = ptr - sump.buffer; + sump.read_start = (pos - tmp) % SUMP_MEMORY_SIZE; + + // calculate the samples after trigger + uint32_t delay_bytes = sump.delay_count * sump.width; + tmp = sump.chunk_size - (pos % sump.chunk_size); + if (tmp >= delay_bytes) { + sump_capture_done(); + return 0; + } + return delay_bytes - tmp; +} + +uint8_t* sump_capture_get_next_dest(uint32_t numch) { + return sump.buffer + (sump.dma_pos + numch * sump.chunk_size) % SUMP_MEMORY_SIZE; +} + +void sump_capture_callback_cancel(void) { + sump_capture_done(); + sump.state = SUMP_STATE_ERROR; +} + +void sump_capture_callback(uint32_t ch, uint32_t numch) { + // reprogram the current DMA channel to the tail + if (sump.next_count <= sump.chunk_size) { + sump.next_count = sump_capture_next(sump.dma_pos); + if (sump.state == SUMP_STATE_DUMP) return; + } else { + sump.next_count -= sump.chunk_size; + } + // sump_irq_debug("%s(): next=0x%x\n", __func__, sump.next_count); + + sump.dma_pos += sump.chunk_size; + sump.dma_pos %= SUMP_MEMORY_SIZE; + + if (sump.state == SUMP_STATE_SAMPLING && sump.next_count >= sump.chunk_size && + sump.next_count < numch * sump.chunk_size) { + // set the last DMA segment to correct size to avoid overwrites + uint32_t mask = sump.next_count / sump.chunk_size; + + sump_hw_capture_setup_next(ch, mask, sump.chunk_size, sump.next_count, sump.width); + } +} + +/* --- */ + +static void sump_xfer_start(uint8_t state) { + sump.dma_start = 0; + sump.dma_pos = 0; + + picoprobe_debug("%s(): read=0x%08x delay=0x%08x divider=%u\n", __func__, sump.read_count, + sump.delay_count, sump.divider); + + uint32_t count = sump.read_count; + if (count > SUMP_MEMORY_SIZE) count = SUMP_MEMORY_SIZE; + sump.dma_count = count; + + if (sump.read_count <= sump.delay_count) + sump.next_count = sump.read_count; + else + sump.next_count = sump.read_count - sump.delay_count; + sump.next_count *= sump.width; + sump.read_start = 0; + + picoprobe_debug("%s(): buffer = 0x%08x, dma_count=0x%08x next_count=0x%08x\n", __func__, + sump.buffer, sump.dma_count, sump.next_count); + + // limit chunk size for slow sampling + sump_set_chunk_size(); + + /*sump.timestamp_start =*/sump_hw_capture_start( + sump.width, sump.flags, sump.chunk_size, sump.buffer); + + sump.state = state; +} + +/* SUMP proto command handling ============================================= */ + +static void sump_do_meta(void) { + char cpu[32]; + uint8_t buf[128], *ptr = buf, *wptr = buf; + + ptr = sump_add_metas(ptr, SUMP_META_NAME, INFO_PRODUCT_BARE " Logic Analyzer v1"); + sump_hw_get_hw_name(cpu); + ptr = sump_add_metas(ptr, SUMP_META_FPGA_VERSION, cpu); + sump_hw_get_cpu_name(cpu); + ptr = sump_add_metas(ptr, SUMP_META_CPU_VERSION, cpu); + ptr = sump_add_meta4(ptr, SUMP_META_SAMPLE_RATE, sump_hw_get_sysclk() / SAMPLING_DIVIDER); + ptr = sump_add_meta4(ptr, SUMP_META_SAMPLE_RAM, SUMP_MEMORY_SIZE); + ptr = sump_add_meta1(ptr, SUMP_META_PROBES_B, SAMPLING_BITS); + ptr = sump_add_meta1(ptr, SUMP_META_PROTOCOL_B, 2); + *ptr++ = SUMP_META_END; + + assert(ptr < &buf[128] && "Stack overflow! aaaa!"); + + while (wptr != ptr) wptr += tud_cdc_n_write(CDC_INTF, wptr, ptr - wptr); + + tud_cdc_n_write_flush(CDC_INTF); +} + +static void sump_do_id(void) { + tud_cdc_n_write_str(CDC_INTF, "1ALS"); + tud_cdc_n_write_flush(CDC_INTF); +} + +static void sump_do_run(void) { + uint8_t state; + uint32_t tmask = 0; + bool tstart = false; + + if (sump.width == 0) { + // invalid config, dump something nice + sump.state = SUMP_STATE_DUMP; + return; + } + + for (uint32_t i = 0; i < count_of(sump.trigger); i++) { + tstart |= sump.trigger[i].start; + tmask |= sump.trigger[i].mask; + } + + if (tstart && tmask) { + state = SUMP_STATE_TRIGGER; + sump.trigger_index = 0; + } else { + state = SUMP_STATE_SAMPLING; + } + + sump_xfer_start(state); +} + +static void sump_do_finish(void) { + if (sump.state == SUMP_STATE_TRIGGER || sump.state == SUMP_STATE_SAMPLING) { + sump.state = SUMP_STATE_DUMP; + sump_capture_done(); + return; + } +} + +static void sump_do_stop(void) { + if (sump.state == SUMP_STATE_INIT) return; + + sump_hw_stop(); + + // protocol state + sump.state = SUMP_STATE_INIT; +} + +static void sump_do_reset(void) { + sump_do_stop(); + memset(&sump.trigger, 0, sizeof(sump.trigger)); +} + +static void sump_set_flags(uint32_t flags) { + uint32_t width = 2; + sump.flags = flags; + + if (flags & SUMP_FLAG1_GR0_DISABLE) width--; + if (flags & SUMP_FLAG1_GR1_DISABLE) width--; + // we don't support 24-bit or 32-bit capture - sorry + if ((flags & SUMP_FLAG1_GR2_DISABLE) == 0) width = 0; + if ((flags & SUMP_FLAG1_GR3_DISABLE) == 0) width = 0; + + picoprobe_debug("%s(): sample %u bytes\n", __func__, width); + + sump.width = width; +} + +static void sump_update_counts(uint32_t val) { + /* + * This just sets up how many samples there should be before + * and after the trigger fires. The read_count is total samples + * to return and delay_count number of samples after + * the trigger. + * + * This sets the buffer splits like 0/100, 25/75, 50/50 + * for example if read_count == delay_count then we should + * return all samples starting from the trigger point. + * If delay_count < read_count we return + * (read_count - delay_count) of samples from before + * the trigger fired. + */ + + uint32_t read_count = ((val & 0xffff) + 1) * 4; + uint32_t delay_count = ((val >> 16) + 1) * 4; + + if (delay_count > read_count) read_count = delay_count; + + sump.read_count = read_count; + sump.delay_count = delay_count; +} + +static void sump_set_trigger_mask(uint32_t trig, uint32_t val) { + struct _trigger* t = &sump.trigger[trig]; + t->mask = val; + + picoprobe_debug("%s(): idx=%u val=0x%08x\n", __func__, trig, val); +} + +static void sump_set_trigger_value(uint32_t trig, uint32_t val) { + struct _trigger* t = &sump.trigger[trig]; + t->value = val; + + picoprobe_debug("%s(): idx=%u val=0x%08x\n", __func__, trig, val); +} + +static void sump_set_trigger_config(uint32_t trig, uint32_t val) { + struct _trigger* t = &sump.trigger[trig]; + t->start = (val & 0x08000000) != 0; + t->serial = (val & 0x02000000) != 0; + t->channel = ((val >> 20) & 0x0f) | ((val >> (24 - 4)) & 0x10); + t->level = (val >> 16) & 3; + t->delay = val & 0xffff; + + picoprobe_debug("%s(): idx=%u val=0x%08x (start=%u serial=%u channel=%u level=%u delay=%u)\n", + __func__, trig, val, t->start, t->serial, t->channel, t->level, t->delay); +} + +/* UART protocol handling ================================================== */ + +static void sump_rx_short(uint8_t cmd) { + picoprobe_debug("%s(): 0x%02x\n", __func__, cmd); + + switch (cmd) { + case SUMP_CMD_RESET: sump_do_reset(); break; + case SUMP_CMD_ARM: sump_do_run(); break; + case SUMP_CMD_ID: sump_do_id(); break; + case SUMP_CMD_META: sump_do_meta(); break; + case SUMP_CMD_FINISH: sump_do_finish(); break; + case SUMP_CMD_QUERY_INPUT: break; + case SUMP_CMD_ADVANCED_ARM: sump_do_run(); break; + default: break; + } +} + +static void sump_rx_long(uint8_t* cmd) { + uint32_t val = cmd[1] | (cmd[2] << 8) | (cmd[3] << 16) | (cmd[4] << 24); + + picoprobe_debug("%s(): [0x%02x] 0x%08x\n", __func__, cmd[0], val); + + switch (cmd[0]) { + case SUMP_CMD_SET_SAMPLE_RATE: + sump_do_stop(); + sump.divider = val + 1; + break; + case SUMP_CMD_SET_COUNTS: + sump_do_stop(); + sump_update_counts(val); + break; + case SUMP_CMD_SET_FLAGS: + sump_do_stop(); + sump_set_flags(val); + break; + case SUMP_CMD_SET_ADV_TRG_SELECT: + case SUMP_CMD_SET_ADV_TRG_DATA: break; /* not implemented */ + + case SUMP_CMD_SET_BTRG0_MASK: + case SUMP_CMD_SET_BTRG1_MASK: + case SUMP_CMD_SET_BTRG2_MASK: + case SUMP_CMD_SET_BTRG3_MASK: + sump_set_trigger_mask((cmd[0] - SUMP_CMD_SET_BTRG0_MASK) / 3, val); + break; + + case SUMP_CMD_SET_BTRG0_VALUE: + case SUMP_CMD_SET_BTRG1_VALUE: + case SUMP_CMD_SET_BTRG2_VALUE: + case SUMP_CMD_SET_BTRG3_VALUE: + sump_set_trigger_value((cmd[0] - SUMP_CMD_SET_BTRG0_VALUE) / 3, val); + break; + + case SUMP_CMD_SET_BTRG0_CONFIG: + case SUMP_CMD_SET_BTRG1_CONFIG: + case SUMP_CMD_SET_BTRG2_CONFIG: + case SUMP_CMD_SET_BTRG3_CONFIG: + sump_set_trigger_config((cmd[0] - SUMP_CMD_SET_BTRG0_CONFIG) / 3, val); + break; + default: return; + } +} + +static void sump_rx(uint8_t* buf, uint32_t count) { + if (count == 0) return; +#if 0 + picoprobe_debug("%s(): ", __func__); + picoprobe_debug_hexa(buf, count); + picoprobe_debug("\n"); +#endif + + while (count-- > 0) { + sump.cmd[sump.cmd_pos++] = *buf++; + + if (SUMP_CMD_IS_SHORT(sump.cmd[0])) { + sump_rx_short(sump.cmd[0]); + sump.cmd_pos = 0; + } else if (sump.cmd_pos >= 5) { + sump_rx_long(sump.cmd); + sump.cmd_pos = 0; + } + } +} + +static uint32_t sump_tx_empty(uint8_t* buf, uint32_t len) { + uint32_t i; + + uint32_t count = sump.read_count; + // picoprobe_debug("%s: count=%u\n", __func__, count); + uint8_t a = 0x55; + + if (sump.flags & SUMP_FLAG1_ENABLE_RLE) { + count += count & 1; // align up + if (sump.width == 1) { + for (i = 0; i < len && count > 0; count -= 2, i += 2) { + *buf++ = 0x81; // RLE mark + two samples + *buf++ = a; + a ^= 0xff; + } + if (i > sump.read_count) + sump.read_count = 0; + else + sump.read_count -= i; + } else if (sump.width == 2) { + for (i = 0; i < len && count > 0; count -= 2, i += 4) { + *buf++ = 0x01; // two samples + *buf++ = 0x80; // RLE mark + two samples + *buf++ = a; + *buf++ = a; + a ^= 0xff; + } + + if (i / 2 > sump.read_count) + sump.read_count = 0; + else + sump.read_count -= i / 2; + } else { + return 0; + } + } else { + if (sump.width == 1) { + for (i = 0; i < len && count > 0; count--, i++) { + *buf++ = a; + a ^= 0xff; + } + + sump.read_count -= i; + } else if (sump.width == 2) { + for (i = 0; i < len && count > 0; count--, i += 2) { + *buf++ = a; + *buf++ = a; + a ^= 0xff; + } + + sump.read_count -= i / 2; + } else { + return 0; + } + } + + // picoprobe_debug("%s: ret=%u\n", __func__, i); + return i; +} + +static uint32_t sump_tx8(uint8_t* buf, uint32_t len) { + uint32_t i; + uint32_t count = sump.read_count; + // picoprobe_debug("%s: count=%u, start=%u\n", __func__, count); + uint8_t* ptr = sump.buffer + (sump.read_start + count) % SUMP_MEMORY_SIZE; + + if (sump.flags & SUMP_FLAG1_ENABLE_RLE) { + uint8_t b, rle_last = 0x80, rle_count = 0; + + for (i = 0; i + 1 < len && count > 0; count--) { + if (ptr == sump.buffer) ptr = sump.buffer + SUMP_MEMORY_SIZE; + + b = *(--ptr) & 0x7f; + + if (b != rle_last) { + if (rle_count > 0) { + *((uint16_t*)buf) = (rle_count - 1) | 0x80 | ((uint16_t)rle_last << 8); + buf += 2; + i += 2; + sump.read_count -= rle_count; + } + + rle_last = b; + rle_count = 1; + continue; + } + if (++rle_count == 0x80) { + *((uint16_t*)buf) = (rle_count - 1) | 0x80 | ((uint16_t)rle_last << 8); + buf += 2; + i += 2; + sump.read_count -= rle_count; + rle_count = 0; + } + } + } else { + for (i = 0; i < len && count > 0; i++, count--) { + if (ptr == sump.buffer) ptr = sump.buffer + SUMP_MEMORY_SIZE; + + *buf++ = *(--ptr); + } + + sump.read_count -= i; + } + + // picoprobe_debug("%s: ret=%u\n", __func__, i); + return i; +} + +static uint32_t sump_tx16(uint8_t* buf, uint32_t len) { + uint32_t i; + uint32_t count = sump.read_count; + // picoprobe_debug("%s: count=%u, start=%u\n", __func__, count, sump.read_count); + volatile uint8_t* ptr = sump.buffer + (sump.read_start + count * 2) % SUMP_MEMORY_SIZE; + + if (sump.flags & SUMP_FLAG1_ENABLE_RLE) { + uint16_t rle_last = 0x8000, rle_count = 0; + + for (i = 0; i + 3 < len && count > 0; count--) { + if (ptr == sump.buffer) ptr = sump.buffer + SUMP_MEMORY_SIZE; + + ptr -= 2; + + uint32_t b = *((uint16_t*)ptr) & 0x7fff; + + if (b != rle_last) { + if (rle_count > 0) { + *((uint32_t*)buf) = (rle_count - 1) | 0x8000 | ((uint32_t)rle_last << 16); + buf += 4; + i += 4; + sump.read_count -= rle_count; + } + + rle_last = b; + rle_count = 1; + continue; + } + if (++rle_count == 0x8000) { + *((uint32_t*)buf) = (rle_count - 1) | 0x8000 | ((uint32_t)rle_last << 16); + buf += 4; + i += 4; + sump.read_count -= rle_count; + rle_count = 0; + } + } + } else { + for (i = 0; i + 1 < len && count > 0; i += 2, count--) { + if (ptr == sump.buffer) ptr = sump.buffer + SUMP_MEMORY_SIZE; + + ptr -= 2; + *((uint16_t*)buf) = *((uint16_t*)ptr); + buf += 2; + } + + sump.read_count -= i / 2; + } + + // picoprobe_debug("%s: ret=%u\n", __func__, i); + return i; +} + +static uint32_t sump_fill_tx(uint8_t* buf, uint32_t len) { + uint32_t ret; + + assert((len & 3) == 0); + if (sump.read_count == 0) { + sump.state = SUMP_STATE_CONFIG; + return 0; + } + + if (sump.state == SUMP_STATE_DUMP) { + if (sump.width == 1) { + ret = sump_tx8(buf, len); + } else if (sump.width == 2) { + ret = sump_tx16(buf, len); + } else { + // invalid + ret = sump_tx_empty(buf, len); + } + } else { + // invalid or error + ret = sump_tx_empty(buf, len); + } + + if (ret == 0) sump.state = SUMP_STATE_CONFIG; + + return ret; +} + +static void sump_init_connect(void) { + memset(&sump, 0, sizeof(sump)); + sump.width = 1; + sump.divider = 1000; // a safe value + sump.read_count = 256; + sump.delay_count = 256; +} + +void cdc_sump_init(void) { + sump_hw_init(); + + sump_init_connect(); + + picoprobe_debug("%s(): memory buffer %u bytes\n", __func__, SUMP_MEMORY_SIZE); +} +void cdc_sump_deinit(void) { + sump_hw_deinit(); + + memset(&sump, 0, sizeof(sump)); +} + +#define MAX_UART_PKT 64 +void cdc_sump_task(void) { + uint8_t buf[MAX_UART_PKT]; + + if (tud_cdc_n_connected(CDC_INTF)) { + if (!sump.cdc_connected) { + sump_init_connect(); + sump.cdc_connected = true; + } + + if (sump.state == SUMP_STATE_DUMP || sump.state == SUMP_STATE_ERROR) { + if (tud_cdc_n_write_available(CDC_INTF) >= sizeof(buf)) { + uint32_t tx_len = sump_fill_tx(buf, sizeof(buf)); + tud_cdc_n_write(CDC_INTF, buf, tx_len); + tud_cdc_n_write_flush(CDC_INTF); + } + } + if (tud_cdc_n_available(CDC_INTF)) { + uint32_t cmd_len = tud_cdc_n_read(CDC_INTF, buf, sizeof(buf)); + sump_rx(buf, cmd_len); + } + /*if (sump.state == SUMP_STATE_TRIGGER || sump.state == SUMP_STATE_SAMPLING) + led_signal_activity(1);*/ + } else if (!sump.cdc_connected) { + sump.cdc_connected = false; + sump_do_reset(); + } +} + +// we could hook the callback, but it's not really used, so, eh. +/*void cdc_sump_line_coding(cdc_line_coding_t const *line_coding); +void cdc_sump_line_coding(cdc_line_coding_t const *line_coding) { + picoprobe_info("Sump new baud rate %d\n", line_coding->bit_rate); +}*/ diff --git a/src/m_sump/sump.h b/src/m_sump/sump.h new file mode 100644 index 0000000..6b582ba --- /dev/null +++ b/src/m_sump/sump.h @@ -0,0 +1,88 @@ + +#ifndef SUMP_H +#define SUMP_H + +#define SUMP_META_END 0 +#define SUMP_META_NAME 1 +#define SUMP_META_FPGA_VERSION 2 +#define SUMP_META_CPU_VERSION 3 +#define SUMP_META_SAMPLE_RAM 0x21 +#define SUMP_META_SAMPLE_RATE 0x23 +#define SUMP_META_PROBES_B 0x40 +#define SUMP_META_PROTOCOL_B 0x41 + +#define SUMP_FLAG1_DDR (1<<0) /* "demux", apparently */ +#define SUMP_FLAG1_GR0_DISABLE (1<<2) +#define SUMP_FLAG1_GR1_DISABLE (1<<3) +#define SUMP_FLAG1_GR2_DISABLE (1<<4) +#define SUMP_FLAG1_GR3_DISABLE (1<<5) +#define SUMP_FLAG1_ENABLE_RLE (1<<8) +#define SUMP_FLAG1_EXT_TEST (1<<10) + +#define SUMP_CMD_RESET 0 +#define SUMP_CMD_ARM 1 +#define SUMP_CMD_ID 2 +#define SUMP_CMD_META 4 +#define SUMP_CMD_FINISH 5 + +/* demon core extensions */ +#define SUMP_CMD_QUERY_INPUT 6 +#define SUMP_CMD_ADVANCED_ARM 0xF + +#define SUMP_CMD_SET_SAMPLE_RATE 0x80 +#define SUMP_CMD_SET_COUNTS 0x81 +#define SUMP_CMD_SET_FLAGS 0x82 + +/* demon core extensiosns */ +#define SUMP_CMD_SET_ADV_TRG_SELECT 0x9E +#define SUMP_CMD_SET_ADV_TRG_DATA 0x9F + +#define SUMP_CMD_SET_BTRG0_MASK 0xC0 +#define SUMP_CMD_SET_BTRG1_MASK 0xC4 +#define SUMP_CMD_SET_BTRG2_MASK 0xC8 +#define SUMP_CMD_SET_BTRG3_MASK 0xCC +#define SUMP_CMD_SET_BTRG0_VALUE 0xC1 +#define SUMP_CMD_SET_BTRG1_VALUE 0xC5 +#define SUMP_CMD_SET_BTRG2_VALUE 0xC9 +#define SUMP_CMD_SET_BTRG3_VALUE 0xCD +#define SUMP_CMD_SET_BTRG0_CONFIG 0xC2 +#define SUMP_CMD_SET_BTRG1_CONFIG 0xC6 +#define SUMP_CMD_SET_BTRG2_CONFIG 0xCA +#define SUMP_CMD_SET_BTRG3_CONFIG 0xCE + +inline static int SUMP_CMD_IS_SHORT(int cmd) { + return !(cmd & 0x80); // crude but works +} + +/* **** */ + +#define ONE_MHZ 1000000u + +/* **** */ + +uint32_t sump_calc_sysclk_divider(); + +uint8_t *sump_capture_get_next_dest(uint32_t numch); +void sump_capture_callback_cancel(void); +void sump_capture_callback(uint32_t ch, uint32_t numch); + +void cdc_sump_init(void); +void cdc_sump_deinit(void); +void cdc_sump_task(void); + +/* --- */ + +void sump_hw_get_cpu_name(char cpu[32]); +void sump_hw_get_hw_name(char hw[32]); + +uint32_t sump_hw_get_sysclk(void); + +void sump_hw_init(void); +void sump_hw_deinit(void); + +void sump_hw_capture_setup_next(uint32_t ch, uint32_t mask, uint32_t chunk_size, uint32_t next_count, uint8_t width); +void sump_hw_capture_start(uint8_t width, int flags, uint32_t chunk_size, uint8_t *destbuf); +void sump_hw_capture_stop(void); +void sump_hw_stop(void); + +#endif diff --git a/src/modeset.c b/src/modeset.c index 8af2b6e..934311c 100644 --- a/src/modeset.c +++ b/src/modeset.c @@ -4,13 +4,15 @@ #include "mode.h" -extern struct mode m_01_default/*, m_02_default2*/; +extern struct mode m_01_default, m_04_sump; // clang-format off const struct mode* const mode_list[16] = { NULL, // dummy 0 entry &m_01_default, // entry 1 CANNOT be NULL! - //&m_02_default2, + NULL, // mode 2 (jtag scan) not implemented yet + NULL, // mode 3 (hw chip programming stuff) not implemented yet + &m_04_sump, NULL, // terminating entry }; // clang-format on