From f5c7be9a86e3131cd13d2cc3493b84b23676f8c4 Mon Sep 17 00:00:00 2001 From: graham sanderson Date: Wed, 20 Jan 2021 11:10:41 -0600 Subject: [PATCH] Initial Release --- .gitignore | 3 + .gitmodules | 5 + CMakeLists.txt | 32 + LICENSE.TXT | 21 + README.md | 46 + external/pico_extras_import.cmake | 62 + lib/lwip | 1 + pico_sdk_import.cmake | 62 + src/CMakeLists.txt | 4 + src/common/CMakeLists.txt | 5 + src/common/pico_audio/CMakeLists.txt | 16 + src/common/pico_audio/audio.cpp | 254 +++ src/common/pico_audio/audio_utils.S | 256 +++ src/common/pico_audio/include/pico/audio.h | 308 +++ .../include/pico/sample_conversion.h | 287 +++ src/common/pico_scanvideo/CMakeLists.txt | 14 + .../pico/scanvideo/composable_scanline.h | 40 + .../include/pico/scanvideo/scanvideo_base.h | 341 ++++ src/common/pico_scanvideo/scanvideo.pio | 119 ++ src/common/pico_scanvideo/vga_modes.c | 528 +++++ src/common/pico_sd_card/CMakeLists.txt | 4 + .../pico_sd_card/include/pico/sd_card.h | 54 + src/common/pico_util_buffer/CMakeLists.txt | 7 + src/common/pico_util_buffer/buffer.c | 17 + .../include/pico/util/buffer.h | 99 + src/common/platypus/CMakeLists.txt | 12 + src/common/platypus/decompress_row.S | 377 ++++ src/common/platypus/platypus.c | 229 +++ src/common/platypus/platypus.h | 24 + src/rp2_common/CMakeLists.txt | 14 + src/rp2_common/hardware_rosc/CMakeLists.txt | 1 + .../hardware_rosc/include/hardware/rosc.h | 90 + src/rp2_common/hardware_rosc/rosc.c | 61 + src/rp2_common/lwip/CMakeLists.txt | 59 + src/rp2_common/lwip/README.md | 1 + src/rp2_common/lwip/include/arch/cc.h | 75 + src/rp2_common/lwip/lwip_arch.c | 22 + src/rp2_common/pico_audio_i2s/CMakeLists.txt | 12 + src/rp2_common/pico_audio_i2s/audio_i2s.c | 362 ++++ src/rp2_common/pico_audio_i2s/audio_i2s.pio | 63 + .../pico_audio_i2s/include/pico/audio_i2s.h | 182 ++ src/rp2_common/pico_audio_pwm/CMakeLists.txt | 19 + src/rp2_common/pico_audio_pwm/audio_pwm.c | 389 ++++ src/rp2_common/pico_audio_pwm/audio_pwm.pio | 58 + .../pico_audio_pwm/include/pico/audio_pwm.h | 183 ++ .../include/pico/audio_pwm/sample_encoding.h | 92 + .../pico_audio_pwm/sample_encoding.cpp | 427 ++++ .../pico_audio_spdif/CMakeLists.txt | 13 + src/rp2_common/pico_audio_spdif/audio_spdif.c | 378 ++++ .../pico_audio_spdif/audio_spdif.pio | 28 + .../include/pico/audio_spdif.h | 163 ++ .../pico/audio_spdif/sample_encoding.h | 44 + .../pico_audio_spdif/sample_encoding.cpp | 51 + .../pico_scanvideo_dbi/CMakeLists.txt | 16 + src/rp2_common/pico_scanvideo_dbi/README.md | 1 + src/rp2_common/pico_scanvideo_dbi/control.pio | 25 + .../pico_scanvideo_dbi/tft_driver.c | 600 ++++++ .../pico_scanvideo_dbi/tft_driver.h | 94 + src/rp2_common/pico_scanvideo_dbi/vga_modes.c | 362 ++++ src/rp2_common/pico_scanvideo_dbi/video.h | 271 +++ src/rp2_common/pico_scanvideo_dbi/video_dbi.c | 1366 +++++++++++++ .../pico_scanvideo_dpi/CMakeLists.txt | 13 + .../include/pico/scanvideo.h | 59 + src/rp2_common/pico_scanvideo_dpi/scanvideo.c | 1813 +++++++++++++++++ src/rp2_common/pico_scanvideo_dpi/timing.pio | 34 + src/rp2_common/pico_sd_card/CMakeLists.txt | 12 + src/rp2_common/pico_sd_card/README.md | 1 + src/rp2_common/pico_sd_card/crc-itu-t.h | 34 + src/rp2_common/pico_sd_card/crc7.h | 34 + src/rp2_common/pico_sd_card/sd_card.c | 1109 ++++++++++ src/rp2_common/pico_sd_card/sd_card.pio | 59 + src/rp2_common/pico_sleep/CMakeLists.txt | 5 + .../pico_sleep/include/pico/sleep.h | 107 + src/rp2_common/pico_sleep/sleep.c | 155 ++ src/rp2_common/usb_common/CMakeLists.txt | 3 + .../usb_common/include/usb/usb_common.h | 134 ++ src/rp2_common/usb_device/CMakeLists.txt | 9 + .../usb_device/include/pico/usb_device.h | 188 ++ .../include/pico/usb_device_private.h | 135 ++ .../include/pico/usb_stream_helper.h | 45 + src/rp2_common/usb_device/usb_device.c | 1303 ++++++++++++ src/rp2_common/usb_device/usb_stream_helper.c | 138 ++ src/rp2_common/usb_device_msc/CMakeLists.txt | 8 + .../usb_device_msc/include/pico/scsi.h | 99 + .../usb_device_msc/include/pico/scsi_ir.h | 38 + .../include/pico/usb_device_msc.h | 21 + .../include/pico/virtual_disk.h | 41 + .../usb_device_msc/usb_device_msc.c | 488 +++++ test/CMakeLists.txt | 2 + test/sample_conversion_test/CMakeLists.txt | 9 + .../sample_conversion_test.cpp | 129 ++ test/sd_test/CMakeLists.txt | 15 + test/sd_test/sd_test.c | 131 ++ 93 files changed, 15120 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 LICENSE.TXT create mode 100644 README.md create mode 100644 external/pico_extras_import.cmake create mode 160000 lib/lwip create mode 100644 pico_sdk_import.cmake create mode 100644 src/CMakeLists.txt create mode 100644 src/common/CMakeLists.txt create mode 100644 src/common/pico_audio/CMakeLists.txt create mode 100644 src/common/pico_audio/audio.cpp create mode 100644 src/common/pico_audio/audio_utils.S create mode 100644 src/common/pico_audio/include/pico/audio.h create mode 100644 src/common/pico_audio/include/pico/sample_conversion.h create mode 100644 src/common/pico_scanvideo/CMakeLists.txt create mode 100644 src/common/pico_scanvideo/include/pico/scanvideo/composable_scanline.h create mode 100644 src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h create mode 100644 src/common/pico_scanvideo/scanvideo.pio create mode 100644 src/common/pico_scanvideo/vga_modes.c create mode 100644 src/common/pico_sd_card/CMakeLists.txt create mode 100644 src/common/pico_sd_card/include/pico/sd_card.h create mode 100644 src/common/pico_util_buffer/CMakeLists.txt create mode 100644 src/common/pico_util_buffer/buffer.c create mode 100644 src/common/pico_util_buffer/include/pico/util/buffer.h create mode 100644 src/common/platypus/CMakeLists.txt create mode 100644 src/common/platypus/decompress_row.S create mode 100644 src/common/platypus/platypus.c create mode 100644 src/common/platypus/platypus.h create mode 100644 src/rp2_common/CMakeLists.txt create mode 100644 src/rp2_common/hardware_rosc/CMakeLists.txt create mode 100644 src/rp2_common/hardware_rosc/include/hardware/rosc.h create mode 100644 src/rp2_common/hardware_rosc/rosc.c create mode 100644 src/rp2_common/lwip/CMakeLists.txt create mode 100644 src/rp2_common/lwip/README.md create mode 100644 src/rp2_common/lwip/include/arch/cc.h create mode 100644 src/rp2_common/lwip/lwip_arch.c create mode 100644 src/rp2_common/pico_audio_i2s/CMakeLists.txt create mode 100644 src/rp2_common/pico_audio_i2s/audio_i2s.c create mode 100644 src/rp2_common/pico_audio_i2s/audio_i2s.pio create mode 100644 src/rp2_common/pico_audio_i2s/include/pico/audio_i2s.h create mode 100644 src/rp2_common/pico_audio_pwm/CMakeLists.txt create mode 100644 src/rp2_common/pico_audio_pwm/audio_pwm.c create mode 100644 src/rp2_common/pico_audio_pwm/audio_pwm.pio create mode 100644 src/rp2_common/pico_audio_pwm/include/pico/audio_pwm.h create mode 100644 src/rp2_common/pico_audio_pwm/include/pico/audio_pwm/sample_encoding.h create mode 100644 src/rp2_common/pico_audio_pwm/sample_encoding.cpp create mode 100644 src/rp2_common/pico_audio_spdif/CMakeLists.txt create mode 100644 src/rp2_common/pico_audio_spdif/audio_spdif.c create mode 100644 src/rp2_common/pico_audio_spdif/audio_spdif.pio create mode 100644 src/rp2_common/pico_audio_spdif/include/pico/audio_spdif.h create mode 100644 src/rp2_common/pico_audio_spdif/include/pico/audio_spdif/sample_encoding.h create mode 100644 src/rp2_common/pico_audio_spdif/sample_encoding.cpp create mode 100644 src/rp2_common/pico_scanvideo_dbi/CMakeLists.txt create mode 100644 src/rp2_common/pico_scanvideo_dbi/README.md create mode 100644 src/rp2_common/pico_scanvideo_dbi/control.pio create mode 100644 src/rp2_common/pico_scanvideo_dbi/tft_driver.c create mode 100644 src/rp2_common/pico_scanvideo_dbi/tft_driver.h create mode 100644 src/rp2_common/pico_scanvideo_dbi/vga_modes.c create mode 100644 src/rp2_common/pico_scanvideo_dbi/video.h create mode 100644 src/rp2_common/pico_scanvideo_dbi/video_dbi.c create mode 100644 src/rp2_common/pico_scanvideo_dpi/CMakeLists.txt create mode 100644 src/rp2_common/pico_scanvideo_dpi/include/pico/scanvideo.h create mode 100644 src/rp2_common/pico_scanvideo_dpi/scanvideo.c create mode 100644 src/rp2_common/pico_scanvideo_dpi/timing.pio create mode 100644 src/rp2_common/pico_sd_card/CMakeLists.txt create mode 100644 src/rp2_common/pico_sd_card/README.md create mode 100644 src/rp2_common/pico_sd_card/crc-itu-t.h create mode 100644 src/rp2_common/pico_sd_card/crc7.h create mode 100644 src/rp2_common/pico_sd_card/sd_card.c create mode 100644 src/rp2_common/pico_sd_card/sd_card.pio create mode 100644 src/rp2_common/pico_sleep/CMakeLists.txt create mode 100644 src/rp2_common/pico_sleep/include/pico/sleep.h create mode 100644 src/rp2_common/pico_sleep/sleep.c create mode 100644 src/rp2_common/usb_common/CMakeLists.txt create mode 100644 src/rp2_common/usb_common/include/usb/usb_common.h create mode 100644 src/rp2_common/usb_device/CMakeLists.txt create mode 100644 src/rp2_common/usb_device/include/pico/usb_device.h create mode 100644 src/rp2_common/usb_device/include/pico/usb_device_private.h create mode 100644 src/rp2_common/usb_device/include/pico/usb_stream_helper.h create mode 100644 src/rp2_common/usb_device/usb_device.c create mode 100644 src/rp2_common/usb_device/usb_stream_helper.c create mode 100644 src/rp2_common/usb_device_msc/CMakeLists.txt create mode 100644 src/rp2_common/usb_device_msc/include/pico/scsi.h create mode 100644 src/rp2_common/usb_device_msc/include/pico/scsi_ir.h create mode 100644 src/rp2_common/usb_device_msc/include/pico/usb_device_msc.h create mode 100644 src/rp2_common/usb_device_msc/include/pico/virtual_disk.h create mode 100644 src/rp2_common/usb_device_msc/usb_device_msc.c create mode 100644 test/CMakeLists.txt create mode 100644 test/sample_conversion_test/CMakeLists.txt create mode 100644 test/sample_conversion_test/sample_conversion_test.cpp create mode 100644 test/sd_test/CMakeLists.txt create mode 100644 test/sd_test/sd_test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85697e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +.vscode +cmake-* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6b991bf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,5 @@ +[submodule "lwip"] + path = lib/lwip + url = https://git.savannah.nongnu.org/git/lwip.git +[submodule "lib/lwip"] + url = git://git.savannah.nongnu.org/lwip.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e60d7fe --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.12) + +# Pull in PICO SDK (must be before project) +include(pico_sdk_import.cmake) + +project(pico_extras C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# Initialize the SDK +pico_sdk_init() + +pico_is_top_level_project(PICO_EXTRAS_TOP_LEVEL_PROJECT) + +add_library(pico_extras_included INTERFACE) +target_compile_definitions(pico_extras_included INTERFACE + -DPICO_EXTRAS=1 +) + +pico_add_platform_library(pico_extras_included) + +if (NOT PICO_EXTRAS_PATH) + set(PICO_EXTRAS_PATH ${CMAKE_CURRENT_LIST_DIR}) +endif() +set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to Pico Extras") + +add_subdirectory(src) + +if (PICO_EXTRAS_TESTS_ENABLED OR PICO_EXTRAS_TOP_LEVEL_PROJECT) + add_subdirectory(test) +endif () \ No newline at end of file diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 0000000..e8a64f1 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,21 @@ +Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e8170c0 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +This repo has additional libraries that are not yet ready for inclusion the Pico SDK proper, +or are just useful but don't necessarily belong in the Pico SDK. + +Note that any API here is a work in progress and subject to change. + +See [pico-playground](https://github.com/raspberrypi/pico-playground) for buildable example code using these extra libraries. + + +Library|Description +---|--- +[hardware_rosc](src/rp2_common/hardware_rosc)| API for the ring oscillator +[lwip](src/rp2_common/lwip)| [LWIP Lightweight IP Library](https://savannah.nongnu.org/projects/lwip/) packed as an INTERFACE library for use with the Pico SDK +[pico_audio](src/common/pico_audio)|Audio output support; this is highly functional, but the API is subject to change +   [pico_audio_i2s](src/rp2_common/pico_audio_spdif)|Audio output via I2S on 3 GPIOs using PIO. Arbitrary frequency +   [pico_audio_pwm](src/rp2_common/pico_audio_spdif)|Audio output via (PIO) PWM. Currently a bit limited in frequency support (it was developed on FPGA to do 22050Hz at 48Mhz system clock). It does however support error diffusion dithering and noise shaping with 16x oversampling to give surprsingly good audio quality. This code will be split to provide both a fixed frequencie(s) version and a slightly slower but rather better arbitrary frequency version supporting ever higher carrier frequencies +   [pico_audio_spdif](src/rp2_common/pico_audio_spdif)|Audio output in S/PDIF on a GPIO using PIO. Supports up to 192khz stereo. Consumed OK in test, haven't tried it with real hardware +[pico_sd_card](src/rp2_common/pico_sd_card)|1 and 4 bit SDIO support using PIO. This is functional (currently writing is only 1 bit), but the the code is very much prototype and the API is just a placeholder - the command set needs to be separated from the SDIO and shared with SPI +[pico_sleep](src/rp2_common/pico_sleep)|Low power related APIs, WIP because they are not sufficiently generic and also only handle core 0 +[pico_scanvideo](src/common/pico_scanvideo)|Support for video output where every pixel is _scanned out_ everry frame. VGA/DPI support is highgly functional and stable, but the API is subject to change +   [pico_scanvideo_dbi](src/rp2_common/pico_scanvideo_dbi)| currently non-compiling... placeholder for adding scanvideo over MIPI DBI support. +   [pico_scanvideo_dpi](src/rp2_common/pico_scanvideo_dbi)| Highly functional and stable support for parallel RGB output and VSYNC/HSYNC/DEN/CLOCK for VGA/DPI. +[pico_util_buffer](src/common/pico_util_buffer)|Rather incomplete buffer abstraction, used by pico_audio and pico_scanvideo +[platypus](src/common/platypus)| Decoder for a custom image compression format suitable for dithered images (good for RGB555) and suitable for decoding on RP2040 at scanline speeds ... i.e you can easily decode a 320x240 image 60x per second to avoid storing the uncompressed image for scanout video. It gets about 50% compression (but is designed only for 4x4 fixed dithered RGB555 images, so is somewhat specific!). TODO add the encoder here :-) +[usb_device](src/rp2_common/usb_device), [usb_common](src/rp2_common/usb_common)| The custom and somewhat minimal USB device stack used in the bootrom. We now use TinyUSB in the Pico SDK but kep here for posterit +[usb_device_msc](src/rp2_common/usb_device_msc)| USB Mass Storage Class implementation using _usb_device_ + + +You can add Pico Extras to your project similarly to the SDK (copying [external/pico_extras_import.cmake](external/pico_extras_import.cmake) into your project) +having set the `PICO_EXTRAS_PATH` variable in your environment or via cmake variable. + +```cmake +cmake_minimum_required(VERSION 3.12) + +# Pull in PICO SDK (must be before project) +include(pico_sdk_import.cmake) + +# We also need PICO EXTRAS +include(pico_extras_import.cmake) + +project(pico_playground C CXX) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +``` + +Alternative you can inject it into an existing project without modifying it via `PICO_CMAKE_POST_LIST_DIRS` + by passing `-DPICO_SDK_POST_LIST_DIRS=/path/to/pico_extras` to cmake diff --git a/external/pico_extras_import.cmake b/external/pico_extras_import.cmake new file mode 100644 index 0000000..706add0 --- /dev/null +++ b/external/pico_extras_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of /external/pico_extras_import.cmake + +# This can be dropped into an external project to help locate pico-extras +# It should be include()ed prior to project() + +if (DEFINED ENV{PICO_EXTRAS_PATH} AND (NOT PICO_EXTRAS_PATH)) + set(PICO_EXTRAS_PATH $ENV{PICO_EXTRAS_PATH}) + message("Using PICO_EXTRAS_PATH from environment ('${PICO_EXTRAS_PATH}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT)) + set(PICO_EXTRAS_FETCH_FROM_GIT $ENV{PICO_EXTRAS_FETCH_FROM_GIT}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT from environment ('${PICO_EXTRAS_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH} AND (NOT PICO_EXTRAS_FETCH_FROM_GIT_PATH)) + set(PICO_EXTRAS_FETCH_FROM_GIT_PATH $ENV{PICO_EXTRAS_FETCH_FROM_GIT_PATH}) + message("Using PICO_EXTRAS_FETCH_FROM_GIT_PATH from environment ('${PICO_EXTRAS_FETCH_FROM_GIT_PATH}')") +endif () + +if (NOT PICO_EXTRAS_PATH) + if (PICO_EXTRAS_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_EXTRAS_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + PICO_EXTRAS + GIT_REPOSITORY https://github.com/raspberrypi/pico-extras + GIT_TAG master + ) + if (NOT PICO_EXTRAS) + message("Downloading PICO EXTRAS") + FetchContent_Populate(PICO_EXTRAS) + set(PICO_EXTRAS_PATH ${PICO_EXTRAS_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + if (PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pico-extras") + set(PICO_EXTRAS_PATH ${PICO_SDK_PATH}/../pico-extras) + message("Defaulting PICO_EXTRAS_PATH as sibling of PICO_SDK_PATH: ${PICO_EXTRAS_PATH}") + else() + message(FATAL_ERROR + "PICO EXTRAS location was not specified. Please set PICO_EXTRAS_PATH or set PICO_EXTRAS_FETCH_FROM_GIT to on to fetch from git." + ) + endif() + endif () +endif () + +set(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" CACHE PATH "Path to the PICO EXTRAS") +set(PICO_EXTRAS_FETCH_FROM_GIT "${PICO_EXTRAS_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO EXTRAS from git if not otherwise locatable") +set(PICO_EXTRAS_FETCH_FROM_GIT_PATH "${PICO_EXTRAS_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download EXTRAS") + +get_filename_component(PICO_EXTRAS_PATH "${PICO_EXTRAS_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_EXTRAS_PATH}) + message(FATAL_ERROR "Directory '${PICO_EXTRAS_PATH}' not found") +endif () + +set(PICO_EXTRAS_PATH ${PICO_EXTRAS_PATH} CACHE PATH "Path to the PICO EXTRAS" FORCE) + +add_subdirectory(${PICO_EXTRAS_PATH} pico_extras) \ No newline at end of file diff --git a/lib/lwip b/lib/lwip new file mode 160000 index 0000000..c385f31 --- /dev/null +++ b/lib/lwip @@ -0,0 +1 @@ +Subproject commit c385f31076b27efb8ee37f00cb5568783a58f299 diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..e02a33e --- /dev/null +++ b/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 PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO 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 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 + "PICO 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 PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..4bd6394 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(common) +if (PICO_ON_DEVICE) + add_subdirectory(rp2_common) +endif() diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..16b1e2a --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(pico_audio) +add_subdirectory(pico_scanvideo) +add_subdirectory(pico_sd_card) +add_subdirectory(pico_util_buffer) +add_subdirectory(platypus) diff --git a/src/common/pico_audio/CMakeLists.txt b/src/common/pico_audio/CMakeLists.txt new file mode 100644 index 0000000..914d56c --- /dev/null +++ b/src/common/pico_audio/CMakeLists.txt @@ -0,0 +1,16 @@ +if (NOT TARGET pico_audio_headers) + add_library(pico_audio_headers INTERFACE) + target_include_directories(pico_audio_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + target_link_libraries(pico_audio_headers INTERFACE pico_util_buffer) +endif() + +if (NOT TARGET pico_audio) + add_library(pico_audio INTERFACE) + + target_sources(pico_audio INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/audio.cpp + $<$>:${CMAKE_CURRENT_LIST_DIR}/audio_utils.S> + ) + + target_link_libraries(pico_audio INTERFACE pico_audio_headers pico_sync) +endif() diff --git a/src/common/pico_audio/audio.cpp b/src/common/pico_audio/audio.cpp new file mode 100644 index 0000000..f528be8 --- /dev/null +++ b/src/common/pico_audio/audio.cpp @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/audio.h" +#include "pico/sample_conversion.h" + +// ====================== +// == DEBUGGING ========= + +#define ENABLE_AUDIO_ASSERTIONS + +#ifdef ENABLE_AUDIO_ASSERTIONS +#define audio_assert(x) assert(x) +#else +#define audio_assert(x) (void)0 +#endif + +inline static audio_buffer_t *list_remove_head(audio_buffer_t **phead) { + audio_buffer_t *ab = *phead; + + if (ab) { + *phead = ab->next; + ab->next = NULL; + } + + return ab; +} + +inline static audio_buffer_t *list_remove_head_with_tail(audio_buffer_t **phead, + audio_buffer_t **ptail) { + audio_buffer_t *ab = *phead; + + if (ab) { + *phead = ab->next; + + if (!ab->next) { + audio_assert(*ptail == ab); + *ptail = NULL; + } else { + ab->next = NULL; + } + } + + return ab; +} + +inline static void list_prepend(audio_buffer_t **phead, audio_buffer_t *ab) { + audio_assert(ab->next == NULL); + audio_assert(ab != *phead); + ab->next = *phead; + *phead = ab; +} + +// todo add a tail for these already sorted lists as we generally insert on the end +inline static void list_append_with_tail(audio_buffer_t **phead, audio_buffer_t **ptail, + audio_buffer_t *ab) { + audio_assert(ab->next == NULL); + audio_assert(ab != *phead); + audio_assert(ab != *ptail); + + if (!*phead) { + audio_assert(!*ptail); + *ptail = ab; + // insert at the beginning + list_prepend(phead, ab); + } else { + // insert at end + (*ptail)->next = ab; + *ptail = ab; + } +} + +audio_buffer_t *get_free_audio_buffer(audio_buffer_pool_t *context, bool block) { + audio_buffer_t *ab; + + do { + uint32_t save = spin_lock_blocking(context->free_list_spin_lock); + ab = list_remove_head(&context->free_list); + spin_unlock(context->free_list_spin_lock, save); + if (ab || !block) break; + __wfe(); + } while (true); + return ab; +} + +void queue_free_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab) { + assert(!ab->next); + uint32_t save = spin_lock_blocking(context->free_list_spin_lock); + list_prepend(&context->free_list, ab); + spin_unlock(context->free_list_spin_lock, save); + __sev(); +} + +audio_buffer_t *get_full_audio_buffer(audio_buffer_pool_t *context, bool block) { + audio_buffer_t *ab; + + do { + uint32_t save = spin_lock_blocking(context->prepared_list_spin_lock); + ab = list_remove_head_with_tail(&context->prepared_list, &context->prepared_list_tail); + spin_unlock(context->prepared_list_spin_lock, save); + if (ab || !block) break; + __wfe(); + } while (true); + return ab; +} + +void queue_full_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab) { + assert(!ab->next); + uint32_t save = spin_lock_blocking(context->prepared_list_spin_lock); + list_append_with_tail(&context->prepared_list, &context->prepared_list_tail, ab); + spin_unlock(context->prepared_list_spin_lock, save); + __sev(); +} + +void producer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer) { + queue_full_audio_buffer(connection->producer_pool, buffer); +} + +audio_buffer_t *producer_pool_take_buffer_default(audio_connection_t *connection, bool block) { + return get_free_audio_buffer(connection->producer_pool, block); +} + +void consumer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer) { + queue_free_audio_buffer(connection->consumer_pool, buffer); +} + +audio_buffer_t *consumer_pool_take_buffer_default(audio_connection_t *connection, bool block) { + return get_full_audio_buffer(connection->consumer_pool, block); +} + +static audio_connection_t connection_default = { + .producer_pool_take = producer_pool_take_buffer_default, + .producer_pool_give = producer_pool_give_buffer_default, + .consumer_pool_take = consumer_pool_take_buffer_default, + .consumer_pool_give = consumer_pool_give_buffer_default, +}; + +audio_buffer_t *audio_new_buffer(audio_buffer_format_t *format, int buffer_sample_count) { + audio_buffer_t *buffer = (audio_buffer_t *) calloc(1, sizeof(audio_buffer_t)); + audio_init_buffer(buffer, format, buffer_sample_count); + return buffer; +} + +void audio_init_buffer(audio_buffer_t *audio_buffer, audio_buffer_format_t *format, int buffer_sample_count) { + audio_buffer->format = format; + audio_buffer->buffer = pico_buffer_alloc(buffer_sample_count * format->sample_stride); + audio_buffer->max_sample_count = buffer_sample_count; + audio_buffer->sample_count = 0; +} + +audio_buffer_pool_t * +audio_new_buffer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) { + audio_buffer_pool_t *ac = (audio_buffer_pool_t *) calloc(1, sizeof(audio_buffer_pool_t)); + audio_buffer_t *audio_buffers = buffer_count ? (audio_buffer_t *) calloc(buffer_count, + sizeof(audio_buffer_t)) : 0; + ac->format = format->format; + for (int i = 0; i < buffer_count; i++) { + audio_init_buffer(audio_buffers + i, format, buffer_sample_count); + audio_buffers[i].next = i != buffer_count - 1 ? &audio_buffers[i + 1] : NULL; + } + // todo one per channel? + ac->free_list_spin_lock = spin_lock_init(SPINLOCK_ID_AUDIO_FREE_LIST_LOCK); + ac->free_list = audio_buffers; + ac->prepared_list_spin_lock = spin_lock_init(SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK); + ac->prepared_list = NULL; + ac->prepared_list_tail = NULL; + ac->connection = &connection_default; + return ac; +} + +audio_buffer_t *audio_new_wrapping_buffer(audio_buffer_format_t *format, mem_buffer_t *buffer) { + audio_buffer_t *audio_buffer = (audio_buffer_t *) calloc(1, sizeof(audio_buffer_t)); + if (audio_buffer) { + audio_buffer->format = format; + audio_buffer->buffer = buffer; + audio_buffer->max_sample_count = buffer->size / format->sample_stride; + audio_buffer->sample_count = 0; + audio_buffer->next = 0; + } + return audio_buffer; + +} + +audio_buffer_pool_t * +audio_new_producer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) { + audio_buffer_pool_t *ac = audio_new_buffer_pool(format, buffer_count, buffer_sample_count); + ac->type = audio_buffer_pool::ac_producer; + return ac; +} + +audio_buffer_pool_t * +audio_new_consumer_pool(audio_buffer_format_t *format, int buffer_count, int buffer_sample_count) { + audio_buffer_pool_t *ac = audio_new_buffer_pool(format, buffer_count, buffer_sample_count); + ac->type = audio_buffer_pool::ac_consumer; + return ac; +} + +void audio_complete_connection(audio_connection_t *connection, audio_buffer_pool_t *producer_pool, + audio_buffer_pool_t *consumer_pool) { + assert(producer_pool->type == audio_buffer_pool::ac_producer); + assert(consumer_pool->type == audio_buffer_pool::ac_consumer); + producer_pool->connection = connection; + consumer_pool->connection = connection; + connection->producer_pool = producer_pool; + connection->consumer_pool = consumer_pool; +} + +void give_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer) { + buffer->user_data = 0; + assert(ac->connection); + if (ac->type == audio_buffer_pool::ac_producer) + ac->connection->producer_pool_give(ac->connection, buffer); + else + ac->connection->consumer_pool_give(ac->connection, buffer); +} + +audio_buffer_t *take_audio_buffer(audio_buffer_pool_t *ac, bool block) { + assert(ac->connection); + if (ac->type == audio_buffer_pool::ac_producer) + return ac->connection->producer_pool_take(ac->connection, block); + else + return ac->connection->consumer_pool_take(ac->connection, block); +} + +// todo rename this - this is s16 to s16 +audio_buffer_t *mono_to_mono_consumer_take(audio_connection_t *connection, bool block) { + return consumer_pool_take, Mono>(connection, block); +} + +// todo rename this - this is s16 to s16 +audio_buffer_t *stereo_to_stereo_consumer_take(audio_connection_t *connection, bool block) { + return consumer_pool_take, Stereo>(connection, block); +} + +// todo rename this - this is s16 to s16 +audio_buffer_t *mono_to_stereo_consumer_take(audio_connection_t *connection, bool block) { + return consumer_pool_take, Mono>(connection, block); +} + +audio_buffer_t *mono_s8_to_mono_consumer_take(audio_connection_t *connection, bool block) { + return consumer_pool_take, Mono>(connection, block); +} + +audio_buffer_t *mono_s8_to_stereo_consumer_take(audio_connection_t *connection, bool block) { + return consumer_pool_take, Mono>(connection, block); +} + +void stereo_to_stereo_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) { + return producer_pool_blocking_give, Stereo>(connection, buffer); +} diff --git a/src/common/pico_audio/audio_utils.S b/src/common/pico_audio/audio_utils.S new file mode 100644 index 0000000..e46ec5e --- /dev/null +++ b/src/common/pico_audio/audio_utils.S @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "hardware/regs/addressmap.h" +#include "hardware/regs/sio.h" + +.syntax unified +.cpu cortex-m0plus +.thumb + +#define AUDIO_UPSAMPLE_SCALE_BITS 12 +.align 2 +.section .time_critical.audio_upsample +.global audio_upsample +.type audio_upsample,%function +// step is fraction of an input sample per output sample * (1 << AUDIO_UPSAMPLE_SCALE_BITS) and should be < (1 << AUDIO_UPSAMPLE_SCALE_BITS) ... i.e. we we are upsampling (otherwise results are undefined) +// void audio_upsample(int16_t *input, int16_t *output, int count, uint32_t step) +.thumb_func +audio_upsample: + push {r4, r5, r6, r7, lr} + lsls r2, #1 + mov ip, r1 + add ip, r2 + ldr r6, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET + // interp_configure_with_signed_and_blend + ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 1) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | ((24 - AUDIO_UPSAMPLE_SCALE_BITS) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE0_BLEND_BITS + str r4, [r6, #SIO_INTERP0_CTRL_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + // interp_configure_with_signed_and_cross_input + ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 8) << SIO_INTERP0_CTRL_LANE1_SHIFT_LSB) | (0 << SIO_INTERP0_CTRL_LANE1_MASK_LSB_LSB) | (7 << SIO_INTERP0_CTRL_LANE1_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE1_SIGNED_BITS | SIO_INTERP0_CTRL_LANE1_CROSS_INPUT_BITS + str r4, [r6, #SIO_INTERP0_CTRL_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + str r0, [r6, #SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + movs r0, #0 + str r0, [r6, #SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + mov r7, r0 // last_offset = 0 (invalid) + movs r2, #2 + + // r0 0 + // r1 output + // r2 2 + // r3 step + // r4 temp + // r5 temp + // r6 interp_hw + // r7 last_offset + // ip end + b 4f + +1: // aligned + ldr r5, [r4] + str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] +2: // unchanged sample ptr + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + add r1, r2 + cmp r1, ip + beq 5f +3: // next sample + ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + cmp r4, r7 + beq 2b + mov r7, r4 + tst r4, r2 + beq 1b + ldrsh r5, [r4, r0] + str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldrsh r4, [r4, r2] + str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + add r1, r2 +4: + cmp r1, ip + bne 3b +5: + pop {r4, r5, r6, r7, pc} + +.align 2 +.section .time_critical.audio_upsample_words +.global audio_upsample_words +.type audio_upsample_words,%function +// step is fraction of an input sample per output sample * (1 << AUDIO_UPSAMPLE_SCALE_BITS) and should be < (1 << AUDIO_UPSAMPLE_SCALE_BITS) ... i.e. we we are upsampling (otherwise results are undefined) +// void audio_upsample(int16_t *input, int16_t *output_aligned, int output_word_count, uint32_t step) +.thumb_func +audio_upsample_words: + push {r4, r5, r6, r7, lr} + lsls r2, #2 + mov ip, r1 + add ip, r2 + ldr r6, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET + // interp_configure_with_blend + ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 1) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | ((24 -AUDIO_UPSAMPLE_SCALE_BITS) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE0_BLEND_BITS + str r4, [r6, #SIO_INTERP0_CTRL_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + // interp_configure_with_signed_and_cross_input + ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 8) << SIO_INTERP0_CTRL_LANE1_SHIFT_LSB) | (0 << SIO_INTERP0_CTRL_LANE1_MASK_LSB_LSB) | (7 << SIO_INTERP0_CTRL_LANE1_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE1_SIGNED_BITS | SIO_INTERP0_CTRL_LANE1_CROSS_INPUT_BITS + str r4, [r6, #SIO_INTERP0_CTRL_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + str r0, [r6, #SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + movs r0, #0 + str r0, [r6, #SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + mov r7, r0 // last_offset = 0 (invalid) + movs r2, #2 + + // r0 0 + // r1 output + // r2 2 + // r3 step + // r4 temp + // r5 temp + // r6 interp_hw + // r7 last_offset + // ip end + b 4f + +1: // aligned A + ldr r5, [r4] + str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] +2: // unchanged sample ptr A + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + // output A + strh r4, [r1] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + + // next sample B + ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + cmp r4, r7 + beq 6f + + mov r7, r4 + tst r4, r2 + bne 7f + +8: + // aligned B + ldr r5, [r4] + str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + +6: // unchanged sample ptr B + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1, r2] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + adds r1, #4 + cmp r1, ip + beq 5f + +3: // next sample A + ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + cmp r4, r7 + beq 2b + mov r7, r4 + tst r4, r2 + beq 1b + ldrsh r5, [r4, r0] + str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldrsh r4, [r4, r2] + str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1] + + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + + // next sample B + ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + cmp r4, r7 + beq 6b + mov r7, r4 + tst r4, r2 + beq 8b +7: // unalignedb + ldrsh r5, [r4, r0] + str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldrsh r4, [r4, r2] + str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1, r2] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + adds r1, #4 + +4: + cmp r1, ip + bne 3b + +5: + pop {r4, r5, r6, r7, pc} + +.global audio_upsample_double +.type audio_upsample_double,%function +// step is fraction of an input sample per output sample * (1 << AUDIO_UPSAMPLE_SCALE_BITS) and should be < (1 << AUDIO_UPSAMPLE_SCALE_BITS) ... i.e. we we are upsampling (otherwise results are undefined) +// void audio_upsample(int16_t *input, int16_t *output, int count, uint32_t step) +.thumb_func +audio_upsample_double: + push {r4, r5, r6, r7, lr} + lsls r2, #2 + mov ip, r1 + add ip, r2 + ldr r6, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET + // interp_configure_with_signed_and_blend + ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 1) << SIO_INTERP0_CTRL_LANE0_SHIFT_LSB) | (1 << SIO_INTERP0_CTRL_LANE0_MASK_LSB_LSB) | ((24 - AUDIO_UPSAMPLE_SCALE_BITS) << SIO_INTERP0_CTRL_LANE0_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE0_BLEND_BITS + str r4, [r6, #SIO_INTERP0_CTRL_LANE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + // interp_configure_with_signed_and_cross_input + ldr r4, =# ((AUDIO_UPSAMPLE_SCALE_BITS - 8) << SIO_INTERP0_CTRL_LANE1_SHIFT_LSB) | (0 << SIO_INTERP0_CTRL_LANE1_MASK_LSB_LSB) | (7 << SIO_INTERP0_CTRL_LANE1_MASK_MSB_LSB) | SIO_INTERP0_CTRL_LANE1_SIGNED_BITS | SIO_INTERP0_CTRL_LANE1_CROSS_INPUT_BITS + str r4, [r6, #SIO_INTERP0_CTRL_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + str r0, [r6, #SIO_INTERP0_BASE2_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + movs r0, #0 + str r0, [r6, #SIO_INTERP0_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + mov r7, r0 // last_offset = 0 (invalid) + movs r2, #2 + + // r0 0 + // r1 output + // r2 2 + // r3 step + // r4 temp + // r5 temp + // r6 interp_hw + // r7 last_offset + // ip end + b 4f + +1: // aligned + ldr r5, [r4] + str r5, [r6, #SIO_INTERP0_BASE_1AND0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] +2: // unchanged sample ptr + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1, #2] + add r1, r2 + add r1, r2 + cmp r1, ip + beq 5f +3: // next sample + ldr r4, [r6, #SIO_INTERP0_PEEK_FULL_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + cmp r4, r7 + beq 2b + mov r7, r4 + tst r4, r2 + beq 1b + ldrsh r5, [r4, r0] + str r5, [r6, #SIO_INTERP0_BASE0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldrsh r4, [r4, r2] + str r4, [r6, #SIO_INTERP0_BASE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + ldr r4, [r6, #SIO_INTERP0_PEEK_LANE1_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1] + str r3, [r6, #SIO_INTERP0_ACCUM0_ADD_OFFSET - SIO_INTERP0_ACCUM0_OFFSET] + strh r4, [r1, #2] + add r1, r2 + add r1, r2 +4: + cmp r1, ip + bne 3b +5: + pop {r4, r5, r6, r7, pc} diff --git a/src/common/pico_audio/include/pico/audio.h b/src/common/pico_audio/include/pico/audio.h new file mode 100644 index 0000000..e92ed16 --- /dev/null +++ b/src/common/pico_audio/include/pico/audio.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_AUDIO_H +#define _PICO_AUDIO_H + +#include "pico.h" +#include "pico/util/buffer.h" +#include "hardware/sync.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file audio.h +* \defgroup pico_audio pico_audio + * + * Common API for audio output + * + */ + +// PICO_CONFIG: SPINLOCK_ID_AUDIO_FREE_LIST_LOCK, Spinlock number for the audio free list, min=0, max=31, default=6, group=audio +#ifndef SPINLOCK_ID_AUDIO_FREE_LIST_LOCK +#define SPINLOCK_ID_AUDIO_FREE_LIST_LOCK 6 +#endif + +// PICO_CONFIG: SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK, Spinlock number for the audio prepared list, min=0, max=31, default=7, group=audio +#ifndef SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK +#define SPINLOCK_ID_AUDIO_PREPARED_LISTS_LOCK 7 +#endif + +// PICO_CONFIG: PICO_AUDIO_NOOP, Enable/disable audio by forcing NOOPS, type=bool, default=0, group=audio +#ifndef PICO_AUDIO_NOOP +#define PICO_AUDIO_NOOP 0 +#endif + + +#define AUDIO_BUFFER_FORMAT_PCM_S16 1 ///< signed 16bit PCM +#define AUDIO_BUFFER_FORMAT_PCM_S8 2 ///< signed 8bit PCM +#define AUDIO_BUFFER_FORMAT_PCM_U16 3 ///< unsigned 16bit PCM +#define AUDIO_BUFFER_FORMAT_PCM_U8 4 ///< unsigned 16bit PCM + +/** \brief Audio format definition + */ +typedef struct audio_format { + uint32_t sample_freq; ///< Sample frequency in Hz + uint16_t format; ///< Audio format \ref audio_formats + uint16_t channel_count; ///< Number of channels +} audio_format_t; + +/** \brief Audio buffer format definition + */ +typedef struct audio_buffer_format { + const audio_format_t *format; ///< Audio format + uint16_t sample_stride; ///< Sample stride +} audio_buffer_format_t; + +/** \brief Audio buffer definition + */ +typedef struct audio_buffer { + mem_buffer_t *buffer; + const audio_buffer_format_t *format; + uint32_t sample_count; + uint32_t max_sample_count; + uint32_t user_data; // only valid while the user has the buffer + // private - todo make an internal version + struct audio_buffer *next; +} audio_buffer_t; + +typedef struct audio_connection audio_connection_t; + +typedef struct audio_buffer_pool { + enum { + ac_producer, ac_consumer + } type; + const audio_format_t *format; + // private + audio_connection_t *connection; + spin_lock_t *free_list_spin_lock; + // ----- begin protected by free_list_spin_lock ----- + audio_buffer_t *free_list; + spin_lock_t *prepared_list_spin_lock; + audio_buffer_t *prepared_list; + audio_buffer_t *prepared_list_tail; +} audio_buffer_pool_t; + +typedef struct audio_connection audio_connection_t; + +struct audio_connection { + audio_buffer_t *(*producer_pool_take)(audio_connection_t *connection, bool block); + + void (*producer_pool_give)(audio_connection_t *connection, audio_buffer_t *buffer); + + audio_buffer_t *(*consumer_pool_take)(audio_connection_t *connection, bool block); + + void (*consumer_pool_give)(audio_connection_t *connection, audio_buffer_t *buffer); + + audio_buffer_pool_t *producer_pool; + audio_buffer_pool_t *consumer_pool; +}; + +/*! \brief Allocate and initialise an audio producer pool + * \ingroup pico_audio + * + * \param format Format of the audio buffer + * \param buffer_count \todo + * \param buffer_sample_count \todo + * \return Pointer to an audio_buffer_pool + */ +audio_buffer_pool_t *audio_new_producer_pool(audio_buffer_format_t *format, int buffer_count, + int buffer_sample_count); + +/*! \brief Allocate and initialise an audio consumer pool + * \ingroup pico_audio + * + * \param format Format of the audio buffer + * \param buffer_count + * \param buffer_sample_count + * \return Pointer to an audio_buffer_pool + */ +audio_buffer_pool_t *audio_new_consumer_pool(audio_buffer_format_t *format, int buffer_count, + int buffer_sample_count); + +/*! \brief Allocate and initialise an audio wrapping buffer + * \ingroup pico_audio + * + * \param format Format of the audio buffer + * \param buffer \todo + * \return Pointer to an audio_buffer + */ +audio_buffer_t *audio_new_wrapping_buffer(audio_buffer_format_t *format, mem_buffer_t *buffer); + +/*! \brief Allocate and initialise an new audio buffer + * \ingroup pico_audio + * + * \param format Format of the audio buffer + * \param buffer_sample_count \todo + * \return Pointer to an audio_buffer + */ +audio_buffer_t *audio_new_buffer(audio_buffer_format_t *format, int buffer_sample_count); + +/*! \brief Initialise an audio buffer + * \ingroup pico_audio + * + * \param audio_buffer Pointer to an audio_buffer + * \param format Format of the audio buffer + * \param buffer_sample_count \todo + */ +void audio_init_buffer(audio_buffer_t *audio_buffer, audio_buffer_format_t *format, int buffer_sample_count); + +/*! \brief \todo + * \ingroup pico_audio + * + * \param ac \todo + * \param buffer \todo + * \return Pointer to an audio_buffer + */ +void give_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer); + +/*! \brief \todo + * \ingroup pico_audio + * + * \return Pointer to an audio_buffer + */ +audio_buffer_t *take_audio_buffer(audio_buffer_pool_t *ac, bool block); + +/*! \brief \todo + * \ingroup pico_audio + * + */ +static inline void release_audio_buffer(audio_buffer_pool_t *ac, audio_buffer_t *buffer) { + buffer->sample_count = 0; + give_audio_buffer(ac, buffer); +} + +/*! \brief \todo + * \ingroup pico_audio + * + * todo we are currently limited to 4095+1 input samples + * step is fraction of an input sample per output sample * 0x1000 and should be < 0x1000 i.e. we we are up-sampling (otherwise results are undefined) + */ +void audio_upsample(int16_t *input, int16_t *output, uint output_count, uint32_t step); + +/*! \brief \todo + * \ingroup pico_audio + * similar but the output buffer is word aligned, and we output an even number of samples.. this is slightly faster than the above + * todo we are currently limited to 4095+1 input samples + * step is fraction of an input sample per output sample * 0x1000 and should be < 0x1000 i.e. we we are up-sampling (otherwise results are undefined) + */ +void audio_upsample_words(int16_t *input, int16_t *output_aligned, uint output_word_count, uint32_t step); + +/*! \brief \todo + * \ingroup pico_audio + */ +void audio_upsample_double(int16_t *input, int16_t *output, uint output_count, uint32_t step); + +/*! \brief \todo + * \ingroup pico_audio + */ +void audio_complete_connection(audio_connection_t *connection, audio_buffer_pool_t *producer, + audio_buffer_pool_t *consumer); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *get_free_audio_buffer(audio_buffer_pool_t *context, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +void queue_free_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *get_full_audio_buffer(audio_buffer_pool_t *context, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +void queue_full_audio_buffer(audio_buffer_pool_t *context, audio_buffer_t *ab); + +/*! \brief \todo + * \ingroup pico_audio + * + * generally an pico_audio connection uses 3 of the defaults and does the hard work in one of them + */ +void consumer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *consumer_pool_take_buffer_default(audio_connection_t *connection, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +void producer_pool_give_buffer_default(audio_connection_t *connection, audio_buffer_t *buffer); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *producer_pool_take_buffer_default(audio_connection_t *connection, bool block); + +enum audio_correction_mode { + none, + fixed_dither, + dither, + noise_shaped_dither, +}; + +struct buffer_copying_on_consumer_take_connection { + struct audio_connection core; + audio_buffer_t *current_producer_buffer; + uint32_t current_producer_buffer_pos; +}; + +struct producer_pool_blocking_give_connection { + audio_connection_t core; + audio_buffer_t *current_consumer_buffer; + uint32_t current_consumer_buffer_pos; +}; + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *mono_to_mono_consumer_take(audio_connection_t *connection, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *mono_s8_to_mono_consumer_take(audio_connection_t *connection, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *stereo_to_stereo_consumer_take(audio_connection_t *connection, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *mono_to_stereo_consumer_take(audio_connection_t *connection, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +audio_buffer_t *mono_s8_to_stereo_consumer_take(audio_connection_t *connection, bool block); + +/*! \brief \todo + * \ingroup pico_audio + */ +void stereo_to_stereo_producer_give(audio_connection_t *connection, audio_buffer_t *buffer); + +// not worth a separate header for now +typedef struct __packed pio_audio_channel_config { + uint8_t base_pin; + uint8_t dma_channel; + uint8_t pio_sm; +} pio_audio_channel_config_t; + +#ifdef __cplusplus +} +#endif + +#endif //_AUDIO_H diff --git a/src/common/pico_audio/include/pico/sample_conversion.h b/src/common/pico_audio/include/pico/sample_conversion.h new file mode 100644 index 0000000..223fdd9 --- /dev/null +++ b/src/common/pico_audio/include/pico/sample_conversion.h @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SOFTWARE_SAMPLE_CONVERSION_H +#define SOFTWARE_SAMPLE_CONVERSION_H + +#include +#include +#include "pico/audio.h" +#include "pico/util/buffer.h" + +template +struct FmtDetails { +public: + static const uint channel_count = 1; + static const uint frame_stride = channel_count * sizeof(_sample_t); + typedef _sample_t sample_t; +}; + +typedef struct : public FmtDetails { +} FmtU8; + +typedef struct : public FmtDetails { +} FmtS8; + +typedef struct : public FmtDetails { +} FmtU16; + +typedef struct : public FmtDetails { +} FmtS16; + +// Multi channel is just N samples back to back +template +struct MultiChannelFmt { + static const uint channel_count = ChannelCount; + static const uint frame_stride = ChannelCount * Fmt::frame_stride; + typedef typename Fmt::sample_t sample_t; +}; + +// define Mono details as one channel +template using Mono = MultiChannelFmt; + +// define Stereo details as two channels +template using Stereo = MultiChannelFmt; + +template +struct sample_converter { + static typename ToFmt::sample_t convert_sample(const typename FromFmt::sample_t &sample); +}; + +// noop conversion + +template +struct sample_converter { + static typename Fmt::sample_t convert_sample(const typename Fmt::sample_t &sample) { + return sample; + } +}; + +// converters to S16 +template<> +struct sample_converter { + static int16_t convert_sample(const uint16_t &sample) { + return sample ^ 0x8000u; + } +}; + +template<> +struct sample_converter { + static int16_t convert_sample(const int8_t &sample) { + return sample << 8u; + } +}; + +template<> +struct sample_converter { + static int16_t convert_sample(const uint8_t &sample) { + return (sample << 8u) ^ 0x8000u; + } +}; + +// converters to U16 + +template<> +struct sample_converter { + static uint16_t convert_sample(const int8_t &sample) { + return (sample << 8u) ^ 0x8000u; + } +}; + +template<> +struct sample_converter { + static uint16_t convert_sample(const uint8_t &sample) { + return sample << 8u; + } +}; + +template<> +struct sample_converter { + static uint16_t convert_sample(const int16_t &sample) { + return sample ^ 0x8000u; + } +}; + +// converters to S8 + +template<> +struct sample_converter { + static int8_t convert_sample(const uint16_t &sample) { + return (sample ^ 0x8000u) >> 8u; + } +}; + +template<> +struct sample_converter { + static int8_t convert_sample(const uint8_t &sample) { + return sample ^ 0x80; + } +}; + +template<> +struct sample_converter { + static int8_t convert_sample(const int16_t &sample) { + return sample >> 8u; + } +}; + +// converters to U8 + +template<> +struct sample_converter { + static uint8_t convert_sample(const uint16_t &sample) { + return sample >> 8u; + } +}; + +template<> +struct sample_converter { + static uint8_t convert_sample(const int8_t &sample) { + return sample ^ 0x80; + } +}; + +template<> +struct sample_converter { + static uint8_t convert_sample(const int16_t &sample) { + return (sample ^ 0x8000u) >> 8u; + } +}; + +// template type for doing sample conversion +template +struct converting_copy { + static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count); +}; + +// Efficient copies of same sample type + +template +struct converting_copy, MultiChannelFmt> { + static void copy(typename MultiChannelFmt::sample_t *dest, + const typename MultiChannelFmt::sample_t *src, + uint sample_count) { + memcpy((void *) dest, (const void *) src, sample_count * MultiChannelFmt::frame_stride); + } +}; + +// N channel to N channel +template +struct converting_copy, MultiChannelFmt> { + static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) { + for (uint i = 0; i < sample_count * NumChannels; i++) { + *dest++ = sample_converter::convert_sample(*src++); + } + } +}; + + +// mono->stereo conversion +template +struct converting_copy, Mono> { + static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) { + for (; sample_count; sample_count--) { + typename ToFmt::sample_t mono_sample = sample_converter::convert_sample(*src++); + *dest++ = mono_sample; + *dest++ = mono_sample; + } + } +}; + +// stereo->mono conversion +template +struct converting_copy, Stereo> { + static void copy(typename ToFmt::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) { + for (; sample_count; sample_count--) { + // average first in case precision is better in source + typename FromFmt::sample_t averaged_sample = (src[0] + src[1]) / 2; + src += 2; + *dest++ = sample_converter::convert_sample(averaged_sample); + } + } +}; + +template +audio_buffer_t *consumer_pool_take(audio_connection_t *connection, bool block) { + struct buffer_copying_on_consumer_take_connection *cc = (struct buffer_copying_on_consumer_take_connection *) connection; + // for now we block until we have all the data in consumer buffers + audio_buffer_t *buffer = get_free_audio_buffer(cc->core.consumer_pool, block); + if (!buffer) return NULL; + assert(buffer->format->sample_stride == ToFmt::frame_stride); + + uint32_t pos = 0; + while (pos < buffer->max_sample_count) { + if (!cc->current_producer_buffer) { + cc->current_producer_buffer = get_full_audio_buffer(cc->core.producer_pool, block); + if (!cc->current_producer_buffer) { + assert(!block); + if (!pos) { + queue_free_audio_buffer(cc->core.consumer_pool, buffer); + return NULL; + } + break; + } + assert(cc->current_producer_buffer->format->format->channel_count == FromFmt::channel_count); + assert(cc->current_producer_buffer->format->sample_stride == FromFmt::frame_stride); + cc->current_producer_buffer_pos = 0; + } + uint sample_count = std::min(buffer->max_sample_count - pos, + cc->current_producer_buffer->sample_count - cc->current_producer_buffer_pos); + converting_copy::copy( + ((typename ToFmt::sample_t *) buffer->buffer->bytes) + pos * ToFmt::channel_count, + ((typename FromFmt::sample_t *) cc->current_producer_buffer->buffer->bytes) + + cc->current_producer_buffer_pos * FromFmt::channel_count, + sample_count); + pos += sample_count; + cc->current_producer_buffer_pos += sample_count; + if (cc->current_producer_buffer_pos == cc->current_producer_buffer->sample_count) { + queue_free_audio_buffer(cc->core.producer_pool, cc->current_producer_buffer); + cc->current_producer_buffer = NULL; + } + } + buffer->sample_count = pos; + return buffer; +} + +template +void producer_pool_blocking_give(audio_connection_t *connection, audio_buffer_t *buffer) { + struct producer_pool_blocking_give_connection *pbc = (struct producer_pool_blocking_give_connection *) connection; + // for now we block until we have all the data in consumer buffers + uint32_t pos = 0; + while (pos < buffer->sample_count) { + if (!pbc->current_consumer_buffer) { + pbc->current_consumer_buffer = get_free_audio_buffer(pbc->core.consumer_pool, true); + pbc->current_consumer_buffer_pos = 0; + } + uint sample_count = std::min(buffer->sample_count - pos, + pbc->current_consumer_buffer->max_sample_count - pbc->current_consumer_buffer_pos); + assert(buffer->format->sample_stride == FromFmt::frame_stride); + assert(buffer->format->format->channel_count == FromFmt::channel_count); + converting_copy::copy( + ((typename ToFmt::sample_t *) pbc->current_consumer_buffer->buffer->bytes) + + pbc->current_consumer_buffer_pos * ToFmt::channel_count, + ((typename FromFmt::sample_t *) buffer->buffer->bytes) + pos * FromFmt::channel_count, sample_count); + pos += sample_count; + pbc->current_consumer_buffer_pos += sample_count; + if (pbc->current_consumer_buffer_pos == pbc->current_consumer_buffer->max_sample_count) { + pbc->current_consumer_buffer->sample_count = pbc->current_consumer_buffer->max_sample_count; + queue_full_audio_buffer(pbc->core.consumer_pool, pbc->current_consumer_buffer); + pbc->current_consumer_buffer = NULL; + } + } + // todo this should be a connection configuration (or a seaparate connection type) +#ifdef BLOCKING_GIVE_SYNCHRONIZE_BUFFERS + if (pbc->current_consumer_buffer) { + pbc->current_consumer_buffer->sample_count = pbc->current_consumer_buffer_pos; + queue_full_audio_buffer(pbc->core.consumer_pool, pbc->current_consumer_buffer); + pbc->current_consumer_buffer = NULL; + } +#endif + assert(pos == buffer->sample_count); + queue_free_audio_buffer(pbc->core.producer_pool, buffer); +} + +#endif //SOFTWARE_SAMPLE_CONVERSION_H diff --git a/src/common/pico_scanvideo/CMakeLists.txt b/src/common/pico_scanvideo/CMakeLists.txt new file mode 100644 index 0000000..387eb7a --- /dev/null +++ b/src/common/pico_scanvideo/CMakeLists.txt @@ -0,0 +1,14 @@ +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND PICO_DEOPTIMIZED_DEBUG) + message("scanvideo is disabled for 'Debug' builds when PICO_DEOPTIMIZED_DEBUG=1") +else() + add_library(pico_scanvideo INTERFACE) + + pico_generate_pio_header(pico_scanvideo ${CMAKE_CURRENT_LIST_DIR}/scanvideo.pio PATH include/pico/scanvideo) + + target_sources(pico_scanvideo INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/vga_modes.c + ) + + target_include_directories(pico_scanvideo INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + target_link_libraries(pico_scanvideo INTERFACE pico_base_headers pico_util_buffer) +endif() diff --git a/src/common/pico_scanvideo/include/pico/scanvideo/composable_scanline.h b/src/common/pico_scanvideo/include/pico/scanvideo/composable_scanline.h new file mode 100644 index 0000000..33f9414 --- /dev/null +++ b/src/common/pico_scanvideo/include/pico/scanvideo/composable_scanline.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SCANVIDEO_COMPOSABLE_SCANLINE_H_ +#define SCANVIDEO_COMPOSABLE_SCANLINE_H_ + +#include "pico/types.h" +#include "scanvideo.pio.h" + +// PICO_CONFIG: PICO_SCANVIDEO_USE_RAW1P_2CYCLE, Enable/disable SVideo use raw 1P 2 cycle, type=bool, default=0, group=video+- +#ifndef PICO_SCANVIDEO_USE_RAW1P_2CYCLE +#define PICO_SCANVIDEO_USE_RAW1P_2CYCLE 0 +#endif + +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE +#define video_24mhz_composable_prefix video_24mhz_composable_default +#else +#define video_24mhz_composable_prefix video_24mhz_composable_raw1p_2cycle +#endif + +// seems needed on some platforms +#define __EXTRA_CONCAT(x, y) __CONCAT(x,y) +#define video_24mhz_composable_program_extern(x) __EXTRA_CONCAT( __EXTRA_CONCAT(video_24mhz_composable_prefix, _offset_), x) +#define __DVP_JMP(x) ((unsigned)video_24mhz_composable_program_extern(x)) +#define COMPOSABLE_COLOR_RUN __DVP_JMP(color_run) +#define COMPOSABLE_EOL_ALIGN __DVP_JMP(end_of_scanline_ALIGN) +#define COMPOSABLE_EOL_SKIP_ALIGN __DVP_JMP(end_of_scanline_skip_word_ALIGN) +#define COMPOSABLE_RAW_RUN __DVP_JMP(raw_run) +#define COMPOSABLE_RAW_1P __DVP_JMP(raw_1p) +#define COMPOSABLE_RAW_2P __DVP_JMP(raw_2p) +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE +#define COMPOSABLE_RAW_1P_SKIP_ALIGN __DVP_JMP(raw_1p_skip_word_ALIGN) +#else +#define COMPOSABLE_RAW_1P_2CYCLE __DVP_JMP(raw_1p_2cycle) +#endif + +#endif diff --git a/src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h b/src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h new file mode 100644 index 0000000..4b811f7 --- /dev/null +++ b/src/common/pico_scanvideo/include/pico/scanvideo/scanvideo_base.h @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SCANVIDEO_scanvideo_H_ +#define SCANVIDEO_scanvideo_H_ + +#include "pico/types.h" + +#if !PICO_NO_HARDWARE + +#include "hardware/pio.h" + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file scanvideo_base.h + * \defgroup pico_scanvideo pico_scanvideo + * + * Common Scan-out Video API + */ +// == CONFIG ============ +#ifndef PICO_SCANVIDEO_PLANE_COUNT +#define PICO_SCANVIDEO_PLANE_COUNT 1 +#endif + +#ifndef PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT +#define PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT 8 +#endif + +#ifndef PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS +#define PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS 0 +#endif + +#ifndef PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA 0 +#endif + +#ifndef PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA 0 +#endif + +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA 1 +#endif + +#ifndef PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA 0 +#endif + +#ifndef PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA 0 +#endif + +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA 1 +#endif + +#ifndef PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA 0 +#endif + +#ifndef PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA 0 +#endif + +#if PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE3_FRAGMENT_DMA 1 +#endif + +// todo these may be DPI only +#ifndef PICO_SCANVIDEO_ENABLE_CLOCK_PIN +#define PICO_SCANVIDEO_ENABLE_CLOCK_PIN 1 +#endif + +#ifndef PICO_SCANVIDEO_ENABLE_DEN_PIN +#define PICO_SCANVIDEO_ENABLE_DEN_PIN 1 +#endif + +#ifndef PICO_SCANVIDEO_COLOR_PIN_BASE +#define PICO_SCANVIDEO_COLOR_PIN_BASE 0 +#endif + +#ifndef PICO_SCANVIDEO_COLOR_PIN_COUNT +#define PICO_SCANVIDEO_COLOR_PIN_COUNT 16 +#endif + +#ifndef PICO_SCANVIDEO_SYNC_PIN_BASE +#define PICO_SCANVIDEO_SYNC_PIN_BASE (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT) +#endif + +#ifndef PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN +#define PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN 0 +#endif + +// todo make multi plane play nicely with mode swapping; +// today we have hard coded blank/empty lines + +//#define PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA 1 +//#define PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA 1 + +#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS +#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS 180 +#endif +#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS +#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS +#endif +#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS +#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS +#endif + +//extern struct semaphore vmode_updated; + + +// ====================== + +#define BPP 16 + +// most likely 24000000 +extern const uint32_t video_clock_freq; + +// todo pragma pack? +typedef struct scanvideo_timing { + uint32_t clock_freq; + + uint16_t h_active; + uint16_t v_active; + + uint16_t h_front_porch; + uint16_t h_pulse; + uint16_t h_total; + uint8_t h_sync_polarity; + + uint16_t v_front_porch; + uint16_t v_pulse; + uint16_t v_total; + uint8_t v_sync_polarity; + + uint8_t enable_clock; + uint8_t clock_polarity; + + uint8_t enable_den; +} scanvideo_timing_t; + +typedef struct scanvideo_pio_program scanvideo_pio_program_t; + +// todo we need to handle blank data correctly (perhaps DMA should just not start for that scanline, +// though obviously this is slightly more complicated with multiple playfields, or perhaps worse with +// just one +typedef struct scanvideo_mode { + const scanvideo_timing_t *default_timing; + const scanvideo_pio_program_t *pio_program; + + uint16_t width; + uint16_t height; + uint8_t xscale; // 1 == normal, 2 == double wide etc. up to what pio timing allows (not sure I have an assert based on delays) + uint16_t yscale; // same for y scale (except any yscale is possible) + // if > 1 then yscale is divided by this to provide the effective yscale; + // note that yscale must be > yscale_denominator; i.e. only stretching is supported + uint16_t yscale_denominator; +} scanvideo_mode_t; + +extern bool scanvideo_setup(const scanvideo_mode_t *mode); +extern bool scanvideo_setup_with_timing(const scanvideo_mode_t *mode, const scanvideo_timing_t *timing); +extern void scanvideo_timing_enable(bool enable); +// these take effect after the next vsync +extern void scanvideo_display_enable(bool enable); +// doesn't exist yet! +// extern void video_set_display_mode(const struct scanvideo_mode *mode); + +// --- scanline management --- + +typedef struct scanvideo_scanline_buffer { + uint32_t scanline_id; + uint32_t *data; + uint16_t data_used; + uint16_t data_max; +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA + uint16_t fragment_words; +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + uint32_t *data2; + uint16_t data2_used; + uint16_t data2_max; +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + uint32_t *data3; + uint16_t data3_used; + uint16_t data3_max; +#endif +#endif + void *user_data; +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS + struct scanvideo_scanline_buffer *link; + uint8_t link_after; +#endif + uint8_t status; +} scanvideo_scanline_buffer_t; + +enum { + SCANLINE_OK = 1, + SCANLINE_ERROR, + SCANLINE_SKIPPED +}; + +// note frame numbers wrap +static inline uint16_t scanvideo_frame_number(uint32_t scanline_id) { + return (uint16_t) (scanline_id >> 16u); +} + +static inline uint16_t scanvideo_scanline_number(uint32_t scanline_id) { + return (uint16_t) scanline_id; +} + +/** + * @return the current vga mode (if there is one) + */ +extern scanvideo_mode_t scanvideo_get_mode(); + +/** + * @return the next scanline_id to be displayed (may be from the next frame) + */ +extern uint32_t scanvideo_get_next_scanline_id(); + +/** + * @return true if in the vblank interval + */ +extern bool scanvideo_in_vblank(); +/** + * @return true if in the hblank interval (or more accurately scanline data is not currently being sent to the PIO, which roughly corresponds, but is not exact). Note also that in + * yscale-d modes, there are height * yscale hblank intervals per frame. + */ +extern bool scanvideo_in_hblank(); + +extern void scanvideo_wait_for_vblank(); + +extern uint32_t scanvideo_wait_for_scanline_complete(uint32_t scanline_id); +/** + * Acquire a scanline that needs generating. The scanline_id field indicates which scanline is required. + * + * This method may be called concurrently + * + * @param block true to block if the vga system is not ready to generate a new scanline + * @return the scanline_buffer or NULL if block is false, and the vga system is not ready + */ +scanvideo_scanline_buffer_t *scanvideo_begin_scanline_generation(bool block); +scanvideo_scanline_buffer_t *scanvideo_begin_scanline_generation2(scanvideo_scanline_buffer_t **second, bool block); +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS +scanvideo_scanline_buffer_t *scanvideo_begin_scanline_generation_linked(uint n, bool block); +#endif + +/** + * Return a scanline that has been generated / or at least the client is done with. + * + * The status field indicates whether the scanline was actually generated OK + * + * This method may be called concurrently (for different buffers) + * + * @param scanline_buffer \todo + */ +void scanvideo_end_scanline_generation(scanvideo_scanline_buffer_t *scanline_buffer); + +typedef uint (*scanvideo_scanline_repeat_count_fn)(uint32_t scanline_id); +void scanvideo_set_scanline_repeat_fn(scanvideo_scanline_repeat_count_fn fn); + +extern const scanvideo_timing_t vga_timing_640x480_60_default; +extern const scanvideo_timing_t vga_timing_wide_480_50; +extern const scanvideo_timing_t vga_timing_648x480_60_alt1; + +extern const scanvideo_mode_t vga_mode_160x120_60; // 3d monster maze anyone :-) +extern const scanvideo_mode_t vga_mode_213x160_60; +extern const scanvideo_mode_t vga_mode_320x240_60; +extern const scanvideo_mode_t vga_mode_640x480_60; +extern const scanvideo_mode_t vga_mode_800x600_54; +extern const scanvideo_mode_t vga_mode_800x600_60; +extern const scanvideo_mode_t vga_mode_1024x768_63; +extern const scanvideo_mode_t vga_mode_1280x1024_40; +extern const scanvideo_mode_t vga_mode_720p_60; +extern const scanvideo_mode_t vga_mode_1080p_60; + +extern const scanvideo_mode_t vga_mode_tft_800x480_50; +extern const scanvideo_mode_t vga_mode_tft_400x240_50; + +#ifndef NDEBUG +// todo this is only for vga composable 24... should exist behind mode impl +extern void validate_scanline(const uint32_t *dma_data, uint dma_data_size, uint max_pixels, uint expected_width); +#endif + +// mode implementation + +struct scanvideo_pio_program { +#if !PICO_NO_HARDWARE + const pio_program_t *program; + const uint8_t entry_point; + // modifiable_instructions is of size program->length + bool (*adapt_for_mode)(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode, + scanvideo_scanline_buffer_t *missing_scanline_buffer, uint16_t *modifiable_instructions); + pio_sm_config (*configure_pio)(pio_hw_t *pio, uint sm, uint offset); +#else + const char *id; +#endif +}; + +extern const scanvideo_pio_program_t video_24mhz_composable; + +#if !PICO_NO_HARDWARE +extern void scanvideo_default_configure_pio(pio_hw_t *pio, uint sm, uint offset, pio_sm_config *config, bool overlay); +#endif + +#ifndef PICO_SPINLOCK_ID_VIDEO_SCANLINE_LOCK +#define PICO_SPINLOCK_ID_VIDEO_SCANLINE_LOCK 2 +#endif + +#ifndef PICO_SPINLOCK_ID_VIDEO_FREE_LIST_LOCK +#define PICO_SPINLOCK_ID_VIDEO_FREE_LIST_LOCK 3 +#endif + +#ifndef PICO_SPINLOCK_ID_VIDEO_DMA_LOCK +#define PICO_SPINLOCK_ID_VIDEO_DMA_LOCK 4 +#endif + +#ifndef PICO_SPINLOCK_ID_VIDEO_IN_USE_LOCK +#define PICO_SPINLOCK_ID_VIDEO_IN_USE_LOCK 5 +#endif + +#define PICO_SCANVIDEO_ALPHA_MASK (1u << PICO_SCANVIDEO_ALPHA_PIN) +#define PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b) ((((b)>>3u)<>3u)<>3u)<>PICO_SCANVIDEO_PIXEL_RSHIFT)&0x1f) +#define PICO_SCANVIDEO_G5_FROM_PIXEL(p) (((p)>>PICO_SCANVIDEO_PIXEL_GSHIFT)&0x1f) +#define PICO_SCANVIDEO_B5_FROM_PIXEL(p) (((p)>>PICO_SCANVIDEO_PIXEL_BSHIFT)&0x1f) + +#ifdef __cplusplus +} +#endif + +#endif //_VIDEO_H diff --git a/src/common/pico_scanvideo/scanvideo.pio b/src/common/pico_scanvideo/scanvideo.pio new file mode 100644 index 0000000..f88f1cf --- /dev/null +++ b/src/common/pico_scanvideo/scanvideo.pio @@ -0,0 +1,119 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; Default scanline program (|| means aligned word boundary, | means hword boundary) +.program video_24mhz_composable_default +.origin 0 ; must load at zero (offsets are hardcoded in instruction stream) +.define extra0 0 ; set later by code based on xscale +.define extra1 0 ; set later by code (1 more than extra0) + +; note bpp must be a factor of 32 +.define bpp 16 +;.define bpp 8 + +public end_of_scanline_skip_word_ALIGN: ; || jmp end_of_scanline_skip_word_ALIGN | ignored || + ; was 16 but we just discard the reset of the OSR + ; so as to also support 8 bit grayscale + out null, 32; + +public end_of_scanline_ALIGN: ; | jmp end_of_scanline_ALIGN || + +public entry_point: + wait irq, 4 ; todo perhaps change this to out exec, 16... so that we can do multiple things (including setting black pixel) +public nop_raw: + out pc, bpp + +public delay_h_0: +public color_run: ; | jmp color_run | color | count-3 | + out pins, bpp + out x, bpp +color_loop: +public delay_a_1: + jmp x-- color_loop [extra1] +public nop_extra1: +public delay_b_1: + out pc, bpp [extra1] + +public raw_run: ; | jmp raw_run | color | n | | +public delay_c_0: + out pins, bpp [extra0] + out x, bpp +pixel_loop: +public delay_d_0: + out pins, bpp [extra0] + jmp x-- pixel_loop +.wrap_target +public raw_1p: ; | jmp raw_1p | color | +public delay_e_0: + out pins, bpp [extra0] + out pc, bpp + +public raw_2p: ; | jmp raw_2p | color | color | +public delay_f_1: + out pins, bpp [extra1] +.wrap + +public raw_1p_skip_word_ALIGN: ; || jmp raw_1p_skip_word_ALIGN | color | ignored || + out pins, 32 ; requires correct out mask +public nop_extra0: +public delay_g_0: + out pc, bpp [extra0] ; note moved extra0 from above line, so we can use this instruction for + +; Variant that replaces raw_1p_skip_work_ALIGN with raw1p_2cycle +.program video_24mhz_composable_raw1p_2cycle +.origin 0 ; must load at zero (offsets are hardcoded in instruction stream) +.define extra0 0 ; set later by code based on xscale +.define extra1 0 ; set later by code (1 more than extra0) + +; note bpp must be a factor of 32 +.define bpp 16 +;.define bpp 8 + +public end_of_scanline_skip_word_ALIGN: ; || jmp end_of_scanline_skip_word_ALIGN | ignored || + ; was 16 but we just discard the reset of the OSR + ; so as to also support 8 bit grayscale + out null, 32; + +public end_of_scanline_ALIGN: ; | jmp end_of_scanline_ALIGN || + +public entry_point: + wait irq, 4 ; todo perhaps change this to out exec, 16... so that we can do multiple things (including setting black pixel) +public nop_raw: + out pc, bpp + +public color_run: ; | jmp color_run | color | count-3 | +public delay_h_0: + out pins, bpp + out x, bpp +color_loop: +public delay_a_1: + jmp x-- color_loop [extra1] +public nop_extra1: +public delay_b_1: + out pc, bpp [extra1] + +public raw_run: ; | jmp raw_run | color | n | | +public delay_c_0: + out pins, bpp [extra0] + out x, bpp +pixel_loop: +public delay_d_0: + out pins, bpp [extra0] + jmp x-- pixel_loop +.wrap_target +public raw_1p: ; | jmp raw_1p | color | +public delay_e_0: + out pins, bpp [extra0] + out pc, bpp + +public raw_2p: ; | jmp raw_2p | color | color | +public delay_f_1: + out pins, bpp [extra1] +.wrap +public raw_1p_2cycle: +public delay_g_0: + out pins, bpp + out pc, bpp diff --git a/src/common/pico_scanvideo/vga_modes.c b/src/common/pico_scanvideo/vga_modes.c new file mode 100644 index 0000000..4c7d568 --- /dev/null +++ b/src/common/pico_scanvideo/vga_modes.c @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/scanvideo.h" + +// todo support for inverted-y (probably belongs in the scanline generators, as would inverted x) + +extern const scanvideo_pio_program_t video_24mhz_composable; +const scanvideo_timing_t vga_timing_640x480_60_default = + { + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 16, + .h_pulse = 64, + .h_total = 800, + .h_sync_polarity = 1, + + .v_front_porch = 1, + .v_pulse = 2, + .v_total = 500, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_800x600_54_default = + { + .clock_freq = 24000000, // wrong, but ok for now + + .h_active = 800, + .v_active = 600, + + .h_front_porch = 3 * 8, + .h_pulse = 10 * 8, + .h_total = 126 * 8, + .h_sync_polarity = 0, + + .v_front_porch = 1, + .v_pulse = 3, + .v_total = 619, + .v_sync_polarity = 0, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_800x600_60_default = + { + .clock_freq = 38400000, + + .h_active = 800, + .v_active = 600, + + .h_front_porch = 4 * 8, + .h_pulse = 10 * 8, + .h_total = 128 * 8, + .h_sync_polarity = 0, + + .v_front_porch = 1, + .v_pulse = 3, + .v_total = 625, + .v_sync_polarity = 0, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_1024x768_63_default = + { + .clock_freq = 24000000, // wrong, but ok for now + + .h_active = 1024, + .v_active = 768, + + .h_front_porch = 7 * 8, + .h_pulse = 13 * 8, + .h_total = 168 * 8, + .h_sync_polarity = 0, + + .v_front_porch = 1, + .v_pulse = 3, + .v_total = 797, + .v_sync_polarity = 0, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_1280x1024_40_default = + { + .clock_freq = 24000000, // wrong, but ok for now + + .h_active = 1280, + .v_active = 1024, + + .h_front_porch = 7 * 8, + .h_pulse = 16 * 8, + .h_total = 206 * 8, + .h_sync_polarity = 0, + + .v_front_porch = 1, + .v_pulse = 3, + .v_total = 1048, + .v_sync_polarity = 0, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + + +const scanvideo_timing_t vga_timing_648x480_60_alt1 = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 16, + .h_pulse = 48, + .h_total = 768, + .h_sync_polarity = 1, + + .v_front_porch = 10, + .v_pulse = 2, + .v_total = 523, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_648x480_50ish = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 56, + .h_pulse = 72, + .h_total = 896, + .h_sync_polarity = 1, + + .v_front_porch = 30, + .v_pulse = 2, + .v_total = 536, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_648x480_50ish2 = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 32, + .h_pulse = 64, + .h_total = 832, + .h_sync_polarity = 1, + + .v_front_porch = 27, + .v_pulse = 2, + .v_total = 577, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_648x480_50ish3 = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 72, + .h_pulse = 96, + .h_total = 928, + .h_sync_polarity = 1, + + .v_front_porch = 8, + .v_pulse = 2, + .v_total = 518, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +#define actual_vga_timing_50 vga_timing_648x480_50ish3 + +const scanvideo_mode_t vga_mode_160x120_60 = + { + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 160, + .height = 120, + .xscale = 4, + .yscale = 4, + }; + +const scanvideo_mode_t vga_mode_213x160_60 = + { + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 213, + .height = 160, + .xscale = 3, + .yscale = 3, + }; + +const scanvideo_mode_t vga_mode_320x240_60 = + { + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 320, + .height = 240, + .xscale = 2, + .yscale = 2, + }; + +const scanvideo_mode_t vga_mode_640x480_60 = + { + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 640, + .height = 480, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_mode_t vga_mode_640x480_50 = + { + .default_timing = &actual_vga_timing_50, + .pio_program = &video_24mhz_composable, + .width = 640, + .height = 480, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_mode_t vga_mode_320x240_50 = + { + .default_timing = &actual_vga_timing_50, + .pio_program = &video_24mhz_composable, + .width = 320, + .height = 240, + .xscale = 2, + .yscale = 2, + }; + +/* this is 50 hz */ +const scanvideo_timing_t vga_timing_wide_480_50 = + { + .clock_freq = 24000000, + + .h_active = 800, + .v_active = 480, + + .h_front_porch = 32, + .h_pulse = 48, + .h_total = 960, + .h_sync_polarity = 0, + + .v_front_porch = 1, + .v_pulse = 2, + .v_total = 500, + .v_sync_polarity = 0, + + .enable_clock = 1, + .clock_polarity = 0, + + .enable_den = 1 + }; + +const scanvideo_mode_t vga_mode_tft_800x480_50 = + { + .default_timing = &vga_timing_wide_480_50, + .pio_program = &video_24mhz_composable, + .width = 800, + .height = 480, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_mode_t vga_mode_tft_400x240_50 = + { + .default_timing = &vga_timing_wide_480_50, + .pio_program = &video_24mhz_composable, + .width = 400, + .height = 240, + .xscale = 2, + .yscale = 2, + }; + +const scanvideo_timing_t vga_timing_512x576_50_attempt1 = + { + .clock_freq = 24000000, + + .h_active = 512, + .v_active = 576, + + .h_front_porch = 64, + .h_pulse = 64, + .h_total = 768, + .h_sync_polarity = 1, + + .v_front_porch = 30, + .v_pulse = 2, + .v_total = 612, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_timing_t vga_timing_512x576_60_attempt1 = + { + .clock_freq = 24000000, + + .h_active = 512, + .v_active = 576, + + .h_front_porch = 64, + .h_pulse = 64, + .h_total = 768, + .h_sync_polarity = 1, + + .v_front_porch = 30, + .v_pulse = 2, + .v_total = 612, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_mode_t vga_mode_256x192_50 = + { + .default_timing = &vga_timing_512x576_50_attempt1, + .pio_program = &video_24mhz_composable, + .width = 256, + .height = 192, + .xscale = 2, + .yscale = 3, + }; + +const scanvideo_timing_t vga_timing_800x600_38 = + { + .clock_freq = 24000000, + + .h_active = 800, + .v_active = 600, + + .h_front_porch = 24, + .h_pulse = 80, + .h_total = 1008, + .h_sync_polarity = 1, + + .v_front_porch = 3, + .v_pulse = 4, + .v_total = 621, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const scanvideo_mode_t vga_mode_800x600_38 = + { + .default_timing = &vga_timing_800x600_38, + .pio_program = &video_24mhz_composable, + .width = 800, + .height = 600, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_mode_t vga_mode_800x600_54 = + { + .default_timing = &vga_timing_800x600_54_default, + .pio_program = &video_24mhz_composable, + .width = 800, + .height = 600, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_mode_t vga_mode_800x600_60 = + { + .default_timing = &vga_timing_800x600_60_default, + .pio_program = &video_24mhz_composable, + .width = 800, + .height = 600, + .xscale = 1, + .yscale = 1, + }; + + +const scanvideo_mode_t vga_mode_1024x768_63 = + { + .default_timing = &vga_timing_1024x768_63_default, + .pio_program = &video_24mhz_composable, + .width = 1024, + .height = 768, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_mode_t vga_mode_1280x1024_40 = + { + .default_timing = &vga_timing_1280x1024_40_default, + .pio_program = &video_24mhz_composable, + .width = 1280, + .height = 1024, + .xscale = 1, + .yscale = 1, + }; + + +const scanvideo_timing_t vga_timing_1280x720_60_default = + { + .clock_freq = 74250000, + + .h_active = 1280, + .v_active = 720, + + .h_front_porch = 110, + .h_pulse = 40, + .h_total = 1650, + .h_sync_polarity = 1, + + .v_front_porch = 5, + .v_pulse = 5, + .v_total = 750, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + + +const scanvideo_mode_t vga_mode_720p_60 = + { + .default_timing = &vga_timing_1280x720_60_default, + .pio_program = &video_24mhz_composable, + .width = 1280, + .height = 720, + .xscale = 1, + .yscale = 1, + }; + +const scanvideo_timing_t vga_timing_1920x1080_60_default = + { + .clock_freq = 148500000, + + .h_active = 1920, + .v_active = 1080, + + .h_front_porch = 88, + .h_pulse = 44, + .h_total = 2200, + .h_sync_polarity = 1, + + .v_front_porch = 4, + .v_pulse = 5, + .v_total = 1125, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + + +const scanvideo_mode_t vga_mode_1080p_60 = + { + .default_timing = &vga_timing_1920x1080_60_default, + .pio_program = &video_24mhz_composable, + .width = 1920, + .height = 1080, + .xscale = 1, + .yscale = 1, + }; + diff --git a/src/common/pico_sd_card/CMakeLists.txt b/src/common/pico_sd_card/CMakeLists.txt new file mode 100644 index 0000000..65ca539 --- /dev/null +++ b/src/common/pico_sd_card/CMakeLists.txt @@ -0,0 +1,4 @@ +if (NOT TARGET pico_sd_card_headers) + add_library(pico_sd_card_headers INTERFACE) + target_include_directories(pico_sd_card_headers INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +endif() \ No newline at end of file diff --git a/src/common/pico_sd_card/include/pico/sd_card.h b/src/common/pico_sd_card/include/pico/sd_card.h new file mode 100644 index 0000000..ce6de99 --- /dev/null +++ b/src/common/pico_sd_card/include/pico/sd_card.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_SD_CARD_H +#define _PICO_SD_CARD_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pico.h" + +#define SD_OK (0) +#define SD_ERR_STUCK (-1) +#define SD_ERR_BAD_RESPONSE (-2) +#define SD_ERR_CRC (-3) +#define SD_ERR_BAD_PARAM (-4) + +#ifndef PICO_SD_CLK_PIN +#define PICO_SD_CLK_PIN 23 +#endif + +#ifndef PICO_SD_CMD_PIN +#define PICO_SD_CMD_PIN 24 +#endif + +#ifndef PICO_SD_DAT0_PIN +#define PICO_SD_DAT0_PIN 19 +#endif + +// todo for now +#define PICO_SD_MAX_BLOCK_COUNT 32 +// todo buffer pool +int sd_init_4pins(); +int sd_init_1pin(); +#define SD_SECTOR_SIZE 512 +int sd_readblocks_sync(uint32_t *buf, uint32_t block, uint block_count); +int sd_readblocks_async(uint32_t *buf, uint32_t block, uint block_count); +int sd_readblocks_scatter_async(uint32_t *control_words, uint32_t block, uint block_count); +bool sd_scatter_read_complete(int *status); +int sd_writeblocks_async(const uint32_t *data, uint32_t sector_num, uint sector_count); +bool sd_write_complete(int *status); +int sd_read_sectors_1bit_crc_async(uint32_t *sector_buf, uint32_t sector, uint sector_count); +int sd_set_wide_bus(bool wide); +int sd_set_clock_divider(uint div); + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/src/common/pico_util_buffer/CMakeLists.txt b/src/common/pico_util_buffer/CMakeLists.txt new file mode 100644 index 0000000..b938199 --- /dev/null +++ b/src/common/pico_util_buffer/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(pico_util_buffer INTERFACE) + +target_include_directories(pico_util_buffer INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + +target_sources(pico_util_buffer INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/buffer.c + ) \ No newline at end of file diff --git a/src/common/pico_util_buffer/buffer.c b/src/common/pico_util_buffer/buffer.c new file mode 100644 index 0000000..d95a5d3 --- /dev/null +++ b/src/common/pico_util_buffer/buffer.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/util/buffer.h" + +#ifdef PICO_BUFFER_USB_ALLOC_HACK +#include + +uint8_t *usb_ram_alloc_ptr = (uint8_t *)(USBCTRL_DPRAM_BASE + USB_DPRAM_MAX); + +static void __attribute__((constructor)) _clear_usb_ram() { + memset(usb_ram_alloc_ptr, 0, USB_DPRAM_SIZE - USB_DPRAM_MAX); +} +#endif diff --git a/src/common/pico_util_buffer/include/pico/util/buffer.h b/src/common/pico_util_buffer/include/pico/util/buffer.h new file mode 100644 index 0000000..4bcd14a --- /dev/null +++ b/src/common/pico_util_buffer/include/pico/util/buffer.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_UTIL_BUFFER_H +#define _PICO_UTIL_BUFFER_H + +#include "pico/types.h" + +/** \file buffer.h + * \defgroup util_buffer buffer + * \brief Buffer management + * \ingroup pico_util + */ + +#ifdef PICO_BUFFER_USB_ALLOC_HACK +#include "hardware/address_mapped.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DEBUG_MALLOC +#include +#endif + +#include + +/** \struct mem_buffer + * \ingroup util_buffer + * \brief Wrapper structure around a memory buffer + * + * Wrapper could be around static or allocated memory + * + * \todo This module needs to be checked - think there are issues with the free function + */ +typedef struct mem_buffer { + size_t size; + uint8_t *bytes; + uint8_t flags; +} mem_buffer_t; + +#ifdef PICO_BUFFER_USB_ALLOC_HACK +#if !defined(USB_DPRAM_MAX) || (USB_DPRAM_MAX > 0) +#include "hardware/structs/usb.h" +#else +#define USB_DPRAM_SIZE 4096 +#endif +#endif + +inline static bool pico_buffer_alloc_in_place(mem_buffer_t *buffer, size_t size) { +#ifdef PICO_BUFFER_USB_ALLOC_HACK + extern uint8_t *usb_ram_alloc_ptr; + if ((usb_ram_alloc_ptr + size) <= (uint8_t *)USBCTRL_DPRAM_BASE + USB_DPRAM_SIZE) { + buffer->bytes = usb_ram_alloc_ptr; + buffer->size = size; +#ifdef DEBUG_MALLOC + printf("balloc %d %p->%p\n", size, buffer->bytes, ((uint8_t *)buffer->bytes) + size); +#endif + usb_ram_alloc_ptr += size; + return true; + } +#endif // inline for now + buffer->bytes = (uint8_t *) calloc(1, size); + if (buffer->bytes) { + buffer->size = size; + return true; + } + buffer->size = 0; + return false; +} + +inline static mem_buffer_t *pico_buffer_wrap(uint8_t *bytes, size_t size) { + mem_buffer_t *buffer = (mem_buffer_t *) malloc(sizeof(mem_buffer_t)); + if (buffer) { + buffer->bytes = bytes; + buffer->size = size; + } + return buffer; +} + +inline static mem_buffer_t *pico_buffer_alloc(size_t size) { + mem_buffer_t *b = (mem_buffer_t *) malloc(sizeof(mem_buffer_t)); + if (b) { + if (!pico_buffer_alloc_in_place(b, size)) { + free(b); + b = NULL; + } + } + return b; +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/common/platypus/CMakeLists.txt b/src/common/platypus/CMakeLists.txt new file mode 100644 index 0000000..2a04d94 --- /dev/null +++ b/src/common/platypus/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(platypus INTERFACE) + +target_sources(platypus INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/platypus.c + $<$:${CMAKE_CURRENT_LIST_DIR}/decompress_row.S> + ) + +target_include_directories(platypus INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +if (PICO_ON_DEVICE) + target_link_libraries(platypus INTERFACE hardware_interp) +endif() \ No newline at end of file diff --git a/src/common/platypus/decompress_row.S b/src/common/platypus/decompress_row.S new file mode 100644 index 0000000..efae1f1 --- /dev/null +++ b/src/common/platypus/decompress_row.S @@ -0,0 +1,377 @@ +#include "hardware/regs/addressmap.h" +#include "hardware/regs/sio.h" +.syntax unified +.cpu cortex-m0plus +.thumb + +#ifndef VIDEO_DBI +#define FIRST_TEST_SHIFT 16 +#define SECOND_TEST_SHIFT 8 +#else +#define FIRST_TEST_SHIFT 6 +#define SECOND_TEST_SHIFT 11 +#endif + +#define r_output r0 +#define r_interps r1 +#define r_input r2 +#define r_top r3 +#define r_bottom r4 +#define r_tmp3 r5 +#define r_tmp2 r6 +#define r_tmp1 r7 +#define r_output_end r8 +#define r_save r12 +#define r_row_delta r14 + +#define INTERP_OFFSET0(x) (x - SIO_INTERP0_ACCUM0_OFFSET) +#define INTERP_OFFSET1(x) (INTERP_OFFSET0(x) + SIO_INTERP1_ACCUM0_OFFSET - SIO_INTERP0_ACCUM0_OFFSET) + +// input r_top -- -- AB CD +// +// output r_top AB CD AB CD +// r_bottom AB CD AB CD +.macro duplicate_4_pixels tmp_a + lsls \tmp_a, r_top, #16 + uxth r_top, r_top + orrs r_top, \tmp_a + mov r_bottom, r_top +.endm + +.macro do_222 +#ifndef VIDEO_DBI + lsls r_tmp1, #3 +#else + // done in interp + // lsrs r_tmp1, #9 +#endif + str r_tmp1, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_ACCUM1_OFFSET)] + ldr r_tmp3, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE1_OFFSET)] + ldmia r_tmp3!, {r_tmp2, r_tmp1} + add r_top, r_tmp2 + add r_bottom, r_tmp1 +.endm + +.macro do_555 +#ifndef VIDEO_DBI + rev16 r_tmp1, r_tmp1 + lsls r_tmp1, #3 +#else + // done in interp + // lsrs r_tmp1, #8 +#endif + str r_tmp1, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_ACCUM0_OFFSET)] + str r_tmp1, [r_interps, #INTERP_OFFSET0(SIO_INTERP0_ACCUM0_OFFSET)] + ldr r_tmp3, [r_interps, #INTERP_OFFSET1(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldmia r_tmp3!, {r_tmp2, r_tmp1} + add r_top, r_tmp2 + add r_bottom, r_tmp1 + ldr r_tmp3, [r_interps, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE0_OFFSET)] + ldmia r_tmp3!, {r_tmp2, r_tmp1} +#ifndef VIDEO_DBI + lsls r_tmp2, #5 + lsls r_tmp1, #5 +#else + lsls r_tmp2, #6 + lsls r_tmp1, #6 +#endif + add r_top, r_tmp2 + add r_bottom, r_tmp1 + ldr r_tmp3, [r_interps, #INTERP_OFFSET0(SIO_INTERP0_PEEK_LANE1_OFFSET)] + ldmia r_tmp3!, {r_tmp2, r_tmp1} +#ifndef VIDEO_DBI + lsls r_tmp2, #10 + lsls r_tmp1, #10 +#else + lsls r_tmp2, #11 + lsls r_tmp1, #11 +#endif + add r_top, r_tmp2 + add r_bottom, r_tmp1 +.endm + +.macro shuffle_7_bytes_to_8 tmp_a tmp_b +#ifndef VIDEO_DBI + ldr \tmp_b, =#0xff018401 + lsls \tmp_a, \tmp_b, #16 + ands \tmp_b, r_bottom + ands \tmp_a, r_top + eors r_bottom, \tmp_b + eors r_top, \tmp_a + + lsrs \tmp_a, \tmp_a, #15 + orrs \tmp_b, \tmp_a + lsrs \tmp_a, \tmp_b, #10 + lsls \tmp_b, #27 + lsls \tmp_a, #24 + orrs \tmp_b, \tmp_a + orrs r_bottom, \tmp_b +#else + ldr \tmp_b, =#0xff210821 + lsls \tmp_a, \tmp_b, #16 + ands \tmp_b, r_bottom + ands \tmp_a, r_top + eors r_bottom, \tmp_b + eors r_top, \tmp_a + + // todo can we shave 1 more cycles to make cycles in the DBI code? a challenge to anyone who reads this! + lsrs \tmp_a, #13 + orrs \tmp_b, \tmp_a + lsrs \tmp_a, \tmp_b, #10 + eors \tmp_b, \tmp_a + lsrs \tmp_a, \tmp_b, #12 + adcs \tmp_b, \tmp_b + lsls \tmp_b, #24 + orrs r_bottom, \tmp_b +#endif +.endm + +.macro write_output tmp_a + mov \tmp_a, r_row_delta + str r_bottom, [r_output, \tmp_a] + stmia r_output!, {r_top} +.endm + +.macro decompressor name data_section_prefix code_section_prefix + +.section \data_section_prefix\().\name\().data + +.global \name\()_5_table +\name\()_5_table: +.word 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001 +.word 0x00010001, 0x00000001, 0x00010000, 0x00000000, 0x00000000, 0x00010001, 0x00010000, 0x00010002 +.word 0x00000000, 0x00010002, 0x00020000, 0x00010001, 0x00010000, 0x00020002, 0x00010000, 0x00010000 +.word 0x00000001, 0x00000001, 0x00010001, 0x00000002, 0x00010001, 0x00000000, 0x00020001, 0x00000001 +.word 0x00020000, 0x00020001, 0x00000001, 0x00000002, 0x00020002, 0x00000001, 0x00020001, 0x00000000 +.word 0x00000000, 0x00000002, 0x00000001, 0x00010002, 0x00010000, 0x00020001, 0x00020000, 0x00010000 +.word 0x00020000, 0x00020002, 0x00000000, 0x00020002, 0x00010002, 0x00000002, 0x00010000, 0x00020003 +.word 0x00000000, 0x00020003, 0x00010000, 0x00030003, 0x00010000, 0x00000002, 0x00000001, 0x00010001 + +.global \name\()_222_table +\name\()_222_table: +#ifndef VIDEO_DBI +.word 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001 +.word 0x00000000, 0x00000020, 0x00000000, 0x00000021, 0x00010000, 0x00010021, 0x00010000, 0x00000021 +.word 0x00200000, 0x00200020, 0x00200000, 0x00200021, 0x00210000, 0x00210021, 0x00210000, 0x00200021 +.word 0x00200000, 0x00000020, 0x00200000, 0x00000021, 0x00210000, 0x00010021, 0x00210000, 0x00000021 +.word 0x00000000, 0x00000400, 0x00000000, 0x00000401, 0x00010000, 0x00010401, 0x00010000, 0x00000401 +.word 0x00000000, 0x00000420, 0x00000000, 0x00000421, 0x00010000, 0x00010421, 0x00010000, 0x00000421 +.word 0x00200000, 0x00200420, 0x00200000, 0x00200421, 0x00210000, 0x00210421, 0x00210000, 0x00200421 +.word 0x00200000, 0x00000420, 0x00200000, 0x00000421, 0x00210000, 0x00010421, 0x00210000, 0x00000421 +.word 0x04000000, 0x04000400, 0x04000000, 0x04000401, 0x04010000, 0x04010401, 0x04010000, 0x04000401 +.word 0x04000000, 0x04000420, 0x04000000, 0x04000421, 0x04010000, 0x04010421, 0x04010000, 0x04000421 +.word 0x04200000, 0x04200420, 0x04200000, 0x04200421, 0x04210000, 0x04210421, 0x04210000, 0x04200421 +.word 0x04200000, 0x04000420, 0x04200000, 0x04000421, 0x04210000, 0x04010421, 0x04210000, 0x04000421 +.word 0x04000000, 0x00000400, 0x04000000, 0x00000401, 0x04010000, 0x00010401, 0x04010000, 0x00000401 +.word 0x04000000, 0x00000420, 0x04000000, 0x00000421, 0x04010000, 0x00010421, 0x04010000, 0x00000421 +.word 0x04200000, 0x00200420, 0x04200000, 0x00200421, 0x04210000, 0x00210421, 0x04210000, 0x00200421 +.word 0x04200000, 0x00000420, 0x04200000, 0x00000421, 0x04210000, 0x00010421, 0x04210000, 0x00000421 +#else +.word 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001 +.word 0x00000000, 0x00000040, 0x00000000, 0x00000041, 0x00010000, 0x00010041, 0x00010000, 0x00000041 +.word 0x00400000, 0x00400040, 0x00400000, 0x00400041, 0x00410000, 0x00410041, 0x00410000, 0x00400041 +.word 0x00400000, 0x00000040, 0x00400000, 0x00000041, 0x00410000, 0x00010041, 0x00410000, 0x00000041 +.word 0x00000000, 0x00000800, 0x00000000, 0x00000801, 0x00010000, 0x00010801, 0x00010000, 0x00000801 +.word 0x00000000, 0x00000840, 0x00000000, 0x00000841, 0x00010000, 0x00010841, 0x00010000, 0x00000841 +.word 0x00400000, 0x00400840, 0x00400000, 0x00400841, 0x00410000, 0x00410841, 0x00410000, 0x00400841 +.word 0x00400000, 0x00000840, 0x00400000, 0x00000841, 0x00410000, 0x00010841, 0x00410000, 0x00000841 +.word 0x08000000, 0x08000800, 0x08000000, 0x08000801, 0x08010000, 0x08010801, 0x08010000, 0x08000801 +.word 0x08000000, 0x08000840, 0x08000000, 0x08000841, 0x08010000, 0x08010841, 0x08010000, 0x08000841 +.word 0x08400000, 0x08400840, 0x08400000, 0x08400841, 0x08410000, 0x08410841, 0x08410000, 0x08400841 +.word 0x08400000, 0x08000840, 0x08400000, 0x08000841, 0x08410000, 0x08010841, 0x08410000, 0x08000841 +.word 0x08000000, 0x00000800, 0x08000000, 0x00000801, 0x08010000, 0x00010801, 0x08010000, 0x00000801 +.word 0x08000000, 0x00000840, 0x08000000, 0x00000841, 0x08010000, 0x00010841, 0x08010000, 0x00000841 +.word 0x08400000, 0x00400840, 0x08400000, 0x00400841, 0x08410000, 0x00410841, 0x08410000, 0x00400841 +.word 0x08400000, 0x00000840, 0x08400000, 0x00000841, 0x08410000, 0x00010841, 0x08410000, 0x00000841 +#endif + +.section \code_section_prefix\().\name\().code, "ax" + +.global \name +.type \name,%function +.thumb_func +//const uint8_t* \name(uint32_t *d0, uint32_t *d1, const uint8_t *s, uint32_t w); +\name: + push {r4, r5, r6, r7, lr} + mov r4, r8 + push {r4} + lsls r3, #1 + adds r3, r0 + // done (near) + bcs 0f + // todo assert r_output is r0 + subs r1, r_output + mov r_row_delta, r1 + // todo assert r_interps is r1 + ldr r_interps, =#SIO_BASE + SIO_INTERP0_ACCUM0_OFFSET + // todo assert r_source is r2 + mov r_output_end, r3 + +\name\()_rem_0: // r_top has 0 valid bytes + ldmia r_input!, {r_top} + +\name\()_rem_4: // r_top has 4 valid bytes LSB first + lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT + bcs 2f + mov r_save, r_top // todo we could skip this (in case we hit 4 byte path anyway) if we have spare reg, or just move duplicate_4_pixels into both branches + duplicate_4_pixels r_tmp2 + lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT + bcc 1f + do_555 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_0 +0: + b \name\()_done +1: + do_222 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_1 + b \name\()_done +2: + //bkpt #0 + ldmia r_input!, {r_bottom} + mov r_save, r_bottom + shuffle_7_bytes_to_8 r_tmp2, r_tmp3 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + bge \name\()_done + // fall thru + +\name\()_rem_1: // r_remaining_bits has 1 byte remaining (in the MSB) + ldmia r_input!, {r_tmp2} + lsrs r_top, #24 + lsls r_tmp1, r_tmp2, #8 + orrs r_top, r_tmp1 + + lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT + bcs 2f + mov r_save, r_tmp2 + duplicate_4_pixels r_tmp2 + lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT + bcc 1f + do_555 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_1 + b \name\()_done +1: + do_222 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_2 + b \name\()_done +2: + //bkpt #0 + // r_top has 4, r_save has 1 + ldmia r_input!, {r_tmp1} + lsrs r_bottom, r_tmp2, #24 + lsls r_tmp2, r_tmp1, #8 + orrs r_bottom, r_tmp2 + + shuffle_7_bytes_to_8 r_tmp2, r_tmp3 + write_output r_tmp2 + + mov r_top, r_tmp1 + cmp r_output, r_output_end + blt \name\()_rem_2 + +\name\()_done: + pop {r3, r4, r5, r6, r7} + mov r8, r3 + pop {pc} + +\name\()_rem_2: // r_remaining_bits has 2 bytes remaining (in the MSB) + ldmia r_input!, {r_tmp2} + lsrs r_top, #16 + lsls r_tmp1, r_tmp2, #16 + orrs r_top, r_tmp1 + + lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT + bcs 2f + mov r_save, r_tmp2 + duplicate_4_pixels r_tmp2 + lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT + bcc 1f + do_555 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_2 + b \name\()_done +1: + do_222 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_3 + b \name\()_done +2: + //bkpt #0 + ldmia r_input!, {r_tmp1} + lsrs r_bottom, r_tmp2, #16 + lsls r_tmp2, r_tmp1, #16 + orrs r_bottom, r_tmp2 + + shuffle_7_bytes_to_8 r_tmp2, r_tmp3 + write_output r_tmp2 + mov r_top, r_tmp1 + cmp r_output, r_output_end + bge \name\()_done + // fall thru + +\name\()_rem_3: // r_remaining_bits has 3 bytes remaining (in the MSB) + ldmia r_input!, {r_tmp2} + lsrs r_top, #8 + lsls r_tmp1, r_tmp2, #24 + orrs r_top, r_tmp1 + + lsrs r_tmp1, r_top, #FIRST_TEST_SHIFT + bcs 2f + mov r_save, r_tmp2 + duplicate_4_pixels r_tmp2 + lsrs r_tmp2, r_tmp1, #SECOND_TEST_SHIFT + bcc 1f + do_555 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + blt \name\()_rem_3 + b \name\()_done +1: + do_222 + write_output r_tmp2 + mov r_top, r_save + cmp r_output, r_output_end + bge \name\()_done2 + b \name\()_rem_4 +2: + //bkpt #0 + ldmia r_input!, {r_tmp1} + lsrs r_bottom, r_tmp2, #8 + lsls r_tmp2, r_tmp1, #24 + orrs r_bottom, r_tmp2 + + shuffle_7_bytes_to_8 r_tmp2, r_tmp3 + write_output r_tmp2 + mov r_top, r_tmp1 + cmp r_output, r_output_end + bge \name\()_done2 + b \name\()_rem_4 +\name\()_done2: + pop {r3, r4, r5, r6, r7} + mov r8, r3 + mov r0, r_input + pop {pc} + +.endm + +// put one decompressor in each scratch bank for use by each core +decompressor platypus_decompress_row_asm_a .scratch_x, .scratch_x +decompressor platypus_decompress_row_asm_b .scratch_y, .scratch_y \ No newline at end of file diff --git a/src/common/platypus/platypus.c b/src/common/platypus/platypus.c new file mode 100644 index 0000000..8104205 --- /dev/null +++ b/src/common/platypus/platypus.c @@ -0,0 +1,229 @@ +#include + +#include "pico.h" +#include "platypus.h" +#include "pico/scanvideo.h" +#if PICO_ON_DEVICE +#include "hardware/interp.h" +#endif + +#ifdef PLATYPUS_565 +#define PIXEL_RSHIFT 0 +#define PIXEL_GSHIFT 6 +#define PIXEL_BSHIFT 11 +#else +#define PIXEL_RSHIFT 0 +#define PIXEL_GSHIFT 5 +#define PIXEL_BSHIFT 10 +#endif +#if PICO_NO_HARDWARE + +const static uint32_t row_5_table[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001, + 0x00010001, 0x00000001, 0x00010000, 0x00000000, 0x00000000, 0x00010001, 0x00010000, 0x00010002, + 0x00000000, 0x00010002, 0x00020000, 0x00010001, 0x00010000, 0x00020002, 0x00010000, 0x00010000, + 0x00000001, 0x00000001, 0x00010001, 0x00000002, 0x00010001, 0x00000000, 0x00020001, 0x00000001, + 0x00020000, 0x00020001, 0x00000001, 0x00000002, 0x00020002, 0x00000001, 0x00020001, 0x00000000, + 0x00000000, 0x00000002, 0x00000001, 0x00010002, 0x00010000, 0x00020001, 0x00020000, 0x00010000, + 0x00020000, 0x00020002, 0x00000000, 0x00020002, 0x00010002, 0x00000002, 0x00010000, 0x00020003, + 0x00000000, 0x00020003, 0x00010000, 0x00030003, 0x00010000, 0x00000002, 0x00000001, 0x00010001, +}; + +#ifndef PLATYPUS_565 +const static uint32_t row_222_table[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001, + 0x00000000, 0x00000020, 0x00000000, 0x00000021, 0x00010000, 0x00010021, 0x00010000, 0x00000021, + 0x00200000, 0x00200020, 0x00200000, 0x00200021, 0x00210000, 0x00210021, 0x00210000, 0x00200021, + 0x00200000, 0x00000020, 0x00200000, 0x00000021, 0x00210000, 0x00010021, 0x00210000, 0x00000021, + 0x00000000, 0x00000400, 0x00000000, 0x00000401, 0x00010000, 0x00010401, 0x00010000, 0x00000401, + 0x00000000, 0x00000420, 0x00000000, 0x00000421, 0x00010000, 0x00010421, 0x00010000, 0x00000421, + 0x00200000, 0x00200420, 0x00200000, 0x00200421, 0x00210000, 0x00210421, 0x00210000, 0x00200421, + 0x00200000, 0x00000420, 0x00200000, 0x00000421, 0x00210000, 0x00010421, 0x00210000, 0x00000421, + 0x04000000, 0x04000400, 0x04000000, 0x04000401, 0x04010000, 0x04010401, 0x04010000, 0x04000401, + 0x04000000, 0x04000420, 0x04000000, 0x04000421, 0x04010000, 0x04010421, 0x04010000, 0x04000421, + 0x04200000, 0x04200420, 0x04200000, 0x04200421, 0x04210000, 0x04210421, 0x04210000, 0x04200421, + 0x04200000, 0x04000420, 0x04200000, 0x04000421, 0x04210000, 0x04010421, 0x04210000, 0x04000421, + 0x04000000, 0x00000400, 0x04000000, 0x00000401, 0x04010000, 0x00010401, 0x04010000, 0x00000401, + 0x04000000, 0x00000420, 0x04000000, 0x00000421, 0x04010000, 0x00010421, 0x04010000, 0x00000421, + 0x04200000, 0x00200420, 0x04200000, 0x00200421, 0x04210000, 0x00210421, 0x04210000, 0x00200421, + 0x04200000, 0x00000420, 0x04200000, 0x00000421, 0x04210000, 0x00010421, 0x04210000, 0x00000421, +}; +#else +const static uint32_t row_222_table[] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00010000, 0x00010001, 0x00010000, 0x00000001, + 0x00000000, 0x00000040, 0x00000000, 0x00000041, 0x00010000, 0x00010041, 0x00010000, 0x00000041, + 0x00400000, 0x00400040, 0x00400000, 0x00400041, 0x00410000, 0x00410041, 0x00410000, 0x00400041, + 0x00400000, 0x00000040, 0x00400000, 0x00000041, 0x00410000, 0x00010041, 0x00410000, 0x00000041, + 0x00000000, 0x00000800, 0x00000000, 0x00000801, 0x00010000, 0x00010801, 0x00010000, 0x00000801, + 0x00000000, 0x00000840, 0x00000000, 0x00000841, 0x00010000, 0x00010841, 0x00010000, 0x00000841, + 0x00400000, 0x00400840, 0x00400000, 0x00400841, 0x00410000, 0x00410841, 0x00410000, 0x00400841, + 0x00400000, 0x00000840, 0x00400000, 0x00000841, 0x00410000, 0x00010841, 0x00410000, 0x00000841, + 0x08000000, 0x08000800, 0x08000000, 0x08000801, 0x08010000, 0x08010801, 0x08010000, 0x08000801, + 0x08000000, 0x08000840, 0x08000000, 0x08000841, 0x08010000, 0x08010841, 0x08010000, 0x08000841, + 0x08400000, 0x08400840, 0x08400000, 0x08400841, 0x08410000, 0x08410841, 0x08410000, 0x08400841, + 0x08400000, 0x08000840, 0x08400000, 0x08000841, 0x08410000, 0x08010841, 0x08410000, 0x08000841, + 0x08000000, 0x00000800, 0x08000000, 0x00000801, 0x08010000, 0x00010801, 0x08010000, 0x00000801, + 0x08000000, 0x00000840, 0x08000000, 0x00000841, 0x08010000, 0x00010841, 0x08010000, 0x00000841, + 0x08400000, 0x00400840, 0x08400000, 0x00400841, 0x08410000, 0x00410841, 0x08410000, 0x00400841, + 0x08400000, 0x00000840, 0x08400000, 0x00000841, 0x08410000, 0x00010841, 0x08410000, 0x00000841, +}; + +#endif + +const uint32_t* platypus_decompress_row(uint32_t *d0, uint32_t *d1, const uint32_t *s4, uint32_t w) { + const uint8_t *s = (const uint8_t *)s4; + for(uint x = 0; x < w; x += 2) + { + uint32_t row0, row1; +#ifndef PLATYPUS_565 + if (s[1] & 0x80) { +#else + if (s[0] & 0x20) { +#endif + uint c0 = s[0] | (s[1] << 8); + uint c1 = s[2] | (s[3] << 8); +#ifndef PLATYPUS_565 + if (s[3] & 0x80) { +#else + if (s[2] & 0x20) { +#endif + // todo this is unused right now + row0 = (c1 << 16) | c0; + row1 = (s[7] << 24) | (s[6] << 16) | (s[5] << 8) | s[4]; + s+=8; + } else { + row0 = (c1 << 16) | c0; + row1 = (s[6] << 16) | (s[5] << 8) | s[4]; +#ifndef PLATYPUS_565 + uint32_t mask = 0xff018401; // note ff at top saves us a mask of 0x00ffffff first for row1 + uint32_t hi_bits = row1 & mask; + row1 ^= hi_bits; + uint32_t lo_bits = row0 & (mask << 16u); + row0 ^= lo_bits; + hi_bits |= lo_bits >> 15u; + row1 |= (hi_bits >> 10u) << 24u; + row1 |= hi_bits << 27u; +#else + uint32_t mask = 0xff210821; + uint32_t hi_bits = row1 & mask; + row1 ^= hi_bits; + uint32_t lo_bits = row0 & (mask << 16u); + row0 ^= lo_bits; + lo_bits >>= 13u; + hi_bits |= lo_bits; + hi_bits ^= (hi_bits >> 10u); + hi_bits = (hi_bits * 2) + ((hi_bits >> 11u) & 1u); + row1 |= hi_bits << 24u; +#endif + s+=7; +// row0=row1=0; + } + } else { + row0 = s[0] | (s[1] << 8); + row1 = row0 = row0 | (row0 << 16); + uint v = s[2]; +#ifndef PLATYPUS_565 + if (v & 0x80u) { + v = (v << 8u) | s[3]; +#else + if (v & 0x01u) { + v = (v >> 1u) | (s[3] << 7u); +#endif + // do Rs +#if PICO_ON_DEVICE + v <<= 11u; + interp1->accum[0] = v; + uint32_t *p = (uint32_t *)(interp1->peek[0]); + row0 += p[0]; + row1 += p[1]; +#else + // *2 for interleave + v <<= 1u; + row0 += row_5_table[v&0x3e] << PIXEL_RSHIFT; + row1 += row_5_table[(v&0x3e)+1] << PIXEL_RSHIFT; +#endif + // do Gs and Bs +#if PICO_ON_DEVICE + interp0->accum[0] = v; + uint32_t *p0 = (uint32_t *)(interp0->peek[0]); + uint32_t *p1 = (uint32_t *)(interp0->peek[1]); + + row0 += p0[0] << PIXEL_GSHIFT; + row1 += p0[1] << PIXEL_GSHIFT; + row0 += p1[0] << PIXEL_BSHIFT; + row1 += p1[1] << PIXEL_BSHIFT; +#else + row0 += row_5_table[(v>>5u)&0x3eu] << PIXEL_GSHIFT; + row1 += row_5_table[1 + ((v>>5u)&0x3eu)] << PIXEL_GSHIFT; + row0 += row_5_table[(v>>10u)&0x3eu] << PIXEL_BSHIFT; + row1 += row_5_table[1 + ((v>>10u)&0x3eu)] << PIXEL_BSHIFT; +#endif + s += 4; + } else { +#ifdef PLATYPUS_565 + v >>= 2; +#endif +#if PICO_ON_DEVICE + interp1->accum[1] = v << 12u; + uint32_t *p = (uint32_t *)(interp1->peek[1]); + row0 += p[0]; + row1 += p[1]; +#else + row0 += row_222_table[v*2]; + row1 += row_222_table[v*2 + 1]; +#endif + s += 3; + } + } + *d0++ = row0; + *d1++ = row1; + } + s += (4u - ((uintptr_t)s) & 3u) & 3u; + // round up to word boundary + return (uint32_t *)s; +} + +#else +void platypus_decompress_configure_interp(bool is_b) { + extern uint32_t platypus_decompress_row_asm_a_222_table, platypus_decompress_row_asm_a_5_table; + extern uint32_t platypus_decompress_row_asm_b_222_table, platypus_decompress_row_asm_b_5_table; + uint32_t row_5 = (uintptr_t)(is_b?&platypus_decompress_row_asm_b_5_table:&platypus_decompress_row_asm_a_5_table); + interp0->base[0] = row_5; + interp0->base[1] = row_5; + interp1->base[0] = row_5; + interp1->base[1] = (uintptr_t)(is_b?&platypus_decompress_row_asm_b_222_table:&platypus_decompress_row_asm_a_222_table); +#ifndef VIDEO_DBI + const uint es_555 = 0; + const uint es_222 = 0; +#else + const uint es_555 = 8; + const uint es_222 = 9; +#endif + interp_config c = interp_default_config(); + interp_config_set_shift(&c, 5 + es_555); + interp_config_set_mask(&c, 3, 7); + interp_set_config(interp0, 0, &c); + + interp_config_set_shift(&c, es_555); + interp_set_config(interp1, 0, &c); + + interp_config_set_mask(&c, 3, 8); + interp_config_set_shift(&c, es_222); + interp_set_config(interp1, 1, &c); + + interp_config_set_mask(&c, 3, 7); + interp_config_set_shift(&c, 10 + es_555); + interp_config_set_cross_input(&c, true); + interp_set_config(interp0, 1, &c); + +// interp_configure_none(interp0, 0, 5 + es_555, 3, 7); +// interp_configure_with_cross_input(interp0, 1, 10 + es_555, 3, 7); +// interp_configure_none(interp1, 0, es_555, 3, 7); +// interp_configure_none(interp1, 1, es_222, 3, 8); + +// interp_add_force_bits(interp0, 0, 2); +// interp_add_force_bits(interp0, 1, 2); +// interp_add_force_bits(interp1, 0, 2); +// interp_add_force_bits(interp1, 1, 2); +} +#endif \ No newline at end of file diff --git a/src/common/platypus/platypus.h b/src/common/platypus/platypus.h new file mode 100644 index 0000000..b7ecdf5 --- /dev/null +++ b/src/common/platypus/platypus.h @@ -0,0 +1,24 @@ +#ifndef _PLATYPUS_H +#define _PLATYPUS_H + +#include + +#ifdef VIDEO_DBI +#undef PLATYPUS_565 +#define PLATYPUS_565 +#endif + +#if PICO_ON_DEVICE +extern const uint32_t* platypus_decompress_row_asm_a(uint32_t *d0, uint32_t *d1, const uint32_t *s, uint32_t w); +extern const uint32_t* platypus_decompress_row_asm_b(uint32_t *d0, uint32_t *d1, const uint32_t *s, uint32_t w); +extern void platypus_decompress_configure_interp(bool is_b); +#define platypus_decompress_row_a platypus_decompress_row_asm_a +#define platypus_decompress_row_b platypus_decompress_row_asm_b +#else +extern const uint32_t* platypus_decompress_row(uint32_t *d0, uint32_t *d1, const uint32_t *s, uint32_t w); +#define platypus_decompress_row_a platypus_decompress_row +#define platypus_decompress_row_b platypus_decompress_row +#endif + +#endif + diff --git a/src/rp2_common/CMakeLists.txt b/src/rp2_common/CMakeLists.txt new file mode 100644 index 0000000..f453ad7 --- /dev/null +++ b/src/rp2_common/CMakeLists.txt @@ -0,0 +1,14 @@ +# This CMakeLists.txt intended to be included from other projectgs +pico_add_subdirectory(hardware_rosc) +pico_add_subdirectory(lwip) +pico_add_subdirectory(pico_sleep) +pico_add_subdirectory(pico_audio_i2s) +pico_add_subdirectory(pico_audio_pwm) +pico_add_subdirectory(pico_audio_spdif) +pico_add_subdirectory(pico_sd_card) +# currently very old and non-compiling +#pico_add_subdirectory(pico_scanvideo_dbi) +pico_add_subdirectory(pico_scanvideo_dpi) +pico_add_subdirectory(usb_common) +pico_add_subdirectory(usb_device) +pico_add_subdirectory(usb_device_msc) \ No newline at end of file diff --git a/src/rp2_common/hardware_rosc/CMakeLists.txt b/src/rp2_common/hardware_rosc/CMakeLists.txt new file mode 100644 index 0000000..3ee667e --- /dev/null +++ b/src/rp2_common/hardware_rosc/CMakeLists.txt @@ -0,0 +1 @@ +pico_simple_hardware_target(rosc) \ No newline at end of file diff --git a/src/rp2_common/hardware_rosc/include/hardware/rosc.h b/src/rp2_common/hardware_rosc/include/hardware/rosc.h new file mode 100644 index 0000000..2720f0b --- /dev/null +++ b/src/rp2_common/hardware_rosc/include/hardware/rosc.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _HARDWARE_ROSC_H_ +#define _HARDWARE_ROSC_H_ + +#include "pico.h" +#include "hardware/structs/rosc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file rosc.h + * \defgroup hardware_rosc hardware_rosc + * + * Ring Oscillator (ROSC) API + * + * A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of + * inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the + * first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a + * crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is + * more accurate than the ring oscillator. + */ + +/*! \brief Set frequency of the Ring Oscillator + * \ingroup hardware_rosc + * + * \param code The drive strengths. See the RP2040 datasheet for information on this value. + */ +void rosc_set_freq(uint32_t code); + +/*! \brief Set range of the Ring Oscillator + * \ingroup hardware_rosc + * + * Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT). + * Clock output will not glitch when changing the range up one step at a time. + * + * \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High. + */ +void rosc_set_range(uint range); + +/*! \brief Disable the Ring Oscillator + * \ingroup hardware_rosc + * + */ +void rosc_disable(void); + +/*! \brief Put Ring Oscillator in to dormant mode. + * \ingroup hardware_rosc + * + * The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt. + * This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low. + * If no IRQ is configured before going into dormant mode the ROSC will never restart. + * + * PLLs should be stopped before selecting dormant mode. + */ +void rosc_set_dormant(void); + +// FIXME: Add doxygen + +uint32_t next_rosc_code(uint32_t code); + +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz); + +void rosc_set_div(uint32_t div); + +inline static void rosc_clear_bad_write(void) { + hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS); +} + +inline static bool rosc_write_okay(void) { + return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS); +} + +inline static void rosc_write(io_rw_32 *addr, uint32_t value) { + rosc_clear_bad_write(); + assert(rosc_write_okay()); + *addr = value; + assert(rosc_write_okay()); +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/hardware_rosc/rosc.c b/src/rp2_common/hardware_rosc/rosc.c new file mode 100644 index 0000000..69b6012 --- /dev/null +++ b/src/rp2_common/hardware_rosc/rosc.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" + +// For MHZ definitions etc +#include "hardware/clocks.h" +#include "hardware/rosc.h" + +// Given a ROSC delay stage code, return the next-numerically-higher code. +// Top result bit is set when called on maximum ROSC code. +uint32_t next_rosc_code(uint32_t code) { + return ((code | 0x08888888u) + 1u) & 0xf7777777u; +} + +uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) { + // TODO: This could be a lot better + rosc_set_div(1); + for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) { + rosc_set_freq(code); + uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000; + if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) { + return rosc_mhz; + } + } + return 0; +} + +void rosc_set_div(uint32_t div) { + assert(div <= 31 && div >= 1); + rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div); +} + +void rosc_set_freq(uint32_t code) { + rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu)); + rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u)); +} + +void rosc_set_range(uint range) { + // Range should use enumvals from the headers and thus have the password correct + rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range); +} + +void rosc_disable(void) { + uint32_t tmp = rosc_hw->ctrl; + tmp &= (~ROSC_CTRL_ENABLE_BITS); + tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB); + rosc_write(&rosc_hw->ctrl, tmp); + // Wait for stable to go away + while(rosc_hw->status & ROSC_STATUS_STABLE_BITS); +} + +void rosc_set_dormant(void) { + // WARNING: This stops the rosc until woken up by an irq + rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT); + // Wait for it to become stable once woken up + while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS)); +} \ No newline at end of file diff --git a/src/rp2_common/lwip/CMakeLists.txt b/src/rp2_common/lwip/CMakeLists.txt new file mode 100644 index 0000000..33a4ca6 --- /dev/null +++ b/src/rp2_common/lwip/CMakeLists.txt @@ -0,0 +1,59 @@ +set(LWIP_TEST_PATH "src/core/tcp.c") +if (NOT LWIP_PATH) + set(LWIP_PATH ${PICO_EXTRAS_PATH}/lib/lwip) + if (NOT EXISTS ${LWIP_PATH}/${LWIP_TEST_PATH}) + message(WARNING "lwIP submodule has not been initialized; TCP/IP support will be unavailable + hint: try 'git submodule update --init'.") + endif() +elseif (NOT EXISTS ${LWIP_PATH}/${LWIP_TEST_PATH}) + message(WARNING "LWIP_PATH specified but content not present.") +endif() + +if (EXISTS ${LWIP_PATH}/${LWIP_TEST_PATH}) + message("lwIP available at ${LWIP_PATH}/${LWIP_TEST_PATH}; TCP/IP support is available.") + + add_library(lwip INTERFACE) + target_sources(lwip INTERFACE + ${LWIP_PATH}/src/core/altcp.c + ${LWIP_PATH}/src/core/altcp_alloc.c + ${LWIP_PATH}/src/core/altcp_tcp.c + ${LWIP_PATH}/src/core/def.c + ${LWIP_PATH}/src/core/dns.c + ${LWIP_PATH}/src/core/inet_chksum.c + ${LWIP_PATH}/src/core/init.c + ${LWIP_PATH}/src/core/ip.c + ${LWIP_PATH}/src/core/mem.c + ${LWIP_PATH}/src/core/memp.c + ${LWIP_PATH}/src/core/netif.c + ${LWIP_PATH}/src/core/pbuf.c + ${LWIP_PATH}/src/core/raw.c + ${LWIP_PATH}/src/core/stats.c + ${LWIP_PATH}/src/core/sys.c + ${LWIP_PATH}/src/core/tcp.c + ${LWIP_PATH}/src/core/tcp_in.c + ${LWIP_PATH}/src/core/tcp_out.c + ${LWIP_PATH}/src/core/timeouts.c + ${LWIP_PATH}/src/core/udp.c + ${LWIP_PATH}/src/core/ipv4/autoip.c + ${LWIP_PATH}/src/core/ipv4/dhcp.c + ${LWIP_PATH}/src/core/ipv4/etharp.c + ${LWIP_PATH}/src/core/ipv4/icmp.c + ${LWIP_PATH}/src/core/ipv4/igmp.c + ${LWIP_PATH}/src/core/ipv4/ip4.c + ${LWIP_PATH}/src/core/ipv4/ip4_addr.c + ${LWIP_PATH}/src/core/ipv4/ip4_frag.c + ${LWIP_PATH}/src/netif/ethernet.c + ${LWIP_PATH}/src/netif/slipif.c + ${LWIP_PATH}/src/apps/http/httpd.c + ${LWIP_PATH}/src/apps/http/fs.c + + ${CMAKE_CURRENT_LIST_DIR}/lwip_arch.c + ) + + target_include_directories(lwip INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/include + ${LWIP_PATH}/src/include + ${LWIP_PATH}/src/include/ipv4 + ${LWIP_PATH}/src/include/lwip/apps + ) +endif() diff --git a/src/rp2_common/lwip/README.md b/src/rp2_common/lwip/README.md new file mode 100644 index 0000000..8c325b5 --- /dev/null +++ b/src/rp2_common/lwip/README.md @@ -0,0 +1 @@ +This directory contains files required to get lwip working on the Pico platform without needing to fork lwIP \ No newline at end of file diff --git a/src/rp2_common/lwip/include/arch/cc.h b/src/rp2_common/lwip/include/arch/cc.h new file mode 100644 index 0000000..56a0cac --- /dev/null +++ b/src/rp2_common/lwip/include/arch/cc.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. + * + * Author: Adam Dunkels + * + */ +#ifndef __CC_H__ +#define __CC_H__ + +//#include "cpu.h" + +typedef int sys_prot_t; + + + +/* define compiler specific symbols */ +#if defined (__ICCARM__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x +#define PACK_STRUCT_USE_INCLUDES + +#elif defined (__CC_ARM) + +#define PACK_STRUCT_BEGIN __packed +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#elif defined (__GNUC__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT __attribute__ ((__packed__)) +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#elif defined (__TASKING__) + +#define PACK_STRUCT_BEGIN +#define PACK_STRUCT_STRUCT +#define PACK_STRUCT_END +#define PACK_STRUCT_FIELD(x) x + +#endif + +#define LWIP_PLATFORM_ASSERT(x) do { if(!(x)) while(1); } while(0) + +#endif /* __CC_H__ */ diff --git a/src/rp2_common/lwip/lwip_arch.c b/src/rp2_common/lwip/lwip_arch.c new file mode 100644 index 0000000..c97172f --- /dev/null +++ b/src/rp2_common/lwip/lwip_arch.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "lwip/init.h" +#include "pico/stdlib.h" + +/* lwip has provision for using a mutex, when applicable */ +sys_prot_t sys_arch_protect(void) { + return 0; +} + +void sys_arch_unprotect(sys_prot_t pval) { + (void) pval; +} + +/* lwip needs a millisecond time source, and the TinyUSB board support code has one available */ +uint32_t sys_now(void) { + return to_ms_since_boot(get_absolute_time()); +} \ No newline at end of file diff --git a/src/rp2_common/pico_audio_i2s/CMakeLists.txt b/src/rp2_common/pico_audio_i2s/CMakeLists.txt new file mode 100644 index 0000000..c82a1b8 --- /dev/null +++ b/src/rp2_common/pico_audio_i2s/CMakeLists.txt @@ -0,0 +1,12 @@ +if (NOT TARGET pico_audio_i2s) + add_library(pico_audio_i2s INTERFACE) + + pico_generate_pio_header(pico_audio_i2s ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.pio) + + target_sources(pico_audio_i2s INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/audio_i2s.c + ) + + target_include_directories(pico_audio_i2s INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + target_link_libraries(pico_audio_i2s INTERFACE hardware_dma hardware_pio hardware_irq pico_audio) +endif() \ No newline at end of file diff --git a/src/rp2_common/pico_audio_i2s/audio_i2s.c b/src/rp2_common/pico_audio_i2s/audio_i2s.c new file mode 100644 index 0000000..70f1b2a --- /dev/null +++ b/src/rp2_common/pico_audio_i2s/audio_i2s.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/audio_i2s.h" +#include "audio_i2s.pio.h" +#include "hardware/pio.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/clocks.h" + + +CU_REGISTER_DEBUG_PINS(audio_timing) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(audio_timing) + + +#if PICO_AUDIO_I2S_MONO_OUTPUT +#define i2s_dma_configure_size DMA_SIZE_16 +#else +#define i2s_dma_configure_size DMA_SIZE_32 +#endif + +#define audio_pio __CONCAT(pio, PICO_AUDIO_I2S_PIO) +#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_I2S_PIO) +#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_I2S_PIO), _TX0) + +#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_I2S_DMA_IRQ) +#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_I2S_DMA_IRQ),_enabled) +#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_I2S_DMA_IRQ) + +struct { + audio_buffer_t *playing_buffer; + uint32_t freq; + uint8_t pio_sm; + uint8_t dma_channel; +} shared_state; + +audio_format_t pio_i2s_consumer_format; +audio_buffer_format_t pio_i2s_consumer_buffer_format = { + .format = &pio_i2s_consumer_format, +}; + +static audio_buffer_t silence_buffer; + +static void __isr __time_critical_func(audio_i2s_dma_irq_handler)(); + +const audio_format_t *audio_i2s_setup(const audio_format_t *intended_audio_format, + const audio_i2s_config_t *config) { + uint func = GPIO_FUNC_PIOx; + gpio_set_function(config->data_pin, func); + gpio_set_function(config->clock_pin_base, func); + gpio_set_function(config->clock_pin_base + 1, func); + + uint8_t sm = shared_state.pio_sm = config->pio_sm; + pio_sm_claim(audio_pio, sm); + + uint offset = pio_add_program(audio_pio, &audio_i2s_program); + + audio_i2s_program_init(audio_pio, sm, offset, config->data_pin, config->clock_pin_base); + + silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH * 4); + silence_buffer.sample_count = PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH; + silence_buffer.format = &pio_i2s_consumer_buffer_format; + + __mem_fence_release(); + uint8_t dma_channel = config->dma_channel; + dma_channel_claim(dma_channel); + + shared_state.dma_channel = dma_channel; + + dma_channel_config dma_config = dma_channel_get_default_config(dma_channel); + + channel_config_set_dreq(&dma_config, + DREQ_PIOx_TX0 + sm + ); + channel_config_set_transfer_data_size(&dma_config, i2s_dma_configure_size); + dma_channel_configure(dma_channel, + &dma_config, + &audio_pio->txf[sm], // dest + NULL, // src + 0, // count + false // trigger + ); + + irq_add_shared_handler(DMA_IRQ_x, audio_i2s_dma_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + dma_channel_set_irqx_enabled(dma_channel, 1); + return intended_audio_format; +} + +static audio_buffer_pool_t *audio_i2s_consumer; + +static void update_pio_frequency(uint32_t sample_freq) { + printf("setting pio freq %d\n", (int) sample_freq); + uint32_t system_clock_frequency = clock_get_hz(clk_sys); + assert(system_clock_frequency < 0x40000000); + uint32_t divider = system_clock_frequency * 4 / sample_freq; // avoid arithmetic overflow + printf("System clock at %u, I2S clock divider 0x%x/256\n", (uint) system_clock_frequency, (uint)divider); + assert(divider < 0x1000000); + pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu); + shared_state.freq = sample_freq; +} + +static audio_buffer_t *wrap_consumer_take(audio_connection_t *connection, bool block) { + // support dynamic frequency shifting + if (connection->producer_pool->format->sample_freq != shared_state.freq) { + update_pio_frequency(connection->producer_pool->format->sample_freq); + } +#if PICO_AUDIO_I2S_MONO_INPUT +#if PICO_AUDIO_I2S_MONO_OUTPUT + return mono_to_mono_consumer_take(connection, block); +#else + return mono_to_stereo_consumer_take(connection, block); +#endif +#else +#if PICO_AUDIO_I2S_MONO_OUTPUT + unsupported; +#else + return stereo_to_stereo_consumer_take(connection, block); +#endif +#endif +} + +static void wrap_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) { + // support dynamic frequency shifting + if (connection->producer_pool->format->sample_freq != shared_state.freq) { + update_pio_frequency(connection->producer_pool->format->sample_freq); + } +#if PICO_AUDIO_I2S_MONO_INPUT +#if PICO_AUDIO_I2S_MONO_OUTPUT + assert(false); +// return mono_to_mono_producer_give(connection, block); +#else + assert(false); + //return mono_to_stereo_producer_give(connection, buffer); +#endif +#else +#if PICO_AUDIO_I2S_MONO_OUTPUT + unsupported; +#else + return stereo_to_stereo_producer_give(connection, buffer); +#endif +#endif +} + +static struct buffer_copying_on_consumer_take_connection m2s_audio_i2s_ct_connection = { + .core = { + .consumer_pool_take = wrap_consumer_take, + .consumer_pool_give = consumer_pool_give_buffer_default, + .producer_pool_take = producer_pool_take_buffer_default, + .producer_pool_give = producer_pool_give_buffer_default, + } +}; + +static struct producer_pool_blocking_give_connection m2s_audio_i2s_pg_connection = { + .core = { + .consumer_pool_take = consumer_pool_take_buffer_default, + .consumer_pool_give = consumer_pool_give_buffer_default, + .producer_pool_take = producer_pool_take_buffer_default, + .producer_pool_give = wrap_producer_give, + } +}; + +bool audio_i2s_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection) { + return audio_i2s_connect_extra(producer, false, 2, 256, connection); +} + +bool audio_i2s_connect(audio_buffer_pool_t *producer) { + return audio_i2s_connect_thru(producer, NULL); +} + +bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, + uint samples_per_buffer, audio_connection_t *connection) { + printf("Connecting PIO I2S audio\n"); + + // todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100 + assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S16); + pio_i2s_consumer_format.format = AUDIO_BUFFER_FORMAT_PCM_S16; + // todo we could do mono + // todo we can't match exact, so we should return what we can do + pio_i2s_consumer_format.sample_freq = producer->format->sample_freq; +#if PICO_AUDIO_I2S_MONO_OUTPUT + pio_i2s_consumer_format.channel_count = 1; + pio_i2s_consumer_buffer_format.sample_stride = 2; +#else + pio_i2s_consumer_format.channel_count = 2; + pio_i2s_consumer_buffer_format.sample_stride = 4; +#endif + + audio_i2s_consumer = audio_new_consumer_pool(&pio_i2s_consumer_buffer_format, buffer_count, samples_per_buffer); + + update_pio_frequency(producer->format->sample_freq); + + // todo cleanup threading + __mem_fence_release(); + + if (!connection) { + if (producer->format->channel_count == 2) { +#if PICO_AUDIO_I2S_MONO_INPUT + panic("need to merge channels down\n"); +#else +#if PICO_AUDIO_I2S_MONO_OUTPUT + panic("trying to play stereo thru mono not yet supported"); +#else + printf("Copying stereo to stereo at %d Hz\n", (int) producer->format->sample_freq); +#endif +#endif + // todo we should support pass thru option anyway + printf("TODO... not completing stereo audio connection properly!\n"); + } else { +#if PICO_AUDIO_I2S_MONO_OUTPUT + printf("Copying mono to mono at %d Hz\n", (int) producer->format->sample_freq); +#else + printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq); +#endif + } + connection = buffer_on_give ? &m2s_audio_i2s_pg_connection.core : &m2s_audio_i2s_ct_connection.core; + } + audio_complete_connection(connection, producer, audio_i2s_consumer); + return true; +} + +static struct buffer_copying_on_consumer_take_connection m2s_audio_i2s_connection_s8 = { + .core = { +#if PICO_AUDIO_I2S_MONO_OUTPUT + .consumer_pool_take = mono_s8_to_mono_consumer_take, +#else + .consumer_pool_take = mono_s8_to_stereo_consumer_take, +#endif + .consumer_pool_give = consumer_pool_give_buffer_default, + .producer_pool_take = producer_pool_take_buffer_default, + .producer_pool_give = producer_pool_give_buffer_default, + } +}; + +bool audio_i2s_connect_s8(audio_buffer_pool_t *producer) { + printf("Connecting PIO I2S audio (U8)\n"); + + // todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100 + assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S8); + pio_i2s_consumer_format.format = AUDIO_BUFFER_FORMAT_PCM_S16; + // todo we could do mono + // todo we can't match exact, so we should return what we can do + pio_i2s_consumer_format.sample_freq = producer->format->sample_freq; +#if PICO_AUDIO_I2S_MONO_OUTPUT + pio_i2s_consumer_format.channel_count = 1; + pio_i2s_consumer_buffer_format.sample_stride = 2; +#else + pio_i2s_consumer_format.channel_count = 2; + pio_i2s_consumer_buffer_format.sample_stride = 4; +#endif + + // we do this on take so should do it quickly... + uint samples_per_buffer = 256; + // todo with take we really only need 1 buffer + audio_i2s_consumer = audio_new_consumer_pool(&pio_i2s_consumer_buffer_format, 2, samples_per_buffer); + + // todo we need a method to calculate this in clocks + uint32_t system_clock_frequency = 48000000; +// uint32_t divider = system_clock_frequency * 256 / producer->format->sample_freq * 16 * 4; + uint32_t divider = system_clock_frequency * 4 / producer->format->sample_freq; // avoid arithmetic overflow + pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu); + + // todo cleanup threading + __mem_fence_release(); + + audio_connection_t *connection; + if (producer->format->channel_count == 2) { +#if PICO_AUDIO_I2S_MONO_OUTPUT + panic("trying to play stereo thru mono not yet supported"); +#endif + // todo we should support pass thru option anyway + printf("TODO... not completing stereo audio connection properly!\n"); + connection = &m2s_audio_i2s_connection_s8.core; + } else { +#if PICO_AUDIO_I2S_MONO_OUTPUT + printf("Copying mono to mono at %d Hz\n", (int) producer->format->sample_freq); +#else + printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq); +#endif + connection = &m2s_audio_i2s_connection_s8.core; + } + audio_complete_connection(connection, producer, audio_i2s_consumer); + return true; +} + +static inline void audio_start_dma_transfer() { + assert(!shared_state.playing_buffer); + audio_buffer_t *ab = take_audio_buffer(audio_i2s_consumer, false); + + shared_state.playing_buffer = ab; + if (!ab) { + DEBUG_PINS_XOR(audio_timing, 1); + DEBUG_PINS_XOR(audio_timing, 2); + DEBUG_PINS_XOR(audio_timing, 1); + //DEBUG_PINS_XOR(audio_timing, 2); + // just play some silence + ab = &silence_buffer; + } + assert(ab->sample_count); + // todo better naming of format->format->format!! + assert(ab->format->format->format == AUDIO_BUFFER_FORMAT_PCM_S16); +#if PICO_AUDIO_I2S_MONO_OUTPUT + assert(ab->format->format->channel_count == 1); + assert(ab->format->sample_stride == 2); +#else + assert(ab->format->format->channel_count == 2); + assert(ab->format->sample_stride == 4); +#endif + dma_channel_transfer_from_buffer_now(shared_state.dma_channel, ab->buffer->bytes, ab->sample_count); +} + +// irq handler for DMA +void __isr __time_critical_func(audio_i2s_dma_irq_handler)() { +#if PICO_AUDIO_I2S_NOOP + assert(false); +#else + uint dma_channel = shared_state.dma_channel; + if (dma_intsx & (1u << dma_channel)) { + dma_intsx = 1u << dma_channel; + DEBUG_PINS_SET(audio_timing, 4); + // free the buffer we just finished + if (shared_state.playing_buffer) { + give_audio_buffer(audio_i2s_consumer, shared_state.playing_buffer); +#ifndef NDEBUG + shared_state.playing_buffer = NULL; +#endif + } + audio_start_dma_transfer(); + DEBUG_PINS_CLR(audio_timing, 4); + } +#endif +} + +static bool audio_enabled; + +void audio_i2s_set_enabled(bool enabled) { + if (enabled != audio_enabled) { +#ifndef NDEBUG + if (enabled) + { + puts("Enabling PIO I2S audio\n"); + printf("(on core %d\n", get_core_num()); + } +#endif + irq_set_enabled(DMA_IRQ_x, enabled); + + if (enabled) { + audio_start_dma_transfer(); + } + + pio_sm_set_enabled(audio_pio, shared_state.pio_sm, enabled); + + audio_enabled = enabled; + } +} diff --git a/src/rp2_common/pico_audio_i2s/audio_i2s.pio b/src/rp2_common/pico_audio_i2s/audio_i2s.pio new file mode 100644 index 0000000..1c306f4 --- /dev/null +++ b/src/rp2_common/pico_audio_i2s/audio_i2s.pio @@ -0,0 +1,63 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; Transmit a mono or stereo I2S audio stream as stereo +; This is 16 bits per sample; can be altered by modifying the "set" params, +; or made programmable by replacing "set x" with "mov x, y" and using Y as a config register. +; +; Autopull must be enabled, with threshold set to 32. +; Since I2S is MSB-first, shift direction should be to left. +; Hence the format of the FIFO word is: +; +; | 31 : 16 | 15 : 0 | +; | sample ws=0 | sample ws=1 | +; +; Data is output at 1 bit per clock. Use clock divider to adjust frequency. +; Fractional divider will probably be needed to get correct bit clock period, +; but for common syslck freqs this should still give a constant word select period. +; +; One output pin is used for the data output. +; Two side-set pins are used. Bit 0 is clock, bit 1 is word select. + +; Send 16 bit words to the PIO for mono, 32 bit words for stereo + +.program audio_i2s +.side_set 2 + + ; /--- LRCLK + ; |/-- BCLK +bitloop1: ; || + out pins, 1 side 0b10 + jmp x-- bitloop1 side 0b11 + out pins, 1 side 0b00 + set x, 14 side 0b01 + +bitloop0: + out pins, 1 side 0b00 + jmp x-- bitloop0 side 0b01 + out pins, 1 side 0b10 +public entry_point: + set x, 14 side 0b11 + +% c-sdk { + +static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) { + pio_sm_config sm_config = audio_i2s_program_get_default_config(offset); + + sm_config_set_out_pins(&sm_config, data_pin, 1); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_out_shift(&sm_config, false, true, 32); + + pio_sm_init(pio, sm, offset, &sm_config); + + uint pin_mask = (1u << data_pin) | (3u << clock_pin_base); + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + pio_sm_set_pins(pio, sm, 0); // clear pins + + pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point)); +} + +%} \ No newline at end of file diff --git a/src/rp2_common/pico_audio_i2s/include/pico/audio_i2s.h b/src/rp2_common/pico_audio_i2s/include/pico/audio_i2s.h new file mode 100644 index 0000000..4791646 --- /dev/null +++ b/src/rp2_common/pico_audio_i2s/include/pico/audio_i2s.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_AUDIO_I2S_H +#define _PICO_AUDIO_I2S_H + +#include "pico/audio.h" + +/** \file audio_i2s.h + * \defgroup pico_audio_i2s pico_audio_i2s + * I2S audio output using the PIO + * + * This library uses the \ref hardware_pio system to implement a I2S audio interface + * + * \todo Must be more we need to say here. + * \todo certainly need an example + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PICO_AUDIO_I2S_DMA_IRQ +#ifdef PICO_AUDIO_DMA_IRQ +#define PICO_AUDIO_I2S_DMA_IRQ PICO_AUDIO_DMA_IRQ +#else +#define PICO_AUDIO_I2S_DMA_IRQ 0 +#endif +#endif + +#ifndef PICO_AUDIO_I2S_PIO +#ifdef PICO_AUDIO_PIO +#define PICO_AUDIO_I2S_PIO PICO_AUDIO_PIO +#else +#define PICO_AUDIO_I2S_PIO 0 +#endif +#endif + +#if !(PICO_AUDIO_I2S_DMA_IRQ == 0 || PICO_AUDIO_I2S_DMA_IRQ == 1) +#error PICO_AUDIO_I2S_DMA_IRQ must be 0 or 1 +#endif + +#if !(PICO_AUDIO_I2S_PIO == 0 || PICO_AUDIO_I2S_PIO == 1) +#error PICO_AUDIO_I2S_PIO ust be 0 or 1 +#endif + +#ifndef PICO_AUDIO_I2S_MAX_CHANNELS +#ifdef PICO_AUDIO_MAX_CHANNELS +#define PICO_AUDIO_I2S_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS +#else +#define PICO_AUDIO_I2S_MAX_CHANNELS 2u +#endif +#endif + +#ifndef PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL +#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL +#define PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL +#else +#define PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL 3u +#endif +#endif + +#ifndef PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH +#ifdef PICO_AUDIO_BUFFER_SAMPLE_LENGTH +#define PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH PICO_AUDIO_BUFFER_SAMPLE_LENGTH +#else +#define PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH 576u +#endif +#endif + +#ifndef PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH +#ifdef PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH +#define PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH PICO_AUDIO_SILENCE_BUFFER_SAMPLE_LENGTH +#else +#define PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH 256u +#endif +#endif + +// Allow use of pico_audio driver without actually doing anything much +#ifndef PICO_AUDIO_I2S_NOOP +#ifdef PICO_AUDIO_NOOP +#define PICO_AUDIO_I2S_NOOP PICO_AUDIO_NOOP +#else +#define PICO_AUDIO_I2S_NOOP 0 +#endif +#endif + +#ifndef PICO_AUDIO_I2S_MONO_INPUT +#define PICO_AUDIO_I2S_MONO_INPUT 0 +#endif +#ifndef PICO_AUDIO_I2S_MONO_OUTPUT +#define PICO_AUDIO_I2S_MONO_OUTPUT 0 +#endif + +#ifndef PICO_AUDIO_I2S_DATA_PIN +//#warning PICO_AUDIO_I2S_DATA_PIN should be defined when using AUDIO_I2S +#define PICO_AUDIO_I2S_DATA_PIN 27 +#endif + +#ifndef PICO_AUDIO_I2S_CLOCK_PIN_BASE +//#warning PICO_AUDIO_I2S_CLOCK_PIN_BASE should be defined when using AUDIO_I2S +#define PICO_AUDIO_I2S_CLOCK_PIN_BASE 25 +#endif + +// todo this needs to come from a build config +/** \brief Base configuration structure used when setting up + * \ingroup pico_audio_i2s + */ +typedef struct audio_i2s_config { + uint8_t data_pin; + uint8_t clock_pin_base; + uint8_t dma_channel; + uint8_t pio_sm; +} audio_i2s_config_t; + +/** \brief Set up system to output I2S audio + * \ingroup pico_audio_i2s + * + * \param intended_audio_format \todo + * \param config The configuration to apply. + */ +const audio_format_t *audio_i2s_setup(const audio_format_t *intended_audio_format, + const audio_i2s_config_t *config); + + +/** \brief \todo + * \ingroup pico_audio_i2s + * + * \param producer + * \param connection + */ +bool audio_i2s_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection); + + +/** \brief \todo + * \ingroup pico_audio_i2s + * + * \param producer + * + * todo make a common version (or a macro) .. we don't want to pull in unnecessary code by default + */ +bool audio_i2s_connect(audio_buffer_pool_t *producer); + + +/** \brief \todo + * \ingroup pico_audio_i2s + * + * \param producer + */ +bool audio_i2s_connect_s8(audio_buffer_pool_t *producer); +bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, uint samples_per_buffer, audio_connection_t *connection); + +/** \brief \todo + * \ingroup pico_audio_i2s + * + * \param producer + * \param buffer_on_give + * \param buffer_count + * \param samples_per_buffer + * \param connection + * \return + */ +bool audio_i2s_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, + uint samples_per_buffer, audio_connection_t *connection); + + +/** \brief Set up system to output I2S audio + * \ingroup pico_audio_i2s + * + * \param enable true to enable I2S audio, false to disable. + */ +void audio_i2s_set_enabled(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif //_AUDIO_I2S_H diff --git a/src/rp2_common/pico_audio_pwm/CMakeLists.txt b/src/rp2_common/pico_audio_pwm/CMakeLists.txt new file mode 100644 index 0000000..3e3ec09 --- /dev/null +++ b/src/rp2_common/pico_audio_pwm/CMakeLists.txt @@ -0,0 +1,19 @@ +if (NOT TARGET pico_audio_pwm) + add_library(pico_audio_pwm INTERFACE) + + pico_generate_pio_header(pico_audio_pwm ${CMAKE_CURRENT_LIST_DIR}/audio_pwm.pio) + + target_sources(pico_audio_pwm INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/audio_pwm.c + ${CMAKE_CURRENT_LIST_DIR}/sample_encoding.cpp + ) + + target_include_directories(pico_audio_pwm INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + target_link_libraries(pico_audio_pwm INTERFACE + hardware_dma + hardware_pio + hardware_irq + hardware_interp + pico_audio + pico_multicore) +endif() \ No newline at end of file diff --git a/src/rp2_common/pico_audio_pwm/audio_pwm.c b/src/rp2_common/pico_audio_pwm/audio_pwm.c new file mode 100644 index 0000000..412378e --- /dev/null +++ b/src/rp2_common/pico_audio_pwm/audio_pwm.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include "pico/audio_pwm.h" + +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "hardware/pio.h" +#include "hardware/irq.h" +#include "pico/multicore.h" +#include "pico/sem.h" +#include "pico/audio_pwm/sample_encoding.h" + +#include "audio_pwm.pio.h" + +// TODO: add noise shaped fixed dither + + +#define audio_pio __CONCAT(pio, PICO_AUDIO_PWM_PIO) +#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_PWM_PIO) +#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_PWM_PIO), _TX0) + +#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_PWM_DMA_IRQ) +#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_PWM_DMA_IRQ),_enabled) +#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_PWM_DMA_IRQ) + +// ====================== +// == DEBUGGING ========= + +#define ENABLE_PIO_AUDIO_PWM_ASSERTIONS + +CU_REGISTER_DEBUG_PINS(audio_timing, audio_underflow) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(audio_timing) + +// ====================== + +#ifdef ENABLE_PIO_AUDIO_PWM_ASSERTIONS +#define audio_assert(x) assert(x) +#else +#define audio_assert(x) (void)0 +#endif + +#define _UNDERSCORE(x, y) x ## _ ## y +#define _CONCAT(x, y) _UNDERSCORE(x,y) +#define audio_program _CONCAT(program_name,program) +#define audio_program_get_default_config _CONCAT(program_name,program_get_default_config) +#define audio_entry_point _CONCAT(program_name,offset_entry_point) + +static bool audio_enabled; +static bool push_queuing_to_core1; + +static void __isr __time_critical_func(audio_pwm_dma_irq_handler)(); + +static struct { + audio_buffer_pool_t *playback_buffer_pool[PICO_AUDIO_PWM_MAX_CHANNELS]; + audio_buffer_t *playing_buffer[PICO_AUDIO_PWM_MAX_CHANNELS]; + // ----- begin protected by free_list_spin_lock ----- + uint8_t pio_sm[PICO_AUDIO_PWM_MAX_CHANNELS]; + uint8_t dma_channel[PICO_AUDIO_PWM_MAX_CHANNELS]; + int channel_count; +} shared_state; + +const audio_pwm_channel_config_t default_left_channel_config = + { + .core = { + .base_pin = PICO_AUDIO_PWM_L_PIN, + .pio_sm = 0, + .dma_channel = 0 + }, + .pattern = 1, + }; + +const audio_pwm_channel_config_t default_right_channel_config = + { + .core = { + .base_pin = PICO_AUDIO_PWM_R_PIN, + .pio_sm = 1, + .dma_channel = 1 + }, + .pattern = 1, + }; + +const audio_pwm_channel_config_t default_mono_channel_config = + { + .core = { + .base_pin = PICO_AUDIO_PWM_MONO_PIN, + .pio_sm = 0, + .dma_channel = 0 + }, + .pattern = 3, + }; + +static audio_buffer_t silence_buffer; + +static inline void audio_start_dma_transfer(int ch) +{ +#if PICO_AUDIO_PWM_NOOP + assert(false); +#else + assert(!shared_state.playing_buffer[ch]); + audio_buffer_t *ab = take_audio_buffer(shared_state.playback_buffer_pool[ch], false); + + shared_state.playing_buffer[ch] = ab; + DEBUG_PINS_SET(audio_underflow, 4); + if (!ab) + { + DEBUG_PINS_XOR(audio_underflow, 2); + // just play some silence + ab = &silence_buffer; +// static int foo; +// printf("underflow %d\n", foo++); + DEBUG_PINS_XOR(audio_underflow, 2); + } + DEBUG_PINS_CLR(audio_underflow, 4); + assert(ab->sample_count); + // todo better naming of format->format->format!! + assert(ab->format->format->format == NATIVE_BUFFER_FORMAT); + assert(ab->format->format->channel_count == 1); + assert(ab->format->sample_stride == sizeof(pwm_cmd_t)); + dma_channel_transfer_from_buffer_now(shared_state.dma_channel[ch], ab->buffer->bytes, + ab->sample_count * sizeof(pwm_cmd_t) / 4); +} + +semaphore_t sem_transfer_buffer_fill, sem_transfer_buffer_drain; +void *volatile transfer_buffer; +int32_t transfer_buffer_sample_count; + +// irq handler for DMA +static void __isr __time_critical_func(audio_pwm_dma_irq_handler)() +{ +#if PICO_AUDIO_PWM_NOOP + assert(false); +#else + // todo better DMA channel handling? (should we combine to keep channels in sync?) + // (pico_audio - sync should be maintained by source of pico_audio buffers, though we need to be able to insert + // the correct amount of silence to re-align) + for(int ch = 0; ch < shared_state.channel_count; ch++) + { + uint dma_channel = shared_state.dma_channel[ch]; + if (dma_intsx & (1u << dma_channel)) + { + dma_intsx = 1u << dma_channel; + DEBUG_PINS_SET(audio_timing, 4); + // free the buffer we just finished + if (shared_state.playing_buffer[ch]) + { + give_audio_buffer(shared_state.playback_buffer_pool[ch], shared_state.playing_buffer[ch]); +#ifndef NDEBUG + shared_state.playing_buffer[ch] = 0; +#endif + } + audio_start_dma_transfer(ch); + DEBUG_PINS_CLR(audio_timing, 4); + } + } +#endif +} + +audio_format_t pwm_consumer_format; +audio_buffer_format_t pwm_consumer_buffer_format = { + .format = &pwm_consumer_format, + .sample_stride = sizeof(pwm_cmd_t) +}; +audio_buffer_pool_t *pwm_consumer_pool; + +const audio_format_t *audio_pwm_setup(const audio_format_t *intended_audio_format, int32_t max_latency_ms, + const audio_pwm_channel_config_t *channel_config0, ...) +{ + va_list args; + + assert(max_latency_ms == -1); // not implemented yet + __builtin_memset(&shared_state, 0, sizeof(shared_state)); + // init non zero members +#if !PICO_AUDIO_PWM_NOOP + + shared_state.channel_count = intended_audio_format->channel_count; +#if !PICO_AUDIO_PWM_ENABLE_NOISE_SHAPING + pwm_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_PWM_CMD1; + pwm_consumer_format.channel_count = 1; +#else + pwm_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_PWM_CMD3; + pwm_consumer_format.channel_count = 1; +#endif +#ifndef AUDIO_HALF_FREQ + pwm_consumer_format.sample_freq = 22058; +#else + pwm_consumer_format.sample_freq = 11029; +#endif + + for(int i = 0; i < shared_state.channel_count; i++) + { + shared_state.playback_buffer_pool[i] = audio_new_consumer_pool(&pwm_consumer_buffer_format, + PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL, + PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH); + } + __mem_fence_release(); + + silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH * sizeof(silence_cmd)); + for(int i = 0; i < PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH; i++) + { + __builtin_memcpy((void *) (silence_buffer.buffer->bytes + i * sizeof(silence_cmd)), &silence_cmd, + sizeof(silence_cmd)); + } + silence_buffer.sample_count = PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH; + silence_buffer.format = &pwm_consumer_buffer_format; + + va_start(args, channel_config0); + uint offset = pio_add_program(audio_pio, &audio_program); + + const audio_pwm_channel_config_t *config = channel_config0; + + // todo should be shared, but we don't have that yet + irq_set_exclusive_handler(DMA_IRQ_x, audio_pwm_dma_irq_handler); + + for(int ch = 0; ch < shared_state.channel_count; ch++) + { + if (!config) + { + config = va_arg(args, const struct audio_pwm_channel_config *); + } + + gpio_set_function(config->core.base_pin, GPIO_FUNC_PIOx); + + uint8_t sm = shared_state.pio_sm[ch] = config->core.pio_sm; + pio_sm_claim(audio_pio, sm); + + pio_sm_config sm_config = audio_program_get_default_config(offset); + sm_config_set_out_pins(&sm_config, config->core.base_pin, 1); + sm_config_set_sideset_pins(&sm_config, config->core.base_pin); + // disable auto-pull for !OSRE (which doesn't work with auto-pull) + static_assert(CYCLES_PER_SAMPLE <= 18, ""); + sm_config_set_out_shift(&sm_config, true, false, CMD_BITS + CYCLES_PER_SAMPLE); + pio_sm_init(audio_pio, sm, offset, &sm_config); + + pio_sm_set_consecutive_pindirs(audio_pio, sm, config->core.base_pin, 1, true); + pio_sm_set_pins(audio_pio, sm, 0); + + // todo this should be part of sm_init + pio_sm_exec(audio_pio, sm, pio_encode_jmp(offset + audio_entry_point)); // jmp to ep + + uint8_t dma_channel = config->core.dma_channel; + dma_channel_claim(dma_channel); + + shared_state.dma_channel[ch] = dma_channel; + + dma_channel_config dma_config = dma_channel_get_default_config(dma_channel); + + channel_config_set_dreq(&dma_config, DREQ_PIOx_TX0 + sm); + dma_channel_configure(dma_channel, + &dma_config, + &audio_pio->txf[sm], // dest + NULL, // src + 0, // count + false // trigger + ); + dma_channel_set_irqx_enabled(dma_channel, 1); + config = 0; + } + + va_end(args); +#endif +#ifndef NDEBUG + puts("PicoAudio: initialized\n"); +#endif + pwm_consumer_pool = (shared_state.playback_buffer_pool[0]); // forcing channel 0 to be consumer for now + // todo we need to update this to what is exact + return intended_audio_format; +} + +void audio_pwm_set_enabled(bool enabled) +{ + if (enabled != audio_enabled) + { +#ifndef NDEBUG + if (enabled) + { + puts("Enabling PIO PWM audio\n"); + } +#endif +#if !PICO_AUDIO_PWM_NOOP + irq_set_enabled(DMA_IRQ_x, enabled); + + if (enabled) + { + // todo this is wrong + for(int ch = 0; ch < shared_state.channel_count; ch++) + { + audio_start_dma_transfer(ch); + } + } + + // todo need to start them in sync - need WAIT in program + for(int ch = 0; ch < shared_state.channel_count; ch++) + { + pio_sm_set_enabled(audio_pio, shared_state.pio_sm[ch], enabled); + } +#endif + + audio_enabled = enabled; + } +} + +#pragma GCC push_options +#ifdef __arm__ +// seems uber keen to inline audio_queue_samples which is large +#pragma GCC optimize("O1") +#endif + +void core1_worker() +{ + while (true) + { + sem_acquire_blocking(&sem_transfer_buffer_drain); +// audio_queue_samples(0, transfer_buffer, transfer_buffer_sample_count, 1, true); + sem_release(&sem_transfer_buffer_fill); + } + __builtin_unreachable(); +} + +#pragma GCC pop_options + +bool audio_start_queue_work_on_core_1() +{ + if (!push_queuing_to_core1) + { + puts("In the spirit of the season, core 1 is helping out too...\n"); + sem_init(&sem_transfer_buffer_drain, 0, 1); + // one fill is implicitly owned by the client application as it has a buffer + // (note the count here is actually the number of buffers the client has) + sem_init(&sem_transfer_buffer_fill, 2, 1); + multicore_launch_core1(core1_worker); + push_queuing_to_core1 = true; + } + return true; +} + +#endif + +static struct producer_pool_blocking_give_connection producer_pool_blocking_give_connection_singleton = { + .core = { + .consumer_pool_take = consumer_pool_take_buffer_default, + .consumer_pool_give = consumer_pool_give_buffer_default, + .producer_pool_take = producer_pool_take_buffer_default, + } + // rest 0 initialized +}; + +bool audio_pwm_default_connect(audio_buffer_pool_t *producer_pool, bool dedicate_core_1) +{ + if (!dedicate_core_1) + { + printf("Connecting PIO PWM audio via 'blocking give'\n"); + assert(pwm_consumer_pool); + assert(pwm_consumer_pool->format->channel_count == 1); // for now + // todo oops this is pulling in everything! + switch (producer_pool->format->format) { + case AUDIO_BUFFER_FORMAT_PCM_S16: + producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s16; + break; + case AUDIO_BUFFER_FORMAT_PCM_S8: + producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s8; + break; + case AUDIO_BUFFER_FORMAT_PCM_U16: + producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s16; + break; + case AUDIO_BUFFER_FORMAT_PCM_U8: + producer_pool_blocking_give_connection_singleton.core.producer_pool_give = producer_pool_blocking_give_to_pwm_s8; + break; + default: + return false; + } + audio_complete_connection(&producer_pool_blocking_give_connection_singleton.core, producer_pool, + pwm_consumer_pool); + return true; + } + else + { + assert(false); + } + return false; +} diff --git a/src/rp2_common/pico_audio_pwm/audio_pwm.pio b/src/rp2_common/pico_audio_pwm/audio_pwm.pio new file mode 100644 index 0000000..d3dde83 --- /dev/null +++ b/src/rp2_common/pico_audio_pwm/audio_pwm.pio @@ -0,0 +1,58 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program pwm_one_bit_dither +.side_set 1 opt +; Format: +; | high len | low len | (dither) * n | +; OSR level +; cycle length = 7 + 2 + 127 + +; 136 clocks/cycle frequency 352941 / 16 = 22058 +delay: + nop [2] +.wrap_target + out pins, 1 +loops: + mov x, isr side 1 +loop1: + jmp x-- loop1 + mov x, y side 0 +loop0: + jmp x-- loop0 + jmp !osre delay +public entry_point: + pull + out isr, 7 + out y, 7 +.wrap + +.program pwm_two_bit_dither +.side_set 1 opt +; Format: +; | high len | low len | (dither) * n | +; OSR level + +; this 138 clocks/cycle frequency 347826 / 16 = 21739Hz +delay: + nop [2] +.wrap_target + out pins, 1 + out pins, 1 + out pins, 1 +loops: + mov x, isr side 1 +loop1: + jmp x-- loop1 + mov x, y side 0 +loop0: + jmp x-- loop0 + jmp !osre delay +entry_point: + pull + out isr, 7 + out y, 7 +.wrap diff --git a/src/rp2_common/pico_audio_pwm/include/pico/audio_pwm.h b/src/rp2_common/pico_audio_pwm/include/pico/audio_pwm.h new file mode 100644 index 0000000..394b05b --- /dev/null +++ b/src/rp2_common/pico_audio_pwm/include/pico/audio_pwm.h @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef _PICO_AUDIO_PWM_H +#define _PICO_AUDIO_PWM_H + +#include "pico/audio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ====================== +// == CONFIG ============ + +#ifndef PICO_AUDIO_PWM_DMA_IRQ +#ifdef PICO_AUDIO_IRQ +#define PICO_AUDIO_PWM_DMA_IRQ PICO_AUDIO_DMA_IRQ +#else +#define PICO_AUDIO_PWM_DMA_IRQ 1 +#endif +#endif + +#ifndef PICO_AUDIO_PWM_PIO +#ifdef PICO_AUDIO_PIO +#define PICO_AUDIO_PWM_PIO PICO_AUDIO_PIO +#else +#define PICO_AUDIO_PWM_PIO 0 +#endif +#endif + +#if !(PICO_AUDIO_PWM_DMA_IRQ == 0 || PICO_AUDIO_PWM_DMA_IRQ == 1) +#error PICO_AUDIO_PWM_DMA_IRQ must be 0 or 1 +#endif + +#if !(PICO_AUDIO_PWM_PIO == 0 || PICO_AUDIO_PWM_PIO == 1) +#error PICO_AUDIO_PWM_PIO ust be 0 or 1 +#endif + +#ifndef PICO_AUDIO_PWM_MAX_CHANNELS +#ifdef PICO_AUDIO_MAX_CHANNELS +#define PICO_AUDIO_PWM_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS +#else +#define PICO_AUDIO_PWM_MAX_CHANNELS 2u +#endif +#endif + +#ifndef PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL +#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL +#define PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL +#else +#define PICO_AUDIO_PWM_BUFFERS_PER_CHANNEL 3u +#endif +#endif + +#ifndef PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH +#ifdef PICO_AUDIO_BUFFER_SAMPLE_LENGTH +#define PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH PICO_AUDIO_BUFFER_SAMPLE_LENGTH +#else +#define PICO_AUDIO_PWM_BUFFER_SAMPLE_LENGTH 576u +#endif +#endif + +#ifndef PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH +#ifdef PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH +#define PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH PICO_AUDIO_SILENCE_BUFFER_SAMPLE_LENGTH +#else +#define PICO_AUDIO_PWM_SILENCE_BUFFER_SAMPLE_LENGTH 256u +#endif +#endif + +// Enable noise shaping when super-sampling +// +// This allows for runtime selection of noise shaping or not, however having the compile +// time definition requires triple the pico_audio buffer RAM usage at runtime, and leads to marginally +// slower code in general. +#ifndef PICO_AUDIO_PWM_ENABLE_NOISE_SHAPING +#define PICO_AUDIO_PWM_ENABLE_NOISE_SHAPING 0 +#endif + +#ifndef PICO_AUDIO_PWM_L_PIN +#define PICO_AUDIO_PWM_L_PIN 0 +#endif + +#ifndef PICO_AUDIO_PWM_R_PIN +#define PICO_AUDIO_PWM_R_PIN 1 +#endif + +#ifndef PICO_AUDIO_PWM_MONO_PIN +#define PICO_AUDIO_PWM_MONO_PIN PICO_AUDIO_PWM_L_PIN +#endif + +// Allow use of pico_audio driver without actually doing anything much +#ifndef PICO_AUDIO_PWM_NOOP +#ifdef PICO_AUDIO_NOOP +#define PICO_AUDIO_PWM_NOOP PICO_AUDIO_NOOP +#else +#define PICO_AUDIO_PWM_NOOP 0 +#endif +#endif + +/** \file audio_pwm.h + * \defgroup pico_audio_pwm pico_audio_pwm + * PWM audio output (with optional noise shaping and error diffusion) using the PIO + * + * This library uses the \ref hardware_pio system to implement a PWM audio interface + * + * \todo Must be more we need to say here. + * \todo certainly need an example + * + */ + +// todo we need a place to register these or just allow them to overlap, or base them on a FOURCC - this is just made up +#define AUDIO_BUFFER_FORMAT_PIO_PWM_FIRST 1000 +#define AUDIO_BUFFER_FORMAT_PIO_PWM_CMD1 (AUDIO_BUFFER_FORMAT_PIO_PWM_FIRST) +#define AUDIO_BUFFER_FORMAT_PIO_PWM_CMD3 (AUDIO_BUFFER_FORMAT_PIO_PWM_FIRST+1) + +typedef struct __packed audio_pwm_channel_config { + pio_audio_channel_config_t core; + uint8_t pattern; +} audio_pwm_channel_config_t; + +// can copy this to modify just the pin +extern const audio_pwm_channel_config_t default_left_channel_config; +extern const audio_pwm_channel_config_t default_right_channel_config; +extern const audio_pwm_channel_config_t default_mono_channel_config; + +/*! \brief + * \ingroup pico_audio_pwm + * \todo + * + * max_latency_ms may be -1 (for don't care) + * \param intended_audio_format + * \param max_latency_ms + * \param channel_config0 + * \param ... + * \return + */ +extern const audio_format_t * +audio_pwm_setup(const audio_format_t *intended_audio_format, int32_t max_latency_ms, + const audio_pwm_channel_config_t *channel_config0, ...); + +/*! \brief + * \ingroup pico_audio_pwm + * \todo + * + * \param producer_pool + * \param dedicate_core_1 + * attempt a default mapping of producer buffers to pio pwm pico_audio output + * dedicate_core_1 to have core 1 set aside entirely to do work offloading as much stuff from the producer side as possible + * todo also allow IRQ handler to do it I guess + */ +extern bool audio_pwm_default_connect(audio_buffer_pool_t *producer_pool, bool dedicate_core_1); + +/*! \brief + * \ingroup pico_audio_pwm + * \todo + * + * \param enable true to enable the PWM audio, false to disable + */ +extern void audio_pwm_set_enabled(bool enabled); + +/*! \brief Set the PWM correction mode + * \ingroup pico_audio_pwm + * + * \param mode \todo + */ +extern bool audio_pwm_set_correction_mode(enum audio_correction_mode mode); + +/*! \brief Get the PWM correction mode + * \ingroup pico_audio_pwm + * + * \return mode + */ +extern enum audio_correction_mode audio_pwm_get_correction_mode(); + +#ifdef __cplusplus +} +#endif + +#endif //_PIO_AUDIO_PWM_H diff --git a/src/rp2_common/pico_audio_pwm/include/pico/audio_pwm/sample_encoding.h b/src/rp2_common/pico_audio_pwm/include/pico/audio_pwm/sample_encoding.h new file mode 100644 index 0000000..3254e8f --- /dev/null +++ b/src/rp2_common/pico_audio_pwm/include/pico/audio_pwm/sample_encoding.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_AUDIO_PWM_SAMPLE_ENCODING_H +#define _PICO_AUDIO_PWM_SAMPLE_ENCODING_H + +#ifdef __cplusplus +extern "C" { +#endif +// todo some if not all of this can go in sample_encoding.cpp + +#define FRACTIONAL_BITS 9u +#define QUANTIZED_BITS 7u + +static_assert(FRACTIONAL_BITS + QUANTIZED_BITS == 16, ""); + +#ifndef ENABLE_NOISE_SHAPING +const uint32_t audio_carrier_freq = 350364; +#define program_name pwm_one_bit_dither +#define NATIVE_BUFFER_FORMAT AUDIO_BUFFER_FORMAT_PIO_PWM_CMD1 +#else +#define program_name pwm_two_bit_dither +#define NATIVE_BUFFER_FORMAT AUDIO_BUFFER_FORMAT_PIO_PWM_CMD3 +#endif + +static_assert(QUANTIZED_BITS == 7, ""); // required by make_cmd below +#define MAKE_CMD(q) (((q)) | (127u - (q)) << 7u) +#define CMD_BITS (QUANTIZED_BITS * 2) +#define SILENCE_LEVEL 0x40u +#define SILENCE_CMD MAKE_CMD(SILENCE_LEVEL) + +#ifdef ENABLE_NOISE_SHAPING +#define DITHER_BITS 3u +// this needs to be divisible by dither bits +#define CYCLES_PER_SAMPLE 15 +typedef struct { + uint32_t a; + uint32_t b; + uint32_t c; +#ifdef AUDIO_HALF_FREQ + uint32_t d, e, f; +#endif +} pwm_cmd_t; // what we send to PIO for each sample +const pwm_cmd_t silence_cmd = {SILENCE_CMD, SILENCE_CMD, SILENCE_CMD, +#ifdef AUDIO_HALF_FREQ + SILENCE_CMD, SILENCE_CMD, SILENCE_CMD, +#endif +}; +#else +#define CYCLES_PER_SAMPLE 16 +#ifndef AUDIO_HALF_FREQ +typedef uint32_t pwm_cmd_t; // what we send to PIO for each sample +const pwm_cmd_t silence_cmd = SILENCE_CMD; +#else +typedef struct { + uint32_t a; + uint32_t b; +} pwm_cmd_t; // what we send to PIO for each sample +const pwm_cmd_t silence_cmd = { SILENCE_CMD, SILENCE_CMD }; +#endif +#define DITHER_BITS 1u +#endif + +static_assert(CYCLES_PER_SAMPLE % DITHER_BITS == 0, ""); +#define CYCLES_PER_WORD (CYCLES_PER_SAMPLE / DITHER_BITS) +#ifndef AUDIO_HALF_FREQ +#define OUTER_LOOP_COUNT DITHER_BITS +#else +#define OUTER_LOOP_COUNT DITHER_BITS*2 +#endif +#define FRACTIONAL_LSB 0u +#define FRACTIONAL_MSB (FRACTIONAL_LSB + FRACTIONAL_BITS - 1u) +#define FRACTIONAL_MASK ((1u << FRACTIONAL_BITS) - 1u) +#define QUANTIZED_LSB FRACTION_BITS +#define QUANTIZED_MSB (QUANTIZED_LSB + QUANTIZED_BITS - 1u) +#define QUANTIZED_MAX ((1u << QUANTIZED_BITS) - 1u) +#define QUANTIZED_MASK QUANTIZED_MAX + + +void producer_pool_blocking_give_to_pwm_s16(audio_connection_t *connection, audio_buffer_t *buffer); +void producer_pool_blocking_give_to_pwm_s8(audio_connection_t *connection, audio_buffer_t *buffer); +void producer_pool_blocking_give_to_pwm_u16(audio_connection_t *connection, audio_buffer_t *buffer); +void producer_pool_blocking_give_to_pwm_u8(audio_connection_t *connection, audio_buffer_t *buffer); + +#ifdef __cplusplus +} +#endif + +#endif //SOFTWARE_SAMPLE_ENCODING_H diff --git a/src/rp2_common/pico_audio_pwm/sample_encoding.cpp b/src/rp2_common/pico_audio_pwm/sample_encoding.cpp new file mode 100644 index 0000000..7bde16f --- /dev/null +++ b/src/rp2_common/pico_audio_pwm/sample_encoding.cpp @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/sample_conversion.h" +#include "pico/audio_pwm/sample_encoding.h" +#include "pico/audio_pwm.h" +#include "hardware/gpio.h" +#include "hardware/interp.h" + +CU_REGISTER_DEBUG_PINS(encoding) +//CU_SELECT_DEBUG_PINS(encoding) + +#ifndef PICO_AUDIO_PWM_DEFAULT_CORRECTION_MODE +#define PICO_AUDIO_PWM_DEFAULT_CORRECTION_MODE dither +#endif + +static enum audio_correction_mode audio_correction_mode = PICO_AUDIO_PWM_DEFAULT_CORRECTION_MODE; + +struct FmtPWM : public FmtDetails { +}; + +bool audio_pwm_set_correction_mode(enum audio_correction_mode mode) +{ + if (mode == none || mode == dither || mode == fixed_dither) + { + audio_correction_mode = mode; + return true; + } +#ifdef ENABLE_NOISE_SHAPING + if (mode == noise_shaped_dither) { + audio_correction_mode = mode; + return true; + } +#endif + return false; +} + +enum audio_correction_mode audio_pwm_get_correction_mode() +{ + return audio_correction_mode; +} + +template void + __no_inline_not_in_flash_func(encode_samples_none)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded) +{ + const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count; + // hacky cast to allow use to DITHER_BITS > 1 + uint32_t *e = (uint32_t *) encoded; + +#if !PIO_AUDIO_PWM_NO_INTERP_SAVE + interp_hw_save_t saver; + interp_save(interp0, &saver); +#endif + +// interp_configure_with_signed(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_BITS - 1); + interp_config config = interp_default_config(); + interp_config_set_signed(&config, true); + interp_config_set_shift(&config, FRACTIONAL_BITS); + interp_config_set_mask(&config, 0, QUANTIZED_BITS - 1); + interp_set_config(interp0, 0, &config); + interp0->base[0] = 0x8000u >> FRACTIONAL_BITS; + + while (s < s_end) + { + // accum = signed_sample_16 + interp0->accum[0] = sample_converter::convert_sample(*s); + // quant = ((0x8000 + signed_sample_16) >> FRACTIONAL_BITS) & QUANTIZED_MASK + uint32_t quant = interp0->pop[0]; + + assert(quant >= 0 && quant <= 127); + uint32_t cmd = MAKE_CMD(quant); + for(uint k = 0; k < OUTER_LOOP_COUNT; k++) + { + *e++ = cmd; + } + s += FromFmt::channel_count; + } + +#if !PIO_AUDIO_PWM_NO_INTERP_SAVE + interp_restore(interp0, &saver); +#endif +} + +//void gen_fixed_dither() +//{ +// for(int i = 0; i < 16; i++) +// { +// printf("0b"); +// int e = 0; +// for(int j = 0; j < 15; j++) +// { +// e += i; +// if (e >= 16) +// { +// printf("1"); +// e -= 16; +// } +// else +// { +// printf("0"); +// } +// } +// printf("\n"); +// } +// +// for(int i = 0; i < 16; i++) +// { +// printf("0b"); +// int e = 0; +// for(int j = 0; j < 15; j++) +// { +// e += i; +// if (e >= 16) +// { +// printf("100"); +// e -= 16; +// } +// else +// { +// printf("000"); +// } +// if (j == 4 || j == 9) +// { +// printf(", 0b"); +// } +// } +// printf("\n"); +// } + +#if DITHER_BITS == 3 +#define FIXED_DITHER_SHIFT 2 +// note 4 not 3 which wastes 16 bytes, but allows interpolator to handle address +static uint32_t fixed_dither_table[16*(1< void +__no_inline_not_in_flash_func(encode_samples_fixed_dither)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded) +{ + const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count; + // hacky cast to allow use to DITHER_BITS > 1 + uint32_t *e = (uint32_t *) encoded; + +#if !PIO_AUDIO_PWM_NO_INTERP_SAVE + interp_hw_save_t saver; + interp_save(interp0, &saver); +#endif + + //interp_configure_with_signed(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_BITS - 1); + interp_config config = interp_default_config(); + interp_config_set_shift(&config, FRACTIONAL_BITS); + interp_config_set_mask(&config, 0, QUANTIZED_BITS - 1); + interp_config_set_signed(&config, true); + interp_set_config(interp0, 0, &config); + +// interp_configure_with_cross_input(interp0, 1, FRACTIONAL_BITS - FIXED_DITHER_SHIFT - 6, FIXED_DITHER_SHIFT + 2, FIXED_DITHER_SHIFT + 5); + config = interp_default_config(); + interp_config_set_shift(&config, FRACTIONAL_BITS - FIXED_DITHER_SHIFT - 6); + interp_config_set_mask(&config, FIXED_DITHER_SHIFT + 2, FIXED_DITHER_SHIFT + 5); + interp_config_set_cross_input(&config, true); + interp_set_config(interp0, 1, &config); + + interp0->base[0] = 0x8000u >> FRACTIONAL_BITS; + interp0->base[1] = (uintptr_t)fixed_dither_table; + + static int16_t error = 0; + while (s < s_end) + { + // accum = signed_sample_16 + interp0->accum[0] = sample_converter::convert_sample(*s) + error; + uint32_t *fdt = (uint32_t *)interp0->peek[1]; + uint32_t quant = interp0->pop[0]; + + assert(quant >= 0 && quant <= 127); + uint32_t cmd = MAKE_CMD(quant); + for(uint k = 0; k < OUTER_LOOP_COUNT; k++) + { + *e++ = cmd | fdt[k]; + } + s += FromFmt::channel_count; + } + +#if !PIO_AUDIO_PWM_NO_INTERP_SAVE + interp_restore(interp0, &saver); +#endif +} + +template void + __no_inline_not_in_flash_func(encode_samples_dither)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded) +{ + static_assert(DITHER_BITS > 0 && DITHER_BITS <= 3, ""); + const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count; + // hacky cast to allow us to DITHER_BITS > 1 + uint32_t *e = (uint32_t *) encoded; + +#if PICO_AUDIO_PWM_INTERP_SAVE + interp_hw_save_t saver; + interp_save(interp0, &saver); +#endif + +// interp_configure_with_signed_and_cross_result(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_MASK - 1); + interp_config config = interp_default_config(); + interp_config_set_shift(&config, FRACTIONAL_BITS); + interp_config_set_mask(&config, 0, QUANTIZED_BITS - 1); + interp_config_set_signed(&config, true); + interp_config_set_cross_result(&config, true); + interp_set_config(interp0, 0, &config); +// interp_configure_with_cross_input(interp0, 1, 0, 0, FRACTIONAL_BITS - 1); + config = interp_default_config(); + interp_config_set_mask(&config, 0, FRACTIONAL_BITS - 1); + interp_config_set_cross_input(&config, true); + interp_set_config(interp0, 1, &config); + + interp0->base[0] = 0; + + int32_t last_sample_error = 0; + static uint32_t saved_error = 0; + + // accum 0 is the error + interp0->accum[0] = saved_error; + + while (s < s_end) + { + uint32_t sample = sample_converter::convert_sample(*s); + + // we will be adding this sample error to accumulated error each time in the super sample loop + uint32_t sample_error = sample & FRACTIONAL_MASK; + interp0->base[1] = sample_error; + + // adjust the accumulated_error from (last_sample_error + error) to (sample_error + error) + // because the error is one cycle ahead of the loop (i.e. we need to have added the sample error once before + // the first loop) + interp0->add_raw[0] = sample_error - last_sample_error; + last_sample_error = sample_error; + + uint32_t quant0 = + (sample >> FRACTIONAL_BITS) & QUANTIZED_MASK; // can't use interp here since accumulator has error in it + assert(quant0 >= 0 && quant0 <= QUANTIZED_MAX); + for(uint k = 0; k < OUTER_LOOP_COUNT; k++) + { + uint32_t cmd = MAKE_CMD(quant0); + uint32_t bit = CMD_BITS + DITHER_BITS - 1; + + for(uint j = 0; j < CYCLES_PER_WORD; j++) + { + // accumulated_error (accum[0]) = previous_accumulated_error + sample_error - note this was done ahead of this iteration + // quant (result[0]) = (accumulated_error) >> FRACTIONAL_BITS) & QUANTIZED_MASK; + // accumulated_error (result[1]->accum[0]) = (previous_accumulated_error + sample_error) & FRACTIONAL_MASK; + uint32_t quant = interp0->pop[0]; + // we can only dither 0 or +1 + assert(quant == 0 || quant == 1); + if (!!quant) + cmd |= 1 << bit; + bit += DITHER_BITS; + } + *e++ = cmd; + } + s += FromFmt::channel_count; + } + saved_error = interp0->accum[0] - last_sample_error; + +#if PICO_AUDIO_PWM_INTERP_SAVE + interp_restore(interp0, &saver); +#endif +} + +#ifdef ENABLE_NOISE_SHAPING +static uint8_t shape_bits[4] = { 0b000, 0b100, 0b110, 0b111 }; + +template void + __no_inline_not_in_flash_func(encode_samples_noise_shaped_dither)(int s_count, const typename FromFmt::sample_t *s, pwm_cmd_t *encoded) { + + static_assert(DITHER_BITS <= 3, ""); + const typename FromFmt::sample_t *s_end = s + s_count * FromFmt::channel_count; + // hacky cast to allow us to DITHER_BITS > 1 + uint32_t * e = (uint32_t *)encoded; +#if PICO_AUDIO_PWM_INTERP_SAVE + interp_hw_save_t saver; + interp_save(interp0, &saver); +#endif + + interp_configure_with_signed_and_cross_result(interp0, 0, FRACTIONAL_BITS, 0, QUANTIZED_BITS - 1); + interp_configure_with_cross_input(interp0, 1, 0, 0, FRACTIONAL_BITS - 1); + + // we offset one to the quantized result because the error is between -1 and 2, giving us a range of 0 to 3 + // todo i keep being tempted to make this zero and remove the -1 offset from quant0 but this does not work + // so i need to comment why... I believe this corrects something we later double to be zero based not -1 based + interp0->base[0] = 1; + + int32_t last_sample_error = 0; + static uint32_t saved_error = 0, previous_accumulated_error = 0; + interp0->accum[0] = saved_error; + + while (s < s_end) + { + uint32_t sample = sample_converter, FromFmt>::convert_sample(s); + + // we will be adding this sample error to accumulated error each time in the super sample loop + uint32_t sample_error = sample & FRACTIONAL_MASK; + interp0->base[1] = sample_error; + + // adjust the accumulated_error from (last_sample_error + error) to (sample_error + error) + // because the error is one cycle ahead of the loop (i.e. we need to have added the sample error once before + // the first loop) + interp0->add_raw[0] = sample_error - last_sample_error; + last_sample_error = sample_error; + + uint32_t quant0 = ((sample >> FRACTIONAL_BITS) - 1u) & QUANTIZED_MASK; // can't use interp here since accumulator has error in it + // todo clearly this could be a problem for too high a volume! + assert(quant0 >= 0 && quant0 <= QUANTIZED_MAX); + for(uint k=0; k < OUTER_LOOP_COUNT; k++) { + uint32_t cmd = MAKE_CMD(quant0); + uint base_bit = CMD_BITS; + for (uint j = 0; j < CYCLES_PER_WORD; j++) { + // accumulated_error (accum[0]) = previous_accumulated_error + sample_error - note this was done ahead of this iteration + uint32_t accumulated_error = interp0->add_raw[1]; + // quant (result[0]) = 1 + (accumulated_error) >> FRACTIONAL_BITS) & QUANTIZED_MASK; + // accumulated_error (result[1]->accum[0]) = (previous_accumulated_error + sample_error) & FRACTIONAL_MASK; + uint32_t quant = interp0->pop[0]; + // accumulated_error += accumulated_error - previous_accumulated_error + // i.e. accumulated_error = ((previous_accumulated_error + sample_error) & FRACTIONAL_MASK) * 2 - previous_accumulated_error + // which is the noise shaping + interp0->add_raw[0] = accumulated_error - previous_accumulated_error; + previous_accumulated_error = accumulated_error; + + assert (quant >=0 && quant <= 3); + cmd |= shape_bits[quant] << base_bit; + base_bit += DITHER_BITS; + } + *e++ = cmd; + } + s += FromFmt::channel_count; + } + saved_error = interp0->accum[0] - last_sample_error; + +#if PICO_AUDIO_PWM_INTERP_SAVE + interp_restore(interp0, &saver); +#endif +} +#endif + +// encoding converter +template struct converting_copy { + static void copy(typename FmtPWM::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) { + DEBUG_PINS_SET(encoding, 1); + switch (audio_correction_mode) + { + case dither: + encode_samples_dither(sample_count, src, dest); + break; + case fixed_dither: + encode_samples_fixed_dither(sample_count, src, dest); + break; +#ifdef ENABLE_NOISE_SHAPING + case noise_shaped_dither: + encode_samples_noise_shaped_dither(sample_count, src, dest); + break; +#endif + default: + encode_samples_none(sample_count, src, dest); + break; + } + DEBUG_PINS_CLR(encoding, 1); + } +}; + +void producer_pool_blocking_give_to_pwm_s16(audio_connection_t *connection, audio_buffer_t *buffer) +{ + producer_pool_blocking_give(connection, buffer); +} + +void producer_pool_blocking_give_to_pwm_s8(audio_connection_t *connection, audio_buffer_t *buffer) +{ + producer_pool_blocking_give(connection, buffer); +} + +void producer_pool_blocking_give_to_pwm_u16(audio_connection_t *connection, audio_buffer_t *buffer) +{ + producer_pool_blocking_give(connection, buffer); +} + +void producer_pool_blocking_give_to_pwm_u8(audio_connection_t *connection, audio_buffer_t *buffer) +{ + producer_pool_blocking_give(connection, buffer); +} \ No newline at end of file diff --git a/src/rp2_common/pico_audio_spdif/CMakeLists.txt b/src/rp2_common/pico_audio_spdif/CMakeLists.txt new file mode 100644 index 0000000..c92ded4 --- /dev/null +++ b/src/rp2_common/pico_audio_spdif/CMakeLists.txt @@ -0,0 +1,13 @@ +if (NOT TARGET pico_audio_spdif) + add_library(pico_audio_spdif INTERFACE) + + pico_generate_pio_header(pico_audio_spdif ${CMAKE_CURRENT_LIST_DIR}/audio_spdif.pio) + + target_sources(pico_audio_spdif INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/audio_spdif.c + ${CMAKE_CURRENT_LIST_DIR}/sample_encoding.cpp + ) + + target_include_directories(pico_audio_spdif INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + target_link_libraries(pico_audio_spdif INTERFACE hardware_dma hardware_pio hardware_irq pico_audio) +endif() \ No newline at end of file diff --git a/src/rp2_common/pico_audio_spdif/audio_spdif.c b/src/rp2_common/pico_audio_spdif/audio_spdif.c new file mode 100644 index 0000000..afbef3d --- /dev/null +++ b/src/rp2_common/pico_audio_spdif/audio_spdif.c @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include "pico/audio_spdif.h" +#include +#include "audio_spdif.pio.h" +#include "hardware/pio.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/clocks.h" + + +CU_REGISTER_DEBUG_PINS(audio_timing) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(audio_timing) + + +#define audio_pio __CONCAT(pio, PICO_AUDIO_SPDIF_PIO) +#define GPIO_FUNC_PIOx __CONCAT(GPIO_FUNC_PIO, PICO_AUDIO_SPDIF_PIO) +#define DREQ_PIOx_TX0 __CONCAT(__CONCAT(DREQ_PIO, PICO_AUDIO_SPDIF_PIO), _TX0) + +#define dma_intsx __CONCAT(dma_hw->ints, PICO_AUDIO_SPDIF_DMA_IRQ) +#define dma_channel_set_irqx_enabled __CONCAT(__CONCAT(dma_channel_set_irq, PICO_AUDIO_SPDIF_DMA_IRQ),_enabled) +#define DMA_IRQ_x __CONCAT(DMA_IRQ_, PICO_AUDIO_SPDIF_DMA_IRQ) + +struct { + audio_buffer_t *playing_buffer; + uint32_t freq; + uint8_t pio_sm; + uint8_t dma_channel; +} shared_state; + +static audio_format_t pio_spdif_consumer_format; +audio_buffer_format_t pio_spdif_consumer_buffer_format = { + .format = &pio_spdif_consumer_format, +}; + +static audio_buffer_t silence_buffer = { + .sample_count = PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT, + .max_sample_count = PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT, + .format = &pio_spdif_consumer_buffer_format +}; + +static void __isr __time_critical_func(audio_spdif_dma_irq_handler)(); + +const audio_spdif_config_t audio_spdif_default_config = { + .pin = PICO_AUDIO_SPDIF_PIN, + .pio_sm = 0, + .dma_channel = 0, +}; + +#define SR_44100 0 +#define SR_48000 1 + +#define PREAMBLE_X 0b11001001 +#define PREAMBLE_Y 0b01101001 +#define PREAMBLE_Z 0b00111001 + +#define SPDIF_CONTROL_WORD (\ + 0x4 | /* copying allowed */ \ + 0x20 | /* PCM encoder/decoder */ \ + (SR_44100 << 24) /* todo is this required */ \ + ) + +// each buffer is pre-filled with data +static void init_spdif_buffer(audio_buffer_t *buffer) { + // BIT DESCRIPTIONS: + // 0–3 Preamble A synchronisation preamble (biphase mark code violation) for audio blocks, frames, and subframes. + // 4–7 Auxiliary sample (optional) A low-quality auxiliary channel used as specified in the channel status word, notably for producer talkback or recording studio-to-studio communication. + // 8–27, or 4–27 Audio sample. One sample stored with most significant bit (MSB) last. If the auxiliary sample is used, bits 4–7 are not included. Data with smaller sample bit depths always have MSB at bit 27 and are zero-extended towards the least significant bit (LSB). + // 28 Validity (V) Unset if the audio data are correct and suitable for D/A conversion. During the presence of defective samples, the receiving equipment may be instructed to mute its output. It is used by most CD players to indicate that concealment rather than error correction is taking place. + // 29 User data (U) Forms a serial data stream for each channel (with 1 bit per frame), with a format specified in the channel status word. + // 30 Channel status (C) Bits from each frame of an audio block are collated giving a 192-bit channel status word. Its structure depends on whether AES3 or S/PDIF is used. + // 31 Parity (P) + + // We want to pre-encode (in our fixed length 192 buffers), the + // * Preamble + // * Aux (0) + // * Low4 (0) + // + // * V(0) + // * U(0) + // * C(0) (or from SPDIF_CONTROL_WORD in the first 32) + + // note everything is encoded in NRZI + // regular data bits are encoded + // 0 -> 10 (LSB first) + // 1 -> 11 + + assert(buffer->max_sample_count == PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT); + spdif_subframe_t *p = (spdif_subframe_t *)buffer->buffer->bytes; + for(uint i=0;i> i) & 1u: 0; +// p->l = (i ? PREAMBLE_X : PREAMBLE_Z) | 0b10101010101010100000000 | 0x55000000; +// p->h = 0x55000000u | (c_bit << 25u) | 0x0055555555; +// p++; +// p->l = PREAMBLE_Y | 0b10101010101010100000000 | 0x55000000; +// p->h = 0x55000000u | (c_bit << 25u) | 0x0055555555; +// p++; + + p->l = (i ? PREAMBLE_X : PREAMBLE_Z) | 0b10101010101010100000000; + p->h = 0x55000000u | (c_bit << 25u); + p++; + p->l = PREAMBLE_Y | 0b10101010101010100000000; + p->h = 0x55000000u | (c_bit << 25u); + p++; + + } +} + +uint32_t spdif_lookup[256]; + +const audio_format_t *audio_spdif_setup(const audio_format_t *intended_audio_format, + const audio_spdif_config_t *config) { + for(uint i=0;i<256;i++) { + uint32_t v = 0x5555; + uint p = 0; + for(uint j = 0; j<8; j++) { + if (i & (1<pin, func); + + uint8_t sm = shared_state.pio_sm = config->pio_sm; + pio_sm_claim(audio_pio, sm); + + uint offset = pio_add_program(audio_pio, &audio_spdif_program); + + spdif_program_init(audio_pio, sm, offset, config->pin); + + silence_buffer.buffer = pico_buffer_alloc(PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT * 2 * sizeof(spdif_subframe_t)); + init_spdif_buffer(&silence_buffer); + spdif_subframe_t *sf = (spdif_subframe_t *)silence_buffer.buffer->bytes; + for(uint i=0;idma_channel; + dma_channel_claim(dma_channel); + + shared_state.dma_channel = dma_channel; + + + dma_channel_config dma_config = dma_channel_get_default_config(dma_channel); + + channel_config_set_dreq(&dma_config, + DREQ_PIOx_TX0 + sm + ); + dma_channel_configure(dma_channel, + &dma_config, + &audio_pio->txf[sm], // dest + NULL, // src + 0, // count + false // trigger + ); + + irq_add_shared_handler(DMA_IRQ_x, audio_spdif_dma_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + dma_channel_set_irqx_enabled(dma_channel, 1); + return intended_audio_format; +} + +static audio_buffer_pool_t *audio_spdif_consumer; + +static void update_pio_frequency(uint32_t sample_freq) { + printf("setting pio freq %d\n", (int) sample_freq); + uint32_t system_clock_frequency = clock_get_hz(clk_sys); + assert(system_clock_frequency < 0x40000000); + // coincidentally * 256 (for 8 bit fraction) / 2 (channels) * 32 (bits) * 2 (time periods) * 2 cycles per time period) + uint32_t divider = system_clock_frequency / sample_freq; + printf("System clock at %u, S/PDIF clock divider 0x%x/256\n", (uint) system_clock_frequency, (uint)divider); + assert(divider < 0x1000000); + pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu); + shared_state.freq = sample_freq; +} + +static audio_buffer_t *wrap_consumer_take(audio_connection_t *connection, bool block) { + // support dynamic frequency shifting + if (connection->producer_pool->format->sample_freq != shared_state.freq) { + update_pio_frequency(connection->producer_pool->format->sample_freq); + } + return consumer_pool_take_buffer_default(connection, block); +} + +static void wrap_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) { + if (buffer->format->format->format == AUDIO_BUFFER_FORMAT_PCM_S16) { +#if PICO_AUDIO_SPDIF_MONO_INPUT + + mono_to_spdif_producer_give(connection, buffer); +#else + stereo_to_spdif_producer_gibe(connection, buffer); +#endif + } else { + panic_unsupported(); + } +} + +static struct producer_pool_blocking_give_connection m2s_audio_spdif_connection = { + .core = { + .consumer_pool_take = wrap_consumer_take, + .consumer_pool_give = consumer_pool_give_buffer_default, + .producer_pool_take = producer_pool_take_buffer_default, + .producer_pool_give = wrap_producer_give, + } +}; + +bool audio_spdif_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection) { + return audio_spdif_connect_extra(producer, true, 2, connection); +} + +bool audio_spdif_connect(audio_buffer_pool_t *producer) { + return audio_spdif_connect_thru(producer, NULL); +} + +bool audio_spdif_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, + audio_connection_t *connection) { + printf("Connecting PIO S/PDIF audio\n"); + + assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S16); + pio_spdif_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_SPDIF; + pio_spdif_consumer_format.sample_freq = producer->format->sample_freq; + pio_spdif_consumer_format.channel_count = 2; + pio_spdif_consumer_buffer_format.sample_stride = 2 * sizeof(spdif_subframe_t); + + audio_spdif_consumer = audio_new_consumer_pool(&pio_spdif_consumer_buffer_format, buffer_count, PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT); + for (audio_buffer_t *buffer = audio_spdif_consumer->free_list; buffer; buffer = buffer->next) { + init_spdif_buffer(buffer); + } + + update_pio_frequency(producer->format->sample_freq); + + // todo cleanup threading + __mem_fence_release(); + + if (!connection) { + if (producer->format->channel_count == 2) { +#if PICO_AUDIO_SPDIF_MONO_INPUT + panic("need to merge channels down\n"); +#else + printf("Copying stereo to stereo at %d Hz\n", (int) producer->format->sample_freq); +#endif + // todo we should support pass thru option anyway + printf("TODO... not completing stereo audio connection properly!\n"); + } else { + printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq); + } + connection = &m2s_audio_spdif_connection.core; + } + audio_complete_connection(connection, producer, audio_spdif_consumer); + return true; +} + +static struct buffer_copying_on_consumer_take_connection m2s_audio_spdif_connection_s8 = { + .core = { + .consumer_pool_take = wrap_consumer_take, + .consumer_pool_give = consumer_pool_give_buffer_default, + .producer_pool_take = producer_pool_take_buffer_default, + .producer_pool_give = wrap_producer_give, + } +}; + +bool audio_spdif_connect_s8(audio_buffer_pool_t *producer) { + panic_unsupported(); // needs fixing up + printf("Connecting PIO S/PDIF audio (U8)\n"); + + // todo we need to pick a connection based on the frequency - e.g. 22050 can be more simply upsampled to 44100 + assert(producer->format->format == AUDIO_BUFFER_FORMAT_PCM_S8); + pio_spdif_consumer_format.format = AUDIO_BUFFER_FORMAT_PIO_SPDIF; + // todo we could do mono + // todo we can't match exact, so we should return what we can do + pio_spdif_consumer_format.sample_freq = producer->format->sample_freq; + pio_spdif_consumer_format.channel_count = 2; + pio_spdif_consumer_buffer_format.sample_stride = 2 * sizeof(spdif_subframe_t); + + // we do this on take so should do it quickly... + uint samples_per_buffer = 256; + // todo with take we really only need 1 buffer + audio_spdif_consumer = audio_new_consumer_pool(&pio_spdif_consumer_buffer_format, 2, samples_per_buffer); + // todo we need a method to calculate this in clocks + uint32_t system_clock_frequency = 48000000; +// uint32_t divider = system_clock_frequency * 256 / producer->format->sample_freq * 16 * 4; + uint32_t divider = system_clock_frequency * 4 / producer->format->sample_freq; // avoid arithmetic overflow + pio_sm_set_clkdiv_int_frac(audio_pio, shared_state.pio_sm, divider >> 8u, divider & 0xffu); + + // todo cleanup threading + __mem_fence_release(); + + audio_connection_t *connection; + if (producer->format->channel_count == 2) { + // todo we should support pass thru option anyway + printf("TODO... not completing stereo audio connection properly!\n"); + connection = &m2s_audio_spdif_connection_s8.core; + } else { + printf("Converting mono to stereo at %d Hz\n", (int) producer->format->sample_freq); + connection = &m2s_audio_spdif_connection_s8.core; + } + audio_complete_connection(connection, producer, audio_spdif_consumer); + return true; +} + +static inline void audio_start_dma_transfer() { + assert(!shared_state.playing_buffer); + audio_buffer_t *ab = take_audio_buffer(audio_spdif_consumer, false); + + shared_state.playing_buffer = ab; + if (!ab) { + DEBUG_PINS_XOR(audio_timing, 1); + DEBUG_PINS_XOR(audio_timing, 2); + DEBUG_PINS_XOR(audio_timing, 1); + //DEBUG_PINS_XOR(audio_timing, 2); + // just play some silence + ab = &silence_buffer; + } + assert(ab->sample_count); + // todo better naming of format->format->format!! + assert(ab->format->format->format == AUDIO_BUFFER_FORMAT_PIO_SPDIF); + assert(ab->format->format->channel_count == 2); + assert(ab->format->sample_stride == 2 * sizeof(spdif_subframe_t)); + dma_channel_transfer_from_buffer_now(shared_state.dma_channel, ab->buffer->bytes, ab->sample_count * 4); +} + +// irq handler for DMA +void __isr __time_critical_func(audio_spdif_dma_irq_handler)() { +#if PICO_AUDIO_SPDIF_NOOP + assert(false); +#else + uint dma_channel = shared_state.dma_channel; + if (dma_intsx & (1u << dma_channel)) { + dma_intsx = 1u << dma_channel; + DEBUG_PINS_SET(audio_timing, 4); + // free the buffer we just finished + if (shared_state.playing_buffer) { + give_audio_buffer(audio_spdif_consumer, shared_state.playing_buffer); +#ifndef NDEBUG + shared_state.playing_buffer = NULL; +#endif + } + audio_start_dma_transfer(); + DEBUG_PINS_CLR(audio_timing, 4); + } +#endif +} + +static bool audio_enabled; + +void audio_spdif_set_enabled(bool enabled) { + if (enabled != audio_enabled) { +#ifndef NDEBUG + if (enabled) + { + puts("Enabling PIO S/PDIF audio\n"); + printf("(on core %d\n", get_core_num()); + } +#endif + irq_set_enabled(DMA_IRQ_x, enabled); + + if (enabled) { + audio_start_dma_transfer(); + } + + pio_sm_set_enabled(audio_pio, shared_state.pio_sm, enabled); + + audio_enabled = enabled; + } +} + diff --git a/src/rp2_common/pico_audio_spdif/audio_spdif.pio b/src/rp2_common/pico_audio_spdif/audio_spdif.pio new file mode 100644 index 0000000..2f68fa6 --- /dev/null +++ b/src/rp2_common/pico_audio_spdif/audio_spdif.pio @@ -0,0 +1,28 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +// Strictly this is NRZI decoder +.program audio_spdif +.side_set 1 +public output_low: + out x, 1 side 0 + jmp !x, output_low side 0 +output_high: + out x, 1 side 1 + jmp !x, output_high side 1 + +% c-sdk { +void spdif_program_init(PIO pio, uint sm, uint offset, uint pin) { + pio_sm_config sm_config = audio_spdif_program_get_default_config(offset); + sm_config_set_out_shift(&sm_config, true, true, 32); + sm_config_set_sideset(&sm_config, 1, false, false); + sm_config_set_sideset_pins(&sm_config, pin); + pio_sm_init(pio, sm, offset, &sm_config); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_set_pins(pio, sm, 0); + pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_spdif_offset_output_low)); +} +%} \ No newline at end of file diff --git a/src/rp2_common/pico_audio_spdif/include/pico/audio_spdif.h b/src/rp2_common/pico_audio_spdif/include/pico/audio_spdif.h new file mode 100644 index 0000000..4576235 --- /dev/null +++ b/src/rp2_common/pico_audio_spdif/include/pico/audio_spdif.h @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_AUDIO_SPDIF_H +#define _PICO_AUDIO_SPDIF_H + +#include "pico/audio.h" + +/** \file audio_spdif.h + * \defgroup pico_audio_spdif pico_audio_spdif + * S/PDIF audio output using the PIO + * + * This library uses the \ref pio system to implement a S/PDIF audio interface + * + * \todo Must be more we need to say here. + * \todo certainly need an example + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PICO_AUDIO_SPDIF_DMA_IRQ +#ifdef PICO_AUDIO_DMA_IRQ +#define PICO_AUDIO_SPDIF_DMA_IRQ PICO_AUDIO_DMA_IRQ +#else +#define PICO_AUDIO_SPDIF_DMA_IRQ 0 +#endif +#endif + +#ifndef PICO_AUDIO_SPDIF_PIO +#ifdef PICO_AUDIO_PIO +#define PICO_AUDIO_SPDIF_PIO PICO_AUDIO_PIO +#else +#define PICO_AUDIO_SPDIF_PIO 0 +#endif +#endif + +#if !(PICO_AUDIO_SPDIF_DMA_IRQ == 0 || PICO_AUDIO_SPDIF_DMA_IRQ == 1) +#error PICO_AUDIO_SPDIF_DMA_IRQ must be 0 or 1 +#endif + +#if !(PICO_AUDIO_SPDIF_PIO == 0 || PICO_AUDIO_SPDIF_PIO == 1) +#error PICO_AUDIO_SPDIF_PIO ust be 0 or 1 +#endif + +#ifndef PICO_AUDIO_SPDIF_MAX_CHANNELS +#ifdef PICO_AUDIO_MAX_CHANNELS +#define PICO_AUDIO_SPDIF_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS +#else +#define PICO_AUDIO_SPDIF_MAX_CHANNELS 2u +#endif +#endif + +#ifndef PICO_AUDIO_SPDIF_BUFFERS_PER_CHANNEL +#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL +#define PICO_AUDIO_SPDIF_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL +#else +#define PICO_AUDIO_SPDIF_BUFFERS_PER_CHANNEL 3u +#endif +#endif + +// fixed by S/PDIF +#define PICO_AUDIO_SPDIF_BLOCK_SAMPLE_COUNT 192u + +// Allow use of pico_audio driver without actually doing anything much +#ifndef PICO_AUDIO_SPDIF_NOOP +#ifdef PICO_AUDIO_NOOP +#define PICO_AUDIO_SPDIF_NOOP PICO_AUDIO_NOOP +#else +#define PICO_AUDIO_SPDIF_NOOP 0 +#endif +#endif + +#ifndef PICO_AUDIO_SPDIF_MONO_INPUT +#define PICO_AUDIO_SPDIF_MONO_INPUT 0 +#endif + +#ifndef PICO_AUDIO_SPDIF_PIN +//#warning PICO_AUDIO_SPDIF_PIN should be defined when using AUDIO_SPDIF +#define PICO_AUDIO_SPDIF_PIN 0 +#endif + +#define AUDIO_BUFFER_FORMAT_PIO_SPDIF 1300 + +// todo this needs to come from a build config +/** \brief Base configuration structure used when setting up + * \ingroup audio_spdif + */ +typedef struct audio_spdif_config { + uint8_t pin; + uint8_t dma_channel; + uint8_t pio_sm; +} audio_spdif_config_t; + +extern const audio_spdif_config_t audio_spdif_default_config; + +/** \brief Set up system to output S/PDIF audio + * \ingroup audio_spdif + * + * \param intended_audio_format \todo + * \param config The configuration to apply. + */ +const audio_format_t *audio_spdif_setup(const audio_format_t *intended_audio_format, + const audio_spdif_config_t *config); + + +/** \brief \todo + * \ingroup audio_spdif + * + * \param producer + * \param connection + */ +bool audio_spdif_connect_thru(audio_buffer_pool_t *producer, audio_connection_t *connection); + + +/** \brief \todo + * \ingroup audio_spdif + * + * \param producer + */ +bool audio_spdif_connect(audio_buffer_pool_t *producer); + + +/** \brief \todo + * \ingroup audio_spdif + * + * \param producer + */ +bool audio_spdif_connect_s8(audio_buffer_pool_t *producer); +bool audio_spdif_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, + audio_connection_t *connection); + +/** \brief \todo + * \ingroup audio_spdif + * + * \param producer + * \param buffer_on_give + * \param buffer_count + * \param samples_per_buffer + * \param connection + * \return + */ +bool audio_spdif_connect_extra(audio_buffer_pool_t *producer, bool buffer_on_give, uint buffer_count, + audio_connection_t *connection); + + +/** \brief Set up system to output S/PDIF audio + * \ingroup audio_spdif + * + * \param enabled true to enable S/PDIF audio, false to disable. + */ +void audio_spdif_set_enabled(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif //_AUDIO_SPDIF_H diff --git a/src/rp2_common/pico_audio_spdif/include/pico/audio_spdif/sample_encoding.h b/src/rp2_common/pico_audio_spdif/include/pico/audio_spdif/sample_encoding.h new file mode 100644 index 0000000..77a1ded --- /dev/null +++ b/src/rp2_common/pico_audio_spdif/include/pico/audio_spdif/sample_encoding.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_AUDIO_SPDIF_SAMPLE_ENCODING_H +#define _PICO_AUDIO_SPDIF_SAMPLE_ENCODING_H + +#include "pico/audio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void mono_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer); +void stereo_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer); + +typedef struct { + uint32_t l; + uint32_t h; +} spdif_subframe_t; + +extern uint32_t spdif_lookup[256]; + +static inline void spdif_update_subframe(spdif_subframe_t *subframe, int16_t sample) { + // the subframe is partially initialized, so we need to insert the sample + // bits and update the parity + uint32_t sl = spdif_lookup[(uint8_t)sample]; + uint32_t sh = spdif_lookup[(uint8_t)(sample>>8u)]; + subframe->l = (subframe->l & 0xffffffu) | (sl << 24u); + uint32_t ph = subframe->h >> 24u; + uint32_t h = (((uint16_t)sh) << 8u) | + (((uint16_t)sl) >> 8u); + uint32_t p = (sl>>16u)^(sh>>16u); + p = p ^ ((__mul_instruction(ph&0x2a,0x2a) >> 6u) & 1u); + subframe->h = h | ((ph&0x7f) << 24u) | (p << 31u); +} + +#ifdef __cplusplus +} +#endif + +#endif //SOFTWARE_SAMPLE_ENCODING_H diff --git a/src/rp2_common/pico_audio_spdif/sample_encoding.cpp b/src/rp2_common/pico_audio_spdif/sample_encoding.cpp new file mode 100644 index 0000000..4a1c070 --- /dev/null +++ b/src/rp2_common/pico_audio_spdif/sample_encoding.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico/sample_conversion.h" +#include "pico/audio_spdif/sample_encoding.h" +#include "pico/audio_spdif.h" +#include "hardware/gpio.h" + +static_assert(8 == sizeof(spdif_subframe_t), ""); + +// subframe within SPDIF +typedef struct : public FmtDetails { +} FmtSPDIF; + +template +struct converting_copy, Stereo> { + static void copy(FmtSPDIF::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) { + for (uint i = 0; i < sample_count * 2; i++) { + spdif_update_subframe(dest++, sample_converter::convert_sample(*src++)); + } + } +}; + +template +struct converting_copy, Mono> { + static void copy(FmtSPDIF::sample_t *dest, const typename FromFmt::sample_t *src, uint sample_count) { + for (uint i = 0; i < sample_count; i++) { + int16_t sample = sample_converter::convert_sample(*src++); + spdif_update_subframe(dest++, sample); + spdif_update_subframe(dest++, sample); + } + } +}; + + +void stereo_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) { + producer_pool_blocking_give, Stereo>(connection, buffer); +} + +void mono_to_spdif_producer_give(audio_connection_t *connection, audio_buffer_t *buffer) { + producer_pool_blocking_give, Mono>(connection, buffer); +} + + + + diff --git a/src/rp2_common/pico_scanvideo_dbi/CMakeLists.txt b/src/rp2_common/pico_scanvideo_dbi/CMakeLists.txt new file mode 100644 index 0000000..7760fbe --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(video_dbi INTERFACE) + +# todo right now this is just a copy of video/ +pico_generate_pio_header(video_dbi ${CMAKE_CURRENT_LIST_DIR}/video.pio) +pico_generate_pio_header(video_dbi ${CMAKE_CURRENT_LIST_DIR}/control.pio) + +target_sources(video_dbi INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/video.h + ${CMAKE_CURRENT_LIST_DIR}/vga_modes.c + $<$:${CMAKE_CURRENT_LIST_DIR}/tft_driver.c> + $<$:${CMAKE_CURRENT_LIST_DIR}/video_dbi.c> + ) + +target_compile_definitions(video_dbi INTERFACE VIDEO_DBI) +target_include_directories(video_dbi INTERFACE ${CMAKE_CURRENT_LIST_DIR}) +target_link_libraries(video_dbi INTERFACE dma pio) \ No newline at end of file diff --git a/src/rp2_common/pico_scanvideo_dbi/README.md b/src/rp2_common/pico_scanvideo_dbi/README.md new file mode 100644 index 0000000..1dd5872 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/README.md @@ -0,0 +1 @@ +THIS IS CURRENTLY NOT COMPILING (A PLACEHOLDER THAT NEEDS FIXING) \ No newline at end of file diff --git a/src/rp2_common/pico_scanvideo_dbi/control.pio b/src/rp2_common/pico_scanvideo_dbi/control.pio new file mode 100644 index 0000000..8cb1165 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/control.pio @@ -0,0 +1,25 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program video_dbi_control +.define out_delay 1 +.define clk_delay 1 + +.side_set 1 ; used for WR strobe +.extern data_run_out + out pins, 16 [out_delay] set 0 + jmp x-- data_run_out [out_delay] set 1 + +.extern entry_point +.wrap_target +.extern new_state_wait + out exec, 16 set 1 + out x, 11 set 1 + out pc, 5 set 1 +.extern clock_run + nop [clk_delay] set 0 + jmp x-- clock_run [clk_delay] set 1 +.wrap diff --git a/src/rp2_common/pico_scanvideo_dbi/tft_driver.c b/src/rp2_common/pico_scanvideo_dbi/tft_driver.c new file mode 100644 index 0000000..357dc42 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/tft_driver.c @@ -0,0 +1,600 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "platform_defs_c.h" +#include "tft_driver.h" +#include "gpio.h" +#include "pio.h" +#include "control.pio.h" +#include "dma.h" +#include "debug.h" + +CU_REGISTER_DEBUG_PINS(cmds) +//CU_SELECT_DEBUG_PINS(cmds) + +#define MAKE_CMD(x, jmp) ((x) | ((jmp)<<11u)) + +//#define NO_SETUP + +static struct __packed { +#ifndef NO_SETUP + uint16_t exec_csrs_l; // + uint16_t wr_1_cmd; // + uint16_t caset16; // + + uint16_t exec_rs_h; + uint16_t wr_4_cmd; // + uint16_t x0_h; + uint16_t x0_l; + uint16_t x1_h; + uint16_t x1_l; + + uint16_t exec_csrs_l_2; // + uint16_t wr_1_cmd_2; // + uint16_t paset16; // + + uint16_t exec_rs_h_2; // + uint16_t wr_4_cmd_2; // + uint16_t y0_h; + uint16_t y0_l; + uint16_t y1_h; + uint16_t y1_l; + + uint16_t exec_csrs_l_3; // + uint16_t wr_1_cmd_3; // + uint16_t ramwr16; // + + uint16_t exec_rs_h_3; // +#else + uint16_t exec_rs_h; +#endif + uint16_t skip_cmd; // + uint16_t exec_set_irq4; // + uint16_t clk_n_cmd; // + + uint16_t exec_csrs_h; // + uint16_t skip_cmd_2; // + uint16_t exec_set_irq0; // + uint16_t skip_cmd_3; // + +#ifndef NO_SETUP + // to align on word boundary + uint16_t exec_skip; // +#endif +} __aligned(4) scanline_control_sequence; + +static struct __packed { + uint16_t exec_csrs_l; // + uint16_t wr_1_cmd; // + +// uint16_t ptlar16; // +// uint16_t exec_rs_h; +// +// uint16_t wr_4_cmd; // +// uint16_t y0_h; +// +// uint16_t y0_l; +// uint16_t y1_h; +// +// uint16_t y1_l; +// uint16_t exec_csrs_l_2; // +// +// uint16_t wr_1_cmd_2; // + uint16_t vscrsaddr16; // + + uint16_t exec_rs_h_2; + uint16_t wr_2_cmd; // + + uint16_t y_h; + uint16_t y_l; + + uint16_t exec_csrs_h; + uint16_t skip_cmd; + + uint16_t exec_skip; +} __aligned(4) switch_buffer_control_sequence; + +//These define the ports and port bits used for the write, chip select (CS) and data/command (RS) lines +#define WR_L gpio_put(WR_PIN, false) + +// We need a slower write strobe for the ILI9488 +#ifdef ILI9486 +#define ILI9481 + #define WR_H ({gpio_put(WR_PIN, false); gpio_put(WR_PIN, true);}) + #define WR_STB ({gpio_put(WR_PIN, false); gpio_put(WR_PIN, false); gpio_put(WR_PIN, true);}) +#else +#define WR_H gpio_put(WR_PIN, true); +#define WR_STB gpio_put(WR_PIN, false); gpio_put(WR_PIN, true); +#endif + +// Chip select must be toggled during setup +#define SETUP_CS_H gpio_put(CS_PIN, true) +#define SETUP_CS_L gpio_put(CS_PIN, false) + +// Chip select can optionally be kept low after setup +#ifndef KEEP_CS_LOW +#define CS_H gpio_put(CS_PIN, true) +#define CS_L gpio_put(CS_PIN, false) +#else +#define CS_H // We do not define this so CS will not be set high + #define CS_L gpio_put(CS_PIN, false) +#endif + +// If pin 4 is hard wired to pin 38 we benefit from all controls on PORTG +#ifdef FAST_RS +#define RS_L gpio_put(RS_PIN, false); + #define RS_H gpio_put(RS_PIN, true); +#else +#define RS_L gpio_put(RS_PIN, false) +#define RS_H gpio_put(RS_PIN, true) +#endif + + +static inline void digitalWrite(uint pin, bool value) { + gpio_put(pin, value); +} + +static inline void set_data_pins16(uint16_t data) { + gpio_put_masked(0xffff, data); +} + +static inline void set_data_pins8(uint8_t data) { + gpio_put_masked(0xff, data); +} + +void writecommand(uint8_t c) +{ + DEBUG_PINS_SET(cmds, 1); + SETUP_CS_L; + RS_L; + set_data_pins16(c); + WR_STB; + RS_H; + SETUP_CS_H; + DEBUG_PINS_CLR(cmds, 1); +} + + +/*************************************************************************************** +** Function name: writedata +** Description: Send a 8 bit data value to the TFT +***************************************************************************************/ +void writedata(uint8_t c) +{ + SETUP_CS_L; + set_data_pins16(c); + WR_STB; + SETUP_CS_H; +} + +#ifdef NO_SETUP +void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) +{ + //if((x1 >= _width) || (y1 >= _height)) return; + CS_L; + RS_L; set_data_pins8(HX8357_CASET); WR_STB; RS_H; + set_data_pins8(x0>>8); WR_STB; + set_data_pins8(x0); WR_STB; + set_data_pins8(x1>>8); WR_STB; + set_data_pins8(x1); WR_STB; + RS_L; set_data_pins8(HX8357_PASET); WR_STB; RS_H; + set_data_pins8(y0>>8); WR_STB; + set_data_pins8(y0); WR_STB; + set_data_pins8(y1>>8); WR_STB; + set_data_pins8(y1); WR_STB; + RS_L; set_data_pins8(HX8357_RAMWR); WR_STB; RS_H; +} +#endif + + +void tft_panel_init() { + // toggle RST low to reset + gpio_put(RST_PIN, true); + sleep_ms(50); + gpio_put(RST_PIN, false); + sleep_ms(10); + gpio_put(RST_PIN, true); + sleep_ms(10); + +#ifndef HX8357C + +#ifdef ILI9486 + writecommand(0x01); + writedata(0x00); + sleep_ms(50); + + writecommand(0x28); + writedata(0x00); + + writecommand(0xC0); // Power Control 1 + writedata(0x0d); + writedata(0x0d); + + writecommand(0xC1); // Power Control 2 + writedata(0x43); + writedata(0x00); + + writecommand(0xC2); // Power Control 3 + writedata(0x00); + + writecommand(0xC5); // VCOM Control + writedata(0x00); + writedata(0x48); + + writecommand(0xB6); // Display Function Control + writedata(0x00); + writedata(0x22); // 0x42 = Rotate display 180 deg. + writedata(0x3B); + + writecommand(0xE0); // PGAMCTRL (Positive Gamma Control) + writedata(0x0f); + writedata(0x24); + writedata(0x1c); + writedata(0x0a); + writedata(0x0f); + writedata(0x08); + writedata(0x43); + writedata(0x88); + writedata(0x32); + writedata(0x0f); + writedata(0x10); + writedata(0x06); + writedata(0x0f); + writedata(0x07); + writedata(0x00); + + writecommand(0xE1); // NGAMCTRL (Negative Gamma Control) + writedata(0x0F); + writedata(0x38); + writedata(0x30); + writedata(0x09); + writedata(0x0f); + writedata(0x0f); + writedata(0x4e); + writedata(0x77); + writedata(0x3c); + writedata(0x07); + writedata(0x10); + writedata(0x05); + writedata(0x23); + writedata(0x1b); + writedata(0x00); + + writecommand(0x20); // Display Inversion OFF, 0x21 = ON + + writecommand(0x36); // Memory Access Control +// writedata(0x0A); + writedata(0x02); // rgb -> bgr + + writecommand(0x3A); // Interface Pixel Format + writedata(0x55); + +// // scroll +// writecommand( 0x37); +// writedata(0); +// writedata(120); +// + writecommand( 0x30); + writedata(0); + writedata(0); + writedata(0); + writedata(240); + + // partial mode + writecommand( 0x12); + + writecommand(0x11); + + sleep_ms(150); + + writecommand(0x29); + sleep_ms(25); + +#else +// Configure HX8357-B display + writecommand(0x11); + sleep_ms(20); + writecommand(0xD0); + writedata(0x07); + writedata(0x42); + writedata(0x18); + + writecommand(0xD1); + writedata(0x00); + writedata(0x07); + writedata(0x10); + + writecommand(0xD2); + writedata(0x01); + writedata(0x02); + + writecommand(0xC0); + writedata(0x10); + writedata(0x3B); + writedata(0x00); + writedata(0x02); + writedata(0x11); + + writecommand(0xC5); + writedata(0x08); + + writecommand(0xC8); + writedata(0x00); + writedata(0x32); + writedata(0x36); + writedata(0x45); + writedata(0x06); + writedata(0x16); + writedata(0x37); + writedata(0x75); + writedata(0x77); + writedata(0x54); + writedata(0x0C); + writedata(0x00); + + writecommand(0x36); + //writedata(0x0a); + writedata(0x02); // rgb -> bgr + + writecommand(0x3A); + writedata(0x55); + + writecommand(0x2A); + writedata(0x00); + writedata(0x00); + writedata(0x01); + writedata(0x3F); + + writecommand(0x2B); + writedata(0x00); + writedata(0x00); + writedata(0x01); + writedata(0xDF); + + sleep_ms(120); + writecommand(0x29); + + sleep_ms(25); +// End of HX8357-B display configuration +#endif + +#else + + // HX8357-C display initialisation + + writecommand(0xB9); // Enable extension command + writedata(0xFF); + writedata(0x83); + writedata(0x57); + sleep_ms(50); + + writecommand(0xB6); //Set VCOM voltage + writedata(0x2C); //0x52 for HSD 3.0" + + writecommand(0x11); // Sleep off + sleep_ms(200); + + writecommand(0x35); // Tearing effect on + writedata(0x00); // Added parameter + + writecommand(0x3A); // Interface pixel format + writedata(0x55); // 16 bits per pixel + + //writecommand(0xCC); // Set panel characteristic + //writedata(0x09); // S960>S1, G1>G480, R-G-B, normally black + + //writecommand(0xB3); // RGB interface + //writedata(0x43); + //writedata(0x00); + //writedata(0x06); + //writedata(0x06); + + writecommand(0xB1); // Power control + writedata(0x00); + writedata(0x15); + writedata(0x0D); + writedata(0x0D); + writedata(0x83); + writedata(0x48); + + + writecommand(0xC0); // Does this do anything? + writedata(0x24); + writedata(0x24); + writedata(0x01); + writedata(0x3C); + writedata(0xC8); + writedata(0x08); + + writecommand(0xB4); // Display cycle + writedata(0x02); + writedata(0x40); + writedata(0x00); + writedata(0x2A); + writedata(0x2A); + writedata(0x0D); + writedata(0x4F); + + writecommand(0xE0); // Gamma curve + writedata(0x00); + writedata(0x15); + writedata(0x1D); + writedata(0x2A); + writedata(0x31); + writedata(0x42); + writedata(0x4C); + writedata(0x53); + writedata(0x45); + writedata(0x40); + writedata(0x3B); + writedata(0x32); + writedata(0x2E); + writedata(0x28); + + writedata(0x24); + writedata(0x03); + writedata(0x00); + writedata(0x15); + writedata(0x1D); + writedata(0x2A); + writedata(0x31); + writedata(0x42); + writedata(0x4C); + writedata(0x53); + writedata(0x45); + writedata(0x40); + writedata(0x3B); + writedata(0x32); + + writedata(0x2E); + writedata(0x28); + writedata(0x24); + writedata(0x03); + writedata(0x00); + writedata(0x01); + + writecommand(0x36); // MADCTL Memory access control + writedata(0x48); + sleep_ms(20); + + writecommand(0x21); //Display inversion on + sleep_ms(20); + + writecommand(0x29); // Display on + + sleep_ms(120); +#endif + +#ifdef NO_SETUP + setAddrWindow(0, 0, 320, 240); +#endif + +#ifdef KEEP_CS_LOW + SETUP_CS_L; +#endif +} + +void tft_driver_init() +{ + for(int i = 0; i < 16; i++) + { + gpio_init(i); + } + gpio_init(RS_PIN); + gpio_init(CS_PIN); +// gpio_init(_fcs); + gpio_init(WR_PIN); + gpio_init(RST_PIN); + + gpio_set_dir(RST_PIN, true); + gpio_put(RST_PIN, true); + + gpio_set_dir(RS_PIN, true); + gpio_set_dir(CS_PIN, true); + gpio_set_dir(WR_PIN, true); + + gpio_put(RS_PIN, true); + +#ifndef KEEP_CS_LOW + gpio_put(CS_PIN, true); +#else + gpio_put(CS_PIN, false); +#endif + + gpio_put(WR_PIN, true); + + // DDRA = 0xFF; // Set direction for the 2 8 bit data ports + // DDRC = 0xFF; + gpio_set_dir_masked(0xffff, 0xffff); + +#define EXEC_CSL_RSL pio_encode_with_sideset_opt(pio_encode_set_pins(0), 2, 1) +#define EXEC_CSL_RSH pio_encode_with_sideset_opt(pio_encode_set_pins(1), 2, 1) +#define EXEC_CSH_RSH pio_encode_with_sideset_opt(pio_encode_set_pins(3), 2, 1) +#define EXEC_SET_IRQ(n) pio_encode_with_sideset_opt(pio_encode_irq_set((n), false), 2, 1) +#define DOH_PROGRAM_OFFSET 16 +#define EXEC_SKIP pio_encode_with_sideset_opt(pio_encode_jmp(DOH_PROGRAM_OFFSET + video_dbi_control_offset_new_state_wait), 2, 1) + +#define WR_CMD(n) MAKE_CMD( (n)-1, DOH_PROGRAM_OFFSET + video_dbi_control_offset_data_run_out) +#define SKIP_CMD MAKE_CMD( 0, DOH_PROGRAM_OFFSET + video_dbi_control_offset_new_state_wait) +#define CLOCK_CMD(n) MAKE_CMD((w)-1, DOH_PROGRAM_OFFSET + video_dbi_control_offset_clock_run) +#ifndef NO_SETUP + scanline_control_sequence.exec_csrs_l = EXEC_CSL_RSL; + scanline_control_sequence.wr_1_cmd = WR_CMD(1); + scanline_control_sequence.caset16 = HX8357_CASET; + scanline_control_sequence.wr_4_cmd = WR_CMD(4); + scanline_control_sequence.exec_csrs_l_2 = EXEC_CSL_RSL; + scanline_control_sequence.wr_1_cmd_2 = WR_CMD(1); + scanline_control_sequence.paset16 = HX8357_PASET; + scanline_control_sequence.exec_rs_h_2 = EXEC_CSL_RSH; + scanline_control_sequence.wr_4_cmd_2 = WR_CMD(4); + scanline_control_sequence.exec_csrs_l_3 = EXEC_CSL_RSL; + scanline_control_sequence.wr_1_cmd_3 = WR_CMD(1); + scanline_control_sequence.ramwr16 = HX8357_RAMWR; + scanline_control_sequence.exec_rs_h_3 = EXEC_CSL_RSH; +#endif + scanline_control_sequence.skip_cmd = SKIP_CMD; + scanline_control_sequence.exec_rs_h = EXEC_CSL_RSH; + scanline_control_sequence.exec_set_irq4 = EXEC_SET_IRQ(4); + scanline_control_sequence.exec_csrs_h = EXEC_CSH_RSH; + scanline_control_sequence.exec_set_irq0 = EXEC_SET_IRQ(0); + scanline_control_sequence.skip_cmd_2 = SKIP_CMD; + scanline_control_sequence.skip_cmd_3 = SKIP_CMD; + scanline_control_sequence.exec_skip = EXEC_SKIP; + scanline_control_sequence.x0_h = 0; + scanline_control_sequence.x0_l = 0; + + switch_buffer_control_sequence.exec_csrs_l = EXEC_CSL_RSL; // + switch_buffer_control_sequence.wr_1_cmd = WR_CMD(1); // +// switch_buffer_control_sequence.ptlar16 = 0x30; // +// switch_buffer_control_sequence.exec_rs_h = EXEC_CSL_RSH; +// switch_buffer_control_sequence.wr_4_cmd = WR_CMD(4); // +// +// switch_buffer_control_sequence.exec_csrs_l_2 = EXEC_CSL_RSL; // +// switch_buffer_control_sequence.wr_1_cmd_2 = WR_CMD(1); // + switch_buffer_control_sequence.vscrsaddr16 = 0x37; // + switch_buffer_control_sequence.exec_rs_h_2 = EXEC_CSL_RSH; + switch_buffer_control_sequence.wr_2_cmd = WR_CMD(2); // + + switch_buffer_control_sequence.exec_csrs_h = EXEC_CSH_RSH; + switch_buffer_control_sequence.skip_cmd = SKIP_CMD; + switch_buffer_control_sequence.exec_skip = EXEC_SKIP; + tft_panel_init(); +} + +#define video_pio pio0 + +// todo duplicate defines from elsewhere +#define SM_DATA 0 +#define SM_CONTROL 3 +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL 0 + +uint32_t *get_control_sequence(uint w, uint y, uint *count, bool buffer) { + y += buffer ? 240 : 0; +#ifndef NO_SETUP + scanline_control_sequence.x1_h = w>>8; + scanline_control_sequence.x1_l = w&0xff; + scanline_control_sequence.y0_h = y>>8; + scanline_control_sequence.y0_l = y&0xff; + scanline_control_sequence.y1_h = (y+1)>>8; + scanline_control_sequence.y1_l = (y+1)&0xff; +#endif + scanline_control_sequence.clk_n_cmd = CLOCK_CMD(w); + + static_assert(!(sizeof(scanline_control_sequence) & 3u), ""); + *count = sizeof(scanline_control_sequence) / 4; + return (uint32_t *)&scanline_control_sequence; +} + +extern uint32_t *get_switch_buffer_sequence(uint *count, bool buffer) { +// switch_buffer_control_sequence.y0_h = 0; +// switch_buffer_control_sequence.y0_l = buffer?240:0; +// switch_buffer_control_sequence.y1_h = buffer?(480>>8):0; +// switch_buffer_control_sequence.y1_l = buffer?(480&0xff):240; + switch_buffer_control_sequence.y_h = 0; + switch_buffer_control_sequence.y_l = buffer?240:0; + *count = sizeof(switch_buffer_control_sequence) / 4; + return (uint32_t *)&switch_buffer_control_sequence; +} \ No newline at end of file diff --git a/src/rp2_common/pico_scanvideo_dbi/tft_driver.h b/src/rp2_common/pico_scanvideo_dbi/tft_driver.h new file mode 100644 index 0000000..78f4f97 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/tft_driver.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SOFTWARE_TFT_DRIVER_H +#define SOFTWARE_TFT_DRIVER_H + +// These register enumerations are not all used, but kept for possible future use +#define HX8357D 0xD +#define HX8357B 0xB7 + +#define HX8357_NOP 0x00 +#define HX8357_SWRESET 0x01 +#define HX8357_RDDID 0x04 +#define HX8357_RDDST 0x09 + +#define HX8357_RDPOWMODE 0x0A +#define HX8357_RDMADCTL 0x0B +#define HX8357_RDCOLMOD 0x0C +#define HX8357_RDDIM 0x0D +#define HX8357_RDDSDR 0x0F + +#define HX8357_SLPIN 0x10 +#define HX8357_SLPOUT 0x11 +#define HX8357B_PTLON 0x12 +#define HX8357B_NORON 0x13 + +#define HX8357_INVOFF 0x20 +#define HX8357_INVON 0x21 +#define HX8357_DISPOFF 0x28 +#define HX8357_DISPON 0x29 + +#define HX8357_CASET 0x2A +#define HX8357_PASET 0x2B +#define HX8357_RAMWR 0x2C +#define HX8357_RAMRD 0x2E + +#define HX8357B_PTLAR 0x30 +#define HX8357_TEON 0x35 +#define HX8357_TEARLINE 0x44 +#define HX8357_MADCTL 0x36 +#define HX8357_COLMOD 0x3A + +#define HX8357_SETOSC 0xB0 +#define HX8357_SETPWR1 0xB1 +#define HX8357B_SETDISPLAY 0xB2 +#define HX8357_SETRGB 0xB3 +#define HX8357D_SETCOM 0xB6 + +#define HX8357B_SETDISPMODE 0xB4 +#define HX8357D_SETCYC 0xB4 +#define HX8357B_SETOTP 0xB7 +#define HX8357D_SETC 0xB9 + +#define HX8357B_SET_PANEL_DRIVING 0xC0 +#define HX8357D_SETSTBA 0xC0 +#define HX8357B_SETDGC 0xC1 +#define HX8357B_SETID 0xC3 +#define HX8357B_SETDDB 0xC4 +#define HX8357B_SETDISPLAYFRAME 0xC5 +#define HX8357B_GAMMASET 0xC8 +#define HX8357B_SETCABC 0xC9 +#define HX8357_SETPANEL 0xCC + + +#define HX8357B_SETPOWER 0xD0 +#define HX8357B_SETVCOM 0xD1 +#define HX8357B_SETPWRNORMAL 0xD2 + +#define HX8357B_RDID1 0xDA +#define HX8357B_RDID2 0xDB +#define HX8357B_RDID3 0xDC +#define HX8357B_RDID4 0xDD + +#define HX8357D_SETGAMMA 0xE0 + +#define HX8357B_SETGAMMA 0xC8 +#define HX8357B_SETPANELRELATED 0xE9 + +#define RS_PIN 24u +#define CS_PIN 25u +#define WR_PIN 26u +#define RST_PIN 27u + +// don't care +#define FCS_PIN 0 //23 + +extern void tft_driver_init(); +extern uint32_t *get_switch_buffer_sequence(uint *count, bool buffer); +extern uint32_t *get_control_sequence(uint w, uint y, uint *count, bool buffer); + +#endif //SOFTWARE_TFT_DRIVER_H diff --git a/src/rp2_common/pico_scanvideo_dbi/vga_modes.c b/src/rp2_common/pico_scanvideo_dbi/vga_modes.c new file mode 100644 index 0000000..afc349e --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/vga_modes.c @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "video.h" + +// todo support for inverted-y (probably belongs in the scanline generators, as would inverted x) +const uint32_t video_clock_freq = 24000000; + +extern const video_pio_program_t video_24mhz_composable; +const video_timing_t vga_timing_640x480_60_default = +{ + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 16, + .h_pulse = 64, + .h_total = 800, + .h_sync_polarity = 1, + + .v_front_porch = 1, + .v_pulse = 2, + .v_total = 500, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 +}; + +const video_timing_t vga_timing_640x240_60_default = + { + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 240, + + .h_front_porch = 16, + .h_pulse = 64, + .h_total = 800, + .h_sync_polarity = 1, + + .v_front_porch = 1, + .v_pulse = 2, + .v_total = 250, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + + +const video_timing_t vga_timing_648x480_60_alt1 = +{ + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 16, + .h_pulse = 48, + .h_total = 768, + .h_sync_polarity = 1, + + .v_front_porch = 10, + .v_pulse = 2, + .v_total = 523, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 +}; + +const video_timing_t vga_timing_648x480_50ish = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 56, + .h_pulse = 72, + .h_total = 896, + .h_sync_polarity = 1, + + .v_front_porch = 30, + .v_pulse = 2, + .v_total = 536, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const video_timing_t vga_timing_648x480_50ish2 = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 32, + .h_pulse = 64, + .h_total = 832, + .h_sync_polarity = 1, + + .v_front_porch = 27, + .v_pulse = 2, + .v_total = 577, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const video_timing_t vga_timing_648x480_50ish3 = + { + + .clock_freq = 24000000, + + .h_active = 640, + .v_active = 480, + + .h_front_porch = 72, + .h_pulse = 96, + .h_total = 928, + .h_sync_polarity = 1, + + .v_front_porch = 8, + .v_pulse = 2, + .v_total = 518, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +#define actual_vga_timing_50 vga_timing_648x480_50ish3 + +const video_mode_t vga_mode_160x120_60 = +{ + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 160, + .height = 120, + .xscale = 4, + .yscale = 4, +}; + +const video_mode_t vga_mode_213x160_60 = +{ + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 213, + .height = 160, + .xscale = 3, + .yscale = 3, +}; + +const video_mode_t vga_mode_320x240_60 = +{ + .default_timing = &vga_timing_640x240_60_default, + .pio_program = &video_24mhz_composable, + .width = 320, + .height = 240, + .xscale = 2, + .yscale = 1, +}; + +const video_mode_t vga_mode_320x480_60 = + { + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 320, + .height = 480, + .xscale = 2, + .yscale = 2, + }; + + +const video_mode_t vga_mode_640x480_60 = +{ + .default_timing = &vga_timing_640x480_60_default, + .pio_program = &video_24mhz_composable, + .width = 640, + .height = 480, + .xscale = 1, + .yscale = 1, +}; + + +const video_mode_t vga_mode_640x480_50 = +{ + .default_timing = &actual_vga_timing_50, + .pio_program = &video_24mhz_composable, + .width = 640, + .height = 480, + .xscale = 1, + .yscale = 1, +}; + +const video_mode_t vga_mode_320x240_50 = +{ + .default_timing = &actual_vga_timing_50, + .pio_program = &video_24mhz_composable, + .width = 320, + .height = 240, + .xscale = 2, + .yscale = 2, +}; + +/* this is 50 hz */ +const video_timing_t vga_timing_wide_480_50 = +{ + .clock_freq = 24000000, + + .h_active = 800, + .v_active = 480, + + .h_front_porch = 32, + .h_pulse = 48, + .h_total = 960, + .h_sync_polarity = 0, + + .v_front_porch = 1, + .v_pulse = 2, + .v_total = 500, + .v_sync_polarity = 0, + + .enable_clock = 1, + .clock_polarity = 0, + + .enable_den = 1 +}; + +const video_mode_t vga_mode_tft_800x480_50 = +{ + .default_timing = &vga_timing_wide_480_50, + .pio_program = &video_24mhz_composable, + .width = 800, + .height = 480, + .xscale = 1, + .yscale = 1, +}; + +const video_mode_t vga_mode_tft_400x240_50 = +{ + .default_timing = &vga_timing_wide_480_50, + .pio_program = &video_24mhz_composable, + .width = 400, + .height = 240, + .xscale = 2, + .yscale = 2, +}; + +const video_timing_t vga_timing_512x576_50_attempt1 = + { + .clock_freq = 24000000, + + .h_active = 512, + .v_active = 576, + + .h_front_porch = 64, + .h_pulse = 64, + .h_total = 768, + .h_sync_polarity = 1, + + .v_front_porch = 30, + .v_pulse = 2, + .v_total = 612, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const video_timing_t vga_timing_512x576_60_attempt1 = + { + .clock_freq = 24000000, + + .h_active = 512, + .v_active = 576, + + .h_front_porch = 64, + .h_pulse = 64, + .h_total = 768, + .h_sync_polarity = 1, + + .v_front_porch = 30, + .v_pulse = 2, + .v_total = 612, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const video_mode_t vga_mode_256x192_50 = + { + .default_timing = &vga_timing_512x576_50_attempt1, + .pio_program = &video_24mhz_composable, + .width = 256, + .height = 192, + .xscale = 2, + .yscale = 3, + }; + +const video_timing_t vga_timing_800x600_38 = + { + .clock_freq = 24000000, + + .h_active = 800, + .v_active = 600, + + .h_front_porch = 24, + .h_pulse = 80, + .h_total = 1008, + .h_sync_polarity = 1, + + .v_front_porch = 3, + .v_pulse = 4, + .v_total = 621, + .v_sync_polarity = 1, + + .enable_clock = 0, + .clock_polarity = 0, + + .enable_den = 0 + }; + +const video_mode_t vga_mode_800x600_38 = + { + .default_timing = &vga_timing_800x600_38, + .pio_program = &video_24mhz_composable, + .width = 800, + .height = 600, + .xscale = 1, + .yscale = 1, + }; + diff --git a/src/rp2_common/pico_scanvideo_dbi/video.h b/src/rp2_common/pico_scanvideo_dbi/video.h new file mode 100644 index 0000000..3751ac9 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/video.h @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#ifndef _VIDEO_H +#define _VIDEO_H + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// == CONFIG ============ +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA 1 +#endif +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA) || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA 1 +#endif +#if PICO_SCANVIDEO_PLANE3_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE3_FIXED_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE3_FRAGMENT_DMA 1 +#endif + +#define ENABLE_VIDEO_CLOCK +#define ENABLE_VIDEO_DEN +// todo make multi plane play nicely with mode swapping; +// today we have hard coded blank/empty lines + +//#define ENABLE_VIDEO_PLANE2 +//#define ENABLE_VIDEO_PLANE3 +//#define PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA 1 +//#define PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA 1 + +#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS +#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS 128 +#endif +#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS +#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS 16 +#endif +#ifndef PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS +#define PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS 16 +#endif + +//extern struct semaphore vmode_updated; + +// note by default we allow for alpha mask (and lose a bit of green) +// todo make this configurable +#define PICO_SCANVIDEO_ALPHA_MASK 0x0020 +#define PICO_SCANVIDEO_PIXEL_RSHIFT 0 +#define PICO_SCANVIDEO_PIXEL_GSHIFT 6 +#define PICO_SCANVIDEO_PIXEL_BSHIFT 11 +#define PICO_SCANVIDEO_PIXEL_FROM_RGB8(r, g, b) ((((b)>>3)<>3)<>3)< 1 + uint32_t *data2; + uint16_t data2_used; + uint16_t data2_max; +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + uint32_t *data3; + uint16_t data3_used; + uint16_t data3_max; +#endif +#endif + // useful to track state between the buffer being passed to + // video_end_scanline_generation, and when the buffer is no longer + // in use by the video code and is returned to a subsequent caller + // via video_begin_scanline_generation + // todo we caould add a callback to begin scanline generation to enuerate + // all already discarded buffers early - not clear this would be useful in general + // because it only saves you space if stuff is running with low buffer utilization + void *user_data; + uint8_t status; +}; + +enum +{ + SCANLINE_OK = 1, + SCANLINE_ERROR, + SCANLINE_SKIPPED +}; + +// note frame numbers wrap +static inline uint16_t frame_number(uint32_t scanline_id) +{ + return (uint16_t) (scanline_id >> 16u); +} + +static inline uint16_t scanline_number(uint32_t scanline_id) +{ + return (uint16_t) scanline_id; +} + +/** + * @return the current vga mode (if there is one) + */ +extern struct video_mode video_get_mode(); + +/** + * @return the next scanline_id to be displayed (may be from the next frame) + */ +extern uint32_t video_get_next_scanline_id(); + +/** + * @return true if in the vblank interval + */ +extern bool video_in_vblank(); +/** + * @return true if in the hblank interval (or more accurately scanline data is not currently being sent to the PIO, which roughly corresponds, but is not exact). Note also that in + * yscale-d modes, there are height * yscale hblank intervals per frame. + */ +extern bool video_in_hblank(); + +extern void video_wait_for_vblank(); + +extern uint32_t video_wait_for_scanline_complete(uint32_t scanline_id); +/** + * Acquire a scanline that needs generating. The scanline_id field indicates which scanline is required. + * + * This method may be called concurrently + * + * @param block true to block if the vga system is not ready to generate a new scanline + * @return the scanline_buffer or NULL if block is false, and the vga system is not ready + */ +struct scanline_buffer *video_begin_scanline_generation(bool block); + +/** + * Return a scanline that has been generated / or at least the client is done with. + * + * The status field indicates whether the scanline was actually generated OK + * + * This method may be called concurrently (for different buffers) + * + * @param scanline_buffer \todo + */ +void video_end_scanline_generation(struct scanline_buffer *scanline_buffer); + +extern const struct video_timing vga_timing_640x480_60_default; +extern const struct video_timing vga_timing_wide_480_50; +extern const struct video_timing vga_timing_648x480_60_alt1; + +extern const struct video_mode vga_mode_160x120_60; // 3d monster maze anyone :-) +extern const struct video_mode vga_mode_213x160_60; +extern const struct video_mode vga_mode_320x240_60; +extern const struct video_mode vga_mode_640x480_60; +extern const struct video_mode vga_mode_320x480_60; + +extern const struct video_mode vga_mode_tft_800x480_50; +extern const struct video_mode vga_mode_tft_400x240_50; + +#ifndef NDEBUG +// todo this is only for vga composable 24... should exist behind mode impl +extern void validate_scanline(const uint32_t *dma_data, uint dma_data_size, uint max_pixels, uint expected_width); +#endif + + +// mode implementation + +pio_hw_t; + +struct video_pio_program +{ +#if !PICO_NO_HARDWARE + const uint16_t *program; + const int program_size; + const int entry_point; + bool (*adapt_for_mode)(const struct video_pio_program *program, const struct video_mode *mode, + struct scanline_buffer *missing_scanline_buffer, uint16_t *buffer, uint buffer_max); + void (*configure_pio)(pio_hw_t *pio, uint sm); +#else + const char *id; +#endif +}; + +extern void video_default_configure_pio(pio_hw_t *pio, uint sm, uint wrap_trarget, uint wrap, bool overlay); + +#include "video.pio.h" +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE +#define video_24mhz_composable_prefix video_24mhz_composable_default +#else +#define video_24mhz_composable_prefix video_24mhz_composable_raw1p_2cycle +#endif +// yuk... extra __P needed for native on some platforms +#define video_24mhz_composable_program_extern(x) __SAFE_CONCAT(__SAFE_CONCAT(video_24mhz_composable_prefix, _offset_), x) +#define __DVP_JMP(x) ((unsigned)video_24mhz_composable_program_extern(x)) +#define COMPOSABLE_COLOR_RUN __DVP_JMP(color_run) +#define COMPOSABLE_EOL_ALIGN __DVP_JMP(end_of_scanline_ALIGN) +#define COMPOSABLE_EOL_SKIP_ALIGN __DVP_JMP(end_of_scanline_skip_word_ALIGN) +#define COMPOSABLE_RAW_RUN __DVP_JMP(raw_run) +#define COMPOSABLE_RAW_1P __DVP_JMP(raw_1p) +#define COMPOSABLE_RAW_2P __DVP_JMP(raw_2p) +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE +#define COMPOSABLE_RAW_1P_SKIP_ALIGN __DVP_JMP(raw_1p_skip_word_ALIGN) +#else +#define COMPOSABLE_RAW_1P_2CYCLE __DVP_JMP(raw_1p_2cycle) +#endif + +#ifdef __cplusplus +} +#endif +#endif //_VIDEO_H diff --git a/src/rp2_common/pico_scanvideo_dbi/video_dbi.c b/src/rp2_common/pico_scanvideo_dbi/video_dbi.c new file mode 100644 index 0000000..73ecb81 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dbi/video_dbi.c @@ -0,0 +1,1366 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "platform.h" +#include "debug.h" +#include "video.h" +#include "gpio.h" +#include "dma.h" +#include "dreq.h" +#include "pio.h" +#include "tft_driver.h" +#include "control.pio.h" + +// todo add ability to shift scanline back a bit (we already have the timing, but it should be a post mode set adjustment) +// we can use this to allow some initial work in the scanline b4 the first pixel (e.g. a dummy black pixel) + +// todo bad state recovery +// - stress test with pause/unpause +// - bad state should cause SCANLINE_ASSERTION_ERROR +// - possible orphaned in_use - perhaps clean up when error state is detected +// - if PIO is not in the right place, pause/clear FIFO join-unjoin/jmp/resume +// - dma may need to be cancelled +// todo dma chaining support + +//#define ENABLE_VIDEO_CLOCK_DOWN + +#define GO_AT_USER_SPEED + +#pragma GCC push_options +//#ifdef __arm__ +//#pragma GCC optimize("O3") +//#endif + +// == CONFIG ============ + +#define PICO_SCANVIDEO_SCANLINE_SM 0u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL 0u +#define TIMING_DMA_CHANNEL 6u + +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA +#define PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL 3u +#endif +#define TIMING_SM 3u +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#define PICO_SCANVIDEO_SCANLINE_SM2 1u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2 1u +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA +#define PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2 4u +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 +#define PICO_SCANVIDEO_SCANLINE_SM3 2u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3 2u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK ((1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL) | ( 1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2) | ( 1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3)) +#else +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK ((1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL) | ( 1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2)) +#endif +#else +#if PICO_SCANVIDEO_PLANE_COUNT > 2 +#fatal must have ENABLE_VIDEO_PLANE2 for ENABLE_VIDEO_PLANE3 +#endif +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK (1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL) +#endif + +// == DEBUGGING ========= + +// note that this is very very important if you see things going wrong with the display, +// however beware, because it will also cause visual artifiacts if we are pushing the edge of the envelope +// since it itself uses cycles that are in short supply! This is why it is off by default +// +// todo note, it should eventually be difficult to get the display into a bad state (even +// with things like runaway scanline program; incomplete DMA etc.. which currently break it). +//#define ENABLE_SCANLINE_ASSERTIONS + +//#define PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + +CU_REGISTER_DEBUG_PINS(sequence, video_timing, video_dma_buffer, video_irq, video_dma_completion, video_generation, video_recovery +) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(video_recovery) +//CU_SELECT_DEBUG_PINS(video_generation) +CU_SELECT_DEBUG_PINS(video_timing) +//CU_SELECT_DEBUG_PINS(video_irq) +//CU_SELECT_DEBUG_PINS(video_dma_buffer) +//CU_SELECT_DEBUG_PINS(video_dma_completion) +//CU_SELECT_DEBUG_PINS(sequence) + +// ====================== + +// todo this needs to come from somwehere useful +#define DISABLE_VIDEO_ASSERTIONS + +#ifndef DISABLE_VIDEO_ASSERTIONS +#define video_assert(x) assert(x) +#else +#define video_assert(x) (void)0 +#endif + +#ifdef ENABLE_SCANLINE_ASSERTIONS +#define scanline_assert(x) assert(x) +#else +#define scanline_assert(x) (void)0 +#endif + +#define video_pio pio0 + +#define VIDEO_ADJUST_BUS_PRIORITY_VAL (BUSCTRL_BUS_PRIORITY_PROC0_BITS | BUSCTRL_BUS_PRIORITY_PROC1_BITS) + +#ifdef VIDEO_MOST_TIME_CRITICAL_CODE_SECTION +#define __video_most_time_critical(x) __attribute__((section(__XSTRING(VIDEO_MOST_TIME_CRITICAL_CODE_SECTION) "." x))) +#else +#define __video_most_time_critical(x) __not_in_flash("video.mostcrit." x) +#endif + +#ifdef VIDEO_TIME_CRITICAL_CODE_SECTION +#define __video_time_critical(x) __attribute__((section(__XSTRING(VIDEO_TIME_CRITICAL_CODE_SECTION) "." x))) +#else +#define __video_time_critical(x) __not_in_flash("video.crit." x) +#endif +// --- video_24mhz_composable --- + +#define video_24mhz_composable_program __CONCAT(video_24mhz_composable_prefix, _program) +#define video_24mhz_composable_wrap_target __CONCAT(video_24mhz_composable_prefix, _wrap_target) +#define video_24mhz_composable_wrap __CONCAT(video_24mhz_composable_prefix, _wrap) + +bool video_24mhz_composable_adapt_for_mode(const struct video_pio_program *program, const struct video_mode *mode, + struct scanline_buffer *missing_scanline_buffer, uint16_t *buffer, + uint buffer_max); +void video_24mhz_composable_configure_pio(pio_hw_t *pio, uint sm); + +const struct video_pio_program video_24mhz_composable = { + .program = video_24mhz_composable_program, + .program_size = count_of(video_24mhz_composable_program), + .entry_point = video_24mhz_composable_program_extern(entry_point), + .adapt_for_mode = video_24mhz_composable_adapt_for_mode, + .configure_pio = video_24mhz_composable_configure_pio +}; + +#define PIO_WAIT_IRQ4 pio_encode_wait_irq(1, 4) + +static const uint16_t video_dbi_control_load_offset = 16; + +// --- video timing stuff + +static struct { + uint16_t v_active; + uint16_t v_total; + uint16_t v_pulse_start; + uint16_t v_pulse_end; + +} timing_state; + +#ifdef ENABLE_VIDEO_CLOCK_DOWN +static uint16_t video_clock_down; +#endif + +struct semaphore vblank_begin; + +// --- scanline stuff +// private representation of scanline buffer (adds link for one list this scanline buffer is currently in) +struct full_scanline_buffer { + struct scanline_buffer core; + struct full_scanline_buffer *next; +}; + +#ifndef PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT +#define PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT 8 +#endif +// each scanline_buffer should be in exactly one of the shared_state lists below +// (unless we don't have USE_SCANLINE_DEBUG in which case we don't keep the generating list, +// in which case the scanline is entirely trusted to the client when generating) +struct full_scanline_buffer scanline_buffers[PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT]; + +// This state is sensitive as it it accessed by either core, and multiple IRQ handlers which may be re-entrant +// Nothing in here should be touched except when protected by the appropriate spin lock. +// +// The separations by spin lock (other than the need for spin locks to protect state consistency) is to allow +// safe concurrent operation by both cores, client, IRQ and nested IRQ (pre-emption) where desirable due +// to timing concerns. +static struct { + struct { + spin_lock_t *lock; + // note in_use is a list as we are lazy in removing buffers from it + struct full_scanline_buffer *in_use_ascending_scanline_id_list; + // pointer to the tail element of the list for making appending by ascending scanline id quick + struct full_scanline_buffer *in_use_ascending_scanline_id_list_tail; + } in_use; + + struct { + spin_lock_t *lock; + struct full_scanline_buffer *current_scanline_buffer; + uint32_t last_scanline_id; + uint32_t next_scanline_id; + // 0 based index of y repeat... goes 0, 0, 0 in non scaled mode, 0, 1, 0, 1 in doubled etc. + uint y_repeat_index; + bool in_vblank; + // This generated list is in this struct because it is accessed together in fsb latching + // and the only other place it is used in video_end_scanline_generation which needs no other + // locks (i.e. we are saving an extra lock in the latch case by not placing in a separate struct) + struct full_scanline_buffer *generated_ascending_scanline_id_list; + struct full_scanline_buffer *generated_ascending_scanline_id_list_tail; + bool vblank_pending; + bool need_prepare_for_active_scanline; +#ifdef ENABLE_SCANLINE_ASSERTIONS + struct full_scanline_buffer *generating_list; +#endif + } scanline; + + struct { + spin_lock_t *lock; + struct full_scanline_buffer *free_list; + } free_list; + + // This is access by DMA IRQ and by SM IRQs + struct { + spin_lock_t *lock; + // bit mask of completed DMA scanline channels + uint32_t dma_completion_state; + // number of buffers to release (may be multiple due to interrupt pre-emption) + // todo combine these two fields + uint8_t buffers_to_release; + bool scanline_in_progress; + } dma; + + bool which_buffer; + // these are not updated, so not locked +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + int scanline_program_wait_index; +#endif +} shared_state; + +static uint32_t missing_scanline_data[] = + { + COMPOSABLE_COLOR_RUN | (PICO_SCANVIDEO_PIXEL_FROM_RGB8(255, 0, 0) << 16u), /* color */ + /*width-3*/ 0u | (COMPOSABLE_RAW_1P << 16u), + 0u | (COMPOSABLE_EOL_ALIGN << 16u) + }; + +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA +static uint32_t variable_fragment_missing_scanline_data_chain[] = { + count_of(missing_scanline_data), + 0, // missing_scanline_data, + 0, + 0, +}; +#endif + +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA +static uint32_t fixed_fragment_missing_scanline_data_chain[] = { + 0, // missing_scanline_data, + 0, +}; +#endif + +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +uint32_t missing_scanline_data_overlay[] = { + // blank line + 0u | (COMPOSABLE_EOL_ALIGN << 16u) +}; +#endif + +static struct full_scanline_buffer missing_scanline_buffer; + +static inline bool is_scanline_after(uint32_t scanline_id1, uint32_t scanline_id2) { + return ((int32_t)(scanline_id1 - scanline_id2)) > 0; +} + +static void prepare_for_active_scanline_irqs_enabled(); + +static void setup_sm(int sm); + +// -- MISC stuff +static struct video_mode video_mode; +static bool video_timing_enabled = false; +static bool display_enabled = true; + +inline static void list_prepend(struct full_scanline_buffer **phead, struct full_scanline_buffer *fsb) { + scanline_assert(fsb->next == NULL); + scanline_assert(fsb != *phead); + fsb->next = *phead; + *phead = fsb; +} + +inline static void list_prepend_all(struct full_scanline_buffer **phead, struct full_scanline_buffer *to_prepend) { + struct full_scanline_buffer *fsb = to_prepend; + + // todo should this be assumed? + if (fsb) { + while (fsb->next) { + fsb = fsb->next; + } + + fsb->next = *phead; + *phead = to_prepend; + } +} + +inline static struct full_scanline_buffer *list_remove_head(struct full_scanline_buffer **phead) { + struct full_scanline_buffer *fsb = *phead; + + if (fsb) { + *phead = fsb->next; + fsb->next = NULL; + } + + return fsb; +} + +inline static struct full_scanline_buffer *list_remove_head_ascending(struct full_scanline_buffer **phead, + struct full_scanline_buffer **ptail) { + struct full_scanline_buffer *fsb = *phead; + + if (fsb) { + *phead = fsb->next; + + if (!fsb->next) { + scanline_assert(*ptail == fsb); + *ptail = NULL; + } else { + fsb->next = NULL; + } + } + + return fsb; +} + +inline static void list_remove(struct full_scanline_buffer **phead, struct full_scanline_buffer *fsb) { + scanline_assert(*phead); + struct full_scanline_buffer *prev = *phead; + + if (prev == fsb) { + *phead = fsb->next; + } else { + while (prev->next && prev->next != fsb) { + prev = prev->next; + } + + scanline_assert(prev->next == fsb); + prev->next = fsb->next; + } + + // todo do we need this without assertions? + fsb->next = NULL; +} + +static inline uint32_t scanline_id_after(uint32_t scanline_id) { + uint32_t tmp = scanline_id & 0xffffu; + + if (tmp < video_mode.height - 1) { + return scanline_id + 1; + } else { + return scanline_id + 0x10000u - tmp; + } +} + +// todo add a tail for these already sorted lists as we generally insert on the end +inline static void list_insert_ascending(struct full_scanline_buffer **phead, struct full_scanline_buffer **ptail, + struct full_scanline_buffer *fsb) { + scanline_assert(fsb->next == NULL); + scanline_assert(fsb != *phead); + scanline_assert(fsb != *ptail); + + if (!*phead || !is_scanline_after(fsb->core.scanline_id, (*phead)->core.scanline_id)) { + if (!*phead) { + scanline_assert(!*ptail); + *ptail = fsb; + } + + // insert at the beginning + list_prepend(phead, fsb); + } else { + if (is_scanline_after(fsb->core.scanline_id, (*ptail)->core.scanline_id)) { + // insert at end + (*ptail)->next = fsb; + *ptail = fsb; + } else { + // not after + struct full_scanline_buffer *prev = *phead; + + while (prev->next && is_scanline_after(fsb->core.scanline_id, prev->next->core.scanline_id)) { + prev = prev->next; + } + + scanline_assert(prev != *ptail); // we should have already inserted at the end in this case + fsb->next = prev->next; + prev->next = fsb; + } + } +} + +inline static void free_local_free_list_irqs_enabled(struct full_scanline_buffer *local_free_list) { + if (local_free_list) { + uint32_t save = spin_lock_blocking(shared_state.free_list.lock); +// DEBUG_PINS_SET(video_timing, 4); + list_prepend_all(&shared_state.free_list.free_list, local_free_list); +// DEBUG_PINS_CLR(video_timing, 4); + spin_unlock(shared_state.free_list.lock, save); + // note also this is useful for triggering video_wait_for_scanline_complete check + __sev(); + } +} + +// Caller must own scanline_state_spin_lock +inline static struct full_scanline_buffer *scanline_locked_try_latch_fsb_if_null_irqs_disabled( + struct full_scanline_buffer **local_free_list) { + // note this just checks that someone owns it not necessarily this core. + scanline_assert(is_spin_locked(shared_state.scanline.lock)); + struct full_scanline_buffer *fsb = shared_state.scanline.current_scanline_buffer; + + if (!fsb) { + // peek the head + while (NULL != (fsb = shared_state.scanline.generated_ascending_scanline_id_list)) { + if (!is_scanline_after(shared_state.scanline.next_scanline_id, fsb->core.scanline_id)) { + if (shared_state.scanline.next_scanline_id == fsb->core.scanline_id) { + int c1 = 0; + for (struct full_scanline_buffer *x = shared_state.scanline.generated_ascending_scanline_id_list; x; x = x->next) c1++; + struct full_scanline_buffer __unused + *dbg = list_remove_head_ascending(&shared_state.scanline.generated_ascending_scanline_id_list, + &shared_state.scanline.generated_ascending_scanline_id_list_tail); + scanline_assert(dbg == fsb); + int c2 = 0; + for (struct full_scanline_buffer *x = shared_state.scanline.generated_ascending_scanline_id_list; x; x = x->next) c2++; + unprotected_spin_lock(shared_state.in_use.lock); +// DEBUG_PINS_SET(video_timing, 2); + list_insert_ascending(&shared_state.in_use.in_use_ascending_scanline_id_list, + &shared_state.in_use.in_use_ascending_scanline_id_list_tail, fsb); +// DEBUG_PINS_CLR(video_timing, 2); + spin_unlock_unsafe(shared_state.in_use.lock); + } + + shared_state.scanline.current_scanline_buffer = fsb; + break; + } else { + // scanline is in the past + struct full_scanline_buffer __unused + *dbg = list_remove_head_ascending(&shared_state.scanline.generated_ascending_scanline_id_list, + &shared_state.scanline.generated_ascending_scanline_id_list_tail); + scanline_assert(dbg == fsb); + list_prepend(local_free_list, fsb); + } + } + } + + return fsb; +} + +static inline void release_scanline_irqs_enabled(int buffers_to_free_count, + struct full_scanline_buffer **local_free_list) { + if (buffers_to_free_count) { + uint32_t save = spin_lock_blocking(shared_state.in_use.lock); + while (buffers_to_free_count--) { + DEBUG_PINS_SET(video_dma_buffer, 2); + // We always discard the head which is the oldest + struct full_scanline_buffer *fsb = list_remove_head_ascending( + &shared_state.in_use.in_use_ascending_scanline_id_list, + &shared_state.in_use.in_use_ascending_scanline_id_list_tail); + scanline_assert(fsb); + list_prepend(local_free_list, fsb); + DEBUG_PINS_CLR(video_dma_buffer, 2); + } + spin_unlock(shared_state.in_use.lock, save); + } +} + +static inline bool update_dma_transfer_state_irqs_enabled(bool cancel_if_not_complete, + int *scanline_buffers_to_release) { + uint32_t save = spin_lock_blocking(shared_state.dma.lock); + if (!shared_state.dma.scanline_in_progress) { + assert(!shared_state.dma.dma_completion_state); + assert(!shared_state.dma.buffers_to_release); + spin_unlock(shared_state.dma.lock, save); + return true; + } + uint32_t old_completed = shared_state.dma.dma_completion_state; + uint32_t new_completed; + while (0 != (new_completed = dma_hw->ints0 & PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK)) { + scanline_assert(!(old_completed & new_completed)); + // clear interrupt flags + dma_hw->ints0 = new_completed; + DEBUG_PINS_SET(video_dma_completion, new_completed); + DEBUG_PINS_CLR(video_dma_completion, new_completed); + new_completed |= old_completed; + if (new_completed == PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK) { + // tell caller to free these buffers... note it is safe to release any outstanding ones + // as only one DMA transfer can be logically in process and we have just finished that + // if the number is > 1 this is due to IRQ / preemption (todo this comment is out of date) + *scanline_buffers_to_release = shared_state.dma.buffers_to_release; + // we have taken ownership of releasing all the current ones + shared_state.dma.buffers_to_release = 0; + if (*scanline_buffers_to_release) { + // now that ISR clearing is protected by lock and also done by the active_scanline start + // we cannot have nesting + scanline_assert(*scanline_buffers_to_release == 1); + DEBUG_PINS_SET(video_dma_completion, 1); + DEBUG_PINS_CLR(video_dma_completion, 1); + } + shared_state.dma.dma_completion_state = shared_state.dma.scanline_in_progress = 0; + spin_unlock(shared_state.dma.lock, save); + return true; + } else { + DEBUG_PINS_SET(video_dma_completion, 2); + DEBUG_PINS_CLR(video_dma_completion, 2); + shared_state.dma.dma_completion_state = old_completed = new_completed; + } + } + // can't cancel yet, note if dma_buffers_to_release = 0 then completion DID happen (todo is this ever the case) + if (cancel_if_not_complete) { +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + if (shared_state.dma.buffers_to_release) { + shared_state.dma.dma_completion_state = shared_state.dma.scanline_in_progress = 0; + *scanline_buffers_to_release = shared_state.dma.buffers_to_release; + shared_state.dma.buffers_to_release = 0; + } + dma_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL); +#else + panic("need VIDEO_RECOVERY"); +#endif + } + spin_unlock(shared_state.dma.lock, save); + return false; +} + +void __video_most_time_critical("irq") + +prepare_for_active_scanline_irqs_enabled() { + // note we are now only called in active display lines.. + DEBUG_PINS_SET(video_timing, 1); + struct full_scanline_buffer *local_free_list = NULL; + int buffers_to_free_count = 0; + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); + // VERY IMPORTANT: THIS CODE CAN ONLY TAKE ABOUT 4.5 us BEFORE LAUNCHING DMA... + // ... otherwise our scanline will be shifted over (because we will have started display) + // + // to alleviate this somewhat, we let the dma_complete alsu do a check for current_scanline_buffer == null, and look for a completed scanlines + // In ideal case the dma complete IRQ handler will have been able to set current_scanline_buffer for us (or indeed this is a y scaled mode + // and we are repeating a line)... in either case we will come in well under time budget + struct full_scanline_buffer *fsb = scanline_locked_try_latch_fsb_if_null_irqs_disabled(&local_free_list); +#ifdef GO_AT_USER_SPEED + bool not_ready = false; + if (!fsb || fsb->core.scanline_id != shared_state.scanline.next_scanline_id) { + shared_state.scanline.need_prepare_for_active_scanline = true; + shared_state.scanline.current_scanline_buffer = 0; + not_ready = true; + } +#endif + spin_unlock(shared_state.scanline.lock, save); +#ifdef GO_AT_USER_SPEED + if (not_ready) return; +#else + if (fsb) + { + if (fsb->core.scanline_id != shared_state.scanline.next_scanline_id) { + // removed to allow for other video modes; not worth abstracting that far... + // also; we basically never see this color anyway! +// ((uint16_t *) (missing_scanline_data))[1] = 0x03e0; + // note: this should be in the future + fsb = &missing_scanline_buffer; + fsb->core.scanline_id = shared_state.scanline.next_scanline_id; // used for y position so we must update + } + } + else + { + // removed to allow for other video modes; not worth abstracting that far... +// ((uint16_t *)(missing_scanline_data))[1] = 0x001f; + // this is usually set by latch + fsb = &missing_scanline_buffer; + fsb->core.scanline_id = shared_state.scanline.next_scanline_id; // used for y position so we must update + } +#endif + + update_dma_transfer_state_irqs_enabled(true, &buffers_to_free_count); + +// DEBUG_PINS_SET(video_irq, 2); + // bit of overkill (to reset src_addr) for y scale repeat lines, but then again those should already have data. but this is now + // required in case current_scanline_buffer was set by the dma complete handler, in which case current_scanline_buffer was null when we got to the test above + + // don't need to reset anything put the CB pointer to start a reload? as we have already configured the rest + // note DMA should already be aborted by here. +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + if (!pio_tx_empty(video_pio, PICO_SCANVIDEO_SCANLINE_SM)) { + pio_fifo_join(video_pio, PICO_SCANVIDEO_SCANLINE_SM, PIO_FIFO_JOIN_NONE); + pio_fifo_join(video_pio, PICO_SCANVIDEO_SCANLINE_SM, PIO_FIFO_JOIN_TX); + } + if (video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].instr != PIO_WAIT_IRQ4) { + // hmm the problem here is we don't know if we should wait or not, because that is purely based on timing.. + // - if irq not posted, and we wait: GOOD + // - if irq not posted and we don't wait: BAD. early line + // - if irq already posted, and we wait: BAD. blank line + // - id irq already posted, and we don't wait: GOOD + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, pio_encode_wait_irq(0, 4)); + if (pio_sm_exec_stalled(video_pio, PICO_SCANVIDEO_SCANLINE_SM)) { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, pio_encode_jmp(shared_state.scanline_program_wait_index)); + } else { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, pio_encode_jmp(shared_state.scanline_program_wait_index+1)); + } + } +#endif +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)->al3_transfer_count = fsb->core.fragment_words; +#endif + //dma_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL, (uintptr_t)fsb->core.data, (uint32_t) fsb->core.data_used); + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL)->al3_read_addr_trig = (uintptr_t)fsb->core.data; +#else + assert(!dma_busy(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)); + dma_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, (uintptr_t) fsb->core.data, + (uint32_t) fsb->core.data_used); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2)->al3_read_addr_trig = (uintptr_t)fsb->core.data2; +#else + dma_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, (uintptr_t)fsb->core.data2, (uint32_t) fsb->core.data2_used); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + dma_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3, (uintptr_t)fsb->core.data3, (uint32_t) fsb->core.data3_used); +// scanline_assert(video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM3].addr == video_24mhz_composable_offset_end_of_scanline_ALIGN); +#endif +// scanline_assert(video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM2].addr == video_24mhz_composable_offset_end_of_scanline_ALIGN); +#endif + + //. send the control signals which will send irq 4 + // todo precheck width is even + DEBUG_PINS_SET(sequence, 4); + uint count; + assert(!dma_busy(TIMING_DMA_CHANNEL)); + uint32_t *control = get_control_sequence(video_mode.width & ~1u, scanline_number(fsb->core.scanline_id), &count, + shared_state.which_buffer); +// printf("pants %p %d\n", control, count); + DEBUG_PINS_SET(video_dma_buffer, 3); + dma_transfer_from_buffer_now(TIMING_DMA_CHANNEL, (uintptr_t) control, count); +// while (dma_busy(TIMING_DMA_CHANNEL)) { +// printf(" %d\n", dma_channel_hw_addr(TIMING_DMA_CHANNEL)->transfer_count); +// } + DEBUG_PINS_CLR(video_dma_buffer, 3); + DEBUG_PINS_CLR(sequence, 4); + +// scanline_assert(video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].addr == video_24mhz_composable_offset_end_of_scanline_ALIGN); +// DEBUG_PINS_CLR(video_irq, 2); + + save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 2); + shared_state.scanline.in_vblank = false; + bool was_correct_scanline = (fsb != &missing_scanline_buffer); + bool free_scanline = false; + if (++shared_state.scanline.y_repeat_index >= video_mode.yscale) { + // pick up a new scanline next time around if we had the right one + if (was_correct_scanline) { + free_scanline = true; + } + + shared_state.scanline.next_scanline_id = scanline_id_after(shared_state.scanline.next_scanline_id); + if (!scanline_number(shared_state.scanline.next_scanline_id)) { + shared_state.scanline.vblank_pending = true; + } + shared_state.scanline.y_repeat_index = 0; + shared_state.scanline.current_scanline_buffer = NULL; + } else if (!was_correct_scanline) { + // not at the the end of yscale, but the wrong (or missing) scanline anyway, so clear that + shared_state.scanline.current_scanline_buffer = NULL; + } + // safe to nest dma lock we never nest the other way + unprotected_spin_lock(shared_state.dma.lock); + shared_state.dma.scanline_in_progress = 1; + if (free_scanline) { + scanline_assert(!shared_state.dma.buffers_to_release); + shared_state.dma.buffers_to_release++; + } + spin_unlock_unsafe(shared_state.dma.lock); + DEBUG_PINS_CLR(video_timing, 3); + spin_unlock(shared_state.scanline.lock, save); + + // because IRQs are enabled, we may obviously be pre-empted before or between either of these + release_scanline_irqs_enabled(buffers_to_free_count, &local_free_list); + free_local_free_list_irqs_enabled(local_free_list); +} + +void __isr __video_most_time_critical("irq") isr_pio0_0() { +#if PICO_SCANVIDEO_ADJUST_BUS_PRIORITY + bus_ctrl_hw->priority = VIDEO_ADJUST_BUS_PRIORITY_VAL; +#endif + if (video_pio->irq & 1u) { + video_pio->irq = 1; + DEBUG_PINS_SET(video_timing, 4); + scanline_assert(!dma_busy(TIMING_DMA_CHANNEL)); + scanline_assert(video_pio->sm[TIMING_SM].addr == + video_dbi_control_load_offset + video_dbi_control_offset_new_state_wait); + + bool signal = false; + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); + signal = shared_state.scanline.vblank_pending; + shared_state.scanline.vblank_pending = false; + spin_unlock(shared_state.scanline.lock, save); + if (signal) { + uint count; + uint32_t *control = get_switch_buffer_sequence(&count, shared_state.which_buffer); + shared_state.which_buffer = !shared_state.which_buffer; + dma_transfer_from_buffer_now(TIMING_DMA_CHANNEL, (uintptr_t) control, count); + while (dma_busy(TIMING_DMA_CHANNEL)); + sem_release(&vblank_begin); + } + + // we are called at then end of a scanline transfer (from the timing SM) + if (display_enabled) { + if (!scanline_number(shared_state.scanline.next_scanline_id)) { + } + bool too_soon = false; + if (!too_soon) { + prepare_for_active_scanline_irqs_enabled(); + } else { + assert(false); // not handled yet + } + } + DEBUG_PINS_CLR(video_timing, 4); + } +} + +static inline bool is_scanline_sm(int sm) { +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + return sm == PICO_SCANVIDEO_SCANLINE_SM || sm == PICO_SCANVIDEO_SCANLINE_SM2 || sm == PICO_SCANVIDEO_SCANLINE_SM3; +#else + return sm == PICO_SCANVIDEO_SCANLINE_SM || sm == PICO_SCANVIDEO_SCANLINE_SM2; +#endif +#else + return sm == PICO_SCANVIDEO_SCANLINE_SM; +#endif +} + +void setup_sm(int sm) { +#ifndef NDEBUG + printf("Setting up SM %d\n", sm); +#endif + pio_sm_init(video_pio, sm); // now paused + + if (is_scanline_sm(sm)) { + video_mode.pio_program->configure_pio(video_pio, sm); + } else if (sm == TIMING_SM) { + // don't join as we want to read too +// pio_fifo_join(video_pio, sm, PIO_FIFO_JOIN_TX); // give the program as much time as we can + + pio_set_consecutive_pindirs(video_pio, TIMING_SM, WR_PIN, 1, true); + //pio_set_consecutive_pindirs(video_pio, TIMING_SM, 0, 16, true); + pio_set_consecutive_pindirs(video_pio, TIMING_SM, RS_PIN, 2, true); + pio_setup_pinctrl(video_pio, sm, 0, 16, RS_PIN, 2, WR_PIN, 0); + + pio_setup_pinctrl(video_pio, sm, 0, 16, RS_PIN, 2, WR_PIN, 0); + pio_setup_sideset(video_pio, sm, 1, false, 0); + pio_setup_out_special(video_pio, sm, 1, 0, 0); // need to be sticky as we output control info + + // init pins + pio_sm_exec(video_pio, sm, pio_encode_set_pins(3)); + pio_set_wrap(video_pio, sm, video_dbi_control_load_offset + video_dbi_control_wrap_target, + video_dbi_control_load_offset + video_dbi_control_wrap); + } + +#ifdef ENABLE_VIDEO_CLOCK_DOWN + pio_set_clkdiv_int_frac(video_pio, sm, video_clock_down, 0); +#endif + // enable auto-pull + pio_setup_shiftctrl(video_pio, sm, SHIFT_TO_RIGHT, SHIFT_TO_RIGHT, 0, 1, 32, 32); +} + +//extern bool video_get_mode(struct video_mode *mode) { +// // todo if initialized +// *mode = video_mode; +// return true; +//} + +extern uint32_t video_get_next_scanline_id() { + return *(volatile uint32_t *) &shared_state.scanline.next_scanline_id; +} + +extern bool video_in_hblank() { + // this is a close estimate + // return !*(volatile bool *) &shared_state.dma.scanline_in_progress; + + // better we can see if the PIO is waiting on IRQ 4 (which it is almost all of the time it isn't drawing pixels) + // + // note that currently we require even custom PIO scanline programs to use WAIT IRQ 4 to sync with start of scanline + return video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].instr == PIO_WAIT_IRQ4; +} + +extern bool video_in_vblank() { + return *(volatile bool *) &shared_state.scanline.in_vblank; +} + +extern struct scanline_buffer __video_time_critical("begin_scanline") * +video_begin_scanline_generation(bool +block) +{ +struct full_scanline_buffer *fsb; + +DEBUG_PINS_SET(video_generation, +1); +do +{ +uint32_t save = spin_lock_blocking(shared_state.free_list.lock); +// DEBUG_PINS_SET(video_timing, 4); +fsb = list_remove_head(&shared_state.free_list.free_list); +// DEBUG_PINS_CLR(video_timing, 4); +spin_unlock(shared_state +.free_list.lock, save); + +if (fsb) +{ +save = spin_lock_blocking(shared_state.scanline.lock); +#ifdef ENABLE_SCANLINE_ASSERTIONS +list_prepend(&shared_state.scanline.generating_list, fsb); +#endif +// todo improve this algorithm... how far ahead should we be +// todo i.e. should we skip ahead a bit further if we are perpetually behind - doesn't really help because we'd +// todo be skipping some scanlines anyway; doesn't really matter which ones at that point +uint32_t scanline_id = shared_state.scanline.next_scanline_id; + +if (! +is_scanline_after(scanline_id, shared_state +.scanline.last_scanline_id)) +{ +// we are buffering ahead of the display +scanline_id = scanline_id_after(shared_state.scanline.last_scanline_id); +} + +fsb->core. +scanline_id = shared_state.scanline.last_scanline_id = scanline_id; +spin_unlock(shared_state +.scanline.lock, save); +break; +} + +if (block) +{ +DEBUG_PINS_SET(video_generation, +4); +__wfe(); +DEBUG_PINS_CLR(video_generation, +4); +} +} +while (block); + +DEBUG_PINS_CLR(video_generation, +1); +return (struct scanline_buffer *) +fsb; +} + +extern void __video_time_critical("end_scanline") + +video_end_scanline_generation(struct scanline_buffer *scanline_buffer) { + DEBUG_PINS_SET(video_generation, 2); + struct full_scanline_buffer *fsb = (struct full_scanline_buffer *) scanline_buffer; + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); +#ifdef ENABLE_SCANLINE_ASSERTIONS + list_remove(&shared_state.scanline.generating_list, fsb); +#endif + list_insert_ascending(&shared_state.scanline.generated_ascending_scanline_id_list, + &shared_state.scanline.generated_ascending_scanline_id_list_tail, fsb); + bool prepare = shared_state.scanline.need_prepare_for_active_scanline; + shared_state.scanline.need_prepare_for_active_scanline = false; + spin_unlock(shared_state.scanline.lock, save); + if (prepare) { + prepare_for_active_scanline_irqs_enabled(); + } + DEBUG_PINS_CLR(video_generation, 2); +} + +#pragma GCC pop_options + +bool video_setup(const struct video_mode *mode) { + return video_setup_with_timing(mode, mode->default_timing); +} + +bool video_setup_with_timing(const struct video_mode *mode, const struct video_timing *timing) { + __builtin_memset(&shared_state, 0, sizeof(shared_state)); + // init non zero members + // todo pass scanline buffers and size, or allow client to allocate + shared_state.scanline.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_SCANLINE_LOCK); + shared_state.dma.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_DMA_LOCK); + shared_state.free_list.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_FREE_LIST_LOCK); + shared_state.in_use.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_IN_USE_LOCK); + shared_state.scanline.last_scanline_id = 0xffffffff; + shared_state.scanline.need_prepare_for_active_scanline = true; + + video_mode = *mode; + video_mode.default_timing = timing; + + static_assert(BPP == 16, ""); // can't do 8 bit now because of pixel count + // this is no longer necessary + //assert(!(mode->width & 1)); + // todo is this still necessary? + video_assert(!(timing->v_active % mode->yscale)); + ((uint16_t * )(missing_scanline_data))[2] = mode->width / 2 - 3; +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + variable_fragment_missing_scanline_data_chain[1] = native_safe_hw_ptr(missing_scanline_data); +#endif +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA + fixed_fragment_missing_scanline_data_chain[0] = native_safe_hw_ptr(missing_scanline_data); +#endif + + sem_init(&vblank_begin, 0, 1); + + for (int i = 0; i < PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT; i++) { + scanline_buffers[i].core.data = (uint32_t *) calloc(PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS, sizeof(uint32_t)); + scanline_buffers[i].core.data_max = PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS; +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + scanline_buffers[i].core.data2 = (uint32_t *) calloc(PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS, sizeof(uint32_t)); + scanline_buffers[i].core.data2_max = PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS; +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + scanline_buffers[i].core.data3 = (uint32_t *) calloc(PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS, sizeof(uint32_t)); + scanline_buffers[i].core.data3_max = PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS; +#endif +#endif + scanline_buffers[i].next = i != PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT - 1 ? &scanline_buffers[i + 1] : NULL; + } + + shared_state.free_list.free_list = &scanline_buffers[0]; + // shared state init complete - probably overkill + __mem_fence_release(); + +#ifndef ENABLE_VIDEO_CLOCK_DOWN + video_assert(timing->clock_freq == video_clock_freq); +#else + video_clock_down = video_clock_freq / timing->clock_freq; + video_assert( video_clock_down * timing->clock_freq == video_clock_freq); +#endif + + setup_sm(PICO_SCANVIDEO_SCANLINE_SM); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + setup_sm(PICO_SCANVIDEO_SCANLINE_SM2); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + setup_sm(PICO_SCANVIDEO_SCANLINE_SM3); +#endif +#endif + setup_sm(TIMING_SM); + + video_assert(mode->width * mode->xscale <= timing->h_active); + video_assert(mode->height * mode->yscale <= timing->v_active); + + uint16_t program[32]; + if (!mode->pio_program->adapt_for_mode(mode->pio_program, mode, &missing_scanline_buffer.core, program, + count_of(program))) { + video_assert(false); + } + video_assert(missing_scanline_buffer.core.data && missing_scanline_buffer.core.data_used); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + video_assert(missing_scanline_buffer.core.data2 && missing_scanline_buffer.core.data2_used); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + video_assert(missing_scanline_buffer.core.data3 && missing_scanline_buffer.core.data3_used); +#endif +#endif + missing_scanline_buffer.core.status = SCANLINE_OK; + +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + int program_wait_index = -1; +#endif +#if defined(PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY) || !defined(DISABLE_VIDEO_ASSERTIONS) + for(int i = 0; i < mode->pio_program->program_size; i++) { + if (program[i] == PIO_WAIT_IRQ4) { +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + video_assert(program_wait_index == -1); + program_wait_index = i; +#endif + } + } +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + video_assert(program_wait_index != -1); + shared_state.scanline_program_wait_index = program_wait_index; +#endif +#endif + + pio_load_program(video_pio, program, mode->pio_program->program_size, 0); + + uint32_t side_set_xor = 0; + static_assert(count_of(video_dbi_control_program) <= count_of(program), "too big"); + __builtin_memcpy(program, video_dbi_control_program, count_of(video_dbi_control_program) * sizeof(uint16_t)); + + if (timing->clock_polarity) { + side_set_xor = 0x1000; // flip the top side set bit + + for (int i = 0; i < count_of(program); i++) { + program[i] ^= side_set_xor; + } + } + + pio_load_program(video_pio, program, count_of(video_dbi_control_program), video_dbi_control_load_offset); + + // todo priorities should be correct anyway... + irq_set_priority(PIO0_IRQ_1, 0x40); // lower priority by 1 + irq_set_priority(DMA_IRQ_0, 0x80); // lower priority by 2 + + // todo merge these calls + dma_enable_irq0(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, true); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + dma_enable_irq0(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, true); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + dma_enable_irq0(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3, true); +#endif +#endif + // also done in video_timing_enable +// video_pio->inte1 = 1u << (TIMING_SM + PIO_IRQ1_INTE_SM0_TXNFULL_LSB); + + // todo reset DMA channels + + dma_configure( + PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, + 0, // src + (uint32_t) & video_pio->txf[PICO_SCANVIDEO_SCANLINE_SM], // dest + SIZE_32, + DMA_INCR, + DMA_NOINCR, + 0, // len (set later) + DREQ_PIO0_TX0 + + PICO_SCANVIDEO_SCANLINE_SM // Select scanline dma dreq to be PICO_SCANVIDEO_SCANLINE_SM TX FIFO not full + ); +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA + dma_chain_to(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL); // individual buffers chain back to master + dma_set_quiet(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, true); + dma_configure_full( + 0, // src +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + (uintptr_t) &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)->al3_transfer_count, // ch DMA config (target "ring" buffer size 8) - this is (transfer_count, read_addr trigger) +#else + (uintptr_t) &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)->al3_read_addr_trig, // ch DMA config (target "ring" buffer size 4) - this is (read_addr trigger) +#endif + SIZE_32, + DMA_INCR, + DMA_INCR, +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + 2, // send 2 words to ctrl block of data chain per transfer +#else + 1, +#endif + DREQ_FORCE, + PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL, // no chain as we trigger the data channel via _trig reg + 1, +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + 3, // wrap the write at 8 bytes (so each transfer writes the same 2 word ctrl registers) +#else + 2, // wrap the write at 4 bytes (so each transfer writes the same ctrl register) +#endif + true, // enable + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL) + ); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + dma_configure( + PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, + 0, // src + (uint32_t)&video_pio->txf[PICO_SCANVIDEO_SCANLINE_SM2], // dest + SIZE_32, + DMA_INCR, + DMA_NOINCR, + 0, // len + DREQ_PIO0_TX0 + PICO_SCANVIDEO_SCANLINE_SM2// Select scanline2 dma dreq to be PICO_SCANVIDEO_SCANLINE_SM2 TX FIFO not full + ); +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA +#if !PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + static_assert(false); +#endif + dma_chain_to(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2); // individual buffers chain back to master + dma_set_quiet(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, true); + dma_configure_full( + 0, // src + (uintptr_t) &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2)->al3_transfer_count, // ch DMA config (target "ring" buffer size 8) - this is (transfer_count, read_addr trigger) + SIZE_32, + DMA_INCR, + DMA_INCR, + 2, // send 2 words to ctrl block of data chain per transfer + DREQ_FORCE, + PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2, // no chain as we trigger the data channel via _trig reg + 1, + 3, // wrap the write at 8 bytes (so each transfer writes the same 2 word ctrl registers) + true, // enable + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2) + ); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 +#if PICO_SCANVIDEO_PLANE3_FRAGMENT_DMA + static_assert(false); +#endif + dma_configure( + PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3, + 0, // src + (uint32_t)&video_pio->txf[PICO_SCANVIDEO_SCANLINE_SM3], // dest + SIZE_32, + DMA_INCR, + DMA_NOINCR, + 0, // len + DREQ_PIO0_TX0 + PICO_SCANVIDEO_SCANLINE_SM3// Select scanline3 dma dreq to be PICO_SCANVIDEO_SCANLINE_SM3 TX FIFO not full + ); +#endif +#endif + + dma_configure( + TIMING_DMA_CHANNEL, + 0, // src + (uint32_t) & video_pio->txf[TIMING_SM], // dest + SIZE_32, + DMA_INCR, + DMA_NOINCR, + 0, // len (set later) + DREQ_PIO0_TX0 + TIMING_SM // Select scanline dma dreq to be PICO_SCANVIDEO_SCANLINE_SM TX FIFO not full + ); + + // clear scanline irq + pio_sm_exec(video_pio, TIMING_SM, pio_encode_irq_clear(4, false)); + + timing_state.v_total = timing->v_total; + timing_state.v_active = timing->v_active; + timing_state.v_pulse_start = timing->v_active + timing->v_front_porch; + timing_state.v_pulse_end = timing_state.v_pulse_start + timing->v_pulse; + + tft_driver_init(); + gpio_set_mask(7u << 19u); + for (uint i = 0; i < 16; i++) { + gpio_funcsel(i, GPIO_FUNC_PIO0); + } + gpio_funcsel(WR_PIN, GPIO_FUNC_PIO0); + gpio_funcsel(CS_PIN, GPIO_FUNC_PIO0); + gpio_funcsel(RS_PIN, GPIO_FUNC_PIO0); + gpio_clr_mask(7u << 19u); +// gpio_funcsel(RST_PIN, GPIO_FUNC_PIO0); + return true; +} + +bool video_24mhz_composable_adapt_for_mode(const struct video_pio_program *program, const struct video_mode *mode, + struct scanline_buffer *missing_scanline_buffer, uint16_t *buffer, + uint buffer_max) { + int delay0 = 2 * mode->xscale - 2; + int delay1 = delay0 + 1; + video_assert(delay0 <= 31); + video_assert(delay1 <= 31); + + video_assert(buffer_max >= program->program_size); + __builtin_memcpy(buffer, program->program, program->program_size * sizeof(uint16_t)); + + // todo macro-ify this + buffer[video_24mhz_composable_program_extern(delay_a_1)] |= (unsigned) delay1 << 8u; + buffer[video_24mhz_composable_program_extern(delay_b_1)] |= (unsigned) delay1 << 8u; + buffer[video_24mhz_composable_program_extern(delay_c_0)] |= (unsigned) delay0 << 8u; + buffer[video_24mhz_composable_program_extern(delay_d_0)] |= (unsigned) delay0 << 8u; + buffer[video_24mhz_composable_program_extern(delay_e_0)] |= (unsigned) delay0 << 8u; + buffer[video_24mhz_composable_program_extern(delay_f_1)] |= (unsigned) delay1 << 8u; +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE + buffer[video_24mhz_composable_program_extern(delay_g_0)] |= (unsigned) delay0 << 8u; +#else + int delay_half = mode->xscale - 2; + buffer[video_24mhz_composable_program_extern(delay_g_0)] |= (unsigned)delay_half << 8u; +#endif + buffer[video_24mhz_composable_program_extern(delay_h_0)] |= (unsigned) delay0 << 8u; + +#if !PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA + missing_scanline_buffer->data = missing_scanline_data; + missing_scanline_buffer->data_used = missing_scanline_buffer->data_max = sizeof(missing_scanline_data) / 4; +#else +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + missing_scanline_buffer->data = variable_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data_used = missing_scanline_buffer->data_max = sizeof(variable_fragment_missing_scanline_data_chain) / 4; +#else + missing_scanline_buffer->data = fixed_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data_used = missing_scanline_buffer->data_max = sizeof(fixed_fragment_missing_scanline_data_chain) / 4; +#endif +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#if !PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA + missing_scanline_buffer->data2 = missing_scanline_data_overlay; + missing_scanline_buffer->data2_used = missing_scanline_buffer->data2_max = sizeof(missing_scanline_data_overlay) / 4; +#else +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + missing_scanline_buffer->data2 = variable_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data2_used = missing_scanline_buffer->data2_max = sizeof(variable_fragment_missing_scanline_data_chain) / 4; +#else + missing_scanline_buffer->data2 = fixed_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data2_used = missing_scanline_buffer->data2_max = sizeof(fixed_fragment_missing_scanline_data_chain) / 4; +#endif +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + missing_scanline_buffer->data3 = missing_scanline_data_overlay; + missing_scanline_buffer->data3_used = missing_scanline_buffer->data3_max = sizeof(missing_scanline_data_overlay) / 4; +#endif +#endif + return true; +} + +bool video_default_adapt_for_mode(const struct video_pio_program *program, const struct video_mode *mode, + uint16_t *buffer, uint buffer_max) { + if (buffer_max >= program->program_size) { + __builtin_memcpy(buffer, program->program, program->program_size * sizeof(uint16_t)); + return true; + } + return false; +} + +void video_default_configure_pio(pio_hw_t *pio, uint sm, uint wrap_trarget, uint wrap, bool overlay) { + const uint BASE = 0; //sm == PICO_SCANVIDEO_SCANLINE_SM ? 0 : 5; + pio_set_consecutive_pindirs(pio, sm, BASE, 16, true); + pio_setup_pinctrl(pio, sm, BASE, 16, 0, 0, 0, 0); + pio_sm_exec(pio, sm, pio_encode_set_pins(0)); + pio_fifo_join(pio, sm, PIO_FIFO_JOIN_TX); // give the program as much time as we can + pio_set_wrap(pio, sm, wrap_trarget, wrap); + if (overlay) { + pio_setup_out_special(pio, sm, 1, 1, 5); + } else { + pio_setup_out_special(pio, sm, 1, 0, 0); + } +} + +void video_24mhz_composable_configure_pio(pio_hw_t *pio, uint sm) { + video_default_configure_pio(pio, sm, video_24mhz_composable_wrap_target, video_24mhz_composable_wrap, + sm != PICO_SCANVIDEO_SCANLINE_SM); +} + +void video_timing_enable(bool enable) { + // todo we need to protect our state here... this can't be frame synced obviously (at least turning on) + // todo but we should make sure we clear out state when we turn it off, and probably reset scanline counter when we turn it on + if (enable != video_timing_enabled) { + // todo should we disable these too? if not move to video_setup + video_pio->inte0 = PIO_IRQ0_INTE_SM0_BITS;// | PIO_IRQ0_INTE_SM1_BITS; +// video_pio->inte1 = (1u << (TIMING_SM + PIO_IRQ1_INTE_SM0_TXNFULL_LSB)); + irq_enable_mask((1u << PIO0_IRQ_0) + //|(1u << PIO0_IRQ_1) +// |(1u << DMA_IRQ_0) + , enable); + uint32_t sm_mask = (1u << PICO_SCANVIDEO_SCANLINE_SM) | 1u << TIMING_SM; +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + sm_mask |= 1u << PICO_SCANVIDEO_SCANLINE_SM2; +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + sm_mask |= 1u << PICO_SCANVIDEO_SCANLINE_SM3; +#endif +#endif + pio_sm_enable_mask(video_pio, sm_mask, false); + + if (enable) { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, pio_encode_jmp(video_mode.pio_program->entry_point)); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM2, pio_encode_jmp(video_mode.pio_program->entry_point)); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM3, pio_encode_jmp(video_mode.pio_program->entry_point)); +#endif +#endif + pio_sm_exec(video_pio, TIMING_SM, + pio_encode_jmp(video_dbi_control_load_offset + video_dbi_control_offset_entry_point)); + pio_sm_enable_mask(video_pio, sm_mask, true); + } + video_timing_enabled = enable; + } +} + +uint32_t video_wait_for_scanline_complete(uint32_t scanline_id) { +// // next_scanline_id is potentially the scanline_id in progress, so we need next_scanline_id to +// // be more than the scanline_id after the passed one +// scanline_id = scanline_id_after(scanline_id); +// uint32_t frame = frame_number(scanline_id); +// uint32_t next_scanline_id; +// // scanline_id > video_get_next_scanline_id() but with wrapping support +// while (0 < (scanline_id - (next_scanline_id = video_get_next_scanline_id()))) { +// // we may end up waiting for the next scanline while in vblank; the one we are waiting for is clearly done +// if (video_in_vblank() && (frame_number(next_scanline_id) - frame) >= 1) +// break; +// assert(video_timing_enabled); // todo should we just return +// __wfe(); +// } +// return next_scanline_id; + assert(false); +} + +void video_wait_for_vblank() { + sem_acquire(&vblank_begin); +} + +#ifndef NDEBUG + +// todo this is for composable only atm +void validate_scanline(const uint32_t *dma_data, uint dma_data_size, + uint max_pixels, uint expected_width) { + const uint16_t *it = (uint16_t *) dma_data; + assert(!(3u & (uintptr_t) dma_data)); + const uint16_t *const dma_data_end = (uint16_t * )(dma_data + dma_data_size); + uint16_t *pixel_buffer = 0; + const uint16_t *const pixels_end = (uint16_t * )(pixel_buffer + max_pixels); + uint16_t *pixels = pixel_buffer; + bool ok = false; + bool done = false; + bool had_black = false; + do { + uint16_t cmd = *it++; + switch (cmd) { + case video_24mhz_composable_program_extern(end_of_scanline_skip_word_ALIGN): + it++; + // fall thru + case video_24mhz_composable_program_extern(end_of_scanline_ALIGN): + done = ok = true; + break; + case video_24mhz_composable_program_extern(color_run): { + it++; + uint16_t len = *it++; + for (int i = 0; i < len + 3; i++) { + assert(pixels < pixels_end); + pixels++; + } + break; + } + case video_24mhz_composable_program_extern(raw_run): { + assert(pixels < pixels_end); + pixels++; + it++; + uint16_t len = *it++; + for (int i = 0; i < len + 2; i++) { + assert(pixels < pixels_end); + pixels++; + it++; + } + break; + } + case video_24mhz_composable_program_extern(raw_2p): + assert(pixels < pixels_end); + pixels++; + it++; + // fall thru + case video_24mhz_composable_program_extern(raw_1p): + if (pixels == pixels_end) { + assert(!had_black); + uint c = *it++; + assert(!c); // must end with black + had_black = true; + } else { + assert(pixels < pixels_end); + pixels++; + it++; + } + break; +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE + case video_24mhz_composable_program_extern(raw_1p_skip_word_ALIGN): + assert(pixels < pixels_end); + pixels++; + it++; + break; +#else + case video_24mhz_composable_program_extern(raw_1p_2cycle): + { + assert(pixels < pixels_end); + uint c = *it++; + had_black= !c; + break; + } +#endif + default: + assert(false); + done = true; + } + } while (!done); + assert(ok); + assert(it == dma_data_end); + assert(!(3u & (uintptr_t)(it))); // should end on dword boundary + assert(!expected_width || pixels == pixel_buffer + + expected_width); // with the correct number of pixels (one more because we stick a black pixel on the end) + assert(had_black); +} + +#endif diff --git a/src/rp2_common/pico_scanvideo_dpi/CMakeLists.txt b/src/rp2_common/pico_scanvideo_dpi/CMakeLists.txt new file mode 100644 index 0000000..ebae8a0 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dpi/CMakeLists.txt @@ -0,0 +1,13 @@ +if (TARGET pico_scanvideo) + add_library(pico_scanvideo_dpi INTERFACE) + + pico_generate_pio_header(pico_scanvideo_dpi ${CMAKE_CURRENT_LIST_DIR}/timing.pio) + + target_sources(pico_scanvideo_dpi INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/scanvideo.c + ) + + target_include_directories(pico_scanvideo_dpi INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) + target_compile_definitions(pico_scanvideo_dpi INTERFACE VIDEO_DPI) + target_link_libraries(pico_scanvideo_dpi INTERFACE hardware_dma hardware_pio hardware_irq pico_scanvideo) +endif() \ No newline at end of file diff --git a/src/rp2_common/pico_scanvideo_dpi/include/pico/scanvideo.h b/src/rp2_common/pico_scanvideo_dpi/include/pico/scanvideo.h new file mode 100644 index 0000000..70d24d7 --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dpi/include/pico/scanvideo.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PICO_SCANVIDEO_H_ +#define PICO_SCANVIDEO_H_ + +// note that defining to false will force non-inclusion also +#if !defined(PICO_SCANVIDEO_DPI) +#define PICO_SCANVIDEO_DPI 1 + +#ifndef PARAM_ASSERTIONS_ENABLED_SCANVIDEO_DPI +#define PARAM_ASSERTIONS_ENABLED_SCANVIDEO_DPI 0 +#endif + +#include "pico/scanvideo/scanvideo_base.h" + +#ifndef PICO_SCANVIDEO_DPI_ALPHA_PIN +#define PICO_SCANVIDEO_DPI_ALPHA_PIN 5u +#endif + +#ifndef PICO_SCANVIDEO_DPI_PIXEL_RSHIFT +#define PICO_SCANVIDEO_DPI_PIXEL_RSHIFT 0u +#endif + +#ifndef PICO_SCANVIDEO_DPI_PIXEL_GSHIFT +#define PICO_SCANVIDEO_DPI_PIXEL_GSHIFT 6u +#endif + +#ifndef PICO_SCANVIDEO_DPI_PIXEL_BSHIFT +#define PICO_SCANVIDEO_DPI_PIXEL_BSHIFT 11u +#endif + +#ifndef PICO_SCANVIDEO_ALPHA_PIN +#define PICO_SCANVIDEO_ALPHA_PIN PICO_SCANVIDEO_DPI_ALPHA_PIN +#endif + +#ifndef PICO_SCANVIDEO_PIXEL_RSHIFT +#define PICO_SCANVIDEO_PIXEL_RSHIFT PICO_SCANVIDEO_DPI_PIXEL_RSHIFT +#endif + +#ifndef PICO_SCANVIDEO_PIXEL_GSHIFT +#define PICO_SCANVIDEO_PIXEL_GSHIFT PICO_SCANVIDEO_DPI_PIXEL_GSHIFT +#endif + +#ifndef PICO_SCANVIDEO_PIXEL_BSHIFT +#define PICO_SCANVIDEO_PIXEL_BSHIFT PICO_SCANVIDEO_DPI_PIXEL_BSHIFT +#endif + +/** \file scanvideo.h + * \defgroup pico_scanvideo_dpi pico_scanvideo_dpi + * + * DPI Scan-out Video using the PIO + */ + +#endif +#endif diff --git a/src/rp2_common/pico_scanvideo_dpi/scanvideo.c b/src/rp2_common/pico_scanvideo_dpi/scanvideo.c new file mode 100644 index 0000000..0d23ffc --- /dev/null +++ b/src/rp2_common/pico_scanvideo_dpi/scanvideo.c @@ -0,0 +1,1813 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#pragma GCC push_options + +#if !PICO_SCANVIDEO_DEBUG_IMPL +#undef PARAM_ASSERTIONS_DISABLE_ALL +#define PARAM_ASSERTIONS_DISABLE_ALL 1 +#pragma GCC optimize("O3") +#endif + +#include +#include +#include "pico/platform.h" +#include "pico/sem.h" +#include "pico/util/buffer.h" +#include "hardware/clocks.h" +#include "hardware/dma.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" +#include "hardware/irq.h" +#include "timing.pio.h" +#include "pico/scanvideo.h" +#include "pico/scanvideo/composable_scanline.h" +#include "hardware/structs/bus_ctrl.h" +#include "pico/binary_info.h" + +#if PICO_SCANVIDEO_PLANE_COUNT > 3 +#error only up to 3 planes supported +#endif + +// PICO_CONFIG: PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY, Enable/disable video recovery,type=bool, default=1, group=video +#ifndef PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY +#define PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY 1 +#endif + +// PICO_CONFIG: PICO_SCANVIDEO_ADJUST_BUS_PRIORITY, Enable/disable adjust bus priority, type=bool, default=0, group=video +#ifndef PICO_SCANVIDEO_ADJUST_BUS_PRIORITY +#define PICO_SCANVIDEO_ADJUST_BUS_PRIORITY 0 +#endif + +// PICO_CONFIG: PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS, Enable/disable scanline assertions, type=bool, default=0, group=video +#ifndef PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS +#define PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS 0 +#endif + +// PICO_CONFIG: PICO_SCANVIDEO_DEBUG_IMPL, Enable/disable debug implementation, type=bool, default=0, group=video +#ifndef PICO_SCANVIDEO_DEBUG_IMPL +#define PICO_SCANVIDEO_DEBUG_IMPL 0 +#endif + +// PICO_CONFIG: PICO_SCANVIDEO_NO_DMA_TRACKING, Enable/disable DMA tracking, type=bool, default=0, group=video +#ifndef PICO_SCANVIDEO_NO_DMA_TRACKING +#define PICO_SCANVIDEO_NO_DMA_TRACKING 0 +#endif + +#define PICO_SCANVIDEO_SCANLINE_SM 0u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL 0u + +// PICO_CONFIG: PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA, Enable/disable plane 1 DMA fragments, type=bool, default=0, group=video +#ifndef PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA 0 +#endif + +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA +#define PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL 3u +#endif + +#define PICO_SCANVIDEO_TIMING_SM 3u +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#define PICO_SCANVIDEO_SCANLINE_SM2 1u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2 1u + +// PICO_CONFIG: PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA, Enable/disable plane 2 DMA fragments, type=bool, default=0, group=video +#ifndef PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA +#define PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA 0 +#endif + +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA +#define PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2 4u +#endif + +#if PICO_SCANVIDEO_PLANE_COUNT > 2 +#define PICO_SCANVIDEO_SCANLINE_SM3 2u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3 2u +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK ((1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL) | ( 1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2) | ( 1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3)) +#else +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK ((1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL) | ( 1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2)) +#endif +#else +#define PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK (1u << PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL) +#endif + +// todo add ability to shift scanline back a bit (we already have the timing, but it should be a post mode set adjustment) +// we can use this to allow some initial work in the scanline b4 the first pixel (e.g. a dummy black pixel) + +// todo bad state recovery +// - stress test with pause/unpause +// - bad state should cause SCANLINE_ASSERTION_ERROR +// - possible orphaned in_use - perhaps clean up when error state is detected +// - if PIO is not in the right place, pause/clear FIFO join-unjoin/jmp/resume +// - dma may need to be cancelled +// todo dma chaining support + + +// == DEBUGGING ========= + +// note that this is very very important if you see things going wrong with the display, +// however beware, because it will also cause visual artifiacts if we are pushing the edge of the envelope +// since it itself uses cycles that are in short supply! This is why it is off by default +// +// todo note, it should eventually be difficult to get the display into a bad state (even +// with things like runaway scanline program; incomplete DMA etc.. which currently break it). + +CU_REGISTER_DEBUG_PINS(video_timing, video_dma_buffer, video_irq, video_dma_completion, video_generation, + video_recovery, video_in_use, video_link) + +// ---- select at most one --- +//CU_SELECT_DEBUG_PINS(video_recovery) +//CU_SELECT_DEBUG_PINS(video_generation) +//CU_SELECT_DEBUG_PINS(video_in_use) +//CU_SELECT_DEBUG_PINS(scanvideo_timing) +//CU_SELECT_DEBUG_PINS(video_irq) +//CU_SELECT_DEBUG_PINS(video_dma_buffer) +//CU_SELECT_DEBUG_PINS(video_dma_completion) +//CU_SELECT_DEBUG_PINS(video_link) + +// ====================== + +#if PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS +// we want some sort of assertion even in release builds +#ifndef NDEBUG +#define scanline_assert(x) assert(x) +#else +#define scanline_assert(x) hard_assert(x) +#endif +#else +#define scanline_assert(x) (void)0 +#endif + +#define video_pio pio0 + +#if PICO_SCANVIDEO_ADJUST_BUS_PRIORITY +#define VIDEO_ADJUST_BUS_PRIORITY_VAL (BUSCTRL_BUS_PRIORITY_PROC0_BITS | BUSCTRL_BUS_PRIORITY_PROC1_BITS) +#endif + +#ifdef VIDEO_MOST_TIME_CRITICAL_CODE_SECTION +#define __video_most_time_critical_func(x) __attribute__((section(__XSTRING(VIDEO_MOST_TIME_CRITICAL_CODE_SECTION) "." x))) x +#else +#define __video_most_time_critical_func(x) __not_in_flash_func(x) +#endif + +#ifdef VIDEO_TIME_CRITICAL_CODE_SECTION +#define __video_time_critical_func(x) __attribute__((section(__XSTRING(VIDEO_TIME_CRITICAL_CODE_SECTION) "." x))) x +#else +#define __video_time_critical_func(x) __not_in_flash_func(x) +#endif +// --- video_24mhz_composable --- + +#define video_24mhz_composable_program __CONCAT(video_24mhz_composable_prefix, _program) +#define video_24mhz_composable_wrap_target __CONCAT(video_24mhz_composable_prefix, _wrap_target) +#define video_24mhz_composable_wrap __CONCAT(video_24mhz_composable_prefix, _wrap) + +bool video_24mhz_composable_adapt_for_mode(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode, + scanvideo_scanline_buffer_t *missing_scanline_buffer, + uint16_t *modifiable_instructions); + +pio_sm_config video_24mhz_composable_configure_pio(pio_hw_t *pio, uint sm, uint offset); + +const scanvideo_pio_program_t video_24mhz_composable = { + .program = &video_24mhz_composable_program, + .entry_point = video_24mhz_composable_program_extern(entry_point), + .adapt_for_mode = video_24mhz_composable_adapt_for_mode, + .configure_pio = video_24mhz_composable_configure_pio +}; + +#define PIO_WAIT_IRQ4 pio_encode_wait_irq(1, false, 4) +static uint8_t video_htiming_load_offset; +static uint8_t video_program_load_offset; + +// --- video timing stuff + +// 4 possible instructions; index into program below +enum { + SET_IRQ_0 = 0u, + SET_IRQ_1 = 1u, + SET_IRQ_SCANLINE = 2u, + CLEAR_IRQ_SCANLINE = 3u, +}; + +static struct { + int32_t v_active; + int32_t v_total; + int32_t v_pulse_start; + int32_t v_pulse_end; + // todo replace with plain polarity + uint32_t vsync_bits_pulse; + uint32_t vsync_bits_no_pulse; + + uint32_t a, a_vblank, b1, b2, c, c_vblank; + uint32_t vsync_bits; + uint16_t dma_state_index; + int32_t timing_scanline; +} timing_state; + +#define DMA_STATE_COUNT 4 +static uint32_t dma_states[DMA_STATE_COUNT]; + +// todo get rid of this altogether +#undef PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN +#define PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN 1 + +#if PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN +static uint16_t video_clock_down; +#endif + +semaphore_t vblank_begin; + +// --- scanline stuff +// private representation of scanline buffer (adds link for one list this scanline buffer is currently in) +typedef struct full_scanline_buffer { + scanvideo_scanline_buffer_t core; + struct full_scanline_buffer *next; +} full_scanline_buffer_t; + +// each scanline_buffer should be in exactly one of the shared_state lists below +// (unless we don't have USE_SCANLINE_DEBUG in which case we don't keep the generating list, +// in which case the scanline is entirely trusted to the client when generating) +full_scanline_buffer_t scanline_buffers[PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT]; + +// This state is sensitive as it it accessed by either core, and multiple IRQ handlers which may be re-entrant +// Nothing in here should be touched except when protected by the appropriate spin lock. +// +// The separations by spin lock (other than the need for spin locks to protect state consistency) is to allow +// safe concurrent operation by both cores, client, IRQ and nested IRQ (pre-emption) where desirable due +// to timing concerns. +static struct { + struct { + spin_lock_t *lock; + // note in_use is a list as we are lazy in removing buffers from it + full_scanline_buffer_t *in_use_ascending_scanline_id_list; + // pointer to the tail element of the list for making appending by ascending scanline id quick + full_scanline_buffer_t *in_use_ascending_scanline_id_list_tail; + } in_use; + + struct { + spin_lock_t *lock; + full_scanline_buffer_t *current_scanline_buffer; + uint32_t last_scanline_id; + uint32_t next_scanline_id; + // 0 based index of y repeat... goes 0, 0, 0 in non scaled mode, 0, 1, 0, 1 in doubled etc. + uint16_t y_repeat_index; + uint16_t y_repeat_target; + bool in_vblank; + // This generated list is in this struct because it is accessed together in fsb latching + // and the only other place it is used in scanvideo_end_scanline_generation which needs no other + // locks (i.e. we are saving an extra lock in the latch case by not placing in a separate struct) + full_scanline_buffer_t *generated_ascending_scanline_id_list; + full_scanline_buffer_t *generated_ascending_scanline_id_list_tail; +#if PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS + full_scanline_buffer_t *generating_list; +#endif + } scanline; + + struct { + spin_lock_t *lock; + full_scanline_buffer_t *free_list; + } free_list; + + // This is access by DMA IRQ and by SM IRQs + struct { + spin_lock_t *lock; +#if !PICO_SCANVIDEO_NO_DMA_TRACKING + // bit mask of completed DMA scanline channels + uint32_t dma_completion_state; +#endif + // number of buffers to release (may be multiple due to interrupt pre-emption) + // todo combine these two fields + uint8_t buffers_to_release; + bool scanline_in_progress; + } dma; + + // these are not updated, so not locked +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + int scanline_program_wait_index; +#endif +} shared_state; + +// PICO_CONFIG: PICO_SCANVIDEO_MISSING_SCANLINE_COLOR, Define colour used for missing scanlines, default=PICO_SVIDEO_PIXEL_FROM_RGB8(0,0,255), group=video +#ifndef PICO_SCANVIDEO_MISSING_SCANLINE_COLOR +#define PICO_SCANVIDEO_MISSING_SCANLINE_COLOR PICO_SCANVIDEO_PIXEL_FROM_RGB8(0,0,255) +#endif +static uint32_t _missing_scanline_data[] = + { + COMPOSABLE_COLOR_RUN | (PICO_SCANVIDEO_MISSING_SCANLINE_COLOR << 16u), + /*width-3*/ 0u | (COMPOSABLE_RAW_1P << 16u), + 0u | (COMPOSABLE_EOL_ALIGN << 16u) + }; + +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA +static uint32_t variable_fragment_missing_scanline_data_chain[] = { + count_of(_missing_scanline_data), + 0, // missing_scanline_data, + 0, + 0, +}; +#endif + +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA +static uint32_t fixed_fragment_missing_scanline_data_chain[] = { + 0, // missing_scanline_data, + 0, +}; +#endif + +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +uint32_t missing_scanline_data_overlay[] = { + // blank line + 0u | (COMPOSABLE_EOL_ALIGN << 16u) +}; +#endif + +static full_scanline_buffer_t _missing_scanline_buffer; + +static inline bool is_scanline_after(uint32_t scanline_id1, uint32_t scanline_id2) { + return ((int32_t) (scanline_id1 - scanline_id2)) > 0; +} + +static void prepare_for_active_scanline_irqs_enabled(); + +static void scanline_dma_complete_irqs_enabled(); + +static void setup_sm(int sm, uint offset); + +// -- MISC stuff +static scanvideo_mode_t video_mode; +static bool video_timing_enabled = false; +static bool display_enabled = true; + +static scanvideo_scanline_repeat_count_fn _scanline_repeat_count_fn; + +inline static void list_prepend(full_scanline_buffer_t **phead, full_scanline_buffer_t *fsb) { + scanline_assert(fsb); + scanline_assert(fsb->next == NULL); + scanline_assert(fsb != *phead); + fsb->next = *phead; + *phead = fsb; +} + +inline static void list_prepend_all(full_scanline_buffer_t **phead, full_scanline_buffer_t *to_prepend) { + full_scanline_buffer_t *fsb = to_prepend; + + // todo should this be assumed? + if (fsb) { + while (fsb->next) { + fsb = fsb->next; + } + + fsb->next = *phead; + *phead = to_prepend; + } +} + +inline static full_scanline_buffer_t *list_remove_head(full_scanline_buffer_t **phead) { + full_scanline_buffer_t *fsb = *phead; + + if (fsb) { + *phead = fsb->next; + fsb->next = NULL; + } + + return fsb; +} + +inline static full_scanline_buffer_t *list_remove_head_ascending(full_scanline_buffer_t **phead, + full_scanline_buffer_t **ptail) { + full_scanline_buffer_t *fsb = *phead; + + if (fsb) { + *phead = fsb->next; + + if (!fsb->next) { + scanline_assert(*ptail == fsb); + *ptail = NULL; + } else { + fsb->next = NULL; + } + } + + return fsb; +} + +inline static void list_remove(full_scanline_buffer_t **phead, full_scanline_buffer_t *fsb) { + scanline_assert(*phead); + full_scanline_buffer_t *prev = *phead; + + if (prev == fsb) { + *phead = fsb->next; + } else { + while (prev->next && prev->next != fsb) { + prev = prev->next; + } + + scanline_assert(prev->next == fsb); + prev->next = fsb->next; + } + + // todo do we need this without assertions? + fsb->next = NULL; +} + +static inline uint32_t scanline_id_after(uint32_t scanline_id) { + uint32_t tmp = scanline_id & 0xffffu; + + if (tmp < video_mode.height - 1) { + return scanline_id + 1; + } else { + return scanline_id + 0x10000u - tmp; + } +} + +// todo add a tail for these already sorted lists as we generally insert on the end +inline static void list_insert_ascending(full_scanline_buffer_t **phead, full_scanline_buffer_t **ptail, + full_scanline_buffer_t *fsb) { + scanline_assert(fsb->next == NULL); + scanline_assert(fsb != *phead); + scanline_assert(fsb != *ptail); + + if (!*phead || !is_scanline_after(fsb->core.scanline_id, (*phead)->core.scanline_id)) { + if (!*phead) { + scanline_assert(!*ptail); + *ptail = fsb; + } + + // insert at the beginning + list_prepend(phead, fsb); + } else { + if (is_scanline_after(fsb->core.scanline_id, (*ptail)->core.scanline_id)) { + // insert at end + (*ptail)->next = fsb; + *ptail = fsb; + } else { + // not after + full_scanline_buffer_t *prev = *phead; + + while (prev->next && is_scanline_after(fsb->core.scanline_id, prev->next->core.scanline_id)) { + prev = prev->next; + } + + scanline_assert(prev != *ptail); // we should have already inserted at the end in this case + fsb->next = prev->next; + prev->next = fsb; + } + } +} + +inline static void free_local_free_list_irqs_enabled(full_scanline_buffer_t *local_free_list) { + if (local_free_list) { + uint32_t save = spin_lock_blocking(shared_state.free_list.lock); + DEBUG_PINS_SET(video_timing, 4); + list_prepend_all(&shared_state.free_list.free_list, local_free_list); + DEBUG_PINS_CLR(video_timing, 4); + spin_unlock(shared_state.free_list.lock, save); + // note also this is useful for triggering scanvideo_wait_for_scanline_complete check + __sev(); + } +} + +// Caller must own scanline_state_spin_lock +inline static full_scanline_buffer_t *scanline_locked_try_latch_fsb_if_null_irqs_disabled( + full_scanline_buffer_t **local_free_list) { + // note this just checks that someone owns it not necessarily this core. + scanline_assert(is_spin_locked(shared_state.scanline.lock)); + full_scanline_buffer_t *fsb = shared_state.scanline.current_scanline_buffer; + + if (!fsb) { + // peek the head + while (NULL != (fsb = shared_state.scanline.generated_ascending_scanline_id_list)) { + if (!is_scanline_after(shared_state.scanline.next_scanline_id, fsb->core.scanline_id)) { + if (shared_state.scanline.next_scanline_id == fsb->core.scanline_id) { + full_scanline_buffer_t __unused *dbg = list_remove_head_ascending( + &shared_state.scanline.generated_ascending_scanline_id_list, + &shared_state.scanline.generated_ascending_scanline_id_list_tail); + scanline_assert(dbg == fsb); + spin_lock_unsafe_blocking(shared_state.in_use.lock); + DEBUG_PINS_SET(video_timing, 2); + DEBUG_PINS_XOR(video_in_use, 1); + list_insert_ascending(&shared_state.in_use.in_use_ascending_scanline_id_list, + &shared_state.in_use.in_use_ascending_scanline_id_list_tail, fsb); + DEBUG_PINS_CLR(video_timing, 2); + spin_unlock_unsafe(shared_state.in_use.lock); + shared_state.scanline.current_scanline_buffer = fsb; + } else { + fsb = NULL; + } + + break; + } else { + // scanline is in the past + full_scanline_buffer_t __unused *dbg = list_remove_head_ascending( + &shared_state.scanline.generated_ascending_scanline_id_list, + &shared_state.scanline.generated_ascending_scanline_id_list_tail); + scanline_assert(dbg == fsb); + list_prepend(local_free_list, fsb); +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS + full_scanline_buffer_t *fsb2; + while(NULL != (fsb2 = (full_scanline_buffer_t *)fsb->core.link)) { + fsb->core.link = NULL; + DEBUG_PINS_SET(video_link, 2); + list_prepend(local_free_list, fsb2); + DEBUG_PINS_CLR(video_link, 2); + fsb = fsb2; + } +#endif + } + } + } + + return fsb; +} + +static inline void release_scanline_irqs_enabled(int buffers_to_free_count, + full_scanline_buffer_t **local_free_list) { + if (buffers_to_free_count) { + uint32_t save = spin_lock_blocking(shared_state.in_use.lock); + while (buffers_to_free_count--) { + DEBUG_PINS_SET(video_dma_buffer, 2); + // We always discard the head which is the oldest + DEBUG_PINS_XOR(video_in_use, 2); + full_scanline_buffer_t *fsb = list_remove_head_ascending( + &shared_state.in_use.in_use_ascending_scanline_id_list, + &shared_state.in_use.in_use_ascending_scanline_id_list_tail); + list_prepend(local_free_list, fsb); +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS + full_scanline_buffer_t *fsb2; + while(NULL != (fsb2 = (full_scanline_buffer_t *)fsb->core.link)) { + fsb->core.link = NULL; + DEBUG_PINS_SET(video_link, 2); + list_prepend(local_free_list, fsb2); + DEBUG_PINS_CLR(video_link, 2); + fsb = fsb2; + } +#endif + DEBUG_PINS_CLR(video_dma_buffer, 2); + } + spin_unlock(shared_state.in_use.lock, save); + } +} + +static inline bool update_dma_transfer_state_irqs_enabled(bool cancel_if_not_complete, + int *scanline_buffers_to_release) { + uint32_t save = spin_lock_blocking(shared_state.dma.lock); + if (!shared_state.dma.scanline_in_progress) { +#if !PICO_SCANVIDEO_NO_DMA_TRACKING + assert(!shared_state.dma.dma_completion_state); +#endif + assert(!shared_state.dma.buffers_to_release); + spin_unlock(shared_state.dma.lock, save); + return true; + } +#if !PICO_SCANVIDEO_NO_DMA_TRACKING + uint32_t old_completed = shared_state.dma.dma_completion_state; + uint32_t new_completed; + while (0 != (new_completed = dma_hw->ints0 & PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK)) { + scanline_assert(!(old_completed & new_completed)); + // clear interrupt flags + dma_hw->ints0 = new_completed; + DEBUG_PINS_SET(video_dma_completion, new_completed); + DEBUG_PINS_CLR(video_dma_completion, new_completed); + new_completed |= old_completed; + if (new_completed == PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK) { + // tell caller to free these buffers... note it is safe to release any outstanding ones + // as only one DMA transfer can be logically in process and we have just finished that + // if the number is > 1 this is due to IRQ / preemption (todo this comment is out of date) + *scanline_buffers_to_release = shared_state.dma.buffers_to_release; + // we have taken ownership of releasing all the current ones + shared_state.dma.buffers_to_release = 0; + if (*scanline_buffers_to_release) { + // now that ISR clearing is protected by lock and also done by the active_scanline start + // we cannot have nesting + scanline_assert(*scanline_buffers_to_release == 1); + DEBUG_PINS_SET(video_dma_completion, 1); + DEBUG_PINS_CLR(video_dma_completion, 1); + } + shared_state.dma.dma_completion_state = shared_state.dma.scanline_in_progress = 0; + spin_unlock(shared_state.dma.lock, save); + return true; + } else { + DEBUG_PINS_SET(video_dma_completion, 2); + DEBUG_PINS_CLR(video_dma_completion, 2); + shared_state.dma.dma_completion_state = old_completed = new_completed; + } + } + // can't cancel yet, note if dma_buffers_to_release = 0 then completion DID happen (todo is this ever the case) + if (cancel_if_not_complete) { +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + if (shared_state.dma.buffers_to_release) { + shared_state.dma.dma_completion_state = shared_state.dma.scanline_in_progress = 0; + *scanline_buffers_to_release = shared_state.dma.buffers_to_release; + shared_state.dma.buffers_to_release = 0; + DEBUG_PINS_XOR(video_in_use, 4); + } + dma_channel_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + dma_channel_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + dma_channel_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3); +#endif +#endif +#else + panic("need VIDEO_RECOVERY"); +#endif + } + spin_unlock(shared_state.dma.lock, save); + return cancel_if_not_complete; +#else + if (shared_state.dma.buffers_to_release) { + shared_state.dma.scanline_in_progress = 0; + *scanline_buffers_to_release = shared_state.dma.buffers_to_release; + shared_state.dma.buffers_to_release = 0; + dma_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + dma_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + dma_channel_abort(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3); +#endif +#endif + } + spin_unlock(shared_state.dma.lock, save); + return false; +#endif +} + +#if !PICO_SCANVIDEO_NO_DMA_TRACKING + +static inline void scanline_dma_complete_irqs_enabled() { + // The DMA interrupt may be pre-empted by SM IRQ interrupt at any point, so it is possible even in non multi plane + // that this IRQ handler is not the one that is responsible for dealing with the end of the transfer. + // In the multi plane case, there are to different DMAs to worry about which may or may not both be complete + // by the time we get here with one having completed. + DEBUG_PINS_SET(video_dma_completion, 4); + int buffers_to_free_count = 0; + bool is_completion_trigger = update_dma_transfer_state_irqs_enabled(false, &buffers_to_free_count); + full_scanline_buffer_t *local_free_list = NULL; + if (is_completion_trigger) { + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 1); + // We make an early attempt to latch a scanline buffer to save time later in the PIO SM IRQ handler + // Of course there may not be a buffer ready yet, or we may have been pre-empty by the PIO SM IRQ handler + // already, in which case fsb will be non null + scanline_locked_try_latch_fsb_if_null_irqs_disabled(&local_free_list); // do an early attempt to latch + DEBUG_PINS_CLR(video_timing, 1); + spin_unlock(shared_state.scanline.lock, save); + } + + // because IRQs are enabled, we may obviously be pre-empted before or between either of these + release_scanline_irqs_enabled(buffers_to_free_count, &local_free_list); + free_local_free_list_irqs_enabled(local_free_list); + DEBUG_PINS_CLR(video_dma_completion, 4); +} + +#endif + +static void set_next_scanline_id(uint32_t scanline_id) { + shared_state.scanline.next_scanline_id = scanline_id; + shared_state.scanline.y_repeat_target = _scanline_repeat_count_fn(scanline_id) * video_mode.yscale; +} + +void __video_most_time_critical_func(prepare_for_active_scanline_irqs_enabled)() { + // note we are now only called in active display lines.. + DEBUG_PINS_SET(video_timing, 1); + full_scanline_buffer_t *local_free_list = NULL; + int buffers_to_free_count = 0; + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); + // VERY IMPORTANT: THIS CODE CAN ONLY TAKE ABOUT 4.5 us BEFORE LAUNCHING DMA... + // ... otherwise our scanline will be shifted over (because we will have started display) + // + // to alleviate this somewhat, we let the dma_complete alsu do a check for current_scanline_buffer == null, and look for a completed scanlines + // In ideal case the dma complete IRQ handler will have been able to set current_scanline_buffer for us (or indeed this is a y scaled mode + // and we are repeating a line)... in either case we will come in well under time budget + full_scanline_buffer_t *fsb = scanline_locked_try_latch_fsb_if_null_irqs_disabled(&local_free_list); + + spin_unlock(shared_state.scanline.lock, save); + DEBUG_PINS_CLR(video_timing, 1); + if (fsb) { + if (fsb->core.scanline_id != shared_state.scanline.next_scanline_id) { + // removed to allow for other video modes; not worth abstracting that far... + // also; we basically never see this color anyway! +// ((uint16_t *) (missing_scanline_data))[1] = 0x03e0; + // note: this should be in the future + fsb = &_missing_scanline_buffer; + } + } else { + // removed to allow for other video modes; not worth abstracting that far... +// ((uint16_t *)(missing_scanline_data))[1] = 0x001f; + // this is usually set by latch + fsb = &_missing_scanline_buffer; + } + + update_dma_transfer_state_irqs_enabled(true, &buffers_to_free_count); + +// DEBUG_PINS_SET(video_irq, 2); + // bit of overkill (to reset src_addr) for y scale repeat lines, but then again those should already have data. but this is now + // required in case current_scanline_buffer was set by the dma complete handler, in which case current_scanline_buffer was null when we got to the test above + + // don't need to reset anything put the CB pointer to start a reload? as we have already configured the rest + // note DMA should already be aborted by here. +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + if (!pio_sm_is_tx_fifo_empty(video_pio, PICO_SCANVIDEO_SCANLINE_SM)) { + pio_sm_clear_fifos(video_pio, PICO_SCANVIDEO_SCANLINE_SM); + } + if (video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].instr != PIO_WAIT_IRQ4) { + // hmm the problem here is we don't know if we should wait or not, because that is purely based on timing.. + // - if irq not posted, and we wait: GOOD + // - if irq not posted and we don't wait: BAD. early line + // - if irq already posted, and we wait: BAD. blank line + // - id irq already posted, and we don't wait: GOOD + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, pio_encode_wait_irq(1, false, 4)); + if (pio_sm_is_exec_stalled(video_pio, PICO_SCANVIDEO_SCANLINE_SM)) { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, pio_encode_jmp(shared_state.scanline_program_wait_index)); + } else { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, + pio_encode_jmp(shared_state.scanline_program_wait_index + 1)); + } + } +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + if (!pio_sm_is_tx_fifo_empty(video_pio, PICO_SCANVIDEO_SCANLINE_SM2)) { + pio_sm_clear_fifos(video_pio, PICO_SCANVIDEO_SCANLINE_SM2); + } + if (video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM2].instr != PIO_WAIT_IRQ4) { + // hmm the problem here is we don't know if we should wait or not, because that is purely based on timing.. + // - if irq not posted, and we wait: GOOD + // - if irq not posted and we don't wait: BAD. early line + // - if irq already posted, and we wait: BAD. blank line + // - id irq already posted, and we don't wait: GOOD + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM2, pio_encode_wait_irq(1, false, 4)); + if (pio_sm_is_exec_stalled(video_pio, PICO_SCANVIDEO_SCANLINE_SM2)) { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM2, pio_encode_jmp(shared_state.scanline_program_wait_index)); + } else { + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM2, + pio_encode_jmp(shared_state.scanline_program_wait_index + 1)); + } + } + #endif +#endif +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)->al3_transfer_count = fsb->core.fragment_words; +#endif + //dma_channel_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL, (uintptr_t)fsb->core.data, (uint32_t) fsb->core.data_used); + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL)->al3_read_addr_trig = (uintptr_t)fsb->core.data; +#else + dma_channel_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, fsb->core.data, + (uint32_t) fsb->core.data_used); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA + dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2)->al3_read_addr_trig = (uintptr_t)fsb->core.data2; +#else + dma_channel_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, fsb->core.data2, (uint32_t) fsb->core.data2_used); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + dma_channel_transfer_from_buffer_now(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3, fsb->core.data3, (uint32_t) fsb->core.data3_used); +// scanline_assert(video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM3].addr == video_24mhz_composable_offset_end_of_scanline_ALIGN); +#endif +// scanline_assert(video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM2].addr == video_24mhz_composable_offset_end_of_scanline_ALIGN); +#endif +// scanline_assert(video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].addr == video_24mhz_composable_offset_end_of_scanline_ALIGN); +// DEBUG_PINS_CLR(video_irq, 2); + + save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 1); + shared_state.scanline.in_vblank = false; + bool was_correct_scanline = (fsb != &_missing_scanline_buffer); + bool free_scanline = false; + shared_state.scanline.y_repeat_index += video_mode.yscale_denominator; + if (shared_state.scanline.y_repeat_index >= shared_state.scanline.y_repeat_target) { + // pick up a new scanline next time around if we had the right one + if (was_correct_scanline) { + free_scanline = true; + } + + shared_state.scanline.y_repeat_index -= shared_state.scanline.y_repeat_target; + set_next_scanline_id(scanline_id_after(shared_state.scanline.next_scanline_id)); + shared_state.scanline.current_scanline_buffer = NULL; + } else if (!was_correct_scanline) { + // not at the the end of yscale, but the wrong (or missing) scanline anyway, so clear that + shared_state.scanline.current_scanline_buffer = NULL; +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS + } else if (fsb->core.link_after && !--fsb->core.link_after) { + assert(fsb->core.link); + spin_lock_unsafe_blocking(shared_state.in_use.lock); + DEBUG_PINS_SET(video_link, 1); + full_scanline_buffer_t *fsb2 = (full_scanline_buffer_t *)fsb->core.link; + fsb->core.link = NULL; // the linkee scanline is now tracked on its own, so shouldn't be freed with the linker + // we need to insert after the current item in the list, which is after fsb + fsb2->core.scanline_id = fsb->core.scanline_id; + fsb2->next = fsb->next; + fsb->next = fsb2; + if (fsb == shared_state.in_use.in_use_ascending_scanline_id_list_tail) { + shared_state.in_use.in_use_ascending_scanline_id_list_tail = fsb2; + } + DEBUG_PINS_CLR(video_link, 1); + spin_unlock_unsafe(shared_state.in_use.lock); + shared_state.scanline.current_scanline_buffer = fsb2; + free_scanline = true; +#endif + } + // safe to nest dma lock we never nest the other way + spin_lock_unsafe_blocking(shared_state.dma.lock); + shared_state.dma.scanline_in_progress = 1; + if (free_scanline) { + scanline_assert(!shared_state.dma.buffers_to_release); + shared_state.dma.buffers_to_release++; + } + spin_unlock_unsafe(shared_state.dma.lock); + DEBUG_PINS_CLR(video_timing, 1); + spin_unlock(shared_state.scanline.lock, save); + + // because IRQs are enabled, we may obviously be pre-empted before or between either of these + release_scanline_irqs_enabled(buffers_to_free_count, &local_free_list); + free_local_free_list_irqs_enabled(local_free_list); +} + +static void __video_time_critical_func(prepare_for_vblank_scanline_irqs_enabled)() { + bool signal = false; + + // To simplify logic below, clean up any active scanlines now + + // note we only really need to do this on the first time in vsync, however as a defensive (potential recovery) + // move we do it every time as it is cheap if nothing to do. + int buffers_to_free_count = 0; + update_dma_transfer_state_irqs_enabled(true, &buffers_to_free_count); + + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 1); + full_scanline_buffer_t *local_free_list = NULL; + + if (!shared_state.scanline.in_vblank) { + shared_state.scanline.in_vblank = true; + shared_state.scanline.y_repeat_index = 0; + + // generally this should already have wrapped, but may not have just after a sync + if (scanvideo_scanline_number(shared_state.scanline.next_scanline_id) != 0) { + // set up for scanline 0 of the next frame when we come out of vblank + shared_state.scanline.next_scanline_id = + (scanvideo_frame_number(shared_state.scanline.next_scanline_id) + 1u) << 16u; + shared_state.scanline.y_repeat_target = _scanline_repeat_count_fn(shared_state.scanline.next_scanline_id); + } + + + signal = true; + } + + if (!shared_state.scanline.current_scanline_buffer || is_scanline_after(shared_state.scanline.next_scanline_id, + shared_state.scanline.current_scanline_buffer->core.scanline_id)) { + // if we had a scanline buffer still (which was in the past, unset it and make sure it will be freed + // before we attempt to relatch which only does something when csb == NULL) + if (shared_state.scanline.current_scanline_buffer) { + buffers_to_free_count++; // make sure it gets removed from in_use list + shared_state.scanline.current_scanline_buffer = NULL; + } + // this will probably succeed, because we are buffering ahead of the actual beam position + scanline_locked_try_latch_fsb_if_null_irqs_disabled(&local_free_list); + } + + DEBUG_PINS_CLR(video_timing, 1); + spin_unlock(shared_state.scanline.lock, save); + + // because IRQs are enabled, we may obviously be pre-empted before or between either of these + release_scanline_irqs_enabled(buffers_to_free_count, &local_free_list); + + free_local_free_list_irqs_enabled(local_free_list); + + if (signal) { + sem_release(&vblank_begin); + } +} + +#define setup_dma_states_vblank() if (true) { dma_states[0] = timing_state.a_vblank; dma_states[1] = timing_state.b1; dma_states[2] = timing_state.b2; dma_states[3] = timing_state.c_vblank; } else __builtin_unreachable() +#define setup_dma_states_no_vblank() if (true) { dma_states[0] = timing_state.a; dma_states[1] = timing_state.b1; dma_states[2] = timing_state.b2; dma_states[3] = timing_state.c; } else __builtin_unreachable() + +static inline void top_up_timing_pio_fifo() { + // todo better irq reset ... we are seeing irq get set again, handled in this loop, then we re-enter here when we don't need to + // keep filling until SM3 TX is full + while (!(video_pio->fstat & (1u << (PICO_SCANVIDEO_TIMING_SM + PIO_FSTAT_TXFULL_LSB)))) { + DEBUG_PINS_XOR(video_irq, 1); + DEBUG_PINS_XOR(video_irq, 1); + pio_sm_put(video_pio, PICO_SCANVIDEO_TIMING_SM, dma_states[timing_state.dma_state_index] | timing_state.vsync_bits); + // todo simplify this now we have a1, a2, b, c + // todo display enable (only goes positive on start of screen) + + // todo right now we are fixed... make this generic for timing and improve + if (++timing_state.dma_state_index >= DMA_STATE_COUNT) { + timing_state.dma_state_index = 0; + timing_state.timing_scanline++; + + // todo check code and put these in a current state struct + if (timing_state.timing_scanline >= timing_state.v_active) { + if (timing_state.timing_scanline >= timing_state.v_total) { + timing_state.timing_scanline = 0; + // active display - gives irq 0 and irq 4 + setup_dma_states_no_vblank(); + } else if (timing_state.timing_scanline <= timing_state.v_pulse_end) { + if (timing_state.timing_scanline == timing_state.v_active) { + setup_dma_states_vblank(); + } else if (timing_state.timing_scanline == timing_state.v_pulse_start) { + timing_state.vsync_bits = timing_state.vsync_bits_pulse; + } else if (timing_state.timing_scanline == timing_state.v_pulse_end) { + timing_state.vsync_bits = timing_state.vsync_bits_no_pulse; + } + } + } + } + } +} + +void __isr __video_most_time_critical_func(isr_pio0_0)() { +#if PICO_SCANVIDEO_ADJUST_BUS_PRIORITY + bus_ctrl_hw->priority = VIDEO_ADJUST_BUS_PRIORITY_VAL; +#endif + + // handler for explicit PIO_IRQ0 from PICO_SCANVIDEO_TIMING_SM at a good time to start a DMA for a scanline + // this called once per scanline during non vblank + if (video_pio->irq & 1u) { + video_pio->irq = 1; + DEBUG_PINS_SET(video_irq, 1); + if (display_enabled) { + prepare_for_active_scanline_irqs_enabled(); + } + DEBUG_PINS_CLR(video_irq, 1); + } +#if PICO_SCANVIDEO_ADJUST_BUS_PRIORITY + bus_ctrl_hw->priority = 0; +#endif + // handler for explicit PIO_IRQ1 from PICO_SCANVIDEO_TIMING_SM at a good time to prepare for a scanline + // this is only called once per scanline during vblank + if (video_pio->irq & 2u) { +// video_pio->irq = 2; + video_pio->irq = 3; // we clear irq1 for good measure, in case we had gotten out of sync + DEBUG_PINS_SET(video_irq, 2); + prepare_for_vblank_scanline_irqs_enabled(); + DEBUG_PINS_CLR(video_irq, 2); + } +} + +// irq for PIO FIFO +void __isr __video_most_time_critical_func(isr_pio0_1)() { + DEBUG_PINS_SET(video_irq, 4); + top_up_timing_pio_fifo(); + DEBUG_PINS_CLR(video_irq, 4); +} + +#if !PICO_SCANVIDEO_NO_DMA_TRACKING + +// DMA complete +void __isr __video_time_critical_func(isr_dma_0)() { +#if PICO_SCANVIDEO_ADJUST_BUS_PRIORITY + bus_ctrl_hw->priority = VIDEO_ADJUST_BUS_PRIORITY_VAL; +#endif + DEBUG_PINS_SET(video_irq, 4); + scanline_dma_complete_irqs_enabled(); + DEBUG_PINS_CLR(video_irq, 4); +#if PICO_SCANVIDEO_ADJUST_BUS_PRIORITY + bus_ctrl_hw->priority = 0; +#endif +} + +#endif + +static inline bool is_scanline_sm(int sm) { +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + return sm == PICO_SCANVIDEO_SCANLINE_SM || sm == PICO_SCANVIDEO_SCANLINE_SM2 || sm == PICO_SCANVIDEO_SCANLINE_SM3; +#else + return sm == PICO_SCANVIDEO_SCANLINE_SM || sm == PICO_SCANVIDEO_SCANLINE_SM2; +#endif +#else + return sm == PICO_SCANVIDEO_SCANLINE_SM; +#endif +} + +void setup_sm(int sm, uint offset) { +#ifndef NDEBUG + printf("Setting up SM %d\n", sm); +#endif + + pio_sm_config config = is_scanline_sm(sm) ? video_mode.pio_program->configure_pio(video_pio, sm, offset) : + video_htiming_program_get_default_config(offset); + +#if PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN + sm_config_set_clkdiv_int_frac(&config, video_clock_down, 0); +#endif + + if (!is_scanline_sm(sm)) { + // enable auto-pull + sm_config_set_out_shift(&config, true, true, 32); + const uint BASE = PICO_SCANVIDEO_SYNC_PIN_BASE; // hsync and vsync are +0 and +1, clock is +2 + uint pin_count; +#if PICO_SCANVIDEO_ENABLE_DEN_PIN + pin_count = 3; + // 3 OUT pins and maybe 1 sideset pin following them +#else + // 2 OUT pins and 1 sideset pin following them + pin_count = 2; +#endif + sm_config_set_out_pins(&config, BASE, pin_count); +#if PICO_SCANVIDEO_ENABLE_DEN_PIN + // side set pin as well + sm_config_set_sideset_pins(&config, BASE + pin_count); + pin_count++; +#endif + pio_sm_set_consecutive_pindirs(video_pio, sm, BASE, pin_count, true); + } + + pio_sm_init(video_pio, sm, offset, &config); // now paused +} + +scanvideo_mode_t scanvideo_get_mode() { + return video_mode; +} + +extern uint32_t scanvideo_get_next_scanline_id() { + return *(volatile uint32_t *) &shared_state.scanline.next_scanline_id; +} + +extern bool scanvideo_in_hblank() { + // this is a close estimate + // return !*(volatile bool *) &shared_state.dma.scanline_in_progress; + + // better we can see if the PIO is waiting on IRQ 4 (which it is almost all of the time it isn't drawing pixels) + // + // note that currently we require even custom PIO scanline programs to use WAIT IRQ 4 to sync with start of scanline + return video_pio->sm[PICO_SCANVIDEO_SCANLINE_SM].instr == PIO_WAIT_IRQ4; +} + +extern bool scanvideo_in_vblank() { + return *(volatile bool *) &shared_state.scanline.in_vblank; +} + + +static uint default_scanvideo_scanline_repeat_count_fn(uint32_t scanline_id) { + return 1; +} + +extern scanvideo_scanline_buffer_t *__video_time_critical_func(scanvideo_begin_scanline_generation)( + bool block) { + full_scanline_buffer_t *fsb; + + DEBUG_PINS_SET(video_link, 1); + DEBUG_PINS_SET(video_generation, 1); + do { + uint32_t save = spin_lock_blocking(shared_state.free_list.lock); +// DEBUG_PINS_SET(video_timing, 4); + fsb = list_remove_head(&shared_state.free_list.free_list); +// DEBUG_PINS_CLR(video_timing, 4); + spin_unlock(shared_state.free_list.lock, save); + + if (fsb) { + save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 1); +#if PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS + list_prepend(&shared_state.scanline.generating_list, fsb); +#endif + // todo improve this algorithm... how far ahead should we be + // todo i.e. should we skip ahead a bit further if we are perpetually behind - doesn't really help because we'd + // todo be skipping some scanlines anyway; doesn't really matter which ones at that point + uint32_t scanline_id = shared_state.scanline.next_scanline_id; + + if (!is_scanline_after(scanline_id, shared_state.scanline.last_scanline_id)) { + // we are buffering ahead of the display + scanline_id = scanline_id_after(shared_state.scanline.last_scanline_id); + } + + fsb->core.scanline_id = shared_state.scanline.last_scanline_id = scanline_id; + DEBUG_PINS_CLR(video_timing, 1); + spin_unlock(shared_state.scanline.lock, save); + break; + } + + if (block) { + __wfe(); + } + } while (block); + + DEBUG_PINS_CLR(video_link, 1); + DEBUG_PINS_CLR(video_generation, 1); +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS + fsb->core.link_after = 0; +#endif + return (scanvideo_scanline_buffer_t *) fsb; +} + +// todo remove this in favor of using scanvideo_begin_scanline_generation_link +scanvideo_scanline_buffer_t *__video_time_critical_func(scanvideo_begin_scanline_generation2)(scanvideo_scanline_buffer_t **second, bool block) +{ + full_scanline_buffer_t *fsb; + + DEBUG_PINS_SET(video_generation, 1); + do + { + uint32_t save = spin_lock_blocking(shared_state.free_list.lock); + DEBUG_PINS_SET(video_timing, 4); + fsb = shared_state.free_list.free_list; + full_scanline_buffer_t *fsb2; + if (fsb) { + fsb2 = fsb->next; + if (fsb2) { + // unlink both + shared_state.free_list.free_list = fsb->next->next; + fsb->next = NULL; + fsb2->next = NULL; + } else { + fsb = NULL; + } + } else { + fsb = NULL; + } + DEBUG_PINS_CLR(video_timing, 4); + spin_unlock(shared_state.free_list.lock, save); + if (fsb) + { + save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 1); +#ifdef ENABLE_SCANLINE_ASSERTIONS + list_prepend(&shared_state.scanline.generating_list, fsb); + list_prepend(&shared_state.scanline.generating_list, fsb2); +#endif + // todo improve this algorithm... how far ahead should we be + // todo i.e. should we skip ahead a bit further if we are perpetually behind - doesn't really help because we'd + // todo be skipping some scanlines anyway; doesn't really matter which ones at that point + uint32_t scanline_id = shared_state.scanline.next_scanline_id; + + if (!is_scanline_after(scanline_id, shared_state.scanline.last_scanline_id)) + { + // we are buffering ahead of the display + scanline_id = scanline_id_after(shared_state.scanline.last_scanline_id); + } + + fsb->core.scanline_id = scanline_id; + scanline_id = scanline_id_after(scanline_id); + fsb2->core.scanline_id = shared_state.scanline.last_scanline_id = scanline_id; + DEBUG_PINS_CLR(video_timing, 1); + *second = &fsb2->core; + spin_unlock(shared_state.scanline.lock, save); + break; + } + + if (block) + { + DEBUG_PINS_SET(video_generation, 4); + __wfe(); + DEBUG_PINS_CLR(video_generation, 4); + } + } + while (block); + + DEBUG_PINS_CLR(video_generation, 1); + return (scanvideo_scanline_buffer_t *) fsb; +} + +#if PICO_SCANVIDEO_LINKED_SCANLINE_BUFFERS +extern scanvideo_scanline_buffer_t *__video_time_critical_func(scanvideo_begin_scanline_generation_linked)(uint n, + bool block) { + full_scanline_buffer_t *fsb; + + DEBUG_PINS_SET(video_link, 1); + DEBUG_PINS_SET(video_generation, 1); + do { + uint32_t save = spin_lock_blocking(shared_state.free_list.lock); +// DEBUG_PINS_SET(video_timing, 4); + fsb = shared_state.free_list.free_list; + if (fsb) { + full_scanline_buffer_t *fsb_tail = fsb; + for(uint i=1; inext; + } + if (fsb_tail) { + shared_state.free_list.free_list = fsb_tail->next; + fsb_tail->next = NULL; + } else { + fsb = NULL; + } + } +// DEBUG_PINS_CLR(video_timing, 4); + spin_unlock(shared_state.free_list.lock, save); + + if (fsb) { + full_scanline_buffer_t *fsb_tail = fsb; + while (fsb_tail) { + full_scanline_buffer_t *fsb_next = fsb_tail->next; + fsb_tail->core.link = fsb_next ? &fsb_next->core : NULL; + fsb_tail->next = NULL; + fsb_tail = fsb_next; + } + save = spin_lock_blocking(shared_state.scanline.lock); + DEBUG_PINS_SET(video_timing, 1); +#if PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS + list_prepend(&shared_state.scanline.generating_list, fsb); +#endif + // todo improve this algorithm... how far ahead should we be + // todo i.e. should we skip ahead a bit further if we are perpetually behind - doesn't really help because we'd + // todo be skipping some scanlines anyway; doesn't really matter which ones at that point + uint32_t scanline_id = shared_state.scanline.next_scanline_id; + + if (!is_scanline_after(scanline_id, shared_state.scanline.last_scanline_id)) { + // we are buffering ahead of the display + scanline_id = scanline_id_after(shared_state.scanline.last_scanline_id); + } + + shared_state.scanline.last_scanline_id = scanline_id; + scanvideo_scanline_buffer_t *fsb_core = &fsb->core; + while (fsb_core) { + fsb_core->scanline_id = scanline_id; + fsb_core = fsb_core->link; + } + DEBUG_PINS_CLR(video_timing, 1); + spin_unlock(shared_state.scanline.lock, save); + break; + } + + if (block) { + __wfe(); + } + } while (block); + + DEBUG_PINS_CLR(video_link, 1); + DEBUG_PINS_CLR(video_generation, 1); + fsb->core.link_after = 0; + return (scanvideo_scanline_buffer_t *) fsb; +} +#endif + +extern void __video_time_critical_func(scanvideo_end_scanline_generation)( + scanvideo_scanline_buffer_t *scanline_buffer) { + DEBUG_PINS_SET(video_generation, 2); + full_scanline_buffer_t *fsb = (full_scanline_buffer_t *) scanline_buffer; + uint32_t save = spin_lock_blocking(shared_state.scanline.lock); +#if PICO_SCANVIDEO_ENABLE_SCANLINE_ASSERTIONS + list_remove(&shared_state.scanline.generating_list, fsb); +#endif + list_insert_ascending(&shared_state.scanline.generated_ascending_scanline_id_list, + &shared_state.scanline.generated_ascending_scanline_id_list_tail, fsb); + spin_unlock(shared_state.scanline.lock, save); + DEBUG_PINS_CLR(video_generation, 2); +} + +//#pragma GCC pop_options + +void scanvideo_set_scanline_repeat_fn(scanvideo_scanline_repeat_count_fn fn) { + _scanline_repeat_count_fn = fn ? fn : default_scanvideo_scanline_repeat_count_fn; +} + +bool scanvideo_setup(const scanvideo_mode_t *mode) { + return scanvideo_setup_with_timing(mode, mode->default_timing); +} + +static pio_program_t copy_program(const pio_program_t *program, uint16_t *instructions, + uint32_t max_instructions) { + assert(max_instructions >= program->length); + pio_program_t copy = *program; + __builtin_memcpy(instructions, program->instructions, MIN(program->length, max_instructions) * sizeof(uint16_t)); + copy.instructions = instructions; + return copy; +} + +bool scanvideo_setup_with_timing(const scanvideo_mode_t *mode, const scanvideo_timing_t *timing) { + __builtin_memset(&shared_state, 0, sizeof(shared_state)); + // init non zero members + // todo pass scanline buffers and size, or allow client to allocate + shared_state.scanline.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_SCANLINE_LOCK); + shared_state.dma.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_DMA_LOCK); + shared_state.free_list.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_FREE_LIST_LOCK); + shared_state.in_use.lock = spin_lock_init(PICO_SPINLOCK_ID_VIDEO_IN_USE_LOCK); + shared_state.scanline.last_scanline_id = 0xffffffff; + + video_mode = *mode; + video_mode.default_timing = timing; + + static_assert(BPP == 16, ""); // can't do 8 bit now because of pixel count + // this is no longer necessary + //assert(!(mode->width & 1)); + if (!video_mode.yscale_denominator) video_mode.yscale_denominator = 1; + // todo is this still necessary? + //invalid_params_if(SCANVIDEO_DPI, (timing->v_active % mode->yscale)); + ((uint16_t *) (_missing_scanline_data))[2] = mode->width / 2 - 3; +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + variable_fragment_missing_scanline_data_chain[1] = native_safe_hw_ptr(_missing_scanline_data); +#endif +#if PICO_SCANVIDEO_PLANE1_FIXED_FRAGMENT_DMA || PICO_SCANVIDEO_PLANE2_FIXED_FRAGMENT_DMA + fixed_fragment_missing_scanline_data_chain[0] = native_safe_hw_ptr(_missing_scanline_data); +#endif + + sem_init(&vblank_begin, 0, 1); + + for (int i = 0; i < PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT; i++) { + mem_buffer_t b; + pico_buffer_alloc_in_place(&b, PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS * sizeof(uint32_t)); + scanline_buffers[i].core.data = (uint32_t *)b.bytes;// calloc(PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS, sizeof(uint32_t)); + scanline_buffers[i].core.data_max = PICO_SCANVIDEO_MAX_SCANLINE_BUFFER_WORDS; +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + scanline_buffers[i].core.data2 = (uint32_t *) calloc(PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS, sizeof(uint32_t)); + scanline_buffers[i].core.data2_max = PICO_SCANVIDEO_MAX_SCANLINE_BUFFER2_WORDS; +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + scanline_buffers[i].core.data3 = (uint32_t *) calloc(PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS, sizeof(uint32_t)); + scanline_buffers[i].core.data3_max = PICO_SCANVIDEO_MAX_SCANLINE_BUFFER3_WORDS; +#endif +#endif + scanline_buffers[i].next = i != PICO_SCANVIDEO_SCANLINE_BUFFER_COUNT - 1 ? &scanline_buffers[i + 1] : NULL; + } + + shared_state.free_list.free_list = &scanline_buffers[0]; + // shared state init complete - probably overkill + __mem_fence_release(); + + // todo this is incorrect + // set first 18 pins to PIO for vga/dpi +#if PICO_SCANVIDEO_ENABLE_DEN_PIN +#define MAX_PIN 20 + bi_decl_if_func_used(bi_4pins_with_names(PICO_SCANVIDEO_SYNC_PIN_BASE, "HSync", + PICO_SCANVIDEO_SYNC_PIN_BASE + 1, "VSync", + PICO_SCANVIDEO_SYNC_PIN_BASE + 2, "Display Enable", + PICO_SCANVIDEO_SYNC_PIN_BASE + 3, "Pixel Clock")); +#else +#define MAX_PIN 19 + bi_local_decl(bi_3pins_with_names(PICO_SCANVIDEO_SYNC_PIN_BASE, "HSync", + PICO_SCANVIDEO_SYNC_PIN_BASE + 1, "VSync", + PICO_SCANVIDEO_SYNC_PIN_BASE + 2, "Pixel Clock")); +#endif + + bi_decl_if_func_used(bi_pin_mask_with_name(0x1f << (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_DPI_PIXEL_RSHIFT), "Red 0-4")); + bi_decl_if_func_used(bi_pin_mask_with_name(0x1f << (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_DPI_PIXEL_GSHIFT), "Green 0-4")); + bi_decl_if_func_used(bi_pin_mask_with_name(0x1f << (PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_DPI_PIXEL_BSHIFT), "Blue 0-4")); + + + for (uint8_t i = 0; i < MAX_PIN; ++i) + gpio_set_function(i, GPIO_FUNC_PIO0); + +#if !PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN + valid_params_if(SCANVIDEO_DPI, timing->clock_freq == video_clock_freq); +#else + uint sys_clk = clock_get_hz(clk_sys); + video_clock_down = sys_clk / (2 * timing->clock_freq); + if (2 * video_clock_down * timing->clock_freq != sys_clk) { + panic("System clock (%d) must be an integer multiple of 2 times the requested pixel clock (%d).", sys_clk, timing->clock_freq); + } +#endif + + valid_params_if(SCANVIDEO_DPI, mode->width * mode->xscale <= timing->h_active); + valid_params_if(SCANVIDEO_DPI, mode->height * mode->yscale <= timing->v_active * video_mode.yscale_denominator); + + uint16_t instructions[32]; + pio_program_t modified_program = copy_program(mode->pio_program->program, instructions, + count_of(instructions)); + + if (!mode->pio_program->adapt_for_mode(mode->pio_program, mode, &_missing_scanline_buffer.core, instructions)) { + valid_params_if(SCANVIDEO_DPI, false); + } + valid_params_if(SCANVIDEO_DPI, _missing_scanline_buffer.core.data && _missing_scanline_buffer.core.data_used); + video_program_load_offset = pio_add_program(video_pio, &modified_program); + +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + int program_wait_index = -1; +#endif +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY || PARAM_ASSERTIONS_ENABLED(SCANVIDEO_DBI) + for (int i = 0; i < mode->pio_program->program->length; i++) { + if (instructions[i] == PIO_WAIT_IRQ4) { +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + valid_params_if(SCANVIDEO_DPI, program_wait_index == -1); + program_wait_index = i; +#endif + } + } +#if PICO_SCANVIDEO_ENABLE_VIDEO_RECOVERY + valid_params_if(SCANVIDEO_DPI, program_wait_index != -1); + shared_state.scanline_program_wait_index = program_wait_index; +#endif +#endif + +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + valid_params_if(SCANVIDEO_DPI,_missing_scanline_buffer.core.data2 && _missing_scanline_buffer.core.data2_used); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + valid_params_if(SCANVIDEO_DPI,_missing_scanline_buffer.core.data3 && _missing_scanline_buffer.core.data3_used); +#endif +#endif + _missing_scanline_buffer.core.status = SCANLINE_OK; + + setup_sm(PICO_SCANVIDEO_SCANLINE_SM, video_program_load_offset); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + setup_sm(PICO_SCANVIDEO_SCANLINE_SM2, video_program_load_offset); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + setup_sm(PICO_SCANVIDEO_SCANLINE_SM3, video_program_load_offset); +#endif +#endif + + uint32_t side_set_xor = 0; + modified_program = copy_program(&video_htiming_program, instructions, count_of(instructions)); + + if (timing->clock_polarity) { + side_set_xor = 0x1000; // flip the top side set bit + + for (uint i = 0; i < video_htiming_program.length; i++) { + instructions[i] ^= side_set_xor; + } + } + + video_htiming_load_offset = pio_add_program(video_pio, &modified_program); + + setup_sm(PICO_SCANVIDEO_TIMING_SM, video_htiming_load_offset); + + // make this highest priority +#if PICO_DEFAULT_IRQ_PRIORITY < 0x40 +#warning pico_scanvideo_dpi may not always function correctly without PIO_IRQ_0 at a higher priority than other interrupts. + irq_set_priority(PIO0_IRQ_1, 0x40); // lower priority by 1 + irq_set_priority(DMA_IRQ_0, 0x80); // lower priority by 2 +#else + irq_set_priority(PIO0_IRQ_0, 0); // highest priority + irq_set_priority(PIO0_IRQ_1, 0x40); // lower priority by 1 +#endif + + dma_claim_mask(PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK); + dma_set_irq0_channel_mask_enabled(PICO_SCANVIDEO_SCANLINE_DMA_CHANNELS_MASK, true); + // also done in scanvideo_timing_enable +// video_pio->inte1 = 1u << (PICO_SCANVIDEO_TIMING_SM + PIO_IRQ1_INTE_SM0_TXNFULL_LSB); + + // todo reset DMA channels + + dma_channel_config channel_config = dma_channel_get_default_config(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL); + channel_config_set_dreq(&channel_config, DREQ_PIO0_TX0 + + PICO_SCANVIDEO_SCANLINE_SM); // Select scanline dma dreq to be PICO_SCANVIDEO_SCANLINE_SM TX FIFO not full +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA + channel_config_set_chain_to(&channel_config, PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL); + channel_config_set_irq_quiet(&channel_config, true); +#endif + dma_channel_configure(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL, + &channel_config, + &video_pio->txf[PICO_SCANVIDEO_SCANLINE_SM], + NULL, // set later + 0, // set later + false); +#if PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA + dma_channel_config chain_config = dma_channel_get_default_config(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL); + channel_config_set_write_increment(&chain_config, true); + // configure write ring + channel_config_set_ring(&chain_config, true, +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + 3 // wrap the write at 8 bytes (so each transfer writes the same 2 word ctrl registers) +#else + 2 // wrap the write at 4 bytes (so each transfer writes the same ctrl register) +#endif + ); + dma_channel_configure(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL, + &chain_config, +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)->al3_transfer_count, // ch DMA config (target "ring" buffer size 8) - this is (transfer_count, read_addr trigger) +#else + &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL)->al3_read_addr_trig, // ch DMA config (target "ring" buffer size 4) - this is (read_addr trigger) +#endif + NULL, // set later +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + 2, // send 2 words to ctrl block of data chain per transfer +#else + 1, +#endif + false); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + channel_config = dma_channel_get_default_config(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2); + channel_config_set_dreq(&channel_config, DREQ_PIO0_TX0 + PICO_SCANVIDEO_SCANLINE_SM2); // Select scanline dma dreq to be PICO_SCANVIDEO_SCANLINE_SM TX FIFO not full +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA + channel_config_set_chain_to(&channel_config, PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2); + channel_config_set_irq_quiet(&channel_config, true); +#endif + dma_channel_configure(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2, + &channel_config, + (void *)&video_pio->txf[PICO_SCANVIDEO_SCANLINE_SM2], + NULL, // set later + 0, // set later + false); +#if PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA + dma_channel_config chain_config2 = dma_channel_get_default_config(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2); + channel_config_set_write_increment(&chain_config2, true); + // configure write ring + channel_config_set_ring(&chain_config2, true, +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + 3 // wrap the write at 8 bytes (so each transfer writes the same 2 word ctrl registers) +#else + 2 // wrap the write at 4 bytes (so each transfer writes the same ctrl register) +#endif + ); + dma_channel_configure(PICO_SCANVIDEO_SCANLINE_DMA_CB_CHANNEL2, + &chain_config2, +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2)->al3_transfer_count, // ch DMA config (target "ring" buffer size 8) - this is (transfer_count, read_addr trigger) +#else + &dma_channel_hw_addr(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL2)->al3_read_addr_trig, // ch DMA config (target "ring" buffer size 4) - this is (read_addr trigger) +#endif + NULL, // set later +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + 2, // send 2 words to ctrl block of data chain per transfer +#else + 1, +#endif + false); +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 +#if PICO_SCANVIDEO_PLANE3_FRAGMENT_DMA + static_assert(false); +#endif + channel_config = dma_channel_get_default_config(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3); + channel_config_set_dreq(&channel_config, DREQ_PIO0_TX0 + PICO_SCANVIDEO_SCANLINE_SM3); // Select scanline dma dreq to be PICO_SCANVIDEO_SCANLINE_SM TX FIFO not full + dma_channel_configure(PICO_SCANVIDEO_SCANLINE_DMA_CHANNEL3, + &channel_config, + (void *)&video_pio->txf[PICO_SCANVIDEO_SCANLINE_SM3], + NULL, // set later + 0, // set later + false); +#endif +#endif + + // clear scanline irq + pio_sm_exec(video_pio, PICO_SCANVIDEO_TIMING_SM, video_htiming_states_program.instructions[CLEAR_IRQ_SCANLINE]); + + // todo there are probably some restrictions :-) + //assert(timing->v_front_porch == 1); + //assert(timing->v_pulse == 2); + //assert(timing->v_total == 500); + + timing_state.v_total = timing->v_total; + timing_state.v_active = timing->v_active; + timing_state.v_pulse_start = timing->v_active + timing->v_front_porch; + timing_state.v_pulse_end = timing_state.v_pulse_start + timing->v_pulse; + const uint32_t vsync_bit = 0x40000000; + timing_state.vsync_bits_pulse = timing->v_sync_polarity ? 0 : vsync_bit; + timing_state.vsync_bits_no_pulse = timing->v_sync_polarity ? vsync_bit : 0; + + // these are read bitwise backwards (lsb to msb) by PIO pogram + + // we can probably do smaller +#define HTIMING_MIN 8 + +#define TIMING_CYCLE 3u +#define timing_encode(state, length, pins) ((video_htiming_states_program.instructions[state] ^ side_set_xor)| (((uint32_t)(length) - TIMING_CYCLE) << 16u) | ((uint32_t)(pins) << 29u)) +#define A_CMD SET_IRQ_0 +#define A_CMD_VBLANK SET_IRQ_1 +#define B1_CMD CLEAR_IRQ_SCANLINE +#define B2_CMD CLEAR_IRQ_SCANLINE +#define C_CMD SET_IRQ_SCANLINE +#define C_CMD_VBLANK CLEAR_IRQ_SCANLINE + + int h_sync_bit = timing->h_sync_polarity ? 0 : 1; + timing_state.a = timing_encode(A_CMD, 4, h_sync_bit); + static_assert(HTIMING_MIN >= 4, ""); + timing_state.a_vblank = timing_encode(A_CMD_VBLANK, 4, h_sync_bit); + int h_back_porch = timing->h_total - timing->h_front_porch - timing->h_pulse - timing->h_active; + + valid_params_if(SCANVIDEO_DPI, timing->h_pulse - 4 >= HTIMING_MIN); + timing_state.b1 = timing_encode(B1_CMD, timing->h_pulse - 4, h_sync_bit); + + // todo decide on what these should be - we should really be asserting the timings + // + // todo note that the placement of the active scanline IRQ from the timing program is super important. + // if it gets moved too much (or indeed at all) it may be that there are problems with DMA/SM IRQ + // overlap, which may require the addition of a separate timing state for the prepare for scanline + // (separate from the needs of setting the hsync pulse) + valid_params_if(SCANVIDEO_DPI, timing->h_active >= HTIMING_MIN); + //assert(timing->h_front_porch >= HTIMING_MIN); + valid_params_if(SCANVIDEO_DPI, h_back_porch >= HTIMING_MIN); + valid_params_if(SCANVIDEO_DPI, (timing->h_total - h_back_porch - timing->h_pulse) >= HTIMING_MIN); + timing_state.b2 = timing_encode(B2_CMD, h_back_porch, !h_sync_bit); + timing_state.c = timing_encode(C_CMD, timing->h_total - h_back_porch - timing->h_pulse, 4 | !h_sync_bit); + timing_state.c_vblank = timing_encode(C_CMD_VBLANK, timing->h_total - h_back_porch - timing->h_pulse, !h_sync_bit); + + // this is two scanlines in vblank + setup_dma_states_vblank(); + timing_state.vsync_bits = timing_state.vsync_bits_no_pulse; + scanvideo_set_scanline_repeat_fn(NULL); + return true; +} + +bool video_24mhz_composable_adapt_for_mode(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode, + scanvideo_scanline_buffer_t *missing_scanline_buffer, + uint16_t *modifiable_instructions) { + int delay0 = 2 * mode->xscale - 2; + int delay1 = delay0 + 1; + valid_params_if(SCANVIDEO_DPI, delay0 <= 31); + valid_params_if(SCANVIDEO_DPI, delay1 <= 31); + + // todo macro-ify this + modifiable_instructions[video_24mhz_composable_program_extern(delay_a_1)] |= (unsigned) delay1 << 8u; + modifiable_instructions[video_24mhz_composable_program_extern(delay_b_1)] |= (unsigned) delay1 << 8u; + modifiable_instructions[video_24mhz_composable_program_extern(delay_c_0)] |= (unsigned) delay0 << 8u; + modifiable_instructions[video_24mhz_composable_program_extern(delay_d_0)] |= (unsigned) delay0 << 8u; + modifiable_instructions[video_24mhz_composable_program_extern(delay_e_0)] |= (unsigned) delay0 << 8u; + modifiable_instructions[video_24mhz_composable_program_extern(delay_f_1)] |= (unsigned) delay1 << 8u; +#if !PICO_SCANVIDEO_USE_RAW1P_2CYCLE + modifiable_instructions[video_24mhz_composable_program_extern(delay_g_0)] |= (unsigned) delay0 << 8u; +#else + int delay_half = mode->xscale - 2; + modifiable_instructions[video_24mhz_composable_program_extern(delay_g_0)] |= (unsigned)delay_half << 8u; +#endif + modifiable_instructions[video_24mhz_composable_program_extern(delay_h_0)] |= (unsigned) delay0 << 8u; + +#if !PICO_SCANVIDEO_PLANE1_FRAGMENT_DMA + missing_scanline_buffer->data = _missing_scanline_data; + missing_scanline_buffer->data_used = missing_scanline_buffer->data_max = sizeof(_missing_scanline_data) / 4; +#else +#if PICO_SCANVIDEO_PLANE1_VARIABLE_FRAGMENT_DMA + missing_scanline_buffer->data = variable_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data_used = missing_scanline_buffer->data_max = sizeof(variable_fragment_missing_scanline_data_chain) / 4; +#else + missing_scanline_buffer->data = fixed_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data_used = missing_scanline_buffer->data_max = sizeof(fixed_fragment_missing_scanline_data_chain) / 4; +#endif +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 1 +#if !PICO_SCANVIDEO_PLANE2_FRAGMENT_DMA + missing_scanline_buffer->data2 = missing_scanline_data_overlay; + missing_scanline_buffer->data2_used = missing_scanline_buffer->data2_max = sizeof(missing_scanline_data_overlay) / 4; +#else +#if PICO_SCANVIDEO_PLANE2_VARIABLE_FRAGMENT_DMA + missing_scanline_buffer->data2 = variable_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data2_used = missing_scanline_buffer->data2_max = sizeof(variable_fragment_missing_scanline_data_chain) / 4; +#else + missing_scanline_buffer->data2 = fixed_fragment_missing_scanline_data_chain; + missing_scanline_buffer->data2_used = missing_scanline_buffer->data2_max = sizeof(fixed_fragment_missing_scanline_data_chain) / 4; +#endif +#endif +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + missing_scanline_buffer->data3 = missing_scanline_data_overlay; + missing_scanline_buffer->data3_used = missing_scanline_buffer->data3_max = sizeof(missing_scanline_data_overlay) / 4; +#endif +#endif + return true; +} + +bool video_default_adapt_for_mode(const scanvideo_pio_program_t *program, const scanvideo_mode_t *mode, + uint16_t *modifiable_instructions) { + return true; +} + +void scanvideo_default_configure_pio(pio_hw_t *pio, uint sm, uint offset, pio_sm_config *config, bool overlay) { + pio_sm_set_consecutive_pindirs(pio, sm, PICO_SCANVIDEO_COLOR_PIN_BASE, PICO_SCANVIDEO_COLOR_PIN_COUNT, true); + sm_config_set_out_pins(config, PICO_SCANVIDEO_COLOR_PIN_BASE, PICO_SCANVIDEO_COLOR_PIN_COUNT); + sm_config_set_out_shift(config, true, true, 32); // autopull + sm_config_set_fifo_join(config, PIO_FIFO_JOIN_TX); + if (overlay) { + sm_config_set_out_special(config, 1, 1, PICO_SCANVIDEO_ALPHA_PIN); + } else { + sm_config_set_out_special(config, 1, 0, 0); + } +} + +pio_sm_config video_24mhz_composable_configure_pio(pio_hw_t *pio, uint sm, uint offset) { + pio_sm_config config = video_24mhz_composable_default_program_get_default_config(offset); + scanvideo_default_configure_pio(pio, sm, offset, &config, sm != PICO_SCANVIDEO_SCANLINE_SM); + return config; +} + +void scanvideo_timing_enable(bool enable) { + // todo we need to protect our state here... this can't be frame synced obviously (at least turning on) + // todo but we should make sure we clear out state when we turn it off, and probably reset scanline counter when we turn it on + if (enable != video_timing_enabled) { + // todo should we disable these too? if not move to scanvideo_setup + video_pio->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS; + video_pio->inte1 = (1u << (PICO_SCANVIDEO_TIMING_SM + PIO_IRQ1_INTE_SM0_TXNFULL_LSB)); + irq_set_mask_enabled((1u << PIO0_IRQ_0) + | (1u << PIO0_IRQ_1) + #if !PICO_SCANVIDEO_NO_DMA_TRACKING + | (1u << DMA_IRQ_0) +#endif + , enable); + uint32_t sm_mask = (1u << PICO_SCANVIDEO_SCANLINE_SM) | 1u << PICO_SCANVIDEO_TIMING_SM; +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + sm_mask |= 1u << PICO_SCANVIDEO_SCANLINE_SM2; +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + sm_mask |= 1u << PICO_SCANVIDEO_SCANLINE_SM3; +#endif +#endif + pio_claim_sm_mask(video_pio, sm_mask); + pio_set_sm_mask_enabled(video_pio, sm_mask, false); +#if PICO_SCANVIDEO_ENABLE_VIDEO_CLOCK_DOWN + pio_clkdiv_restart_sm_mask(video_pio, sm_mask); +#endif + + if (enable) { + uint jmp = video_program_load_offset + pio_encode_jmp(video_mode.pio_program->entry_point); + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM, jmp); +#if PICO_SCANVIDEO_PLANE_COUNT > 1 + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM2, jmp); +#if PICO_SCANVIDEO_PLANE_COUNT > 2 + pio_sm_exec(video_pio, PICO_SCANVIDEO_SCANLINE_SM3, jmp); +#endif +#endif + // todo we should offset the addresses for the SM + pio_sm_exec(video_pio, PICO_SCANVIDEO_TIMING_SM, + pio_encode_jmp(video_htiming_load_offset + video_htiming_offset_entry_point)); + pio_set_sm_mask_enabled(video_pio, sm_mask, true); + } + video_timing_enabled = enable; + } +} + +uint32_t scanvideo_wait_for_scanline_complete(uint32_t scanline_id) { + // next_scanline_id is potentially the scanline_id in progress, so we need next_scanline_id to + // be more than the scanline_id after the passed one + scanline_id = scanline_id_after(scanline_id); + uint32_t frame = scanvideo_frame_number(scanline_id); + uint32_t next_scanline_id; + // scanline_id > scanvideo_get_next_scanline_id() but with wrapping support + while (0 < (scanline_id - (next_scanline_id = scanvideo_get_next_scanline_id()))) { + // we may end up waiting for the next scanline while in vblank; the one we are waiting for is clearly done + if (scanvideo_in_vblank() && (scanvideo_frame_number(next_scanline_id) - frame) >= 1) + break; + assert(video_timing_enabled); // todo should we just return + __wfe(); + } + return next_scanline_id; +} + +void scanvideo_wait_for_vblank() { + sem_acquire_blocking(&vblank_begin); +} + +#ifndef NDEBUG +// todo this is for composable only atm +void validate_scanline(const uint32_t *dma_data, uint dma_data_size, + uint max_pixels, uint expected_width) { + const uint16_t *it = (uint16_t *)dma_data; + assert(!(3u&(uintptr_t)dma_data)); + const uint16_t *const dma_data_end = (uint16_t *)(dma_data + dma_data_size); + uint16_t *pixel_buffer = 0; + const uint16_t *const pixels_end = (uint16_t *)(pixel_buffer + max_pixels); + uint16_t *pixels = pixel_buffer; + bool ok = false; + bool done = false; + bool had_black = false; + do { + uint16_t cmd = *it++; + switch (cmd) { + case video_24mhz_composable_program_extern(end_of_scanline_skip_word_ALIGN): + it++; + // fall thru + case video_24mhz_composable_program_extern(end_of_scanline_ALIGN): + done = ok = true; + break; + case video_24mhz_composable_program_extern(color_run): + { + it++; + uint16_t len = *it++; + for(int i=0; i +#include "pico/sd_card.h" +#include "hardware/pio.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "sd_card.pio.h" +#include "crc7.h" +#include "crc-itu-t.h" +#include "pico/binary_info.h" + +#define SD_CLK_SM 0u +#define SD_CMD_SM 1u +#define SD_DAT_SM 2u +// pins are hard-coded + +// todo this is very much a WIP - lots of hacked together stuff and test code that needs to be teased into an actual sensible library with error handling + +// todo note there is a lot of crud in here right now + +#if 0 +#define sd_debug(format, args...) printf(format, ## args) +#else +#define sd_debug(format,args...) (void)0 +#endif + +#define CMD(n) ((n)+0x40) + +static inline uint32_t sd_pio_cmd(uint cmd, uint32_t param) +{ + assert(cmd <= sd_cmd_or_dat_program.length); + assert(param <= 0xffff); + return (pio_encode_jmp(cmd) << 16u) | param; +} + +#define SD_PIO_CMD(a, b) +PIO sd_pio = pio1; + +static uint sd_dat_pin_base; // todo remove me +// todo struct these +static uint8_t rca_high, rca_low; +static enum bus_width {bw_unknown, bw_narrow, bw_wide} bus_width; + +const int sd_cmd_dma_channel = 11; +const int sd_data_dma_channel = 10; +const int sd_chain_dma_channel = 9; +const int sd_pio_dma_channel = 8; + +static bool allow_four_data_pins; + +struct message { + int len; + uint8_t msg[8]; +}; + +static inline void check_pio_debug(const char *s) { +#ifndef NDEBUG + static int counter = 0; + counter++; + uint32_t debug = sd_pio->fdebug & 0xffffff; + if (debug) { + printf("AWOOGA: %d %s %08x\n", counter, s, (uint)debug); + sd_pio->fdebug = debug; + } +#endif +} + +static inline uint64_t sd_make_command(uint8_t cmd, uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) +{ + cmd |= 0x40u; + uint8_t crc = 0; + crc = crc7_table[crc ^ cmd]; + crc = crc7_table[crc ^ b0]; + crc = crc7_table[crc ^ b1]; + crc = crc7_table[crc ^ b2]; + crc = crc7_table[crc ^ b3]; + + uint64_t rc = b3; + rc = (rc << 8u) | crc | 1u; // crc and stop bit + rc = (rc << 16u) | sd_cmd_or_dat_offset_no_arg_state_wait_high; + rc = (rc << 8u) | cmd; + rc = (rc << 8u) | b0; + rc = (rc << 8u) | b1; + rc = (rc << 8u) | b2; + return rc; +} + +inline static int safe_wait_tx_empty(pio_hw_t *pio, uint sm) { + int wooble = 0; + while (!pio_sm_is_tx_fifo_empty(pio, sm)) { + wooble++; + if (wooble > 1000000) { + check_pio_debug("stuck"); + printf("stuck %d @ %d\n", sm, (int)pio->sm[sm].addr); + __breakpoint(); + return SD_ERR_STUCK; + } + } + return SD_OK; +} + +inline static int safe_wait_tx_not_full(pio_hw_t *pio, uint sm) { + int wooble = 0; + while (pio_sm_is_tx_fifo_full(pio, sm)) { + wooble++; + if (wooble > 1000000) { + check_pio_debug("stuck"); + printf("stuck %d @ %d\n", sm, (int)pio->sm[sm].addr); + __breakpoint(); + return SD_ERR_STUCK; + } + } + return SD_OK; +} + +inline static int safe_dma_wait_for_finish(pio_hw_t *pio, uint sm, uint chan) { + int wooble = 0; + while (dma_channel_is_busy(chan)) { + wooble++; + if (wooble > 8000000) { + check_pio_debug("stuck dma"); + printf("stuck dma channel %d rem %08x %d @ %d\n", chan, (uint)dma_hw->ch[chan].transfer_count, sm, (int)pio->sm[sm].addr); + __breakpoint(); + return SD_ERR_STUCK; + } + } + return SD_OK; +} + + +static inline int acquiesce_sm(int sm) { + check_pio_debug("ac1"); + int rc = safe_wait_tx_empty(sd_pio, sm); + if (rc) return rc; + check_pio_debug("ac2"); + uint32_t foo = 0; + uint32_t timeout = 1000000; + while (--timeout) { + uint32_t addr = sd_pio->sm[sm].addr; + foo |= 1<rxf[sm], // src + word_length, + false + ); + if (sniff) + { + assert(sm == SD_DAT_SM); + dma_sniffer_enable(dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + dma_hw->sniff_data = 0; + } + dma_channel_start(dma_channel); + gpio_clr_mask(1); + return SD_OK; +} + +static void __time_critical_func(start_chain_dma_read_with_address_size_only)(uint sm, uint32_t *buf, bool bswap, bool sniff) { + assert(!sniff); // for now + dma_channel_config c = dma_channel_get_default_config(sd_data_dma_channel); + channel_config_set_bswap(&c, bswap); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_dreq(&c, DREQ_PIO1_RX0 + sm); + channel_config_set_chain_to(&c, sd_chain_dma_channel); // individual buffers chain back to master + channel_config_set_irq_quiet(&c, true); + + dma_channel_configure( + sd_data_dma_channel, + &c, + 0, // dest + &sd_pio->rxf[sm], // src + 0, + false + ); + + c = dma_channel_get_default_config(sd_chain_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, true); + channel_config_set_ring(&c, 1, 3); // wrap the write at 8 bytes (so each transfer writes the same 2 word ctrl registers) + dma_channel_configure( + sd_chain_dma_channel, + &c, + &dma_channel_hw_addr(sd_data_dma_channel)->al1_write_addr, // dest + buf, // src + 2, // send 2 words to ctrl block of data chain per transfer + false + ); + + gpio_set_mask(1); +// if (sniff) +// { +// dma_enable_sniffer(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16); +// dma_hw->sniff_data = 0; +// } + dma_channel_start(sd_chain_dma_channel); + gpio_clr_mask(1); +} + +static void __time_critical_func(start_chain_dma_read_with_full_cb)(uint sm, uint32_t *buf) { + dma_channel_config c = dma_get_channel_config(sd_data_dma_channel); + channel_config_set_chain_to(&c, sd_chain_dma_channel); // todo isn't this the case already + channel_config_set_irq_quiet(&c, true); // todo isn't this the case already + dma_channel_set_config(sd_data_dma_channel, &c, false); + + c = dma_channel_get_default_config(sd_chain_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, true); + channel_config_set_ring(&c, 1, 4); // wrap the write at 16 bytes + dma_channel_configure( + sd_chain_dma_channel, + &c, + &dma_channel_hw_addr(sd_data_dma_channel)->read_addr, // ch DMA config (target "ring" buffer size 16) - this is (read_addr, write_addr, transfer_count, ctrl), // dest + buf, // src + 4, // send 4 words to ctrl block of data chain per transfer + false + ); + gpio_set_mask(1); + dma_channel_start(sd_chain_dma_channel); + gpio_clr_mask(1); +} +static __attribute__((used)) __noinline void spoop() { + int dma_channel = 3; + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, true); + channel_config_set_dreq(&config, DREQ_SPI0_RX); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + dma_channel_set_config(dma_channel, &config, false); + + *(volatile uint32_t *)(DMA_BASE + DMA_CH3_AL1_CTRL_OFFSET) = 0x00089831; +} +static int __time_critical_func(start_read)(int sm, uint32_t *buf, uint byte_length, bool enable) +{ + spoop(); + int rc; + gpio_set_mask(1); + assert(!(3u & (uintptr_t)buf)); // in all fairness we should receive into a buffer from the pool + uint bit_length = byte_length * 8; + if (sm == SD_DAT_SM) { + assert(!(bit_length & 31u)); + bit_length += bus_width == bw_wide ? 64 : 16; + } + rc = safe_wait_tx_not_full(sd_pio, sm); + if (rc) return rc; + check_pio_debug("sd_repsone_dma"); + if (bus_width == bw_wide && sm != SD_CMD_SM) + { + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_receive_bits, bit_length / 4 - 1)); + pio_sm_set_wrap(sd_pio, sm, sd_cmd_or_dat_offset_wrap_target_for_4bit_receive, + sd_cmd_or_dat_offset_wrap_for_4bit_receive - + 1); // note -1since wrap values are on the last instruction before wrap + } + else + { + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_receive_bits, bit_length - 1)); + pio_sm_set_wrap(sd_pio, sm, sd_cmd_or_dat_wrap_target, sd_cmd_or_dat_wrap); + } + gpio_clr_mask(1); + gpio_set_mask(1); + if (enable) pio_sm_set_enabled(sd_pio, sm, true); + if (bit_length & 31u) + { + rc = safe_wait_tx_not_full(sd_pio, sm); + if (rc) return rc; + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, + pio_encode_in(pio_null, 32 - (bit_length & 31u)))); + } + // now go back to wait state + rc = safe_wait_tx_not_full(sd_pio, sm); + if (rc) return rc; + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp( + sm == SD_DAT_SM ? sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd + : sd_cmd_or_dat_offset_no_arg_state_wait_high))); + gpio_clr_mask(1); + return SD_OK; +} + +static int __time_critical_func(finish_read)(uint dma_channel, int sm, uint16_t *suffixed_crc, uint16_t *sniffed_crc) +{ + gpio_set_mask(1); + int rc = safe_dma_wait_for_finish(sd_pio, sm, dma_channel); + if (rc) return rc; + if (sniffed_crc) { + *sniffed_crc = (uint16_t)dma_hw->sniff_data; + } + if (sm == SD_DAT_SM) { + // todo not forever + while (pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + uint32_t w = sd_pio->rxf[SD_DAT_SM]; + if (suffixed_crc) *suffixed_crc = w >> 16u; + if (bus_width == bw_wide) { + while (pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + sd_pio->rxf[SD_DAT_SM]; + } + } + assert(pio_sm_is_rx_fifo_empty(sd_pio, sm)); + gpio_clr_mask(1); + return SD_OK; +} + +int __time_critical_func(sd_response_dma)(uint dma_channel, uint sm, uint32_t *buf, uint byte_length, bool bswap, uint16_t *suffixed_crc, uint16_t *sniffed_crc, bool first, bool last, bool enable) +{ + int rc = SD_OK; + if (first) { + rc = start_single_dma(dma_channel, sm, buf, byte_length, bswap, sniffed_crc != 0); + if (!rc) rc = start_read(sm, buf, byte_length, enable); + } + + if (!rc) rc = finish_read(dma_channel, sm, suffixed_crc, sniffed_crc); + + if (!last && !rc) { + if (!rc) rc = start_single_dma(dma_channel, sm, buf, byte_length, bswap, sniffed_crc != 0); + if (!rc) rc = start_read(sm, buf, byte_length, enable); + } + return rc; +} + +int __noinline sd_command(uint64_t packed_command, uint32_t *receive_buf, uint byte_length) +{ + int rc = acquiesce_sm(SD_CMD_SM); + if (rc) return rc; + sd_debug("SD command %d\n", 0x3fu & (((uint)packed_command) >> 24u)); + // disable SM so we don't have a race on us filling the FIFO - we must not stall or we will lose sync with clock + pio_sm_set_enabled(sd_pio, SD_CMD_SM, false); + pio_sm_put(sd_pio, SD_CMD_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_send_bits, 48 - 1)); + pio_sm_put(sd_pio, SD_CMD_SM, (uint32_t) packed_command); + pio_sm_put(sd_pio, SD_CMD_SM, (uint32_t) (packed_command >> 32u)); + // todo we know the recvlen based on the command + if (byte_length) + { + rc = sd_response_dma(sd_cmd_dma_channel, SD_CMD_SM, receive_buf, byte_length, false, NULL, NULL, true, true, true); + if (!rc) + { + uint32_t cmd = ((uint32_t) packed_command) >> 24u; + cmd &= 63u; + uint32_t w0 = receive_buf[0] >> 1u; + uint32_t w1 = (receive_buf[1] >> 1u) | (receive_buf[0] & 1u ? 0x80000000u : 0u); + bool ok = true; + switch (cmd) + { + case 2: + break; + case 41: + ok = (w0 & 0xff1e0000) == 0x3f000000; + ok &= (w1 >> 16) == 0xff; + break; + default: + { + if (cmd != w0 >> 24u) + { + printf("tsk\n"); + } + uint8_t crc = crc7_table[w0 >> 24u]; + crc = crc7_table[crc ^ (uint8_t) (w0 >> 16u)]; + crc = crc7_table[crc ^ (uint8_t) (w0 >> 8u)]; + crc = crc7_table[crc ^ (uint8_t) w0]; + crc = crc7_table[crc ^ (uint8_t) (w1 >> 24u)]; + if ((crc | 1u) != (uint8_t) (w1 >> 16u)) + { + panic("bad crc %02x != %02x\n", crc | 1u, (uint8_t) (w1 >> 16u)); + ok = false; + } + } + } + if (!ok) + { + printf("bad response from card\n"); + return SD_ERR_BAD_RESPONSE; + } + } + } else { + pio_sm_set_enabled(sd_pio, SD_CMD_SM, true); + } + sd_debug("SD command done %d\n", 0x3fu & (((uint)packed_command) >> 24u)); + return SD_OK; +} + +int sd_wait() +{ + int rc = acquiesce_sm(SD_DAT_SM); + if (!rc) + { + pio_sm_put(sd_pio, SD_DAT_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_set(pio_pindirs, 0))); + pio_sm_put(sd_pio, SD_DAT_SM, + sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_wait_pin(true, 0))); + pio_sm_put(sd_pio, SD_DAT_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, + pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high))); + rc = acquiesce_sm(SD_DAT_SM); + } + return rc; +} + +static inline void fixup_cmd_response_48(uint32_t *buf) +{ + uint32_t b0 = buf[0]; + buf[1] = __builtin_bswap32((buf[1] >> 1u) | ((b0 & 1u) << 31u)); + buf[0] = __builtin_bswap32(b0 >> 1u); +} + +const char *states[] = { + "idle", "ready", "ident", "stby", "tran", "data", "rcv", "prg", "dis", "(9)", "(a)", "(b)", "(c)", "(d)", "(e)", "(f)" +}; + +void print_status(uint32_t *response_buffer, bool needs_fixup) { + uint32_t r[2]; + const uint8_t *b; + if (needs_fixup) { + r[0] = response_buffer[0]; + r[1] = response_buffer[1]; + fixup_cmd_response_48(r); + b = (const uint8_t *)r; + } else { + b = (const uint8_t *)response_buffer; + } + if (b[1]&0x80) printf(" ORANGE"); + if (b[1]&0x40) printf(" ADDRESS"); + if (b[1]&0x20) printf(" BLEN"); + if (b[1]&0x10) printf(" ESEQ"); + if (b[1]&0x8) printf(" EPARM"); + if (b[1]&0x4) printf(" WPV"); + if (b[1]&0x2) printf(" LOCKED"); + if (b[1]&0x1) printf(" UNLOCK"); + if (b[2]&0x80) printf(" CRC"); + if (b[2]&0x40) printf(" ILLEGAL"); + if (b[2]&0x20) printf(" ECC"); + if (b[2]&0x10) printf(" INTERNAL"); + if (b[2]&0x8) printf(" << ERRORS: "); + if (b[3]&0x80) printf(" era_skip"); + if (b[3]&0x40) printf(" ecc_dis"); + if (b[3]&0x20) printf(" era_reset"); + printf(" %s", states[(b[3]>>1u)&0xfu]); + printf((b[3] & 1) ? " ready" : " not-ready"); + if (b[4] & 0x20) printf(" ACMD..."); + printf("\n"); +} + +void read_status(bool dump) +{ + uint32_t response_buffer[5]; + // let's see the status + sd_command(sd_make_command(13, rca_high, rca_low, 0, 0), response_buffer, 6); + fixup_cmd_response_48(response_buffer); + uint8_t *b = (uint8_t *)response_buffer; + printf("%02x %02x %02x %02x : %02x %02x\n", b[0], b[1], b[2], b[3], b[4], b[5]); + if (dump) { + print_status(response_buffer, false); + } +} + +int sd_set_wide_bus(bool wide) +{ + if (bus_width == bw_unknown || bus_width == (wide ? bw_narrow : bw_wide)) { + if (wide && !allow_four_data_pins) { + printf("May not select wide pus without 4 data pins\n"); + return SD_ERR_BAD_PARAM; + } + uint32_t response_buffer[5]; + int rc = sd_command(sd_make_command(55, rca_high, rca_low, 0, 0), response_buffer, 6); + if (!rc) + { + rc = sd_command(sd_make_command(6, 0, 0, 0, wide ? 2 : 0), response_buffer, 6); + } + if (!rc) { + bus_width = wide ? bw_wide : bw_narrow; + } else { + bus_width = bw_unknown; + } + return rc; + } + return SD_OK; +} + +int sd_set_clock_divider(uint div) { +#ifdef PICO_SD_CARD_EXTRA_CLOCK_DIVIDER + div *= PICO_SD_CARD_EXTRA_CLOCK_DIVIDER; +#endif + pio_sm_set_clkdiv_int_frac(sd_pio, SD_CLK_SM, div, 0); + pio_sm_set_clkdiv_int_frac(sd_pio, SD_CMD_SM, div, 0); + pio_sm_set_clkdiv_int_frac(sd_pio, SD_DAT_SM, div, 0); + pio_clkdiv_restart_sm_mask(sd_pio, (1u << SD_CMD_SM) | (1u << SD_CLK_SM) | (1u << SD_DAT_SM)); + return SD_OK; +} + +// todo fixup error handling +static int sd_init( bool _allow_four_data_pins) +{ + bi_decl_if_func_used(bi_2pins_with_names(PICO_SD_CLK_PIN, "SDIO clock", PICO_SD_CMD_PIN, "SDIO cmd")); + bus_width = bw_unknown; + int sd_clk_pin = PICO_SD_CLK_PIN; + int sd_cmd_pin = PICO_SD_CMD_PIN; + sd_dat_pin_base = PICO_SD_DAT0_PIN; + // todo #define for four allowing four pins + gpio_set_function(sd_clk_pin, GPIO_FUNC_PIO1); + gpio_set_function(sd_cmd_pin, GPIO_FUNC_PIO1); + gpio_set_function(sd_dat_pin_base, GPIO_FUNC_PIO1); + gpio_set_pulls(sd_clk_pin, false, true); + gpio_set_pulls(sd_cmd_pin, true, false); + gpio_set_pulls(sd_dat_pin_base, true, false); + allow_four_data_pins = _allow_four_data_pins; + + // Have to set pulls on other pins regardless otherwise SD card fails to + // initialise in 1 bit mode + gpio_set_function(sd_dat_pin_base+1, GPIO_FUNC_PIO1); + gpio_set_function(sd_dat_pin_base+2, GPIO_FUNC_PIO1); + gpio_set_function(sd_dat_pin_base+3, GPIO_FUNC_PIO1); + gpio_set_pulls(sd_dat_pin_base+1, true, false); + gpio_set_pulls(sd_dat_pin_base+2, true, false); + gpio_set_pulls(sd_dat_pin_base+3, true, false); + + static bool added; // todo this is a temporary hack as we don't free + static uint cmd_or_dat_offset; + static uint clk_program_offset; + + if (!added) { + cmd_or_dat_offset = pio_add_program(sd_pio, &sd_cmd_or_dat_program); + assert(!cmd_or_dat_offset); // we don't add this later because it is assumed to be 0 + clk_program_offset = pio_add_program(sd_pio, &sd_clk_program); + added = true; + } + + pio_sm_config c = sd_clk_program_get_default_config(clk_program_offset); + sm_config_set_sideset_pins(&c, sd_clk_pin); + pio_sm_init(sd_pio, SD_CLK_SM, clk_program_offset, &c); + + c = sd_cmd_or_dat_program_get_default_config(cmd_or_dat_offset); + sm_config_set_out_pins(&c, sd_cmd_pin, 1); + sm_config_set_set_pins(&c, sd_cmd_pin, 1); + sm_config_set_in_pins(&c, sd_cmd_pin); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_out_shift(&c, false, true, 32); + pio_sm_init(sd_pio, SD_CMD_SM, cmd_or_dat_offset, &c); + + c = sd_cmd_or_dat_program_get_default_config(cmd_or_dat_offset); + uint num_dat_pins = allow_four_data_pins ? 4 : 1; + sm_config_set_out_pins(&c, sd_dat_pin_base, num_dat_pins); + sm_config_set_set_pins(&c, sd_dat_pin_base, num_dat_pins); + sm_config_set_in_pins(&c, sd_dat_pin_base); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_out_shift(&c, false, true, 32); + pio_sm_init(sd_pio, SD_DAT_SM, cmd_or_dat_offset, &c); + + sd_set_clock_divider(50); +// int div = 50; +// pio_sm_set_clkdiv_int_frac(pio, SD_CLK_SM, div, 0); // run at 240 kHz as required initially +// pio_sm_set_clkdiv_int_frac(pio, SD_CMD_SM, div, 0); // run at 240 kHz as required initially +// pio_sm_set_clkdiv_int_frac(pio, SD_DAT_SM, div, 0); // run at 240 kHz as required initially +// // pio_clkdiv_restart_mask(pio, (1u << SD_CMD_SM) | (1u << SD_CLK_SM) | (1u << SD_DAT_SM)); + + // set later anyway +// pio_set_wrap(pio, SD_DAT_SM, sd_cmd_or_dat_wrap_target, sd_cmd_or_dat_wrap); + + + pio_sm_exec(sd_pio, SD_CMD_SM, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high)); + pio_sm_exec(sd_pio, SD_DAT_SM, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd)); + + uint32_t dat_pin_mask = allow_four_data_pins ? 0xfu : 0x1u; + uint32_t all_pin_mask = (dat_pin_mask << sd_dat_pin_base) | (1u << sd_cmd_pin) | (1u << sd_clk_pin); + pio_sm_set_pindirs_with_mask(sd_pio, SD_CLK_SM, all_pin_mask, all_pin_mask); + pio_sm_exec(sd_pio, SD_DAT_SM, pio_encode_set(pio_pins, dat_pin_mask)); + + //pio_sm_enable(pio, SD_CLK_SM, 1); +// pio_sm_put(sd_pio, SD_CMD_SM, sd_pio_cmd(sd_offset_state_send_bits, 72 - 1)); + // we use 80 rather than 72 so we can keep our 16 bit instruction stream aligned + pio_sm_put(sd_pio, SD_CMD_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_send_bits, 80 - 1)); + pio_sm_put(sd_pio, SD_CMD_SM, 0xffffffff); + pio_sm_put(sd_pio, SD_CMD_SM, 0xffffffff); +// pio_sm_put(sd_pio, SD_CMD_SM, 0xff000000 | (sd_offset_dump_osr_then_wait_high << 16u)); + pio_sm_put(sd_pio, SD_CMD_SM, 0xffff0000 | pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high)); + pio_enable_sm_mask_in_sync(sd_pio, (1u<sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) { + printf("oops %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + } + assert(sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd); + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + assert(block_count <= PICO_SD_MAX_BLOCK_COUNT); + + assert(total == block_count * (128 + (bus_width == bw_wide ? 2 : 1))); + start_chain_dma_read_with_address_size_only(SD_DAT_SM, control_words, true, false); + uint32_t *buf = pio_cmd_buf; + for(int i=0;itxf[SD_DAT_SM], // dest + pio_cmd_buf, // src + buf - pio_cmd_buf, + true + ); + // todo decide timing of this - as long as dat lines are hi, this is fine. (note this comment now applies to the trigger true in the dma_channel_configure) + // dma_channel_start(sd_pio_dma_channel); + assert(block_count); + int rc; + if (block_count == 1) { + rc = sd_command(sd_make_command(17, block >> 24, block >> 16, block >> 8, block & 0xffu), response_buffer, 6); + } else + { +// read_status(true); + // todo can we expect support for 23? + rc = sd_command(sd_make_command(23, block_count >> 24, block_count >> 16, block_count >> 8, block_count & 0xffu), + response_buffer, 6); + if (!rc) rc = sd_command(sd_make_command(18, block >> 24, block >> 16, block >> 8, block & 0xffu), response_buffer, 6); + } + return rc; +} + +int check_crc_count; + +bool sd_scatter_read_complete(int *status) { +// printf("%d:%d %d:%d %d:%d %d\n", dma_busy(sd_chain_dma_channel), (uint)dma_hw->ch[sd_chain_dma_channel].transfer_count, +// dma_busy(sd_data_dma_channel), (uint)dma_hw->ch[sd_data_dma_channel].transfer_count, +// dma_busy(sd_pio_dma_channel), (uint)dma_hw->ch[sd_pio_dma_channel].transfer_count, (uint)pio->sm[SD_DAT_SM].addr); + // this is a bit half arsed atm + bool rc; + if (dma_channel_is_busy(sd_chain_dma_channel) || dma_channel_is_busy(sd_data_dma_channel) || dma_channel_is_busy(sd_pio_dma_channel)) { + rc = false; + } else { + rc = sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd; + } + int s = SD_OK; + if (rc) { +// read_status(true); + for(int i=0;i> 16u) != crcs[i*2+1]) { + printf("CRC error on block %d\n", i); + s = SD_ERR_CRC; + break; + } + } + check_crc_count = 0; + } + if (status) *status = s; + return rc; +} + +static void __time_critical_func(start_chain_dma_write)(uint sm, uint32_t *buf) { + dma_channel_config c = dma_get_channel_config(sd_data_dma_channel); + channel_config_set_chain_to(&c, sd_chain_dma_channel); + channel_config_set_irq_quiet(&c, true); + dma_channel_set_config(sd_data_dma_channel, &c, false); + + c = dma_channel_get_default_config(sd_chain_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, true); + channel_config_set_ring(&c, 1, 4); // wrap the write at 16 bytes + dma_channel_configure( + sd_chain_dma_channel, + &c, + &dma_channel_hw_addr(sd_data_dma_channel)->read_addr, // ch DMA config (target "ring" buffer size 16) - this is (read_addr, write_addr, transfer_count, ctrl), // dest + buf, // src + 4, // send 4 words to ctrl block of data chain per transfer + false + ); + gpio_set_mask(1); + dma_channel_start(sd_chain_dma_channel); + gpio_clr_mask(1); +} + +static __unused uint8_t flapulent[1024]; +static __unused uint32_t flap_count; +uint32_t zeroes; +uint32_t start_bit = 0xfffffffe; + +static uint32_t dma_ctrl_for(enum dma_channel_transfer_size size, bool src_incr, bool dst_incr, uint dreq, + uint chain_to, bool ring_sel, uint ring_size, bool enable) { + dma_channel_config c = dma_channel_get_default_config(0); // channel doesn't matter as we set chain_to later (it is just use to pre-populate that) + channel_config_set_transfer_data_size(&c, size); + channel_config_set_read_increment(&c, src_incr); + channel_config_set_write_increment(&c, dst_incr); + channel_config_set_dreq(&c, dreq); + channel_config_set_chain_to(&c, chain_to); + channel_config_set_ring(&c, ring_sel, ring_size); + channel_config_set_enable(&c, enable); + return c.ctrl; +} +//#define CRC_FIRST +// note caller must make space for CRC (2 word) in 4 bit mode +int sd_writeblocks_async(const uint32_t *data, uint32_t sector_num, uint sector_count) +{ + uint32_t response_buffer[5]; + +#if 1 //def CRC_FIRST + // lets crc the first sector + dma_channel_config c = dma_channel_get_default_config(sd_data_dma_channel); + if (true) + { + channel_config_set_bswap(&c, true); + dma_sniffer_enable(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + dma_sniffer_set_byte_swap_enabled(true); + } else { + dma_sniffer_enable(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + } + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_dreq(&c, DREQ_FORCE); + dma_channel_configure( + sd_data_dma_channel, + &c, + flapulent, // dest + data, // src + 128, + false + ); + hw_set_bits(&dma_hw->ch[sd_data_dma_channel].al1_ctrl, DMA_CH0_CTRL_TRIG_BSWAP_BITS); + dma_hw->sniff_data = 0; + dma_channel_start(sd_data_dma_channel); + dma_channel_wait_for_finish_blocking(sd_data_dma_channel); + printf("Sniff raw %08x, word %04x\n", (uint)dma_hw->sniff_data, __bswap16(dma_hw->sniff_data)); + // todo we need to be able to reset the sniff data correctly + crcs[0] = __bswap16(dma_hw->sniff_data); +#endif + + uint32_t *buf = pio_cmd_buf; + for(int i=0; i < sector_count; i++) + { + // we send an extra word even though the CRC is only 16 bits to make life easy... the receiver doesn't care + // todo that would need to work anyway for inline CRC (which can't include a pio_cmd) + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_send_bits, 512 * 8 + 32 + 32 - 1); + } + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high)); + + if (sector_count > (PICO_SD_MAX_BLOCK_COUNT - 1) / 4) { + panic("too many blocks for now"); + } + + assert(pio_sm_is_tx_fifo_empty(sd_pio, SD_DAT_SM)); + + uint32_t *p = ctrl_words; +//#define SEND_TO_BUFFER +#ifdef SEND_TO_BUFFER + uint32_t *output_buffer = flapulent; + uint32_t offset = 0; + #define build_transfer(src, words, size, flags) \ + *p++ = (uintptr_t)(src); \ + *p++ = output_buffer + offset; \ + *p++ = words; \ + offset += words; \ + *p++ = dma_ctrl_for(size, true, true, DREQ_FORCE, sd_chain_dma_channel, 0, 0, true) | (flags); +#else +#define build_transfer(src, words, size, flags) \ + *p++ = (uintptr_t)(src); \ + *p++ = (uintptr_t)(&sd_pio->txf[SD_DAT_SM]); \ + *p++ = words; \ + *p++ = dma_ctrl_for(size, true, false, DREQ_PIO1_TX0 + SD_DAT_SM, sd_chain_dma_channel, 0, 0, true) | (flags); + +#endif + for(int i=0;isniff_data); + *p++ = 1; + *p++ = dma_ctrl_for(DMA_SIZE_32, false, false, DREQ_FORCE, sd_chain_dma_channel, 0, 0, true); +#endif + // second cb - send bits command + build_transfer(pio_cmd_buf + i, 1, DMA_SIZE_32, 0); + build_transfer(&start_bit, 1, DMA_SIZE_32, 0); + // third cb - 128 words of sector data + build_transfer(data + i * 128, 128, DMA_SIZE_32, DMA_CH0_CTRL_TRIG_BSWAP_BITS | DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS); +// // fourth cb - transfer sniff +#ifdef CRC_FIRST + build_transfer( crcs, 1, DMA_SIZE_32, DMA_CH0_CTRL_TRIG_BSWAP_BITS); +#else + // note offset of 2, since we bswap the data + build_transfer( (uintptr_t)&dma_hw->sniff_data, 1, DMA_SIZE_16, 0);//DMA_CH0_CTRL_TRIG_BSWAP_BITS); +#endif + } + // final cb - return to wait state + build_transfer(pio_cmd_buf + sector_count, 1, DMA_SIZE_32, 0); +#ifdef SEND_TO_BUFFER + flap_count = offset; +#endif + *p++ = 0; + *p++ = 0; + + // todo further state checks + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) { + printf("oops %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + + } + assert(sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd); + assert(pio_sm_is_tx_fifo_empty(sd_pio, SD_DAT_SM)); + pio_sm_put(sd_pio, SD_DAT_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high))); + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) { + printf("reps %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + + } + + assert(sector_count); + int rc = sd_set_wide_bus(false); // use 1 bit writes for now + if (rc) return rc; + if (sector_count == 1) { + rc = sd_command(sd_make_command(24, sector_num >> 24, sector_num >> 16, sector_num >> 8, sector_num & 0xffu), response_buffer, 6); + } else + { + // todo this is only writing the first sector on SanDisk EDGE 16G right now - probably need a delay between sectors... works fine on a SAMSUNG EVO 32G + + // todo can we expect support for 23? + rc = sd_command(sd_make_command(23, sector_count >> 24, sector_count >> 16, sector_count >> 8, sector_count & 0xffu), + response_buffer, 6); + if (!rc) rc = sd_command(sd_make_command(25, sector_num >> 24, sector_num >> 16, sector_num >> 8, sector_num & 0xffu), response_buffer, 6); + } + read_status(true); + if (!rc) + { + pio_sm_set_enabled(sd_pio, SD_DAT_SM, false); + dma_sniffer_enable(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + dma_sniffer_set_byte_swap_enabled(true); + start_chain_dma_write(SD_DAT_SM, ctrl_words); + pio_sm_set_enabled(sd_pio, SD_DAT_SM, true); + printf("dma chain data (rem %04x @ %08x) data (rem %04x @ %08x) pio data (rem %04x @ %08x) datsm @ %d\n", + (uint) dma_hw->ch[sd_chain_dma_channel].transfer_count, + (uint) dma_hw->ch[sd_chain_dma_channel].read_addr, + (uint) dma_hw->ch[sd_data_dma_channel].transfer_count, (uint) dma_hw->ch[sd_data_dma_channel].read_addr, + (uint) dma_hw->ch[sd_pio_dma_channel].transfer_count, (uint) dma_hw->ch[sd_pio_dma_channel].read_addr, + (int) sd_pio->sm[SD_DAT_SM].addr); + + } + return rc; +} + +bool sd_write_complete(int *status) { + printf("dma chain data (rem %04x @ %08x) data (rem %04x @ %08x) datsm @ %d\n", + (uint)dma_hw->ch[sd_chain_dma_channel].transfer_count, (uint)dma_hw->ch[sd_chain_dma_channel].read_addr, + (uint)dma_hw->ch[sd_data_dma_channel].transfer_count, (uint)dma_hw->ch[sd_data_dma_channel].read_addr, + (int)sd_pio->sm[SD_DAT_SM].addr); + // this is a bit half arsed atm + bool rc; + if (dma_channel_is_busy(sd_chain_dma_channel) || dma_channel_is_busy(sd_data_dma_channel)) rc = false; + else rc = sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd; + if (rc) { + read_status(true); + printf("sniffo %08x\n", (uint)dma_hw->sniff_data); + } + if (status) *status = SD_OK; + return rc; +} + +#if 1 +// note caller must make space for CRC (2 word) in 4 bit mode +int sd_read_sectors_1bit_crc_async(uint32_t *sector_buf, uint32_t sector, uint sector_count) +{ + uint32_t response_buffer[5]; + + sd_set_wide_bus(false); + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + + if (sector_count > (PICO_SD_MAX_BLOCK_COUNT - 1) / 4) { + panic("too many blocks for now"); + } + + check_crc_count = sector_count; + uint32_t *p = ctrl_words; + for(int i=0;isniff_data); + *p++ = 1; + *p++ = dma_ctrl_for(DMA_SIZE_32, false, false, DREQ_FORCE, sd_chain_dma_channel, 0, 0, true); + // second cb - 128 words of sector data + *p++ = (uintptr_t)(&sd_pio->rxf[SD_DAT_SM]); + *p++ = (uintptr_t)(sector_buf + i * 128); + *p++ = 128; + *p++ = dma_ctrl_for(DMA_SIZE_32, false, true, DREQ_PIO1_RX0 + SD_DAT_SM, sd_chain_dma_channel, 0, 0, true) | DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS | DMA_CH0_CTRL_TRIG_BSWAP_BITS; + // third crc from stream + *p++ = (uintptr_t)(&sd_pio->rxf[SD_DAT_SM]); + *p++ = (uintptr_t)(crcs + i * 2); + *p++ = 1; + *p++ = dma_ctrl_for(DMA_SIZE_32, false, false, DREQ_PIO1_RX0 + SD_DAT_SM, sd_chain_dma_channel, 0, 0, true); + // fourth crc from sniff + *p++ = (uintptr_t)&dma_hw->sniff_data; + *p++ = (uintptr_t)(crcs + i * 2 + 1); + *p++ = 1; + *p++ = dma_ctrl_for(DMA_SIZE_32, false, false, DREQ_FORCE, sd_chain_dma_channel, 0, 0, true); + } + *p++ = 0; + *p++ = 0; + + // todo further state checks + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) { + printf("oops %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + } + assert(sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd); + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + assert(sector_count <= PICO_SD_MAX_BLOCK_COUNT); + + dma_sniffer_enable(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, false); + //dma_enable_sniffer_byte_swap(true); + start_chain_dma_read_with_full_cb(SD_DAT_SM, ctrl_words); + uint32_t *buf = pio_cmd_buf; + for(int i=0;itxf[SD_DAT_SM], // dest + pio_cmd_buf, // src + buf - pio_cmd_buf, + false + ); + // todo decide timing of this - as long as dat lines are hi, this is fine. + dma_channel_start(sd_pio_dma_channel); + assert(sector_count); + int rc; + if (sector_count == 1) { + rc = sd_command(sd_make_command(17, sector >> 24, sector >> 16, sector >> 8, sector & 0xffu), response_buffer, 6); + } else + { +// read_status(true); + // todo can we expect support for 23? + rc = sd_command(sd_make_command(23, sector_count >> 24, sector_count >> 16, sector_count >> 8, sector_count & 0xffu), + response_buffer, 6); + if (!rc) rc = sd_command(sd_make_command(18, sector >> 24, sector >> 16, sector >> 8, sector & 0xffu), response_buffer, 6); + } + return rc; +} + +#endif diff --git a/src/rp2_common/pico_sd_card/sd_card.pio b/src/rp2_common/pico_sd_card/sd_card.pio new file mode 100644 index 0000000..4a0d295 --- /dev/null +++ b/src/rp2_common/pico_sd_card/sd_card.pio @@ -0,0 +1,59 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +; NOTE IT IS IMPERATIVE THAT YOU DON"T SCREW UP THE CLOCK EVEN FOR ONE HALF CYCLE, OTHERWISE THE DEVICE WILL LIKELY BE DISPLEASED AND MAY SEND GARBAGE (EVEN IF IT ISN"T DOING ANYTHING IMPORTANT WHEN YOU DO SO) + +.define sd_irq_num 7 + +.program sd_clk +.side_set 1 +.wrap_target + irq sd_irq_num side 1 + irq clear sd_irq_num side 0 +.wrap + +.program sd_cmd_or_dat +.origin 0 ; must load at zero (offsets are hardcoded in instruction stream) +public no_arg_state_wait_high: ; this is a no arg state which means it must always appear in the second half of a word + ; make sure pins are hi when we set output dir (note if we are 1 bit we'll be configured for 1 pin only, so sending 0b1111 is fine) + set pins, 0b1111 + set pindirs, 0b1111 + +public no_arg_state_waiting_for_cmd: + out exec, 16 ; expected to be a jmp to a state + +public state_send_bits: + out x, 16 + wait 0 irq sd_irq_num +send_loop1: + out pins, 1 + jmp x-- send_loop1 + +public state_inline_instruction: + out exec, 16 ; may be any instruction +.wrap_target + out exec, 16 ; expected to be a jmp to a state + +public state_receive_bits: + out x, 16 + set pindirs, 0 + wait 1 pin, 0 + wait 0 pin, 0 + wait 0 irq sd_irq_num + ; note we use wrap setup to configure receive bit/nibble transfers +public wrap_for_4bit_receive: +receive_loop1: + in pins, 1 + jmp x-- receive_loop1 +.wrap + +; #if INCLUDE_4BIT +public wrap_target_for_4bit_receive: +receive_loop4: + in pins, 4 + jmp x-- receive_loop4 + out exec, 16 ; expected to be a jmp to a state +; #endif diff --git a/src/rp2_common/pico_sleep/CMakeLists.txt b/src/rp2_common/pico_sleep/CMakeLists.txt new file mode 100644 index 0000000..bb8ea07 --- /dev/null +++ b/src/rp2_common/pico_sleep/CMakeLists.txt @@ -0,0 +1,5 @@ +pico_simple_hardware_target(sleep) +target_link_libraries(hardware_sleep INTERFACE + hardware_clocks + hardware_rosc + hardware_rtc) \ No newline at end of file diff --git a/src/rp2_common/pico_sleep/include/pico/sleep.h b/src/rp2_common/pico_sleep/include/pico/sleep.h new file mode 100644 index 0000000..b97a231 --- /dev/null +++ b/src/rp2_common/pico_sleep/include/pico/sleep.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _PICO_SLEEP_H_ +#define _PICO_SLEEP_H_ + +#include "pico.h" +#include "hardware/rtc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** \file sleep.h + * \defgroup hardware_sleep hardware_sleep + * + * Lower Power Sleep API + * + * The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, + * until the source (either xosc or rosc) is started again by an external event. + * In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks + * block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) + * can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. + * + * \subsection sleep_example Example + * \addtogroup hardware_sleep + * \include hello_sleep.c + + */ +typedef enum { + DORMANT_SOURCE_NONE, + DORMANT_SOURCE_XOSC, + DORMANT_SOURCE_ROSC +} dormant_source_t; + +/*! \brief Set all clock sources to the the dormant clock source to prepare for sleep. + * \ingroup hardware_sleep + * + * \param dormant_source The dormant clock source to use + */ +void sleep_run_from_dormant_source(dormant_source_t dormant_source); + +/*! \brief Set the dormant clock source to be the crystal oscillator + * \ingroup hardware_sleep + */ +static inline void sleep_run_from_xosc(void) { + sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC); +} + +/*! \brief Set the dormant clock source to be the ring oscillator + * \ingroup hardware_sleep + */ +static inline void sleep_run_from_rosc(void) { + sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC); +} + +/*! \brief Send system to sleep until the specified time + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param t The time to wake up + * \param callback Function to call on wakeup. + */ +void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback); + +/*! \brief Send system to sleep until the specified GPIO changes + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param gpio_pin The pin to provide the wake up + * \param edge true for leading edge, false for trailing edge + * \param high true for active high, false for active low + */ +void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high); + +/*! \brief Send system to sleep until a leading high edge is detected on GPIO + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param gpio_pin The pin to provide the wake up + */ +static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) { + sleep_goto_dormant_until_pin(gpio_pin, true, true); +} + +/*! \brief Send system to sleep until a high level is detected on GPIO + * \ingroup hardware_sleep + * + * One of the sleep_run_* functions must be called prior to this call + * + * \param gpio_pin The pin to provide the wake up + */ +static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) { + sleep_goto_dormant_until_pin(gpio_pin, false, true); +} + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/pico_sleep/sleep.c b/src/rp2_common/pico_sleep/sleep.c new file mode 100644 index 0000000..0aab405 --- /dev/null +++ b/src/rp2_common/pico_sleep/sleep.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico.h" + +#include "pico/stdlib.h" +#include "pico/sleep.h" + +#include "hardware/rtc.h" +#include "hardware/pll.h" +#include "hardware/clocks.h" +#include "hardware/xosc.h" +#include "hardware/rosc.h" +#include "hardware/regs/io_bank0.h" +// For __wfi +#include "hardware/sync.h" +// For scb_hw so we can enable deep sleep +#include "hardware/structs/scb.h" + +// The difference between sleep and dormant is that ALL clocks are stopped in dormant mode, +// until the source (either xosc or rosc) is started again by an external event. +// In sleep mode some clocks can be left running controlled by the SLEEP_EN registers in the clocks +// block. For example you could keep clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) +// can't be stopped in sleep mode otherwise there wouldn't be enough logic to wake up again. + + +// TODO: Optionally, memories can also be powered down. + +static dormant_source_t _dormant_source; + +bool dormant_source_valid(dormant_source_t dormant_source) { + return (dormant_source == DORMANT_SOURCE_XOSC) || (dormant_source == DORMANT_SOURCE_ROSC); +} + +// In order to go into dormant mode we need to be running from a stoppable clock source: +// either the xosc or rosc with no PLLs running. This means we disable the USB and ADC clocks +// and all PLLs +void sleep_run_from_dormant_source(dormant_source_t dormant_source) { + assert(dormant_source_valid(dormant_source)); + _dormant_source = dormant_source; + + // FIXME: Just defining average rosc freq here. + uint src_hz = (dormant_source == DORMANT_SOURCE_XOSC) ? XOSC_MHZ * MHZ : 6.5 * MHZ; + uint clk_ref_src = (dormant_source == DORMANT_SOURCE_XOSC) ? + CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC : + CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH; + + // CLK_REF = XOSC or ROSC + clock_configure(clk_ref, + clk_ref_src, + 0, // No aux mux + src_hz, + src_hz); + + // CLK SYS = CLK_REF + clock_configure(clk_sys, + CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, + 0, // Using glitchless mux + src_hz, + src_hz); + + // CLK USB = 0MHz + clock_stop(clk_usb); + + // CLK ADC = 0MHz + clock_stop(clk_adc); + + // CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc + uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC) ? + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC : + CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH; + + clock_configure(clk_rtc, + 0, // No GLMUX + clk_rtc_src, + src_hz, + 46875); + + // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable + clock_configure(clk_peri, + 0, + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, + src_hz, + src_hz); + + pll_deinit(pll_sys); + pll_deinit(pll_usb); + + // Assuming both xosc and rosc are running at the moment + if (dormant_source == DORMANT_SOURCE_XOSC) { + // Can disable rosc + rosc_disable(); + } else { + // Can disable xosc + xosc_disable(); + } + + // Reconfigure uart with new clocks + setup_default_uart(); +} + +// Go to sleep until woken up by the RTC +void sleep_goto_sleep_until(datetime_t *t, rtc_callback_t callback) { + // We should have already called the sleep_run_from_dormant_source function + assert(dormant_source_valid(_dormant_source)); + + // Turn off all clocks when in sleep mode except for RTC + clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS; + clocks_hw->sleep_en1 = 0x0; + + rtc_set_alarm(t, callback); + + uint save = scb_hw->scr; + // Enable deep sleep at the proc + scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; + + // Go to sleep + __wfi(); +} + +static void _go_dormant(void) { + assert(dormant_source_valid(_dormant_source)); + + if (_dormant_source == DORMANT_SOURCE_XOSC) { + xosc_dormant(); + } else { + rosc_set_dormant(); + } +} + +void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) { + bool low = !high; + bool level = !edge; + + // Configure the appropriate IRQ at IO bank 0 + assert(gpio_pin < NUM_BANK0_GPIOS); + + uint32_t event = 0; + + if (level && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS; + if (level && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS; + if (edge && high) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS; + if (edge && low) event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS; + + gpio_set_dormant_irq_enabled(gpio_pin, event, true); + + _go_dormant(); + // Execution stops here until woken up + + // Clear the irq so we can go back to dormant mode again if we want + gpio_acknowledge_irq(gpio_pin, event); +} \ No newline at end of file diff --git a/src/rp2_common/usb_common/CMakeLists.txt b/src/rp2_common/usb_common/CMakeLists.txt new file mode 100644 index 0000000..b90d37e --- /dev/null +++ b/src/rp2_common/usb_common/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(usb_common INTERFACE) + +target_include_directories(usb_common INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) \ No newline at end of file diff --git a/src/rp2_common/usb_common/include/usb/usb_common.h b/src/rp2_common/usb_common/include/usb/usb_common.h new file mode 100644 index 0000000..507bdf4 --- /dev/null +++ b/src/rp2_common/usb_common/include/usb/usb_common.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _USB_USB_COMMON_H +#define _USB_USB_COMMON_H + +#include "pico/types.h" +#include "hardware/structs/usb.h" + +// bmRequestType bit definitions +#define USB_REQ_TYPE_STANDARD 0x00u +#define USB_REQ_TYPE_TYPE_MASK 0x60u +#define USB_REQ_TYPE_TYPE_CLASS 0x20u +#define USB_REQ_TYPE_TYPE_VENDOR 0x40u + +#define USB_REQ_TYPE_RECIPIENT_MASK 0x1fu +#define USB_REQ_TYPE_RECIPIENT_DEVICE 0x00u +#define USB_REQ_TYPE_RECIPIENT_INTERFACE 0x01u +#define USB_REQ_TYPE_RECIPIENT_ENDPOINT 0x02u + +#define USB_DIR_OUT 0x00u +#define USB_DIR_IN 0x80u + +#define USB_TRANSFER_TYPE_CONTROL 0x0 +#define USB_TRANSFER_TYPE_ISOCHRONOUS 0x1 +#define USB_TRANSFER_TYPE_BULK 0x2 +#define USB_TRANSFER_TYPE_INTERRUPT 0x3 +#define USB_TRANSFER_TYPE_BITS 0x3 + +// Descriptor types +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 + +#define USB_REQUEST_GET_STATUS 0x0 +#define USB_REQUEST_CLEAR_FEATURE 0x01 +#define USB_REQUEST_SET_FEATURE 0x03 +#define USB_REQUEST_SET_ADDRESS 0x05 +#define USB_REQUEST_GET_DESCRIPTOR 0x06 +#define USB_REQUEST_SET_DESCRIPTOR 0x07 +#define USB_REQUEST_GET_CONFIGURATION 0x08 +#define USB_REQUEST_SET_CONFIGURATION 0x09 +#define USB_REQUEST_GET_INTERFACE 0x0a +#define USB_REQUEST_SET_INTERFACE 0x0b +#define USB_REQUEST_SYNC_FRAME 0x0c + +#define USB_REQUEST_MSC_GET_MAX_LUN 0xfe +#define USB_REQUEST_MSC_RESET 0xff + +#define USB_FEAT_ENDPOINT_HALT 0x00 +#define USB_FEAT_DEVICE_REMOTE_WAKEUP 0x01 +#define USB_FEAT_TEST_MODE 0x02 + +#define USB_DESCRIPTOR_TYPE_ENDPOINT 0x05 + +struct usb_setup_packet { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} __packed; + +struct usb_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; +}; + +struct usb_device_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} __packed; + +struct usb_configuration_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t bMaxPower; +} __packed; + +struct usb_interface_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; +} __packed; + +struct usb_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; +} __packed; + +struct usb_endpoint_descriptor_long { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + uint8_t bRefresh; + uint8_t bSyncAddr; +} __attribute__((packed)); + +#endif \ No newline at end of file diff --git a/src/rp2_common/usb_device/CMakeLists.txt b/src/rp2_common/usb_device/CMakeLists.txt new file mode 100644 index 0000000..281c210 --- /dev/null +++ b/src/rp2_common/usb_device/CMakeLists.txt @@ -0,0 +1,9 @@ +add_library(usb_device INTERFACE) + +target_sources(usb_device INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/usb_device.c + ${CMAKE_CURRENT_LIST_DIR}/usb_stream_helper.c +) + +target_include_directories(usb_device INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(usb_device INTERFACE usb_common hardware_irq hardware_dma hardware_pio pico_fix_rp2040_usb_device_enumeration) \ No newline at end of file diff --git a/src/rp2_common/usb_device/include/pico/usb_device.h b/src/rp2_common/usb_device/include/pico/usb_device.h new file mode 100644 index 0000000..c4d7898 --- /dev/null +++ b/src/rp2_common/usb_device/include/pico/usb_device.h @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _USB_USB_DEVICE_H +#define _USB_USB_DEVICE_H + +#include "usb/usb_common.h" +#include "pico/assert.h" + +#ifndef PICO_USBDEV_ENABLE_DEBUG_TRACE +#define PICO_USBDEV_ENABLE_DEBUG_TRACE 0 +#endif + +#ifndef PICO_USBDEV_ASSUME_ZERO_INIT +#define PICO_USBDEV_ASSUME_ZERO_INIT 0 +#endif + +#ifndef PICO_USBDEV_MAX_ENDPOINTS +#define PICO_USBDEV_MAX_ENDPOINTS USB_NUM_ENDPOINTS +#endif +static_assert(PICO_USBDEV_MAX_ENDPOINTS >= 1 && PICO_USBDEV_MAX_ENDPOINTS <= 16, ""); + +#ifndef PICO_USBDEV_MAX_DESCRIPTOR_SIZE +#define PICO_USBDEV_MAX_DESCRIPTOR_SIZE 64 +#endif + +// Enabling configuration items can reduce the size of the runtime code at the cost of some functionality +// or improve speed at the cost of some flexibility + +// no custom per device setup packet handler +#ifndef PICO_USBDEV_NO_DEVICE_SETUP_HANDLER +#define PICO_USBDEV_NO_DEVICE_SETUP_HANDLER 0 +#endif + +// no custom per endpoint setup packet handlers +#ifndef PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER +#define PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER 0 +#endif + +// if all endpoints are bulk, then it allows simplification of some code +#ifndef PICO_USBDEV_BULK_ONLY_EP1_THRU_16 +#define PICO_USBDEV_BULK_ONLY_EP1_THRU_16 0 +#endif + +// our interfaces are zero based number in the order they appear on the device - require that +#ifndef PICO_USBDEV_USE_ZERO_BASED_INTERFACES +#define PICO_USBDEV_USE_ZERO_BASED_INTERFACES 0 +#endif + +// no_init method for transfer +#ifndef PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD +#define PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD 0 +#endif + +// do on_cancel method for transfer +#ifndef PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD +#define PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD 0 +#endif + +// todo this needs to be part of configuration +#ifndef PICO_USBDEV_NO_INTERFACE_ALTERNATES +#define PICO_USBDEV_NO_INTERFACE_ALTERNATES 0 +#endif + +// todo this needs to be part of configuration +#ifndef PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE +#define PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE 0 +#endif + +static_assert((PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE >= 0) && (PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE < 4), + ""); + +// don't zero out most structures (since we do so globablly for BSS) +//#define USB_SKIP_COMMON_INIT + +// only 16 bytes saved to not set a sense code +//#define USB_SILENT_FAIL_ON_EXCLUSIVE + +struct usb_transfer; +struct usb_endpoint; + +typedef void (*usb_transfer_func)(struct usb_endpoint *ep); +typedef void (*usb_transfer_completed_func)(struct usb_endpoint *ep, struct usb_transfer *transfer); + +struct usb_buffer { + uint8_t *data; + uint8_t data_len; + uint8_t data_max; + // then... + bool valid; // aka user owned +}; + +#include "usb_device_private.h" + +struct usb_transfer_type { + // for IN transfers this is called to setup new packet buffers + // for OUT transfers this is called with packet data + // + // In any case usb_packet_done must be called if this function has handled the buffer + usb_transfer_func on_packet; +#if !PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD + usb_transfer_func on_init; +#endif +#if !PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD + usb_transfer_func on_cancel; +#endif + uint8_t initial_packet_count; +}; + +struct usb_interface *usb_interface_init(struct usb_interface *interface, const struct usb_interface_descriptor *desc, + struct usb_endpoint *const *endpoints, uint endpoint_count, + bool double_buffered); +struct usb_device *usb_device_init(const struct usb_device_descriptor *desc, + const struct usb_configuration_descriptor *config_desc, + struct usb_interface *const *interfaces, uint interface_count, + const char *(*get_descriptor_string)(uint index)); + +void usb_device_start(); +void usb_device_stop(); + +// explicit stall +void usb_halt_endpoint_on_condition(struct usb_endpoint *ep); +void usb_halt_endpoint(struct usb_endpoint *endpoint); +void usb_clear_halt_condition(struct usb_endpoint *ep); +static inline bool usb_is_endpoint_stalled(struct usb_endpoint *endpoint); +void usb_set_default_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer); +void usb_reset_transfer(struct usb_transfer *transfer, const struct usb_transfer_type *type, + usb_transfer_completed_func on_complete); +void usb_start_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer); +void usb_reset_and_start_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer, + const struct usb_transfer_type *type, usb_transfer_completed_func on_complete); +void usb_chain_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer); +void usb_grow_transfer(struct usb_transfer *transfer, uint packet_count); +void usb_start_default_transfer_if_not_already_running_or_halted(struct usb_endpoint *ep); + +typedef void (*usb_transfer_func)(struct usb_endpoint *ep); + +struct usb_buffer *usb_current_in_packet_buffer(struct usb_endpoint *ep); +struct usb_buffer *usb_current_out_packet_buffer(struct usb_endpoint *ep); +uint8_t *usb_get_single_packet_response_buffer(struct usb_endpoint *ep, uint len); + +// call during (or asynchronously after) on_packet to mark the packet as done +void usb_packet_done(struct usb_endpoint *ep); + +extern const struct usb_transfer_type usb_current_packet_only_transfer_type; + +static inline struct usb_endpoint *usb_get_control_in_endpoint(); +static inline struct usb_endpoint *usb_get_control_out_endpoint(); + +void usb_start_empty_control_in_transfer(usb_transfer_completed_func on_complete); +void usb_start_empty_control_in_transfer_null_completion(); +void usb_start_tiny_control_in_transfer(uint32_t data, uint len); +void usb_start_single_buffer_control_in_transfer(); +void usb_start_control_out_transfer(const struct usb_transfer_type *type); +void usb_start_empty_transfer(struct usb_endpoint *endpoint, struct usb_transfer *transfer, + usb_transfer_completed_func on_complete); + +void usb_soft_reset_endpoint(struct usb_endpoint *ep); +void usb_hard_reset_endpoint(struct usb_endpoint *ep); + +#if PICO_USBDEV_ENABLE_DEBUG_TRACE +void usb_dump_trace(void); +void usb_reset_trace(void); +#else + +static inline void usb_dump_trace() {} + +static inline void usb_reset_trace() {} + +#endif + +#define usb_warn(format, args...) ({printf("WARNING: "); printf(format, ## args); }) +#if false && !defined(NDEBUG) +#define usb_debug(format,args...) printf(format, ## args) +#else +#define usb_debug(format, ...) ((void)0) +#endif + +#if false && !defined(NDEBUG) +#define usb_trace(format,args...) printf(format, ## args) +#else +#define usb_trace(format, ...) ((void)0) +#endif + +#endif \ No newline at end of file diff --git a/src/rp2_common/usb_device/include/pico/usb_device_private.h b/src/rp2_common/usb_device/include/pico/usb_device_private.h new file mode 100644 index 0000000..b37921f --- /dev/null +++ b/src/rp2_common/usb_device/include/pico/usb_device_private.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _USB_DEVICE_PRIVATE_H +#define _USB_DEVICE_PRIVATE_H + +#include "usb/usb_common.h" +#include + +struct usb_transfer { + // prototype + const struct usb_transfer_type *type; + usb_transfer_completed_func on_complete; + // total number of buffers (packets) that still need to be handed over to the hardware + // during the remaining course of the transfer (with data for IN, empty for data for out) + uint32_t remaining_packets_to_submit; + // total number of buffers when we will expect to receive IRQ/handle_buffer for during + // the remaining course of the transfer + uint32_t remaining_packets_to_handle; +#ifdef GENERAL_SIZE_HACKS + union { + struct { +#endif + // number of packets which require usb_packet_done() + bool outstanding_packet; + // received a packet which we couldn't deliver because there was one outstanding + bool packet_queued; + bool started; + bool completed; +#ifdef GENERAL_SIZE_HACKS + }; + uint32_t all_flags; +}; +#endif +}; + +struct usb_interface { + const struct usb_interface_descriptor *descriptor; + struct usb_endpoint *const *endpoints; + uint8_t endpoint_count; + bool (*setup_request_handler)(struct usb_interface *interface, struct usb_setup_packet *setup); +#if !PICO_USBDEV_NO_INTERFACE_ALTERNATES + bool (*set_alternate_handler)(struct usb_interface *interface, uint alt); + uint8_t alt; +#endif +}; + +struct usb_configuration { + const struct usb_configuration_descriptor *descriptor; + struct usb_interface *const *interfaces; +#ifndef PICO_USBDEV_FIXED_INTERFACE_COUNT + uint8_t interface_count; +#endif +}; +#ifdef PICO_USBDEV_FIXED_INTERFACE_COUNT +#define _usb_interface_count(config) PICO_USBDEV_FIXED_INTERFACE_COUNT +#else +#define _usb_interface_count(config) config->interface_count +#endif + +struct usb_device { + const struct usb_device_descriptor *descriptor; +#if !PICO_USBDEV_NO_DEVICE_SETUP_HANDLER + bool (*setup_request_handler)(struct usb_device *dev, struct usb_setup_packet *setup); +#endif + void (*on_configure)(struct usb_device *dev, bool configured); + const char *(*get_descriptor_string)(uint index); + // only support one config for now + struct usb_configuration config; + uint8_t current_address; // 0 if unaddressed + uint8_t current_config_num; // 0 if unconfigured + uint8_t pending_address; // address to set on completion of SET_ADDRESS CSW + uint16_t next_buffer_offset; +// bool started; +}; + +enum usb_halt_state { + HS_NONE = 0, + HS_NON_HALT_STALL = 1, // just stalled + HS_HALTED = 2, // halted or command halted + HS_HALTED_ON_CONDITION = 3 // halted that cannot be simply cleared by CLEAR_FEATURE +}; + +struct usb_endpoint { + const struct usb_endpoint_descriptor *descriptor; + struct usb_transfer *default_transfer; + struct usb_transfer *current_transfer; + struct usb_transfer *chain_transfer; + void (*on_stall_change)(struct usb_endpoint *ep); +#if !PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER + bool (*setup_request_handler)(struct usb_endpoint *ep, struct usb_setup_packet *setup); +#endif + uint16_t dpram_buffer_offset; + uint16_t buffer_size; // for an individual buffer + struct usb_buffer current_hw_buffer; +#if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16 + uint16_t buffer_stride; +#endif + uint8_t num; + uint8_t next_pid; + uint8_t buffer_bit_index; + uint8_t owned_buffer_count; + uint8_t current_give_buffer; + uint8_t current_take_buffer; + uint8_t halt_state; + bool first_buffer_after_reset; + bool double_buffered; + bool in; +}; + +static inline uint usb_endpoint_number(struct usb_endpoint *ep) { + assert(ep); + return ep->descriptor ? ep->descriptor->bEndpointAddress & 0xfu : 0; +} + +static inline bool usb_is_endpoint_stalled(struct usb_endpoint *endpoint) { + return endpoint->halt_state != HS_NONE; +} + +const char *usb_endpoint_dir_string(struct usb_endpoint *ep); + +static inline struct usb_endpoint *usb_get_control_in_endpoint() { + extern struct usb_endpoint usb_control_in; + return &usb_control_in; +} + +static inline struct usb_endpoint *usb_get_control_out_endpoint() { + extern struct usb_endpoint usb_control_out; + return &usb_control_out; +} + +#endif //_USB_DEVICE_PRIVATE_H diff --git a/src/rp2_common/usb_device/include/pico/usb_stream_helper.h b/src/rp2_common/usb_device/include/pico/usb_stream_helper.h new file mode 100644 index 0000000..a42f081 --- /dev/null +++ b/src/rp2_common/usb_device/include/pico/usb_stream_helper.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _USB_STREAM_HELPER_H +#define _USB_STREAM_HELPER_H + +#include "usb_device.h" + +struct usb_transfer_funcs; + +struct usb_stream_transfer { + struct usb_transfer core; + uint32_t offset; // offset within the stream + uint32_t transfer_length; + uint32_t chunk_size; + uint8_t *chunk_buffer; + struct usb_endpoint *ep; + const struct usb_stream_transfer_funcs *funcs; +#ifndef NDEBUG + bool packet_handler_complete_expected; +#endif +}; + +typedef void (*stream_on_packet_complete_function)(struct usb_stream_transfer *transfer); +typedef bool (*stream_on_chunk_function)(uint32_t chunk_len, + struct usb_stream_transfer *transfer); + +struct usb_stream_transfer_funcs { + stream_on_packet_complete_function on_packet_complete; + // returns whether processing async + stream_on_chunk_function on_chunk; +}; + +void usb_stream_setup_transfer(struct usb_stream_transfer *transfer, const struct usb_stream_transfer_funcs *funcs, + uint8_t *chunk_buffer, uint32_t chunk_size, uint32_t transfer_length, + usb_transfer_completed_func on_complete); + +void usb_stream_chunk_done(struct usb_stream_transfer *transfer); + +void usb_stream_noop_on_packet_complete(struct usb_stream_transfer *transfer); +bool usb_stream_noop_on_chunk(uint32_t chunk_len, struct usb_stream_transfer *transfer); +#endif //SOFTWARE_USB_STREAM_HELPER_H diff --git a/src/rp2_common/usb_device/usb_device.c b/src/rp2_common/usb_device/usb_device.c new file mode 100644 index 0000000..045c983 --- /dev/null +++ b/src/rp2_common/usb_device/usb_device.c @@ -0,0 +1,1303 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico.h" +#include "hardware/gpio.h" +#include "hardware/sync.h" +#include "hardware/irq.h" +#include "hardware/structs/usb.h" +#include "pico/usb_device.h" + +#if PICO_USBDEV_MAX_DESCRIPTOR_SIZE > 64 +#include "pico/usb_stream_helper.h" +#include "pico/fix/rp2040_usb_device_enumeration.h" +#endif + +// ------------------------------------------------------------------------------------------------------------- +// Note this is a small code size focused USB device abstraction, which also avoids using any mutable static +// data so it is easy to include in bootrom. +// ------------------------------------------------------------------------------------------------------------- + +//#define USB_SINGLE_BUFFERED + +CU_REGISTER_DEBUG_PINS(usb_irq) +//CU_SELECT_DEBUG_PINS(usb_irq) + +#if PICO_USBDEV_ENABLE_DEBUG_TRACE +static uint32_t debug_trace[128][2]; +static volatile uint32_t trace_i; +#endif + +// note we treat all errors the same (we just ignore) +#define USB_INTS_ERROR_BITS ( \ + USB_INTS_ERROR_DATA_SEQ_BITS | \ + USB_INTS_ERROR_BIT_STUFF_BITS | \ + USB_INTS_ERROR_CRC_BITS | \ + USB_INTS_ERROR_RX_OVERFLOW_BITS | \ + USB_INTS_ERROR_RX_TIMEOUT_BITS) + +// define some macros so we implement different allocation schemes (right now we use bootrom which is no-alloc and assume zero) +#if PICO_USBDEV_ASSUME_ZERO_INIT +#define usb_init_clear_deref(x) ((void)0) +#else +#define usb_init_clear_deref(x) memset(x, 0, sizeof(*(x))) +#endif +#define usb_common_init(ep) ({assert(ep); usb_init_clear_deref(ep); ep; }) + +#define usb_hw_set hw_set_alias(usb_hw) +#define usb_hw_clear hw_clear_alias(usb_hw) + +void _usb_transfer_current_packet_only(struct usb_endpoint *ep); + +const struct usb_transfer_type usb_current_packet_only_transfer_type = { + .on_packet = _usb_transfer_current_packet_only, + .initial_packet_count = 1, +}; + +/** + * Public ep 0 IN/OUT + */ +struct usb_endpoint usb_control_in, usb_control_out; + +#if PICO_USBDEV_MAX_DESCRIPTOR_SIZE > 64 +static struct usb_stream_transfer _control_in_stream_transfer; +#define _control_in_transfer _control_in_stream_transfer.core +#else +static struct usb_transfer _control_in_transfer; +#endif +static struct usb_transfer _control_out_transfer; + +static struct usb_device _device; +static struct usb_endpoint *_endpoints[PICO_USBDEV_MAX_ENDPOINTS]; + +static inline const char *_in_out_string(bool in) { + return in ? "IN" : "OUT"; +} + +/** + * @param ep + * @return a 32 bit pointer to both buffer control registers for an endpoint + */ +static io_rw_32 *_usb_buf_ctrl_wide(const struct usb_endpoint *ep) { + return ep->in ? &usb_dpram->ep_buf_ctrl[ep->num].in : &usb_dpram->ep_buf_ctrl[ep->num].out; +} + +/** + * @param ep + * @param which 0 or 1 double-buffer index + * @return a 16 bit pointer to the specified (1 of 2) buffer control register for an endpoint + */ +static io_rw_16 *_usb_buf_ctrl_narrow(const struct usb_endpoint *ep, uint which) { + return &((io_rw_16 *) _usb_buf_ctrl_wide(ep))[which]; +} + +static uint _ep_buffer_count(const struct usb_endpoint *ep) { + return ep->double_buffered ? 2 : 1; +} + +#ifndef NDEBUG +static void _usb_dump_eps(void) +{ + printf("\n"); + for (int num = 1; num < PICO_USBDEV_MAX_ENDPOINTS; num++) { + for(int b = 0; b < 2; b++) + { + struct usb_endpoint *ep = _endpoints[num]; + uint16_t ctrl = (uint16_t) *_usb_buf_ctrl_narrow(ep, b); + uint8_t pid = (ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; + printf("ep %d %s <= 0x%04x (DATA%d", ep->num, usb_endpoint_dir_string(ep), ctrl, pid); + if (ctrl & USB_BUF_CTRL_FULL) + { printf(", FULL"); } + if (ctrl & USB_BUF_CTRL_LAST) + { printf(", LAST"); } + if (ctrl & USB_BUF_CTRL_SEL) + { printf(", SEL"); } + printf(", LEN = %04x)\n", ctrl & USB_BUF_CTRL_LEN_MASK); + } + } + usb_reset_trace(); +} +#endif + +/** + * Reset the buffers for an endpoint to CPU ownership, aborting the buffers if necessary + * @param ep + */ +static void _usb_reset_buffers(struct usb_endpoint *ep) { + if ((USB_BUF_CTRL_AVAIL * 0x10001) & *_usb_buf_ctrl_wide((ep))) { + usb_debug("Must abort buffers %d %s owned=%d %08x!!!\n", ep->num, usb_endpoint_dir_string(ep), + ep->owned_buffer_count, (uint) *_usb_buf_ctrl_wide( + ep)); + // if the hardware owns 1 buffer, then when we reset we toggle the pid (in double-buffer mode it could own two) + if (!ep->double_buffered || ep->owned_buffer_count == 1) { + usb_debug("Toggling PID as buffers restored"); + ep->next_pid ^= 1u; + } + usb_dump_trace(); + uint32_t mask = 1u << ep->buffer_bit_index; + usb_hw_clear->abort_done = mask; + usb_hw_set->abort = mask; + int count = 100000; + while (!(usb_hw->abort_done & mask) && --count) { + usb_hw_set->abort = mask; + } + if (!count) { + usb_warn("**** FAILED TO ABORT %d %s: %08x %08x\n", ep->num, usb_endpoint_dir_string(ep), + (uint) usb_hw->abort, (uint) usb_hw->abort_done); + } + usb_hw_clear->abort = mask; + usb_hw_clear->abort_done = mask; + } + *_usb_buf_ctrl_wide(ep) = 0; + ep->owned_buffer_count = _ep_buffer_count(ep); + usb_debug("clear current buffer %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + ep->current_give_buffer = ep->current_take_buffer = 0; + ep->first_buffer_after_reset = true; +} + +/** + * Stall the given endpoint + * + * @param ep + * @param hs + */ +static void _usb_stall_endpoint(struct usb_endpoint *ep, enum usb_halt_state hs) { + assert(hs); + __unused enum usb_halt_state old_hs = ep->halt_state; + if (!ep->halt_state) { + if (ep->num == 0) { + // A stall on EP0 has to be armed so it can be cleared on the next setup packet + usb_hw_set->ep_stall_arm = ep->in ? USB_EP_STALL_ARM_EP0_IN_BITS : USB_EP_STALL_ARM_EP0_OUT_BITS; + } + *_usb_buf_ctrl_wide(ep) |= USB_BUF_CTRL_STALL; + ep->halt_state = hs; + if (ep->on_stall_change) ep->on_stall_change(ep); + } else { + // we should be stalled + assert(USB_BUF_CTRL_STALL & *_usb_buf_ctrl_wide(ep)); + if (hs > ep->halt_state) ep->halt_state = hs; + } + usb_debug("Stall %d %s %d->%d\n", ep->num, usb_endpoint_dir_string(ep), old_hs, hs); +} + +/** + * Initialize any endpoint (0 or user defined) + * @param ep + * @param num + * @param in + * @param max_buffer_size + * @param double_buffered + * @return + */ +static __noinline struct usb_endpoint *_usb_endpoint_init_internal(struct usb_endpoint *ep, + uint num, + bool in, + uint max_buffer_size, + bool double_buffered) { + // for some inling of memset reason, removing this makes the code larger! + usb_common_init(ep); + ep->num = num; + ep->in = in; + ep->buffer_size = max_buffer_size; +#ifndef USB_SINGLE_BUFFERED + ep->double_buffered = double_buffered; +#endif +#if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16 + ep->buffer_stride = 64; +#endif + ep->buffer_bit_index = ((num * 2u) + (in ? 0u : 1u)); + return ep; +} + +static uint32_t _usb_endpoint_stride(__unused struct usb_endpoint *ep) { +#if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16 + return ep->buffer_stride; +#else + return 64; +#endif +} + +static void _usb_endpoint_hw_init(struct usb_endpoint *ep, __unused uintptr_t data) { + uint ep_num = usb_endpoint_number(ep); + usb_dpram->ep_buf_ctrl[ep_num].in = 0; + usb_dpram->ep_buf_ctrl[ep_num].out = 0; + ep->dpram_buffer_offset = _device.next_buffer_offset; + usb_debug("endpoint %d %s buf at %04x %04xx%d\n", ep_num, usb_endpoint_dir_string(ep), ep->dpram_buffer_offset, + ep->buffer_size, _ep_buffer_count(ep)); + uint32_t stride = _usb_endpoint_stride(ep); + if (ep->double_buffered) stride <<= 1u; + _device.next_buffer_offset += stride; + assert(_device.next_buffer_offset <= USB_DPRAM_MAX); + if (ep_num) { + uint32_t reg = EP_CTRL_ENABLE_BITS + | (ep->double_buffered ? EP_CTRL_DOUBLE_BUFFERED_BITS : 0u) + | EP_CTRL_INTERRUPT_PER_BUFFER + //| EP_CTRL_INTERRUPT_ON_NAK + // | EP_CTRL_INTERRUPT_ON_STALL + | ep->dpram_buffer_offset + #if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16 + | (ep->descriptor->bmAttributes << EP_CTRL_BUFFER_TYPE_LSB); +#else + | (USB_TRANSFER_TYPE_BULK << EP_CTRL_BUFFER_TYPE_LSB); + assert(ep->descriptor->bmAttributes == USB_TRANSFER_TYPE_BULK); +#endif + // todo coordinate with buff control + if (ep->in) { + usb_dpram->ep_ctrl[ep_num - 1].in = reg; + usb_dpram->ep_ctrl[ep_num - 1].out = 0; + } else { + usb_dpram->ep_ctrl[ep_num - 1].in = 0; + usb_dpram->ep_ctrl[ep_num - 1].out = reg; + } + } +} + +typedef void (*endpoint_callback)(struct usb_endpoint *endpoint, uintptr_t data); + +static void _usb_for_each_endpoint(endpoint_callback callback, bool include_control, uintptr_t data) { + // note order is important here as the buffers are allocated in enumeration order + if (include_control) { + callback(&usb_control_in, data); + callback(&usb_control_out, data); + } + for (uint i = 1; i < count_of(_endpoints); i++) { + if (_endpoints[i]) { + callback(_endpoints[i], data); + } + } +} + +void _usb_transfer_current_packet_only(struct usb_endpoint *ep) { +// printf("usb_transfer_current_packet_only %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + if (ep->in) { + assert(usb_current_in_packet_buffer(ep)->data_len < + ep->buffer_size); // must not be buffer_size or we'd need two + } + usb_packet_done(ep); +} + +static void _usb_reset_endpoint(struct usb_endpoint *ep, bool hard) { + // ok we need to update the packet +#if !PICO_USBDEV_NO_TRANSFER_ON_CANCEL_METHOD + if (ep->current_transfer && ep->current_transfer->type->on_cancel) { + ep->current_transfer->type->on_cancel(ep); + } +#endif + ep->current_transfer = NULL; + _usb_reset_buffers(ep); // hopefully a no-op + if (hard) { + // must be done after reset buffers above + if (ep->next_pid) { + usb_debug("Reset pid to 0 %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + } + ep->next_pid = 0; + } + ep->current_hw_buffer.valid = false; + if (ep->halt_state) { + ep->halt_state = HS_NONE; + if (ep->on_stall_change) ep->on_stall_change(ep); + } + // note on_stall_change might have started a transfer + if (_device.current_config_num && ep->default_transfer && !ep->current_transfer) { + usb_debug("start default %d %s, nextpid = %d\n", ep->num, usb_endpoint_dir_string(ep), ep->next_pid); + usb_reset_and_start_transfer(ep, ep->default_transfer, ep->default_transfer->type, 0); + } +} + +static void _usb_hard_reset_endpoint_callback(struct usb_endpoint *ep, __unused uintptr_t data) { + usb_hard_reset_endpoint(ep); +} + +static void _usb_handle_set_address(uint addr) { + assert(!_device.current_config_num); // we expect to be unconfigured + _device.current_address = addr; + usb_hw->dev_addr_ctrl = addr; +} + +static void _usb_handle_set_config(uint config_num) { + _device.current_config_num = config_num; + _usb_for_each_endpoint(_usb_hard_reset_endpoint_callback, false, 0); + if (_device.on_configure) { + _device.on_configure(&_device, config_num != 0); + } +} + +static void _usb_handle_bus_reset() { +#if PICO_USBDEV_ENABLE_DEBUG_TRACE + usb_dump_trace(); + usb_reset_trace(); +#endif + + // downgrade to unconfigured state + _usb_handle_set_config(0); + // downgrade to unaddressed state + _usb_handle_set_address(0); + + // Clear buf status + sie status + usb_hw_clear->buf_status = 0xffffffff; + usb_hw_clear->sie_status = 0xffffffff; +// // todo? +// //usb_hw->abort = 0xffffffff; +} + +#define should_handle_setup_request(e, s) (!(e)->setup_request_handler || !(e)->setup_request_handler(e, s)) + +struct usb_buffer *usb_current_packet_buffer(struct usb_endpoint *ep) { + struct usb_buffer *packet = &ep->current_hw_buffer; +// usb_debug("cpb %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + if (!packet->valid) { + packet->data_max = ep->buffer_size; + uint which = ep->in ? ep->current_give_buffer : ep->current_take_buffer; + if (ep->in) { + assert(!(USB_BUF_CTRL_FULL & *_usb_buf_ctrl_narrow(ep, which))); + } else { + assert((USB_BUF_CTRL_FULL & *_usb_buf_ctrl_narrow(ep, which))); + } + packet->data = ((uint8_t *) (USBCTRL_DPRAM_BASE + ep->dpram_buffer_offset + + (which ? _usb_endpoint_stride(ep) : 0))); +// usb_debug("%d %s which %d len %08x\n", ep->num, usb_endpoint_dir_string(ep), which, (uint)*_usb_buf_ctrl_wide(ep)); + packet->data_len = ep->in ? 0 : (USB_BUF_CTRL_LEN_MASK & *_usb_buf_ctrl_narrow(ep, which)); + //usb_debug("getting buffer for endpoint %02x %s %p: buf_ctrl %d -> %04x\n", usb_endpoint_number(ep), usb_endpoint_dir_string(ep), packet->data, which, *_usb_buf_ctrl_narrow(ep, which)); + packet->valid = true; + } + return packet; +} + +void _usb_give_buffer(struct usb_endpoint *ep, uint32_t len) { + assert(ep->owned_buffer_count); + assert(ep->current_transfer); + assert(!ep->halt_state); + ep->halt_state = HS_NONE; // best effort recovery + + assert(len < 1023); + uint32_t val = len | USB_BUF_CTRL_AVAIL; + + if (ep->first_buffer_after_reset) { + assert(!ep->current_give_buffer); + val |= USB_BUF_CTRL_SEL; + ep->first_buffer_after_reset = false; + } + + assert(len <= ep->buffer_size); + if (ep->in) val |= USB_BUF_CTRL_FULL; + val |= ep->next_pid ? USB_BUF_CTRL_DATA1_PID : USB_BUF_CTRL_DATA0_PID; + ep->next_pid ^= 1u; +#if PICO_USBDEV_ENABLE_DEBUG_TRACE + debug_trace[trace_i][0] = (uint32_t) _usb_buf_ctrl_narrow(ep, ep->current_give_buffer); + debug_trace[trace_i][1] = val; + trace_i++; + if (trace_i == 128) { + trace_i = 0; + } +#endif + +#if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16 + if (ep->current_give_buffer) { + val |= PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE + << 11u; // 11 + 16 = 27 - which is where stride bits go (and only relevant on buffer 1) + } +#endif + + *_usb_buf_ctrl_narrow(ep, ep->current_give_buffer) = val; + if (ep->in) { + // if there is a buffer len, then it must have been accessed to fill it with data + assert(!len || ep->current_hw_buffer.valid); + } + + ep->current_hw_buffer.valid = false; + ep->owned_buffer_count--; + ep->current_transfer->remaining_packets_to_submit--; + if (ep->double_buffered) { + ep->current_give_buffer ^= 1u; +// usb_debug("toggle current give buffer %d %s to %d\n", ep->num, usb_endpoint_dir_string(ep), ep->current_give_buffer); + } +} + +static void _usb_call_on_packet(struct usb_endpoint *ep) { + struct usb_transfer *current_transfer = ep->current_transfer; + assert(current_transfer); + assert(!current_transfer->outstanding_packet); + current_transfer->outstanding_packet = true; + current_transfer->type->on_packet(ep); +} + +// If we own buffers, we try and transfer them to the hardware (either by filling packets via on_packet for +// IN or by passing empty buffers for out) +void _usb_give_as_many_buffers_as_possible(struct usb_endpoint *ep) { + while (ep->current_transfer && ep->current_transfer->remaining_packets_to_submit && ep->owned_buffer_count && + !ep->halt_state) { + if (ep->in) { + uint old = ep->owned_buffer_count; + _usb_call_on_packet(ep); + if (old == ep->owned_buffer_count) { + // on_packet did not yet submit anything + break; + } + } else { + if (ep->current_transfer->outstanding_packet) { + usb_warn("untested? give buffer with outstanding packet %d %s owned %d\n", ep->num, + usb_endpoint_dir_string(ep), ep->owned_buffer_count); + } + _usb_give_buffer(ep, ep->buffer_size); + } + } +} + +static void __noinline _usb_check_for_transfer_completion(struct usb_endpoint *ep) { + struct usb_transfer *transfer = ep->current_transfer; + assert(transfer); + if (ep->halt_state || !(transfer->remaining_packets_to_handle || transfer->outstanding_packet)) { + assert(!transfer->completed); + transfer->completed = true; + ep->current_transfer = NULL; + if (ep->halt_state) { + if (transfer->on_complete) { + usb_warn("untested? stall of transfer with on_complete set %d %s %p\n", ep->num, + usb_endpoint_dir_string(ep), transfer->on_complete); + } + transfer->remaining_packets_to_submit = transfer->remaining_packets_to_handle = 0; + return; + } + if (transfer->on_complete) { + assert(!ep->chain_transfer); + usb_debug("calling on complete\n"); + transfer->on_complete(ep, transfer); + } else if (ep->chain_transfer) { + usb_debug("chaining transfer\n"); + usb_start_transfer(ep, ep->chain_transfer); + } + } else if (!transfer->remaining_packets_to_handle) { + usb_debug("outstanding packet %d on %d %s\n", transfer->outstanding_packet, ep->num, + usb_endpoint_dir_string(ep)); + } +} + +static void _usb_handle_transfer(uint ep_num, bool in, uint which) { + struct usb_endpoint *ep; + assert(ep_num < PICO_USBDEV_MAX_ENDPOINTS); + if (ep_num) { + ep = _endpoints[ep_num]; + } else { + ep = in ? &usb_control_in : &usb_control_out; + } + assert(ep); // "Received buffer IRQ for unknown EP"); + assert(!ep->halt_state); + ep->owned_buffer_count++; + struct usb_transfer *transfer = ep->current_transfer; + if (!transfer) { + usb_warn("received unexpected packet on %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + return usb_halt_endpoint(ep); + } + assert(transfer->remaining_packets_to_handle); + if (transfer->outstanding_packet) { + usb_debug("re-enter %d %s which=%d\n", ep->num, usb_endpoint_dir_string(ep), which); + assert(ep->double_buffered); + assert(which != ep->current_take_buffer); + transfer->packet_queued = true; + } else { + ep->current_take_buffer = which; + // we only called on_packet for submit-able packets for an in transfer + if (!ep->in || transfer->remaining_packets_to_submit) { + _usb_call_on_packet(ep); + } + // transfer might already be completed during on_packet() if we stalled. + if (!transfer->completed) { + assert(transfer->remaining_packets_to_handle); + --transfer->remaining_packets_to_handle; + _usb_check_for_transfer_completion(ep); + } + } +} + +void usb_packet_done(struct usb_endpoint *ep) { + struct usb_buffer *buffer = &ep->current_hw_buffer; + assert(buffer == &ep->current_hw_buffer); + struct usb_transfer *transfer = ep->current_transfer; + assert(transfer); + assert(transfer->outstanding_packet); + transfer->outstanding_packet = false; + _usb_check_for_transfer_completion(ep); + if (!transfer->completed) { + // usb_debug("buffer done for endpoint %02x %s %d/%d\n", usb_endpoint_number(ep), usb_endpoint_dir_string(ep), + // buffer->data_len, buffer->data_max); + if (ep->in) { + assert(buffer->valid); + assert(buffer->data_len <= ep->buffer_size); + _usb_give_buffer(ep, buffer->data_len); + } + ep->current_hw_buffer.valid = false; + + if (transfer->packet_queued) { + assert(ep->double_buffered); + usb_debug("Toggling current take buffer to %d and sending deferred packet %d %s\n", + ep->current_take_buffer ^ 1u, ep->num, + usb_endpoint_dir_string(ep)); + transfer->packet_queued = false; + ep->owned_buffer_count--; // todo this is a bit of a hack because the function increments it a second time - maybe pass a param + _usb_handle_transfer(ep->num, ep->in, ep->current_take_buffer ^ 1u); + } else { + // we may now need to top up double buffer; + // note this call may cause recursion back into this function + _usb_give_as_many_buffers_as_possible(ep); + } + } +} + +void usb_set_default_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer) { + assert(!ep->default_transfer); + ep->default_transfer = transfer; +} + +void usb_start_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer) { + assert(!ep->current_transfer); + ep->current_transfer = transfer; + ep->chain_transfer = NULL; + assert(transfer); + assert(!transfer->started); + transfer->started = true; + assert(transfer->type->on_packet); + // currently we explicitly disallow these rather than ending immediately. + assert(transfer->remaining_packets_to_submit); + assert(transfer->remaining_packets_to_handle); +#if !PICO_USBDEV_NO_TRANSFER_ON_INIT_METHOD + if (transfer->type->on_init) { + transfer->type->on_init(ep); + } +#endif + _usb_give_as_many_buffers_as_possible(ep); +} + +void usb_chain_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer) { + assert(ep->current_transfer); + assert(!ep->current_transfer->completed); + assert(!ep->current_transfer->on_complete); + ep->chain_transfer = transfer; +} + +void __noinline usb_reset_transfer(struct usb_transfer *transfer, const struct usb_transfer_type *type, + usb_transfer_completed_func on_complete) { + memset(transfer, 0, sizeof(struct usb_transfer)); + transfer->type = type; + transfer->on_complete = on_complete; + transfer->remaining_packets_to_submit = transfer->remaining_packets_to_handle = type->initial_packet_count; +} + +void usb_reset_and_start_transfer(struct usb_endpoint *ep, struct usb_transfer *transfer, + const struct usb_transfer_type *type, usb_transfer_completed_func on_complete) { + usb_reset_transfer(transfer, type, on_complete); + usb_start_transfer(ep, transfer); +} + +void usb_stall_control_pipe(__unused struct usb_setup_packet *setup) { + // NOTE: doing this inside of usb_stall_endpoint which might seem reasonable allows a RACE with the host + // whereby it may send a new SETUP packet in response to one STALL before we have gotten to clearing + // the second buffer (yes I see this with the USB 2 Command Verifier!) + _usb_reset_buffers(&usb_control_in); + _usb_reset_buffers(&usb_control_out); + + _usb_stall_endpoint(&usb_control_in, HS_NON_HALT_STALL); + _usb_stall_endpoint(&usb_control_out, HS_NON_HALT_STALL); +} + +static void _tf_send_control_in_ack(__unused struct usb_endpoint *endpoint, __unused struct usb_transfer *transfer) { + assert(endpoint == &usb_control_in); + assert(transfer == &_control_in_transfer); + usb_debug("_tf_setup_control_ack\n"); + usb_start_empty_transfer(&usb_control_out, &_control_out_transfer, 0); +} + +static void _tf_send_control_out_ack(__unused struct usb_endpoint *endpoint, __unused struct usb_transfer *transfer) { + assert(endpoint == &usb_control_out); + assert(transfer == &_control_out_transfer); + usb_debug("_tf_setup_control_ack\n"); + usb_start_empty_transfer(&usb_control_in, &_control_in_transfer, 0); +} + +static void _tf_set_address(__unused struct usb_endpoint *endpoint, __unused struct usb_transfer *transfer) { + assert(endpoint == &usb_control_in); + usb_debug("_tf_set_address %d\n", _device.pending_address); + _usb_handle_set_address(_device.pending_address); +} + +static struct usb_configuration *_usb_get_current_configuration() { + if (_device.current_config_num) return &_device.config; + return NULL; +} + +static struct usb_configuration *_usb_find_configuration(uint num) { + if (_device.config.descriptor->bConfigurationValue == num) { + return &_device.config; + } + return NULL; +} + +static int _usb_prepare_string_descriptor(uint8_t *buf, __unused uint buf_len, const char *str) { + int len = 2; + uint8_t c; + while (0 != (c = *str++)) { + assert(len < buf_len); + *(uint16_t *) (buf + len) = c; + len += 2; + } + buf[0] = len; + buf[1] = 3; // bDescriptorType + return len; +} + +static int _usb_handle_get_descriptor(uint8_t *buf, uint buf_len, struct usb_setup_packet *setup) { + int len = -1; + const uint8_t *src = NULL; + buf = __builtin_assume_aligned(buf, 4); + switch (setup->wValue >> 8u) { + case USB_DT_DEVICE: { + usb_trace("GET DEVICE DESCRIPTOR\n"); + len = sizeof(*_device.descriptor); + src = (const uint8_t *) _device.descriptor; + break; + } + case USB_DT_CONFIG: { + usb_trace("GET CONFIG DESCRIPTOR %d\n", (uint8_t) setup->wValue); + if (!(uint8_t) setup->wValue) { + len = _device.config.descriptor->wTotalLength; + src = (const uint8_t *) _device.config.descriptor; + } + break; + } + case USB_DT_STRING: { + uint8_t index = setup->wValue; + usb_trace("GET STRING DESCRIPTOR %d\n", index); + if (index == 0) { + // todo for now english only + static const uint8_t lang_descriptor[] = + { + 4, // bLength + 0x03, // bDescriptorType == String Descriptor + 0x09, 0x04 // language id = us english + }; + len = 4; + src = lang_descriptor; + } else { + assert(_device.get_descriptor_string); + const char *descriptor_string = _device.get_descriptor_string(index); + assert(descriptor_string); + len = _usb_prepare_string_descriptor(buf, buf_len, descriptor_string); + } + break; + } + } + if (src && len > 0) { + assert(len <= buf_len); + memcpy(buf, src, len); + } + return len; +} + +static void _usb_default_handle_device_setup_request(struct usb_setup_packet *setup) { + setup = __builtin_assume_aligned(setup, 4); + if (!(setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { + if (setup->bmRequestType & USB_DIR_IN) { + struct usb_buffer *in_packet = usb_current_in_packet_buffer(&usb_control_in); + uint8_t *buf = in_packet->data; + uint buf_len = in_packet->data_max; + int len = -1; + + switch (setup->bRequest) { + case USB_REQUEST_GET_STATUS: { + usb_debug("DEVICE GET_STATUS\n"); + *((uint16_t *) in_packet->data) = 0; + len = 2; + break; + } + case USB_REQUEST_GET_DESCRIPTOR: { + usb_debug("DEVICE GET_DESCRIPTOR\n"); +#if PICO_USBDEV_MAX_DESCRIPTOR_SIZE > 64 + static __aligned(4) uint8_t descriptor_buf[PICO_USBDEV_MAX_DESCRIPTOR_SIZE]; + static struct usb_stream_transfer_funcs control_stream_funcs = { + .on_chunk = usb_stream_noop_on_chunk, + .on_packet_complete = usb_stream_noop_on_packet_complete + }; + len = _usb_handle_get_descriptor(descriptor_buf, sizeof(descriptor_buf), setup); + if (len != -1) + { + len = MIN(len, setup->wLength); + usb_stream_setup_transfer(&_control_in_stream_transfer, &control_stream_funcs, descriptor_buf, + sizeof(descriptor_buf), len, _tf_send_control_in_ack); + + _control_in_stream_transfer.ep = &usb_control_in; + return usb_start_transfer(&usb_control_in, &_control_in_stream_transfer.core); + } else { + //usb_warn("Didn't find requested device descriptor\n"); + } +#else + len = _usb_handle_get_descriptor(buf, buf_len, setup); +#endif + break; + } + case USB_REQUEST_GET_CONFIGURATION: { + usb_debug("DEVICE GET_CONFIGURATION\n"); + *((uint8_t *) buf) = _device.current_config_num; + len = 1; + break; + } + } + if (len >= 0) { + assert(len < buf_len); // a bit late + in_packet->data_len = MIN(len, setup->wLength); + return usb_start_single_buffer_control_in_transfer(); + } + usb_warn("Unhandled device IN setup request %02x\n", setup->bRequest); + } else { + switch (setup->bRequest) { + case USB_REQUEST_SET_FEATURE: { + assert(false); + break; + } + case USB_REQUEST_SET_ADDRESS: { + uint8_t addr = setup->wValue; + if (addr && addr <= 127) { + usb_debug("SET ADDRESS %02x\n", addr); + _device.pending_address = addr; + return usb_start_empty_control_in_transfer(_tf_set_address); + } + break; + } + case USB_REQUEST_SET_DESCRIPTOR: { + assert(false); + break; + } + case USB_REQUEST_SET_CONFIGURATION: { + uint8_t config_num = setup->wValue; + usb_debug("SET CONFIGURATION %02x\n", config_num); + if (!config_num || _usb_find_configuration(config_num)) { + // graham 1/3/20 removed this: + // USB 2.0 9.4.7: "If the specified configuration value matches the configuration value from a + // configuration descriptor, then that configuration is selected and the device remains in + // the Configured state" + // USB 2.0 9.4.5: "The Halt feature is reset to zero after either a SetConfiguration() or SetInterface() request even if the + // requested configuration or interface is the same as the current configuration or interface." + // + // Since there isn't a particularly clean way to unset a STALL, i'm taking this to mean that we should just do regular config setting tuff + // if (config_num != device.current_config_num) + // { + _usb_handle_set_config(config_num); + // } + return usb_start_empty_control_in_transfer_null_completion(); + } + break; + } + } + usb_warn("Unhandled device OUT setup request %02x\n", setup->bRequest); + } + } + // default + return usb_stall_control_pipe(setup); +} + +static void _usb_default_handle_interface_setup_request(struct usb_setup_packet *setup, + __unused struct usb_interface *interface) { + // check for valid class request + if (!(setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK) && !(setup->wIndex >> 8u)) { + if (setup->bmRequestType & USB_DIR_IN) { + switch (setup->bRequest) { + case USB_REQUEST_GET_STATUS: { + usb_debug("DEVICE GET_STATUS\n"); + return usb_start_tiny_control_in_transfer(0, 2); + } +#if !PICO_USBDEV_NO_INTERFACE_ALTERNATES + case USB_REQUEST_GET_INTERFACE: { + if (!setup->wValue && setup->wLength == 1) { + return usb_start_tiny_control_in_transfer(interface->alt, 1); + } + } +#endif + } + } else { + switch (setup->bRequest) { + case USB_REQUEST_SET_INTERFACE: { +#if !PICO_USBDEV_NO_INTERFACE_ALTERNATES + if (interface->set_alternate_handler) { + if (interface->set_alternate_handler(interface, setup->wValue)) { + interface->alt = setup->wValue; + return usb_start_empty_control_in_transfer_null_completion(); + } + } +#endif + // todo should we at least clear all HALT? - i guess given that we don't support this is fine + usb_warn("(ignored) set interface %d (alt %d)\n", setup->wIndex, setup->wValue); + break; + } + } + } + } + usb_warn("Unhandled interface %02x setup request %02x bmRequestType %02x\n", + interface->descriptor->bInterfaceNumber, setup->bRequest, setup->bmRequestType); + // default + return usb_stall_control_pipe(setup); +} + +static void _usb_default_handle_endpoint_setup_request(struct usb_setup_packet *setup, struct usb_endpoint *ep) { + if (!(setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { + if (setup->bmRequestType & USB_DIR_IN) { + switch (setup->bRequest) { + case USB_REQUEST_GET_STATUS: { + if (!setup->wValue && setup->wLength == 2) { + // HALT FEATURE is not set for control stall + return usb_start_tiny_control_in_transfer(ep->halt_state > HS_NON_HALT_STALL ? 1 : 0, 2); + } + break; + } + } + usb_warn("Unhandled ep %02x %s IN setup request %02x\n", ep->num, usb_endpoint_dir_string(ep), + setup->bRequest); + } else { + switch (setup->bRequest) { + case USB_REQUEST_CLEAR_FEATURE: { + if (setup->wValue == USB_FEAT_ENDPOINT_HALT) { + if (ep->halt_state < HS_HALTED_ON_CONDITION) { + usb_debug("Request unhalt EP %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + usb_hard_reset_endpoint(ep); + } else { + ep->next_pid = 0; // must always reset data toggle + usb_debug("Skipped unhalt EP %d %s halt_state = %d\n", ep->num, usb_endpoint_dir_string(ep), + ep->halt_state); + } + return usb_start_empty_control_in_transfer_null_completion(); + } + break; + } + case USB_REQUEST_SET_FEATURE: { + if (setup->wValue == USB_FEAT_ENDPOINT_HALT) { + usb_debug("Request halt EP %d %s\n", ep->num, usb_endpoint_dir_string(ep)); + _usb_stall_endpoint(ep, HS_HALTED); + return usb_start_empty_control_in_transfer_null_completion(); + } + break; + } + } + usb_warn("Unhandled ep %02x %s OUT setup request %02x\n", ep->num, usb_endpoint_dir_string(ep), + setup->bRequest); + } + } else { + usb_warn("Unhandled endpoint %d %s setup request %02x bmRequestType %02x\n", ep->num, + usb_endpoint_dir_string(ep), setup->bRequest, setup->bmRequestType); + } + // default + return usb_stall_control_pipe(setup); +} + +// returns null if device not configured +static struct usb_interface *_usb_find_interface(uint num) { + struct usb_configuration *config = _usb_get_current_configuration(); + if (config) { +#if PICO_USBDEV_USE_ZERO_BASED_INTERFACES + if (num < _usb_interface_count(config)) { + return config->interfaces[num]; + } +#else + for (uint i = 0; i < _usb_interface_count(config); i++) { + if (config->interfaces[i]->descriptor->bInterfaceNumber == num) { + return config->interfaces[i]; + } + } +#endif + } + return NULL; +} + +// returns null if device not configured +static struct usb_endpoint *_usb_find_endpoint(uint num) { + if (!num) { + return &usb_control_out; + } else if (num == USB_DIR_IN) { + return &usb_control_in; + } + if (_usb_get_current_configuration()) { + for (uint i = 1; i < count_of(_endpoints); i++) { + if (_endpoints[i]->descriptor->bEndpointAddress == num) { + return _endpoints[i]; + } + } + } + return NULL; +} + +static void _usb_handle_setup_packet(struct usb_setup_packet *setup) { + usb_debug("Setup packet\n"); + // a setup packet is always accepted, so reset anything in progress + usb_soft_reset_endpoint(&usb_control_in); + usb_soft_reset_endpoint(&usb_control_out); + usb_control_in.next_pid = usb_control_out.next_pid = 1; + switch (setup->bmRequestType & USB_REQ_TYPE_RECIPIENT_MASK) { + case USB_REQ_TYPE_RECIPIENT_DEVICE: { +#if !PICO_USBDEV_NO_DEVICE_SETUP_HANDLER + if (!should_handle_setup_request(&_device, setup)) return; +#endif + return _usb_default_handle_device_setup_request(setup); + } + case USB_REQ_TYPE_RECIPIENT_INTERFACE: { + struct usb_interface *interface = _usb_find_interface( + setup->wIndex & 0xffu); // todo interface is only one byte; high byte seems to be used for entity + usb_debug("Interface request %d %p\n", setup->wIndex, interface); + if (interface) { + if (!should_handle_setup_request(interface, setup)) return; + return _usb_default_handle_interface_setup_request(setup, interface); + } + usb_warn("Setup request %04x for unknown interface %04x\n", setup->bRequest, setup->wIndex); + break; + } + case USB_REQ_TYPE_RECIPIENT_ENDPOINT: { + struct usb_endpoint *endpoint = _usb_find_endpoint(setup->wIndex); + if (endpoint) { +#if !PICO_USBDEV_NO_ENDPOINT_SETUP_HANDLER + if (!should_handle_setup_request(endpoint, setup)) return; +#endif + return _usb_default_handle_endpoint_setup_request(setup, endpoint); + } + usb_warn("Setup packet %04x for unknown endpoint %04x\n", setup->wValue, setup->wIndex); + break; + } + } + usb_warn("Unhandled setup packet - stalling control pipe\n"); + // default + usb_stall_control_pipe(setup); +} + +static void _usb_handle_buffer() { + uint32_t buffers = usb_hw->buf_status; + uint32_t remaining_buffers = buffers; + + if (!buffers) { + usb_debug("_usb_handle_buffer called without any buffers set\n"); + } + + // do this for now could be smarter + uint bit = 1u; + for (uint i = 0; remaining_buffers && i < PICO_USBDEV_MAX_ENDPOINTS * 2; i++) { + if (remaining_buffers & bit) { + uint which = (usb_hw->buf_cpu_should_handle & bit) ? 1 : 0; + // clear this in advance + usb_hw_clear->buf_status = bit; + // IN transfer for even i, OUT transfer for odd i + _usb_handle_transfer(i >> 1u, !(i & 1u), which); + remaining_buffers &= ~bit; + } + bit <<= 1u; + } + if (remaining_buffers) { + usb_debug("Ignoring buffer event for impossible mask %08x\n", (uint) remaining_buffers); + usb_hw_clear->buf_status = remaining_buffers; + } +} + +void __isr __used isr_usbctrl(void) { + uint32_t status = usb_hw->ints; + DEBUG_PINS_SET(usb_irq, 1); + + uint32_t handled = 0; + if (status & USB_INTS_SETUP_REQ_BITS) { + handled |= USB_INTS_SETUP_REQ_BITS; + _usb_handle_setup_packet(remove_volatile_cast(struct usb_setup_packet *, &usb_dpram->setup_packet)); + usb_hw_clear->sie_status = USB_SIE_STATUS_SETUP_REC_BITS; + } + + if (status & USB_INTS_BUFF_STATUS_BITS) { + handled |= USB_INTS_BUFF_STATUS_BITS; + _usb_handle_buffer(); + // Interrupt is cleared when buff flag is cleared + } + + if (status & USB_INTS_BUS_RESET_BITS) { + handled |= USB_INTS_BUS_RESET_BITS; + usb_debug("Bus Reset\n"); + _usb_handle_bus_reset(); + usb_hw_clear->sie_status = USB_SIE_STATUS_BUS_RESET_BITS; + rp2040_usb_device_enumeration_fix(); + } + + if (status & USB_INTS_ERROR_BITS) { + handled |= (status & USB_INTS_ERROR_BITS); +#ifndef NDEBUG + _usb_dump_eps(); +#endif + //uint32_t errs = usb_hw->sie_status; + usb_warn("Error 0x%lx (sie status 0x%lx)\n", (status & USB_INTS_ERROR_BITS), usb_hw->sie_status); + if (usb_hw->sie_status & USB_SIE_STATUS_DATA_SEQ_ERROR_BITS) { + usb_dump_trace(); + usb_warn("Data seq error\n"); + usb_hw_clear->sie_status = USB_SIE_STATUS_DATA_SEQ_ERROR_BITS; + } else { + // Assume we have been unplugged + usb_debug("Assuming unplugged\n"); + _usb_handle_bus_reset(); + } + } + + if (status ^ handled) { + usb_warn("Unhandled IRQ 0x%x\n", (uint) (status ^ handled)); + } + + DEBUG_PINS_CLR(usb_irq, 1); +} + +#if PICO_USBDEV_ENABLE_DEBUG_TRACE +void usb_dump_trace(void) +{ + usb_debug("\n"); + for (int i = 0; i < trace_i; i++) { + uint16_t ctrl = (uint16_t)debug_trace[i][1]; + uint8_t pid = (ctrl & USB_BUF_CTRL_DATA1_PID) ? 1 : 0; + int ep = -1, b = -1, d = -1; + for(int e=0;eep_buf_ctrl[e].in) + { + ep = e; + b = 0; + d = 0; + } else if (debug_trace[i][0] == 2 + (uintptr_t)&usb_dpram->ep_buf_ctrl[e].in) { + ep = e; + b = 1; + d = 0; + } else if (debug_trace[i][0] == (uintptr_t)&usb_dpram->ep_buf_ctrl[e].out) { + ep = e; + b = 0; + d = 1; + } else if (debug_trace[i][0] == 2 + (uintptr_t)&usb_dpram->ep_buf_ctrl[e].out) { + ep = e; + b = 1; + d = 1; + } + } + usb_debug("0x%lx (ep %d, b %d, d %d) <= 0x%x (DATA%d", debug_trace[i][0], ep, b, d, ctrl, pid); + if (debug_trace[i][0] & 0b100) { + usb_debug(", OUT"); + } else { + usb_debug(", IN "); + } + if (ctrl & USB_BUF_CTRL_FULL) { usb_debug(", FULL"); } + if (ctrl & USB_BUF_CTRL_LAST) { usb_debug(", LAST"); } + if (ctrl & USB_BUF_CTRL_SEL) { usb_debug(", SEL"); } + usb_debug(", LEN = %d)\n", ctrl & USB_BUF_CTRL_LEN_MASK); + } + usb_reset_trace(); +} + +void usb_reset_trace(void) +{ + trace_i = 0; +} +#endif + +const char *usb_endpoint_dir_string(struct usb_endpoint *ep) { + return _in_out_string(ep->in); +} + +static const void *usb_next_descriptor(const void *d, uint8_t type) { + const struct usb_descriptor *desc = (const struct usb_descriptor *) d; + do { + desc = (const struct usb_descriptor *) (((const uint8_t *) desc) + desc->bLength); + } while (desc->bDescriptorType != type); + return desc; +} + +/** + * Initialize the runtime data structures for an interface, and all its endpoints + * @param interface + * @param desc + * @param endpoints + * @param endpoint_count + * @param double_buffered + * @return + */ +struct usb_interface *usb_interface_init(struct usb_interface *interface, const struct usb_interface_descriptor *desc, + struct usb_endpoint *const *endpoints, uint endpoint_count, + bool double_buffered) { + assert(desc->bLength == sizeof(struct usb_interface_descriptor)); + assert(desc->bNumEndpoints == endpoint_count); + interface = usb_common_init(interface); + interface->descriptor = desc; + interface->endpoints = endpoints; + interface->endpoint_count = endpoint_count; + const void *p = (const void *) desc; + for (uint i = 0; i < endpoint_count; i++) { + p = usb_next_descriptor(p, USB_DESCRIPTOR_TYPE_ENDPOINT); + const struct usb_endpoint_descriptor *ep_desc = (const struct usb_endpoint_descriptor *) p; + assert(ep_desc->bLength >= sizeof(struct usb_endpoint_descriptor)); + assert(ep_desc->bDescriptorType == USB_DESCRIPTOR_TYPE_ENDPOINT); + uint8_t ep_num = ep_desc->bEndpointAddress & 0xfu; + assert(ep_num && ep_num < PICO_USBDEV_MAX_ENDPOINTS); + _usb_endpoint_init_internal(endpoints[i], ep_num, ep_desc->bEndpointAddress & USB_DIR_IN, + ep_desc->wMaxPacketSize, + double_buffered); + endpoints[i]->descriptor = ep_desc; +#if !PICO_USBDEV_BULK_ONLY_EP1_THRU_16 + if (USB_TRANSFER_TYPE_ISOCHRONOUS == (ep_desc->bmAttributes & USB_TRANSFER_TYPE_BITS)) { + endpoints[i]->buffer_stride = 128 << PICO_USBDEV_ISOCHRONOUS_BUFFER_STRIDE_TYPE; + } else { + endpoints[i]->buffer_stride = 64; + } + assert(ep_desc->wMaxPacketSize <= endpoints[i]->buffer_stride); +#endif + } + return interface; +} + +struct usb_device *usb_device_init(const struct usb_device_descriptor *desc, + const struct usb_configuration_descriptor *config_desc, + struct usb_interface *const *interfaces, uint interface_count, + const char *(*get_descriptor_string)(uint index)) { + usb_debug("-------------------------------------------------------------------------------\n"); + assert(desc->bLength == sizeof(struct usb_device_descriptor)); + assert(desc->bNumConfigurations == + 1); // all that is supported right now (otherwise we must handle GET/SET_CONFIGURATION better + assert(config_desc->bNumInterfaces == interface_count); + _device.descriptor = desc; + _device.config.descriptor = config_desc; + _device.config.interfaces = interfaces; +#ifndef PICO_USBDEV_FIXED_INTERFACE_COUNT + _device.config.interface_count = interface_count; +#endif + _device.get_descriptor_string = get_descriptor_string; + + _usb_endpoint_init_internal(&usb_control_in, 0, true, 64, false); + _usb_endpoint_init_internal(&usb_control_out, 0, false, 64, false); + usb_init_clear_deref(&_endpoints); + for (uint i = 0; i < interface_count; i++) { + for (uint e = 0; e < interfaces[i]->endpoint_count; e++) { + struct usb_endpoint *ep = interfaces[i]->endpoints[e]; + uint ep_num = usb_endpoint_number(ep); + assert(ep_num && ep_num < count_of(_endpoints)); + _endpoints[ep_num] = ep; + } + } +#if PICO_USBDEV_USE_ZERO_BASED_INTERFACES + for (uint i = 0; i < interface_count; i++) { + assert(interfaces[i]->descriptor->bInterfaceNumber == i); + } +#endif + + _device.next_buffer_offset = 0x100; + _usb_endpoint_hw_init(&usb_control_in, 0); + _device.next_buffer_offset = 0x100; + _usb_endpoint_hw_init(&usb_control_out, 0); + _device.next_buffer_offset = 0x180; + _usb_for_each_endpoint(_usb_endpoint_hw_init, false, 0); + return &_device; +} + +void usb_grow_transfer(struct usb_transfer *transfer, uint packet_count) { + transfer->remaining_packets_to_submit += packet_count; + transfer->remaining_packets_to_handle += packet_count; +} + +void usb_soft_reset_endpoint(struct usb_endpoint *ep) { + _usb_reset_endpoint(ep, false); +} + +void usb_hard_reset_endpoint(struct usb_endpoint *ep) { + _usb_reset_endpoint(ep, true); +} + +void usb_halt_endpoint(struct usb_endpoint *ep) { + _usb_stall_endpoint(ep, HS_HALTED); +}; + +void usb_halt_endpoint_on_condition(struct usb_endpoint *ep) { + _usb_stall_endpoint(ep, HS_HALTED_ON_CONDITION); +}; + +void usb_clear_halt_condition(struct usb_endpoint *ep) { + if (ep->halt_state == HS_HALTED_ON_CONDITION) { + ep->halt_state = HS_HALTED; // can be reset by regular unstall + } +} + +void usb_device_start() { + // At least on FPGA we don't know the previous state + // so clean up registers. Should be fine not clearing DPSRAM + io_rw_32 *reg = &usb_hw->dev_addr_ctrl; + // Don't touch phy trim + while (reg != &usb_hw->phy_trim) + *reg++ = 0; + + // Start setup +#if PICO_USBDEV_ENABLE_DEBUG_TRACE + trace_i = 0; +#endif + + usb_hw->muxing = USB_USB_MUXING_TO_PHY_BITS | USB_USB_MUXING_SOFTCON_BITS; + usb_hw->pwr = USB_USB_PWR_VBUS_DETECT_BITS | USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN_BITS; + usb_hw->main_ctrl = USB_MAIN_CTRL_CONTROLLER_EN_BITS; + + // Reset various things to default state + _usb_handle_bus_reset(); + + // Pull up starts the show. Enable IRQ for EP0 buffer done + usb_hw->sie_ctrl = USB_SIE_CTRL_PULLUP_EN_BITS | USB_SIE_CTRL_EP0_INT_1BUF_BITS; + // Present pull up before enabling bus reset irq + usb_hw->inte = USB_INTS_BUFF_STATUS_BITS | USB_INTS_BUS_RESET_BITS | USB_INTS_SETUP_REQ_BITS | + USB_INTS_ERROR_BITS;// | USB_INTS_EP_STALL_NAK_BITS; + + irq_set_enabled(USBCTRL_IRQ, true); +} + +void usb_device_stop(__unused struct usb_device *device) { + assert(false); +} + +void usb_start_tiny_control_in_transfer(uint32_t data, uint len) { + assert(len <= 4); + struct usb_buffer *buffer = usb_current_in_packet_buffer(&usb_control_in); + // little endian so this works for any len + *(uint32_t *) buffer->data = data; + buffer->data_len = len; + return usb_start_single_buffer_control_in_transfer(); +} + +void usb_start_single_buffer_control_in_transfer() { + assert(usb_current_in_packet_buffer(&usb_control_in)->data_len < + 64); // we don't want to have to send an extra packet + usb_reset_and_start_transfer(&usb_control_in, &_control_in_transfer, &usb_current_packet_only_transfer_type, + _tf_send_control_in_ack); +} + +void usb_start_control_out_transfer(const struct usb_transfer_type *type) { + usb_reset_and_start_transfer(&usb_control_out, &_control_out_transfer, type, _tf_send_control_out_ack); +} + +void usb_start_empty_transfer(struct usb_endpoint *endpoint, struct usb_transfer *transfer, + usb_transfer_completed_func on_complete) { + if (endpoint->in) usb_current_in_packet_buffer(endpoint)->data_len = 0; + usb_reset_and_start_transfer(endpoint, transfer, &usb_current_packet_only_transfer_type, on_complete); +} + +void usb_start_empty_control_in_transfer(usb_transfer_completed_func on_complete) { + usb_start_empty_transfer(&usb_control_in, &_control_in_transfer, on_complete); +} + +void usb_start_empty_control_in_transfer_null_completion() { + usb_start_empty_control_in_transfer(0); +} + +// this is provided as a wrapper to catch coding errors +struct usb_buffer *usb_current_in_packet_buffer(struct usb_endpoint *ep) { + assert(ep->in); + return usb_current_packet_buffer(ep); +} + +// this is provided as a wrapper to catch coding errors +struct usb_buffer *usb_current_out_packet_buffer(struct usb_endpoint *ep) { + assert(!ep->in); + return usb_current_packet_buffer(ep); +} + +void usb_start_default_transfer_if_not_already_running_or_halted(struct usb_endpoint *ep) { + // if we are in halt state we will do this again later; defensively check against current transfer already in place + if (!ep->halt_state && ep->current_transfer != ep->default_transfer) { + usb_reset_and_start_transfer(ep, ep->default_transfer, ep->default_transfer->type, 0); + } +} diff --git a/src/rp2_common/usb_device/usb_stream_helper.c b/src/rp2_common/usb_device/usb_stream_helper.c new file mode 100644 index 0000000..24a94ce --- /dev/null +++ b/src/rp2_common/usb_device/usb_stream_helper.c @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "pico/usb_stream_helper.h" + +static uint32_t _usb_stream_chunk_offset(struct usb_stream_transfer *transfer) { + return transfer->offset & (transfer->chunk_size - 1); +} + +void usb_stream_packet_handler_complete(struct usb_stream_transfer *transfer) { + struct usb_buffer *buffer; + struct usb_endpoint *ep = transfer->ep; +#ifndef NDEBUG + assert(transfer->packet_handler_complete_expected); + transfer->packet_handler_complete_expected = false; +#endif + assert(ep); + if (ep->in) { + buffer = usb_current_in_packet_buffer(ep); + assert(buffer); + assert(buffer->data_max == 64); + uint chunk_offset = _usb_stream_chunk_offset(transfer); + uint data_len = 64; + if (transfer->offset + 64 > transfer->transfer_length) { + data_len = transfer->transfer_length - transfer->offset; + } + buffer->data_len = data_len; + memcpy(buffer->data, transfer->chunk_buffer + chunk_offset, data_len); + } else { + buffer = usb_current_out_packet_buffer(ep); + assert(buffer); + assert(buffer->data_len); + } + transfer->offset += buffer->data_len; + if (ep->num > 2) usb_debug(" %d transfer_offset %d\n", ep->num, (uint) transfer->offset); + assert(transfer->funcs && transfer->funcs->on_packet_complete); + transfer->funcs->on_packet_complete(transfer); +#ifdef USE_BOOTROM_GPIO + gpio_clr_mask(usb_activity_gpio_pin_mask); +#endif + usb_packet_done(ep); +} + +void usb_stream_chunk_done(struct usb_stream_transfer *transfer) { + usb_stream_packet_handler_complete(transfer); +} + +void _usb_stream_packet_packet_handler(struct usb_endpoint *ep) { +#ifdef USE_BOOTROM_GPIO + gpio_set_mask(usb_activity_gpio_pin_mask); +#endif + // todo assert type + struct usb_stream_transfer *transfer = (struct usb_stream_transfer *) ep->current_transfer; + uint chunk_offset = _usb_stream_chunk_offset(transfer); + uint chunk_len = 0; // set to non zero to call on_chunk + if (ep->in) { + if (!chunk_offset) { + // we are at the beginning of a chunk so want to call on_chunk + chunk_len = (transfer->offset + transfer->chunk_size) > transfer->transfer_length ? + transfer->transfer_length - transfer->offset : transfer->chunk_size; + if (ep->num > 2) + usb_warn("chunko %d len %05x offset %08x size %04x transfer %08x\n", ep->num, chunk_len, chunk_offset, + (uint) transfer->chunk_size, (uint) transfer->transfer_length); + } + } else { + // usb_debug("write packet %04x %d\n", (uint)transfer->offset, ep->current_take_buffer); + struct usb_buffer *buffer = usb_current_out_packet_buffer(ep); + assert(buffer); + // note we only set chunk_len if this is the end of a chunk + if (transfer->offset + 64 >= transfer->transfer_length) { + // we have ended the transfer (possibly mid-chunk) + chunk_len = transfer->transfer_length & (transfer->chunk_size - 1); + if (chunk_len) { + usb_warn(">> Truncated %08x\n", chunk_len); + } else { + chunk_len = transfer->chunk_size; + } + } else if (chunk_offset + 64 >= transfer->chunk_size) { + // end of regular chunk + chunk_len = transfer->chunk_size; + } + assert(chunk_len || buffer->data_len == 64); +// if (!(!chunk_len || buffer->data_len == ((chunk_len & 63u) ? (chunk_len & 63u) : 64u))) { +// usb_warn("ooh off=%08x len=%08x chunk_off=%04x chunk_len=%04x data_len=%04x\n", (uint)transfer->offset, (uint)transfer->transfer_length, chunk_offset, chunk_len, buffer->data_len); +// } + assert(!chunk_len || buffer->data_len == ((chunk_len & 63u) ? (chunk_len & 63u) : 64u)); + // zero buffer when we start a new buffer, so that the chunk callback never sees data it shouldn't (for partial chunks) + if (!chunk_offset) { + memset(transfer->chunk_buffer, 0, transfer->chunk_size); + } + memcpy(transfer->chunk_buffer + chunk_offset, buffer->data, buffer->data_len); // always safe to copy all + } +#ifndef NDEBUG + transfer->packet_handler_complete_expected = true; +#endif + + // todo i think this is reasonable since 0 length chunk does nothing + if (chunk_len) { + assert(transfer->funcs && transfer->funcs->on_chunk); + if (transfer->funcs->on_chunk(chunk_len, transfer)) + return; + } + usb_stream_packet_handler_complete(transfer); +} + +static const struct usb_transfer_type _usb_stream_transfer_type = { + .on_packet = _usb_stream_packet_packet_handler +}; + +void usb_stream_setup_transfer(struct usb_stream_transfer *transfer, const struct usb_stream_transfer_funcs *funcs, + uint8_t *chunk_buffer, uint32_t chunk_size, uint32_t transfer_length, + usb_transfer_completed_func on_complete) { + transfer->funcs = funcs; + transfer->chunk_buffer = chunk_buffer; + assert(!(chunk_size & 63u)); // buffer should be a multiple of USB packet buffer size + transfer->chunk_size = chunk_size; + transfer->offset = 0; + // todo combine with residue? + transfer->transfer_length = transfer_length; + usb_reset_transfer(&transfer->core, &_usb_stream_transfer_type, on_complete); + usb_grow_transfer(&transfer->core, (transfer_length + 63) / 64); +} + +void usb_stream_noop_on_packet_complete(__unused struct usb_stream_transfer *transfer) { + +} + +bool usb_stream_noop_on_chunk(uint32_t size, __unused struct usb_stream_transfer *transfer) { + return false; +} + + + diff --git a/src/rp2_common/usb_device_msc/CMakeLists.txt b/src/rp2_common/usb_device_msc/CMakeLists.txt new file mode 100644 index 0000000..debd7e9 --- /dev/null +++ b/src/rp2_common/usb_device_msc/CMakeLists.txt @@ -0,0 +1,8 @@ +add_library(usb_device_msc INTERFACE) + +target_sources(usb_device_msc INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/usb_device_msc.c +) + +target_include_directories(usb_device_msc INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_link_libraries(usb_device_msc INTERFACE usb_device) \ No newline at end of file diff --git a/src/rp2_common/usb_device_msc/include/pico/scsi.h b/src/rp2_common/usb_device_msc/include/pico/scsi.h new file mode 100644 index 0000000..3b14ab3 --- /dev/null +++ b/src/rp2_common/usb_device_msc/include/pico/scsi.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _SCSI_H +#define _SCSI_H + +#include +#include +#include + +#define CBW_SIG 0x43425355 +struct __aligned(4) __packed scsi_cbw { + uint32_t sig; + uint32_t tag; + uint32_t data_transfer_length; + uint8_t flags; + uint8_t lun; + uint8_t cb_length; + uint8_t cb[16]; +}; + +#define CSW_SIG 0x53425355 +struct __packed scsi_csw { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +}; + +struct __packed scsi_capacity { + uint32_t lba; // last block addr + uint32_t block_len; // probably 512 +}; + +struct __packed scsi_read_cb { + uint8_t opcode; + uint8_t flags; + uint32_t lba; + uint8_t reserved; + uint16_t blocks; + uint8_t control; +}; + +enum csw_status { + CSW_STATUS_COMMAND_PASSED = 0x00, + CSW_STATUS_COMMAND_FAILED = 0x01, + CSW_STATUS_PHASE_ERROR = 0x02, +}; + +enum scsi_cmd { + INQUIRY = 0x12, + MODE_SELECT_6 = 0x15, + MODE_SELECT_10 = 0x55, + MODE_SENSE_6 = 0x1a, + MODE_SENSE_10 = 0x5a, + PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1e, + READ_6 = 0x08, + READ_10 = 0x28, + READ_12 = 0xa8, + READ_FORMAT_CAPACITIES = 0x23, + READ_CAPACITY_10 = 0x25, + REPORT_LUNS = 0xa0, + REQUEST_SENSE = 0x03, + SEND_DIAGNOSTIC = 0x1d, + START_STOP_UNIT = 0x1b, + SYNCHRONIZE_CACHE = 0x35, + TEST_UNIT_READY = 0x00, + VERIFY = 0x2f, + WRITE_6 = 0x0a, + WRITE_10 = 0x2a, + WRITE_12 = 0xaa, +}; + +enum scsi_sense_key { + SK_OK = 0x00, + SK_NOT_READY = 0x02, + SK_ILLEGAL_REQUEST = 0x05, + SK_UNIT_ATTENTION = 0x06, + SK_DATA_PROTECT = 0x07 +}; + +enum scsi_additional_sense_code { + ASC_NONE = 0x00, + ASC_INVALID_COMMAND_OPERATION_CODE = 0x20, + ASC_PERIPHERAL_DEVICE_WRITE_FAULT = 0x03, + ASC_ACCESS_DENIED = 0x20, + ASC_LBA_OUT_OF_RANGE = 0x21, + ASC_WRITE_PROTECTED = 0x27, + ASC_NOT_READY_TO_READY_CHANGE = 0x28, + ASC_MEDIUM_NOT_PRESENT = 0x3a, +}; + +enum scsi_additional_sense_code_qualifier { + ASCQ_NA = 0x00, +}; +#endif \ No newline at end of file diff --git a/src/rp2_common/usb_device_msc/include/pico/scsi_ir.h b/src/rp2_common/usb_device_msc/include/pico/scsi_ir.h new file mode 100644 index 0000000..ed63a85 --- /dev/null +++ b/src/rp2_common/usb_device_msc/include/pico/scsi_ir.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _SCSI_IR_H +#define _SCSI_IR_H + +// NOTE THIS IS IN A SEPARATE HEADER AS IT IS COMPRESSED WHEN USING COMPRESS_TEXT +typedef unsigned char uint8_t; + +struct scsi_inquiry_response { + uint8_t pdt; + uint8_t rmb; + uint8_t spc_version; + uint8_t rdf; + uint8_t additional_length; + uint8_t inquiry5; + uint8_t inquiry6; + uint8_t inquiry7; + char vendor[8]; + char product[16]; + char version[4]; +} __packed; + +#ifndef COMPRESS_TEXT +static const struct scsi_inquiry_response scsi_ir = { + .rmb = 0x80, + .spc_version = 2, + .rdf = 2, + .additional_length = sizeof(struct scsi_inquiry_response) - 4, + .vendor = "RPI ", + .product = "RP2 ", + .version = "1 ", +}; +#endif +#endif \ No newline at end of file diff --git a/src/rp2_common/usb_device_msc/include/pico/usb_device_msc.h b/src/rp2_common/usb_device_msc/include/pico/usb_device_msc.h new file mode 100644 index 0000000..17dd92e --- /dev/null +++ b/src/rp2_common/usb_device_msc/include/pico/usb_device_msc.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _USB_MSC_H +#define _USB_MSC_H + +#define SECTOR_SIZE 512u + +bool msc_setup_request_handler(struct usb_interface *interface, struct usb_setup_packet *setup); +void msc_on_configure(__unused struct usb_device *device, bool configured); +//struct usb_endpoint msc_in, msc_out; +extern struct usb_endpoint msc_endpoints[2]; + +// provided by the hosting code +uint32_t msc_get_serial_number32(); +void msc_eject(); + +#endif \ No newline at end of file diff --git a/src/rp2_common/usb_device_msc/include/pico/virtual_disk.h b/src/rp2_common/usb_device_msc/include/pico/virtual_disk.h new file mode 100644 index 0000000..cd24c5d --- /dev/null +++ b/src/rp2_common/usb_device_msc/include/pico/virtual_disk.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _VIRTUAL_DISK_H +#define _VIRTUAL_DISK_H + +#include "usb_device_msc.h" + +#define USE_INFO_UF2 + +void vd_init(); +void vd_reset(); + +// return true for async operation +bool vd_read_block(uint32_t token, uint32_t lba, uint8_t *buf, uint32_t buf_size); +bool vd_write_block(uint32_t token, uint32_t lba, uint8_t *buf, uint32_t buf_size); + +// give us ourselves 16M which should strictly be the minimum for FAT16 - Note Win10 doesn't like FAT12 - go figure! +// upped to 64M which allows us to download a 32M UF2 +#define CLUSTER_UP_SHIFT 0u +#define CLUSTER_UP_MUL (1u << CLUSTER_UP_SHIFT) +#define VOLUME_SIZE (CLUSTER_UP_MUL * 128u * 1024u * 1024u) + +#define SECTOR_COUNT (VOLUME_SIZE / SECTOR_SIZE) + +#ifndef GENERAL_SIZE_HACKS + +static inline uint32_t vd_sector_count() { + return SECTOR_COUNT; +} + +#else +// needs to be a compile time constant +#define vd_sector_count() SECTOR_COUNT +#endif + +void vd_async_complete(uint32_t token, uint32_t result); +#endif diff --git a/src/rp2_common/usb_device_msc/usb_device_msc.c b/src/rp2_common/usb_device_msc/usb_device_msc.c new file mode 100644 index 0000000..22d4448 --- /dev/null +++ b/src/rp2_common/usb_device_msc/usb_device_msc.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include "hardware/sync.h" +#include "pico/usb_device.h" +#include "pico/usb_device_msc.h" +#include "pico/scsi.h" +#include "pico/scsi_ir.h" +#include "pico/virtual_disk.h" +#include "pico/usb_stream_helper.h" + +static __attribute__((aligned(4))) uint8_t _sector_buf[SECTOR_SIZE]; + +struct __packed scsi_request_sense_response { + uint8_t code; + uint8_t _pad; + uint8_t key; + uint32_t _info; + uint8_t additonal_sense_len; + uint32_t _cmd_specific; + uint8_t asc; + uint8_t ascq; + uint8_t _fruc; + uint8_t _sense_specific[3]; +}; +static_assert(sizeof(struct scsi_request_sense_response) == 18, ""); + +enum scsi_direction { + SCSI_DIR_NONE = 0, + SCSI_DIR_IN = 1, + SCSI_DIR_OUT = 2, +}; + +static struct msc_state { + struct scsi_csw csw; + struct scsi_request_sense_response request_sense; + uint32_t data_phase_length; + uint8_t stall_direction_before_csw; + bool send_csw_on_unstall; + bool ejected; +} _msc_state; + +// not part of _msc_state since we never reset it +static uint32_t _msc_async_token; + +void _msc_cmd_packet(struct usb_endpoint *ep); + +static const struct usb_transfer_type _msc_cmd_transfer_type = { + .on_packet = _msc_cmd_packet, + .initial_packet_count = 1, +}; + +// note we need these to be adjacent, so rather than relying on the fact just making them into an array which seems to produce the same code otherwise +//struct usb_endpoint msc_in, msc_out; + +struct usb_endpoint msc_endpoints[2]; +#define msc_in msc_endpoints[0] +#define msc_out msc_endpoints[1] + +static struct usb_transfer _msc_cmd_transfer; +static struct usb_transfer _msc_cmd_response_transfer; + +static void _tf_wait_command(__unused struct usb_endpoint *ep, __unused struct usb_transfer *transfer) { + assert(ep == &msc_in); + usb_debug("_tf_wait_command\n"); + assert(msc_out.default_transfer); +#ifndef GENERAL_SIZE_HACKS + usb_start_default_transfer_if_not_already_running_or_halted(&msc_out); +#else + assert(&msc_out == &msc_in + 1); + usb_start_default_transfer_if_not_already_running_or_halted(ep + 1); +#endif +} + +static __noinline void _msc_reset_and_start_cmd_response_transfer(usb_transfer_completed_func func) { + usb_reset_and_start_transfer(&msc_in, &_msc_cmd_response_transfer, &usb_current_packet_only_transfer_type, func); +} + +static void _msc_send_csw() { + _msc_state.send_csw_on_unstall = false; + uint8_t *buffer = usb_get_single_packet_response_buffer(&msc_in, sizeof(_msc_state.csw)); + memcpy(buffer, &_msc_state.csw, sizeof(_msc_state.csw)); + _msc_reset_and_start_cmd_response_transfer(_tf_wait_command); +} + +static void _msc_set_csw_failed(enum scsi_sense_key sk, enum scsi_additional_sense_code asc, + enum scsi_additional_sense_code_qualifier ascq) { + _msc_state.csw.status = CSW_STATUS_COMMAND_FAILED; + _msc_state.request_sense.key = sk; + _msc_state.request_sense.asc = asc; + _msc_state.request_sense.ascq = ascq; +} + +static void _msc_data_phase_complete() { + if (_msc_state.stall_direction_before_csw == SCSI_DIR_IN) { + _msc_state.stall_direction_before_csw = SCSI_DIR_NONE; + _msc_state.send_csw_on_unstall = true; + usb_debug("Stalling in\n"); + usb_halt_endpoint(&msc_in); + } else { + if (_msc_state.stall_direction_before_csw == SCSI_DIR_OUT) { + _msc_state.stall_direction_before_csw = SCSI_DIR_NONE; + usb_debug("Stalling out\n"); + usb_halt_endpoint(&msc_out); + } + _msc_send_csw(); + } +} + +#ifndef GENERAL_SIZE_HACKS + +static void _tf_data_phase_complete(__unused struct usb_endpoint *endpoint, __unused struct usb_transfer *transfer) { + assert(endpoint == &msc_in || endpoint == &msc_out); + usb_debug("_tf_data_phase_complete\n"); + _msc_data_phase_complete(); +} + +#else +#define _tf_data_phase_complete ((usb_transfer_completed_func)_msc_data_phase_complete) +#endif + +// noinline here saves us 4 bytes; go figure +static enum scsi_direction _scsi_dir(const struct scsi_cbw *cbw) { + return (cbw->flags & USB_DIR_IN) ? SCSI_DIR_IN : SCSI_DIR_OUT; +} + +static void _msc_init_for_dn(const struct scsi_cbw *cbw) { + _msc_state.stall_direction_before_csw = SCSI_DIR_NONE; + if (cbw->data_transfer_length) { + enum scsi_direction cbw_dir = _scsi_dir(cbw); + _msc_state.stall_direction_before_csw = cbw_dir; + } + _msc_data_phase_complete(); +} + +static bool _msc_init_for_di_or_do(const struct scsi_cbw *cbw, uint32_t expected_length, enum scsi_direction dir) { + _msc_state.stall_direction_before_csw = SCSI_DIR_NONE; + _msc_state.data_phase_length = 0; + enum scsi_direction cbw_dir = _scsi_dir(cbw); + if (cbw_dir != dir) { + usb_debug("Will stall because direction wrong\n"); + _msc_state.stall_direction_before_csw = cbw_dir; + _msc_state.csw.status = CSW_STATUS_PHASE_ERROR; + } else { + if (expected_length != cbw->data_transfer_length) { + _msc_state.stall_direction_before_csw = dir; + } + if (expected_length > cbw->data_transfer_length) { + _msc_state.csw.status = CSW_STATUS_PHASE_ERROR; + } + _msc_state.data_phase_length = MIN(expected_length, cbw->data_transfer_length); + } + usb_debug("_msc_init_for_di exp = %d tran = %d stall = %d status = %d length = %d\n", (uint) expected_length, + (uint) cbw->data_transfer_length, + _msc_state.stall_direction_before_csw, _msc_state.csw.status, (uint) _msc_state.data_phase_length); + if (!_msc_state.data_phase_length) { + _msc_data_phase_complete(); + return false; + } + return true; +} + +static void _scsi_fail_cmd(const struct scsi_cbw *cbw, enum scsi_sense_key sk, enum scsi_additional_sense_code asc, + enum scsi_additional_sense_code_qualifier ascq) { + _msc_set_csw_failed(sk, asc, ascq); + // this handily takes care of the STALLing/CSW based on us not intending to send data + _msc_init_for_dn(cbw); +} + +static void _scsi_standard_response(const struct scsi_cbw *cbw) { + struct usb_buffer *buffer = usb_current_in_packet_buffer(&msc_in); + assert(buffer->data_len); + if (_msc_init_for_di_or_do(cbw, MIN(buffer->data_len, cbw->data_transfer_length), SCSI_DIR_IN)) { + assert(_msc_state.data_phase_length <= buffer->data_len); + buffer->data_len = _msc_state.data_phase_length; // truncate buffer + _msc_state.csw.residue -= buffer->data_len; + _msc_reset_and_start_cmd_response_transfer(_tf_data_phase_complete); + } +} + +static_assert(sizeof(struct scsi_inquiry_response) == 36, ""); + +static void _scsi_handle_inquiry_response(struct scsi_cbw *cbw) { + uint8_t *buf = usb_get_single_packet_response_buffer(&msc_in, sizeof(struct scsi_inquiry_response)); + memcpy(buf, &scsi_ir, sizeof(scsi_ir)); + _scsi_standard_response(cbw); +} + +static struct msc_sector_transfer { + struct usb_stream_transfer stream; + uint32_t lba; +} _msc_sector_transfer; + +static void _msc_on_sector_stream_packet_complete(__unused struct usb_stream_transfer *transfer) { + assert(transfer == &_msc_sector_transfer.stream); + _msc_state.csw.residue -= 64; +} + +bool _msc_on_sector_stream_chunk(__unused uint32_t chunk_len, __unused struct usb_stream_transfer *transfer) { + assert(transfer == &_msc_sector_transfer.stream); + assert(chunk_len == SECTOR_SIZE); + bool (*vd_read_or_write)(uint32_t token, uint32_t lba, uint8_t *buf, uint32_t buf_size); + vd_read_or_write = _msc_sector_transfer.stream.ep->in ? vd_read_block : vd_write_block; + return vd_read_or_write(++_msc_async_token, _msc_sector_transfer.lba++, _sector_buf, SECTOR_SIZE); +} + +static const struct usb_stream_transfer_funcs _msc_sector_funcs = { + .on_packet_complete = _msc_on_sector_stream_packet_complete, + .on_chunk = _msc_on_sector_stream_chunk +}; + +// note that this may be called during regular vd_operation +void vd_async_complete(uint32_t token, uint32_t result) { + usb_debug("complete token %d\n", (int) token); + // note that this USB library is not thread safe, however this is the only function called + // from non IRQ handler code after usb_device_start; therefore we just disable IRQs for this call + uint32_t save = save_and_disable_interrupts(); + if (token == _msc_async_token) { + if (result) { + // if we error, we'll just abort and send csw + // todo does it matter what we send? - we have a residue - prefer to send locked or write error +#ifndef USB_SILENT_FAIL_ON_EXCLUSIVE + _msc_set_csw_failed(SK_DATA_PROTECT, ASC_ACCESS_DENIED, 2); // no access rights +#endif + _msc_state.stall_direction_before_csw = SCSI_DIR_OUT; + _msc_data_phase_complete(); + } + usb_stream_chunk_done(&_msc_sector_transfer.stream); + } else { + usb_warn("async complete for incorrect token %d != %d\n", (int) token, (int) _msc_async_token); + } + restore_interrupts(save); +} + +static void _scsi_read_or_write_blocks(const struct scsi_cbw *cbw, uint32_t lba, uint32_t blocks, + enum scsi_direction dir) { + assert(dir); + _msc_sector_transfer.stream.ep = (dir == SCSI_DIR_IN) ? &msc_in : &msc_out; + _msc_sector_transfer.lba = lba; + uint32_t expected_length = blocks * SECTOR_SIZE; + if (_msc_init_for_di_or_do(cbw, expected_length, dir)) { + assert(_msc_state.data_phase_length <= expected_length); + expected_length = _msc_state.data_phase_length / + 64; // round down... this means we may send less than dwTransferLength, but residue will be correct + // todo we could remove the if if start_transfer allows empty transfers + if (expected_length) { + _msc_async_token++; + // transfer length is exact multiple of 64 as per above rounding comment + usb_stream_setup_transfer(&_msc_sector_transfer.stream, &_msc_sector_funcs, _sector_buf, SECTOR_SIZE, + expected_length * 64, + _tf_data_phase_complete); + if (dir == SCSI_DIR_IN) { + usb_start_transfer(&msc_in, &_msc_sector_transfer.stream.core); + } else { + usb_chain_transfer(&msc_out, &_msc_sector_transfer.stream.core); + } + } else { + _msc_data_phase_complete(); + } + } +} + +static void _scsi_handle_test_unit_ready(const struct scsi_cbw *cbw) { + if (_msc_state.ejected) { + return _scsi_fail_cmd(cbw, SK_NOT_READY, ASC_MEDIUM_NOT_PRESENT, ASCQ_NA); + } + return _msc_init_for_dn(cbw); +} + +void msc_eject() { + _msc_state.ejected = true; +} + +static void _scsi_handle_start_stop_unit(const struct scsi_cbw *cbw) { + if (2u == (cbw->cb[4] & 3u)) { + usb_warn("EJECT immed %02x\n", cbw->cb[1]); + msc_eject(); + } + return _msc_init_for_dn(cbw); +} + +static void _scsi_handle_read_or_write_command(const struct scsi_cbw *cbw, enum scsi_direction dir) { + const struct scsi_read_cb *cb = (const struct scsi_read_cb *) &cbw->cb[0]; + uint32_t lba = __builtin_bswap32(cb->lba); + uint16_t blocks = __builtin_bswap16(cb->blocks); + usb_debug(dir == SCSI_DIR_IN ? "Read %d blocks starting at lba %ld\n" : + "Write %d blocks starting at lba %ld\n", + blocks, lba); + _scsi_read_or_write_blocks(cbw, lba, blocks, dir); +} + +static void _scsi_memcpy_response(const struct scsi_cbw *cbw, uint8_t *data, uint len) { + memcpy(usb_get_single_packet_response_buffer(&msc_in, len), data, len); + _scsi_standard_response(cbw); +} + +static void _scsi_handle_read_capacity(const struct scsi_cbw *cbw) { + struct scsi_capacity _resp = { + .lba = __builtin_bswap32(vd_sector_count() - 1), + .block_len = __builtin_bswap32(SECTOR_SIZE) + }; + _scsi_memcpy_response(cbw, (uint8_t *) &_resp, sizeof(_resp)); +} + +struct __packed scsi_read_format_capacity_response { + uint8_t _pad[3]; + uint8_t descriptors_size; + uint32_t descriptor_1_block_count_msb; + uint32_t descriptor_1_type_and_block_size; +}; + +static void _scsi_handle_read_format_capacities(const struct scsi_cbw *cbw) { + struct scsi_read_format_capacity_response _resp = { + .descriptor_1_block_count_msb = __builtin_bswap32(vd_sector_count() - 1), + .descriptor_1_type_and_block_size = 2u | // formatted + __builtin_bswap32(SECTOR_SIZE) + }; + _scsi_memcpy_response(cbw, (uint8_t *) &_resp, sizeof(_resp)); +} + +static void _scsi_handle_request_sense(const struct scsi_cbw *cbw) { + uint8_t *buf = usb_get_single_packet_response_buffer(&msc_in, sizeof(_msc_state.request_sense)); +// printf("RS %d\n", scsi.request_sense.key); + memcpy(buf, &_msc_state.request_sense, sizeof(_msc_state.request_sense)); + _msc_state.request_sense.key = SK_OK; + _msc_state.request_sense.asc = 0; + _msc_state.request_sense.ascq = 0; + _scsi_standard_response(cbw); +} + +static void _scsi_handle_mode_sense(const struct scsi_cbw *cbw) { + uint8_t *buf = usb_get_single_packet_response_buffer(&msc_in, 4); + *(uint32_t *) buf = 3; + _scsi_standard_response(cbw); +} + +static void _msc_in_on_stall_change(struct usb_endpoint *ep) { + usb_debug("Stall change in stalled %d send csw %d \n", usb_is_endpoint_stalled(ep), _msc_state.send_csw_on_unstall); + if (!usb_is_endpoint_stalled(ep) && ep == &msc_in) { + // todo we need to clear this on the ep cancel + if (_msc_state.send_csw_on_unstall) { + usb_debug("Sending CSW on unstall\n"); + _msc_send_csw(); + } + } +} + +static void _msc_reset(void) { + static bool one_time; + if (!one_time) { + _msc_cmd_transfer.type = &_msc_cmd_transfer_type; + usb_set_default_transfer(&msc_out, &_msc_cmd_transfer); + msc_in.on_stall_change = _msc_in_on_stall_change; + vd_init(); + one_time = true; + } + memset(&_msc_state, 0, sizeof(_msc_state)); + _msc_state.request_sense.code = 0x70; + _msc_state.request_sense.additonal_sense_len = 0xa; + vd_reset(); + usb_soft_reset_endpoint(&msc_in); + usb_soft_reset_endpoint(&msc_out); +} + +static void _msc_cmd_halt() { + usb_halt_endpoint_on_condition(&msc_in); + usb_halt_endpoint_on_condition(&msc_out); +} + +static void _msc_cmd_packet_internal(struct usb_endpoint *ep) { + struct usb_buffer *buffer = usb_current_out_packet_buffer(ep); + uint len = buffer->data_len; + + struct scsi_cbw *cbw = (struct scsi_cbw *) buffer->data; + if (len == 31u && cbw->sig == CBW_SIG && !cbw->lun && !(cbw->flags & 0x7fu) && cbw->cb_length && + cbw->cb_length <= 16) { + // todo we need to validate CBW sizes + _msc_state.csw.sig = CSW_SIG; + _msc_state.csw.tag = cbw->tag; + _msc_state.csw.residue = cbw->data_transfer_length; + usb_debug("SCSI: "); + enum scsi_cmd cmd = cbw->cb[0]; + if (cmd != REQUEST_SENSE) { + _msc_state.request_sense.key = SK_OK; + _msc_state.request_sense.asc = 0; + _msc_state.request_sense.ascq = 0; + } + _msc_state.csw.status = CSW_STATUS_COMMAND_PASSED; + switch (cmd) { + case INQUIRY: + usb_debug("INQUIRY\n"); + return _scsi_handle_inquiry_response(cbw); + case MODE_SENSE_6: + usb_debug("MODESENSE(6)\n"); + return _scsi_handle_mode_sense(cbw); + case PREVENT_ALLOW_MEDIUM_REMOVAL: + usb_debug("PREVENT ALLOW MEDIUM REMOVAL\n");// %d\n", buf[4] & 3u); + // Nothing to do just reply success + return _msc_init_for_dn(cbw); + case READ_10: + usb_debug("READ(10)\n"); + return _scsi_handle_read_or_write_command(cbw, SCSI_DIR_IN); + case WRITE_10: + usb_debug("WRITE(10)\n"); + return _scsi_handle_read_or_write_command(cbw, SCSI_DIR_OUT); + case READ_FORMAT_CAPACITIES: + usb_debug("READ FORMAT_CAPACITIES\n"); + return _scsi_handle_read_format_capacities(cbw); + case READ_CAPACITY_10: + usb_debug("READ CAPACITY(10)\n"); + return _scsi_handle_read_capacity(cbw); + case REQUEST_SENSE: + usb_debug("REQUEST SENSE\n"); + return _scsi_handle_request_sense(cbw); + case TEST_UNIT_READY: + usb_debug("TEST UNIT READY\n"); + return _scsi_handle_test_unit_ready(cbw); + case START_STOP_UNIT: + usb_debug("START STOP UNIT\n"); + return _scsi_handle_start_stop_unit(cbw); + case SYNCHRONIZE_CACHE: + usb_debug("SYNCHRONIZE CACHE(10)\n"); + return _msc_init_for_dn(cbw); + case VERIFY: + usb_debug("VERIFY\n"); + return _msc_init_for_dn(cbw); + default: + usb_debug("cmd %02x\n", cbw->cb[0]); + break; + } + return _scsi_fail_cmd(cbw, SK_ILLEGAL_REQUEST, ASC_INVALID_COMMAND_OPERATION_CODE, ASCQ_NA); + } else { + usb_debug("invalid cbw\n"); + return _msc_cmd_halt(); + } +} + +void _msc_cmd_packet(struct usb_endpoint *ep) { + _msc_cmd_packet_internal(ep); + usb_packet_done(ep); +} + +bool msc_setup_request_handler(__unused struct usb_interface *interface, struct usb_setup_packet *setup) { + setup = __builtin_assume_aligned(setup, 4); + if (USB_REQ_TYPE_TYPE_CLASS == (setup->bmRequestType & USB_REQ_TYPE_TYPE_MASK)) { + if (setup->bmRequestType & USB_DIR_IN) { + if (setup->bRequest == USB_REQUEST_MSC_GET_MAX_LUN) { + if (!setup->wValue && setup->wLength) { + usb_debug("GET_MAX_LUN\n"); + struct usb_buffer *buffer = usb_current_in_packet_buffer(usb_get_control_in_endpoint()); + buffer->data[0] = 0; + buffer->data_len = 1; + usb_start_single_buffer_control_in_transfer(); + return true; + } else { + usb_debug("INVALID GET_MAX_LUN\n"); + } + } + } else { + if (setup->bRequest == USB_REQUEST_MSC_RESET) { + if (!setup->wValue && !setup->wLength) { + usb_debug("MSC_RESET\n"); + // doesn't unstall, but allows CLEAR_HALT to proceed + usb_clear_halt_condition(&msc_in); + usb_clear_halt_condition(&msc_out); + _msc_reset(); + usb_start_empty_control_in_transfer_null_completion(); + return true; + } else { + usb_debug("INVALID MSC_RESET\n"); + } + } + } + } + return false; +} + + +void msc_on_configure(__unused struct usb_device *device, bool configured) { + if (configured) { + _msc_reset(); + } +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..a8659fa --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(sample_conversion_test) +add_subdirectory(sd_test) diff --git a/test/sample_conversion_test/CMakeLists.txt b/test/sample_conversion_test/CMakeLists.txt new file mode 100644 index 0000000..44f8d38 --- /dev/null +++ b/test/sample_conversion_test/CMakeLists.txt @@ -0,0 +1,9 @@ +if (NOT PICO_ON_DEVICE OR NOT PICO_NO_FLASH) # too big for RAM + add_executable(sample_conversion_test sample_conversion_test.cpp) + + target_compile_definitions(sample_conversion_test PRIVATE + #PICO_ENTER_USB_BOOT_ON_EXIT=1 + ) + target_link_libraries(sample_conversion_test PRIVATE pico_stdlib pico_audio) + pico_add_extra_outputs(sample_conversion_test) +endif() diff --git a/test/sample_conversion_test/sample_conversion_test.cpp b/test/sample_conversion_test/sample_conversion_test.cpp new file mode 100644 index 0000000..461895a --- /dev/null +++ b/test/sample_conversion_test/sample_conversion_test.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include "pico/stdlib.h" +#include "pico/bit_ops.h" +#include "pico/sample_conversion.h" + +typedef int (*sample_converter_fn)(int); + +int u16_to_u16(int s) { return (uint16_t) s; } + +int s16_to_u16(int s) { return (uint16_t) (s ^ 0x8000u); } + +int u8_to_u16(int s) { return (uint16_t) (s << 8); } + +int s8_to_u16(int s) { return (uint16_t) ((s << 8u) ^ 0x8000u); } + +int u16_to_s16(int s) { return (int16_t) (s ^ 0x8000u); } + +int s16_to_s16(int s) { return (int16_t) s; } + +int u8_to_s16(int s) { return (int16_t) ((s << 8u) ^ 0x8000u); } + +int s8_to_s16(int s) { return (int16_t) (s << 8u); } + +int u16_to_u8(int s) { return (uint8_t) (s >> 8u); } + +int s16_to_u8(int s) { return (uint8_t) ((s ^ 0x8000u) >> 8u); } + +int u8_to_u8(int s) { return (uint8_t) s; } + +int s8_to_u8(int s) { return (uint8_t) (s ^ 0x80); } + +int u16_to_s8(int s) { return (int8_t) ((s ^ 0x8000u) >> 8u); } + +int s16_to_s8(int s) { return (int8_t) (s >> 8u); } + +int u8_to_s8(int s) { return (int8_t) (s ^ 0x80); } + +int s8_to_s8(int s) { return (int8_t) s; } + +template +typename Fmt::sample_t random_sample() { + return (typename Fmt::sample_t) rand(); +} + +void check_sample(int from, int expected, int actual) { + if (expected != actual) { + printf("Failed converting %04x to %04x (got %04x)\n", from, expected, actual); + assert(false); + } +} + +template +void check_conversion(sample_converter_fn converter_fn) { + uint length = 256 + rand() & 0xffu; + typename ToFmt::sample_t to_buffer[length * ToFmt::channel_count]; + typename FromFmt::sample_t from_buffer[length * FromFmt::channel_count]; + for (uint i = 0; i < length * FromFmt::channel_count; i++) { + from_buffer[i] = random_sample(); + } + converting_copy::copy(to_buffer, from_buffer, length); + if (ToFmt::channel_count == FromFmt::channel_count) { + for (uint i = 0; i < length * ToFmt::channel_count; i++) { + check_sample(from_buffer[i], converter_fn(from_buffer[i]), to_buffer[i]); + } + } else if (ToFmt::channel_count == 2 & FromFmt::channel_count == 1) { + // mono -> stereo duplicates + for (uint i = 0; i < length; i++) { + check_sample(from_buffer[i], converter_fn(from_buffer[i]), to_buffer[i * 2]); + check_sample(from_buffer[i], converter_fn(from_buffer[i]), to_buffer[i * 2 + 1]); + } + } else if (ToFmt::channel_count == 1 & FromFmt::channel_count == 2) { + // stereo -> mono averages + for (uint i = 0; i < length; i++) { + // can't represent both samples + check_sample(0xf00d, converter_fn((from_buffer[i * 2] + from_buffer[i * 2 + 1]) / 2), to_buffer[i]); + } + } else { + assert(false); + } +} + +template +void check_conversions(sample_converter_fn converter_fn) { + // for a given format check conversions to and from + check_conversion, Mono>(converter_fn); + check_conversion, Mono>(converter_fn); + check_conversion, Stereo>(converter_fn); + check_conversion, Stereo>(converter_fn); +} + +int main() { + // On FPGA, pins 28 and 29 are connected to the VC707 board USB-UART + uart_init(uart0, 115200); + gpio_set_function(28, GPIO_FUNC_UART); + gpio_set_function(29, GPIO_FUNC_UART); + + // check all permutations of supported formats + + check_conversions(u16_to_u16); + check_conversions(u16_to_s16); + check_conversions(u16_to_u8); + check_conversions(u16_to_s8); + + check_conversions(s16_to_u16); + check_conversions(s16_to_s16); + check_conversions(s16_to_u8); + check_conversions(s16_to_s8); + + check_conversions(u8_to_u16); + check_conversions(u8_to_s16); + check_conversions(u8_to_u8); + check_conversions(u8_to_s8); + + check_conversions(s8_to_u16); + check_conversions(s8_to_s16); + check_conversions(s8_to_u8); + check_conversions(s8_to_s8); + + printf("OK\n"); +} + diff --git a/test/sd_test/CMakeLists.txt b/test/sd_test/CMakeLists.txt new file mode 100644 index 0000000..d4349aa --- /dev/null +++ b/test/sd_test/CMakeLists.txt @@ -0,0 +1,15 @@ +if (PICO_ON_DEVICE) + if (TARGET pico_sd_card) + add_executable(sd_test + sd_test.c + ) + +# target_compile_definitions(sd_test PRIVATE +# PICO_DEFAULT_UART_TX_PIN=28 +# PICO_DEFAULT_UART_RX_PIN=29 +# PICO_DEFAULT_UART=0 +# ) + target_link_libraries(sd_test pico_stdlib pico_sd_card) + pico_add_extra_outputs(sd_test) + endif() +endif() \ No newline at end of file diff --git a/test/sd_test/sd_test.c b/test/sd_test/sd_test.c new file mode 100644 index 0000000..a971df9 --- /dev/null +++ b/test/sd_test/sd_test.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include + +#include "pico/stdlib.h" +#include "pico/sd_card.h" + +static uint8_t sector_data[1024]; + +#define ENABLE_4_PIN 0 + +int main(void) { + set_sys_clock_48mhz(); + stdio_init_all(); + + printf("SD Card test\n"); + + int i = 0; + + // 0-2 for SD_CLK, SD_CMD, SD_DAT + + int sd_pin_base = 25; + +// gpio_init(0); +// gpio_init(5); +// gpio_init(10); +// gpio_set_dir_out_masked(0x421); + +#if ENABLE_4_PIN + if (sd_init_4pin() < 0) +#else + if (sd_init_1pin() < 0) +#endif + { + panic("doh"); + } + + static int block_base = 0; + #define BLOCK_COUNT 2 + +#define STREAMING + +#ifdef STREAMING + static uint32_t b[BLOCK_COUNT * 128]; + for(int div = 4; div >= 1; div--) + { + uint8_t *buf = (uint8_t *)b; + printf("-----------------------\n"); + printf("SPEED %uMB/s\n", 12/div); + sd_set_clock_divider(div); + printf("1 bit no crc\n"); + sd_set_wide_bus(false); + memset(buf, 0xaa, 512); + sd_readblocks_sync(b, block_base, BLOCK_COUNT); + for(int byte = 0; byte < 512; byte += 16) + { + printf("%08x ", i * 512 + byte); + for(int j = 0; j < 16; j++) printf("%02x ", buf[byte + j]); + for(int j = 0; j < 16; j++) putchar(isprint(buf[byte + j]) ? buf[byte + j] : '.'); + printf("\n"); + } +#if ENABLE_4_PIN + memset(buf, 0xaa, 512); + printf("4 bit no crc\n"); + sd_set_wide_bus(true); + sd_readblocks_sync(b, block_base, BLOCK_COUNT); + for(int byte = 0; byte < 512; byte += 16) + { + printf("%08x ", i * 512 + byte); + for(int j = 0; j < 16; j++) printf("%02x ", buf[byte + j]); + for(int j = 0; j < 16; j++) putchar(isprint(buf[byte + j]) ? buf[byte + j] : '.'); + printf("\n"); + } +#endif + memset(buf, 0xaa, 512); + printf("1 bit crc\n"); + sd_read_sectors_1bit_crc_async(b, block_base, BLOCK_COUNT); + int status = 0; + while (!sd_scatter_read_complete(&status)); + printf("Status: %d\n", status); +#endif + for(i = 0; i < BLOCK_COUNT; i++) + { +#ifndef STREAMING + uint8_t *buf = sd_readblock(i); +#endif + //if (i == BLOCK_COUNT-1) + for(int byte = 0; byte < 512; byte += 16) + { + printf("%08x ", i * 512 + byte); + for(int j = 0; j < 16; j++) printf("%02x ", buf[byte + j]); + for(int j = 0; j < 16; j++) putchar(isprint(buf[byte + j]) ? buf[byte + j] : '.'); + printf("\n"); + } + printf("\n"); +#ifdef STREAMING + buf += 512; +#endif + } + } + +#if 0 + strcpy(sector_data, "fish And Hello there zif squiffy!"); + sector_data[511] = 0xaa; + sd_writeblocks_async((uint32_t*)sector_data, 0, 1); + static int timeout = 10; + int rc; + while (!sd_write_complete(&rc)) { + printf("Waiting for completion\n"); + if (!--timeout) break; + } + printf("Done %d!\n", rc); + strcpy(sector_data, "vasil fleplic yoeville frentucky arrivant sklim avary ron giblet And Hello there zif squiffy!"); + sector_data[511] = 0x55; + strcpy(sector_data + 512, "and this is sector 2 folks"); + sd_writeblocks_async((uint32_t*)sector_data, 0, 2); + timeout = 10; + while (!sd_write_complete(&rc)) { + printf("Waiting for completion\n"); + if (!--timeout) break; + } +#endif + printf("Done!\n"); + __breakpoint(); +}