SUMP logic analyzer mode, stolen from piocprobe-sump (mode 4)

This commit is contained in:
Triss 2021-07-18 22:03:58 +02:00
parent 8f1f4b1ad0
commit 82ff925001
11 changed files with 1539 additions and 10 deletions

View File

@ -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)

34
LICENSE.picoprobe-sump Normal file
View File

@ -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
<https://github.com/perexg/picoprobe-sump> (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 <perex@perex.cz>
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.

View File

@ -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

View File

@ -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

367
bsp/rp2040/m_sump/sump_hw.c Normal file
View File

@ -0,0 +1,367 @@
#include <hardware/clocks.h>
#include <hardware/dma.h>
#include <hardware/irq.h>
#include <hardware/pio.h>
#include <hardware/pwm.h>
#include <hardware/structs/bus_ctrl.h>
#include <hardware/sync.h>
#include <pico/platform.h>
#include <pico/stdlib.h>
#include <stdio.h>
#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);
}

View File

@ -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

View File

@ -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

170
src/m_sump/_sump.c Normal file
View File

@ -0,0 +1,170 @@
// vim: set et:
#include <tusb.h>
#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

817
src/m_sump/cdc_sump.c Normal file
View File

@ -0,0 +1,817 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2021 Jaroslav Kysela <perex@perex.cz>
*
* 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 <assert.h>
#include <tusb.h>
#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);
}*/

88
src/m_sump/sump.h Normal file
View File

@ -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

View File

@ -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