commit 10a8df94db6e700e87d8e012a73edbf34cb10d9f Author: sys64738 Date: Sun Sep 26 12:40:27 2021 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..15dd144 --- /dev/null +++ b/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") + diff --git a/README.md b/README.md new file mode 100644 index 0000000..092ef64 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# PicoFTDI + +WIP reimplementation of the FT2232D chip from FTDI on the Raspberry Pico. diff --git a/bsp/rp2040/board.h b/bsp/rp2040/board.h new file mode 100644 index 0000000..bc1d289 --- /dev/null +++ b/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 + +#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_ */ diff --git a/bsp/rp2040/bsp-feature.h b/bsp/rp2040/bsp-feature.h new file mode 100644 index 0000000..e981da3 --- /dev/null +++ b/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 diff --git a/bsp/rp2040/bsp-info.h b/bsp/rp2040/bsp-info.h new file mode 100644 index 0000000..fd2e2bd --- /dev/null +++ b/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 diff --git a/bsp/rp2040/cdc_stdio.c b/bsp/rp2040/cdc_stdio.c new file mode 100644 index 0000000..f4aae06 --- /dev/null +++ b/bsp/rp2040/cdc_stdio.c @@ -0,0 +1,166 @@ +// vim: set et: + +#ifdef USE_USBCDC_FOR_STDIO + +#include +#include +#include +#include +#include +#include +#include + +#include "tusb_config.h" +#include + +#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 + diff --git a/bsp/rp2040/ftdi/asyncbb.c b/bsp/rp2040/ftdi/asyncbb.c new file mode 100644 index 0000000..8b113a0 --- /dev/null +++ b/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; +} + + diff --git a/bsp/rp2040/ftdi/cpufifo.c b/bsp/rp2040/ftdi/cpufifo.c new file mode 100644 index 0000000..7a28dfa --- /dev/null +++ b/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; +} + diff --git a/bsp/rp2040/ftdi/fifo.c b/bsp/rp2040/ftdi/fifo.c new file mode 100644 index 0000000..514f535 --- /dev/null +++ b/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; +} + diff --git a/bsp/rp2040/ftdi/ftdi_hw.c b/bsp/rp2040/ftdi/ftdi_hw.c new file mode 100644 index 0000000..ecf0fce --- /dev/null +++ b/bsp/rp2040/ftdi/ftdi_hw.c @@ -0,0 +1,336 @@ +// vim: set et: + +/* include order matters here */ +#include +#include +#include +#include +#include + +#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]); + } + } +} + diff --git a/bsp/rp2040/ftdi/ftdi_hw.h b/bsp/rp2040/ftdi/ftdi_hw.h new file mode 100644 index 0000000..51f672a --- /dev/null +++ b/bsp/rp2040/ftdi/ftdi_hw.h @@ -0,0 +1,73 @@ +// vim: set et: + +#ifndef FTDI_BASE_H_ +#define FTDI_BASE_H_ + +#include +#include + +#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 + diff --git a/bsp/rp2040/ftdi/ftdi_proto.c b/bsp/rp2040/ftdi/ftdi_proto.c new file mode 100644 index 0000000..66dcac9 --- /dev/null +++ b/bsp/rp2040/ftdi/ftdi_proto.c @@ -0,0 +1,100 @@ +// vim: set et: + +#include + +#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: ? */ } + diff --git a/bsp/rp2040/ftdi/ftdi_uart_rx.pio b/bsp/rp2040/ftdi/ftdi_uart_rx.pio new file mode 100644 index 0000000..8a83f68 --- /dev/null +++ b/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; +} + +%} diff --git a/bsp/rp2040/ftdi/ftdi_uart_tx.pio b/bsp/rp2040/ftdi/ftdi_uart_tx.pio new file mode 100644 index 0000000..8a56489 --- /dev/null +++ b/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++); +} + +%} diff --git a/bsp/rp2040/ftdi/mcuhost.c b/bsp/rp2040/ftdi/mcuhost.c new file mode 100644 index 0000000..5613d8e --- /dev/null +++ b/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; +} + diff --git a/bsp/rp2040/ftdi/mpsse.c b/bsp/rp2040/ftdi/mpsse.c new file mode 100644 index 0000000..d3817a3 --- /dev/null +++ b/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; +} + diff --git a/bsp/rp2040/ftdi/syncbb.c b/bsp/rp2040/ftdi/syncbb.c new file mode 100644 index 0000000..3127c73 --- /dev/null +++ b/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; +} + diff --git a/bsp/rp2040/ftdi/uart.c b/bsp/rp2040/ftdi/uart.c new file mode 100644 index 0000000..7d69706 --- /dev/null +++ b/bsp/rp2040/ftdi/uart.c @@ -0,0 +1,148 @@ +// vim: set et: + +#include +#include +#include +#include + +#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 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); +} + diff --git a/bsp/rp2040/pinout.h b/bsp/rp2040/pinout.h new file mode 100644 index 0000000..c6278e2 --- /dev/null +++ b/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 + +#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 + diff --git a/bsp/rp2040/unique.c b/bsp/rp2040/unique.c new file mode 100644 index 0000000..ab4df45 --- /dev/null +++ b/bsp/rp2040/unique.c @@ -0,0 +1,59 @@ +// vim: set et: + +#include + +#include +#include +#include + +#include "tusb.h" +#include "util.h" + +uint8_t get_unique_id_u8(uint8_t* desc_str) { + pico_unique_board_id_t uid; + + uint8_t chr_count = 0; + + pico_get_unique_board_id(&uid); + + for (size_t byte = 0; byte < PICO_UNIQUE_BOARD_ID_SIZE_BYTES/*TU_ARRAY_SIZE(uid.id)*/; byte++) { + uint8_t tmp = uid.id[byte]; + for (int digit = 0; digit < 2; digit++) { + desc_str[chr_count++] = nyb2hex(tmp >> 4); + tmp <<= 4; + } + } + + return chr_count; +} + +uint8_t get_unique_id_u16(uint16_t* desc_str) { + pico_unique_board_id_t uid; + + uint8_t chr_count = 0; + + pico_get_unique_board_id(&uid); + + for (size_t byte = 0; byte < PICO_UNIQUE_BOARD_ID_SIZE_BYTES/*TU_ARRAY_SIZE(uid.id)*/; byte++) { + uint8_t tmp = uid.id[byte]; + for (int digit = 0; digit < 2; digit++) { + desc_str[chr_count++] = nyb2hex(tmp >> 4); + tmp <<= 4; + } + } + + return chr_count; +} + +// IDK, let's just put this somewhere + +#ifdef PICO_NO_FLASH +bi_decl(bi_program_build_attribute("Not in flash")); +#elif defined(PICO_COPY_TO_RAM) +bi_decl(bi_program_build_attribute("Copy-to-RAM")); +#endif + +#ifdef USE_USBCDC_FOR_STDIO +bi_decl(bi_program_build_attribute("USB-CDC stdio debug interface")); +#endif + diff --git a/cmake/pico_sdk_import.cmake b/cmake/pico_sdk_import.cmake new file mode 100644 index 0000000..28efe9e --- /dev/null +++ b/cmake/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG master + ) + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/libco/libco.S b/libco/libco.S new file mode 100644 index 0000000..85ad0d9 --- /dev/null +++ b/libco/libco.S @@ -0,0 +1,132 @@ +@ vim: set ft=armv5: + +.cpu cortex-m0 +.thumb + +/*.section .bss.co_active_buffer, "aw", %nobits +.type co_active_buffer, %object +.size co_active_buffer, 4*64 +co_active_buffer: + .space 4*64 + +.section .bss.co_active_handle, "aw", %nobits +.type co_active_handle, %object +.size co_active_handle, 4 +co_active_handle: + .space 4*/ + +.extern co_active_handle +.extern co_active_buffer + +.section .text.co_active, "ax", %progbits +.type co_active, %function +.thumb_func +.global co_active +co_active:@() + ldr r1, =co_active_handle + ldr r0, [r1] + cmp r0, #0 + bne 1f + + ldr r0, =co_active_buffer + str r0, [r1] + +1: bx lr + + .pool + +.section .text.co_switch, "ax", %progbits +.type co_switch, %function +.thumb_func +.global co_switch +co_switch:@(cothread_t handle r0) + @ co_previous_handle(r1) = co_active_handle + @ co_active_handle = handle(r0) + ldr r2, =co_active_handle + ldr r1, [r2] + str r0, [r2] + + @ co_swap() + + @ NOTE: we're assuming that the hw divider's state persistance won't be + @ used + + @ original ARM version: + @stmia r1!, {r4-r11,sp,lr} + @ldmia r0!, {r4-r11,sp,pc} + @bx lr + + stmia r1!, {r4-r7} + mov r2, r8 + mov r3, r9 + mov r4, r10 + mov r5, r11 + mov r6, r12 + mov r7, sp + stmia r1!, {r2-r7} + mov r2, lr + stmia r1!, {r2} + + @ TODO: this could be done better, but oh well + ldr r2, [r0, #0x10] @ r8 + ldr r3, [r0, #0x14] @ r9 + ldr r4, [r0, #0x18] @ r10 + ldr r5, [r0, #0x1c] @ r11 + ldr r6, [r0, #0x20] @ r12 + ldr r7, [r0, #0x24] @ sp + mov r8, r2 + mov r9, r3 + mov r10, r4 + mov r11, r5 + mov r12, r6 + mov sp, r7 + + ldmia r0!, {r4-r7} + + ldr r2, [r0, #(0x28-0x10)] @ pc + @ldr r0, [r0, #(0x2c-0x10)] @ ud/r0 / retval + mov pc, r2 + + bx lr + + .pool + +.section .text.co_derive, "ax", %progbits +.type co_derive, %function +.thumb_func +.global co_derive +co_derive:@(void* memory r0, unsigned int size r1, void(*entrypoint)(void*) r2, void* ud r3) + push {r4} + + @mov r12, r3 @ save ud for later + + @ if (!co_active_handle) co_active_handle = &co_active_buffer + ldr r3, =co_active_handle + ldr r4, [r3] + cmp r4, #0 + bne 1f + + ldr r4, =co_active_buffer + str r4, [r3] + + @ if (!memory) return 0; +1: cmp r0, #0 + beq .Lret + + @ offset(r3) = size(r1) & ~15 + mov r3, r1 + mov r4, #0xf + bic r3, r4 @ doesn't want to do an immediate, sigh... + @ p(r3) = handle(r0) + offset(r3) + add r3, r0 + @ initialize stack, entrypoint + @str r12, [r0, #(11*4)] @ init r0: user argument + str r3 , [r0, #( 9*4)] @ init sp/r13 + str r2 , [r0, #(10*4)] @ init pc/r15 + +.Lret: + pop {r4} + bx lr + + .pool + diff --git a/libco/libco.h b/libco/libco.h new file mode 100644 index 0000000..a787b9c --- /dev/null +++ b/libco/libco.h @@ -0,0 +1,16 @@ +// vim: set et: + +/* derived from libco v20, by byuu (ISC) */ + +#ifndef LIBCO_H_ +#define LIBCO_H_ + +typedef void* cothread_t; + +cothread_t co_active(void); +cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void/***/)/*, void* ud*/); +void co_switch(cothread_t); // should we make this return void* (and thus `ud`)? +int co_serializable(void); + +#endif + diff --git a/src/ftdi.c b/src/ftdi.c new file mode 100644 index 0000000..4d4bec5 --- /dev/null +++ b/src/ftdi.c @@ -0,0 +1,495 @@ +// vim: set et: + +#include "tusb_config.h" +#include + +#include "thread.h" + +#include "bsp-feature.h" +#include "ftdi.h" + +#include + +// 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) +// +// 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 + uint32_t frac = ftdi_brate_frac_lut[(enc_brate >> 14) & 7]; + //printf("enc: div=%lu frac=%lu\n", div, frac); + div = div | frac; + uint32_t baud = FT2232D_CLOCK / div; + //printf("rawbaud=%lu\n", baud); + + if (baud & 1) baud = (baud >> 1) + 1; // rounding + 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: { + // BUG: somehow, req->wValue gets completely mangled over USB? should + // be 0x001a for 115200 but I'm getting 0x4183 or so + //printf("value=%04x index=%04x\n", req->wValue, req->wIndex); + uint32_t enc_brate = (uint32_t)req->wValue | ((uint32_t)(req->wIndex & 0xff00) << 8); + uint32_t brate = ftdi_if_decode_baudrate(enc_brate); + //printf("br dec: %lu -> %lu\n", enc_brate, 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 */ + diff --git a/src/ftdi.h b/src/ftdi.h new file mode 100644 index 0000000..783c5f5 --- /dev/null +++ b/src/ftdi.h @@ -0,0 +1,375 @@ +// vim: set et: + +#ifndef FTDI_H_ +#define FTDI_H_ + +#include "tusb_config.h" +#include + +// 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 + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..8e86cd5 --- /dev/null +++ b/src/main.c @@ -0,0 +1,88 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 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. + * + */ + +#include +#include +#include +#include + +#include "bsp/board.h" +#include "bsp-feature.h" +#include "tusb.h" +#include "thread.h" +#include "ftdi.h" +#include "usbstdio.h" + +static cothread_t ifathread, ifbthread; +static uint8_t ifastack[0x800], ifbstack[0x800]; + +static void ifa_fn(void) { + while (1) { + ftdi_task_ifa(); + thread_yield(); + } +} + +static void ifb_fn(void) { + while (1) { + ftdi_task_ifb(); + thread_yield(); + } +} + +int main(void) { + board_init(); + + tusb_init(); + + // TODO: to main +#ifdef USE_USBCDC_FOR_STDIO + stdio_usb_set_itf_num(CDC_N_STDIO); +#endif + ftdi_init(); + + thread_init(); + + ifathread = co_derive(ifastack, sizeof ifastack, ifa_fn); + ifbthread = co_derive(ifbstack, sizeof ifbstack, ifb_fn); + + gpio_init(25); + gpio_set_function(25,GPIO_FUNC_SIO); + gpio_set_dir(25,true); + + while (1) { + tud_task(); // tinyusb device task + thread_enter(ifathread); + /*tud_task(); // tinyusb device task + thread_enter(ifbthread);*/ + + /*if (tud_vendor_n_mounted(VND_N_FTDI_IFA)) { + tud_task(); + }*/ + } + + return 0; +} + diff --git a/src/thread.c b/src/thread.c new file mode 100644 index 0000000..5f7550a --- /dev/null +++ b/src/thread.c @@ -0,0 +1,44 @@ +// vim: set et: + +#include +#include +#include + +#include "thread.h" + +extern uint32_t co_active_buffer[64]; +uint32_t co_active_buffer[64]; +extern cothread_t co_active_handle; +cothread_t co_active_handle; + +static cothread_t mainthread; + +static cothread_t threadarr[16]; /* 16 nested threads should be enough... */ +static size_t threadind; + +void thread_init(void) { + memset(threadarr, 0, sizeof threadarr); + + mainthread = co_active(); + threadarr[0] = mainthread; + threadind = 0; +} + +void thread_yield(void) { + /*cothread_t newthrd = threadarr[threadind]; + if (threadind > 0) --threadind;*/ + + co_switch(mainthread/*newthrd*/); +} + +void thread_enter(cothread_t thrid) { + /*if (threadind + 1 == sizeof(threadarr) / sizeof(threadarr[0])) { + // TODO: PANIC! + } + + threadarr[threadind] = co_active(); + ++threadind;*/ + + co_switch(thrid); +} + diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..846c6fa --- /dev/null +++ b/src/thread.h @@ -0,0 +1,19 @@ +// vim: set et: + +#ifndef THREAD_H_ +#define THREAD_H_ + +#include + +#define THREAD_STACK_SIZE 512 + +void thread_init (void); +void thread_yield(void); + +/* thread_enter + thread_yield can be used to "call" threads in a stack-like + * way, much like functions. this is needed because vnd_cfg might call mode + * stuff which might do stuff in its own tasks and so on. */ +void thread_enter(cothread_t thrid); + +#endif + diff --git a/src/tusb_config.h b/src/tusb_config.h new file mode 100644 index 0000000..f9f5e4b --- /dev/null +++ b/src/tusb_config.h @@ -0,0 +1,120 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 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. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// defined by board.mk +#ifndef CFG_TUSB_MCU + #error CFG_TUSB_MCU must be defined +#endif + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +// This example doesn't use an RTOS +#define CFG_TUSB_OS OPT_OS_PICO + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#ifdef USE_USBCDC_FOR_STDIO +#define CFG_TUD_CDC 1 +#else +#define CFG_TUD_CDC 0 +#endif +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 2 +#define CFG_TUD_NET 0 + +#define CFG_TUD_HID_EP_BUFSIZE 64 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/src/usb_descriptors.c b/src/usb_descriptors.c new file mode 100644 index 0000000..c3f024c --- /dev/null +++ b/src/usb_descriptors.c @@ -0,0 +1,212 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 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. + * + */ + +#include "tusb.h" +#include "util.h" +#include "ftdi.h" +#include "bsp-feature.h" +#include "usbstdio.h" + +// String Descriptor Index +enum +{ + STRID_LANGID = 0, + STRID_MANUFACTURER, + STRID_PRODUCT, + STRID_SERIAL, + + STRID_CONFIG, + + 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, + +#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 +#ifdef USE_USBCDC_FOR_STDIO + + TUD_CDC_DESC_LEN +#endif +}; + + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0x0403, + .idProduct = 0x6010, + .bcdDevice = 0x0500, + + .iManufacturer = STRID_MANUFACTURER, + .iProduct = STRID_PRODUCT, + .iSerialNumber = STRID_SERIAL, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +#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_CDC_STDIO_OUT 0x05 +#define EPNUM_CDC_STDIO_IN 0x85 +#define EPNUM_CDC_STDIO_NOTIF 0x86 + +uint8_t const 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), + +#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 +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + return desc_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + [STRID_LANGID] = (const char[]) { 0x09, 0x04 }, // supported language is English (0x0409) + [STRID_MANUFACTURER] = "not-FTDI", // Manufacturer + [STRID_PRODUCT] = "not-FT2232D", // Product + [STRID_CONFIG] = "Configuration descriptor", + // max string length check: ||||||||||||||||||||||||||||||| + [STRID_IF_VND_FTDI_IFA] = "FT2232D interface A", + [STRID_IF_VND_FTDI_IFB] = "FT2232D interface B", +#ifdef USE_USBCDC_FOR_STDIO + [STRID_IF_CDC_STDIO] = "stdio CDC interface (debug)", +#endif +}; +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count = 0; + + if (STRID_LANGID == index) + { + memcpy(&_desc_str[1], string_desc_arr[STRID_LANGID], 2); + chr_count = 1; + } else if (STRID_SERIAL == index) + { + chr_count = get_unique_id_u16(_desc_str + 1); + } else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = TU_MIN(strlen(str), 31); + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; i + +#ifdef USE_USBCDC_FOR_STDIO +void stdio_usb_init(void); + +void stdio_usb_set_itf_num(int itf); + +void stdio_usb_line_coding_cb(cdc_line_coding_t const* line_coding); +#endif + +void stdio_uart_init(void); + +#endif + diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..ae47e2d --- /dev/null +++ b/src/util.h @@ -0,0 +1,19 @@ +// vim: set et: + +#ifndef UTIL_H_ +#define UTIL_H_ + +static inline char nyb2hex(int x) { + if (x < 0xa) + return '0' + (x - 0x0); + else + return 'A' + (x - 0xa); +} + +// clang-format off +uint8_t get_unique_id_u8 (uint8_t * desc_str); +uint8_t get_unique_id_u16(uint16_t* desc_str); +// clang-format on + +#endif +