Merge pull request #1 from adafruit/rp2350-fruitjam

Rp2350 fruitjam
This commit is contained in:
Jeff Epler 2025-03-24 11:21:36 -05:00 committed by GitHub
commit 3edf26ed1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1180 additions and 35 deletions

62
.github/workflows/build.yaml vendored Normal file
View file

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

13
.gitmodules vendored
View file

@ -1,6 +1,17 @@
[submodule "external/umac"] [submodule "external/umac"]
url = https://github.com/evansm7/umac url = https://github.com/adafruit/umac
path = external/umac path = external/umac
branch = soundemu
[submodule "external/no-OS-FatFS-SD-SPI-RPi-Pico"] [submodule "external/no-OS-FatFS-SD-SPI-RPi-Pico"]
path = 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 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

View file

@ -28,6 +28,16 @@ cmake_minimum_required(VERSION 3.13)
# Options that should be defined when initialising the build # Options that should be defined when initialising the build
# directory with cmake, e.g. "cmake .. -DOPTION=true": # 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) option(USE_SD "Build in SD support" OFF)
set(SD_TX 3 CACHE STRING "SD SPI TX pin") 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_SCK 2 CACHE STRING "SD SPI SCK pin")
set(SD_CS 5 CACHE STRING "SD SPI CS pin") set(SD_CS 5 CACHE STRING "SD SPI CS pin")
set(SD_MHZ 5 CACHE STRING "SD SPI speed in MHz") 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) 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=<size in KB> will configure umac's memory size, # See below, -DMEMSIZE=<size in KB> will configure umac's memory size,
# overriding defaults. # 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 # initialize the SDK based on PICO_SDK_PATH
# note: this must happen before project() # note: this must happen before project()
include(pico_sdk_import.cmake) include(pico_sdk_import.cmake)
project(firmware) project(${FIRMWARE})
# initialize the Raspberry Pi Pico SDK # initialize the Raspberry Pi Pico SDK
pico_sdk_init() pico_sdk_init()
@ -54,8 +117,6 @@ pico_sdk_init()
set(FAMILY rp2040) set(FAMILY rp2040)
set(BOARD raspberry_pi_pico) set(BOARD raspberry_pi_pico)
set(TINYUSB_PATH ${PICO_SDK_PATH}/lib/tinyusb)
# umac subproject (and Musashi sub-subproject) # umac subproject (and Musashi sub-subproject)
set(UMAC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/umac) set(UMAC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/umac)
set(UMAC_MUSASHI_PATH ${UMAC_PATH}/external/Musashi) set(UMAC_MUSASHI_PATH ${UMAC_PATH}/external/Musashi)
@ -74,8 +135,8 @@ set(UMAC_SOURCES
${UMAC_MUSASHI_PATH}/softfloat/softfloat.c ${UMAC_MUSASHI_PATH}/softfloat/softfloat.c
) )
set(MEMSIZE 128 CACHE STRING "Memory size, in KB") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb -g3 -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\" -DUMAC_MEMSIZE=${MEMSIZE}")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\" -DUMAC_MEMSIZE=${MEMSIZE}")
if (USE_SD) if (USE_SD)
add_compile_definitions(USE_SD=1) 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}) add_compile_definitions(SD_TX=${SD_TX} SD_RX=${SD_RX} SD_SCK=${SD_SCK} SD_CS=${SD_CS} SD_MHZ=${SD_MHZ})
endif() endif()
if (USE_VGA_RES) add_compile_definitions(PIN_USB_HOST_DP=${PIN_USB_HOST_DP})
add_compile_definitions(USE_VGA_RES=1) add_compile_definitions(PIN_USB_HOST_DM=${PIN_USB_HOST_DM})
add_compile_definitions(DISP_WIDTH=640) add_compile_definitions(PICO_DEFAULT_PIO_USB_DP_PIN=${PIN_USB_HOST_DP})
add_compile_definitions(DISP_HEIGHT=480) add_compile_definitions(PICO_DEFAULT_PIO_USB_DM_PIN=${PIN_USB_HOST_DM})
else()
add_compile_definitions(DISP_WIDTH=512) if (NOT UART STREQUAL "")
add_compile_definitions(DISP_HEIGHT=342) 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() endif()
add_compile_definitions(GPIO_VID_BASE=${VIDEO_PIN})
if (TARGET tinyusb_device) if (TARGET tinyusb_device)
add_executable(firmware add_executable(${FIRMWARE}
src/main.c src/main.c
src/video.c ${VIDEO_SRC}
src/kbd.c src/kbd.c
src/hid.c src/hid.c
${EXTRA_SD_SRC} ${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} ${UMAC_SOURCES}
) )
@ -115,9 +194,9 @@ if (TARGET tinyusb_device)
add_custom_target(prepare_umac add_custom_target(prepare_umac
DEPENDS ${UMAC_MUSASHI_PATH}/m68kops.c 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_stdlib
pico_multicore pico_multicore
tinyusb_host tinyusb_host
@ -126,22 +205,27 @@ if (TARGET tinyusb_device)
hardware_pio hardware_pio
hardware_sync hardware_sync
${EXTRA_SD_LIB} ${EXTRA_SD_LIB}
${EXTRA_AUDIO_LIB}
) )
target_include_directories(firmware PRIVATE target_include_directories(${FIRMWARE} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/include ${CMAKE_CURRENT_LIST_DIR}/include
${TINYUSB_PATH}/hw ${PICO_TINYUSB_PATH}/hw
${TINYUSB_PATH}/src ${PICO_TINYUSB_PATH}/src
${UMAC_INCLUDE_PATHS} ${UMAC_INCLUDE_PATHS}
${PIOUSB_PATH}/src
incbin 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: # Needed for UF2:
pico_add_extra_outputs(firmware) pico_add_extra_outputs(${FIRMWARE})
elseif(PICO_ON_DEVICE) elseif(PICO_ON_DEVICE)
message(WARNING "not building firmware because TinyUSB submodule is not initialized in the SDK") message(WARNING "not building firmware because TinyUSB submodule is not initialized in the SDK")

View file

@ -1,5 +1,37 @@
# Pico Micro Mac (pico-umac) # 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 v0.21 20 December 2024

1
external/pico-extras vendored Submodule

@ -0,0 +1 @@
Subproject commit c73f2f3a2b1ef65dc50113a22aadc5a262a92e0f

2
external/umac vendored

@ -1 +1 @@
Subproject commit 606f7f75fee8525d4735a0771bf521007115285e Subproject commit ce55830a1babd681d0e9a639c9b23f10cd9e3c96

21
fetch-rom-dsk.sh Executable file
View file

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

84
fruitjam-build.sh Executable file
View file

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

View file

@ -83,10 +83,16 @@
// max device support (excluding hub device) // max device support (excluding hub device)
#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports #define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports
#define CFG_TUH_RPI_PIO_USB 1
//------------- HID -------------// //------------- HID -------------//
#define CFG_TUH_HID_EPIN_BUFSIZE 64 #define CFG_TUH_HID_EPIN_BUFSIZE 64
#define CFG_TUH_HID_EPOUT_BUFSIZE 64 #define CFG_TUH_HID_EPOUT_BUFSIZE 64
#ifndef BOARD_TUH_RHPORT
#define BOARD_TUH_RHPORT 1
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

1
lib/Pico-PIO-USB Submodule

@ -0,0 +1 @@
Subproject commit 1862cc008e026cbd07b97b28e29eafb5f38b35fb

1
lib/tinyusb Submodule

@ -0,0 +1 @@
Subproject commit 5333d042f9384ec1cc0663a2708c0ca7cb5cdc32

View file

@ -24,7 +24,7 @@
* THE SOFTWARE. * THE SOFTWARE.
*/ */
#include "bsp/rp2040/board.h" #include "bsp/rp2040/boards/adafruit_fruit_jam/board.h"
#include "tusb.h" #include "tusb.h"
#include "kbd.h" #include "kbd.h"

View file

@ -40,7 +40,8 @@
#include "video.h" #include "video.h"
#include "kbd.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 "tusb.h"
#include "umac.h" #include "umac.h"
@ -52,6 +53,19 @@
#include "hw_config.h" #include "hw_config.h"
#endif #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 // Imports and data
@ -68,7 +82,16 @@ static const uint8_t umac_rom[] = {
#include "umac-rom.h" #include "umac-rom.h"
}; };
#if USE_PSRAM
#define umac_ram ((uint8_t*)0x11000000)
#else
static uint8_t umac_ram[RAM_SIZE]; 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) { if (absolute_time_diff_us(last, now) > 500*1000) {
last = now; last = now;
led_on ^= 1; //led_on ^= 1;
gpio_put(GPIO_LED_PIN, led_on); //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_y = 0;
static int umac_cursor_button = 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<DISP_HEIGHT; i++) {
uint32_t *dest = umac_framebuffer_mirror + (DISP_YOFFSET * LONGS_PER_OUTPUT_ROW + DISP_XOFFSET) + LONGS_PER_OUTPUT_ROW * i;
for(int j=0; j<LONGS_PER_INPUT_ROW; j++) {
*dest++ = *src++ ^ 0xffffffff;
}
}
#else
#error Unsupported display geometry for framebuffer mirroring
#endif
}
#endif
static void poll_umac() static void poll_umac()
{ {
static absolute_time_t last_1hz = 0; static absolute_time_t last_1hz = 0;
@ -106,7 +155,14 @@ static void poll_umac()
int64_t p_1hz = absolute_time_diff_us(last_1hz, now); int64_t p_1hz = absolute_time_diff_us(last_1hz, now);
int64_t p_vsync = absolute_time_diff_us(last_vsync, now); int64_t p_vsync = absolute_time_diff_us(last_vsync, now);
if (p_vsync >= 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 */ /* FIXME: Trigger this off actual vsync */
umac_vsync_event(); umac_vsync_event();
last_vsync = now; 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); printf(" *** Can't open %s: %s (%d)!\n", disc0_name, FRESULT_str(fr), fr);
goto no_sd; goto no_sd;
} else { } 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) if (read_only)
printf(" (disc is read-only)\n"); printf(" (disc is read-only)\n");
discs[0].base = 0; // Means use R/W ops 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 /* Video runs on core 1, i.e. IRQs/DMA are unaffected by
* core 0's USB activity. * 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())); 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"); printf("Enjoyable Mac times now begin:\n\n");
while (true) { 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() int main()
{ {
set_sys_clock_khz(250*1000, true); // set_sys_clock_khz(250*1000, true);
setup_psram();
stdio_init_all(); stdio_init_all();
io_init(); io_init();
#if ENABLE_AUDIO
audio_setup();
#endif
multicore_launch_core1(core1_main); multicore_launch_core1(core1_main);
printf("Starting, init usb\n"); 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: */ /* This happens on core 0: */
while (true) { while (true) {
@ -285,3 +512,305 @@ int main()
return 0; 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<SAMPLES_PER_BUFFER; i++) {
int32_t a = (*audiodata++ & 0xff) - offset;
a = (a * scale) >> 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

View file

@ -38,9 +38,13 @@ socket, which SPI it is driven by, and how it is wired.
// Hardware Configuration of SPI "objects" // Hardware Configuration of SPI "objects"
// Note: multiple SD cards can be driven by one SPI if they use different slave // Note: multiple SD cards can be driven by one SPI if they use different slave
// selects. // selects.
#ifndef SD_SPI_INST
#define SD_SPI_INST (spi0)
#endif
static spi_t spis[] = { // One for each SPI. 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) .miso_gpio = SD_RX, // GPIO number (not pin number)
.mosi_gpio = SD_TX, .mosi_gpio = SD_TX,
.sck_gpio = SD_SCK, .sck_gpio = SD_SCK,

309
src/video_hstx.c Normal file
View file

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