diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..2018d0f --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,62 @@ +name: Build pico-mac + +on: + push: + pull_request: + release: + types: [published] + check_suite: + types: [rerequested] + + +jobs: + bins: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Install ARM GCC + uses: carlosperate/arm-none-eabi-gcc-action@v1 + with: + release: '13.2.Rel1' + + - name: install sdl + run: sudo apt-get update && sudo apt-get install -y eatmydata && sudo eatmydata apt-get install -y libsdl2-dev + + - name: get submodules + run: git submodule update --init --recursive + + - name: get pico-sdk + run: git clone --depth=1 -b adafruit-fruit-jam https://github.com/adafruit/pico-sdk ../pico-sdk + + - name: build targets + run: | + ./fetch-rom-dsk.sh + ./fruitjam-build.sh -m 4096 + ./fruitjam-build.sh -m 4096 -v + ./fruitjam-build.sh -m 400 + ./fruitjam-build.sh -m 400 -v + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: uf2 files + path: build*/*.uf2 + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: elf files + path: build*/*.elf + + - name: Create release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v1 + with: + files: build*/*.uf2 + fail_on_unmatched_files: true + body: "Select a uf2 from the list below." + diff --git a/.gitmodules b/.gitmodules index 0e239de..c6ca2f5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,17 @@ [submodule "external/umac"] - url = https://github.com/evansm7/umac + url = https://github.com/adafruit/umac path = external/umac + branch = soundemu [submodule "external/no-OS-FatFS-SD-SPI-RPi-Pico"] path = external/no-OS-FatFS-SD-SPI-RPi-Pico url = https://github.com/evansm7/no-OS-FatFS-SD-SPI-RPi-Pico.git +[submodule "lib/tinyusb"] + path = lib/tinyusb + url = https://github.com/hathach/tinyusb +[submodule "lib/Pico-PIO-USB"] + path = lib/Pico-PIO-USB + url = https://github.com/tannewt/Pico-PIO-USB +[submodule "external/pico-extras"] + path = external/pico-extras + url = https://github.com/adafruit/pico-extras.git + branch = i2s-audio-rp2350b-high-pins diff --git a/CMakeLists.txt b/CMakeLists.txt index 65be3ad..87af654 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,16 @@ cmake_minimum_required(VERSION 3.13) # Options that should be defined when initialising the build # directory with cmake, e.g. "cmake .. -DOPTION=true": # +# Note: to build for pico2 / rp2350: cmake .. -DPICO_BOARD=pico2 +# Note: to build for fruit jam: +# cmake -DBOARD=adafruit_fruit_jam -DPICO_BOARD=pico2 -DUSE_HSTX=1 -S . -B build_hstx -DPICO_SDK_PATH=../pico-sdk -DSD_TX=35 -DSD_RX=36 -DSD_SCK=34 -DSD_CS=39 + +set(PICO_TINYUSB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/tinyusb) +set(PIOUSB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib/Pico-PIO-USB) + +set(UART_TX CACHE STRING "") +set(UART_RX CACHE STRING "") +set(UART CACHE STRING "") option(USE_SD "Build in SD support" OFF) set(SD_TX 3 CACHE STRING "SD SPI TX pin") @@ -35,17 +45,70 @@ set(SD_RX 4 CACHE STRING "SD SPI RX pin") set(SD_SCK 2 CACHE STRING "SD SPI SCK pin") set(SD_CS 5 CACHE STRING "SD SPI CS pin") set(SD_MHZ 5 CACHE STRING "SD SPI speed in MHz") + +option(USE_HSTX "Use HSTX digital video (only for rp2350 / pico2)" OFF) + +# Options for HSTX output (defaults are for Adafruit FruitJam) +# HSTX always uses 640x480 for now +set(HSTX_CKP 13 CACHE STRING "HSTX CK+ PIN") +set(HSTX_D0P 15 CACHE STRING "HSTX D0+ PIN") +set(HSTX_D1P 17 CACHE STRING "HSTX D1+ PIN") +set(HSTX_D2P 19 CACHE STRING "HSTX D2+ PIN") + +# Options for analog VGA output option(USE_VGA_RES "Video uses VGA (640x480) resolution" OFF) -set(VIDEO_PIN 18 CACHE STRING "Video GPIO base pin (followed by VS, CLK, HS)") +set(VIDEO_PIN 18 CACHE STRING "VGA Video GPIO base pin (followed by VS, CLK, HS)") + +option(USE_PSRAM "Locate main Mac ram in PSRAM (only for rp2350 / pico 2)" OFF) +set(PSRAM_CS 47 CACHE STRING "PSRAM Chip select pin") + +# Pins for PIO-based USB host +set(PIN_USB_HOST_DP 1 CACHE STRING "USB D+ PIN") +set(PIN_USB_HOST_DM 2 CACHE STRING "USB D- PIN") + +set(USE_AUDIO 1 CACHE STRING "Use audio") +set(PIN_AUDIO_PWM 41 CACHE STRING "Pin for PWM audio") # See below, -DMEMSIZE= will configure umac's memory size, # overriding defaults. +set(MEMSIZE 128 CACHE STRING "Memory size, in KB") + +if (USE_HSTX) + add_compile_definitions(USE_VGA_RES=1) + add_compile_definitions(HSTX_CKP=${HSTX_CKP} HSTX_D0P=${HSTX_D0P} HSTX_D1P=${HSTX_D1P} HSTX_D2P=${HSTX_D2P}) + set(VIDEO_SRC src/video_hstx.c) +else() + add_compile_definitions(GPIO_VID_BASE=${VIDEO_PIN}) + set(VIDEO_SRC src/video_vga.c) +endif() + +if (USE_VGA_RES) + add_compile_definitions(USE_VGA_RES=1) + add_compile_definitions(DISP_WIDTH=640) + add_compile_definitions(DISP_HEIGHT=480) + set(RES "640x480") +else() + add_compile_definitions(DISP_WIDTH=512) + add_compile_definitions(DISP_HEIGHT=342) + set(RES "512x342") +endif() + +if (USE_PSRAM) + add_compile_definitions(PIN_PSRAM_CS=${PSRAM_CS} USE_PSRAM=1) + set(OPT_PSRAM "-psram") +else() + add_compile_definitions(USE_PSRAM=0) + set(OPT_PSRAM "") +endif() + +set(FIRMWARE "pico-mac-${PICO_BOARD}-${MEMSIZE}k-${RES}${OPT_PSRAM}") + # initialize the SDK based on PICO_SDK_PATH # note: this must happen before project() include(pico_sdk_import.cmake) -project(firmware) +project(${FIRMWARE}) # initialize the Raspberry Pi Pico SDK pico_sdk_init() @@ -54,8 +117,6 @@ pico_sdk_init() set(FAMILY rp2040) set(BOARD raspberry_pi_pico) -set(TINYUSB_PATH ${PICO_SDK_PATH}/lib/tinyusb) - # umac subproject (and Musashi sub-subproject) set(UMAC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/umac) set(UMAC_MUSASHI_PATH ${UMAC_PATH}/external/Musashi) @@ -74,8 +135,8 @@ set(UMAC_SOURCES ${UMAC_MUSASHI_PATH}/softfloat/softfloat.c ) -set(MEMSIZE 128 CACHE STRING "Memory size, in KB") -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\" -DUMAC_MEMSIZE=${MEMSIZE}") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb -g3 -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\" -DUMAC_MEMSIZE=${MEMSIZE}") + if (USE_SD) add_compile_definitions(USE_SD=1) @@ -86,24 +147,42 @@ if (USE_SD) add_compile_definitions(SD_TX=${SD_TX} SD_RX=${SD_RX} SD_SCK=${SD_SCK} SD_CS=${SD_CS} SD_MHZ=${SD_MHZ}) endif() -if (USE_VGA_RES) - add_compile_definitions(USE_VGA_RES=1) - add_compile_definitions(DISP_WIDTH=640) - add_compile_definitions(DISP_HEIGHT=480) -else() - add_compile_definitions(DISP_WIDTH=512) - add_compile_definitions(DISP_HEIGHT=342) +add_compile_definitions(PIN_USB_HOST_DP=${PIN_USB_HOST_DP}) +add_compile_definitions(PIN_USB_HOST_DM=${PIN_USB_HOST_DM}) +add_compile_definitions(PICO_DEFAULT_PIO_USB_DP_PIN=${PIN_USB_HOST_DP}) +add_compile_definitions(PICO_DEFAULT_PIO_USB_DM_PIN=${PIN_USB_HOST_DM}) + +if (NOT UART STREQUAL "") + add_compile_definitions(PICO_DEFAULT_UART=${UART}) +endif() +if (NOT UART_TX STREQUAL "") + add_compile_definitions(PICO_DEFAULT_UART_TX_PIN=${UART_TX}) +endif() +if (NOT UART_RX STREQUAL "") + add_compile_definitions(PICO_DEFAULT_UART_RX_PIN=${UART_RX}) +endif() + +if (USE_AUDIO) + add_subdirectory(external/pico-extras/src/rp2_common/pico_audio_i2s) + add_subdirectory(external/pico-extras/src/common/pico_audio) + add_subdirectory(external/pico-extras/src/common/pico_util_buffer) + add_compile_definitions(ENABLE_AUDIO=1 PICO_AUDIO_I2S_PIO=1 PICO_AUDIO_I2S_DMA_IRQ=0 PICO_AUDIO_I2S_DATA_PIN=24 PICO_AUDIO_I2S_CLOCK_PIN_BASE=25 PICO_AUDIO_I2S_MONO_INPUT=1 PICO_AUDIO_I2S_SWAP_CLOCK=1) + set(EXTRA_AUDIO_LIB pico_util_buffer pico_audio pico_audio_i2s hardware_i2c) endif() -add_compile_definitions(GPIO_VID_BASE=${VIDEO_PIN}) if (TARGET tinyusb_device) - add_executable(firmware + add_executable(${FIRMWARE} src/main.c - src/video.c + ${VIDEO_SRC} src/kbd.c src/hid.c ${EXTRA_SD_SRC} + ${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c + ${PIOUSB_PATH}/src/pio_usb.c + ${PIOUSB_PATH}/src/pio_usb_host.c + ${PIOUSB_PATH}/src/usb_crc.c + ${UMAC_SOURCES} ) @@ -115,9 +194,9 @@ if (TARGET tinyusb_device) add_custom_target(prepare_umac DEPENDS ${UMAC_MUSASHI_PATH}/m68kops.c ) - add_dependencies(firmware prepare_umac) + add_dependencies(${FIRMWARE} prepare_umac) - target_link_libraries(firmware + target_link_libraries(${FIRMWARE} pico_stdlib pico_multicore tinyusb_host @@ -126,22 +205,27 @@ if (TARGET tinyusb_device) hardware_pio hardware_sync ${EXTRA_SD_LIB} + ${EXTRA_AUDIO_LIB} ) - target_include_directories(firmware PRIVATE + target_include_directories(${FIRMWARE} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include - ${TINYUSB_PATH}/hw - ${TINYUSB_PATH}/src + ${PICO_TINYUSB_PATH}/hw + ${PICO_TINYUSB_PATH}/src ${UMAC_INCLUDE_PATHS} + ${PIOUSB_PATH}/src incbin + ${CMAKE_CURRENT_LIST_DIR} ) - pico_generate_pio_header(firmware ${CMAKE_CURRENT_LIST_DIR}/src/pio_video.pio) + if (NOT USE_HSTX) + pico_generate_pio_header(${FIRMWARE} ${CMAKE_CURRENT_LIST_DIR}/src/pio_video.pio) + endif() - pico_enable_stdio_uart(firmware 1) + pico_enable_stdio_uart(${FIRMWARE} 1) # Needed for UF2: - pico_add_extra_outputs(firmware) + pico_add_extra_outputs(${FIRMWARE}) elseif(PICO_ON_DEVICE) message(WARNING "not building firmware because TinyUSB submodule is not initialized in the SDK") diff --git a/README.md b/README.md index 19b17e9..aa94369 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,37 @@ # Pico Micro Mac (pico-umac) +v0.21-fruitjam 22 March 2025 + +I (@jepler) have run roughshod across the code, breaking things willy-nilly and adding + * 512x342 & 640x480 digital output on HSTX + * PIO USB + * PSRAM support + * Some Sound support + * To enable that, some VIA timer 2 support + +The two main variants offered are the "400kB" mac with a 640x480 resolution & a +4MB mac with 512x342 resolution (presented centered on a 640x480 display). + +For now, I2S is on pins A1 (data) A2 (LRCK) A3 (bit clock). With any luck it'll be moved to the on-board I2S soon. + +What works? + * System beep + * A fair amount of hypercard, though not playing melodies with 'play "Boing" "a b c"' + * Hypercard 'play "Boing"' does play audio though (as does 'beep') + * Dark Castle including audio + * After Dark screensavers including audio + +What almost works + * Glider was working, but my sound changes made it boot with an error about missing coprocessor?? (appears linked to the timer2 implementation) + +There are artifacts that you can grab from the latest Actions build, at least until they expire. + + +Some good Mac software: + * https://archive.org/details/HyperCardBootSystem7 + * https://archive.org/details/mac_DarkCastle_1_2 + * https://archive.org/details/AfterDark2 + v0.21 20 December 2024 diff --git a/external/pico-extras b/external/pico-extras new file mode 160000 index 0000000..c73f2f3 --- /dev/null +++ b/external/pico-extras @@ -0,0 +1 @@ +Subproject commit c73f2f3a2b1ef65dc50113a22aadc5a262a92e0f diff --git a/external/umac b/external/umac index 606f7f7..ce55830 160000 --- a/external/umac +++ b/external/umac @@ -1 +1 @@ -Subproject commit 606f7f75fee8525d4735a0771bf521007115285e +Subproject commit ce55830a1babd681d0e9a639c9b23f10cd9e3c96 diff --git a/fetch-rom-dsk.sh b/fetch-rom-dsk.sh new file mode 100755 index 0000000..b0b67b3 --- /dev/null +++ b/fetch-rom-dsk.sh @@ -0,0 +1,21 @@ +#!/bin/sh +mkdir -p incbin + +if ! [ -f rom.bin ]; then + if ! [ -f '4D1F8172 - MacPlus v3.ROM' ]; then + curl -L 'https://ia902205.us.archive.org/view_archive.php?archive=/18/items/mac_rom_archive_-_as_of_8-19-2011/mac_rom_archive_-_as_of_8-19-2011.zip&file=4D1F8172%20-%20MacPlus%20v3.ROM' > '4D1F8172 - MacPlus v3.ROM' + fi + make -C external/umac clean + make -C external/umac DISP_WIDTH=512 DISP_HEIGHT=342 + ./external/umac/main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin +fi + +xxd -i < rom.bin > incbin/umac-rom.h + +if ! [ -f umac0ro.img ]; then + curl -L 'https://archive.org/download/apple-mac-os-system-3.2-finder-5.3-system-tools-1.0-512-ke-jun-1986-3.5-800k.-7z/Apple%20Mac%20OS%20%28System%203.2%20Finder%205.3%29%20%28System%20Tools%201.0%20Mac%20128%2C%20512K%29%20%28Jun%201986%29%20%283.5-400k%29.7z' > 'Apple Mac OS (System 3.2 Finder 5.3) (System Tools 1.1 Mac Plus) (Jun 1986) (3.5-800k).7z' + 7z x -so 'Apple Mac OS (System 3.2 Finder 5.3) (System Tools 1.1 Mac Plus) (Jun 1986) (3.5-800k).7z' 'Apple Mac OS (System 3.2 Finder 5.3) (System Tools 1.0 Mac 128, 512K) (Jun 1986) (3.5-400k)/System Installation.img' > umac0ro.img +fi + +xxd -i < umac0ro.img > incbin/umac-disc.h + diff --git a/fruitjam-build.sh b/fruitjam-build.sh new file mode 100755 index 0000000..eaee774 --- /dev/null +++ b/fruitjam-build.sh @@ -0,0 +1,84 @@ +#!/bin/sh +set -e + +# Some configurations that actually work at the time I committed this: +# ./fruitjam-build.sh -v # vga resolution, no psram, 128KiB +# ./fruitjam-build.sh -v -m448 # vga resolution, no psram, 448KiB +# ./fruitjam-build.sh -m4096 # 512x342 resolution, psram, 4096KiB +# ./fruitjam-build.sh -d disk.img # specify disk image + +DISP_WIDTH=512 +DISP_HEIGHT=342 +MEMSIZE=400 +DISK_IMAGE="" +CMAKE_ARGS="" + +while getopts "hvd:m:" o; do + case "$o" in + (v) + DISP_WIDTH=640 + DISP_HEIGHT=480 + CMAKE_ARGS="-DUSE_VGA_RES=1" + ;; + (m) + MEMSIZE=$OPTARG + ;; + (d) + DISK_IMAGE=$OPTARG + ;; + (h|?) + echo "Usage: $0 [-v] [-m KiB] [-d diskimage]" + echo "" + echo " -v: Use framebuffer resolution 640x480 instead of 512x342" + echo " -m: Set memory size in KiB" + echo " -d: Specify disk image to include" + echo "" + echo "PSRAM is automatically set depending on memory & framebuffer details" + exit + ;; + esac +done + +shift $((OPTIND-1)) + +TAG=fruitjam_${DISP_WIDTH}x${DISP_HEIGHT}_${MEMSIZE}k +PSRAM=$((MEMSIZE > 400)) +if [ $PSRAM -ne 0 ] ; then + TAG=${TAG}_psram + CMAKE_ARGS="$CMAKE_ARGS -DUSE_PSRAM=1" +fi + +MIRROR_FRAMEBUFFER=$((USE_PSRAM || DISP_WIDTH != 640)) +if [ "$MIRROR_FRAMEBUFFER" -eq 0 ]; then + CMAKE_ARGS="$CMAKE_ARGS -DHSTX_CKP=12 -DHSTX_D0P=14 -DHSTX_D1P=16 -DHSTX_D2P=18 " +fi + +# Append disk name to build directory if disk image is specified +if [ -n "$DISK_IMAGE" ] && [ -f "$DISK_IMAGE" ]; then + # Extract filename without extension + DISK_NAME=$(basename "$DISK_IMAGE" | sed 's/\.[^.]*$//') + TAG=${TAG}_${DISK_NAME} +fi + +set -x +make -C external/umac clean +make -C external/umac DISP_WIDTH=${DISP_WIDTH} DISP_HEIGHT=${DISP_HEIGHT} MEMSIZE=${MEMSIZE} +rm -f rom.bin +./external/umac/main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin || true +[ -f rom.bin ] +xxd -i < rom.bin > incbin/umac-rom.h +if [ -n "$DISK_IMAGE" ] && [ -f "$DISK_IMAGE" ]; then + xxd -i < "$DISK_IMAGE" > incbin/umac-disc.h +fi +rm -rf build_${TAG} +cmake -S . -B build_${TAG} \ + -DPICO_SDK_PATH=../pico-sdk \ + -DPICOTOOL_FETCH_FROM_GIT_PATH="$(pwd)/picotool" \ + -DBOARD=adafruit_fruit_jam -DPICO_BOARD=adafruit_fruit_jam \ + -DMEMSIZE=${MEMSIZE} \ + -DUSE_HSTX=1 \ + -DSD_TX=35 -DSD_RX=36 -DSD_SCK=34 -DSD_CS=39 -DUSE_SD=1 \ + -DUART_TX=44 -DUART_RX=45 -DUART=0 \ + -DBOARD_FILE=boards/adafruit_fruit_jam.c \ + ${CMAKE_ARGS} "$@" +make -C build_${TAG} -j$(nproc) diff --git a/include/tusb_config.h b/include/tusb_config.h index ba1b92f..88be64d 100644 --- a/include/tusb_config.h +++ b/include/tusb_config.h @@ -83,10 +83,16 @@ // max device support (excluding hub device) #define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports +#define CFG_TUH_RPI_PIO_USB 1 + //------------- HID -------------// #define CFG_TUH_HID_EPIN_BUFSIZE 64 #define CFG_TUH_HID_EPOUT_BUFSIZE 64 +#ifndef BOARD_TUH_RHPORT +#define BOARD_TUH_RHPORT 1 +#endif + #ifdef __cplusplus } #endif diff --git a/lib/Pico-PIO-USB b/lib/Pico-PIO-USB new file mode 160000 index 0000000..1862cc0 --- /dev/null +++ b/lib/Pico-PIO-USB @@ -0,0 +1 @@ +Subproject commit 1862cc008e026cbd07b97b28e29eafb5f38b35fb diff --git a/lib/tinyusb b/lib/tinyusb new file mode 160000 index 0000000..5333d04 --- /dev/null +++ b/lib/tinyusb @@ -0,0 +1 @@ +Subproject commit 5333d042f9384ec1cc0663a2708c0ca7cb5cdc32 diff --git a/src/hid.c b/src/hid.c index caae549..6f74eeb 100644 --- a/src/hid.c +++ b/src/hid.c @@ -24,7 +24,7 @@ * THE SOFTWARE. */ -#include "bsp/rp2040/board.h" +#include "bsp/rp2040/boards/adafruit_fruit_jam/board.h" #include "tusb.h" #include "kbd.h" diff --git a/src/main.c b/src/main.c index 53c2c62..4e5bc3a 100644 --- a/src/main.c +++ b/src/main.c @@ -40,7 +40,8 @@ #include "video.h" #include "kbd.h" -#include "bsp/rp2040/board.h" +#include "pio_usb_configuration.h" +#include "bsp/rp2040/boards/adafruit_fruit_jam/board.h" #include "tusb.h" #include "umac.h" @@ -52,6 +53,19 @@ #include "hw_config.h" #endif +#if USE_PSRAM +#include "hardware/structs/qmi.h" +#include "hardware/structs/xip.h" +#endif + +#if ENABLE_AUDIO +#include "pico/audio_i2s.h" +#include "hardware/i2c.h" +uint8_t *audio_base; +static void audio_setup(); +static bool audio_poll(); +#endif + //////////////////////////////////////////////////////////////////////////////// // Imports and data @@ -68,7 +82,16 @@ static const uint8_t umac_rom[] = { #include "umac-rom.h" }; +#if USE_PSRAM +#define umac_ram ((uint8_t*)0x11000000) +#else static uint8_t umac_ram[RAM_SIZE]; +#endif + +#define MIRROR_FRAMEBUFFER (USE_PSRAM || DISP_WIDTH != 640) +#if MIRROR_FRAMEBUFFER +static uint32_t umac_framebuffer_mirror[640*480/32]; +#endif //////////////////////////////////////////////////////////////////////////////// @@ -87,8 +110,8 @@ static void poll_led_etc() if (absolute_time_diff_us(last, now) > 500*1000) { last = now; - led_on ^= 1; - gpio_put(GPIO_LED_PIN, led_on); + //led_on ^= 1; + //gpio_put(GPIO_LED_PIN, led_on); } } @@ -96,6 +119,32 @@ static int umac_cursor_x = 0; static int umac_cursor_y = 0; static int umac_cursor_button = 0; +#define umac_get_audio_offset() (RAM_SIZE - 768) +#if MIRROR_FRAMEBUFFER +static void copy_framebuffer() { + uint32_t *src = (uint32_t*)(umac_ram + umac_get_fb_offset()); +#if DISP_WIDTH==640 && DISP_HEIGHT==480 + uint32_t *dest = umac_framebuffer_mirror; + for(int i=0; i<640*480/32; i++) { + *dest++ = *src++; + } +#elif DISP_WIDTH==512 && DISP_HEIGHT==342 + #define DISP_XOFFSET ((640 - DISP_WIDTH) / 32 / 2) + #define DISP_YOFFSET ((480 - DISP_HEIGHT) / 2) + #define LONGS_PER_INPUT_ROW (DISP_WIDTH / 32) + #define LONGS_PER_OUTPUT_ROW (640 / 32) + for(int i=0; i= 16667) { + bool pending_vsync = p_vsync > 16667; +#if ENABLE_AUDIO + pending_vsync |= audio_poll(); +#endif + if (pending_vsync) { +#if MIRROR_FRAMEBUFFER + copy_framebuffer(); +#endif /* FIXME: Trigger this off actual vsync */ umac_vsync_event(); last_vsync = now; @@ -213,7 +269,7 @@ static void disc_setup(disc_descr_t discs[DISC_NUM_DRIVES]) printf(" *** Can't open %s: %s (%d)!\n", disc0_name, FRESULT_str(fr), fr); goto no_sd; } else { - printf(" Opened, size 0x%x\n", f_size(&discfp)); + printf(" Opened, size %d (0x%x)\n", (unsigned)f_size(&discfp), (unsigned)f_size(&discfp)); if (read_only) printf(" (disc is read-only)\n"); discs[0].base = 0; // Means use R/W ops @@ -254,8 +310,15 @@ static void core1_main() /* Video runs on core 1, i.e. IRQs/DMA are unaffected by * core 0's USB activity. */ +#if MIRROR_FRAMEBUFFER + video_init((uint32_t *)(umac_framebuffer_mirror)); +#else video_init((uint32_t *)(umac_ram + umac_get_fb_offset())); +#endif +#if ENABLE_AUDIO + audio_base = (uint8_t*)umac_ram + umac_get_audio_offset(); +#endif printf("Enjoyable Mac times now begin:\n\n"); while (true) { @@ -263,17 +326,181 @@ static void core1_main() } } +size_t psram_size; + +size_t _psram_size; +static void __no_inline_not_in_flash_func(setup_psram)(void) { + _psram_size = 0; +#if USE_PSRAM + gpio_set_function(PIN_PSRAM_CS, GPIO_FUNC_XIP_CS1); + uint32_t save = save_and_disable_interrupts(); + // Try and read the PSRAM ID via direct_csr. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | + QMI_DIRECT_CSR_EN_BITS; + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + // Exit out of QMI in case we've inited already + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + // Transmit as quad. + qmi_hw->direct_tx = QMI_DIRECT_TX_OE_BITS | + QMI_DIRECT_TX_IWIDTH_VALUE_Q << QMI_DIRECT_TX_IWIDTH_LSB | + 0xf5; + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + (void)qmi_hw->direct_rx; + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + + // Read the id + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + uint8_t kgd = 0; + uint8_t eid = 0; + for (size_t i = 0; i < 7; i++) { + if (i == 0) { + qmi_hw->direct_tx = 0x9f; + } else { + qmi_hw->direct_tx = 0xff; + } + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_TXEMPTY_BITS) == 0) { + } + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + if (i == 5) { + kgd = qmi_hw->direct_rx; + } else if (i == 6) { + eid = qmi_hw->direct_rx; + } else { + (void)qmi_hw->direct_rx; + } + } + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + if (kgd != 0x5D) { + restore_interrupts(save); + return; + } + + // Enable quad mode. + qmi_hw->direct_csr = 30 << QMI_DIRECT_CSR_CLKDIV_LSB | + QMI_DIRECT_CSR_EN_BITS; + // Need to poll for the cooldown on the last XIP transfer to expire + // (via direct-mode BUSY flag) before it is safe to perform the first + // direct-mode operation + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + + // RESETEN, RESET and quad enable + for (uint8_t i = 0; i < 3; i++) { + qmi_hw->direct_csr |= QMI_DIRECT_CSR_ASSERT_CS1N_BITS; + if (i == 0) { + qmi_hw->direct_tx = 0x66; + } else if (i == 1) { + qmi_hw->direct_tx = 0x99; + } else { + qmi_hw->direct_tx = 0x35; + } + while ((qmi_hw->direct_csr & QMI_DIRECT_CSR_BUSY_BITS) != 0) { + } + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS); + for (size_t j = 0; j < 20; j++) { + asm ("nop"); + } + (void)qmi_hw->direct_rx; + } + // Disable direct csr. + qmi_hw->direct_csr &= ~(QMI_DIRECT_CSR_ASSERT_CS1N_BITS | QMI_DIRECT_CSR_EN_BITS); + + qmi_hw->m[1].timing = + QMI_M0_TIMING_PAGEBREAK_VALUE_1024 << QMI_M0_TIMING_PAGEBREAK_LSB | // Break between pages. + 3 << QMI_M0_TIMING_SELECT_HOLD_LSB | // Delay releasing CS for 3 extra system cycles. + 1 << QMI_M0_TIMING_COOLDOWN_LSB | + 1 << QMI_M0_TIMING_RXDELAY_LSB | + 16 << QMI_M0_TIMING_MAX_SELECT_LSB | // In units of 64 system clock cycles. PSRAM says 8us max. 8 / 0.00752 / 64 = 16.62 + 7 << QMI_M0_TIMING_MIN_DESELECT_LSB | // In units of system clock cycles. PSRAM says 50ns.50 / 7.52 = 6.64 + 2 << QMI_M0_TIMING_CLKDIV_LSB; + qmi_hw->m[1].rfmt = (QMI_M0_RFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_PREFIX_WIDTH_LSB | + QMI_M0_RFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_RFMT_ADDR_WIDTH_LSB | + QMI_M0_RFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_RFMT_SUFFIX_WIDTH_LSB | + QMI_M0_RFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_RFMT_DUMMY_WIDTH_LSB | + QMI_M0_RFMT_DUMMY_LEN_VALUE_24 << QMI_M0_RFMT_DUMMY_LEN_LSB | + QMI_M0_RFMT_DATA_WIDTH_VALUE_Q << QMI_M0_RFMT_DATA_WIDTH_LSB | + QMI_M0_RFMT_PREFIX_LEN_VALUE_8 << QMI_M0_RFMT_PREFIX_LEN_LSB | + QMI_M0_RFMT_SUFFIX_LEN_VALUE_NONE << QMI_M0_RFMT_SUFFIX_LEN_LSB); + qmi_hw->m[1].rcmd = 0xeb << QMI_M0_RCMD_PREFIX_LSB | + 0 << QMI_M0_RCMD_SUFFIX_LSB; + qmi_hw->m[1].wfmt = (QMI_M0_WFMT_PREFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_PREFIX_WIDTH_LSB | + QMI_M0_WFMT_ADDR_WIDTH_VALUE_Q << QMI_M0_WFMT_ADDR_WIDTH_LSB | + QMI_M0_WFMT_SUFFIX_WIDTH_VALUE_Q << QMI_M0_WFMT_SUFFIX_WIDTH_LSB | + QMI_M0_WFMT_DUMMY_WIDTH_VALUE_Q << QMI_M0_WFMT_DUMMY_WIDTH_LSB | + QMI_M0_WFMT_DUMMY_LEN_VALUE_NONE << QMI_M0_WFMT_DUMMY_LEN_LSB | + QMI_M0_WFMT_DATA_WIDTH_VALUE_Q << QMI_M0_WFMT_DATA_WIDTH_LSB | + QMI_M0_WFMT_PREFIX_LEN_VALUE_8 << QMI_M0_WFMT_PREFIX_LEN_LSB | + QMI_M0_WFMT_SUFFIX_LEN_VALUE_NONE << QMI_M0_WFMT_SUFFIX_LEN_LSB); + qmi_hw->m[1].wcmd = 0x38 << QMI_M0_WCMD_PREFIX_LSB | + 0 << QMI_M0_WCMD_SUFFIX_LSB; + + restore_interrupts(save); + + _psram_size = 1024 * 1024; // 1 MiB + uint8_t size_id = eid >> 5; + if (eid == 0x26 || size_id == 2) { + _psram_size *= 8; + } else if (size_id == 0) { + _psram_size *= 2; + } else if (size_id == 1) { + _psram_size *= 4; + } + + // Mark that we can write to PSRAM. + xip_ctrl_hw->ctrl |= XIP_CTRL_WRITABLE_M1_BITS; + + // Test write to the PSRAM. + volatile uint32_t *psram_nocache = (volatile uint32_t *)0x15000000; + psram_nocache[0] = 0x12345678; + volatile uint32_t readback = psram_nocache[0]; + if (readback != 0x12345678) { + _psram_size = 0; + return; + } +#endif +} + int main() { - set_sys_clock_khz(250*1000, true); + // set_sys_clock_khz(250*1000, true); + + setup_psram(); stdio_init_all(); io_init(); +#if ENABLE_AUDIO + audio_setup(); +#endif + multicore_launch_core1(core1_main); printf("Starting, init usb\n"); - tusb_init(); + + pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG; + pio_cfg.tx_ch = 2; + pio_cfg.pin_dp = PICO_DEFAULT_PIO_USB_DP_PIN; + _Static_assert(PIN_USB_HOST_DP + 1 == PIN_USB_HOST_DM || PIN_USB_HOST_DP - 1 == PIN_USB_HOST_DM, "Permitted USB D+/D- configuration"); + pio_cfg.pinout = PIN_USB_HOST_DP + 1 == PIN_USB_HOST_DM ? PIO_USB_PINOUT_DPDM : PIO_USB_PINOUT_DMDP; + +#ifdef PICO_DEFAULT_PIO_USB_VBUSEN_PIN + gpio_init(PICO_DEFAULT_PIO_USB_VBUSEN_PIN); + gpio_set_dir(PICO_DEFAULT_PIO_USB_VBUSEN_PIN, GPIO_OUT); + gpio_put(PICO_DEFAULT_PIO_USB_VBUSEN_PIN, PICO_DEFAULT_PIO_USB_VBUSEN_STATE); +#endif + + tuh_configure(BOARD_TUH_RHPORT, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &pio_cfg); + + tuh_init(BOARD_TUH_RHPORT); /* This happens on core 0: */ while (true) { @@ -285,3 +512,305 @@ int main() return 0; } +#if ENABLE_AUDIO + +#define I2C_ADDR 0x18 + +void writeRegister(uint8_t reg, uint8_t value) { + char buf[2]; + buf[0] = reg; + buf[1] = value; + int res = i2c_write_timeout_us(i2c0, I2C_ADDR, buf, sizeof(buf), /* nostop */ false, 1000); + if (res != 2) { +printf("res=%d\n", res); + panic("i2c_write_timeout failed: res=%d\n", res); + } + printf("Write Reg: %d = 0x%x\n", reg, value); +} + +uint8_t readRegister(uint8_t reg) { + char buf[1]; + buf[0] = reg; + int res = i2c_write_timeout_us(i2c0, I2C_ADDR, buf, sizeof(buf), /* nostop */ true, 1000); + if (res != 1) { +printf("res=%d\n", res); + panic("i2c_write_timeout failed: res=%d\n", res); + } + res = i2c_read_timeout_us(i2c0, I2C_ADDR, buf, sizeof(buf), /* nostop */ false, 1000); + if (res != 1) { +printf("res=%d\n", res); + panic("i2c_read_timeout failed: res=%d\n", res); + } + uint8_t value = buf[0]; + printf("Read Reg: %d = 0x%x\n", reg, value); + return value; +} + +void modifyRegister(uint8_t reg, uint8_t mask, uint8_t value) { + uint8_t current = readRegister(reg); + printf("Modify Reg: %d = [Before: 0x%x] with mask 0x%x and value 0x%x\n", reg, current, mask, value); + uint8_t new_value = (current & ~mask) | (value & mask); + writeRegister(reg, new_value); +} + +void setPage(uint8_t page) { + printf("Set page %d\n", page); + writeRegister(0x00, page); +} + + +void Wire_begin() { + i2c_init(i2c0, 100000); + gpio_set_function(20, GPIO_FUNC_I2C); + gpio_set_function(21, GPIO_FUNC_I2C); +} + + +static void setup_i2s_dac() { +gpio_init(22); +gpio_set_dir(22, true); +gpio_put(22, true); // allow i2s to come out of reset + + Wire_begin(); + sleep_ms(1000); + + printf("initialize codec\n"); + + // Reset codec + writeRegister(0x01, 0x01); + sleep_ms(10); + + // Interface Control + modifyRegister(0x1B, 0xC0, 0x00); + modifyRegister(0x1B, 0x30, 0x00); + + // Clock MUX and PLL settings + modifyRegister(0x04, 0x03, 0x03); + modifyRegister(0x04, 0x0C, 0x04); + + writeRegister(0x06, 0x20); // PLL J + writeRegister(0x08, 0x00); // PLL D LSB + writeRegister(0x07, 0x00); // PLL D MSB + + modifyRegister(0x05, 0x0F, 0x02); // PLL P/R + modifyRegister(0x05, 0x70, 0x10); + + // DAC/ADC Config + modifyRegister(0x0B, 0x7F, 0x08); // NDAC + modifyRegister(0x0B, 0x80, 0x80); + + modifyRegister(0x0C, 0x7F, 0x02); // MDAC + modifyRegister(0x0C, 0x80, 0x80); + + modifyRegister(0x12, 0x7F, 0x08); // NADC + modifyRegister(0x12, 0x80, 0x80); + + modifyRegister(0x13, 0x7F, 0x02); // MADC + modifyRegister(0x13, 0x80, 0x80); + + // PLL Power Up + modifyRegister(0x05, 0x80, 0x80); + + // Headset and GPIO Config +setPage(1); +modifyRegister(0x2e, 0xFF, 0x0b); +setPage(0); + modifyRegister(0x43, 0x80, 0x80); // Headset Detect + modifyRegister(0x30, 0x80, 0x80); // INT1 Control + modifyRegister(0x33, 0x3C, 0x14); // GPIO1 + + + // DAC Setup + modifyRegister(0x3F, 0xC0, 0xC0); + + // DAC Routing + setPage(1); + modifyRegister(0x23, 0xC0, 0x40); + modifyRegister(0x23, 0x0C, 0x04); + + // DAC Volume Control + setPage(0); + modifyRegister(0x40, 0x0C, 0x00); + writeRegister(0x41, 0x28); // Left DAC Vol + writeRegister(0x42, 0x28); // Right DAC Vol + + // ADC Setup + modifyRegister(0x51, 0x80, 0x80); + modifyRegister(0x52, 0x80, 0x00); + writeRegister(0x53, 0x68); // ADC Volume + + // Headphone and Speaker Setup + setPage(1); + modifyRegister(0x1F, 0xC0, 0xC0); // HP Driver + modifyRegister(0x28, 0x04, 0x04); // HP Left Gain + modifyRegister(0x29, 0x04, 0x04); // HP Right Gain + writeRegister(0x24, 0x0A); // Left Analog HP + writeRegister(0x25, 0x0A); // Right Analog HP + + modifyRegister(0x28, 0x78, 0x40); // HP Left Gain + modifyRegister(0x29, 0x78, 0x40); // HP Right Gain + + // Speaker Amp + modifyRegister(0x20, 0x80, 0x80); + modifyRegister(0x2A, 0x04, 0x04); + modifyRegister(0x2A, 0x18, 0x08); + writeRegister(0x26, 0x0A); + + // Return to page 0 + setPage(0); + + printf("Initialization complete!\n"); + + + // Read all registers for verification + printf("Reading all registers for verification:\n"); + + setPage(0); + readRegister(0x00); // AIC31XX_PAGECTL + readRegister(0x01); // AIC31XX_RESET + readRegister(0x03); // AIC31XX_OT_FLAG + readRegister(0x04); // AIC31XX_CLKMUX + readRegister(0x05); // AIC31XX_PLLPR + readRegister(0x06); // AIC31XX_PLLJ + readRegister(0x07); // AIC31XX_PLLDMSB + readRegister(0x08); // AIC31XX_PLLDLSB + readRegister(0x0B); // AIC31XX_NDAC + readRegister(0x0C); // AIC31XX_MDAC + readRegister(0x0D); // AIC31XX_DOSRMSB + readRegister(0x0E); // AIC31XX_DOSRLSB + readRegister(0x10); // AIC31XX_MINI_DSP_INPOL + readRegister(0x12); // AIC31XX_NADC + readRegister(0x13); // AIC31XX_MADC + readRegister(0x14); // AIC31XX_AOSR + readRegister(0x19); // AIC31XX_CLKOUTMUX + readRegister(0x1A); // AIC31XX_CLKOUTMVAL + readRegister(0x1B); // AIC31XX_IFACE1 + readRegister(0x1C); // AIC31XX_DATA_OFFSET + readRegister(0x1D); // AIC31XX_IFACE2 + readRegister(0x1E); // AIC31XX_BCLKN + readRegister(0x1F); // AIC31XX_IFACESEC1 + readRegister(0x20); // AIC31XX_IFACESEC2 + readRegister(0x21); // AIC31XX_IFACESEC3 + readRegister(0x22); // AIC31XX_I2C + readRegister(0x24); // AIC31XX_ADCFLAG + readRegister(0x25); // AIC31XX_DACFLAG1 + readRegister(0x26); // AIC31XX_DACFLAG2 + readRegister(0x27); // AIC31XX_OFFLAG + readRegister(0x2C); // AIC31XX_INTRDACFLAG + readRegister(0x2D); // AIC31XX_INTRADCFLAG + readRegister(0x2E); // AIC31XX_INTRDACFLAG2 + readRegister(0x2F); // AIC31XX_INTRADCFLAG2 + readRegister(0x30); // AIC31XX_INT1CTRL + readRegister(0x31); // AIC31XX_INT2CTRL + readRegister(0x33); // AIC31XX_GPIO1 + readRegister(0x3C); // AIC31XX_DACPRB + readRegister(0x3D); // AIC31XX_ADCPRB + readRegister(0x3F); // AIC31XX_DACSETUP + readRegister(0x40); // AIC31XX_DACMUTE + readRegister(0x41); // AIC31XX_LDACVOL + readRegister(0x42); // AIC31XX_RDACVOL + readRegister(0x43); // AIC31XX_HSDETECT + readRegister(0x51); // AIC31XX_ADCSETUP + readRegister(0x52); // AIC31XX_ADCFGA + readRegister(0x53); // AIC31XX_ADCVOL + + setPage(1); + readRegister(0x1F); // AIC31XX_HPDRIVER + readRegister(0x20); // AIC31XX_SPKAMP + readRegister(0x21); // AIC31XX_HPPOP + readRegister(0x22); // AIC31XX_SPPGARAMP + readRegister(0x23); // AIC31XX_DACMIXERROUTE + readRegister(0x24); // AIC31XX_LANALOGHPL + readRegister(0x25); // AIC31XX_RANALOGHPR + readRegister(0x26); // AIC31XX_LANALOGSPL + readRegister(0x27); // AIC31XX_RANALOGSPR + readRegister(0x28); // AIC31XX_HPLGAIN + readRegister(0x29); // AIC31XX_HPRGAIN + readRegister(0x2A); // AIC31XX_SPLGAIN + readRegister(0x2B); // AIC31XX_SPRGAIN + readRegister(0x2C); // AIC31XX_HPCONTROL + readRegister(0x2E); // AIC31XX_MICBIAS + readRegister(0x2F); // AIC31XX_MICPGA + readRegister(0x30); // AIC31XX_MICPGAPI + readRegister(0x31); // AIC31XX_MICPGAMI + readRegister(0x32); // AIC31XX_MICPGACM + + setPage(3); + readRegister(0x10); // AIC31XX_TIMERDIVIDER + +} +static int volscale; + + +#define SAMPLES_PER_BUFFER (370) +int16_t audio[SAMPLES_PER_BUFFER]; + +void umac_audio_trap() { +static int led_on; + led_on ^= 1; + gpio_put(GPIO_LED_PIN, 1); + int32_t offset = 128; + uint16_t *audiodata = (uint16_t*)audio_base; + int scale = volscale; + if (!scale) { + memset(audio, 0, sizeof(audio)); + return; + } + int16_t *stream = audio; + for(int i=0; i> 8; + *stream++ = a; + } +} + +struct audio_buffer_pool *producer_pool; + +static audio_format_t audio_format = { + .format = AUDIO_BUFFER_FORMAT_PCM_S16, + .sample_freq = 22256, // 60.15Hz*370, rounded up + .channel_count = 1, +}; + +const struct audio_i2s_config config = + { + .data_pin = PICO_AUDIO_I2S_DATA_PIN, + .clock_pin_base = PICO_AUDIO_I2S_CLOCK_PIN_BASE, + .clock_pin_swapped = true, + .pio_sm = 0, + .dma_channel = 3 + }; + +static struct audio_buffer_format producer_format = { + .format = &audio_format, + .sample_stride = 2 +}; + +static void audio_setup() { +setup_i2s_dac(); + const struct audio_format *output_format = audio_i2s_setup(&audio_format, &config); + assert(output_format); + if (!output_format) { + panic("PicoAudio: Unable to open audio device.\n"); + } + producer_pool = audio_new_producer_pool(&producer_format, 3, SAMPLES_PER_BUFFER); + assert(producer_pool); + bool ok = audio_i2s_connect(producer_pool); + assert(ok); + audio_i2s_set_enabled(true); +} + +static bool audio_poll() { + audio_buffer_t *buffer = take_audio_buffer(producer_pool, false); + if (!buffer) return false; + gpio_put(GPIO_LED_PIN, 0); + memcpy(buffer->buffer->bytes, audio, sizeof(audio)); + buffer->sample_count = SAMPLES_PER_BUFFER; + give_audio_buffer(producer_pool, buffer); + return true; +} + +void umac_audio_cfg(int volume, int sndres) { + volscale = sndres ? 0 : 65536 * volume / 7; +} +#endif diff --git a/src/sd_hw_config.c b/src/sd_hw_config.c index db270a2..446ff7d 100755 --- a/src/sd_hw_config.c +++ b/src/sd_hw_config.c @@ -38,9 +38,13 @@ socket, which SPI it is driven by, and how it is wired. // Hardware Configuration of SPI "objects" // Note: multiple SD cards can be driven by one SPI if they use different slave // selects. +#ifndef SD_SPI_INST +#define SD_SPI_INST (spi0) +#endif + static spi_t spis[] = { // One for each SPI. { - .hw_inst = spi0, // SPI component + .hw_inst = SD_SPI_INST, // SPI component .miso_gpio = SD_RX, // GPIO number (not pin number) .mosi_gpio = SD_TX, .sck_gpio = SD_SCK, diff --git a/src/video_hstx.c b/src/video_hstx.c new file mode 100644 index 0000000..91aa910 --- /dev/null +++ b/src/video_hstx.c @@ -0,0 +1,309 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "stdlib.h" + +// This is from: https://github.com/raspberrypi/pico-examples-rp2350/blob/a1/hstx/dvi_out_hstx_encoder/dvi_out_hstx_encoder.c + +#include "hardware/dma.h" +#include "hardware/gpio.h" +#include "hardware/structs/bus_ctrl.h" +#include "hardware/structs/hstx_ctrl.h" +#include "hardware/structs/hstx_fifo.h" + +// ---------------------------------------------------------------------------- +// DVI constants + +#define TMDS_CTRL_00 0x354u +#define TMDS_CTRL_01 0x0abu +#define TMDS_CTRL_10 0x154u +#define TMDS_CTRL_11 0x2abu + +#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) +#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20)) + +#define MODE_H_SYNC_POLARITY 0 +#define MODE_H_FRONT_PORCH 16 +#define MODE_H_SYNC_WIDTH 96 +#define MODE_H_BACK_PORCH 48 +#define MODE_H_ACTIVE_PIXELS 640 + +#define MODE_V_SYNC_POLARITY 0 +#define MODE_V_FRONT_PORCH 10 +#define MODE_V_SYNC_WIDTH 2 +#define MODE_V_BACK_PORCH 33 +#define MODE_V_ACTIVE_LINES 480 + +#define MODE_H_TOTAL_PIXELS ( \ + MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \ + MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \ + ) +#define MODE_V_TOTAL_LINES ( \ + MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \ + MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \ + ) + +#define HSTX_CMD_RAW (0x0u << 12) +#define HSTX_CMD_RAW_REPEAT (0x1u << 12) +#define HSTX_CMD_TMDS (0x2u << 12) +#define HSTX_CMD_TMDS_REPEAT (0x3u << 12) +#define HSTX_CMD_NOP (0xfu << 12) + +#define DO_BSWAP (1) +#if DO_BSWAP +#define BSWAP_MAYBE(x) (\ + ((x) & 0xff) << 24 \ +| (((x) >> 8) & 0xff) << 16 \ +| (((x) >> 16) & 0xff) << 8 \ +| (((x) >> 24) & 0xff) \ +) +#else +#define BSWAP_MAYBE(x) (x) +#endif + +// ---------------------------------------------------------------------------- +// HSTX command lists + +static uint32_t vblank_line_vsync_off[] = { + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH), + BSWAP_MAYBE(SYNC_V1_H1), + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH), + BSWAP_MAYBE(SYNC_V1_H0), + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS)), + BSWAP_MAYBE(SYNC_V1_H1), +}; + +static uint32_t vblank_line_vsync_on[] = { + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH), + BSWAP_MAYBE(SYNC_V0_H1), + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH), + BSWAP_MAYBE(SYNC_V0_H0), + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS)), + BSWAP_MAYBE(SYNC_V0_H1), +}; + +static uint32_t vactive_line[] = { + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH), + BSWAP_MAYBE(SYNC_V1_H1), + BSWAP_MAYBE(HSTX_CMD_NOP), + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH), + BSWAP_MAYBE(SYNC_V1_H0), + BSWAP_MAYBE(HSTX_CMD_NOP), + BSWAP_MAYBE(HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH), + BSWAP_MAYBE(SYNC_V1_H1), + BSWAP_MAYBE(HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS), +}; + +typedef struct { + uint32_t *dma_commands; + size_t dma_commands_len; // in words + uint8_t dma_pixel_channel; + uint8_t dma_command_channel; +} picodvi_framebuffer_obj_t; + +picodvi_framebuffer_obj_t *active_picodvi = NULL; +picodvi_framebuffer_obj_t picodvi; + +static void __not_in_flash_func(dma_irq_handler)(void) { + if (active_picodvi == NULL) { + return; + } + uint ch_num = active_picodvi->dma_pixel_channel; + dma_channel_hw_t *ch = &dma_hw->ch[ch_num]; + dma_hw->intr = 1u << ch_num; + + // Set the read_addr back to the start and trigger the first transfer (which + // will trigger the pixel channel). + ch = &dma_hw->ch[active_picodvi->dma_command_channel]; + ch->al3_read_addr_trig = (uintptr_t)active_picodvi->dma_commands; +} + +#define REAL_DISP_WIDTH 640 +#define REAL_DISP_HEIGHT 480 + +void video_init(uint32_t *framebuffer) { + picodvi_framebuffer_obj_t *self = &picodvi; + + // We compute all DMA transfers needed for a single frame. This ensure we don't have any super + // quick interrupts that we need to respond to. Each transfer takes two words, trans_count and + // read_addr. Active pixel lines need two transfers due to different read addresses. When pixel + // doubling, then we must also set transfer size. + size_t dma_command_size = 2; + self->dma_commands_len = (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH + 2 * MODE_V_ACTIVE_LINES + 1) * dma_command_size; + self->dma_commands = (uint32_t *)malloc(self->dma_commands_len * sizeof(uint32_t)); + if (self->dma_commands == NULL) { + return; + } + + self->dma_pixel_channel = dma_claim_unused_channel(true); + self->dma_command_channel = dma_claim_unused_channel(true); + + size_t pixels_per_word = 32; + size_t words_per_line = REAL_DISP_WIDTH / pixels_per_word; + uint8_t rot = 24; // 24 + color_depth; + size_t shift_amount = 31; // color_depth % 32; + + size_t command_word = 0; + size_t frontporch_start = MODE_V_TOTAL_LINES - MODE_V_FRONT_PORCH; + size_t frontporch_end = frontporch_start + MODE_V_FRONT_PORCH; + size_t vsync_start = 0; + size_t vsync_end = vsync_start + MODE_V_SYNC_WIDTH; + size_t backporch_start = vsync_end; + size_t backporch_end = backporch_start + MODE_V_BACK_PORCH; + size_t active_start = backporch_end; + + uint32_t dma_ctrl = (self->dma_command_channel << DMA_CH0_CTRL_TRIG_CHAIN_TO_LSB) | + (DREQ_HSTX << DMA_CH0_CTRL_TRIG_TREQ_SEL_LSB) | + DMA_CH0_CTRL_TRIG_IRQ_QUIET_BITS | + DMA_CH0_CTRL_TRIG_INCR_READ_BITS | + DMA_CH0_CTRL_TRIG_EN_BITS; + uint32_t dma_pixel_ctrl = dma_ctrl | ((pixels_per_word == 32 ? DMA_SIZE_32 : DMA_SIZE_16) << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); +#if DO_BSWAP + dma_pixel_ctrl |= DMA_CH0_CTRL_TRIG_BSWAP_BITS; +#endif + dma_ctrl |= (DMA_SIZE_32 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB); + + uint32_t dma_write_addr = (uint32_t)&hstx_fifo_hw->fifo; + // Write ctrl and write_addr once when not pixel doubling because they don't + // change. (write_addr doesn't change when pixel doubling either but we need + // to rewrite it because it is after the ctrl register.) + dma_channel_hw_addr(self->dma_pixel_channel)->al1_ctrl = dma_pixel_ctrl; + dma_channel_hw_addr(self->dma_pixel_channel)->al1_write_addr = dma_write_addr; + + for (size_t v_scanline = 0; v_scanline < MODE_V_TOTAL_LINES; v_scanline++) { + if (vsync_start <= v_scanline && v_scanline < vsync_end) { + self->dma_commands[command_word++] = count_of(vblank_line_vsync_on); + self->dma_commands[command_word++] = (uintptr_t)vblank_line_vsync_on; + } else if (backporch_start <= v_scanline && v_scanline < backporch_end) { + self->dma_commands[command_word++] = count_of(vblank_line_vsync_off); + self->dma_commands[command_word++] = (uintptr_t)vblank_line_vsync_off; + } else if (frontporch_start <= v_scanline && v_scanline < frontporch_end) { + self->dma_commands[command_word++] = count_of(vblank_line_vsync_off); + self->dma_commands[command_word++] = (uintptr_t)vblank_line_vsync_off; + } else { + self->dma_commands[command_word++] = count_of(vactive_line); + self->dma_commands[command_word++] = (uintptr_t)vactive_line; + size_t row = v_scanline - active_start; + size_t transfer_count = words_per_line; + self->dma_commands[command_word++] = transfer_count; + uintptr_t row_start = row * (REAL_DISP_WIDTH / 8) + (uintptr_t)framebuffer; + self->dma_commands[command_word++] = row_start; + } + } + // Last command is NULL which will trigger an IRQ. + self->dma_commands[command_word++] = 0; + self->dma_commands[command_word++] = 0; + + // B&W + size_t color_depth = 1; + hstx_ctrl_hw->expand_tmds = + (color_depth - 1) << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB | + rot << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB | + (color_depth - 1) << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB | + rot << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB | + (color_depth - 1) << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB | + rot << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB; + size_t shifts_before_empty = (pixels_per_word % 32); + + // Pixels come in 32 bits at a time. color_depth dictates the number + // of pixels per word. Control symbols (RAW) are an entire 32-bit word. + hstx_ctrl_hw->expand_shift = + (shifts_before_empty << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB) | + (shift_amount << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB) | + 1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB | + 0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB; + + // Serial output config: clock period of 5 cycles, pop from command + // expander every 5 cycles, shift the output shiftreg by 2 every cycle. + hstx_ctrl_hw->csr = 0; + hstx_ctrl_hw->csr = + HSTX_CTRL_CSR_EXPAND_EN_BITS | + (5u << HSTX_CTRL_CSR_CLKDIV_LSB) | + (5u << HSTX_CTRL_CSR_N_SHIFTS_LSB) | + (2u << HSTX_CTRL_CSR_SHIFT_LSB) | + HSTX_CTRL_CSR_EN_BITS; + + // XXX this may be wrong, because pico-mac is using an overclock (but is it OC'ing HSTX? not sure) + + // Note we are leaving the HSTX clock at the SDK default of 125 MHz; since + // we shift out two bits per HSTX clock cycle, this gives us an output of + // 250 Mbps, which is very close to the bit clock for 480p 60Hz (252 MHz). + // If we want the exact rate then we'll have to reconfigure PLLs. + +#define HSTX_FIRST_PIN 12 + + // Setup the data to pin mapping. + hstx_ctrl_hw->bit[(HSTX_CKP ) - HSTX_FIRST_PIN] = HSTX_CTRL_BIT0_CLK_BITS; + hstx_ctrl_hw->bit[(HSTX_CKP ^ 1) - HSTX_FIRST_PIN] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS; + + const int pinout[] = { HSTX_D0P, HSTX_D1P, HSTX_D2P }; + + for(uint lane = 0; lane < 3; lane++ ) { + int bit = pinout[lane]; + + uint32_t lane_data_sel_bits = + (lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB | + (lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB; + // The two halves of each pair get identical data, but one pin is inverted. + hstx_ctrl_hw->bit[(bit ) - HSTX_FIRST_PIN] = lane_data_sel_bits; + hstx_ctrl_hw->bit[(bit ^ 1) - HSTX_FIRST_PIN] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS; + } + + for (int i = 12; i <= 19; ++i) { + gpio_set_function(i, 0); // HSTX + } + + dma_channel_config c; + c = dma_channel_get_default_config(self->dma_command_channel); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, true); + // This wraps the transfer back to the start of the write address. + size_t wrap = 3; // 8 bytes because we write two DMA registers. + volatile uint32_t *write_addr = &dma_hw->ch[self->dma_pixel_channel].al3_transfer_count; + channel_config_set_ring(&c, true, wrap); + // No chain because we use an interrupt to reload this channel instead of a + // third channel. + dma_channel_configure( + self->dma_command_channel, + &c, + write_addr, + self->dma_commands, + (1 << wrap) / sizeof(uint32_t), + false + ); + + dma_hw->ints2 = (1u << self->dma_pixel_channel); + dma_hw->inte2 = (1u << self->dma_pixel_channel); + irq_set_exclusive_handler(DMA_IRQ_2, dma_irq_handler); + irq_set_enabled(DMA_IRQ_2, true); + + bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS; + + active_picodvi = self; + + dma_irq_handler(); +} diff --git a/src/video.c b/src/video_vga.c similarity index 100% rename from src/video.c rename to src/video_vga.c