Browse Source

initial

main
sys64738 2 months ago
commit
10a8df94db
  1. 1
      .gitignore
  2. 104
      CMakeLists.txt
  3. 3
      README.md
  4. 61
      bsp/rp2040/board.h
  5. 27
      bsp/rp2040/bsp-feature.h
  6. 30
      bsp/rp2040/bsp-info.h
  7. 166
      bsp/rp2040/cdc_stdio.c
  8. 28
      bsp/rp2040/ftdi/asyncbb.c
  9. 23
      bsp/rp2040/ftdi/cpufifo.c
  10. 22
      bsp/rp2040/ftdi/fifo.c
  11. 336
      bsp/rp2040/ftdi/ftdi_hw.c
  12. 73
      bsp/rp2040/ftdi/ftdi_hw.h
  13. 100
      bsp/rp2040/ftdi/ftdi_proto.c
  14. 94
      bsp/rp2040/ftdi/ftdi_uart_rx.pio
  15. 61
      bsp/rp2040/ftdi/ftdi_uart_tx.pio
  16. 38
      bsp/rp2040/ftdi/mcuhost.c
  17. 82
      bsp/rp2040/ftdi/mpsse.c
  18. 22
      bsp/rp2040/ftdi/syncbb.c
  19. 148
      bsp/rp2040/ftdi/uart.c
  20. 119
      bsp/rp2040/pinout.h
  21. 59
      bsp/rp2040/unique.c
  22. 62
      cmake/pico_sdk_import.cmake
  23. 132
      libco/libco.S
  24. 16
      libco/libco.h
  25. 495
      src/ftdi.c
  26. 375
      src/ftdi.h
  27. 88
      src/main.c
  28. 44
      src/thread.c
  29. 19
      src/thread.h
  30. 120
      src/tusb_config.h
  31. 212
      src/usb_descriptors.c
  32. 19
      src/usbstdio.h
  33. 19
      src/util.h

1
.gitignore

@ -0,0 +1 @@
build/

104
CMakeLists.txt

@ -0,0 +1,104 @@
# use directory name for project id
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)
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(PROJECT "picoftdi")
#set(PROJECT ${BOARD}-${PROJECT})
# TOP is absolute path to root directory of TinyUSB git repo
# Check for -DFAMILY=
if(FAMILY STREQUAL "rp2040")
option(PICO_NO_FLASH "Disable writing the compiled program to flash, and only load it to RAM. Useful for testing, but not much else (OFF by default)." ON)
option(PICO_COPY_TO_RAM "Run all code in RAM, while the program is also stored on flash. On bootup, everything will be copied to RAM (OFF by default)." OFF)
cmake_minimum_required(VERSION 3.12)
set(TOP "$ENV{PICO_SDK_PATH}/lib/tinyusb")
get_filename_component(TOP "${TOP}" REALPATH)
include(cmake/pico_sdk_import.cmake)
include(${TOP}/hw/bsp/${FAMILY}/family.cmake)
#family_get_project_name(PROJECT ${CMAKE_CURRENT_LIST_DIR})
project(${PROJECT})
family_initialize_project(PROJECT ${CMAKE_CURRENT_LIST_DIR}) # calls pico_sdk_init()
#pico_sdk_init()
add_executable(${PROJECT})
if(USE_USBCDC_FOR_STDIO)
# we're going to manually implement this case
#pico_enable_stdio_uart(${PROJECT} 0)
target_compile_definitions(${PROJECT} PUBLIC USE_USBCDC_FOR_STDIO=1)
else()
#pico_enable_stdio_uart(${PROJECT} 1)
endif()
# TODO: separate flag for disabling this one?
pico_enable_stdio_uart(${PROJECT} 1)
pico_enable_stdio_usb(${PROJECT} 0)
# Example source
target_sources(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/libco/libco.S
${CMAKE_CURRENT_SOURCE_DIR}/src/main.c
${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c
${CMAKE_CURRENT_SOURCE_DIR}/src/thread.c
${CMAKE_CURRENT_SOURCE_DIR}/src/ftdi.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/cdc_stdio.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/unique.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/asyncbb.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/cpufifo.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/fifo.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/ftdi_hw.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/ftdi_proto.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/mcuhost.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/mpsse.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/syncbb.c
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/uart.c
)
# Example include
target_include_directories(${PROJECT} PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/libco/
${CMAKE_CURRENT_SOURCE_DIR}/src/
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/
${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/
)
# Example defines
target_compile_definitions(${PROJECT} PUBLIC
)
target_link_libraries(${PROJECT} pico_stdlib pico_unique_id hardware_spi
hardware_i2c hardware_adc hardware_pio hardware_dma hardware_pwm
pico_fix_rp2040_usb_device_enumeration
tinyusb_device tinyusb_board tinyusb_additions)
if(USE_USBCDC_FOR_STDIO)
target_include_directories(${PROJECT} PUBLIC
${PICO_SDK_PATH}/src/rp2_common/pico_stdio_usb/include/
)
# extremely ugly hack to prevent the Pico SDK to declare *its* TinyUSB config
# and also to modify the tinyusb board.h file a bit
target_compile_definitions(${PROJECT} PUBLIC
_PICO_STDIO_USB_TUSB_CONFIG_H=1
BOARD_H_=1
)
target_link_libraries(${PROJECT} pico_stdio)
endif()
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/ftdi_uart_rx.pio)
pico_generate_pio_header(${PROJECT} ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/ftdi/ftdi_uart_tx.pio)
pico_add_extra_outputs(${PROJECT})
else()
message(FATAL_ERROR "Invalid FAMILY specified")
endif()
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_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--cref")

3
README.md

@ -0,0 +1,3 @@
# PicoFTDI
WIP reimplementation of the FT2232D chip from FTDI on the Raspberry Pico.

61
bsp/rp2040/board.h

@ -0,0 +1,61 @@
// vim: set et:
/*
* The MIT License (MIT)
*
* Copyright (c) 2021, Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* This file is part of the TinyUSB stack.
*/
#ifndef BOARD_H_MOD
#define BOARD_H_MOD
#include <pico/bootrom.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef PICO_DEFAULT_LED_PIN
#define LED_PIN PICO_DEFAULT_LED_PIN
#define LED_STATE_ON (!(PICO_DEFAULT_LED_PIN_INVERTED))
#endif
// Button pin is BOOTSEL which is flash CS pin
#define BUTTON_BOOTSEL
#define BUTTON_STATE_ACTIVE 0
#if !defined(USE_USBCDC_FOR_STDIO) && defined(PICO_DEFAULT_UART_TX_PIN) && \
defined(PICO_DEFAULT_UART_RX_PIN) && defined(PICO_DEFAULT_UART)
#define UART_DEV PICO_DEFAULT_UART
#define UART_TX_PIN PICO_DEFAULT_UART_TX_PIN
#define UART_RX_PIN PICO_DEFAULT_UART_RX_PIN
#endif
// (not actually for TinyUSB overrides)
// Reset to bootloader
#define bsp_reset_bootloader() reset_usb_boot(0, 0)
#ifdef __cplusplus
}
#endif
#endif /* BOARD_H_ */

27
bsp/rp2040/bsp-feature.h

@ -0,0 +1,27 @@
#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 {
#ifdef USE_USBCDC_FOR_STDIO
CDC_N_STDIO = 0,
#endif
CDC_N__NITF
};
enum {
VND_N_FTDI_IFA = 0,
VND_N_FTDI_IFB = 1,
VND_N__NITF
};
#endif

30
bsp/rp2040/bsp-info.h

@ -0,0 +1,30 @@
// vim: set et:
#ifndef BSP_INFO_H_
#define BSP_INFO_H_
/*#define USB_VID 0x2e8a*/ /* Raspberry Pi */
#define USB_VID 0xcafe /* TinyUSB */
/*#define USB_VID 0x1209*/ /* Generic */
/*#define USB_VID 0x1d50*/ /* OpenMoko */
#define USB_PID 0x1312
// TODO: other RP2040 boards
#define INFO_BOARDNAME "RP2040 Pico"
/* each CFG_TUD_xxx macro must be the max across all modes */
// TODO: have this depend on the DBOARD_HAS_xxx macros?
/*#define CFG_TUD_HID 0
#ifdef USE_USBCDC_FOR_STDIO
#define CFG_TUD_CDC 1
#else
#define CFG_TUD_CDC 0
#endif
#define CFG_TUD_VENDOR 2*/
/* don't access storage for RAM-only builds */
#if !PICO_NO_FLASH
#define DBOARD_HAS_STORAGE
#endif
#endif

166
bsp/rp2040/cdc_stdio.c

@ -0,0 +1,166 @@
// vim: set et:
#ifdef USE_USBCDC_FOR_STDIO
#include <hardware/irq.h>
#include <pico/binary_info.h>
#include <pico/bootrom.h>
#include <pico/mutex.h>
#include <pico/stdio.h>
#include <pico/stdio/driver.h>
#include <pico/time.h>
#include "tusb_config.h"
#include <tusb.h>
#include "board.h"
// PICO_CONFIG: PICO_STDIO_USB_STDOUT_TIMEOUT_US, Number of microseconds to be blocked trying to write USB output before assuming the host has disappeared and discarding data, default=500000, group=pico_stdio_usb
#ifndef PICO_STDIO_USB_STDOUT_TIMEOUT_US
#define PICO_STDIO_USB_STDOUT_TIMEOUT_US 500000
#endif
// PICO_CONFIG: PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE, Enable/disable resetting into BOOTSEL mode if the host sets the baud rate to a magic value (PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE), type=bool, default=1, group=pico_stdio_usb
#ifndef PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE
#define PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE 1
#endif
// PICO_CONFIG: PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE, baud rate that if selected causes a reset into BOOTSEL mode (if PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE is set), default=1200, group=pico_stdio_usb
#ifndef PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE
#define PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE 1200
#endif
// PICO_CONFIG: PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED, Optionally define a pin to use as bootloader activity LED when BOOTSEL mode is entered via USB (either VIA_BAUD_RATE or VIA_VENDOR_INTERFACE), type=int, min=0, max=29, group=pico_stdio_usb
// PICO_CONFIG: PICO_STDIO_USB_RESET_BOOTSEL_FIXED_ACTIVITY_LED, Whether the pin specified by PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED is fixed or can be modified by picotool over the VENDOR USB interface, type=bool, default=0, group=pico_stdio_usb
#ifndef PICO_STDIO_USB_RESET_BOOTSEL_FIXED_ACTIVITY_LED
#define PICO_STDIO_USB_RESET_BOOTSEL_FIXED_ACTIVITY_LED 0
#endif
// Any modes disabled here can't be re-enabled by picotool via VENDOR_INTERFACE.
// PICO_CONFIG: PICO_STDIO_USB_RESET_BOOTSEL_INTERFACE_DISABLE_MASK, Optionally disable either the mass storage interface (bit 0) or the PICOBOOT interface (bit 1) when entering BOOTSEL mode via USB (either VIA_BAUD_RATE or VIA_VENDOR_INTERFACE), type=int, min=0, max=3, default=0, group=pico_stdio_usb
#ifndef PICO_STDIO_USB_RESET_BOOTSEL_INTERFACE_DISABLE_MASK
#define PICO_STDIO_USB_RESET_BOOTSEL_INTERFACE_DISABLE_MASK 0u
#endif
// *mostly* the same as the SDK code, *except* we have to explicitely pass the
// CDC interface number to the tusb functions, making the SDK code itself very
// non-reusable >__>
static mutex_t stdio_usb_mutex;
static int CDC_N_STDIO = 0;
void stdio_usb_set_itf_num(int itf) {
CDC_N_STDIO = itf;
}
static void stdio_usb_out_chars(const char* buf, int length) {
static uint64_t last_avail_time;
uint32_t owner;
if (!mutex_try_enter(&stdio_usb_mutex, &owner)) {
if (owner == get_core_num()) return; // would deadlock otherwise
mutex_enter_blocking(&stdio_usb_mutex);
}
if (tud_cdc_n_connected(CDC_N_STDIO)) {
for (int i = 0; i < length;) {
int n = length - i;
int avail = tud_cdc_n_write_available(CDC_N_STDIO);
if (n > avail) n = avail;
if (n) {
int n2 = tud_cdc_n_write(CDC_N_STDIO, buf + i, n);
tud_task();
tud_cdc_n_write_flush(CDC_N_STDIO);
i += n2;
last_avail_time = time_us_64();
} else {
tud_task();
tud_cdc_n_write_flush(CDC_N_STDIO);
if (!tud_cdc_n_connected(CDC_N_STDIO) ||
(!tud_cdc_n_write_available(CDC_N_STDIO) &&
time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) {
break;
}
}
}
} else {
// reset our timeout
last_avail_time = 0;
}
mutex_exit(&stdio_usb_mutex);
}
static int stdio_usb_in_chars(char* buf, int length) {
uint32_t owner;
// tricky thing
/*for (size_t i = 0; i < length; ++i)
if (buf[i] == 'R')
bsp_reset_bootloader();*/
if (!mutex_try_enter(&stdio_usb_mutex, &owner)) {
if (owner == get_core_num()) return PICO_ERROR_NO_DATA; // would deadlock otherwise
mutex_enter_blocking(&stdio_usb_mutex);
}
int rc = PICO_ERROR_NO_DATA;
if (tud_cdc_n_connected(CDC_N_STDIO) && tud_cdc_n_available(CDC_N_STDIO)) {
int count = tud_cdc_n_read(CDC_N_STDIO, buf, length);
rc = count ? count : PICO_ERROR_NO_DATA;
}
mutex_exit(&stdio_usb_mutex);
return rc;
}
extern stdio_driver_t stdio_usb;
// clang-format off
stdio_driver_t stdio_usb = {
.out_chars = stdio_usb_out_chars,
.in_chars = stdio_usb_in_chars,
#if PICO_STDIO_ENABLE_CRLF_SUPPORT
.crlf_enabled = PICO_STDIO_DEFAULT_CRLF
#endif
};
// clang-format on
bool stdio_usb_init(void) {
//#if !PICO_NO_BI_STDIO_USB
bi_decl_if_func_used(bi_program_feature("USB stdin / stdout"));
//#endif
mutex_init(&stdio_usb_mutex);
// unlike with the SDK code, we don't need to add IRQ stuff for the USB
// task, as our main function handles this automatically
stdio_set_driver_enabled(&stdio_usb, true);
return true;
}
void stdio_usb_line_coding_cb(cdc_line_coding_t const* line_coding) {
#if PICO_STDIO_USB_ENABLE_RESET_VIA_BAUD_RATE
if (line_coding->bit_rate == PICO_STDIO_USB_RESET_MAGIC_BAUD_RATE) {
uint32_t gpio = 0;
#ifdef PICO_STDIO_USB_RESET_BOOTSEL_ACTIVITY_LED
gpio = 1u << PICO_STDIO_USB_RESET_BOOTSEL_FIXED_ACTIVITY_LED;
#endif
reset_usb_boot(gpio, PICO_STDIO_USB_RESET_BOOTSEL_INTERFACE_DISABLE_MASK);
}
#endif
}
#endif

28
bsp/rp2040/ftdi/asyncbb.c

@ -0,0 +1,28 @@
// vim: set et:
#include "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;
}

23
bsp/rp2040/ftdi/cpufifo.c

@ -0,0 +1,23 @@
// vim: set et:
#include "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;
}

22
bsp/rp2040/ftdi/fifo.c

@ -0,0 +1,22 @@
// vim: set et:
#include "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;
}

336
bsp/rp2040/ftdi/ftdi_hw.c

@ -0,0 +1,336 @@
// 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 "ftdi/ftdi_hw.h"
#include "ftdi_uart_tx.pio.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;
fr->rx.dmabuf_dataend = 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);*/
printf("inited hw\n");
}
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;
printf("inited ch\n");
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) {
return;
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) {
return;
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) {
return;
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) {
return;
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;
io_ro_8* rx = (io_ro_8*)&hw->pio->rxf[hw->rx.piosm];
for (; !pio_sm_is_rx_fifo_empty(hw->pio, hw->rx.piosm) && rv < maxsize; ++rv) {
dest[rv] = *rx;
}
return rv;
// 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;
}
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
//io_wo_32/*8*/* tx = (io_wo_32/*8*/*)hw->pio->txf[hw->tx.piosm];
for (size_t i = 0; !pio_sm_is_tx_fifo_full(hw->pio, hw->tx.piosm) && i < size; ++i) {
printf("send %02x=%c\n", src[i], src[i]);
ftdi_uart_tx_program_putc(hw->pio, hw->tx.piosm, src[i]);
//*tx = (uint32_t)src[i];
}
return true;
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]);
}
}
}

73
bsp/rp2040/ftdi/ftdi_hw.h

@ -0,0 +1,73 @@
// vim: set et:
#ifndef FTDI_BASE_H_
#define FTDI_BASE_H_
#include <stdint.h>
#include <stdbool.h>
#include "pinout.h"
#include "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

100
bsp/rp2040/ftdi/ftdi_proto.c

@ -0,0 +1,100 @@
// vim: set et:
#include <hardware/pio.h>
#include "ftdi.h"
#include "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: ? */ }

94
bsp/rp2040/ftdi/ftdi_uart_rx.pio

@ -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;
}
%}

61
bsp/rp2040/ftdi/ftdi_uart_tx.pio

@ -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++);
}
%}

38
bsp/rp2040/ftdi/mcuhost.c

@ -0,0 +1,38 @@
// vim: set et:
#include "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;
}

82
bsp/rp2040/ftdi/mpsse.c

@ -0,0 +1,82 @@
// vim: set et:
#include "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;
}

22
bsp/rp2040/ftdi/syncbb.c

@ -0,0 +1,22 @@
// vim: set et:
#include "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;
}

148
bsp/rp2040/ftdi/uart.c

@ -0,0 +1,148 @@
// vim: set et:
#include <hardware/dma.h>
#include <hardware/gpio.h>
#include <hardware/pio.h>
#include <hardware/structs/dma.h>
#include "pinout.h"
#include "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);
printf("inited baudrate=%lu\n", 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 (baudrate == 0) return;
if (!STATEOF(itf).enabled) return;
return; // always ignore - decoding is broken atm
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;
// TODO: directly set clock divider
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;
printf("set baudrate=%lu\n", 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);
}

119
bsp/rp2040/pinout.h

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