Compare commits
21 Commits
Author | SHA1 | Date |
---|---|---|
Triss | 334d45ee83 | |
Triss | 22e8f70e30 | |
Triss | 0c10da762a | |
Triss | 3b76c6fb4a | |
Triss | 1e9cac1ace | |
Triss | 210f74dbc5 | |
Triss | c5a4a6b3e5 | |
Triss | ca11c8e1e1 | |
Triss | d2860c8e10 | |
Triss | 45cce4dfff | |
Triss | a1561ee35a | |
Triss | 493be92bd2 | |
Triss | 383dcc4ea9 | |
Triss | 07db20ecb6 | |
Triss | 6a4eafb96e | |
Triss | 9d2a970161 | |
Triss | 1318e0f9c2 | |
Triss | 4015b7bf42 | |
Triss | befced7132 | |
Triss | 36b55483a8 | |
Triss | 3c3795f8cb |
|
@ -1,4 +1,5 @@
|
||||||
option(USE_USBCDC_FOR_STDIO "Export an extra USB-CDC interface for stdio, instead of echoing it to a UART port (and requiring UART loopback for receiving stdio output on a host computer)." OFF)
|
option(USE_USBCDC_FOR_STDIO "Export an extra USB-CDC interface for stdio, instead of echoing it to a UART port (and requiring UART loopback for receiving stdio output on a host computer)." OFF)
|
||||||
|
option(PERSISTENT_STORAGE "Use persistent storage (usually on-chip/on-board flash) to save the current mode, and settings of modes" ON)
|
||||||
set(FAMILY "rp2040" CACHE STRING "Board/MCU family, decides which drivers to use. Set to RP2040 by default.")
|
set(FAMILY "rp2040" CACHE STRING "Board/MCU family, decides which drivers to use. Set to RP2040 by default.")
|
||||||
set(BOARD "raspberry_pi_pico" CACHE STRING "Board used, determines the pinout. Defaults to the Raspberry Pi Pico.")
|
set(BOARD "raspberry_pi_pico" CACHE STRING "Board used, determines the pinout. Defaults to the Raspberry Pi Pico.")
|
||||||
|
|
||||||
|
@ -45,11 +46,12 @@ if(FAMILY STREQUAL "rp2040")
|
||||||
# need uart stdio, usb is busy doing other stuff
|
# need uart stdio, usb is busy doing other stuff
|
||||||
if(USE_USBCDC_FOR_STDIO)
|
if(USE_USBCDC_FOR_STDIO)
|
||||||
# we're going to manually implement this case
|
# we're going to manually implement this case
|
||||||
#pico_enable_stdio_uart(${PROJECT} 0)
|
#pico_enable_stdio_uart(${PROJECT} 0)
|
||||||
target_compile_definitions(${PROJECT} PUBLIC USE_USBCDC_FOR_STDIO=1 PICO_STDIO_USB=1)
|
target_compile_definitions(${PROJECT} PUBLIC USE_USBCDC_FOR_STDIO=1)
|
||||||
else()
|
else()
|
||||||
#pico_enable_stdio_uart(${PROJECT} 1)
|
#pico_enable_stdio_uart(${PROJECT} 1)
|
||||||
endif()
|
endif()
|
||||||
|
# TODO: separate flag for disabling this one?
|
||||||
pico_enable_stdio_uart(${PROJECT} 1)
|
pico_enable_stdio_uart(${PROJECT} 1)
|
||||||
pico_enable_stdio_usb(${PROJECT} 0)
|
pico_enable_stdio_usb(${PROJECT} 0)
|
||||||
else()
|
else()
|
||||||
|
@ -69,6 +71,8 @@ target_sources(${PROJECT} PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/alloc.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/alloc.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/modeset.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/modeset.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/storage.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/storage_save.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/thread.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/thread.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/tusb_plt.S
|
${CMAKE_CURRENT_SOURCE_DIR}/src/tusb_plt.S
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
|
||||||
|
@ -81,6 +85,8 @@ target_sources(${PROJECT} PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/m_jscan/jscan.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/m_jscan/jscan.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/m_sump/_sump.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/m_sump/_sump.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/m_sump/cdc_sump.c
|
${CMAKE_CURRENT_SOURCE_DIR}/src/m_sump/cdc_sump.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/m_ftdi/_ftdi.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/m_ftdi/ftdi.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/cdc_uart.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/cdc_uart.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/dap_jtag.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/dap_jtag.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/dap_swd.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/dap_swd.c
|
||||||
|
@ -91,12 +97,25 @@ target_sources(${PROJECT} PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/tempsensor.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/tempsensor.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_jscan/jscan_hw.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_jscan/jscan_hw.c
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_sump/sump_hw.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_sump/sump_hw.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/ftdi_hw.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/ftdi_proto.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/uart.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/mpsse.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/asyncbb.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/syncbb.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/mcuhost.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/fifo.c
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/cpufifo.c
|
||||||
)
|
)
|
||||||
if(USE_USBCDC_FOR_STDIO)
|
if(USE_USBCDC_FOR_STDIO)
|
||||||
|
target_compile_definitions(${PROJECT} PUBLIC USE_USBCDC_FOR_STDIO=1)
|
||||||
target_sources(${PROJECT} PUBLIC
|
target_sources(${PROJECT} PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/cdc_stdio.c
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/cdc_stdio.c
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
if (PERSISTENT_STORAGE)
|
||||||
|
target_compile_definitions(${PROJECT} PUBLIC PERSISTENT_STORAGE=1)
|
||||||
|
endif()
|
||||||
target_include_directories(${PROJECT} PUBLIC
|
target_include_directories(${PROJECT} PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/src/
|
${CMAKE_CURRENT_SOURCE_DIR}/src/
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/libco/
|
${CMAKE_CURRENT_SOURCE_DIR}/libco/
|
||||||
|
@ -108,8 +127,9 @@ target_include_directories(${PROJECT} PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/bsp/default/
|
${CMAKE_CURRENT_SOURCE_DIR}/bsp/default/
|
||||||
)
|
)
|
||||||
|
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror=implicit-function-declaration -Werror=return-type")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Werror=implicit-function-declaration -Werror=return-type -Werror=maybe-uninitialized")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--cref")
|
||||||
|
|
||||||
add_custom_target(fix_db ALL WORKING_DIRECTORY ${OUTPUT_DIR}
|
add_custom_target(fix_db ALL WORKING_DIRECTORY ${OUTPUT_DIR}
|
||||||
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/fix_clang_db.py")
|
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/fix_clang_db.py")
|
||||||
|
@ -141,6 +161,9 @@ if(FAMILY STREQUAL "rp2040")
|
||||||
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/swo_uart_rx.pio)
|
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/swo_uart_rx.pio)
|
||||||
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/swo_manchester_encoding.pio)
|
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_default/swo_manchester_encoding.pio)
|
||||||
|
|
||||||
|
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/ftdi_uart_rx.pio)
|
||||||
|
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/m_ftdi/ftdi_uart_tx.pio)
|
||||||
|
|
||||||
pico_add_extra_outputs(${PROJECT})
|
pico_add_extra_outputs(${PROJECT})
|
||||||
|
|
||||||
else()
|
else()
|
||||||
|
|
|
@ -20,6 +20,11 @@
|
||||||
#else
|
#else
|
||||||
#define CFG_TUD_CDC 2
|
#define CFG_TUD_CDC 2
|
||||||
#endif
|
#endif
|
||||||
#define CFG_TUD_VENDOR 1
|
#define CFG_TUD_VENDOR 3
|
||||||
|
|
||||||
|
/* don't access storage for RAM-only builds */
|
||||||
|
#if !PICO_NO_FLASH
|
||||||
|
#define DBOARD_HAS_STORAGE
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#ifndef BSP_STORAGE_H_
|
||||||
|
#define BSP_STORAGE_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#if !PICO_NO_FLASH
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <hardware/regs/addressmap.h>
|
||||||
|
#include <hardware/regs/xip.h>
|
||||||
|
#include <hardware/structs/xip_ctrl.h>
|
||||||
|
#include <hardware/flash.h>
|
||||||
|
#include <hardware/sync.h>
|
||||||
|
#include <pico/bootrom.h>
|
||||||
|
|
||||||
|
#ifndef PICO_FLASH_SIZE_BYTES
|
||||||
|
#error "PICO_FLASH_SIZE_BYTES not defined"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline size_t storage_get_program_size(void) {
|
||||||
|
extern uint8_t __flash_binary_start, __flash_binary_end;
|
||||||
|
|
||||||
|
return (size_t)&__flash_binary_end - (size_t)&__flash_binary_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t storage_get_program_offset(void) {
|
||||||
|
extern uint8_t __flash_binary_start;
|
||||||
|
|
||||||
|
return (size_t)&__flash_binary_start - XIP_BASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define STORAGE_SIZE PICO_FLASH_SIZE_BYTES
|
||||||
|
#define STORAGE_ERASEWRITE_ALIGN FLASH_SECTOR_SIZE
|
||||||
|
// reads don't require any alignment
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
static inline void storage_read(void* dest, size_t offset, size_t size) {
|
||||||
|
// TODO: XIP/SSI DMA?
|
||||||
|
// * XIP DMA: used for loading stuff in the background while running code
|
||||||
|
// * SSI DMA: blocking & fast, code needs to run from RAM. a bit unwieldy
|
||||||
|
memcpy(dest, (uint8_t*)(XIP_BASE+offset), size);
|
||||||
|
}
|
||||||
|
static bool storage_erasewrite(size_t offset, const void* src, size_t size) {
|
||||||
|
// bad alignment => give an error in advance
|
||||||
|
if (offset & (FLASH_SECTOR_SIZE - 1)) return false;
|
||||||
|
if (size & (FLASH_SECTOR_SIZE - 1)) return false;
|
||||||
|
|
||||||
|
// memcmp pre: if no changes, don't flash
|
||||||
|
if (!memcmp(src, (uint8_t*)(XIP_BASE+offset), size)) return true;
|
||||||
|
|
||||||
|
flash_range_erase(offset, size);
|
||||||
|
flash_range_program(offset, src, size);
|
||||||
|
|
||||||
|
return !memcmp(src, (uint8_t*)(XIP_BASE+offset), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -61,6 +61,7 @@ uint32_t SWO_Mode_UART(uint32_t enable) {
|
||||||
mode_enabled = false;
|
mode_enabled = false;
|
||||||
|
|
||||||
if (swo_dmach >= 0) {
|
if (swo_dmach >= 0) {
|
||||||
|
dma_channel_abort(swo_dmach);
|
||||||
dma_channel_unclaim(swo_dmach); // ugh why is it "dma_channel_xyz" and "dma_xyz_channel"
|
dma_channel_unclaim(swo_dmach); // ugh why is it "dma_channel_xyz" and "dma_xyz_channel"
|
||||||
swo_dmach = -1;
|
swo_dmach = -1;
|
||||||
}
|
}
|
||||||
|
@ -189,6 +190,7 @@ uint32_t SWO_Mode_Manchester(uint32_t enable) {
|
||||||
mode_enabled = false;
|
mode_enabled = false;
|
||||||
|
|
||||||
if (swo_dmach >= 0) {
|
if (swo_dmach >= 0) {
|
||||||
|
dma_channel_abort(swo_dmach);
|
||||||
dma_channel_unclaim(swo_dmach); // ugh why is it "dma_channel_xyz" and "dma_xyz_channel"
|
dma_channel_unclaim(swo_dmach); // ugh why is it "dma_channel_xyz" and "dma_xyz_channel"
|
||||||
swo_dmach = -1;
|
swo_dmach = -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// vim: set et ts=8:
|
// vim: set et ts=8:
|
||||||
|
|
||||||
#ifndef PINOUT_H_
|
#ifndef BSP_PINOUT_M_DEFAULT_H_
|
||||||
#define PINOUT_H_
|
#define BSP_PINOUT_M_DEFAULT_H_
|
||||||
|
|
||||||
// UART config
|
// UART config
|
||||||
#define PINOUT_UART_TX 8
|
#define PINOUT_UART_TX 8
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
void ftdi_if_asyncbb_init(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
|
||||||
|
}
|
||||||
|
void ftdi_if_asyncbb_deinit(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
|
||||||
|
}
|
||||||
|
void ftdi_if_asyncbb_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
(void)itf; (void)baudrate;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_asyncbb_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize) {
|
||||||
|
(void)itf; (void)data; (void)datasize;
|
||||||
|
|
||||||
|
}
|
||||||
|
size_t ftdi_if_asyncbb_read(struct ftdi_interface* itf, uint8_t* data, size_t maxsize) {
|
||||||
|
(void)itf; (void)data; (void)maxsize;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
#ifndef BSP_FEATURE_M_FTDI_H_
|
||||||
|
#define BSP_FEATURE_M_FTDI_H_
|
||||||
|
|
||||||
|
#define DBOARD_HAS_FTDI
|
||||||
|
|
||||||
|
/* TODO: more fine-grained FTDI support/not-support stuff? */
|
||||||
|
|
||||||
|
#include "bsp-info.h"
|
||||||
|
|
||||||
|
// not all that much here
|
||||||
|
|
||||||
|
enum {
|
||||||
|
HID_N__NITF = 0
|
||||||
|
};
|
||||||
|
enum {
|
||||||
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
|
CDC_N_STDIO = 0,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CDC_N__NITF
|
||||||
|
};
|
||||||
|
enum {
|
||||||
|
/*VND_N_FTDI_IFA = 0,
|
||||||
|
VND_N_FTDI_IFB,*/
|
||||||
|
VND_N_CFG = 0,
|
||||||
|
|
||||||
|
VND_N__NITF
|
||||||
|
};
|
||||||
|
|
||||||
|
#define VND_N_FTDI_IFA 42
|
||||||
|
#define VND_N_FTDI_IFB 69
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,23 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
void ftdi_if_cpufifo_init(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_cpufifo_deinit(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_cpufifo_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
(void)itf; (void)baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_cpufifo_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize) {
|
||||||
|
(void)itf; (void)data; (void)datasize;
|
||||||
|
}
|
||||||
|
size_t ftdi_if_cpufifo_read(struct ftdi_interface* itf, uint8_t* data, size_t maxsize) {
|
||||||
|
(void)itf; (void)data; (void)maxsize;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
void ftdi_if_fifo_init(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_fifo_deinit(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_fifo_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
(void)itf; (void)baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_fifo_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize) {
|
||||||
|
(void)itf; (void)data; (void)datasize;
|
||||||
|
}
|
||||||
|
size_t ftdi_if_fifo_read(struct ftdi_interface* itf, uint8_t* data, size_t maxsize) {
|
||||||
|
(void)itf; (void)data; (void)maxsize;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
/* include order matters here */
|
||||||
|
#include <hardware/dma.h>
|
||||||
|
#include <hardware/gpio.h>
|
||||||
|
#include <hardware/irq.h>
|
||||||
|
#include <hardware/pio.h>
|
||||||
|
#include <hardware/structs/dma.h>
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi_hw.h"
|
||||||
|
|
||||||
|
struct ftdi_hw itf_bsp_data[2];
|
||||||
|
|
||||||
|
static int piosm_to_dreq(int pio, int sm) {
|
||||||
|
return DREQ_PIO0_TX0 + pio * 8 + sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ftdihw_dma_isr();
|
||||||
|
|
||||||
|
void ftdihw_init(struct ftdi_hw* fr, struct ftdi_interface* itf) {
|
||||||
|
memset(fr, 0, sizeof *fr);
|
||||||
|
|
||||||
|
fr->itf = itf;
|
||||||
|
fr->pio = ftdihw_itf_to_pio(itf);
|
||||||
|
fr->pinbase = ftdihw_itf_to_base(itf);
|
||||||
|
|
||||||
|
fr->rx.prg_off = 0xff;
|
||||||
|
fr->rx.piosm = 0xff;
|
||||||
|
fr->rx.dmach = 0xff;
|
||||||
|
fr->tx.prg_off = 0xff;
|
||||||
|
fr->tx.piosm = 0xff;
|
||||||
|
fr->tx.dmach = 0xff;
|
||||||
|
|
||||||
|
// we start with nothing to write out, but we can start filling a buffer already
|
||||||
|
fr->rx.dmabuf_dend = sizeof(fr->dma_in_buf) - 1;
|
||||||
|
|
||||||
|
irq_set_enabled(DMA_IRQ_0 + (fr->itf->index & 1), false);
|
||||||
|
irq_set_exclusive_handler(DMA_IRQ_0 + (fr->itf->index & 1), ftdihw_dma_isr);
|
||||||
|
irq_set_enabled(DMA_IRQ_0 + (fr->itf->index & 1), true);
|
||||||
|
}
|
||||||
|
void ftdihw_deinit(struct ftdi_hw* hw) {
|
||||||
|
irq_set_enabled(DMA_IRQ_0 + (hw->itf->index & 1), false);
|
||||||
|
irq_remove_handler(DMA_IRQ_0 + (hw->itf->index & 1), ftdihw_dma_isr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ftdihw_dma_ch_init(struct ftdi_hw_ch* ch, struct ftdi_hw* hw, const void* prg) {
|
||||||
|
int off, sm, dmach;
|
||||||
|
sm = pio_claim_unused_sm(hw->pio, false);
|
||||||
|
if (sm == -1) return false;
|
||||||
|
|
||||||
|
dmach = dma_claim_unused_channel(false);
|
||||||
|
if (dmach == -1) {
|
||||||
|
pio_sm_unclaim(hw->pio, sm);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pio_can_add_program(hw->pio, prg)) {
|
||||||
|
dma_channel_unclaim(dmach);
|
||||||
|
pio_sm_unclaim(hw->pio, sm);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
off = pio_add_program(hw->pio, prg);
|
||||||
|
|
||||||
|
ch->prg = prg;
|
||||||
|
ch->prg_off = off;
|
||||||
|
ch->piosm = sm;
|
||||||
|
ch->dmach = dmach;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void ftdihw_dma_ch_deinit(struct ftdi_hw_ch* ch, struct ftdi_hw* hw) {
|
||||||
|
dma_channel_unclaim(ch->dmach);
|
||||||
|
pio_sm_set_enabled(hw->pio, ch->piosm, false);
|
||||||
|
pio_sm_unclaim(hw->pio, ch->piosm);
|
||||||
|
pio_remove_program(hw->pio, ch->prg, ch->prg_off);
|
||||||
|
|
||||||
|
ch->dmach = 0xff;
|
||||||
|
ch->piosm = 0xff;
|
||||||
|
ch->prg_off = 0xff;
|
||||||
|
ch->prg = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdihw_dma_rx_setup(struct ftdi_hw* hw, bool start) {
|
||||||
|
dma_irqn_set_channel_enabled(hw->itf->index & 1, hw->rx.dmach, false);
|
||||||
|
|
||||||
|
if (hw->rx.dmabuf_dend == hw->rx.dmabuf_dstart)
|
||||||
|
--hw->rx.dmabuf_dend; // mod 256 automatically
|
||||||
|
|
||||||
|
dma_channel_config dcfg = dma_channel_get_default_config(hw->rx.dmach);
|
||||||
|
channel_config_set_read_increment(&dcfg, false);
|
||||||
|
channel_config_set_write_increment(&dcfg, true);
|
||||||
|
channel_config_set_dreq(&dcfg, piosm_to_dreq(hw->itf->index, hw->rx.piosm));
|
||||||
|
channel_config_set_transfer_data_size(&dcfg, DMA_SIZE_8);
|
||||||
|
channel_config_set_ring(&dcfg, true, 8); // 1<<8 -sized ring buffer on write end
|
||||||
|
dma_channel_configure(hw->rx.dmach, &dcfg,
|
||||||
|
&hw->dma_in_buf[hw->rx.dmabuf_dstart], &hw->pio->rxf[hw->rx.piosm],
|
||||||
|
(hw->rx.dmabuf_dend - hw->rx.dmabuf_dstart) % sizeof(hw->dma_in_buf), start);
|
||||||
|
|
||||||
|
dma_irqn_set_channel_enabled(hw->itf->index & 1, hw->rx.dmach, true);
|
||||||
|
}
|
||||||
|
void ftdihw_dma_rx_stop(struct ftdi_hw* hw) {
|
||||||
|
dma_irqn_set_channel_enabled(hw->itf->index & 1, hw->rx.dmach, false);
|
||||||
|
dma_channel_abort(hw->rx.dmach);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdihw_dma_tx_setup(struct ftdi_hw* hw, bool start) {
|
||||||
|
dma_irqn_set_channel_enabled(hw->itf->index & 1, hw->tx.dmach, false);
|
||||||
|
|
||||||
|
if (hw->tx.dmabuf_dend == hw->tx.dmabuf_dstart)
|
||||||
|
--hw->tx.dmabuf_dend; // mod 256 automatically
|
||||||
|
|
||||||
|
dma_channel_config dcfg = dma_channel_get_default_config(hw->tx.dmach);
|
||||||
|
channel_config_set_read_increment(&dcfg, true);
|
||||||
|
channel_config_set_write_increment(&dcfg, false);
|
||||||
|
channel_config_set_dreq(&dcfg, piosm_to_dreq(hw->itf->index, hw->tx.piosm));
|
||||||
|
channel_config_set_transfer_data_size(&dcfg, DMA_SIZE_8);
|
||||||
|
channel_config_set_ring(&dcfg, false, 8); // 1<<8 -sized ring buffer on read end
|
||||||
|
dma_channel_configure(hw->tx.dmach, &dcfg,
|
||||||
|
&hw->pio->txf[hw->tx.piosm], &hw->dma_out_buf[hw->tx.dmabuf_dstart],
|
||||||
|
(hw->tx.dmabuf_dend - hw->tx.dmabuf_dstart) % sizeof(hw->dma_out_buf), start);
|
||||||
|
|
||||||
|
dma_irqn_set_channel_enabled(hw->itf->index & 1, hw->tx.dmach, true);
|
||||||
|
}
|
||||||
|
void ftdihw_dma_tx_stop(struct ftdi_hw* hw) {
|
||||||
|
dma_irqn_set_channel_enabled(hw->itf->index & 1, hw->tx.dmach, false);
|
||||||
|
dma_channel_abort(hw->tx.dmach);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ftdihw_dma_read(struct ftdi_hw* hw, uint8_t* dest, size_t maxsize) {
|
||||||
|
// DMA is sending data from PIO to between dstart and dend, and is planned
|
||||||
|
// to continue up to dataend. when it finishes an xfer (between dstart and
|
||||||
|
// dend), it looks at dataend to see if it should continue sending more. if
|
||||||
|
// not, it pauses itself as tehre's no more work to be done, and sets a
|
||||||
|
// FIFO overrun flag in the FTDI error things
|
||||||
|
//
|
||||||
|
// here we read from dataend to dstart, careful not to touch the area the
|
||||||
|
// DMA is currently writing to (while holding the channel paused), and then
|
||||||
|
// moving the dataend marker. the channel is always reenabled, as either it
|
||||||
|
// was busy and now it has more space to put data in, or needs to be
|
||||||
|
// restarted as there is space now
|
||||||
|
//
|
||||||
|
// pausing the DMA channel is ok as it'll pause the PIO FIFO a bit, but
|
||||||
|
// nothing drastic
|
||||||
|
|
||||||
|
if (maxsize == 0) return 0;
|
||||||
|
|
||||||
|
size_t rv = 0;
|
||||||
|
|
||||||
|
// TODO: make time between pause & resume shorter by moving stuff around?
|
||||||
|
bool wasbusy = dma_hw->ch[hw->rx.dmach].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS;
|
||||||
|
hw_clear_bits(&dma_hw->ch[hw->rx.dmach].ctrl_trig, DMA_CH0_CTRL_TRIG_EN_BITS); // pause (also prevents IRQ)
|
||||||
|
|
||||||
|
// dstart can get modified by the IRQ; IRQ reads dataend
|
||||||
|
uint8_t dstart = hw->rx.dmabuf_dstart, dataend = hw->rx.dmabuf_dataend;
|
||||||
|
if (dataend > dstart) dstart += sizeof(hw->dma_in_buf);
|
||||||
|
// nothing ready yet - bail out
|
||||||
|
if (dstart == (dataend + 1) % sizeof(hw->dma_in_buf)) {
|
||||||
|
goto END;
|
||||||
|
}
|
||||||
|
|
||||||
|
__compiler_memory_barrier();
|
||||||
|
|
||||||
|
// copy from data in ringbuffer that was read in by PIO
|
||||||
|
rv = (size_t)dstart - (size_t)dataend - 1;
|
||||||
|
if (rv > maxsize) rv = maxsize;
|
||||||
|
for (size_t i = 0; i < rv; ++i) {
|
||||||
|
dest[i] = hw->dma_in_buf[(dataend + i) % sizeof(hw->dma_in_buf)];
|
||||||
|
}
|
||||||
|
uint8_t dataend_new = (dataend + rv) % sizeof(hw->dma_in_buf);
|
||||||
|
|
||||||
|
hw->rx.dmabuf_dataend = dataend_new;
|
||||||
|
|
||||||
|
hw->itf->modemstat &= ~sio_modem_fifoerr;
|
||||||
|
|
||||||
|
END:
|
||||||
|
if (!wasbusy) {
|
||||||
|
hw->rx.dmabuf_dstart = hw->rx.dmabuf_dend; // not required to set in DMA hw, but need to keep track of it in code
|
||||||
|
hw->rx.dmabuf_dend = hw->rx.dmabuf_dataend;
|
||||||
|
ftdihw_dma_rx_setup(hw, true);
|
||||||
|
} else // if already busy easlier, simply reenable
|
||||||
|
hw_set_bits(&dma_hw->ch[hw->rx.dmach].ctrl_trig, DMA_CH0_CTRL_TRIG_EN_BITS); // resume (also reenables IRQ)
|
||||||
|
return rv;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
bool ftdihw_dma_write(struct ftdi_hw* hw, const uint8_t* src, size_t size) {
|
||||||
|
// DMA is sending data between dstart and dend to PIO, and is planned to
|
||||||
|
// continue up to dataend. when it finishes an xfer (between dstart and
|
||||||
|
// dend), it looks at dataned to see if it should continue sending more. if
|
||||||
|
// not, it pauses itself as there's no more work to be done
|
||||||
|
//
|
||||||
|
// here we insert some data in the ring buffer, careful to not overwrite
|
||||||
|
// what the DMA is currently transferring (while holding the channel
|
||||||
|
// paused), and then moving the dataend marker. the channel is always
|
||||||
|
// reenabled, as either it was busy and now has more work to do, or needs
|
||||||
|
// to be restarted as there's data now
|
||||||
|
//
|
||||||
|
// pausing the DMA channel is ok as it'll pause the PIO FIFO a bit, but
|
||||||
|
// nothing drastic
|
||||||
|
|
||||||
|
if (size >= 255) return false; // don't even bother
|
||||||
|
|
||||||
|
bool rv = true;
|
||||||
|
|
||||||
|
// TODO: make time between pause & resume shorter by moving stuff around?
|
||||||
|
bool wasbusy = dma_hw->ch[hw->tx.dmach].ctrl_trig & DMA_CH0_CTRL_TRIG_BUSY_BITS;
|
||||||
|
hw_clear_bits(&dma_hw->ch[hw->tx.dmach].ctrl_trig, DMA_CH0_CTRL_TRIG_EN_BITS); // pause (also prevents IRQ)
|
||||||
|
|
||||||
|
// dstart can get modified by the IRQ; IRQ reads dataend
|
||||||
|
uint8_t dstart = hw->tx.dmabuf_dstart, dataend = hw->tx.dmabuf_dataend;
|
||||||
|
if (dataend > dstart) dstart += sizeof(hw->dma_out_buf);
|
||||||
|
// no space to put it in - overrun error
|
||||||
|
if ((size_t)dstart - (size_t)dataend < size + 1) {
|
||||||
|
hw->itf->modemstat |= sio_modem_fifoerr;
|
||||||
|
rv = false;
|
||||||
|
goto END;
|
||||||
|
}
|
||||||
|
|
||||||
|
__compiler_memory_barrier();
|
||||||
|
|
||||||
|
// copy data to buffer, to be copied next
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
hw->dma_out_buf[(dataend + i) % sizeof(hw->dma_out_buf)] = src[i];
|
||||||
|
}
|
||||||
|
uint8_t dataend_new = (dataend + size) % sizeof(hw->dma_out_buf);
|
||||||
|
|
||||||
|
hw->tx.dmabuf_dataend = dataend_new;
|
||||||
|
|
||||||
|
hw->itf->modemstat &= ~sio_modem_temt;
|
||||||
|
|
||||||
|
END:
|
||||||
|
if (!wasbusy) {
|
||||||
|
hw->tx.dmabuf_dstart = hw->tx.dmabuf_dend; // not required to set in DMA hw, but need to keep track of it in code
|
||||||
|
hw->tx.dmabuf_dend = hw->tx.dmabuf_dataend;
|
||||||
|
ftdihw_dma_tx_setup(hw, true);
|
||||||
|
} else // if already busy easlier, simply reenable
|
||||||
|
hw_set_bits(&dma_hw->ch[hw->tx.dmach].ctrl_trig, DMA_CH0_CTRL_TRIG_EN_BITS); // resume (also reenables IRQ)
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdihw_dma_rx_flush(struct ftdi_hw* hw) {
|
||||||
|
ftdihw_dma_rx_stop(hw);
|
||||||
|
hw->rx.dmabuf_dstart = 0;
|
||||||
|
hw->rx.dmabuf_dend = 0;
|
||||||
|
hw->rx.dmabuf_dataend = 0;
|
||||||
|
}
|
||||||
|
void ftdihw_dma_tx_flush(struct ftdi_hw* hw) {
|
||||||
|
ftdihw_dma_tx_stop(hw);
|
||||||
|
hw->tx.dmabuf_dstart = 0;
|
||||||
|
hw->tx.dmabuf_dend = 0;
|
||||||
|
hw->tx.dmabuf_dataend = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ftdihw_dma_rx_irq(struct ftdi_hw* hw) {
|
||||||
|
// an rx DMA transfer has finished. if in the meantime (between DMA xfer
|
||||||
|
// start and now) more room has become available, restart the DMA channel
|
||||||
|
// to write to the new space instead. otherwise, if there's no more space
|
||||||
|
// left, we're in a data overrun condition, so don't restart, and set the
|
||||||
|
// relevant FTDI error flags
|
||||||
|
|
||||||
|
uint8_t dend = hw->rx.dmabuf_dend, dataend = hw->rx.dmabuf_dataend;
|
||||||
|
|
||||||
|
if (dend == dataend) {
|
||||||
|
// data overrun, stop stuff (until read() restarts the DMA)
|
||||||
|
hw->itf->modemstat |= sio_modem_fifoerr;
|
||||||
|
} else {
|
||||||
|
hw->rx.dmabuf_dstart = dend;
|
||||||
|
hw->rx.dmabuf_dend = dataend;
|
||||||
|
ftdihw_dma_rx_setup(hw, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void ftdihw_dma_tx_irq(struct ftdi_hw* hw) {
|
||||||
|
// a tx DMA transfer has finished. if in the meantile (between DMA xfer
|
||||||
|
// start and now) more data has become available, restart the DMA channel
|
||||||
|
// to read from the new data instead. otherwise, if there's no more data to
|
||||||
|
// be read, we have nothing to do and we can leave the DMA channel in its
|
||||||
|
// idle state.
|
||||||
|
|
||||||
|
uint8_t dend = hw->tx.dmabuf_dend, dataend = hw->tx.dmabuf_dataend;
|
||||||
|
|
||||||
|
if (dend == dataend) {
|
||||||
|
// nothing to do
|
||||||
|
hw->itf->modemstat |= sio_modem_temt;
|
||||||
|
} else {
|
||||||
|
hw->tx.dmabuf_dstart = dend;
|
||||||
|
hw->tx.dmabuf_dend = dataend;
|
||||||
|
ftdihw_dma_tx_setup(hw, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ftdihw_dma_isr() {
|
||||||
|
// interrupt service routine: dispatch to functions above depending on INTS0
|
||||||
|
uint32_t flags = dma_hw->ints0;
|
||||||
|
uint32_t allflg = 1u << itf_bsp_data[0].rx.dmach;
|
||||||
|
allflg |= 1u << itf_bsp_data[0].tx.dmach;
|
||||||
|
allflg |= 1u << itf_bsp_data[1].rx.dmach;
|
||||||
|
allflg |= 1u << itf_bsp_data[1].tx.dmach;
|
||||||
|
|
||||||
|
for (size_t i = 0; (i < 2) && (flags & allflg); ++i) {
|
||||||
|
uint32_t flg = 1u << itf_bsp_data[i].rx.dmach;
|
||||||
|
if (flags & flg) {
|
||||||
|
flags &= ~flg;
|
||||||
|
dma_hw->ints0 = flg; // ack int
|
||||||
|
ftdihw_dma_rx_irq(&itf_bsp_data[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
flg = 1u << itf_bsp_data[i].tx.dmach;
|
||||||
|
if (flags & flg) {
|
||||||
|
flags &= ~flg;
|
||||||
|
dma_hw->ints0 = flg; // ack int
|
||||||
|
ftdihw_dma_tx_irq(&itf_bsp_data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#ifndef FTDI_BASE_H_
|
||||||
|
#define FTDI_BASE_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "m_ftdi/pinout.h"
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
struct ftdi_hw_ch {
|
||||||
|
const void* prg;
|
||||||
|
|
||||||
|
uint8_t prg_off, piosm, dmach;
|
||||||
|
|
||||||
|
volatile uint8_t dmabuf_dstart, dmabuf_dend, dmabuf_dataend;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ftdi_hw {
|
||||||
|
struct ftdi_interface* itf;
|
||||||
|
PIO pio;
|
||||||
|
|
||||||
|
struct ftdi_hw_ch rx, tx;
|
||||||
|
uint8_t pinbase;
|
||||||
|
|
||||||
|
volatile uint8_t dma_in_buf[256/*CFG_TUD_VENDOR_TX_BUFSIZE*/];
|
||||||
|
volatile uint8_t dma_out_buf[256/*CFG_TUD_VENDOR_RX_BUFSIZE*/];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct ftdi_hw itf_bsp_data[2];
|
||||||
|
|
||||||
|
void ftdihw_init(struct ftdi_hw* fr, struct ftdi_interface* itf);
|
||||||
|
void ftdihw_deinit(struct ftdi_hw* fr);
|
||||||
|
|
||||||
|
bool ftdihw_dma_ch_init(struct ftdi_hw_ch* ch, struct ftdi_hw* fr, const void* prg);
|
||||||
|
void ftdihw_dma_ch_deinit(struct ftdi_hw_ch* fr, struct ftdi_hw* hw);
|
||||||
|
|
||||||
|
void ftdihw_dma_rx_setup(struct ftdi_hw* fr, bool start);
|
||||||
|
void ftdihw_dma_rx_stop(struct ftdi_hw* fr);
|
||||||
|
|
||||||
|
void ftdihw_dma_tx_setup(struct ftdi_hw* fr, bool start);
|
||||||
|
void ftdihw_dma_tx_stop(struct ftdi_hw* fr);
|
||||||
|
|
||||||
|
size_t ftdihw_dma_read(struct ftdi_hw* fr, uint8_t* dest, size_t maxsize);
|
||||||
|
bool ftdihw_dma_write(struct ftdi_hw* fr, const uint8_t* src, size_t size);
|
||||||
|
|
||||||
|
void ftdihw_dma_rx_flush(struct ftdi_hw* fr);
|
||||||
|
void ftdihw_dma_tx_flush(struct ftdi_hw* fr);
|
||||||
|
|
||||||
|
static inline int ftdihw_idx_to_base(int itf_idx) {
|
||||||
|
return itf_idx ? PINOUT_ITF_B_BASE : PINOUT_ITF_A_BASE;
|
||||||
|
}
|
||||||
|
static inline int ftdihw_itf_to_base(struct ftdi_interface* itf) {
|
||||||
|
return ftdihw_idx_to_base(itf->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline PIO ftdihw_idx_to_pio(int itf_idx) {
|
||||||
|
return itf_idx ? PINOUT_ITF_A_PIO : PINOUT_ITF_B_PIO;
|
||||||
|
}
|
||||||
|
static inline PIO ftdihw_itf_to_pio(struct ftdi_interface* itf) {
|
||||||
|
return ftdihw_idx_to_pio(itf->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct ftdi_hw* ftdihw_idx_to_hw(int itf_idx) {
|
||||||
|
return &itf_bsp_data[itf_idx & 1];
|
||||||
|
}
|
||||||
|
static inline struct ftdi_hw* ftdihw_itf_to_hw(struct ftdi_interface* itf) {
|
||||||
|
return ftdihw_idx_to_hw(itf->index);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include <hardware/pio.h>
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
#include "m_ftdi/ftdi_hw.h"
|
||||||
|
|
||||||
|
static void init_mode(struct ftdi_interface* itf, enum ftdi_mode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ftmode_uart: ftdi_if_uart_init(itf); ftdi_if_uart_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
case ftmode_mpsse: ftdi_if_mpsse_init(itf); ftdi_if_mpsse_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
case ftmode_asyncbb: ftdi_if_asyncbb_init(itf); ftdi_if_asyncbb_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
case ftmode_syncbb: ftdi_if_syncbb_init(itf); ftdi_if_syncbb_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
case ftmode_mcuhost: ftdi_if_mcuhost_init(itf); ftdi_if_mcuhost_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
case ftmode_fifo: ftdi_if_fifo_init(itf); ftdi_if_fifo_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
case ftmode_cpufifo: ftdi_if_cpufifo_init(itf); ftdi_if_cpufifo_set_baudrate(itf, itf->baudrate); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void deinit_mode(struct ftdi_interface* itf, enum ftdi_mode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case ftmode_uart: ftdi_if_uart_deinit(itf); break;
|
||||||
|
case ftmode_mpsse: ftdi_if_mpsse_deinit(itf); break;
|
||||||
|
case ftmode_asyncbb: ftdi_if_asyncbb_deinit(itf); break;
|
||||||
|
case ftmode_syncbb: ftdi_if_syncbb_deinit(itf); break;
|
||||||
|
case ftmode_mcuhost: ftdi_if_mcuhost_deinit(itf); break;
|
||||||
|
case ftmode_fifo: ftdi_if_fifo_deinit(itf); break;
|
||||||
|
case ftmode_cpufifo: ftdi_if_cpufifo_deinit(itf); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_init(struct ftdi_interface* itf) {
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
ftdihw_init(hw, itf);
|
||||||
|
|
||||||
|
init_mode(itf, ftdi_if_get_mode(itf));
|
||||||
|
}
|
||||||
|
void ftdi_if_deinit(struct ftdi_interface* itf) {
|
||||||
|
deinit_mode(itf, ftdi_if_get_mode(itf));
|
||||||
|
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
ftdihw_deinit(hw);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_set_modemctrl(struct ftdi_interface* itf, uint8_t mask, uint8_t data) {
|
||||||
|
(void)itf; (void)mask; (void)data;
|
||||||
|
// TODO: what's this?
|
||||||
|
}
|
||||||
|
void ftdi_if_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
switch (ftdi_if_get_mode(itf)) {
|
||||||
|
case ftmode_uart: ftdi_if_uart_set_baudrate(itf, baudrate); break;
|
||||||
|
case ftmode_mpsse: ftdi_if_mpsse_set_baudrate(itf, baudrate); break;
|
||||||
|
case ftmode_asyncbb: ftdi_if_asyncbb_set_baudrate(itf, baudrate); break;
|
||||||
|
case ftmode_syncbb: ftdi_if_syncbb_set_baudrate(itf, baudrate); break;
|
||||||
|
case ftmode_mcuhost: ftdi_if_mcuhost_set_baudrate(itf, baudrate); break;
|
||||||
|
case ftmode_fifo: ftdi_if_fifo_set_baudrate(itf, baudrate); break;
|
||||||
|
case ftmode_cpufifo: ftdi_if_cpufifo_set_baudrate(itf, baudrate); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enum ftdi_sio_modemstat ftdi_if_poll_modemstat(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
|
||||||
|
return sio_modem_cts | sio_modem_dts; // TODO: use this to read part of UART flow ctrl?
|
||||||
|
}
|
||||||
|
void ftdi_if_set_eventchar(struct ftdi_interface* itf, bool enable, uint8_t evchar) {
|
||||||
|
(void)itf; (void)enable; (void)evchar;
|
||||||
|
// TODO: when is this used? bitmode0-only? also ftd2xx headers make this look like its not just an "event on char" thing
|
||||||
|
}
|
||||||
|
void ftdi_if_set_errorchar(struct ftdi_interface* itf, bool enable, uint8_t erchar) {
|
||||||
|
(void)itf; (void)enable; (void)erchar;
|
||||||
|
// TODO: when is this used? bitmode0-only? also ftd2xx headers make this look like its not just an "error on char" thing
|
||||||
|
}
|
||||||
|
uint8_t ftdi_if_read_pins(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
|
||||||
|
return 0; // TODO: which pins does this return?
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_set_bitbang(struct ftdi_interface* itf, uint8_t dirmask,
|
||||||
|
enum ftdi_sio_bitmode bitmode, uint8_t olddir, enum ftdi_sio_bitmode oldmode) {
|
||||||
|
if (bitmode == oldmode && dirmask == olddir) return; // nothing to do
|
||||||
|
|
||||||
|
deinit_mode(itf, ftdi_get_mode_of(oldmode, itf->index ? FTDI_EEP_IFB_MODE : FTDI_EEP_IFA_MODE));
|
||||||
|
init_mode(itf, ftdi_if_get_mode(itf));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_sio_reset(struct ftdi_interface* itf) { (void)itf; /* TODO: ? */ }
|
||||||
|
void ftdi_if_sio_tciflush(struct ftdi_interface* itf) {
|
||||||
|
(void)itf; /* TODO: ? */
|
||||||
|
}
|
||||||
|
void ftdi_if_sio_tcoflush(struct ftdi_interface* itf) {
|
||||||
|
(void)itf; /* TODO: ? */
|
||||||
|
}
|
||||||
|
void ftdi_if_set_latency(struct ftdi_interface* itf, uint8_t latency) { (void)itf; (void)latency; /* TODO: ? */ }
|
||||||
|
uint8_t ftdi_if_get_latency(struct ftdi_interface* itf) { return itf->latency; /* TODO: ? */ }
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
;
|
||||||
|
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
;
|
||||||
|
; SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
;
|
||||||
|
|
||||||
|
.program ftdi_uart_rx_mini
|
||||||
|
|
||||||
|
; Minimum viable 8n1 UART receiver. Wait for the start bit, then sample 8 bits
|
||||||
|
; with the correct timing.
|
||||||
|
; IN pin 0 is mapped to the GPIO used as UART RX.
|
||||||
|
; Autopush must be enabled, with a threshold of 8.
|
||||||
|
|
||||||
|
wait 0 pin 0 ; Wait for start bit
|
||||||
|
set x, 7 [10] ; Preload bit counter, delay until eye of first data bit
|
||||||
|
bitloop: ; Loop 8 times
|
||||||
|
in pins, 1 ; Sample data
|
||||||
|
jmp x-- bitloop [6] ; Each iteration is 8 cycles
|
||||||
|
|
||||||
|
% c-sdk {
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
|
||||||
|
static inline void ftdi_uart_rx_mini_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
|
||||||
|
pio_gpio_init(pio, pin);
|
||||||
|
gpio_pull_up(pin);
|
||||||
|
|
||||||
|
pio_sm_config c = ftdi_uart_rx_mini_program_get_default_config(offset);
|
||||||
|
sm_config_set_in_pins(&c, pin); // for WAIT, IN
|
||||||
|
// Shift to right, autopush enabled
|
||||||
|
sm_config_set_in_shift(&c, true, true, 8);
|
||||||
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
|
||||||
|
// SM transmits 1 bit per 8 execution cycles.
|
||||||
|
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
|
||||||
|
sm_config_set_clkdiv(&c, div);
|
||||||
|
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
|
}
|
||||||
|
%}
|
||||||
|
|
||||||
|
.program ftdi_uart_rx
|
||||||
|
|
||||||
|
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
|
||||||
|
; break conditions more gracefully.
|
||||||
|
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
|
||||||
|
|
||||||
|
start:
|
||||||
|
wait 0 pin 0 ; Stall until start bit is asserted
|
||||||
|
set x, 7 [10] ; Preload bit counter, then delay until halfway through
|
||||||
|
bitloop: ; the first data bit (12 cycles incl wait, set).
|
||||||
|
in pins, 1 ; Shift data bit into ISR
|
||||||
|
jmp x-- bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
|
||||||
|
jmp pin good_stop ; Check stop bit (should be high)
|
||||||
|
|
||||||
|
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
|
||||||
|
wait 1 pin 0 ; and wait for line to return to idle state.
|
||||||
|
jmp start ; Don't push data if we didn't see good framing.
|
||||||
|
|
||||||
|
good_stop: ; No delay before returning to start; a little slack is
|
||||||
|
push ; important in case the TX clock is slightly too fast.
|
||||||
|
|
||||||
|
|
||||||
|
% c-sdk {
|
||||||
|
static inline void ftdi_uart_rx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
|
||||||
|
pio_gpio_init(pio, pin);
|
||||||
|
gpio_pull_up(pin);
|
||||||
|
|
||||||
|
pio_sm_config c = ftdi_uart_rx_program_get_default_config(offset);
|
||||||
|
sm_config_set_in_pins(&c, pin); // for WAIT, IN
|
||||||
|
sm_config_set_jmp_pin(&c, pin); // for JMP
|
||||||
|
// Shift to right, autopull disabled
|
||||||
|
sm_config_set_in_shift(&c, true, false, 32);
|
||||||
|
// Deeper FIFO as we're not doing any TX
|
||||||
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
|
||||||
|
// SM transmits 1 bit per 8 execution cycles.
|
||||||
|
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
|
||||||
|
sm_config_set_clkdiv(&c, div);
|
||||||
|
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline char ftdi_uart_rx_program_getc(PIO pio, uint sm) {
|
||||||
|
// 8-bit read from the uppermost byte of the FIFO, as data is left-justified
|
||||||
|
io_rw_8 *rxfifo_shift = (io_rw_8*)&pio->rxf[sm] + 3;
|
||||||
|
while (pio_sm_is_rx_fifo_empty(pio, sm))
|
||||||
|
tight_loop_contents();
|
||||||
|
return (char)*rxfifo_shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
%}
|
|
@ -0,0 +1,61 @@
|
||||||
|
;
|
||||||
|
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
;
|
||||||
|
; SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
;
|
||||||
|
|
||||||
|
.program ftdi_uart_tx
|
||||||
|
.side_set 1 opt
|
||||||
|
|
||||||
|
; An 8n1 UART transmit program.
|
||||||
|
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
|
||||||
|
|
||||||
|
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
|
||||||
|
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
|
||||||
|
bitloop: ; This loop will run 8 times (8n1 UART)
|
||||||
|
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
|
||||||
|
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
|
||||||
|
|
||||||
|
|
||||||
|
% c-sdk {
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
|
||||||
|
static inline void ftdi_uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint baud) {
|
||||||
|
// Tell PIO to initially drive output-high on the selected pin, then map PIO
|
||||||
|
// onto that pin with the IO muxes.
|
||||||
|
pio_sm_set_pins_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx);
|
||||||
|
pio_sm_set_pindirs_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx);
|
||||||
|
pio_gpio_init(pio, pin_tx);
|
||||||
|
|
||||||
|
pio_sm_config c = ftdi_uart_tx_program_get_default_config(offset);
|
||||||
|
|
||||||
|
// OUT shifts to right, no autopull
|
||||||
|
sm_config_set_out_shift(&c, true, false, 32);
|
||||||
|
|
||||||
|
// We are mapping both OUT and side-set to the same pin, because sometimes
|
||||||
|
// we need to assert user data onto the pin (with OUT) and sometimes
|
||||||
|
// assert constant values (start/stop bit)
|
||||||
|
sm_config_set_out_pins(&c, pin_tx, 1);
|
||||||
|
sm_config_set_sideset_pins(&c, pin_tx);
|
||||||
|
|
||||||
|
// We only need TX, so get an 8-deep FIFO!
|
||||||
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||||
|
|
||||||
|
// SM transmits 1 bit per 8 execution cycles.
|
||||||
|
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
|
||||||
|
sm_config_set_clkdiv(&c, div);
|
||||||
|
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ftdi_uart_tx_program_putc(PIO pio, uint sm, char c) {
|
||||||
|
pio_sm_put_blocking(pio, sm, (uint32_t)c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void ftdi_uart_tx_program_puts(PIO pio, uint sm, const char *s) {
|
||||||
|
while (*s)
|
||||||
|
ftdi_uart_tx_program_putc(pio, sm, *s++);
|
||||||
|
}
|
||||||
|
|
||||||
|
%}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
void ftdi_if_mcuhost_init(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_mcuhost_deinit(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_mcuhost_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
(void)itf; (void)baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_mcuhost_flush(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_mcuhost_wait_io(struct ftdi_interface* itf, bool level) {
|
||||||
|
(void)itf; (void)level;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ftdi_if_mcuhost_read8(struct ftdi_interface* itf, uint8_t addr) {
|
||||||
|
(void)itf; (void)addr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint8_t ftdi_if_mcuhost_read16(struct ftdi_interface* itf, uint16_t addr) {
|
||||||
|
(void)itf; (void)addr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void ftdi_if_mcuhost_write8(struct ftdi_interface* itf, uint8_t addr, uint8_t value) {
|
||||||
|
(void)itf; (void)addr; (void)value;
|
||||||
|
}
|
||||||
|
void ftdi_if_mcuhost_write16(struct ftdi_interface* itf, uint16_t addr, uint8_t value) {
|
||||||
|
(void)itf; (void)addr; (void)value;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_init(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_deinit(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
(void)itf; (void)baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_flush(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_wait_io(struct ftdi_interface* itf, bool level) {
|
||||||
|
(void)itf; (void)level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_set_dirval_lo(struct ftdi_interface* itf, uint8_t dir, uint8_t val) {
|
||||||
|
(void)itf; (void)dir; (void)val;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_set_dirval_hi(struct ftdi_interface* itf, uint8_t dir, uint8_t val) {
|
||||||
|
(void)itf; (void)dir; (void)val;
|
||||||
|
}
|
||||||
|
uint8_t ftdi_if_mpsse_read_lo(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint8_t ftdi_if_mpsse_read_hi(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_loopback(struct ftdi_interface* itf, bool enable) {
|
||||||
|
(void)itf; (void)enable;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_set_clkdiv(struct ftdi_interface* itf, uint16_t div) {
|
||||||
|
(void)itf; (void)div;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ftdi_if_mpsse_xfer_bits(struct ftdi_interface* itf, int flags, size_t nbits, uint8_t value) {
|
||||||
|
(void)itf; (void)flags; (void)nbits; (void)value;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_xfer_bytes(struct ftdi_interface* itf, int flags, size_t nbytes, uint8_t* dst, const uint8_t* src) {
|
||||||
|
(void)itf; (void)flags; (void)nbytes; (void)dst; (void)src;
|
||||||
|
}
|
||||||
|
uint8_t ftdi_if_mpsse_tms_xfer(struct ftdi_interface* itf, int flags, size_t nbits, uint8_t value) {
|
||||||
|
(void)itf; (void)flags; (void)nbits; (void)value;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_div5(struct ftdi_interface* itf, bool enable) {
|
||||||
|
(void)itf; (void)enable;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_data_3ph(struct ftdi_interface* itf, bool enable) {
|
||||||
|
(void)itf; (void)enable;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_adaptive(struct ftdi_interface* itf, bool enable) {
|
||||||
|
(void)itf; (void)enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_clockonly(struct ftdi_interface* itf, uint32_t cycles) {
|
||||||
|
(void)itf; (void)cycles;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_clock_wait_io(struct ftdi_interface* itf, bool level) {
|
||||||
|
(void)itf; (void)level;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_clockonly_wait_io(struct ftdi_interface* itf, bool level, uint32_t cycles) {
|
||||||
|
(void)itf; (void)level; (void)cycles;
|
||||||
|
}
|
||||||
|
void ftdi_if_mpsse_hi_is_tristate(struct ftdi_interface* itf, uint16_t pinmask) {
|
||||||
|
(void)itf; (void)pinmask;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
|
||||||
|
#ifndef BSP_PINOUT_M_FTDI_H_
|
||||||
|
#define BSP_PINOUT_M_FTDI_H_
|
||||||
|
|
||||||
|
/* NOTE: no C code here! needs to be include-able from PIO asm! */
|
||||||
|
|
||||||
|
#define PINOUT_ITF_A_BASE 2
|
||||||
|
#define PINOUT_ITF_B_BASE 14
|
||||||
|
|
||||||
|
// TODO: ? or just different SMs on the same PIO? would complicate things tho
|
||||||
|
#define PINOUT_ITF_A_PIO pio0
|
||||||
|
#define PINOUT_ITF_B_PIO pio1
|
||||||
|
|
||||||
|
#define PINOUT_DBUS0_OFF 0
|
||||||
|
#define PINOUT_DBUS1_OFF 1
|
||||||
|
#define PINOUT_DBUS2_OFF 2
|
||||||
|
#define PINOUT_DBUS3_OFF 3
|
||||||
|
#define PINOUT_DBUS4_OFF 4
|
||||||
|
#define PINOUT_DBUS5_OFF 5
|
||||||
|
#define PINOUT_DBUS6_OFF 6
|
||||||
|
#define PINOUT_DBUS7_OFF 7
|
||||||
|
#define PINOUT_CBUS0_OFF 8
|
||||||
|
#define PINOUT_CBUS1_OFF 9
|
||||||
|
#define PINOUT_CBUS2_OFF 10
|
||||||
|
#define PINOUT_CBUS3_OFF 11
|
||||||
|
|
||||||
|
#define PINOUT_UART_TXD_OFF 0
|
||||||
|
#define PINOUT_UART_RXD_OFF 1
|
||||||
|
#define PINOUT_UART_nRTS_OFF 2
|
||||||
|
#define PINOUT_UART_nCTS_OFF 3
|
||||||
|
#define PINOUT_UART_nDTR_OFF 4
|
||||||
|
#define PINOUT_UART_nDSR_OFF 5
|
||||||
|
#define PINOUT_UART_nDCD_OFF 6
|
||||||
|
#define PINOUT_UART_nRI_OFF 7
|
||||||
|
#define PINOUT_UART_TXDEN_OFF 8
|
||||||
|
#define PINOUT_UART_nSLEEP_OFF 9
|
||||||
|
#define PINOUT_UART_nRXLED_OFF 10
|
||||||
|
#define PINOUT_UART_nTXLED_OFF 11
|
||||||
|
|
||||||
|
#define PINOUT_FIFO_D0_OFF 0
|
||||||
|
#define PINOUT_FIFO_D1_OFF 1
|
||||||
|
#define PINOUT_FIFO_D2_OFF 2
|
||||||
|
#define PINOUT_FIFO_D3_OFF 3
|
||||||
|
#define PINOUT_FIFO_D4_OFF 4
|
||||||
|
#define PINOUT_FIFO_D5_OFF 5
|
||||||
|
#define PINOUT_FIFO_D6_OFF 6
|
||||||
|
#define PINOUT_FIFO_D7_OFF 7
|
||||||
|
#define PINOUT_FIFO_nRXF_OFF 8
|
||||||
|
#define PINOUT_FIFO_nTXE_OFF 9
|
||||||
|
#define PINOUT_FIFO_nRD_OFF 10
|
||||||
|
#define PINOUT_FIFO_WR_OFF 11
|
||||||
|
|
||||||
|
#define PINOUT_BBANG_D0_OFF 0
|
||||||
|
#define PINOUT_BBANG_D1_OFF 1
|
||||||
|
#define PINOUT_BBANG_D2_OFF 2
|
||||||
|
#define PINOUT_BBANG_D3_OFF 3
|
||||||
|
#define PINOUT_BBANG_D4_OFF 4
|
||||||
|
#define PINOUT_BBANG_D5_OFF 5
|
||||||
|
#define PINOUT_BBANG_D6_OFF 6
|
||||||
|
#define PINOUT_BBANG_D7_OFF 7
|
||||||
|
#define PINOUT_BBANG_nWR0_OFF 8
|
||||||
|
#define PINOUT_BBANG_nRD0_OFF 9
|
||||||
|
#define PINOUT_BBANG_nWR1_OFF 10
|
||||||
|
#define PINOUT_BBANG_nRD1_OFF 11
|
||||||
|
|
||||||
|
#define PINOUT_MPSSE_TCK_CK_OFF 0
|
||||||
|
#define PINOUT_MPSSE_TDI_DO_OFF 1
|
||||||
|
#define PINOUT_MPSSE_TDO_DI_OFF 2
|
||||||
|
#define PINOUT_MPSSE_TMS_CS_OFF 3
|
||||||
|
#define PINOUT_MPSSE_GPIOL0_OFF 4
|
||||||
|
#define PINOUT_MPSSE_GPIOL1_OFF 5
|
||||||
|
#define PINOUT_MPSSE_GPIOL2_OFF 6
|
||||||
|
#define PINOUT_MPSSE_GPIOL3_OFF 7
|
||||||
|
#define PINOUT_MPSSE_GPIOH0_OFF 8
|
||||||
|
#define PINOUT_MPSSE_GPIOH1_OFF 9
|
||||||
|
#define PINOUT_MPSSE_GPIOH2_OFF 10
|
||||||
|
#define PINOUT_MPSSE_GPIOH3_OFF 11
|
||||||
|
|
||||||
|
#define PINOUT_MCUHOST_AD0_OFF 0
|
||||||
|
#define PINOUT_MCUHOST_AD1_OFF 1
|
||||||
|
#define PINOUT_MCUHOST_AD2_OFF 2
|
||||||
|
#define PINOUT_MCUHOST_AD3_OFF 3
|
||||||
|
#define PINOUT_MCUHOST_AD4_OFF 4
|
||||||
|
#define PINOUT_MCUHOST_AD5_OFF 5
|
||||||
|
#define PINOUT_MCUHOST_AD6_OFF 6
|
||||||
|
#define PINOUT_MCUHOST_AD7_OFF 7
|
||||||
|
#define PINOUT_MCUHOST_IO0_OFF 8
|
||||||
|
#define PINOUT_MCUHOST_IO1_OFF 9
|
||||||
|
#define PINOUT_MCUHOST_IORDY_OFF 10
|
||||||
|
#define PINOUT_MCUHOST_OSC_OFF 11
|
||||||
|
// ---
|
||||||
|
#define PINOUT_MCUHOST_A8_OFF 0
|
||||||
|
#define PINOUT_MCUHOST_A9_OFF 1
|
||||||
|
#define PINOUT_MCUHOST_AA_OFF 2
|
||||||
|
#define PINOUT_MCUHOST_AB_OFF 3
|
||||||
|
#define PINOUT_MCUHOST_AC_OFF 4
|
||||||
|
#define PINOUT_MCUHOST_AD_OFF 5
|
||||||
|
#define PINOUT_MCUHOST_AE_OFF 6
|
||||||
|
#define PINOUT_MCUHOST_AF_OFF 7
|
||||||
|
#define PINOUT_MCUHOST_nCS_OFF 8
|
||||||
|
#define PINOUT_MCUHOST_ALE_OFF 9
|
||||||
|
#define PINOUT_MCUHOST_nRD_OFF 10
|
||||||
|
#define PINOUT_MCUHOST_nWR_OFF 11
|
||||||
|
|
||||||
|
#define PINOUT_CPUFIFO_D0_OFF 0
|
||||||
|
#define PINOUT_CPUFIFO_D1_OFF 1
|
||||||
|
#define PINOUT_CPUFIFO_D2_OFF 2
|
||||||
|
#define PINOUT_CPUFIFO_D3_OFF 3
|
||||||
|
#define PINOUT_CPUFIFO_D4_OFF 4
|
||||||
|
#define PINOUT_CPUFIFO_D5_OFF 5
|
||||||
|
#define PINOUT_CPUFIFO_D6_OFF 6
|
||||||
|
#define PINOUT_CPUFIFO_D7_OFF 7
|
||||||
|
#define PINOUT_CPUFIFO_nCS_OFF 8
|
||||||
|
#define PINOUT_CPUFIFO_A0_OFF 9
|
||||||
|
#define PINOUT_CPUFIFO_nRD_OFF 10
|
||||||
|
#define PINOUT_CPUFIFO_nWR_OFF 11
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
void ftdi_if_syncbb_init(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_syncbb_deinit(struct ftdi_interface* itf) {
|
||||||
|
(void)itf;
|
||||||
|
}
|
||||||
|
void ftdi_if_syncbb_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
(void)itf; (void)baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ftdi_if_syncbb_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize) {
|
||||||
|
|
||||||
|
}
|
||||||
|
size_t ftdi_if_syncbb_read(struct ftdi_interface* itf, uint8_t* data, size_t maxsize) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include <hardware/dma.h>
|
||||||
|
#include <hardware/gpio.h>
|
||||||
|
#include <hardware/pio.h>
|
||||||
|
#include <hardware/structs/dma.h>
|
||||||
|
|
||||||
|
#include "m_ftdi/pinout.h"
|
||||||
|
#include "m_ftdi/ftdi_hw.h"
|
||||||
|
|
||||||
|
#include "ftdi_uart_rx.pio.h"
|
||||||
|
#include "ftdi_uart_tx.pio.h"
|
||||||
|
|
||||||
|
struct uart_state {
|
||||||
|
uint32_t baudrate;
|
||||||
|
bool enabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct uart_state state[2] = {
|
||||||
|
(struct uart_state){ .baudrate = 115200, .enabled = false },
|
||||||
|
(struct uart_state){ .baudrate = 115200, .enabled = false },
|
||||||
|
};
|
||||||
|
|
||||||
|
#define STATEOF(itf) (state[(itf)->index & 1])
|
||||||
|
|
||||||
|
// set up PIO->dma_in_buf DMA:
|
||||||
|
// * src=pio dst=buf 8bit 256words pacing=pio
|
||||||
|
// * IRQ: set overrun bit
|
||||||
|
// * start it
|
||||||
|
// * SETUP AT MODE ENTER, STOP AT MODE EXIT
|
||||||
|
//
|
||||||
|
// set up dma_out_buf->PIO DMA:
|
||||||
|
// * src=buf dst=pio 8bit <num>words pacing=pio
|
||||||
|
// * ~~no IRQ I think?~~ IRQ: next ringbuffer part (see below)
|
||||||
|
// * DO NOT start it on mode enter!
|
||||||
|
// * STOP AT MODE EXIT
|
||||||
|
//
|
||||||
|
// read routine:
|
||||||
|
// * abort DMA
|
||||||
|
// * copy data from dma_in_buf (read xfer len? == remaining of 256 bytes)
|
||||||
|
// * resetup & start DMA:
|
||||||
|
//
|
||||||
|
// write routine:
|
||||||
|
// * if DMA running: set overrun bit, bail out?
|
||||||
|
// * should use ringbuffer-like structure
|
||||||
|
// * pointers: dma start, dma end, data end (after dma, contiguous)
|
||||||
|
// * dma end can be calculated from DMA MMIO, but, race conditions so no
|
||||||
|
// * use DMA IRQ for next block (and wraparound: dma cannot do wraparound manually)
|
||||||
|
// * do not start next block if data end == dma start
|
||||||
|
// * can we set DMA xfer len while in-flight? datasheet p92 2.5.1: nope. sad
|
||||||
|
// * only bail out when data end == dma start - 1
|
||||||
|
// * copy data to dma_out_buf
|
||||||
|
// * set up & start DMA
|
||||||
|
//
|
||||||
|
// * what with buffers larger than 256 bytes?
|
||||||
|
// * just drop for now ig
|
||||||
|
// * what is the actual FTDI buffer size??
|
||||||
|
// * device-dependent so aaaa
|
||||||
|
// * which bits get set on errors?
|
||||||
|
// * do TCIFLUSH/TCOFLUSH influence these buffers, or only the USB proto
|
||||||
|
// handling buffers?
|
||||||
|
|
||||||
|
void ftdi_if_uart_init(struct ftdi_interface* itf) {
|
||||||
|
if (STATEOF(itf).enabled) return; // shrug
|
||||||
|
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
if (!ftdihw_dma_ch_init(&hw->rx, hw, &ftdi_uart_rx_program)) return;
|
||||||
|
if (!ftdihw_dma_ch_init(&hw->tx, hw, &ftdi_uart_tx_program)) {
|
||||||
|
ftdihw_dma_ch_deinit(&hw->rx, hw);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pin_rx = hw->pinbase + PINOUT_UART_RXD_OFF,
|
||||||
|
pin_tx = hw->pinbase + PINOUT_UART_TXD_OFF;
|
||||||
|
|
||||||
|
ftdi_uart_rx_program_init(hw->pio, hw->rx.piosm, hw->rx.prg_off,
|
||||||
|
pin_rx, STATEOF(itf).baudrate);
|
||||||
|
ftdi_uart_tx_program_init(hw->pio, hw->tx.piosm, hw->tx.prg_off,
|
||||||
|
pin_tx, STATEOF(itf).baudrate);
|
||||||
|
|
||||||
|
gpio_set_function(pin_rx, GPIO_FUNC_PIO0 + itf->index);
|
||||||
|
gpio_set_function(pin_tx, GPIO_FUNC_PIO0 + itf->index);
|
||||||
|
|
||||||
|
ftdihw_dma_rx_setup(hw, true);
|
||||||
|
ftdihw_dma_tx_setup(hw, false);
|
||||||
|
|
||||||
|
STATEOF(itf).enabled = true;
|
||||||
|
}
|
||||||
|
void ftdi_if_uart_deinit(struct ftdi_interface* itf) {
|
||||||
|
if (!STATEOF(itf).enabled) return; // shrug
|
||||||
|
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
int pin_rx = hw->pinbase + PINOUT_UART_RXD_OFF,
|
||||||
|
pin_tx = hw->pinbase + PINOUT_UART_TXD_OFF;
|
||||||
|
|
||||||
|
ftdihw_dma_rx_flush(hw);
|
||||||
|
ftdihw_dma_tx_flush(hw);
|
||||||
|
|
||||||
|
ftdihw_dma_ch_deinit(&hw->rx, hw);
|
||||||
|
ftdihw_dma_ch_deinit(&hw->tx, hw);
|
||||||
|
|
||||||
|
gpio_set_function(pin_rx, GPIO_FUNC_NULL);
|
||||||
|
gpio_set_function(pin_tx, GPIO_FUNC_NULL);
|
||||||
|
gpio_set_pulls(pin_rx, false, false);
|
||||||
|
gpio_set_pulls(pin_tx, false, false);
|
||||||
|
|
||||||
|
STATEOF(itf).enabled = false;
|
||||||
|
}
|
||||||
|
void ftdi_if_uart_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate) {
|
||||||
|
if (!STATEOF(itf).enabled) return;
|
||||||
|
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
int pin_rx = ftdihw_itf_to_base(itf) + PINOUT_UART_RXD_OFF,
|
||||||
|
pin_tx = ftdihw_itf_to_base(itf) + PINOUT_UART_TXD_OFF;
|
||||||
|
|
||||||
|
ftdi_uart_rx_program_init(hw->pio, hw->rx.piosm, hw->rx.prg_off, pin_rx, baudrate);
|
||||||
|
ftdi_uart_tx_program_init(hw->pio, hw->tx.piosm, hw->tx.prg_off, pin_tx, baudrate);
|
||||||
|
|
||||||
|
STATEOF(itf).baudrate = baudrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ftdi_if_set_flowctrl(struct ftdi_interface* itf, enum ftdi_flowctrl flow) {
|
||||||
|
(void)itf; (void)flow; // TODO: bluh
|
||||||
|
}
|
||||||
|
void ftdi_if_set_lineprop(struct ftdi_interface* itf, enum ftdi_sio_lineprop lineprop) {
|
||||||
|
(void)itf; (void)lineprop; // TODO: break, stop, parity, #bits
|
||||||
|
}
|
||||||
|
|
||||||
|
void ftdi_if_uart_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize) {
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
ftdihw_dma_write(hw, data, datasize);
|
||||||
|
}
|
||||||
|
size_t ftdi_if_uart_read(struct ftdi_interface* itf, uint8_t* data, size_t maxsize) {
|
||||||
|
struct ftdi_hw* hw = ftdihw_itf_to_hw(itf);
|
||||||
|
|
||||||
|
return ftdihw_dma_read(hw, data, maxsize);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _PICO_ASSERT_H
|
||||||
|
#define _PICO_ASSERT_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#else
|
||||||
|
#include <assert.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLE_ALL, Global assert enable, type=bool, default=0, group=pico_base
|
||||||
|
// PICO_CONFIG: PARAM_ASSERTIONS_DISABLE_ALL, Global assert disable, type=bool, default=0, group=pico_base
|
||||||
|
|
||||||
|
#ifndef PARAM_ASSERTIONS_ENABLE_ALL
|
||||||
|
#define PARAM_ASSERTIONS_ENABLE_ALL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PARAM_ASSERTIONS_DISABLE_ALL
|
||||||
|
#define PARAM_ASSERTIONS_DISABLE_ALL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PARAM_ASSERTIONS_ENABLED(x) ((PARAM_ASSERTIONS_ENABLED_ ## x || PARAM_ASSERTIONS_ENABLE_ALL) && !PARAM_ASSERTIONS_DISABLE_ALL)
|
||||||
|
|
||||||
|
#define invalid_params_if(x, test) ({if (PARAM_ASSERTIONS_ENABLED(x)) assert(!(test));})
|
||||||
|
#define valid_params_if(x, test) ({if (PARAM_ASSERTIONS_ENABLED(x)) assert(test);})
|
||||||
|
#define hard_assert_if(x, test) ({if (PARAM_ASSERTIONS_ENABLED(x)) hard_assert(!(test), #x);})
|
||||||
|
|
||||||
|
#ifdef NDEBUG
|
||||||
|
/*extern void hard_assertion_failure(void);
|
||||||
|
static inline void hard_assert(bool condition, ...) {
|
||||||
|
if (!condition)
|
||||||
|
hard_assertion_failure();
|
||||||
|
}*/
|
||||||
|
#define hard_assert(cond, ...) do { if (!(cond)) panic("Hard assert: " __FILE__); } while (0)
|
||||||
|
#else
|
||||||
|
#define hard_assert assert
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
|
@ -52,6 +52,12 @@ def dpctl_do(args: Any) -> int:
|
||||||
return 1
|
return 1
|
||||||
return devcmds.sump_overclock_set(conn, oven)
|
return devcmds.sump_overclock_set(conn, oven)
|
||||||
|
|
||||||
|
def sto_info(conn, args):
|
||||||
|
return devcmds.storage_info(conn)
|
||||||
|
def sto_flush(conn, args):
|
||||||
|
return devcmds.storage_flush(conn)
|
||||||
|
def sto_get(conn, args):
|
||||||
|
return devcmds.storage_get(conn, args.mode)
|
||||||
|
|
||||||
#print(repr(args))
|
#print(repr(args))
|
||||||
cmds = {
|
cmds = {
|
||||||
|
@ -59,6 +65,9 @@ def dpctl_do(args: Any) -> int:
|
||||||
'get-mode-info': get_mode_info,
|
'get-mode-info': get_mode_info,
|
||||||
'set-mode': set_mode,
|
'set-mode': set_mode,
|
||||||
'bootloader': bootloader,
|
'bootloader': bootloader,
|
||||||
|
'storage-info': sto_info,
|
||||||
|
'storage-flush': sto_flush,
|
||||||
|
'storage-get': sto_get,
|
||||||
|
|
||||||
'uart-cts-rts': uart_hw_flowctl,
|
'uart-cts-rts': uart_hw_flowctl,
|
||||||
'tempsensor': tempsensor,
|
'tempsensor': tempsensor,
|
||||||
|
@ -142,6 +151,15 @@ def main() -> int:
|
||||||
|
|
||||||
bootloader = subcmds.add_parser("bootloader", help="Set the device in bootloader mode")
|
bootloader = subcmds.add_parser("bootloader", help="Set the device in bootloader mode")
|
||||||
|
|
||||||
|
# persistent storage commands
|
||||||
|
storagehdr = subcmds.add_parser("storage-info", help="Get persistent storage info")
|
||||||
|
|
||||||
|
storageflush = subcmds.add_parser("storage-flush", help="Flush persistent storage data to storage medium")
|
||||||
|
|
||||||
|
storageget = subcmds.add_parser("storage-get", help="Get data of a particular mode")
|
||||||
|
storageget.add_argument('mode', default=None, nargs='?',
|
||||||
|
help="Mode to get data of. Defaults to the current mode, 'all' means all modes.")
|
||||||
|
|
||||||
# mode 1 commands
|
# mode 1 commands
|
||||||
usbhwfctl = subcmds.add_parser("uart-cts-rts", help="Get, enable/disable"+\
|
usbhwfctl = subcmds.add_parser("uart-cts-rts", help="Get, enable/disable"+\
|
||||||
" UART hardware flow control")
|
" UART hardware flow control")
|
||||||
|
|
|
@ -85,6 +85,56 @@ def set_mode(dev: DPDevice, mode: int) -> int:
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
|
|
||||||
|
def storage_info(dev: DPDevice) -> int:
|
||||||
|
try:
|
||||||
|
res = dev.storage_info()
|
||||||
|
print("magic: %s, version=%04x, current mode=%d, #modes=%d, DJB2(table)=%d" \
|
||||||
|
% (('OK' if res.magic == b'\xf0\x9f\x8f\xb3\xef\xb8\x8f\xe2\x80\x8d\xe2\x9a\xa7\xef\xb8\x8f' else 'BAD!'),
|
||||||
|
res.version, res.curmode, res.nmodes, res.table_djb2))
|
||||||
|
for md in res.mode_data:
|
||||||
|
print("\tmode %d version %04x: 0x%x..+0x%x, DJB2=%d" % \
|
||||||
|
(md.mode, md.version, md.offset, md.datasize, md.data_djb2))
|
||||||
|
if len(res.mode_data) == 0:
|
||||||
|
print("No mode data");
|
||||||
|
return 0
|
||||||
|
except Exception as e:
|
||||||
|
print("Could not get storage info: %s" % str(e))
|
||||||
|
traceback.print_exc()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def storage_flush(dev: DPDevice) -> int:
|
||||||
|
try:
|
||||||
|
res = dev.storage_flush()
|
||||||
|
print("storage saved" if res else "no write needed")
|
||||||
|
return 0
|
||||||
|
except Exception as e:
|
||||||
|
print("Could not flush persistent storage: %s" % str(e))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
def storage_get(dev: DPDevice, mode: str) -> int:
|
||||||
|
try:
|
||||||
|
if mode == 'all':
|
||||||
|
for m in dev.mode_info.keys():
|
||||||
|
res = dev.storage_get(mode)
|
||||||
|
print(repr(res)) # TODO
|
||||||
|
return 0
|
||||||
|
elif mode is None:
|
||||||
|
mode = dev.current_mode
|
||||||
|
else: mode = int(mode,0)
|
||||||
|
|
||||||
|
res = dev.storage_get(mode)
|
||||||
|
print(repr(res)) # TODO
|
||||||
|
return 0
|
||||||
|
except Exception as e:
|
||||||
|
print("Could not get storage data of mode %d: %s" % (mode, str(e)))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# ---
|
||||||
|
|
||||||
|
|
||||||
def uart_hw_flowctl_get(dev: DPDevice) -> int:
|
def uart_hw_flowctl_get(dev: DPDevice) -> int:
|
||||||
try:
|
try:
|
||||||
res = dev.m1_usb_hw_flowctl_get()
|
res = dev.m1_usb_hw_flowctl_get()
|
||||||
|
|
|
@ -32,10 +32,12 @@ class UsbConn(DevConn):
|
||||||
import usb, usb.core
|
import usb, usb.core
|
||||||
|
|
||||||
cfg = dev.get_active_configuration()
|
cfg = dev.get_active_configuration()
|
||||||
|
#print("get active", cfg)
|
||||||
|
|
||||||
if cfg is None: # should be configured already, but eh
|
if cfg is None: # should be configured already, but eh
|
||||||
dev.set_configuration()
|
dev.set_configuration()
|
||||||
cfg = dev.get_active_configuration()
|
cfg = dev.get_active_configuration()
|
||||||
|
#print("set active", cfg)
|
||||||
|
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
return "Couldn't get or set device configuration, aaaaa"
|
return "Couldn't get or set device configuration, aaaaa"
|
||||||
|
@ -45,6 +47,7 @@ class UsbConn(DevConn):
|
||||||
if i.bInterfaceClass == usb.CLASS_VENDOR_SPEC and
|
if i.bInterfaceClass == usb.CLASS_VENDOR_SPEC and
|
||||||
i.bInterfaceSubClass == UsbConn._SUBCLASS and
|
i.bInterfaceSubClass == UsbConn._SUBCLASS and
|
||||||
i.bInterfaceProtocol == UsbConn._PROTOCOL]
|
i.bInterfaceProtocol == UsbConn._PROTOCOL]
|
||||||
|
#print("vnd itf", itf)
|
||||||
|
|
||||||
if len(itf) == 0:
|
if len(itf) == 0:
|
||||||
return "No vendor control interface found for device"
|
return "No vendor control interface found for device"
|
||||||
|
@ -58,12 +61,18 @@ class UsbConn(DevConn):
|
||||||
epin = usb.util.find_descriptor(itf, custom_match =
|
epin = usb.util.find_descriptor(itf, custom_match =
|
||||||
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
|
lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
|
||||||
|
|
||||||
|
#print("epout", epout, "epin", epin)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# try to read the version number. if it throws, it means the usbdev
|
# try to read the version number. if it throws, it means the usbdev
|
||||||
# is in use by something else
|
# is in use by something else
|
||||||
|
#print("write")
|
||||||
epout.write(b'\x00')
|
epout.write(b'\x00')
|
||||||
|
#print("wrote")
|
||||||
resp = epin.read(4)
|
resp = epin.read(4)
|
||||||
except usb.core.USBError:
|
#print("resp", resp)
|
||||||
|
except usb.core.USBError as e:
|
||||||
|
print("eep", e)
|
||||||
return "Device is busy, already used by something else? (If you use "+\
|
return "Device is busy, already used by something else? (If you use "+\
|
||||||
"the kernel module, use a character device from /dev instead.)"
|
"the kernel module, use a character device from /dev instead.)"
|
||||||
|
|
||||||
|
@ -77,6 +86,7 @@ class UsbConn(DevConn):
|
||||||
if verno > DevConn._VER_MAX:
|
if verno > DevConn._VER_MAX:
|
||||||
return "Version of device (%04x) too new, must be max. %04x" \
|
return "Version of device (%04x) too new, must be max. %04x" \
|
||||||
% (hex(verno, DevConn._VER_MAX))
|
% (hex(verno, DevConn._VER_MAX))
|
||||||
|
#print("verno", verno)
|
||||||
|
|
||||||
return UsbConn(dev, cfg, itf, epin, epout)
|
return UsbConn(dev, cfg, itf, epin, epout)
|
||||||
|
|
||||||
|
@ -89,6 +99,8 @@ class UsbConn(DevConn):
|
||||||
if dev is None or len(dev) != 1:
|
if dev is None or len(dev) != 1:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
#print("devs", dev)
|
||||||
|
|
||||||
rv = UsbConn._open_dev(dev[0])
|
rv = UsbConn._open_dev(dev[0])
|
||||||
return None if isinstance(rv, str) else rv
|
return None if isinstance(rv, str) else rv
|
||||||
|
|
||||||
|
@ -127,6 +139,8 @@ class UsbConn(DevConn):
|
||||||
if conntup is None:
|
if conntup is None:
|
||||||
return "Could not open USB device '%s': not recognised" % conn
|
return "Could not open USB device '%s': not recognised" % conn
|
||||||
|
|
||||||
|
#print("conntup", conntup)
|
||||||
|
|
||||||
dev = None
|
dev = None
|
||||||
if conn_busdev:
|
if conn_busdev:
|
||||||
if len(conntup) == 2:
|
if len(conntup) == 2:
|
||||||
|
@ -148,9 +162,12 @@ class UsbConn(DevConn):
|
||||||
return UsbConn._open_dev(dev[0])
|
return UsbConn._open_dev(dev[0])
|
||||||
|
|
||||||
def read_raw(self, arr) -> int:
|
def read_raw(self, arr) -> int:
|
||||||
return self._epin.read(arr)
|
rv = self._epin.read(arr)
|
||||||
|
print("read", arr[:rv])
|
||||||
|
return rv
|
||||||
|
|
||||||
def write_raw(self, b: bytes) -> int:
|
def write_raw(self, b: bytes) -> int:
|
||||||
|
print("write", b)
|
||||||
return self._epout.write(b)
|
return self._epout.write(b)
|
||||||
|
|
||||||
def __init__(self, dev, cfg, itf, epin, epout):
|
def __init__(self, dev, cfg, itf, epin, epout):
|
||||||
|
@ -168,6 +185,7 @@ class UsbConn(DevConn):
|
||||||
|
|
||||||
usb.util.release_interface(self._dev, self._itf)
|
usb.util.release_interface(self._dev, self._itf)
|
||||||
usb.util.dispose_resources(self._dev)
|
usb.util.dispose_resources(self._dev)
|
||||||
|
#print("released & disposed")
|
||||||
self._epout = None
|
self._epout = None
|
||||||
self._epin = None
|
self._epin = None
|
||||||
self._itf = None
|
self._itf = None
|
||||||
|
@ -205,6 +223,7 @@ class ChardevConn(DevConn):
|
||||||
"a Linux kernel module" % sys.platform
|
"a Linux kernel module" % sys.platform
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
#print("try char", conn)
|
||||||
fd = os.open(conn, os.O_RDWR)
|
fd = os.open(conn, os.O_RDWR)
|
||||||
if fd < 0:
|
if fd < 0:
|
||||||
raise OSError("Negative file descriptor returned")
|
raise OSError("Negative file descriptor returned")
|
||||||
|
|
|
@ -18,6 +18,56 @@ STAT_BADARG = 0x04
|
||||||
STAT_ILLSTATE = 0x05
|
STAT_ILLSTATE = 0x05
|
||||||
|
|
||||||
|
|
||||||
|
class StorageInfoMode(NamedTuple):
|
||||||
|
version: int
|
||||||
|
datasize: int
|
||||||
|
offset: int
|
||||||
|
mode: int
|
||||||
|
data_djb2: int
|
||||||
|
|
||||||
|
def from_bytes(b: bytes) -> StorageInfoMode:
|
||||||
|
assert len(b) == 12
|
||||||
|
v, ds, oam, d = struct.unpack('<HHII', b)
|
||||||
|
return StorageInfoMode(v, ds, oam & ((1<<28)-1), (oam >> 28) & 15, d)
|
||||||
|
|
||||||
|
def list_from_bytes(b: bytes) -> List[StorageInfoMode]:
|
||||||
|
nelem = len(b) // 12
|
||||||
|
assert nelem * 12 == len(b)
|
||||||
|
|
||||||
|
r = [None]*nelem
|
||||||
|
for i in range(nelem): r[i] = StorageInfoMode.from_bytes(b[(i*12):((i+1)*12)])
|
||||||
|
return [re for re in r if re.version != 0xffff and re.datasize != 0xffff]
|
||||||
|
|
||||||
|
|
||||||
|
class StorageInfo(NamedTuple):
|
||||||
|
magic: bytes
|
||||||
|
version: int
|
||||||
|
curmode: int
|
||||||
|
nmodes: int
|
||||||
|
reserved: bytes
|
||||||
|
table_djb2: int
|
||||||
|
mode_data: List[StorageInfoMode]
|
||||||
|
|
||||||
|
def from_bytes(b: bytes) -> StorageInfo:
|
||||||
|
assert len(b) == 256
|
||||||
|
|
||||||
|
mag = b[:16]
|
||||||
|
ver, cm, nm = struct.unpack('<HBB', b[16:20])
|
||||||
|
res = b[20:28] + b[245-32:]
|
||||||
|
d2tab = struct.unpack('<I', b[28:32])[0]
|
||||||
|
|
||||||
|
mdat = StorageInfoMode.list_from_bytes(b[32:256-32])
|
||||||
|
|
||||||
|
assert len(mdat) == nm
|
||||||
|
|
||||||
|
return StorageInfo(mag, ver, cm, nm, res, d2tab, mdat)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "StorageInfo(magic=%s, version=%d, curmode=%d, nmodes=%d, table_djb2=%d, mode_data=%s)" \
|
||||||
|
% (' '.join(hex(b) for b in self.magic), self.version,
|
||||||
|
self.curmode, self.nmodes, self.table_djb2, repr(self.mode_data))
|
||||||
|
|
||||||
|
|
||||||
class JtagMatch(NamedTuple):
|
class JtagMatch(NamedTuple):
|
||||||
tck: int
|
tck: int
|
||||||
tms: int
|
tms: int
|
||||||
|
@ -144,11 +194,11 @@ class DPDevice:
|
||||||
|
|
||||||
if (plen & 0x80) != 0:
|
if (plen & 0x80) != 0:
|
||||||
plen &= 0x7f
|
plen &= 0x7f
|
||||||
plen |= self.read(1) << 7
|
plen |= self.read(1)[0] << 7
|
||||||
|
|
||||||
if (plen & 0x4000) != 0:
|
if (plen & 0x4000) != 0:
|
||||||
plen &= 0x3fff
|
plen &= 0x3fff
|
||||||
plen |= self.read(1) << 14
|
plen |= self.read(1)[0] << 14
|
||||||
|
|
||||||
bs = self.read(plen)
|
bs = self.read(plen)
|
||||||
#print("==> got resp %d res %s" % (resp, repr(bs)))
|
#print("==> got resp %d res %s" % (resp, repr(bs)))
|
||||||
|
@ -236,6 +286,32 @@ class DPDevice:
|
||||||
|
|
||||||
return { i for i in range(0, 8) if (pl[0] & (1<<i)) != 0 }
|
return { i for i in range(0, 8) if (pl[0] & (1<<i)) != 0 }
|
||||||
|
|
||||||
|
# persistent storage
|
||||||
|
|
||||||
|
def storage_info(self) -> StorageInfo:
|
||||||
|
cmd = bytearray(b'\x0c')
|
||||||
|
self.write(b'\x0c')
|
||||||
|
stat, pl = self.read_resp()
|
||||||
|
check_statpl(stat, pl, "get storage info", 256, 256)
|
||||||
|
|
||||||
|
return StorageInfo.from_bytes(pl)
|
||||||
|
|
||||||
|
def storage_flush(self) -> bool:
|
||||||
|
self.write(b'\x0e')
|
||||||
|
stat, pl = self.read_resp()
|
||||||
|
check_statpl(stat, pl, "flush storage", 1, 1)
|
||||||
|
return pl[0]
|
||||||
|
|
||||||
|
def storage_get(self, mode: int) -> bytes:
|
||||||
|
cmd = bytearray(b'\x0d\x00')
|
||||||
|
cmd[1] = mode
|
||||||
|
self.write(cmd)
|
||||||
|
stat, pl = self.read_resp()
|
||||||
|
check_statpl(stat, pl, "get storage data", None, None)
|
||||||
|
|
||||||
|
return pl # TODO: parse
|
||||||
|
|
||||||
|
|
||||||
# mode 1 commands
|
# mode 1 commands
|
||||||
|
|
||||||
def m1_usb_hw_flowctl_get(self) -> bool:
|
def m1_usb_hw_flowctl_get(self) -> bool:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
_storage_test/
|
|
@ -3,6 +3,7 @@
|
||||||
#include <tusb.h>
|
#include <tusb.h>
|
||||||
|
|
||||||
#include "mode.h"
|
#include "mode.h"
|
||||||
|
#include "storage.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "usbstdio.h"
|
#include "usbstdio.h"
|
||||||
#include "vnd_cfg.h"
|
#include "vnd_cfg.h"
|
||||||
|
@ -35,6 +36,8 @@ enum m_default_feature {
|
||||||
mdef_feat_tempsense = 1<<4,
|
mdef_feat_tempsense = 1<<4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool data_dirty = false;
|
||||||
|
|
||||||
#ifdef DBOARD_HAS_UART
|
#ifdef DBOARD_HAS_UART
|
||||||
static cothread_t uartthread;
|
static cothread_t uartthread;
|
||||||
static uint8_t uartstack[THREAD_STACK_SIZE];
|
static uint8_t uartstack[THREAD_STACK_SIZE];
|
||||||
|
@ -81,6 +84,17 @@ static void enter_cb(void) {
|
||||||
serprogthread = co_derive(serprogstack, sizeof serprogstack, serprog_thread_fn);
|
serprogthread = co_derive(serprogstack, sizeof serprogstack, serprog_thread_fn);
|
||||||
thread_enter(serprogthread); // will call cdc_serprog_init() on correct thread
|
thread_enter(serprogthread); // will call cdc_serprog_init() on correct thread
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!data_dirty) { // only read when not read yet
|
||||||
|
struct mode_info mi = storage_mode_get_info(1);
|
||||||
|
if (mi.size != 0 && mi.version == 0x0010 /* TODO: version migration? */) {
|
||||||
|
uint8_t dst[2];
|
||||||
|
storage_mode_read(1, dst, 0, 2);
|
||||||
|
|
||||||
|
cdc_uart_set_hwflow(dst[0]);
|
||||||
|
tempsense_set_addr(dst[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
static void leave_cb(void) {
|
static void leave_cb(void) {
|
||||||
// TODO: CMSISDAP?
|
// TODO: CMSISDAP?
|
||||||
|
@ -144,7 +158,12 @@ static void handle_cmd_cb(uint8_t cmd) {
|
||||||
break;
|
break;
|
||||||
case mdef_cmd_tempsense:
|
case mdef_cmd_tempsense:
|
||||||
#ifdef DBOARD_HAS_TEMPSENSOR
|
#ifdef DBOARD_HAS_TEMPSENSOR
|
||||||
tempsense_bulk_cmd();
|
{
|
||||||
|
uint8_t addra = tempsense_get_addr();
|
||||||
|
tempsense_bulk_cmd();
|
||||||
|
uint8_t addrb = tempsense_get_addr();
|
||||||
|
data_dirty |= addra != addrb;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
vnd_cfg_write_str(cfg_resp_illcmd, "temperature sensor not implemented on this device");
|
vnd_cfg_write_str(cfg_resp_illcmd, "temperature sensor not implemented on this device");
|
||||||
#endif
|
#endif
|
||||||
|
@ -156,9 +175,10 @@ static void handle_cmd_cb(uint8_t cmd) {
|
||||||
resp = cdc_uart_get_hwflow() ? 1 : 0;
|
resp = cdc_uart_get_hwflow() ? 1 : 0;
|
||||||
vnd_cfg_write_resp(cfg_resp_ok, 1, &resp);
|
vnd_cfg_write_resp(cfg_resp_ok, 1, &resp);
|
||||||
} else {
|
} else {
|
||||||
if (cdc_uart_set_hwflow(resp != 0))
|
if (cdc_uart_set_hwflow(resp != 0)) {
|
||||||
vnd_cfg_write_resp(cfg_resp_ok, 0, NULL);
|
vnd_cfg_write_resp(cfg_resp_ok, 0, NULL);
|
||||||
else
|
data_dirty = true;
|
||||||
|
} else
|
||||||
vnd_cfg_write_str(cfg_resp_illcmd, "UART flow control setting not supported on this device");
|
vnd_cfg_write_str(cfg_resp_illcmd, "UART flow control setting not supported on this device");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
@ -387,6 +407,19 @@ static bool my_vendor_control_xfer_cb(uint8_t rhport, uint8_t ep_addr,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static uint16_t my_get_size(void) { return 2; }
|
||||||
|
static void my_get_data(void* dst, size_t offset, size_t maxsize) {
|
||||||
|
(void)offset; (void)maxsize;
|
||||||
|
|
||||||
|
uint8_t* d = dst;
|
||||||
|
|
||||||
|
d[0] = cdc_uart_get_hwflow() ? 1 : 0;
|
||||||
|
d[1] = tempsense_get_addr();
|
||||||
|
|
||||||
|
data_dirty = false;
|
||||||
|
}
|
||||||
|
static bool my_is_dirty(void) { return data_dirty; }
|
||||||
|
|
||||||
extern struct mode m_01_default;
|
extern struct mode m_01_default;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
struct mode m_01_default = {
|
struct mode m_01_default = {
|
||||||
|
@ -402,6 +435,13 @@ struct mode m_01_default = {
|
||||||
.task = task_cb,
|
.task = task_cb,
|
||||||
.handle_cmd = handle_cmd_cb,
|
.handle_cmd = handle_cmd_cb,
|
||||||
|
|
||||||
|
.storage = {
|
||||||
|
.stclass = mode_storage_32b,
|
||||||
|
.get_size = my_get_size,
|
||||||
|
.get_data = my_get_data,
|
||||||
|
.is_dirty = my_is_dirty
|
||||||
|
},
|
||||||
|
|
||||||
#if defined(DBOARD_HAS_CMSISDAP) && CFG_TUD_HID > 0
|
#if defined(DBOARD_HAS_CMSISDAP) && CFG_TUD_HID > 0
|
||||||
#if 0
|
#if 0
|
||||||
.tud_hid_get_report_cb = my_hid_get_report_cb,
|
.tud_hid_get_report_cb = my_hid_get_report_cb,
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "tusb_config.h"
|
||||||
|
#include <tusb.h>
|
||||||
|
|
||||||
|
#include "mode.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "usbstdio.h"
|
||||||
|
#include "vnd_cfg.h"
|
||||||
|
|
||||||
|
#include "m_ftdi/bsp-feature.h"
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
static bool data_dirty = false;
|
||||||
|
void ftdi_eeprom_dirty_set(bool v) { (void)v; data_dirty = true; }
|
||||||
|
bool ftdi_eeprom_dirty_get(void) { return data_dirty; }
|
||||||
|
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
static cothread_t ftdithread_ifa, ftdithread_ifb;
|
||||||
|
static uint8_t ftdistack_ifa[THREAD_STACK_SIZE>>1], ftdistack_ifb[THREAD_STACK_SIZE>>1];
|
||||||
|
|
||||||
|
static void ftdi_thread_fn_ifa(void) {
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
printf("fn ifa thread!\n");
|
||||||
|
//ftdi_task_ifa();
|
||||||
|
thread_yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void ftdi_thread_fn_ifb(void) {
|
||||||
|
while (1) {
|
||||||
|
printf("fn ifb thread!\n");
|
||||||
|
//ftdi_task_ifb();
|
||||||
|
thread_yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void enter_cb(void) {
|
||||||
|
printf("mode5 enter begin VND_N_CONFIG=%d\n", VND_N_CFG);
|
||||||
|
|
||||||
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
|
stdio_usb_set_itf_num(CDC_N_STDIO);
|
||||||
|
#endif
|
||||||
|
//vnd_cfg_set_itf_num(VND_N_CFG);
|
||||||
|
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
/*ftdithread_ifa = co_derive(ftdistack_ifa, sizeof ftdistack_ifa, ftdi_thread_fn_ifa);
|
||||||
|
ftdithread_ifb = co_derive(ftdistack_ifb, sizeof ftdistack_ifb, ftdi_thread_fn_ifb);*/
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*if (!data_dirty) {
|
||||||
|
struct mode_info mi = storage_mode_get_info(5);
|
||||||
|
if (mi.size != 0 && mi.version == 0x0010) {
|
||||||
|
storage_mode_read(5, ftdi_eeprom, 0, sizeof ftdi_eeprom);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
//ftdi_init();
|
||||||
|
printf("mode5 enter end\n");
|
||||||
|
}
|
||||||
|
static void leave_cb(void) {
|
||||||
|
printf("mode5 leave\n");
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
//ftdi_deinit();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void task_cb(void) {
|
||||||
|
printf("mode5 task\n");
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
//tud_task();
|
||||||
|
//thread_enter(ftdithread_ifa);
|
||||||
|
//tud_task();
|
||||||
|
//thread_enter(ftdithread_ifb);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_cmd_cb(uint8_t cmd) {
|
||||||
|
printf("mode5 handlecmd %02x\n", cmd);
|
||||||
|
uint8_t resp = 0;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case mode_cmd_get_features:
|
||||||
|
resp = 0; // TODO: what do we put here?
|
||||||
|
vnd_cfg_write_resp(cfg_resp_ok, 1, &resp);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
vnd_cfg_write_strf(cfg_resp_illcmd, "unknown mode5 command %02x", cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum {
|
||||||
|
STRID_LANGID = 0,
|
||||||
|
STRID_MANUFACTURER,
|
||||||
|
STRID_PRODUCT,
|
||||||
|
STRID_SERIAL,
|
||||||
|
|
||||||
|
STRID_CONFIG,
|
||||||
|
|
||||||
|
STRID_IF_VND_CFG,
|
||||||
|
STRID_IF_VND_FTDI_IFA,
|
||||||
|
STRID_IF_VND_FTDI_IFB,
|
||||||
|
STRID_IF_CDC_STDIO,
|
||||||
|
};
|
||||||
|
enum {
|
||||||
|
/*ITF_NUM_VND_FTDI_IFA,
|
||||||
|
ITF_NUM_VND_FTDI_IFB,*/
|
||||||
|
|
||||||
|
#if CFG_TUD_VENDOR > 0
|
||||||
|
ITF_NUM_VND_CFG,
|
||||||
|
#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
|
||||||
|
/*+ TUD_VENDOR_DESC_LEN
|
||||||
|
+ TUD_VENDOR_DESC_LEN*/
|
||||||
|
#if CFG_TUD_VENDOR > 0
|
||||||
|
+ TUD_VENDOR_DESC_LEN
|
||||||
|
#endif
|
||||||
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
|
+ TUD_CDC_DESC_LEN
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/*#define EPNUM_VND_FTDI_IFA_OUT 0x02
|
||||||
|
#define EPNUM_VND_FTDI_IFA_IN 0x81
|
||||||
|
#define EPNUM_VND_FTDI_IFB_OUT 0x04
|
||||||
|
#define EPNUM_VND_FTDI_IFB_IN 0x83
|
||||||
|
|
||||||
|
#define EPNUM_VND_CFG_OUT 0x05
|
||||||
|
#define EPNUM_VND_CFG_IN 0x85
|
||||||
|
#define EPNUM_CDC_STDIO_OUT 0x06
|
||||||
|
#define EPNUM_CDC_STDIO_IN 0x86
|
||||||
|
#define EPNUM_CDC_STDIO_NOTIF 0x87*/
|
||||||
|
|
||||||
|
#define EPNUM_VND_CFG_OUT 0x01
|
||||||
|
#define EPNUM_VND_CFG_IN 0x81
|
||||||
|
#define EPNUM_CDC_STDIO_OUT 0x02
|
||||||
|
#define EPNUM_CDC_STDIO_IN 0x82
|
||||||
|
#define EPNUM_CDC_STDIO_NOTIF 0x83
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
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),
|
||||||
|
|
||||||
|
/*TUD_VENDOR_DESCRIPTOR(ITF_NUM_VND_FTDI_IFA, STRID_IF_VND_FTDI_IFA,
|
||||||
|
EPNUM_VND_FTDI_IFA_OUT, EPNUM_VND_FTDI_IFA_IN, CFG_TUD_VENDOR_RX_BUFSIZE),
|
||||||
|
TUD_VENDOR_DESCRIPTOR(ITF_NUM_VND_FTDI_IFB, STRID_IF_VND_FTDI_IFB,
|
||||||
|
EPNUM_VND_FTDI_IFB_OUT, EPNUM_VND_FTDI_IFB_IN, CFG_TUD_VENDOR_RX_BUFSIZE),*/
|
||||||
|
|
||||||
|
#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 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_VND_FTDI_IFA] = "DragonProbe FT2232D interface A",
|
||||||
|
[STRID_IF_VND_FTDI_IFB] = "DragonProbe FT2232D interface B",
|
||||||
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
|
[STRID_IF_CDC_STDIO] = "stdio CDC interface (debug)",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
static tusb_desc_device_t desc_device = {
|
||||||
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
|
.bcdUSB = 0x0110, // TODO: 0x0200 ? is an eeprom option
|
||||||
|
.bDeviceClass = 0x00,
|
||||||
|
.bDeviceSubClass = 0x00,
|
||||||
|
.bDeviceProtocol = 0x00,
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
|
||||||
|
.idVendor = 0x0403, // ?
|
||||||
|
.idProduct = 0x6010, // ?
|
||||||
|
.bcdDevice = 0x0500, // required!
|
||||||
|
|
||||||
|
.iManufacturer = STRID_MANUFACTURER,
|
||||||
|
.iProduct = STRID_PRODUCT,
|
||||||
|
.iSerialNumber = STRID_SERIAL,
|
||||||
|
|
||||||
|
.bNumConfigurations = 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t* my_descriptor_device_cb(void) {
|
||||||
|
return (const uint8_t*)&desc_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CFG_TUD_CDC > 0
|
||||||
|
static void my_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding) {
|
||||||
|
printf("mode5 linecoding %02x, %lu\n", itf, line_coding->bit_rate);
|
||||||
|
switch (itf) {
|
||||||
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
|
case CDC_N_STDIO:
|
||||||
|
stdio_usb_line_coding_cb(line_coding);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static uint16_t my_get_size(void) { return sizeof ftdi_eeprom; }
|
||||||
|
static void my_get_data(void* dst, size_t offset, size_t maxsize) {
|
||||||
|
memcpy(dst, (const uint8_t*)ftdi_eeprom + offset, maxsize);
|
||||||
|
|
||||||
|
data_dirty = false;
|
||||||
|
}
|
||||||
|
static bool my_is_dirty(void) { return data_dirty; }
|
||||||
|
|
||||||
|
extern struct mode m_05_ftdi;
|
||||||
|
// clang-format off
|
||||||
|
struct mode m_05_ftdi = {
|
||||||
|
.name = "FTDI FT2232D emulation 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,
|
||||||
|
|
||||||
|
/*.storage = {
|
||||||
|
.stclass = mode_storage_512b,
|
||||||
|
.get_size = my_get_size,
|
||||||
|
.get_data = my_get_data,
|
||||||
|
.is_dirty = my_is_dirty
|
||||||
|
},*/
|
||||||
|
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
//.tud_descriptor_device_cb = my_descriptor_device_cb,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CFG_TUD_CDC > 0
|
||||||
|
.tud_cdc_line_coding_cb = my_cdc_line_coding_cb,
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
//.tud_vendor_control_xfer_cb = ftdi_control_xfer_cb,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
|
@ -0,0 +1,486 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "tusb_config.h"
|
||||||
|
#include <tusb.h>
|
||||||
|
|
||||||
|
#include "thread.h"
|
||||||
|
|
||||||
|
#include "m_ftdi/bsp-feature.h"
|
||||||
|
#include "m_ftdi/ftdi.h"
|
||||||
|
|
||||||
|
// index = interface number (A/B)
|
||||||
|
// out/in bRequest wValue wIndex data wLength
|
||||||
|
// RESET: out 0 0 index
|
||||||
|
// TCIFLUSH: out 0 2 index
|
||||||
|
// TCOFLUSH: out 0 1 index
|
||||||
|
// SETMODEMCTRL:out 1 (mask:8<<8 | data:8) // bit0=dtr bit1=rts
|
||||||
|
// SETFLOWCTRL: out 2 xon?1:0 (flowctrl | index) // flowctrl: 0=disable, 1=ctsrts, 2=dtrdsr 4=xonxoff FROM VALUE, not set/reset!
|
||||||
|
// SETBAUDRATE: out 3 brate brate24|index // 48 MHz clocks, /16? , baudrate is 24bit, highest byte in index MSB
|
||||||
|
// SETLINEPROP: out 4 (break:1<<14 | stop:2<<11 | parity:3<<8 | bits:8) ; break: off/on, stop=1/15/2, parity=none/odd/even/mark/space; bits=7/8
|
||||||
|
// POLLMODEMSTAT:in 5 0 index &modemstat len=2 // first byte: bit0..3=0 bit4=cts bit5=dts bit6=ri bit7=rlsd ; second byte: bit0=dr bit1=oe bit2=pe bit3=fe bit4=bi bit5=thre bit6=temt bit7=fifoerr
|
||||||
|
// SETEVENTCHAR:out 6 (endis:1<<8 | char)
|
||||||
|
// SETERRORCHAR:out 7 (endir:1<<8 | char)
|
||||||
|
// <there is no bReqest 8>
|
||||||
|
// SETLATENCY: out 9 latency(1..255)
|
||||||
|
// GETLATENCY: in 0xa 0 index &latency len=1
|
||||||
|
// SETBITBANG: out 0xb dirmask:8<<8 | mode:8 // mode: MPSSE mode: 0=serial/fifo, 1=bitbang, 2=mpsse, 4=syncbb, 8=mcu, 16=opto
|
||||||
|
// READPINS: in 0xc 0 index &pins len=1
|
||||||
|
// READEEPROM: in 0x90 0 eepaddr &val 2 // eepaddr in 16-bit units(?)
|
||||||
|
// WRITEEEPROM: out 0x91 value eepaddr
|
||||||
|
// ERASEEEPROM: out 0x92 0 0
|
||||||
|
|
||||||
|
// eeprom layout:
|
||||||
|
// max size is 128 words (256 bytes), little-endian
|
||||||
|
// 00: type, driver, .. stuff (chanA byte, chanB byte) -> TODO: what does this mean exactly?
|
||||||
|
// 0=UART 1=FIFO 2=opto 4=CPUFIFO 8=FT1284
|
||||||
|
// 01: VID
|
||||||
|
// 02: PID
|
||||||
|
// 03: bcdDevice
|
||||||
|
// 04: byte08: flags: bit7=1 bit6=1=selfpowered bit5=1=remotewakeup ; byte09=max power, 2mA units
|
||||||
|
// 05: bit0=epin isochr, bit1=epout isochr, bit2=suspend pulldn, bit3=serialno use, bit4=usbver change, bit5..7=0
|
||||||
|
// 06: bcdUSB
|
||||||
|
// 07: byte0E=manufstr.off-0x80, byte0F=manufstr.len (in bytes, but 16-bit ascii)
|
||||||
|
// 08: ^ but product
|
||||||
|
// 09: ^ but serial
|
||||||
|
// 0a: chip type (66 etc -> we emulate '00', internal)
|
||||||
|
// "strings start at 0x96 for ft2232c" (byte addr)
|
||||||
|
// checksum:
|
||||||
|
// initial: 0xaaaa
|
||||||
|
// digest 1 byte:
|
||||||
|
// checksum = (checksum ^ word) <<< 1;
|
||||||
|
// placed at last word of EEPROM
|
||||||
|
|
||||||
|
// ftdi_write_data(), ftdi_read_data(): bulk xfer
|
||||||
|
|
||||||
|
// input/tristate: 200K pullup
|
||||||
|
// SI/WU: let's ignore this
|
||||||
|
// UART mode: standard stuff
|
||||||
|
// FIFO mode: RDF#=0: enable output. RD# rising when RXF#=0: fetch next byte
|
||||||
|
// TXF#=0: enable input. WR falling when TXF#=0: write byte
|
||||||
|
// bitbang mode: ^ similar, no RDF#/TXF#, RD#/WR# (now WR# jenai WR) pos depends on UART mode std (UART vs FIFO)
|
||||||
|
// sync bitbang: doesn't use RD#/WR#, clocked by baudrate control
|
||||||
|
// MPSSE: lots of magic. TCK/SK, TDI/DO, TDO/DI, TMS/CS, GPIOL0..3, GPIOH0..3(7?)
|
||||||
|
|
||||||
|
// bulk xfer formats: (interface index <-> bulk epno)
|
||||||
|
// * UART: ok I guess
|
||||||
|
// * FIFO: same
|
||||||
|
// * bitbang, sync bitbang: ???? (just data or also clock stuff?? ?)
|
||||||
|
// * MPSSE, MCU: see separate PDF
|
||||||
|
// * CPUFIFIO: same as FIFO ig
|
||||||
|
// * opto, FT1284: not supported
|
||||||
|
|
||||||
|
#ifdef DBOARD_HAS_FTDI
|
||||||
|
|
||||||
|
int __builtin_ctz(unsigned int v);
|
||||||
|
|
||||||
|
uint16_t ftdi_eeprom[128];
|
||||||
|
|
||||||
|
struct ftdi_interface ftdi_ifa, ftdi_ifb;
|
||||||
|
|
||||||
|
void ftdi_init(void) {
|
||||||
|
// init eeprom defaults
|
||||||
|
memset(ftdi_eeprom, 0xff, sizeof ftdi_eeprom);
|
||||||
|
ftdi_eeprom[ 0] = 0x0000; // both default to UART
|
||||||
|
ftdi_eeprom[ 1] = 0x0403; // idVendor
|
||||||
|
ftdi_eeprom[ 2] = 0x6010; // idProduct
|
||||||
|
ftdi_eeprom[ 3] = 0x0500; // bcdDevice
|
||||||
|
ftdi_eeprom[ 4] = 0x5080; // 100 mA, no flags
|
||||||
|
ftdi_eeprom[ 5] = 0x0000; // more flags
|
||||||
|
ftdi_eeprom[ 6] = 0x0110; // bcdUSB
|
||||||
|
ftdi_eeprom[ 7] = 0x0000; // no manuf. str (TODO?)
|
||||||
|
ftdi_eeprom[ 8] = 0x0000; // no prod. str (TODO?)
|
||||||
|
ftdi_eeprom[ 9] = 0x0000; // no serial str (TODO?)
|
||||||
|
ftdi_eeprom[10] = 0x0000; // internal chip
|
||||||
|
ftdi_eeprom[0x7f] = ftdi_eeprom_checksum_calc(ftdi_eeprom, 0x7f);
|
||||||
|
|
||||||
|
memset(&ftdi_ifa, 0, sizeof ftdi_ifa);
|
||||||
|
memset(&ftdi_ifb, 0, sizeof ftdi_ifb);
|
||||||
|
ftdi_ifa.lineprop = sio_bits_8 | sio_stop_1; // 8n1
|
||||||
|
ftdi_ifb.lineprop = sio_bits_8 | sio_stop_1; // 8n1
|
||||||
|
ftdi_ifa.index = 0;
|
||||||
|
ftdi_ifb.index = 1;
|
||||||
|
ftdi_ifa.modemstat = sio_modem_temt;
|
||||||
|
ftdi_ifb.modemstat = sio_modem_temt;
|
||||||
|
ftdi_if_init(&ftdi_ifa);
|
||||||
|
ftdi_if_init(&ftdi_ifb);
|
||||||
|
}
|
||||||
|
void ftdi_deinit(void) {
|
||||||
|
ftdi_if_deinit(&ftdi_ifa);
|
||||||
|
ftdi_if_deinit(&ftdi_ifb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t vnd_read_byte(struct ftdi_interface* itf, int itfnum) {
|
||||||
|
while (itf->rxavail <= 0) {
|
||||||
|
if (!tud_vendor_n_mounted(itfnum) || !tud_vendor_n_available(itfnum)) {
|
||||||
|
thread_yield();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
itf->rxpos = 0;
|
||||||
|
itf->rxavail = tud_vendor_n_read(itfnum, itf->bufbuf, sizeof itf->bufbuf);
|
||||||
|
|
||||||
|
if (itf->rxavail == 0) thread_yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t rv = itf->bufbuf[itf->rxpos];
|
||||||
|
++itf->rxpos;
|
||||||
|
--itf->rxavail;
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void (*ftfifo_write_fn)(struct ftdi_interface*, const uint8_t*, size_t);
|
||||||
|
typedef size_t (*ftfifo_read_fn)(struct ftdi_interface*, uint8_t*, size_t);
|
||||||
|
struct ftfifo_fns {
|
||||||
|
ftfifo_write_fn write;
|
||||||
|
ftfifo_read_fn read ;
|
||||||
|
};
|
||||||
|
static const struct ftfifo_fns fifocbs[] = {
|
||||||
|
{ ftdi_if_uart_write, ftdi_if_uart_read }, // technically mpsse
|
||||||
|
{ ftdi_if_asyncbb_write, ftdi_if_asyncbb_read },
|
||||||
|
{ ftdi_if_syncbb_write, ftdi_if_syncbb_read },
|
||||||
|
{ NULL, NULL }, // mcuhost
|
||||||
|
{ ftdi_if_fifo_write, ftdi_if_fifo_read },
|
||||||
|
{ NULL, NULL }, // opto
|
||||||
|
{ ftdi_if_cpufifo_write, ftdi_if_cpufifo_read },
|
||||||
|
{ NULL, NULL }, // ft1284
|
||||||
|
};
|
||||||
|
|
||||||
|
// for handling USB bulk commands
|
||||||
|
static void ftdi_task(int itfnum) {
|
||||||
|
if (!tud_vendor_n_mounted(itfnum)) return; // can't do much in this case
|
||||||
|
|
||||||
|
struct ftdi_interface* itf;
|
||||||
|
if (itfnum == VND_N_FTDI_IFA) itf = &ftdi_ifa;
|
||||||
|
else if (itfnum == VND_N_FTDI_IFB) itf = &ftdi_ifb;
|
||||||
|
else return;
|
||||||
|
|
||||||
|
|
||||||
|
// for UART, FIFO, asyncbb, syncbb, and CPUfifo modes, operation is
|
||||||
|
// relatively straightforward: the device acts like some sort of FIFO, so
|
||||||
|
// it's just shoving bytes to the output pins. MPSSE and MCU host emulation
|
||||||
|
// modes are more difficult, as the bulk data actually has some form of
|
||||||
|
// protocol.
|
||||||
|
enum ftdi_mode mode = ftdi_if_get_mode(itf);
|
||||||
|
struct ftfifo_fns fifocb = fifocbs[(mode == 0) ? 0 : __builtin_ctz(mode)];
|
||||||
|
uint32_t avail;
|
||||||
|
uint8_t cmdbyte = 0;
|
||||||
|
switch (mode) {
|
||||||
|
case ftmode_uart : case ftmode_fifo : case ftmode_cpufifo:
|
||||||
|
case ftmode_asyncbb: case ftmode_syncbb:
|
||||||
|
if (fifocb.read == NULL || fifocb.write == NULL) goto CASE_DEFAULT; // welp
|
||||||
|
|
||||||
|
avail = tud_vendor_n_available(itfnum);
|
||||||
|
if (avail) {
|
||||||
|
tud_vendor_n_read(itfnum, itf->writebuf, avail);
|
||||||
|
fifocb.write(itf, itf->writebuf, avail);
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
avail = fifocb.read(itf, itf->readbuf, sizeof itf->readbuf);
|
||||||
|
|
||||||
|
if (avail) tud_vendor_n_write(itfnum, itf->readbuf, avail);
|
||||||
|
} while (avail == sizeof itf->readbuf);
|
||||||
|
break;
|
||||||
|
case ftmode_mpsse:
|
||||||
|
avail = 0;
|
||||||
|
|
||||||
|
switch ((cmdbyte = vnd_read_byte(itf, itfnum))) {
|
||||||
|
case ftmpsse_set_dirval_lo: // low byte of output gpio, not to low level
|
||||||
|
itf->writebuf[0] = vnd_read_byte(itf, itfnum); // val
|
||||||
|
itf->writebuf[1] = vnd_read_byte(itf, itfnum); // dir
|
||||||
|
ftdi_if_mpsse_set_dirval_lo(itf, itf->writebuf[1], itf->writebuf[0]);
|
||||||
|
break;
|
||||||
|
case ftmpsse_set_dirval_hi:
|
||||||
|
itf->writebuf[0] = vnd_read_byte(itf, itfnum); // val
|
||||||
|
itf->writebuf[1] = vnd_read_byte(itf, itfnum); // dir
|
||||||
|
ftdi_if_mpsse_set_dirval_hi(itf, itf->writebuf[1], itf->writebuf[0]);
|
||||||
|
break;
|
||||||
|
case ftmpsse_read_lo:
|
||||||
|
itf->readbuf[0] = ftdi_if_mpsse_read_lo(itf);
|
||||||
|
avail = 1;
|
||||||
|
break;
|
||||||
|
case ftmpsse_read_hi:
|
||||||
|
itf->readbuf[0] = ftdi_if_mpsse_read_hi(itf);
|
||||||
|
avail = 1;
|
||||||
|
break;
|
||||||
|
case ftmpsse_loopback_on : ftdi_if_mpsse_loopback(itf, true ); break;
|
||||||
|
case ftmpsse_loopback_off: ftdi_if_mpsse_loopback(itf, false); break;
|
||||||
|
case ftmpsse_set_clkdiv:
|
||||||
|
avail = vnd_read_byte(itf, itfnum);
|
||||||
|
avail |= (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
ftdi_if_mpsse_set_clkdiv(itf, (uint16_t)avail);
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ftmpsse_flush: ftdi_if_mpsse_flush(itf); break;
|
||||||
|
case ftmpsse_wait_io_hi: ftdi_if_mpsse_wait_io(itf, true ); break;
|
||||||
|
case ftmpsse_wait_io_lo: ftdi_if_mpsse_wait_io(itf, false); break;
|
||||||
|
|
||||||
|
case ftmpsse_div5_disable: ftdi_if_mpsse_div5(itf, false); break;
|
||||||
|
case ftmpsse_div5_enable : ftdi_if_mpsse_div5(itf, true ); break;
|
||||||
|
case ftmpsse_data_3ph_en : ftdi_if_mpsse_data_3ph(itf, true ); break;
|
||||||
|
case ftmpsse_data_3ph_dis: ftdi_if_mpsse_data_3ph(itf, false); break;
|
||||||
|
case ftmpsse_clockonly_bits: ftdi_if_mpsse_clockonly(itf, vnd_read_byte(itf, itfnum)); break;
|
||||||
|
case ftmpsse_clockonly_bytes:
|
||||||
|
avail = vnd_read_byte(itf, itfnum);
|
||||||
|
avail |= (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
ftdi_if_mpsse_clockonly(itf, avail);
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
case ftmpsse_clock_wait_io_hi: ftdi_if_mpsse_clock_wait_io(itf, true ); break;
|
||||||
|
case ftmpsse_clock_wait_io_lo: ftdi_if_mpsse_clock_wait_io(itf, false); break;
|
||||||
|
case ftmpsse_adapclk_enable : ftdi_if_mpsse_adaptive(itf, true ); break;
|
||||||
|
case ftmpsse_adapclk_disable: ftdi_if_mpsse_adaptive(itf, false); break;
|
||||||
|
case ftmpsse_clock_bits_wait_io_hi:
|
||||||
|
avail = vnd_read_byte(itf, itfnum);
|
||||||
|
avail |= (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
ftdi_if_mpsse_clockonly_wait_io(itf, true, avail);
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
case ftmpsse_clock_bits_wait_io_lo:
|
||||||
|
avail = vnd_read_byte(itf, itfnum);
|
||||||
|
avail |= (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
ftdi_if_mpsse_clockonly_wait_io(itf, false, avail);
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
case ftmpsse_hi_is_tristate:
|
||||||
|
avail = vnd_read_byte(itf, itfnum);
|
||||||
|
avail |= (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
ftdi_if_mpsse_hi_is_tristate(itf, avail);
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: CASE_DEFAULT:
|
||||||
|
if (!(cmdbyte & ftmpsse_specialcmd)) {
|
||||||
|
if (cmdbyte & ftmpsse_tmswrite) {
|
||||||
|
if (cmdbyte & ftmpsse_bitmode) {
|
||||||
|
itf->writebuf[0] = vnd_read_byte(itf, itfnum); // number of bits
|
||||||
|
itf->writebuf[1] = vnd_read_byte(itf, itfnum); // data bits to output
|
||||||
|
itf->readbuf[0] = ftdi_if_mpsse_tms_xfer(itf, cmdbyte, itf->writebuf[0], itf->writebuf[1]);
|
||||||
|
if (cmdbyte & ftmpsse_tdoread) avail = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// else: fallthru to error code
|
||||||
|
} else {
|
||||||
|
if (cmdbyte & ftmpsse_bitmode) {
|
||||||
|
itf->writebuf[0] = vnd_read_byte(itf, itfnum); // number of bits
|
||||||
|
if (cmdbyte & ftmpsse_tdiwrite)
|
||||||
|
itf->writebuf[1] = vnd_read_byte(itf, itfnum); // data bits to output
|
||||||
|
itf->readbuf[0] = ftdi_if_mpsse_xfer_bits(itf, cmdbyte, itf->writebuf[0], itf->writebuf[1]);
|
||||||
|
if (cmdbyte & ftmpsse_tdoread) avail = 1;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
avail = vnd_read_byte(itf, itfnum);
|
||||||
|
avail |= (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < avail; i += 64) {
|
||||||
|
uint32_t thisbatch = avail - i;
|
||||||
|
if (thisbatch > 64) thisbatch = 64;
|
||||||
|
for (size_t j = 0; j < thisbatch; ++j)
|
||||||
|
itf->writebuf[j] = vnd_read_byte(itf, itfnum);
|
||||||
|
ftdi_if_mpsse_xfer_bytes(itf, cmdbyte, thisbatch, itf->readbuf, itf->writebuf);
|
||||||
|
tud_vendor_n_write(itfnum, itf->readbuf, thisbatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
itf->readbuf[0] = 0xfa;
|
||||||
|
itf->readbuf[1] = cmdbyte;
|
||||||
|
avail = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avail) tud_vendor_n_write(itfnum, itf->readbuf, avail);
|
||||||
|
break;
|
||||||
|
case ftmode_mcuhost:
|
||||||
|
avail = 0;
|
||||||
|
switch ((cmdbyte = vnd_read_byte(itf, itfnum))) {
|
||||||
|
case ftmcu_flush: ftdi_if_mcuhost_flush(itf); break;
|
||||||
|
case ftmcu_wait_io_hi: ftdi_if_mcuhost_wait_io(itf, true ); break;
|
||||||
|
case ftmcu_wait_io_lo: ftdi_if_mcuhost_wait_io(itf, false); break;
|
||||||
|
|
||||||
|
case ftmcu_read8:
|
||||||
|
itf->readbuf[0] = ftdi_if_mcuhost_read8(itf, vnd_read_byte(itf, itfnum));
|
||||||
|
avail = 1;
|
||||||
|
break;
|
||||||
|
case ftmcu_read16:
|
||||||
|
avail = (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
avail |= vnd_read_byte(itf, itfnum);
|
||||||
|
itf->readbuf[0] = ftdi_if_mcuhost_read16(itf, (uint16_t)avail);
|
||||||
|
avail = 1;
|
||||||
|
break;
|
||||||
|
case ftmcu_write8:
|
||||||
|
itf->writebuf[0] = vnd_read_byte(itf, itfnum);
|
||||||
|
itf->writebuf[1] = vnd_read_byte(itf, itfnum);
|
||||||
|
ftdi_if_mcuhost_write8(itf, itf->writebuf[0], itf->writebuf[1]);
|
||||||
|
break;
|
||||||
|
case ftmcu_write16:
|
||||||
|
avail = (uint32_t)vnd_read_byte(itf, itfnum) << 8;
|
||||||
|
avail |= vnd_read_byte(itf, itfnum);
|
||||||
|
itf->writebuf[0] = vnd_read_byte(itf, itfnum);
|
||||||
|
ftdi_if_mcuhost_write8(itf, avail, itf->writebuf[0]);
|
||||||
|
avail = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: // send error response when command doesn't exist
|
||||||
|
itf->readbuf[0] = 0xfa;
|
||||||
|
itf->readbuf[1] = cmdbyte;
|
||||||
|
avail = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avail) tud_vendor_n_write(itfnum, itf->readbuf, avail);
|
||||||
|
break;
|
||||||
|
default: // drop incoming data so that the pipes don't get clogged. can't do much else
|
||||||
|
avail = tud_vendor_n_available(itfnum);
|
||||||
|
if (avail) tud_vendor_n_read(itfnum, itf->writebuf, avail);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void ftdi_task_ifa(void) { ftdi_task(VND_N_FTDI_IFA); }
|
||||||
|
void ftdi_task_ifb(void) { ftdi_task(VND_N_FTDI_IFB); }
|
||||||
|
|
||||||
|
#define FT2232D_CLOCK (48*1000*1000)
|
||||||
|
|
||||||
|
uint32_t ftdi_if_decode_baudrate(uint32_t enc_brate) { // basically reversing libftdi ftdi_to_clkbits
|
||||||
|
static const uint8_t ftdi_brate_frac_lut[8] = { 0, 4, 2, 1, 3, 5, 6, 7 };
|
||||||
|
|
||||||
|
// special cases
|
||||||
|
if (enc_brate == 0) return FT2232D_CLOCK >> 4;
|
||||||
|
else if (enc_brate == 1) return FT2232D_CLOCK / 24;
|
||||||
|
else if (enc_brate == 2) return FT2232D_CLOCK >> 5;
|
||||||
|
|
||||||
|
uint32_t div = (enc_brate & 0x7fff) << 3; // integer part
|
||||||
|
div = div | ftdi_brate_frac_lut[(enc_brate >> 14) & 7];
|
||||||
|
uint32_t baud = FT2232D_CLOCK / div;
|
||||||
|
|
||||||
|
if (baud & 1) baud = (baud >> 1) + 1; // raunding
|
||||||
|
else baud >>= 1;
|
||||||
|
return baud;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t control_buf[2];
|
||||||
|
|
||||||
|
bool ftdi_control_xfer_cb(uint8_t rhport, uint8_t stage,
|
||||||
|
tusb_control_request_t const* req) {
|
||||||
|
// return true: don't stall
|
||||||
|
// return false: stall
|
||||||
|
|
||||||
|
// not a vendor request -> not meant for this code
|
||||||
|
if (req->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR) return true;
|
||||||
|
|
||||||
|
// data stage not needed: data stages only for device->host xfers, never host->device
|
||||||
|
if (stage != CONTROL_STAGE_SETUP) return true;
|
||||||
|
|
||||||
|
// tud_control_status(rhport, req); : acknowledge
|
||||||
|
// tud_control_xfer(rhport, req, bufaddr, size); : ack + there's a data phase with stuff to do
|
||||||
|
// write: send bufaddr value. read: data stage will have bufaddr filled out
|
||||||
|
|
||||||
|
// do EEPROM stuff first, as these don't use wIndex as channel select
|
||||||
|
uint16_t tmp16;
|
||||||
|
switch (req->bRequest) {
|
||||||
|
case sio_readeeprom:
|
||||||
|
tmp16 = ftdi_eeprom[req->wIndex & 0x7f];
|
||||||
|
control_buf[0] = tmp16 & 0xff;
|
||||||
|
control_buf[1] = tmp16 >> 8;
|
||||||
|
return tud_control_xfer(rhport, req, control_buf, 2);
|
||||||
|
case sio_writeeeprom:
|
||||||
|
ftdi_eeprom[req->wIndex & 0x7f] = req->wValue;
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_eraseeeprom:
|
||||||
|
memset(ftdi_eeprom, 0xff, sizeof ftdi_eeprom);
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
int itfnum = req->wIndex & 0xff;
|
||||||
|
struct ftdi_interface* itf = &ftdi_ifa; // default
|
||||||
|
|
||||||
|
if (itfnum > 2) return false; // bad interface number
|
||||||
|
else if (itfnum == 1) itf = &ftdi_ifa;
|
||||||
|
else if (itfnum == 2) itf = &ftdi_ifb;
|
||||||
|
|
||||||
|
switch (req->bRequest) {
|
||||||
|
case sio_cmd:
|
||||||
|
if (req->wValue == sio_reset) {
|
||||||
|
itf->modem_mask = 0;
|
||||||
|
itf->modem_data = 0;
|
||||||
|
itf->flow = ftflow_none;
|
||||||
|
itf->lineprop = sio_bits_8 | sio_stop_1; // 8n1
|
||||||
|
itf->charen = 0;
|
||||||
|
itf->bb_dir = 0;
|
||||||
|
itf->bb_mode = sio_mode_reset;
|
||||||
|
itf->mcu_addr_latch = 0;
|
||||||
|
itf->rxavail = 0; itf->rxpos = 0;
|
||||||
|
ftdi_if_sio_reset(itf);
|
||||||
|
} else if (req->wValue == sio_tciflush) {
|
||||||
|
itf->rxavail = 0; itf->rxpos = 0;
|
||||||
|
ftdi_if_sio_tciflush(itf);
|
||||||
|
} else if (req->wValue == sio_tcoflush) {
|
||||||
|
// nothing extra to clear here I think
|
||||||
|
ftdi_if_sio_tcoflush(itf);
|
||||||
|
} else return false; // unk
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_setmodemctrl:
|
||||||
|
ftdi_if_set_modemctrl(itf,
|
||||||
|
itf->modem_mask = (req->wValue >> 8),
|
||||||
|
itf->modem_data = (req->wValue & 0xff));
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_setflowctrl: {
|
||||||
|
enum ftdi_flowctrl flow = (req->wIndex >> 8);
|
||||||
|
if (!req->wValue)
|
||||||
|
flow = (flow & ~ftflow_xonxoff) | (itf->flow & ftflow_xonxoff);
|
||||||
|
ftdi_if_set_flowctrl(itf, itf->flow = flow);
|
||||||
|
} return tud_control_status(rhport, req);
|
||||||
|
case sio_setbaudrate: {
|
||||||
|
uint32_t enc_brate = (uint32_t)req->wValue | ((uint32_t)(req->wIndex & 0xff00) << 8);
|
||||||
|
uint32_t brate = ftdi_if_decode_baudrate(enc_brate);
|
||||||
|
ftdi_if_set_baudrate(itf, itf->baudrate = brate);
|
||||||
|
} return tud_control_status(rhport, req);
|
||||||
|
case sio_setlineprop:
|
||||||
|
ftdi_if_set_lineprop(itf, itf->lineprop = req->wValue);
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_pollmodemstat:
|
||||||
|
tmp16 = ftdi_if_poll_modemstat(itf);
|
||||||
|
control_buf[0] = tmp16 & 0xff;
|
||||||
|
control_buf[1] = tmp16 >> 8;
|
||||||
|
return tud_control_xfer(rhport, req, control_buf, 2);
|
||||||
|
case sio_seteventchar:
|
||||||
|
if (req->wValue >> 8) itf->charen |= eventchar_enable;
|
||||||
|
else if (itf->charen & eventchar_enable) itf->charen ^= eventchar_enable;
|
||||||
|
ftdi_if_set_eventchar(itf, req->wValue >> 8,
|
||||||
|
itf->eventchar = (req->wValue & 0xff));
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_seterrorchar:
|
||||||
|
if (req->wValue >> 8) itf->charen |= errorchar_enable;
|
||||||
|
else if (itf->charen & errorchar_enable) itf->charen ^= errorchar_enable;
|
||||||
|
ftdi_if_set_errorchar(itf, req->wValue >> 8,
|
||||||
|
itf->errorchar = (req->wValue & 0xff));
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_setlatency:
|
||||||
|
ftdi_if_set_latency(itf, itf->latency = (req->wValue & 0xff));
|
||||||
|
return tud_control_status(rhport, req);
|
||||||
|
case sio_getlatency:
|
||||||
|
control_buf[0] = ftdi_if_get_latency(itf);
|
||||||
|
return tud_control_xfer(rhport, req, control_buf, 1);
|
||||||
|
case sio_setbitbang: {
|
||||||
|
uint8_t olddir = itf->bb_dir;
|
||||||
|
enum ftdi_sio_bitmode oldmode = itf->bb_mode;
|
||||||
|
ftdi_if_set_bitbang(itf,
|
||||||
|
itf->bb_dir = (req->wValue >> 8),
|
||||||
|
itf->bb_mode = (req->wValue & 0xff),
|
||||||
|
olddir, oldmode);
|
||||||
|
} return tud_control_status(rhport, req);
|
||||||
|
case sio_readpins:
|
||||||
|
control_buf[0] = ftdi_if_read_pins(itf);
|
||||||
|
return tud_control_xfer(rhport, req, control_buf, 1);
|
||||||
|
default: return false; // stall if not recognised
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* DBOARD_HAS_FTDI */
|
||||||
|
|
|
@ -0,0 +1,375 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#ifndef FTDI_H_
|
||||||
|
#define FTDI_H_
|
||||||
|
|
||||||
|
#include "tusb_config.h"
|
||||||
|
#include <tusb.h>
|
||||||
|
|
||||||
|
// USB command handling, and mode interfacing stuff
|
||||||
|
|
||||||
|
void ftdi_init(void);
|
||||||
|
void ftdi_deinit(void);
|
||||||
|
|
||||||
|
// have separate tasks for the separate bulk endpoints so that a wait-for-data
|
||||||
|
// in one ep won't cause the otehr to stall, too
|
||||||
|
void ftdi_task_ifa(void);
|
||||||
|
void ftdi_task_ifb(void);
|
||||||
|
|
||||||
|
bool ftdi_control_xfer_cb(uint8_t rhport, uint8_t ep_addr,
|
||||||
|
tusb_control_request_t const* req);
|
||||||
|
|
||||||
|
void ftdi_eeprom_dirty_set(bool v);
|
||||||
|
bool ftdi_eeprom_dirty_get(void);
|
||||||
|
|
||||||
|
extern uint16_t ftdi_eeprom[128];
|
||||||
|
|
||||||
|
#define FTDI_EEP_IFA_MODE (ftdi_eeprom[0] & 0xff)
|
||||||
|
#define FTDI_EEP_IFB_MODE (ftdi_eeprom[0] >> 8)
|
||||||
|
#define FTDI_EEP_IDVENDOR (ftdi_eeprom[1])
|
||||||
|
#define FTDI_EEP_IDPRODUCT (ftdi_eeprom[2])
|
||||||
|
#define FTDI_EEP_BCDDEVICE (ftdi_eeprom[3])
|
||||||
|
#define FTDI_EEP_PWFLAGS (ftdi_eeprom[4] & 0xff)
|
||||||
|
#define FTDI_EEP_MAXAMP (ftdi_eeprom[4] >> 8)
|
||||||
|
#define FTDI_EEP_USBFLAGS (ftdi_eeprom[5])
|
||||||
|
#define FTDI_EEP_BCDUSB (ftdi_eeprom[6])
|
||||||
|
#define FTDI_EEP_MANUF_OFF (ftdi_eeprom[7] & 0xff)
|
||||||
|
#define FTDI_EEP_MANUF_LEN (ftdi_eeprom[7] >> 8)
|
||||||
|
#define FTDI_EEP_PROD_OFF (ftdi_eeprom[8] & 0xff)
|
||||||
|
#define FTDI_EEP_PROD_LEN (ftdi_eeprom[8] >> 8)
|
||||||
|
#define FTDI_EEP_SERIAL_OFF (ftdi_eeprom[9] & 0xff)
|
||||||
|
#define FTDI_EEP_SERIAL_LEN (ftdi_eeprom[9] >> 8)
|
||||||
|
#define FTDI_EEP_CHIPTYPE (ftdi_eeprom[10])
|
||||||
|
|
||||||
|
static inline uint16_t ftdi_eeprom_checksum_init(void) {
|
||||||
|
return 0xaaaa;
|
||||||
|
}
|
||||||
|
static inline uint16_t ftdi_eeprom_checksum_digest(uint16_t val, const uint16_t* data, size_t len) {
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
val = (val ^ data[i]);
|
||||||
|
val = (val << 1) | (val >> 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
static inline uint16_t ftdi_eeprom_checksum_calc(const uint16_t* data, size_t len) {
|
||||||
|
return ftdi_eeprom_checksum_digest(ftdi_eeprom_checksum_init(), data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// USB protocol stuff
|
||||||
|
|
||||||
|
enum ftdi_request {
|
||||||
|
sio_cmd = 0,
|
||||||
|
sio_setmodemctrl = 1,
|
||||||
|
sio_setflowctrl = 2,
|
||||||
|
sio_setbaudrate = 3,
|
||||||
|
sio_setlineprop = 4,
|
||||||
|
sio_pollmodemstat = 5,
|
||||||
|
sio_seteventchar = 6,
|
||||||
|
sio_seterrorchar = 7,
|
||||||
|
|
||||||
|
sio_setlatency = 9,
|
||||||
|
sio_getlatency = 10,
|
||||||
|
sio_setbitbang = 11,
|
||||||
|
sio_readpins = 12,
|
||||||
|
|
||||||
|
sio_readeeprom = 0x90,
|
||||||
|
sio_writeeeprom = 0x91,
|
||||||
|
sio_eraseeeprom = 0x92
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ftdi_sio_cmd {
|
||||||
|
sio_reset = 0,
|
||||||
|
sio_tciflush = 2,
|
||||||
|
sio_tcoflush = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ftdi_sio_lineprop {
|
||||||
|
sio_break_on = 1<<14,
|
||||||
|
sio_break_off = 0<<14,
|
||||||
|
sio_break__mask = 1<<14,
|
||||||
|
|
||||||
|
sio_stop_1 = 0<<11,
|
||||||
|
sio_stop_15 = 1<<11, // deprecated?
|
||||||
|
sio_stop_2 = 2<<11,
|
||||||
|
sio_stop__mask = 3<<11,
|
||||||
|
|
||||||
|
sio_parity_none = 0<<8,
|
||||||
|
sio_parity_odd = 1<<8,
|
||||||
|
sio_parity_even = 2<<8,
|
||||||
|
sio_parity_mark = 3<<8,
|
||||||
|
sio_parity_space = 4<<8,
|
||||||
|
sio_parity__mask = 7<<8,
|
||||||
|
|
||||||
|
sio_bits_7 = 7<<0,
|
||||||
|
sio_bits_8 = 8<<0,
|
||||||
|
sio_bits__mask = 0xff<<0,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ftdi_sio_modemstat {
|
||||||
|
sio_modem_cts = 1<< 4, // Clear to Send active
|
||||||
|
sio_modem_dts = 1<< 5, // Data Set Ready active
|
||||||
|
sio_modem_ri = 1<< 6, // Ring Indicator active
|
||||||
|
sio_modem_rlsd = 1<< 7, // Receive Line Signal Detect active
|
||||||
|
|
||||||
|
sio_modem_dr = 1<< 8, // Data Ready
|
||||||
|
sio_modem_oe = 1<< 9, // Overrun Error
|
||||||
|
sio_modem_pe = 1<<10, // Parity Error
|
||||||
|
sio_modem_fe = 1<<11, // Framing Error
|
||||||
|
sio_modem_bi = 1<<12, // Break Interrupt
|
||||||
|
sio_modem_thre = 1<<13, // Transmitter Holding REgister
|
||||||
|
sio_modem_temt = 1<<14, // Transmitter Empty
|
||||||
|
sio_modem_fifoerr = 1<<15 // Error in receive FIFO
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ftdi_sio_bitmode {
|
||||||
|
sio_mode_reset = 0, // i.e. from EEPROM
|
||||||
|
sio_mode_bitbang = 1,
|
||||||
|
sio_mode_mpsse = 2,
|
||||||
|
sio_mode_syncbb = 4,
|
||||||
|
sio_mode_mcu = 8,
|
||||||
|
|
||||||
|
// 0x10: opto
|
||||||
|
// 0x20: cbus bitbang (R-type only)
|
||||||
|
// 0x40: sync fifo (2232h) // like regular fifo mode, but with clock output
|
||||||
|
// 0x80: ft1284 (232h, not 2232d)
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ftdi_eep_defmode {
|
||||||
|
fteep_mode_uart = 0,
|
||||||
|
fteep_mode_fifo = 1,
|
||||||
|
fteep_mode_opto = 2, // not implementing this here
|
||||||
|
fteep_mode_cpu = 4,
|
||||||
|
fteep_mode_ft1284 = 8, // not impl. on 2232d, 232h-only
|
||||||
|
};
|
||||||
|
|
||||||
|
// mpsse, mcuhost commands
|
||||||
|
|
||||||
|
// if bit 7 of an MPSSE command byte
|
||||||
|
|
||||||
|
enum ftdi_mpsse_cflg {
|
||||||
|
ftmpsse_negedge_wr = 1<<0, // if 0, output bits on positive clock edige
|
||||||
|
ftmpsse_bitmode = 1<<1, // if 0, byte mode
|
||||||
|
ftmpsse_negedge_rd = 1<<2, // if 0, input bits on positive clock edge
|
||||||
|
ftmpsse_lsbfirst = 1<<3, // if 0, msb first
|
||||||
|
ftmpsse_tdiwrite = 1<<4, // 1 == do perform output
|
||||||
|
ftmpsse_tdoread = 1<<5, // 1 == do perform input
|
||||||
|
ftmpsse_tmswrite = 1<<6, // 1 == do perform output?
|
||||||
|
|
||||||
|
ftmpsse_specialcmd = 1<<7 // see below enum if set
|
||||||
|
};
|
||||||
|
// bitmode: 1 length byte, max=7 (#bits = length+1) for separate bits
|
||||||
|
// bytemode: 2 length bytes == number of bytes that follow
|
||||||
|
// both tdiwrite and tdoread high: only one length value/equal number of bits in/out!
|
||||||
|
// if both tdiwrite and tdoread are high, negedge_wr and negedge_rd must differ
|
||||||
|
// tms read/writes: readback is from tdo, bit7 in databyte is tdi output, held constant
|
||||||
|
// tdiwrite always 0, bitmode 1 in impls, can be ignored I guess
|
||||||
|
// also always lsbfirst, but not too hard to support msbfirst too
|
||||||
|
// idle levels (of eg. tms/cs) -> set_dirval?
|
||||||
|
|
||||||
|
enum ftdi_mpssemcu_cmd {
|
||||||
|
ftmpsse_set_dirval_lo = 0x80, // sets initial clock level!
|
||||||
|
ftmpsse_set_dirval_hi = 0x82,
|
||||||
|
ftmpsse_read_lo = 0x81,
|
||||||
|
ftmpsse_read_hi = 0x83,
|
||||||
|
ftmpsse_loopback_on = 0x84,
|
||||||
|
ftmpsse_loopback_off = 0x85,
|
||||||
|
ftmpsse_set_clkdiv = 0x86, // period = 12MHz / ((1 + value16) * 2)
|
||||||
|
|
||||||
|
ftmpsse_flush = 0x87, // flush dev->host usb buffer
|
||||||
|
ftmpsse_wait_io_hi = 0x88, // wait for gpiol1/io1 to be high
|
||||||
|
ftmpsse_wait_io_lo = 0x89, // wait for gpiol1/io1 to be low
|
||||||
|
|
||||||
|
// technically ft2232h-only but we can support these, too
|
||||||
|
ftmpsse_div5_disable = 0x8a, // ft2232h internally has a 5x faster clock, but slows it down by default
|
||||||
|
ftmpsse_div5_enable = 0x8b, // for backwards compat. these two commands enable/disable that slowdown
|
||||||
|
ftmpsse_data_3ph_en = 0x8c, // enable 3-phase data
|
||||||
|
ftmpsse_data_3ph_dis = 0x8d, // disable 3-phase data
|
||||||
|
ftmpsse_clockonly_bits = 0x8e, // enable clock for n bits, no data xfer
|
||||||
|
ftmpsse_clockonly_bytes = 0x8f, // enable clock for n bytes, no data xfer
|
||||||
|
ftmpsse_clock_wait_io_hi = 0x94, // wait_io_hi + clockonly
|
||||||
|
ftmpsse_clock_wait_io_lo = 0x95, // wait_io_lo + clockonly
|
||||||
|
ftmpsse_adapclk_enable = 0x96, // enable ARM JTAG adaptive clocking (rtck gpiol3 input)
|
||||||
|
ftmpsse_adapclk_disable = 0x97, // disable ARM JTAG adaptive clocking (rtck gpiol3 input)
|
||||||
|
ftmpsse_clock_bits_wait_io_hi = 0x9c, // clock_wait_io_hi + clockonly_bits
|
||||||
|
ftmpsse_clock_bits_wait_io_lo = 0x9d, // clock_wait_io_lo + clockonly_bits
|
||||||
|
ftmpsse_hi_is_tristate = 0x9e, // turns 1 output to tristate for selected outputs
|
||||||
|
|
||||||
|
ftmcu_flush = 0x87, // flush dev->host usb buffer
|
||||||
|
ftmcu_wait_io_hi = 0x88, // wait for gpiol1/io1 to be high
|
||||||
|
ftmcu_wait_io_lo = 0x89, // wait for gpiol1/io1 to be low
|
||||||
|
|
||||||
|
ftmcu_read8 = 0x90,
|
||||||
|
ftmcu_read16 = 0x91,
|
||||||
|
ftmcu_write8 = 0x92,
|
||||||
|
ftmcu_write16 = 0x93
|
||||||
|
};
|
||||||
|
|
||||||
|
// internal use only types
|
||||||
|
|
||||||
|
enum ftdi_mode { // combines EEPROM setting + bitmode
|
||||||
|
ftmode_uart = 0,
|
||||||
|
|
||||||
|
ftmode_mpsse = 1,
|
||||||
|
ftmode_asyncbb = 2,
|
||||||
|
ftmode_syncbb = 4,
|
||||||
|
ftmode_mcuhost = 8,
|
||||||
|
|
||||||
|
ftmode_fifo = 0x10,
|
||||||
|
ftmode_opto = 0x20, // not implementing this here
|
||||||
|
ftmode_cpufifo = 0x40,
|
||||||
|
ftmode_ft1284 = 0x80, // not impl. on 2232d
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ftdi_flowctrl {
|
||||||
|
ftflow_none = 0,
|
||||||
|
ftflow_ctsrts = 1,
|
||||||
|
ftflow_dtrdts = 2,
|
||||||
|
ftflow_xonxoff = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ftdi_interface {
|
||||||
|
// TODO soft fields maybe, because it's a mess with lots of padding right now
|
||||||
|
int index;
|
||||||
|
|
||||||
|
uint8_t modem_mask;
|
||||||
|
uint8_t modem_data;
|
||||||
|
|
||||||
|
enum ftdi_flowctrl flow;
|
||||||
|
|
||||||
|
uint32_t baudrate;
|
||||||
|
enum ftdi_sio_lineprop lineprop;
|
||||||
|
|
||||||
|
enum ftdi_sio_modemstat modemstat;
|
||||||
|
|
||||||
|
uint8_t eventchar, errorchar;
|
||||||
|
enum { eventchar_enable = 1<<0, errorchar_enable = 1<<1 } charen;
|
||||||
|
|
||||||
|
uint8_t latency; // latency timer. TODO: implement this
|
||||||
|
|
||||||
|
uint8_t bb_dir; // high/1 bit = output, 0=input
|
||||||
|
enum ftdi_sio_bitmode bb_mode;
|
||||||
|
|
||||||
|
uint16_t mcu_addr_latch;
|
||||||
|
|
||||||
|
// these are for USB bulk cmds etc.
|
||||||
|
// "write" means write to hardware output pins
|
||||||
|
// "read" means read from hardware input pins
|
||||||
|
uint8_t writebuf[CFG_TUD_VENDOR_RX_BUFSIZE];
|
||||||
|
uint8_t readbuf [CFG_TUD_VENDOR_TX_BUFSIZE];
|
||||||
|
uint8_t bufbuf [CFG_TUD_VENDOR_RX_BUFSIZE]; // for buffered IO
|
||||||
|
uint32_t rxavail, rxpos;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct ftdi_interface ftdi_ifa, ftdi_ifb;
|
||||||
|
|
||||||
|
// interface control stuff
|
||||||
|
|
||||||
|
static inline enum ftdi_mode ftdi_get_mode_of(enum ftdi_sio_bitmode bb_mode, uint8_t eepmode) {
|
||||||
|
if (bb_mode == 0x10) return ftmode_opto;
|
||||||
|
|
||||||
|
if (bb_mode == sio_mode_reset) {
|
||||||
|
return (eepmode << 4) & 0xf0;
|
||||||
|
} else return bb_mode & 0xf;
|
||||||
|
}
|
||||||
|
static inline enum ftdi_mode ftdi_if_get_mode(struct ftdi_interface* itf) {
|
||||||
|
return ftdi_get_mode_of(itf->bb_mode, itf->index ? FTDI_EEP_IFB_MODE : FTDI_EEP_IFA_MODE);
|
||||||
|
}
|
||||||
|
uint32_t ftdi_if_decode_baudrate(uint32_t enc_brate);
|
||||||
|
|
||||||
|
// control request stuff. implemented by bsp driver
|
||||||
|
void ftdi_if_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_deinit(struct ftdi_interface* itf);
|
||||||
|
|
||||||
|
void ftdi_if_sio_reset(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_sio_tciflush(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_sio_tcoflush(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_set_modemctrl(struct ftdi_interface* itf, uint8_t mask, uint8_t data);
|
||||||
|
void ftdi_if_set_flowctrl(struct ftdi_interface* itf, enum ftdi_flowctrl flow);
|
||||||
|
void ftdi_if_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_set_lineprop(struct ftdi_interface* itf, enum ftdi_sio_lineprop lineprop);
|
||||||
|
enum ftdi_sio_modemstat ftdi_if_poll_modemstat(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_set_eventchar(struct ftdi_interface* itf, bool enable, uint8_t evchar);
|
||||||
|
void ftdi_if_set_errorchar(struct ftdi_interface* itf, bool enable, uint8_t erchar);
|
||||||
|
void ftdi_if_set_latency(struct ftdi_interface* itf, uint8_t latency);
|
||||||
|
uint8_t ftdi_if_get_latency(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_set_bitbang(struct ftdi_interface* itf, uint8_t dirmask, enum ftdi_sio_bitmode,
|
||||||
|
uint8_t olddir, enum ftdi_sio_bitmode oldmode);
|
||||||
|
uint8_t ftdi_if_read_pins(struct ftdi_interface* itf);
|
||||||
|
|
||||||
|
// bulk commands (also implemented by bsp driver)
|
||||||
|
|
||||||
|
// "write" means write to hardware output pins
|
||||||
|
// "read" means read from hardware input pins
|
||||||
|
void ftdi_if_uart_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize);
|
||||||
|
size_t ftdi_if_uart_read (struct ftdi_interface* itf, uint8_t* data, size_t maxsize);
|
||||||
|
|
||||||
|
void ftdi_if_fifo_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize);
|
||||||
|
size_t ftdi_if_fifo_read (struct ftdi_interface* itf, uint8_t* data, size_t maxsize);
|
||||||
|
void ftdi_if_cpufifo_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize);
|
||||||
|
size_t ftdi_if_cpufifo_read (struct ftdi_interface* itf, uint8_t* data, size_t maxsize);
|
||||||
|
|
||||||
|
void ftdi_if_asyncbb_write(struct ftdi_interface* itf, const uint8_t* data, size_t datasize);
|
||||||
|
size_t ftdi_if_asyncbb_read (struct ftdi_interface* itf, uint8_t* data, size_t maxsize);
|
||||||
|
void ftdi_if_syncbb_write (struct ftdi_interface* itf, const uint8_t* data, size_t datasize);
|
||||||
|
size_t ftdi_if_syncbb_read (struct ftdi_interface* itf, uint8_t* data, size_t maxsize);
|
||||||
|
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_flush(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mpsse_wait_io(struct ftdi_interface* itf, bool level);
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_set_dirval_lo(struct ftdi_interface* itf, uint8_t dir, uint8_t val);
|
||||||
|
void ftdi_if_mpsse_set_dirval_hi(struct ftdi_interface* itf, uint8_t dir, uint8_t val);
|
||||||
|
uint8_t ftdi_if_mpsse_read_lo(struct ftdi_interface* itf);
|
||||||
|
uint8_t ftdi_if_mpsse_read_hi(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mpsse_loopback(struct ftdi_interface* itf, bool enable);
|
||||||
|
void ftdi_if_mpsse_set_clkdiv(struct ftdi_interface* itf, uint16_t div);
|
||||||
|
|
||||||
|
uint8_t ftdi_if_mpsse_xfer_bits(struct ftdi_interface* itf, int flags, size_t nbits, uint8_t value);
|
||||||
|
void ftdi_if_mpsse_xfer_bytes(struct ftdi_interface* itf, int flags, size_t nbytes, uint8_t* dst, const uint8_t* src);
|
||||||
|
uint8_t ftdi_if_mpsse_tms_xfer(struct ftdi_interface* itf, int flags, size_t nbits, uint8_t value);
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_div5(struct ftdi_interface* itf, bool enable);
|
||||||
|
void ftdi_if_mpsse_data_3ph(struct ftdi_interface* itf, bool enable);
|
||||||
|
void ftdi_if_mpsse_adaptive(struct ftdi_interface* itf, bool enable);
|
||||||
|
|
||||||
|
void ftdi_if_mpsse_clockonly(struct ftdi_interface* itf, uint32_t cycles);
|
||||||
|
void ftdi_if_mpsse_clock_wait_io(struct ftdi_interface* itf, bool level);
|
||||||
|
void ftdi_if_mpsse_clockonly_wait_io(struct ftdi_interface* itf, bool level, uint32_t cycles);
|
||||||
|
void ftdi_if_mpsse_hi_is_tristate(struct ftdi_interface* itf, uint16_t pinmask);
|
||||||
|
|
||||||
|
|
||||||
|
void ftdi_if_mcuhost_flush(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mcuhost_wait_io(struct ftdi_interface* itf, bool level);
|
||||||
|
|
||||||
|
uint8_t ftdi_if_mcuhost_read8 (struct ftdi_interface* itf, uint8_t addr);
|
||||||
|
uint8_t ftdi_if_mcuhost_read16(struct ftdi_interface* itf, uint16_t addr);
|
||||||
|
void ftdi_if_mcuhost_write8 (struct ftdi_interface* itf, uint8_t addr, uint8_t value);
|
||||||
|
void ftdi_if_mcuhost_write16(struct ftdi_interface* itf, uint16_t addr, uint8_t value);
|
||||||
|
|
||||||
|
void ftdi_if_uart_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mpsse_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_asyncbb_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_syncbb_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mcuhost_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_fifo_init(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_cpufifo_init(struct ftdi_interface* itf);
|
||||||
|
|
||||||
|
void ftdi_if_uart_deinit(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mpsse_deinit(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_asyncbb_deinit(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_syncbb_deinit(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_mcuhost_deinit(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_fifo_deinit(struct ftdi_interface* itf);
|
||||||
|
void ftdi_if_cpufifo_deinit(struct ftdi_interface* itf);
|
||||||
|
|
||||||
|
void ftdi_if_uart_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_mpsse_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_asyncbb_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_syncbb_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_mcuhost_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_fifo_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
void ftdi_if_cpufifo_set_baudrate(struct ftdi_interface* itf, uint32_t baudrate);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <tusb.h>
|
#include <tusb.h>
|
||||||
|
|
||||||
#include "mode.h"
|
#include "mode.h"
|
||||||
|
#include "storage.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "usbstdio.h"
|
#include "usbstdio.h"
|
||||||
#include "vnd_cfg.h"
|
#include "vnd_cfg.h"
|
||||||
|
@ -20,6 +21,8 @@ enum m_sump_feature {
|
||||||
msump_feat_sump = 1<<0,
|
msump_feat_sump = 1<<0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static bool data_dirty = false;
|
||||||
|
|
||||||
#ifdef DBOARD_HAS_SUMP
|
#ifdef DBOARD_HAS_SUMP
|
||||||
static cothread_t sumpthread;
|
static cothread_t sumpthread;
|
||||||
static uint8_t sumpstack[THREAD_STACK_SIZE];
|
static uint8_t sumpstack[THREAD_STACK_SIZE];
|
||||||
|
@ -44,6 +47,16 @@ static void enter_cb(void) {
|
||||||
sumpthread = co_derive(sumpstack, sizeof sumpstack, sump_thread_fn);
|
sumpthread = co_derive(sumpstack, sizeof sumpstack, sump_thread_fn);
|
||||||
thread_enter(sumpthread);
|
thread_enter(sumpthread);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!data_dirty) { // only read when not read yet
|
||||||
|
struct mode_info mi = storage_mode_get_info(4);
|
||||||
|
if (mi.size != 0 && mi.version == 0x0010 /* TODO: version migration? */) {
|
||||||
|
uint8_t dst[1];
|
||||||
|
storage_mode_read(4, dst, 0, 1);
|
||||||
|
|
||||||
|
sump_hw_set_overclock(dst[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
static void leave_cb(void) {
|
static void leave_cb(void) {
|
||||||
#ifdef DBOARD_HAS_SUMP
|
#ifdef DBOARD_HAS_SUMP
|
||||||
|
@ -133,7 +146,6 @@ enum {
|
||||||
#define EPNUM_CDC_STDIO_NOTIF 0x85
|
#define EPNUM_CDC_STDIO_NOTIF 0x85
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
// TODO: replace magic 64s by actual buffer size macros
|
|
||||||
static const uint8_t desc_configuration[] = {
|
static const uint8_t desc_configuration[] = {
|
||||||
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM__TOTAL, STRID_CONFIG, CONFIG_TOTAL_LEN,
|
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM__TOTAL, STRID_CONFIG, CONFIG_TOTAL_LEN,
|
||||||
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
||||||
|
@ -178,6 +190,17 @@ static void my_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_cod
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static uint16_t my_get_size(void) { return 1; }
|
||||||
|
static void my_get_data(void* dst, size_t offset, size_t maxsize) {
|
||||||
|
(void)offset; (void)maxsize;
|
||||||
|
|
||||||
|
uint8_t* d = dst;
|
||||||
|
d[0] = sump_hw_get_overclock();
|
||||||
|
|
||||||
|
data_dirty = false;
|
||||||
|
}
|
||||||
|
static bool my_is_dirty(void) { return data_dirty; }
|
||||||
|
|
||||||
extern struct mode m_04_sump;
|
extern struct mode m_04_sump;
|
||||||
// clang-format off
|
// clang-format off
|
||||||
struct mode m_04_sump = {
|
struct mode m_04_sump = {
|
||||||
|
@ -188,6 +211,13 @@ struct mode m_04_sump = {
|
||||||
.usb_desc = desc_configuration,
|
.usb_desc = desc_configuration,
|
||||||
.string_desc = string_desc_arr,
|
.string_desc = string_desc_arr,
|
||||||
|
|
||||||
|
.storage = {
|
||||||
|
.stclass = mode_storage_32b,
|
||||||
|
.get_size = my_get_size,
|
||||||
|
.get_data = my_get_data,
|
||||||
|
.is_dirty = my_is_dirty
|
||||||
|
},
|
||||||
|
|
||||||
.enter = enter_cb,
|
.enter = enter_cb,
|
||||||
.leave = leave_cb,
|
.leave = leave_cb,
|
||||||
.task = task_cb,
|
.task = task_cb,
|
||||||
|
|
18
src/main.c
18
src/main.c
|
@ -7,6 +7,7 @@
|
||||||
#include "tusb.h"
|
#include "tusb.h"
|
||||||
|
|
||||||
#include "mode.h"
|
#include "mode.h"
|
||||||
|
#include "storage.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "usbstdio.h"
|
#include "usbstdio.h"
|
||||||
#include "vnd_cfg.h"
|
#include "vnd_cfg.h"
|
||||||
|
@ -24,6 +25,8 @@ static void vndcfg_thread_fn(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
stdio_uart_init();
|
||||||
|
|
||||||
thread_init();
|
thread_init();
|
||||||
|
|
||||||
board_init(); // tinyusb hardware support function
|
board_init(); // tinyusb hardware support function
|
||||||
|
@ -31,15 +34,24 @@ int main() {
|
||||||
vndcfg_thread = co_derive(vndcfg_stack, sizeof vndcfg_stack, vndcfg_thread_fn);
|
vndcfg_thread = co_derive(vndcfg_stack, sizeof vndcfg_stack, vndcfg_thread_fn);
|
||||||
thread_enter(vndcfg_thread);
|
thread_enter(vndcfg_thread);
|
||||||
|
|
||||||
modes_init();
|
#if defined(PERSISTENT_STORAGE) && defined(DBOARD_HAS_STORAGE)
|
||||||
|
int startupmode = storage_init();
|
||||||
|
#else
|
||||||
|
int startupmode = -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// FIXME: put elsewhere?
|
||||||
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
|
stdio_usb_set_itf_num(0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
modes_init(startupmode);
|
||||||
if (mode_current) mode_current->enter();
|
if (mode_current) mode_current->enter();
|
||||||
|
|
||||||
tusb_init();
|
tusb_init();
|
||||||
|
|
||||||
// FIXME: put elsewhere?
|
// FIXME: put elsewhere?
|
||||||
#ifdef USE_USBCDC_FOR_STDIO
|
#ifdef USE_USBCDC_FOR_STDIO
|
||||||
stdio_usb_set_itf_num(0);
|
|
||||||
|
|
||||||
stdio_usb_init();
|
stdio_usb_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
#include "tusb_config.h"
|
#include "tusb_config.h"
|
||||||
#include <tusb.h>
|
#include <tusb.h>
|
||||||
|
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
||||||
struct mode {
|
struct mode {
|
||||||
|
@ -19,6 +21,8 @@ struct mode {
|
||||||
const uint8_t* usb_desc;
|
const uint8_t* usb_desc;
|
||||||
const char** string_desc;
|
const char** string_desc;
|
||||||
|
|
||||||
|
struct mode_storage storage;
|
||||||
|
|
||||||
void (*enter)(void); // claim required hardware. no tusb calls here please
|
void (*enter)(void); // claim required hardware. no tusb calls here please
|
||||||
void (*leave)(void); // release current in-use hardware. no tusb calls here please
|
void (*leave)(void); // release current in-use hardware. no tusb calls here please
|
||||||
void (*task )(void);
|
void (*task )(void);
|
||||||
|
@ -35,7 +39,7 @@ struct mode {
|
||||||
void (*tud_cdc_line_coding_cb)(uint8_t itf, cdc_line_coding_t const* line_coding);
|
void (*tud_cdc_line_coding_cb)(uint8_t itf, cdc_line_coding_t const* line_coding);
|
||||||
#endif
|
#endif
|
||||||
//#if CFG_TUD_VENDOR > 0
|
//#if CFG_TUD_VENDOR > 0
|
||||||
bool (*tud_vendor_control_xfer_cb)(uint8_t rhport, uint8_t ep_addr,
|
bool (*tud_vendor_control_xfer_cb)(uint8_t rhport, uint8_t stage,
|
||||||
tusb_control_request_t const* req);
|
tusb_control_request_t const* req);
|
||||||
//#endif
|
//#endif
|
||||||
|
|
||||||
|
@ -46,8 +50,9 @@ struct mode {
|
||||||
};
|
};
|
||||||
|
|
||||||
// call this BEFORE tusb_init!
|
// call this BEFORE tusb_init!
|
||||||
void modes_init(void);
|
void modes_init(int init_mode);
|
||||||
|
|
||||||
|
// IMMEDIATELY switches mode. use "mode_next_id' to do a graceful switch
|
||||||
void modes_switch(uint8_t newmode);
|
void modes_switch(uint8_t newmode);
|
||||||
|
|
||||||
extern int mode_current_id;
|
extern int mode_current_id;
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
#include "alloc.h"
|
#include "alloc.h"
|
||||||
#include "board.h" /* bsp_reset_bootloader() */
|
#include "board.h" /* bsp_reset_bootloader() */
|
||||||
#include "mode.h"
|
#include "mode.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
extern struct mode m_01_default, m_03_jscan, m_04_sump;
|
extern struct mode m_01_default, m_03_jscan, m_04_sump, m_05_ftdi;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const struct mode* const mode_list[16] = {
|
const struct mode* const mode_list[16] = {
|
||||||
|
@ -15,6 +16,7 @@ const struct mode* const mode_list[16] = {
|
||||||
NULL, // mode 2 (hw chip programming stuff) not implemented yet
|
NULL, // mode 2 (hw chip programming stuff) not implemented yet
|
||||||
&m_03_jscan,
|
&m_03_jscan,
|
||||||
&m_04_sump,
|
&m_04_sump,
|
||||||
|
&m_05_ftdi,
|
||||||
NULL, // terminating entry
|
NULL, // terminating entry
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
@ -37,9 +39,13 @@ enum tusbgot_index {
|
||||||
|
|
||||||
#define ORDEF(a,b) ((a != NULL) ? a : b)
|
#define ORDEF(a,b) ((a != NULL) ? a : b)
|
||||||
|
|
||||||
void modes_init(void) {
|
void modes_init(int newid) {
|
||||||
// switch to the default mode, but without doing a USB reboot thing
|
if (newid < 0 || newid >= 16 || mode_list[newid] == NULL) {
|
||||||
mode_current_id = &mode_default - mode_list;
|
// switch to the default mode, but without doing a USB reboot thing
|
||||||
|
mode_current_id = &mode_default - mode_list;
|
||||||
|
} else {
|
||||||
|
mode_current_id = newid;
|
||||||
|
}
|
||||||
mode_next_id = -1;
|
mode_next_id = -1;
|
||||||
|
|
||||||
//if (!mode_default) return;
|
//if (!mode_default) return;
|
||||||
|
@ -94,6 +100,8 @@ void modes_switch(uint8_t newmode) {
|
||||||
// maybe wait a second or so for the host to notice this
|
// maybe wait a second or so for the host to notice this
|
||||||
sleep_ms(500/2);
|
sleep_ms(500/2);
|
||||||
|
|
||||||
|
printf("disconnect\n");
|
||||||
|
|
||||||
if (newmode == 0) bsp_reset_bootloader();
|
if (newmode == 0) bsp_reset_bootloader();
|
||||||
|
|
||||||
// now apply the new tusb settings
|
// now apply the new tusb settings
|
||||||
|
@ -159,11 +167,25 @@ void modes_switch(uint8_t newmode) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printf("reconnect\n");
|
||||||
|
|
||||||
// and reconnect
|
// and reconnect
|
||||||
tud_connect();
|
tud_connect();
|
||||||
sleep_ms(500/2);
|
sleep_ms(500/2);
|
||||||
//while (!tud_mounted()) sleep_ms(5);
|
//while (!tud_mounted()) sleep_ms(5);
|
||||||
|
|
||||||
|
printf("enter\n");
|
||||||
|
|
||||||
if (mode_current) mode_current->enter();
|
if (mode_current) mode_current->enter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(PERSISTENT_STORAGE) && defined(DBOARD_HAS_STORAGE)
|
||||||
|
void tud_umount_cb(void) {
|
||||||
|
storage_flush_data();
|
||||||
|
}
|
||||||
|
void tud_suspend_cb(bool remote_wakeup_en) {
|
||||||
|
(void)remote_wakeup_en;
|
||||||
|
storage_flush_data();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "bsp-info.h"
|
||||||
|
#include "bsp-storage.h"
|
||||||
|
#include "mode.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
|
#if !defined(PERSISTENT_STORAGE) || !defined(DBOARD_HAS_STORAGE)
|
||||||
|
int storage_init(void) { return -1; }
|
||||||
|
bool storage_flush_data(void) { return false; }
|
||||||
|
struct mode_info storage_mode_get_info(int _) {
|
||||||
|
(void)_; return (struct mode_info){ .size = 0, .version = 0 };
|
||||||
|
}
|
||||||
|
void storage_mode_read(int _, void* __, size_t ___, size_t ____) {
|
||||||
|
(void)_; (void)__; (void)___; (void)____;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "storage_internal.h"
|
||||||
|
|
||||||
|
uint32_t str_hash_djb2_digest(uint32_t hash, const void* data, size_t len) {
|
||||||
|
const uint8_t* d = data;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; ++i) hash += hash * 33 + d[i];
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool header_valid = false;
|
||||||
|
struct storage_header header_tmp;
|
||||||
|
uint8_t data_tmp[256];
|
||||||
|
uint16_t mode_bad = 0;
|
||||||
|
|
||||||
|
static int in_init = 0;
|
||||||
|
|
||||||
|
static void storage_init_defaults(void) {
|
||||||
|
memcpy(header_tmp.magic, STORAGE_MAGIC, STORAGE_MAGIC_LEN);
|
||||||
|
header_tmp.fwversion = STORAGE_VER;
|
||||||
|
header_tmp.curmode = mode_current_id;
|
||||||
|
header_tmp.nmodes = 0;
|
||||||
|
memset(header_tmp.reserved, 0xff, sizeof(header_tmp.reserved));
|
||||||
|
memset(header_tmp.mode_data_table, 0xff, sizeof(header_tmp.mode_data_table));
|
||||||
|
header_tmp.table_djb2 = str_hash_djb2(header_tmp.mode_data_table,
|
||||||
|
sizeof(struct mode_data)*MAX_MDT_ELEMENTS);
|
||||||
|
|
||||||
|
header_valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int storage_init(void) {
|
||||||
|
++in_init;
|
||||||
|
|
||||||
|
mode_bad = 0;
|
||||||
|
storage_read(&header_tmp, STORAGE_SIZE - sizeof(struct storage_header),
|
||||||
|
sizeof(struct storage_header));
|
||||||
|
|
||||||
|
bool bad = false;
|
||||||
|
if (memcmp(header_tmp.magic, STORAGE_MAGIC, STORAGE_MAGIC_LEN)) {
|
||||||
|
storage_init_defaults();
|
||||||
|
if (in_init == 1) storage_flush_data();
|
||||||
|
--in_init;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_tmp.fwversion != STORAGE_VER) {
|
||||||
|
// TODO: migrate... if there were any older versions
|
||||||
|
header_valid = false;
|
||||||
|
--in_init;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header_tmp.nmodes >= 16) bad = true;
|
||||||
|
else if (str_hash_djb2(header_tmp.mode_data_table,
|
||||||
|
sizeof(struct mode_data)*MAX_MDT_ELEMENTS) != header_tmp.table_djb2)
|
||||||
|
bad = true;
|
||||||
|
else if (header_tmp.curmode >= 16 || header_tmp.curmode == 0
|
||||||
|
|| mode_list[header_tmp.curmode] == NULL)
|
||||||
|
bad = true;
|
||||||
|
|
||||||
|
if (bad) {
|
||||||
|
storage_init_defaults();
|
||||||
|
if (in_init == 1) storage_flush_data();
|
||||||
|
--in_init;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_valid = true;
|
||||||
|
--in_init;
|
||||||
|
return header_tmp.curmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mode_info storage_mode_get_info(int mode) {
|
||||||
|
#define DEF_RETVAL ({ \
|
||||||
|
if (mode < 16 && mode > 0 && header_valid && header_tmp.nmodes != 0) mode_bad |= 1<<mode; \
|
||||||
|
(struct mode_info){ .size = 0, .version = 0 }; \
|
||||||
|
}) \
|
||||||
|
|
||||||
|
|
||||||
|
if (mode >= 16 || !header_valid || mode <= 0 || header_tmp.nmodes == 0)
|
||||||
|
return DEF_RETVAL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < header_tmp.nmodes; ++i) {
|
||||||
|
struct mode_data md = header_tmp.mode_data_table[i];
|
||||||
|
int mdmode = (uint8_t)(md.offsetandmode >> 28);
|
||||||
|
uint16_t mdsize = md.datasize;
|
||||||
|
uint32_t mdoffset = md.offsetandmode & ((1<<28)-1);
|
||||||
|
|
||||||
|
if (mdmode != mode) continue;
|
||||||
|
if (mdsize == 0xffff || md.version == 0xffff || md.offsetandmode == 0xffffffffu)
|
||||||
|
continue; // empty (wut?)
|
||||||
|
|
||||||
|
// found it!
|
||||||
|
|
||||||
|
if (mdsize == 0) return DEF_RETVAL; // no data stored
|
||||||
|
if (mdoffset == 0 || mdoffset >= STORAGE_SIZE)
|
||||||
|
return DEF_RETVAL; // bad offset
|
||||||
|
// program code collision cases
|
||||||
|
if (mdoffset < storage_get_program_offset() && mdoffset+mdsize >=
|
||||||
|
storage_get_program_offset()) return DEF_RETVAL;
|
||||||
|
if (mdoffset < storage_get_program_offset()+storage_get_program_size()
|
||||||
|
&& mdoffset+mdsize >= storage_get_program_offset()+storage_get_program_size())
|
||||||
|
return DEF_RETVAL;
|
||||||
|
if (mdoffset >= storage_get_program_offset()
|
||||||
|
&& mdoffset+mdsize <= storage_get_program_offset()+storage_get_program_size())
|
||||||
|
return DEF_RETVAL;
|
||||||
|
|
||||||
|
// now check whether the data hash is corrupted
|
||||||
|
uint32_t hash = str_hash_djb2_init();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mdsize; i += sizeof(data_tmp)) {
|
||||||
|
size_t toread = sizeof(data_tmp);
|
||||||
|
if (mdsize - i < toread) toread = mdsize - i;
|
||||||
|
|
||||||
|
storage_read(data_tmp, mdoffset + i, toread);
|
||||||
|
|
||||||
|
hash = str_hash_djb2_digest(hash, data_tmp, toread);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash != md.data_djb2) return DEF_RETVAL;
|
||||||
|
|
||||||
|
return (struct mode_info) {
|
||||||
|
.size = mdsize,
|
||||||
|
.version = md.version
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEF_RETVAL;
|
||||||
|
|
||||||
|
#undef DEF_RETVAL
|
||||||
|
}
|
||||||
|
void storage_mode_read(int mode, void* dst, size_t offset, size_t maxlen) {
|
||||||
|
<<<<<<< HEAD
|
||||||
|
if (mode >= 16 || !header_valid || mode <= 0 || header_tmp.nmodes == 0) return;
|
||||||
|
=======
|
||||||
|
if (mode >= 16 || !header_valid || mode <= 0) return;
|
||||||
|
>>>>>>> 62e3181 (PERSISTENT_STORAGE usage flag)
|
||||||
|
|
||||||
|
for (size_t i = 0; i < header_tmp.nmodes; ++i) {
|
||||||
|
struct mode_data md = header_tmp.mode_data_table[i];
|
||||||
|
int mdmode = (uint8_t)(md.offsetandmode >> 28);
|
||||||
|
uint16_t mdsize = md.datasize;
|
||||||
|
uint32_t mdoffset = md.offsetandmode & ((1<<28)-1);
|
||||||
|
|
||||||
|
if (mdmode != mode) continue;
|
||||||
|
if (mdsize == 0xffff || md.version == 0xffff || md.offsetandmode == 0xffffffffu)
|
||||||
|
continue; // empty (wut?)
|
||||||
|
|
||||||
|
// found it!
|
||||||
|
|
||||||
|
if (mdsize == 0) { mode_bad |= 1<<mode; return; /* no data stored */ }
|
||||||
|
if (mdoffset == 0 || mdoffset >= STORAGE_SIZE) {
|
||||||
|
mode_bad |= 1<<mode; return; /* bad offset */
|
||||||
|
}
|
||||||
|
// program code collision cases
|
||||||
|
if (mdoffset < storage_get_program_offset() && mdoffset+mdsize >=
|
||||||
|
storage_get_program_offset()) { mode_bad |= 1<<mode; return; }
|
||||||
|
if (mdoffset < storage_get_program_offset()+storage_get_program_size()
|
||||||
|
&& mdoffset+mdsize >= storage_get_program_offset()+storage_get_program_size()) {
|
||||||
|
mode_bad |= 1<<mode; return;
|
||||||
|
}
|
||||||
|
if (mdoffset >= storage_get_program_offset()
|
||||||
|
&& mdoffset+mdsize <= storage_get_program_offset()+storage_get_program_size()) {
|
||||||
|
mode_bad |= 1<<mode; return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset >= mdsize) return;
|
||||||
|
|
||||||
|
// skip hash check in this case
|
||||||
|
storage_read(dst, mdoffset + offset, mdsize < maxlen ? mdsize : maxlen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* BOARD_HAS_STORAGE */
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#ifndef STORAGE_H_
|
||||||
|
#define STORAGE_H_
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define STORAGE_VER 0x0010
|
||||||
|
|
||||||
|
enum mode_storage_class {
|
||||||
|
mode_storage_none, // this mode has no storage
|
||||||
|
mode_storage_32b , // this mode typically won't use more than 32 bytes of data
|
||||||
|
mode_storage_128b, // this mode typically won't use more than 128 bytes of data
|
||||||
|
mode_storage_512b, // this mode typically won't use more than 512 bytes of data
|
||||||
|
mode_storage_big // this mode uses a lot of data
|
||||||
|
};
|
||||||
|
|
||||||
|
// mode callbacks used by the storage subsystem
|
||||||
|
struct mode_storage {
|
||||||
|
enum mode_storage_class stclass;
|
||||||
|
uint16_t (*get_size)(void);
|
||||||
|
// if stclass < 512b, offset & maxsize can be ignored
|
||||||
|
void (*get_data)(void* dst, size_t offset, size_t maxsize);
|
||||||
|
bool (*is_dirty)(void); // if data was changed since last mode_read/get_data call
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mode_info {
|
||||||
|
uint32_t size;
|
||||||
|
uint16_t version;
|
||||||
|
};
|
||||||
|
|
||||||
|
// functions mode-specific code can use to retrieve the save data
|
||||||
|
|
||||||
|
struct mode_info storage_mode_get_info(int mode); // returns size=0 for none found
|
||||||
|
void storage_mode_read(int mode, void* dst, size_t offset, size_t maxlen);
|
||||||
|
|
||||||
|
// global functions
|
||||||
|
|
||||||
|
// reads all data, creates table if needed
|
||||||
|
int storage_init(void);
|
||||||
|
|
||||||
|
// flush edits if anything has been edited
|
||||||
|
bool storage_flush_data(void);
|
||||||
|
|
||||||
|
bool storage_priv_mode_has(int mode);
|
||||||
|
void* storage_priv_get_header_ptr(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#ifndef STORAGE_INTERNAL_H_
|
||||||
|
#define STORAGE_INTERNAL_H_
|
||||||
|
|
||||||
|
inline static uint32_t str_hash_djb2_init(void) {
|
||||||
|
return 5381;
|
||||||
|
}
|
||||||
|
uint32_t str_hash_djb2_digest(uint32_t hash, const void* data, size_t len);
|
||||||
|
inline static uint32_t str_hash_djb2(const void* data, size_t len) {
|
||||||
|
return str_hash_djb2_digest(str_hash_djb2_init(), data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* storage header (in last 256b of flash) (endianness is whatever is native to the device):
|
||||||
|
*
|
||||||
|
* 3 7 b f
|
||||||
|
* --+----------------------------------------------------+
|
||||||
|
* 0 | f0 9f 8f b3 ef b8 8f e2 80 8d e2 9a a7 ef b8 8f | magic number
|
||||||
|
* 10 | fwver mm nm <reserved (0xff) > <tbl djb2> | fwver: current version (bcd)
|
||||||
|
* 20 | <mode data table ...> | mm: current mode, nm: number of modes saved (~size of "mode data table")
|
||||||
|
* 30 | | tbl djb2: djb2-hash of "mode data table" (entire table, not nmodes)
|
||||||
|
* 40 | <reserved (0xff) ...> | empty "mode data table" entries are 0xff-filled
|
||||||
|
*
|
||||||
|
* mode data table: array of:
|
||||||
|
* struct mode_data {
|
||||||
|
* uint16_t version;
|
||||||
|
* uint16_t datasize;
|
||||||
|
* uint28_t flashoffset; // from beginning of flash
|
||||||
|
* uint4_t modeid;
|
||||||
|
* uint32_t data_djb2;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* mode data blobs are typically allocated smallest to largest (according to
|
||||||
|
* mode_storage_class), from the last page of flash (cf. bsp-storage.h) down
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define STORAGE_MAGIC "\xf0\x9f\x8f\xb3\xef\xb8\x8f\xe2\x80\x8d\xe2\x9a\xa7\xef\xb8\x8f"
|
||||||
|
#define STORAGE_MAGIC_LEN 16
|
||||||
|
|
||||||
|
__attribute__((__packed__)) struct mode_data {
|
||||||
|
uint16_t version;
|
||||||
|
uint16_t datasize;
|
||||||
|
uint32_t offsetandmode; // mode ID stored in MSNybble
|
||||||
|
uint32_t data_djb2;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define MAX_MDT_ELEMENTS ((256 - 64) / sizeof(struct mode_data)) /* should be 16 */
|
||||||
|
|
||||||
|
__attribute__((__packed__)) struct storage_header {
|
||||||
|
// +0
|
||||||
|
uint8_t magic[STORAGE_MAGIC_LEN];
|
||||||
|
// +16
|
||||||
|
uint16_t fwversion;
|
||||||
|
uint8_t curmode;
|
||||||
|
uint8_t nmodes; // *stored* modes, not modes it knows of
|
||||||
|
uint8_t reserved[8];
|
||||||
|
uint32_t table_djb2;
|
||||||
|
// +32
|
||||||
|
struct mode_data mode_data_table[MAX_MDT_ELEMENTS]; // 192 bytes in size
|
||||||
|
// +224
|
||||||
|
uint8_t reserved2[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: static assert sizeof(struct storage_header) == 256
|
||||||
|
// TODO: static assert MAX_MDT_ELEMENTS >= 16
|
||||||
|
|
||||||
|
extern bool header_valid;
|
||||||
|
extern struct storage_header header_tmp;
|
||||||
|
extern uint8_t data_tmp[256];
|
||||||
|
extern uint16_t mode_bad;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
// vim: set et:
|
||||||
|
|
||||||
|
#include "bsp-info.h"
|
||||||
|
#include "bsp-storage.h"
|
||||||
|
#include "mode.h"
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
|
#if defined(PERSISTENT_STORAGE) && defined(DBOARD_HAS_STORAGE)
|
||||||
|
|
||||||
|
#include "storage_internal.h"
|
||||||
|
|
||||||
|
bool storage_priv_mode_has(int i) {
|
||||||
|
if (mode_list[i]->storage.stclass == mode_storage_none) return false;
|
||||||
|
if (mode_list[i]->storage.get_size == NULL) return false;
|
||||||
|
if (mode_list[i]->storage.get_data == NULL) return false;
|
||||||
|
if (mode_list[i]->storage.is_dirty == NULL) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void* storage_priv_get_header_ptr(void) {
|
||||||
|
return &header_tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct mode_storage msto[16];
|
||||||
|
|
||||||
|
static size_t storage_allocate_new(void) {
|
||||||
|
static const size_t stclass_sz[] = { 0, 32, 128, 512, 0xffffffff };
|
||||||
|
|
||||||
|
memcpy(header_tmp.magic, STORAGE_MAGIC, STORAGE_MAGIC_LEN);
|
||||||
|
memset(header_tmp.reserved, 0xff, sizeof(header_tmp.reserved));
|
||||||
|
memset(header_tmp.reserved2, 0xff, sizeof(header_tmp.reserved2));
|
||||||
|
memset(header_tmp.mode_data_table, 0xff, sizeof(header_tmp.mode_data_table));
|
||||||
|
|
||||||
|
header_tmp.fwversion = STORAGE_VER;
|
||||||
|
header_tmp.curmode = mode_current_id;
|
||||||
|
header_tmp.nmodes = 0;
|
||||||
|
|
||||||
|
size_t current_page = STORAGE_SIZE - STORAGE_ERASEWRITE_ALIGN,
|
||||||
|
current_page_end = STORAGE_SIZE - sizeof(struct storage_header);
|
||||||
|
size_t current_wrhead = current_page;
|
||||||
|
size_t npages = 1;
|
||||||
|
|
||||||
|
for (enum mode_storage_class stcls = mode_storage_32b; stcls <= mode_storage_big; ++stcls) {
|
||||||
|
for (int mode = 1; mode < 16; ++mode) {
|
||||||
|
if (mode_list[mode] == NULL || !storage_priv_mode_has(mode)) continue;
|
||||||
|
if (mode_list[mode]->storage.stclass != stcls) continue;
|
||||||
|
|
||||||
|
// too big for the class? don't write the data, then
|
||||||
|
uint16_t dsize = mode_list[mode]->storage.get_size();
|
||||||
|
if (dsize > stclass_sz[stcls]) continue;
|
||||||
|
|
||||||
|
if (current_wrhead + dsize > current_page_end) { // FIXME: data that is >1 page size (do we want to support this?)
|
||||||
|
current_page_end = current_page;
|
||||||
|
current_page -= STORAGE_ERASEWRITE_ALIGN;
|
||||||
|
current_wrhead = current_page;
|
||||||
|
++npages;
|
||||||
|
|
||||||
|
if (current_page < storage_get_program_offset() + storage_get_program_size())
|
||||||
|
return 0; // welp, out of space
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mode_data* md = &header_tmp.mode_data_table[header_tmp.nmodes];
|
||||||
|
md->version = mode_list[mode]->version;
|
||||||
|
md->datasize = dsize;
|
||||||
|
md->offsetandmode = current_wrhead | ((uint32_t)mode << 28);
|
||||||
|
msto[header_tmp.nmodes] = mode_list[mode]->storage; // copy to RAM because mode_list is in rodata!
|
||||||
|
|
||||||
|
current_wrhead += stclass_sz[stcls];
|
||||||
|
|
||||||
|
uint32_t hash = str_hash_djb2_init();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < dsize; i += sizeof(data_tmp)) {
|
||||||
|
size_t tohash = sizeof(data_tmp);
|
||||||
|
if (dsize - i < tohash) tohash = dsize - i;
|
||||||
|
|
||||||
|
mode_list[mode]->storage.get_data(data_tmp, i, tohash);
|
||||||
|
|
||||||
|
hash = str_hash_djb2_digest(hash, data_tmp, tohash);
|
||||||
|
}
|
||||||
|
|
||||||
|
md->data_djb2 = hash;
|
||||||
|
++header_tmp.nmodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header_tmp.table_djb2 = str_hash_djb2(header_tmp.mode_data_table,
|
||||||
|
sizeof(header_tmp.mode_data_table));
|
||||||
|
|
||||||
|
return npages;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void st_safe_memcpy(void* dst, const void* src, size_t size) {
|
||||||
|
const uint64_t* s = src;
|
||||||
|
uint64_t* d = dst;
|
||||||
|
for (size_t i = 0; i < (size>>3); ++i) {
|
||||||
|
d[i] = s[i];
|
||||||
|
asm volatile("":::"memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size & 7) {
|
||||||
|
for (size_t i = (size>>3)<<3; i < size; ++i) {
|
||||||
|
((uint8_t*)dst)[i] = ((const uint8_t*)src)[i];
|
||||||
|
asm volatile("":::"memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void storage_serialize_xip(size_t page,
|
||||||
|
size_t pagestart, size_t pageend, void* dest) {
|
||||||
|
if (page == 0) {
|
||||||
|
st_safe_memcpy((uint8_t*)dest + pageend - pagestart,
|
||||||
|
&header_tmp, sizeof(struct storage_header));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < header_tmp.nmodes; ++i) {
|
||||||
|
struct mode_data* md = &header_tmp.mode_data_table[i];
|
||||||
|
uint32_t mdoffset = md->offsetandmode & ((1<<28)-1);
|
||||||
|
uint32_t mdsize = md->datasize;
|
||||||
|
|
||||||
|
if (mdoffset < pagestart || mdoffset + mdsize >= pageend)
|
||||||
|
continue; // must be fully within the page
|
||||||
|
|
||||||
|
msto[i].get_data((uint8_t*)dest + mdoffset - pagestart, 0, mdsize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void storage_write_data(void) {
|
||||||
|
size_t npages = storage_allocate_new();
|
||||||
|
if (npages == 0) {
|
||||||
|
storage_init();
|
||||||
|
return; // TODO: error, somehow
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t base[STORAGE_ERASEWRITE_ALIGN]; // TODO FIXME: HUGE RAM HOG!
|
||||||
|
|
||||||
|
size_t current_page = STORAGE_SIZE - STORAGE_ERASEWRITE_ALIGN,
|
||||||
|
current_page_end = STORAGE_SIZE - sizeof(struct storage_header);
|
||||||
|
for (size_t page = 0; page < npages; ++page) {
|
||||||
|
storage_serialize_xip(page, current_page, current_page_end, base);
|
||||||
|
|
||||||
|
if (!storage_erasewrite(current_page, base, STORAGE_ERASEWRITE_ALIGN)) {
|
||||||
|
storage_init();
|
||||||
|
return; // TODO: error, somehow
|
||||||
|
}
|
||||||
|
|
||||||
|
current_page_end = current_page;
|
||||||
|
current_page -= STORAGE_ERASEWRITE_ALIGN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// * save on a timer event?
|
||||||
|
// * try to save when unplugging???
|
||||||
|
}
|
||||||
|
|
||||||
|
bool storage_flush_data(void) {
|
||||||
|
if (mode_bad != 0 || mode_current_id != header_tmp.curmode) {
|
||||||
|
storage_write_data();
|
||||||
|
return true;
|
||||||
|
} else for (int i = 1; i < 16; ++i) {
|
||||||
|
if (mode_list[i] == NULL || !storage_priv_mode_has(i)) continue;
|
||||||
|
|
||||||
|
if (mode_list[i]->storage.is_dirty()) {
|
||||||
|
storage_write_data();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -13,5 +13,7 @@ void stdio_usb_set_itf_num(int itf);
|
||||||
void stdio_usb_line_coding_cb(cdc_line_coding_t const* line_coding);
|
void stdio_usb_line_coding_cb(cdc_line_coding_t const* line_coding);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void stdio_uart_init(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ static uint32_t rxavail, rxpos, txpos;
|
||||||
|
|
||||||
static int VND_N_CFG = 0;
|
static int VND_N_CFG = 0;
|
||||||
|
|
||||||
|
extern uint8_t data_tmp[256];
|
||||||
|
|
||||||
void vnd_cfg_init(void) {
|
void vnd_cfg_init(void) {
|
||||||
rxavail = 0;
|
rxavail = 0;
|
||||||
rxpos = 0;
|
rxpos = 0;
|
||||||
|
@ -125,6 +127,8 @@ void vnd_cfg_task(void) {
|
||||||
uint8_t cmd = vnd_cfg_read_byte();
|
uint8_t cmd = vnd_cfg_read_byte();
|
||||||
uint8_t verbuf[2];
|
uint8_t verbuf[2];
|
||||||
|
|
||||||
|
//printf("vcfg %02x\n", cmd);
|
||||||
|
|
||||||
if (cmd & 0xf0) {
|
if (cmd & 0xf0) {
|
||||||
uint8_t mode = (uint8_t)(cmd & 0xf0) >> 4;
|
uint8_t mode = (uint8_t)(cmd & 0xf0) >> 4;
|
||||||
uint8_t mcmd = cmd & 0x0f;
|
uint8_t mcmd = cmd & 0x0f;
|
||||||
|
@ -191,11 +195,54 @@ void vnd_cfg_task(void) {
|
||||||
case cfg_cmd_get_infostr:
|
case cfg_cmd_get_infostr:
|
||||||
vnd_cfg_write_str(cfg_resp_ok, INFO_PRODUCT(INFO_BOARDNAME));
|
vnd_cfg_write_str(cfg_resp_ok, INFO_PRODUCT(INFO_BOARDNAME));
|
||||||
break;
|
break;
|
||||||
|
#if defined(PERSISTENT_STORAGE) && defined(DBOARD_HAS_STORAGE)
|
||||||
|
case cfg_cmd_storage_get_header:
|
||||||
|
vnd_cfg_write_resp(cfg_resp_ok, 256, storage_priv_get_header_ptr());
|
||||||
|
break;
|
||||||
|
case cfg_cmd_storage_get_modedata:
|
||||||
|
verbuf[0] = vnd_cfg_read_byte();
|
||||||
|
if (verbuf[0] == 0 || verbuf[0] >= 16 || mode_list[verbuf[0]] == NULL) {
|
||||||
|
vnd_cfg_write_resp(cfg_resp_nosuchmode, 0, NULL);
|
||||||
|
} else if (!storage_priv_mode_has(verbuf[0])) {
|
||||||
|
vnd_cfg_write_resp(cfg_resp_badarg, 0, NULL);
|
||||||
|
} else {
|
||||||
|
uint32_t len = storage_mode_get_info(verbuf[0]).size;
|
||||||
|
vnd_cfg_write_byte(cfg_resp_ok);
|
||||||
|
if (len < (1<<7)) {
|
||||||
|
vnd_cfg_write_byte(len);
|
||||||
|
} else if (len < (1<<14)) {
|
||||||
|
vnd_cfg_write_byte((len & 0x7f) | 0x80);
|
||||||
|
vnd_cfg_write_byte((len >> 7) & 0x7f);
|
||||||
|
} else {
|
||||||
|
vnd_cfg_write_byte((len & 0x7f) | 0x80);
|
||||||
|
vnd_cfg_write_byte(((len >> 7) & 0x7f) | 0x80);
|
||||||
|
vnd_cfg_write_byte(((len >> 14) & 0x7f));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i += sizeof data_tmp) {
|
||||||
|
size_t tosend = sizeof data_tmp;
|
||||||
|
if (tosend > len - i) tosend = len - i;
|
||||||
|
storage_mode_read(verbuf[0], data_tmp, i, tosend);
|
||||||
|
|
||||||
|
for (size_t ii = 0; ii < tosend; ++ii)
|
||||||
|
vnd_cfg_write_byte(data_tmp[ii]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vnd_cfg_write_flush();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case cfg_cmd_storage_flush_data:
|
||||||
|
verbuf[0] = storage_flush_data() ? 1 : 0;
|
||||||
|
vnd_cfg_write_resp(cfg_resp_ok, 1, verbuf);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
vnd_cfg_write_resp(cfg_resp_illcmd, 0, NULL);
|
vnd_cfg_write_resp(cfg_resp_illcmd, 0, NULL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//printf("vnd cfg cmd=%02x done\n", cmd);
|
||||||
}
|
}
|
||||||
#else /* CFG_TUD_VENDOR == 0 */
|
#else /* CFG_TUD_VENDOR == 0 */
|
||||||
void vnd_cfg_init(void) { }
|
void vnd_cfg_init(void) { }
|
||||||
|
|
|
@ -30,6 +30,12 @@ enum cfg_cmd {
|
||||||
cfg_cmd_get_cur_mode = 0x02,
|
cfg_cmd_get_cur_mode = 0x02,
|
||||||
cfg_cmd_set_cur_mode = 0x03,
|
cfg_cmd_set_cur_mode = 0x03,
|
||||||
cfg_cmd_get_infostr = 0x04,
|
cfg_cmd_get_infostr = 0x04,
|
||||||
|
|
||||||
|
#if defined(PERSISTENT_STORAGE) && defined(DBOARD_HAS_STORAGE)
|
||||||
|
cfg_cmd_storage_get_header = 0x0c,
|
||||||
|
cfg_cmd_storage_get_modedata = 0x0d,
|
||||||
|
cfg_cmd_storage_flush_data = 0x0e,
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// common commands for every mode
|
// common commands for every mode
|
||||||
|
|
Loading…
Reference in New Issue