commit
3edf26ed1d
16 changed files with 1180 additions and 35 deletions
62
.github/workflows/build.yaml
vendored
Normal file
62
.github/workflows/build.yaml
vendored
Normal 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
13
.gitmodules
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
132
CMakeLists.txt
132
CMakeLists.txt
|
|
@ -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")
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -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
1
external/pico-extras
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit c73f2f3a2b1ef65dc50113a22aadc5a262a92e0f
|
||||||
2
external/umac
vendored
2
external/umac
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 606f7f75fee8525d4735a0771bf521007115285e
|
Subproject commit ce55830a1babd681d0e9a639c9b23f10cd9e3c96
|
||||||
21
fetch-rom-dsk.sh
Executable file
21
fetch-rom-dsk.sh
Executable 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
84
fruitjam-build.sh
Executable 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)
|
||||||
|
|
@ -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
1
lib/Pico-PIO-USB
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1862cc008e026cbd07b97b28e29eafb5f38b35fb
|
||||||
1
lib/tinyusb
Submodule
1
lib/tinyusb
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5333d042f9384ec1cc0663a2708c0ca7cb5cdc32
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
543
src/main.c
543
src/main.c
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
309
src/video_hstx.c
Normal 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();
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue