├── .gitmodules ├── CMakeLists.txt ├── README.md ├── include ├── hw.h ├── kbd.h ├── tusb_config.h └── video.h ├── pico_sdk_import.cmake └── src ├── hid.c ├── kbd.c ├── main.c ├── pio_video.pio ├── sd_hw_config.c └── video.c /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/umac"] 2 | url = https://github.com/evansm7/umac 3 | path = external/umac 4 | [submodule "external/no-OS-FatFS-SD-SPI-RPi-Pico"] 5 | path = external/no-OS-FatFS-SD-SPI-RPi-Pico 6 | url = https://github.com/evansm7/no-OS-FatFS-SD-SPI-RPi-Pico.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists 2 | # 3 | # MIT License 4 | # 5 | # Copyright (c) 2021, 2024 Matt Evans 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | # 25 | # 26 | cmake_minimum_required(VERSION 3.13) 27 | 28 | # Options that should be defined when initialising the build 29 | # directory with cmake, e.g. "cmake .. -DOPTION=true": 30 | # 31 | 32 | option(USE_SD "Build in SD support" OFF) 33 | set(SD_TX 3 CACHE STRING "SD SPI TX pin") 34 | set(SD_RX 4 CACHE STRING "SD SPI RX pin") 35 | set(SD_SCK 2 CACHE STRING "SD SPI SCK pin") 36 | set(SD_CS 5 CACHE STRING "SD SPI CS pin") 37 | set(SD_MHZ 5 CACHE STRING "SD SPI speed in MHz") 38 | option(USE_VGA_RES "Video uses VGA (640x480) resolution" OFF) 39 | set(VIDEO_PIN 18 CACHE STRING "Video GPIO base pin (followed by VS, CLK, HS)") 40 | 41 | # See below, -DMEMSIZE= will configure umac's memory size, 42 | # overriding defaults. 43 | 44 | # initialize the SDK based on PICO_SDK_PATH 45 | # note: this must happen before project() 46 | include(pico_sdk_import.cmake) 47 | 48 | project(firmware) 49 | 50 | # initialize the Raspberry Pi Pico SDK 51 | pico_sdk_init() 52 | 53 | # For TUSB host stuff: 54 | set(FAMILY rp2040) 55 | set(BOARD raspberry_pi_pico) 56 | 57 | set(TINYUSB_PATH ${PICO_SDK_PATH}/lib/tinyusb) 58 | 59 | # umac subproject (and Musashi sub-subproject) 60 | set(UMAC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/umac) 61 | set(UMAC_MUSASHI_PATH ${UMAC_PATH}/external/Musashi) 62 | set(UMAC_INCLUDE_PATHS ${UMAC_PATH}/include ${UMAC_MUSASHI_PATH}) 63 | 64 | # This isn't very nice, but hey it's Sunday :p 65 | set(UMAC_SOURCES 66 | ${UMAC_PATH}/src/disc.c 67 | ${UMAC_PATH}/src/main.c 68 | ${UMAC_PATH}/src/rom.c 69 | ${UMAC_PATH}/src/scc.c 70 | ${UMAC_PATH}/src/via.c 71 | ${UMAC_MUSASHI_PATH}/m68kcpu.c 72 | ${UMAC_MUSASHI_PATH}/m68kdasm.c 73 | ${UMAC_MUSASHI_PATH}/m68kops.c 74 | ${UMAC_MUSASHI_PATH}/softfloat/softfloat.c 75 | ) 76 | 77 | set(MEMSIZE 128 CACHE STRING "Memory size, in KB") 78 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\" -DUMAC_MEMSIZE=${MEMSIZE}") 79 | 80 | if (USE_SD) 81 | add_compile_definitions(USE_SD=1) 82 | set(FF_DISABLE_RTC ${PICO_RP2350}) # RP2350 doesn't have RTC, so disable it 83 | add_subdirectory(external/no-OS-FatFS-SD-SPI-RPi-Pico/FatFs_SPI build) 84 | set(EXTRA_SD_SRC src/sd_hw_config.c) 85 | set(EXTRA_SD_LIB FatFs_SPI) 86 | add_compile_definitions(SD_TX=${SD_TX} SD_RX=${SD_RX} SD_SCK=${SD_SCK} SD_CS=${SD_CS} SD_MHZ=${SD_MHZ}) 87 | endif() 88 | 89 | if (USE_VGA_RES) 90 | add_compile_definitions(USE_VGA_RES=1) 91 | add_compile_definitions(DISP_WIDTH=640) 92 | add_compile_definitions(DISP_HEIGHT=480) 93 | else() 94 | add_compile_definitions(DISP_WIDTH=512) 95 | add_compile_definitions(DISP_HEIGHT=342) 96 | endif() 97 | add_compile_definitions(GPIO_VID_BASE=${VIDEO_PIN}) 98 | 99 | if (TARGET tinyusb_device) 100 | add_executable(firmware 101 | src/main.c 102 | src/video.c 103 | src/kbd.c 104 | src/hid.c 105 | ${EXTRA_SD_SRC} 106 | 107 | ${UMAC_SOURCES} 108 | ) 109 | 110 | # The umac sources need to prepare Musashi (some sources are generated): 111 | add_custom_command(OUTPUT ${UMAC_MUSASHI_PATH}/m68kops.c 112 | COMMAND echo "*** Preparing umac source ***" 113 | COMMAND make -C ${UMAC_PATH} prepare 114 | ) 115 | add_custom_target(prepare_umac 116 | DEPENDS ${UMAC_MUSASHI_PATH}/m68kops.c 117 | ) 118 | add_dependencies(firmware prepare_umac) 119 | 120 | target_link_libraries(firmware 121 | pico_stdlib 122 | pico_multicore 123 | tinyusb_host 124 | tinyusb_board 125 | hardware_dma 126 | hardware_pio 127 | hardware_sync 128 | ${EXTRA_SD_LIB} 129 | ) 130 | 131 | target_include_directories(firmware PRIVATE 132 | ${CMAKE_CURRENT_LIST_DIR}/include 133 | ${TINYUSB_PATH}/hw 134 | ${TINYUSB_PATH}/src 135 | ${UMAC_INCLUDE_PATHS} 136 | incbin 137 | ) 138 | 139 | pico_generate_pio_header(firmware ${CMAKE_CURRENT_LIST_DIR}/src/pio_video.pio) 140 | 141 | pico_enable_stdio_uart(firmware 1) 142 | 143 | # Needed for UF2: 144 | pico_add_extra_outputs(firmware) 145 | 146 | elseif(PICO_ON_DEVICE) 147 | message(WARNING "not building firmware because TinyUSB submodule is not initialized in the SDK") 148 | endif() 149 | 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pico Micro Mac (pico-umac) 2 | 3 | v0.21 20 December 2024 4 | 5 | 6 | This project embeds the [umac Mac 128K 7 | emulator](https://github.com/evansm7/umac) project into a Raspberry Pi 8 | Pico microcontroller. At long last, the worst Macintosh in a cheap, 9 | portable form factor! 10 | 11 | It has features, many features, the best features: 12 | 13 | * Outputs VGA 640x480@60Hz, monochrome, using three resistors 14 | * USB HID keyboard and mouse 15 | * Read-only disc image in flash (your creations are ephemeral, like life itself) 16 | * Or, if you have a hard time letting go, support for rewritable 17 | disc storage on an SPI-attached SD card 18 | * Mac 128K by default, or you can make use of more of the Pico's 19 | memory and run as a _Mac 208K_ 20 | * Since you now have more memory, you can splash out on more 21 | screen real-estate, and use 640x480 resolution! 22 | 23 | Great features. It even doesn't hang at random! (Anymore.) 24 | 25 | The _Mac 208K_ was, of course, never a real machine. But, _umac_ 26 | supports odd-sized memories, and more memory runs more things. A 27 | surprising amount of software runs on the 128K config, but if you need 28 | to run _MacPaint_ specifically then you'll need to build both SD 29 | storage in addition to the _Mac 208K_ config. 30 | 31 | So anyway, you can build this project yourself for less than the cost 32 | of a beer! You'll need at least a RPi Pico board, a VGA monitor (or 33 | VGA-HDMI adapter), a USB mouse (and maybe a USB keyboard/hub), plus a 34 | couple of cheap components. 35 | 36 | # Build 37 | 38 | ## Prerequisites/essentials 39 | 40 | * git submodules 41 | - Clone the repo with `--recursive`, or `git submodule update --init --recursive` 42 | * Install/set up the [Pico/RP2040 SDK](https://github.com/raspberrypi/pico-sdk) 43 | 44 | ## Build umac 45 | 46 | Install and build `umac` first. It'll give you a preview of the fun 47 | to come, plus is required to generate a patched ROM image. 48 | 49 | If you want to use a non-default memory size (i.e. >128K) you will 50 | need to build `umac` with a matching `MEMSIZE` build parameter, for 51 | example: 52 | 53 | ``` 54 | cd external/umac 55 | make MEMSIZE=208 56 | ``` 57 | 58 | This is because `umac` is used to patch the ROM, and when using 59 | unsupported sizes between 128K and 512K the RAM size can't be probed 60 | automatically, so the size needs to be embedded. 61 | 62 | This is also the case for altering the video resolution, because the ROM 63 | must be patched for this. Build `umac` with `DISP_WIDTH=640 DISP_HEIGHT=480` 64 | when you intend to use the `USE_VGA_RES` option. For example: 65 | 66 | ``` 67 | cd external/umac 68 | make MEMSIZE=208 DISP_WIDTH=640 DISP_HEIGHT=480 69 | ``` 70 | 71 | ## Build pico-umac 72 | 73 | Do the initial Pico SDK `cmake` setup into an out-of-tree build dir, 74 | providing config options if required. 75 | 76 | From the top-level `pico-umac` directory: 77 | 78 | ``` 79 | mkdir build 80 | (cd build ; PICO_SDK_PATH=/path/to/sdk cmake .. ) 81 | ``` 82 | 83 | Options are required if you want SD support, more than the default 128K of memory, 84 | higher resolution, to change pin configs, etc.: 85 | 86 | * `-DUSE_SD=true`: Include SD card support. The GPIOs default to 87 | `spi0` running at 5MHz, and GPIOs 2,3,4,5 for 88 | `SCK`/`TX`/`RX`/`CS` respectively. These can be overridden for 89 | your board/setup: 90 | - `-DSD_TX=` 91 | - `-DSD_RX=` 92 | - `-DSD_SCK=` 93 | - `-DSD_CS=` 94 | - `-DSD_MHZ=` 95 | * `-DMEMSIZE=`: The maximum practical size is about 96 | 208KB, but values between 128 and 208 should work on a RP2040. 97 | Note that although apps and Mac OS seem to gracefully detect free 98 | memory, these products never existed and some apps might behave 99 | strangely. 100 | - With the `Mac Plus` ROM, a _Mac 128K_ doesn't quite have 101 | enough memory to run _MacPaint_. So, 192 or 208 (and a 102 | writeable boot volume on SD) will allow _MacPaint_ to run. 103 | - **NOTE**: When this option is used, the ROM image must be 104 | built with an `umac` build with a corresponding `MEMSIZE` 105 | * `-DUSE_VGA_RES=1`: Use 640x480 screen resolution instead of the 106 | native 512x342. This uses an additional 16KB of RAM, so this 107 | option makes a _Mac 128K_ configuration virtually unusable. 108 | It is recommended only to use this when configuring >208K 109 | using the option above. 110 | * `-DVIDEO_PIN=`: Move the video output pins; defaults 111 | to the pinout shown below. 112 | 113 | Tip: `cmake` caches these variables, so if you see weird behaviour 114 | having built previously and then changed an option, delete the `build` 115 | directory and start again. 116 | 117 | ## ROM image 118 | 119 | The flow is to use `umac` built on your workstation (e.g. Linux, 120 | but WSL may work too) to prepare a patched ROM image. 121 | 122 | `umac` is passed the 4D1F8172 MacPlusv3 ROM, and `-W` to write the 123 | post-patching binary out: 124 | 125 | ``` 126 | ./external/umac/main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin 127 | ``` 128 | 129 | Note: Again, remember that if you are using the `-DMEMSIZE` option to 130 | increase the `pico-umac` memory, or the `-DUSE_VGA_RES` option to 131 | increase the `pico-umac` screen resolution, you will need to create 132 | this ROM image with a `umac` built with the corresponding 133 | `MEMSIZE`/`DISP_WIDTH`/`DISP_HEIGHT` options, as above. 134 | 135 | ## Disc image 136 | 137 | If you don't build SD support, an internal read-only disc image is 138 | stored in flash. If you do build SD support, you have the option to 139 | still include an image in flash, and this is used as a fallback if 140 | SD boot fails. 141 | 142 | Grab a Macintosh system disc from somewhere. A 400K or 800K floppy 143 | image works just fine, up to System 3.2 (the last version to support 144 | Mac128Ks). I've used images from 145 | but also check 146 | the various forums and MacintoshRepository. See the `umac` README for 147 | info on formats (it needs to be raw data without header). 148 | 149 | The image size can be whatever you have space for in flash (typically 150 | about 1.3MB is free there), or on the SD card. (I don't know what the 151 | HFS limits are. But if you make a 50MB disc you're unlikely to fill 152 | it with software that actually works on the _Mac 128K_ :) ) 153 | 154 | If using an SD card, use a FAT-formatted card and copy your disc image 155 | into _one_ of the following files in the root of the card: 156 | 157 | * `umac0.img`: A normal read/write disc image 158 | * `umac0ro.img`: A read-only disc image 159 | 160 | ## Putting it together, and building 161 | 162 | Given the `rom.bin` prepared above and a `disc.bin` destinated for 163 | flash, you can now generate includes from them and perform the build: 164 | 165 | ``` 166 | mkdir incbin 167 | xxd -i < rom.bin > incbin/umac-rom.h 168 | 169 | # When using an internal disc image: 170 | xxd -i < disc.bin > incbin/umac-disc.h 171 | # OR, if using SD and if you do _not_ want an internal image: 172 | echo > incbin/umac-disc.h 173 | 174 | make -C build 175 | ``` 176 | 177 | You'll get a `build/firmware.uf2` out the other end. Flash this to 178 | your Pico: e.g. plug it in with button held/drag/drop. (When 179 | iterating/testing during development, unplugging the OTG cable each 180 | time is a pain – I ended up moving to SWD probe programming.) 181 | 182 | The LED should flash at about 2Hz once powered up. 183 | 184 | # Hardware contruction 185 | 186 | It's a simple circuit in terms of having few components: just the 187 | Pico, with three series resistors and a VGA connection, and DC power. 188 | However, if you're not comfortable soldering then don't choose this as 189 | your first project: I don't want you to zap your mouse, keyboard, 190 | monitor, SD cards... 191 | 192 | Disclaimer: This is a hardware project with zero warranty. All due 193 | care has been taken in design/docs, but if you choose to build it then 194 | I disclaim any responsibility for your hardware or personal safety. 195 | 196 | With that out of the way... 197 | 198 | ## Theory of operation 199 | 200 | Three 3.3V GPIO pins are driven by PIO to give VSYNC, HSYNC, and video 201 | out signals. 202 | 203 | The syncs are in many similar projects driven directly from GPIO, but 204 | here I suggest a 66Ω series resistor on each in order to keep the 205 | voltages at the VGA end (presumably into 75Ω termination?) in the 206 | correct range. 207 | 208 | For the video output, one GPIO drives R,G,B channels for mono/white 209 | output. A 100Ω resistor gives roughly 0.7V (max intensity) into 3*75Ω 210 | signals. 211 | 212 | That's it... power in, USB adapter. 213 | 214 | ## Pinout and circuit 215 | 216 | Parts needed: 217 | 218 | * Pico/RP2040 board 219 | * USB OTG micro-B to A adapter 220 | * USB keyboard, mouse (and hub, if not integrated) 221 | * 5V DC supply (600mA+), and maybe a DC jack 222 | * 100Ω resistor 223 | * 2x 66Ω resistors 224 | * VGA DB15 connector, or janky chopped VGA cable 225 | * (optional) SD card breakout, SD card 226 | 227 | If you want to get fancy with an SD card, you will need some kind of 228 | SD card SPI breakout adapter. (There are a lot of these around, but 229 | many seem to have a buffer/level-converter for 5V operation. Find one 230 | without, or modify your adapter for a 3.3V supply. Doing so, and 231 | finding an SD card that works well with SPI is out of scope of this 232 | doc.) 233 | 234 | Pins are given for a RPi Pico board, but this will work on any RP2040 235 | board with 2MB+ flash as long as all required GPIOs are pinned out: 236 | 237 | | GPIO/pin | Pico pin | Usage | 238 | | ------------ | ------------ | -------------- | 239 | | GP0 | 1 | UART0 TX | 240 | | GP1 | 2 | UART0 RX | 241 | | GP18 | 24 | Video output % | 242 | | GP19 | 25 | VSYNC | 243 | | GP21 | 27 | HSYNC | 244 | | Gnd | 23, 28 | Video ground | 245 | | VBUS (5V) | 40 | +5V supply | 246 | | Gnd | 38 | Supply ground | 247 | 248 | %: The video pins default here, but can be moved by building with the 249 | `-DVIDEO_PIN` option. This sets the position of the Video pin, 250 | which is immediately followed by VSYNC, then a gap, then HSYNC. 251 | For example, `-DVIDEO_PIN=20` configures the Video pin at 20, 252 | VSYNC at 21, HSYNC at 23. 253 | 254 | Method: 255 | 256 | * Wire 5V supply to VBUS/Gnd 257 | * Video output --> 100Ω --> VGA RGB (pins 1,2,3) all connected together 258 | * HSYNC --> 66Ω --> VGA pin 13 259 | * VSYNC --> 66Ω --> VGA pin 14 260 | * Video ground --> VGA grounds (pins 5-8, 10) 261 | 262 | If you don't have exactly 100Ω, using slightly more is OK but display 263 | will be dimmer. If you don't have 66Ω for the syncs, connecting them 264 | directly is "probably OK", but YMMV. 265 | 266 | If you are including an SD card, the default pinout is as follows 267 | (this can be changed at build time, above): 268 | 269 | | GPIO/pin | Pico pin | Usage | 270 | | ------------ | ------------ | -------------- | 271 | | GP2 | 4 | SPI0 SCK | 272 | | GP3 | 5 | SPI0 TX (MOSI) | 273 | | GP4 | 6 | SPI0 RX (MISO) | 274 | | GP5 | 7 | SPI0 /CS | 275 | 276 | (The SD card needs a good ground, e.g. Pico pin 8 nearby, and 3.3V 277 | supply from Pico pin 36.) 278 | 279 | If your SD breakout board is "raw", i.e. has no buffer or series 280 | resistors on-board, you may find adding a 66Ω resistor in series on 281 | all of the four signal lines will help. Supply decoupling caps will 282 | also be important (e.g. 1uF+0.1uF) to keep the SD card happy. _Keep 283 | SD card wiring short._ The default SPI clock (5MHz) is 284 | conservative/slow, but I suggest verifying the circuit/SD card works 285 | before increasing it. 286 | 287 | Test your connections: the key part is not getting over 0.7V into your 288 | VGA connector's signals, or shorting SD card pins. 289 | 290 | Connect USB mouse, and keyboard if you like, and power up. 291 | 292 | # Software 293 | 294 | Both CPU cores are used, and are overclocked (blush) to 250MHz so that 295 | Missile Command is enjoyable to play. 296 | 297 | The `umac` emulator and video output runs on core 1, and core 0 deals 298 | with USB HID input. Video DMA is initialised pointing to the 299 | framebuffer in the Mac's RAM. 300 | 301 | Other than that, it's just a main loop in `main.c` shuffling things 302 | into `umac`. 303 | 304 | Quite a lot of optimisation has been done in `umac` and `Musashi` to 305 | get performance up on Cortex-M0+ and the RP2040, like careful location 306 | of certain routines in RAM, ensuring inlining/constants can be 307 | foldeed, etc. It's 5x faster than it was at the beginning. 308 | 309 | The top-level project might be a useful framework for other emulators, 310 | or other projects that need USB HID input and a framebuffer (e.g. a 311 | VT220 emulator!). 312 | 313 | The USB HID code is largely stolen from the TinyUSB example, but shows 314 | how in practice you might capture keypresses/deal with mouse events. 315 | 316 | ## Video 317 | 318 | The video system is pretty good and IMHO worth stealing for other 319 | projects: It uses one PIO state machine and 3 DMA channels to provide 320 | a rock-solid bitmapped 1BPP 640x480 video output. The Mac 512x342 321 | framebuffer is centred inside this by using horizontal blanking 322 | regions (programmed into the line scan-out) and vertical blanking 323 | areas from a dummy "always black" mini-framebuffer. 324 | 325 | It supports (at build time) flexible resolutions/timings. The one 326 | caveat (or advantage?) is that it uses an HSYNC IRQ routine to 327 | recalculate the next DMA buffer pointer; doing this at scan-time costs 328 | about 1% of the CPU time (on core 1). However, it could be used to 329 | generate video on-the-fly from characters/tiles without a true 330 | framebuffer. 331 | 332 | I'm considering improvements to the video system: 333 | 334 | * Supporting multiple BPP/colour output 335 | * Implement the rest of `DE`/display valid strobe support, making 336 | driving LCDs possible. 337 | * Using a video DMA address list and another DMA channel to reduce 338 | the IRQ frequency (CPU overhead) to per-frame, at the cost of a 339 | couple of KB of RAM. 340 | 341 | 342 | # Licence 343 | 344 | `hid.c` and `tusb_config.h` are based on code from the TinyUSB 345 | project, which is Copyright (c) 2019, 2021 Ha Thach (tinyusb.org) and 346 | released under the MIT licence. `sd_hw_config.c` is based on code 347 | from the no-OS-FatFS-SD-SPI-RPi-Pico project, which is Copyright (c) 348 | 2021 Carl John Kugler III. 349 | 350 | The remainder of the code is released under the MIT licence: 351 | 352 | Copyright (c) 2024 Matt Evans: 353 | 354 | Permission is hereby granted, free of charge, to any person obtaining a copy 355 | of this software and associated documentation files (the "Software"), to deal 356 | in the Software without restriction, including without limitation the rights 357 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 358 | copies of the Software, and to permit persons to whom the Software is 359 | furnished to do so, subject to the following conditions: 360 | 361 | The above copyright notice and this permission notice shall be included in all 362 | copies or substantial portions of the Software. 363 | 364 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 365 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 366 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 367 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 368 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 369 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 370 | SOFTWARE. 371 | 372 | -------------------------------------------------------------------------------- /include/hw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pico-umac pin definitions 3 | * 4 | * Copyright 2024 Matt Evans 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation files 8 | * (the "Software"), to deal in the Software without restriction, 9 | * including without limitation the rights to use, copy, modify, merge, 10 | * publish, distribute, sublicense, and/or sell copies of the Software, 11 | * and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #ifndef HW_H 28 | #define HW_H 29 | 30 | #define GPIO_LED_PIN PICO_DEFAULT_LED_PIN 31 | 32 | #define GPIO_VID_DATA GPIO_VID_BASE 33 | #define GPIO_VID_VS (GPIO_VID_DATA + 1) 34 | #define GPIO_VID_CLK (GPIO_VID_VS + 1) 35 | #define GPIO_VID_HS (GPIO_VID_CLK + 1) 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /include/kbd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pico-umac keyboard scancode mapping 3 | * 4 | * Copyright 2024 Matt Evans 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation files 8 | * (the "Software"), to deal in the Software without restriction, 9 | * including without limitation the rights to use, copy, modify, merge, 10 | * publish, distribute, sublicense, and/or sell copies of the Software, 11 | * and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #ifndef KBD_H 28 | #define KBD_H 29 | 30 | #include 31 | #include 32 | 33 | bool kbd_queue_empty(); 34 | /* If empty, return 0, else return a mac keycode in [7:0] and [15] set if a press (else release) */ 35 | uint16_t kbd_queue_pop(); 36 | 37 | /* FIXME: map modifiers */ 38 | bool kbd_queue_push(uint8_t hid_keycode, bool pressed); 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /include/tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //-------------------------------------------------------------------- 34 | // COMMON CONFIGURATION 35 | //-------------------------------------------------------------------- 36 | 37 | // defined by compiler flags for flexibility 38 | #ifndef CFG_TUSB_MCU 39 | #error CFG_TUSB_MCU must be defined 40 | #endif 41 | 42 | #if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX 43 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST | OPT_MODE_HIGH_SPEED) 44 | #else 45 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_HOST 46 | #endif 47 | 48 | #ifndef CFG_TUSB_OS 49 | #define CFG_TUSB_OS OPT_OS_NONE 50 | #endif 51 | 52 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 53 | // #define CFG_TUSB_DEBUG 0 54 | 55 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 56 | * Tinyusb use follows macros to declare transferring memory so that they can be put 57 | * into those specific section. 58 | * e.g 59 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 60 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 61 | */ 62 | #ifndef CFG_TUSB_MEM_SECTION 63 | #define CFG_TUSB_MEM_SECTION 64 | #endif 65 | 66 | #ifndef CFG_TUSB_MEM_ALIGN 67 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 68 | #endif 69 | 70 | //-------------------------------------------------------------------- 71 | // CONFIGURATION 72 | //-------------------------------------------------------------------- 73 | 74 | // Size of buffer to hold descriptors and other data used for enumeration 75 | #define CFG_TUH_ENUMERATION_BUFSIZE 256 76 | 77 | #define CFG_TUH_HUB 1 78 | #define CFG_TUH_CDC 0 79 | #define CFG_TUH_HID 4 // typical keyboard + mouse device can have 3-4 HID interfaces 80 | #define CFG_TUH_MSC 0 81 | #define CFG_TUH_VENDOR 0 82 | 83 | // max device support (excluding hub device) 84 | #define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports 85 | 86 | //------------- HID -------------// 87 | #define CFG_TUH_HID_EPIN_BUFSIZE 64 88 | #define CFG_TUH_HID_EPOUT_BUFSIZE 64 89 | 90 | #ifdef __cplusplus 91 | } 92 | #endif 93 | 94 | #endif /* _TUSB_CONFIG_H_ */ 95 | -------------------------------------------------------------------------------- /include/video.h: -------------------------------------------------------------------------------- 1 | /* PIO video output 2 | * 3 | * Copyright 2024 Matt Evans 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation files 7 | * (the "Software"), to deal in the Software without restriction, 8 | * including without limitation the rights to use, copy, modify, merge, 9 | * publish, distribute, sublicense, and/or sell copies of the Software, 10 | * and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be 14 | * included in all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | #ifndef VIDEO_H 27 | #define VIDEO_H 28 | 29 | #include 30 | 31 | void video_init(uint32_t *framebuffer); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | FetchContent_Declare( 33 | pico_sdk 34 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 35 | GIT_TAG master 36 | ) 37 | if (NOT pico_sdk) 38 | message("Downloading Raspberry Pi Pico SDK") 39 | FetchContent_Populate(pico_sdk) 40 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 41 | endif () 42 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 43 | else () 44 | message(FATAL_ERROR 45 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 46 | ) 47 | endif () 48 | endif () 49 | 50 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 51 | if (NOT EXISTS ${PICO_SDK_PATH}) 52 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 53 | endif () 54 | 55 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 56 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 57 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 58 | endif () 59 | 60 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 61 | 62 | include(${PICO_SDK_INIT_CMAKE_FILE}) 63 | -------------------------------------------------------------------------------- /src/hid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Derived from pico-examples/usb/host/host_cdc_msc_hid/hid_app.c, which is 3 | * Copyright (c) 2021, Ha Thach (tinyusb.org) 4 | * Further changes are Copyright 2024 Matt Evans 5 | * 6 | * The MIT License (MIT) 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include "bsp/rp2040/board.h" 28 | #include "tusb.h" 29 | 30 | #include "kbd.h" 31 | 32 | //--------------------------------------------------------------------+ 33 | // MACRO TYPEDEF CONSTANT ENUM DECLARATION 34 | //--------------------------------------------------------------------+ 35 | 36 | // If your host terminal support ansi escape code such as TeraTerm 37 | // it can be use to simulate mouse cursor movement within terminal 38 | #define USE_ANSI_ESCAPE 0 39 | 40 | #define MAX_REPORT 4 41 | 42 | static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII }; 43 | 44 | // Each HID instance can has multiple reports 45 | static struct 46 | { 47 | uint8_t report_count; 48 | tuh_hid_report_info_t report_info[MAX_REPORT]; 49 | } hid_info[CFG_TUH_HID]; 50 | 51 | static void process_kbd_report(hid_keyboard_report_t const *report); 52 | static void process_mouse_report(hid_mouse_report_t const * report); 53 | static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len); 54 | 55 | void hid_app_task(void) 56 | { 57 | // nothing to do 58 | } 59 | 60 | //--------------------------------------------------------------------+ 61 | // TinyUSB Callbacks 62 | //--------------------------------------------------------------------+ 63 | 64 | // Invoked when device with hid interface is mounted 65 | // Report descriptor is also available for use. tuh_hid_parse_report_descriptor() 66 | // can be used to parse common/simple enough descriptor. 67 | // Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped 68 | // therefore report_desc = NULL, desc_len = 0 69 | void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) 70 | { 71 | printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance); 72 | 73 | // Interface protocol (hid_interface_protocol_enum_t) 74 | const char* protocol_str[] = { "None", "Keyboard", "Mouse" }; 75 | uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); 76 | 77 | printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]); 78 | 79 | // By default host stack will use activate boot protocol on supported interface. 80 | // Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser) 81 | if ( itf_protocol == HID_ITF_PROTOCOL_NONE ) 82 | { 83 | hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len); 84 | printf("HID has %u reports \r\n", hid_info[instance].report_count); 85 | } 86 | 87 | // request to receive report 88 | // tuh_hid_report_received_cb() will be invoked when report is available 89 | if ( !tuh_hid_receive_report(dev_addr, instance) ) 90 | { 91 | printf("Error: cannot request to receive report\r\n"); 92 | } 93 | } 94 | 95 | // Invoked when device with hid interface is un-mounted 96 | void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) 97 | { 98 | printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance); 99 | } 100 | 101 | // Invoked when received report from device via interrupt endpoint 102 | void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) 103 | { 104 | uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance); 105 | 106 | switch (itf_protocol) 107 | { 108 | case HID_ITF_PROTOCOL_KEYBOARD: 109 | TU_LOG2("HID receive boot keyboard report\r\n"); 110 | process_kbd_report( (hid_keyboard_report_t const*) report ); 111 | break; 112 | 113 | case HID_ITF_PROTOCOL_MOUSE: 114 | TU_LOG2("HID receive boot mouse report\r\n"); 115 | process_mouse_report( (hid_mouse_report_t const*) report ); 116 | break; 117 | 118 | default: 119 | // Generic report requires matching ReportID and contents with previous parsed report info 120 | process_generic_report(dev_addr, instance, report, len); 121 | break; 122 | } 123 | 124 | // continue to request to receive report 125 | if ( !tuh_hid_receive_report(dev_addr, instance) ) 126 | { 127 | printf("Error: cannot request to receive report\r\n"); 128 | } 129 | } 130 | 131 | //--------------------------------------------------------------------+ 132 | // Keyboard 133 | //--------------------------------------------------------------------+ 134 | 135 | static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode) 136 | { 137 | for(uint8_t i=0; i<6; i++) 138 | { 139 | if (report->keycode[i] == keycode) return true; 140 | } 141 | 142 | return false; 143 | } 144 | 145 | static void process_kbd_report(hid_keyboard_report_t const *report) 146 | { 147 | /* Previous report is stored to compare against for key release: */ 148 | static hid_keyboard_report_t prev_report = { 0, 0, {0} }; 149 | 150 | for(uint8_t i=0; i<6; i++) { 151 | if (report->keycode[i]) { 152 | if (find_key_in_report(&prev_report, report->keycode[i])) { 153 | /* Key held */ 154 | } else { 155 | /* printf("Key pressed: %02x\n", report->keycode[i]); */ 156 | kbd_queue_push(report->keycode[i], true); 157 | } 158 | } 159 | if (prev_report.keycode[i] && !find_key_in_report(report, prev_report.keycode[i])) { 160 | /* printf("Key released: %02x\n", prev_report.keycode[i]); */ 161 | kbd_queue_push(prev_report.keycode[i], false); 162 | } 163 | } 164 | uint8_t mod_change = report->modifier ^ prev_report.modifier; 165 | if (mod_change) { 166 | uint8_t mp = mod_change & report->modifier; 167 | uint8_t mr = mod_change & prev_report.modifier; 168 | if (mp) { 169 | /* printf("Modifiers pressed %02x\n", mp); */ 170 | mp = (mp | (mp >> 4)) & 0xf; /* Don't care if left or right :P */ 171 | if (mp & 1) 172 | kbd_queue_push(HID_KEY_CONTROL_LEFT, true); 173 | if (mp & 2) 174 | kbd_queue_push(HID_KEY_SHIFT_LEFT, true); 175 | if (mp & 4) 176 | kbd_queue_push(HID_KEY_ALT_LEFT, true); 177 | if (mp & 8) 178 | kbd_queue_push(HID_KEY_GUI_LEFT, true); 179 | } 180 | if (mr) { 181 | /* printf("Modifiers released %02x\n", mr); */ 182 | mr = (mr | (mr >> 4)) & 0xf; 183 | if (mr & 1) 184 | kbd_queue_push(HID_KEY_CONTROL_LEFT, false); 185 | if (mr & 2) 186 | kbd_queue_push(HID_KEY_SHIFT_LEFT, false); 187 | if (mr & 4) 188 | kbd_queue_push(HID_KEY_ALT_LEFT, false); 189 | if (mr & 8) 190 | kbd_queue_push(HID_KEY_GUI_LEFT, false); 191 | } 192 | } 193 | prev_report = *report; 194 | } 195 | 196 | //--------------------------------------------------------------------+ 197 | // Mouse 198 | //--------------------------------------------------------------------+ 199 | 200 | /* Exported for use by other thread! */ 201 | int cursor_x = 0; 202 | int cursor_y = 0; 203 | int cursor_button = 0; 204 | 205 | #define MAX_DELTA 8 206 | 207 | static int clamp(int i) 208 | { 209 | return (i >= 0) ? (i > MAX_DELTA ? MAX_DELTA : i) : 210 | (i < -MAX_DELTA ? -MAX_DELTA : i); 211 | } 212 | 213 | static void process_mouse_report(hid_mouse_report_t const * report) 214 | { 215 | static hid_mouse_report_t prev_report = { 0 }; 216 | uint8_t button_changed_mask = report->buttons ^ prev_report.buttons; 217 | /* report->wheel can be used too... */ 218 | 219 | cursor_button = !!(report->buttons & MOUSE_BUTTON_LEFT); 220 | cursor_x += clamp(report->x); 221 | cursor_y += clamp(report->y); 222 | } 223 | 224 | //--------------------------------------------------------------------+ 225 | // Generic Report 226 | //--------------------------------------------------------------------+ 227 | static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) 228 | { 229 | (void) dev_addr; 230 | 231 | uint8_t const rpt_count = hid_info[instance].report_count; 232 | tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info; 233 | tuh_hid_report_info_t* rpt_info = NULL; 234 | 235 | if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0) 236 | { 237 | // Simple report without report ID as 1st byte 238 | rpt_info = &rpt_info_arr[0]; 239 | }else 240 | { 241 | // Composite report, 1st byte is report ID, data starts from 2nd byte 242 | uint8_t const rpt_id = report[0]; 243 | 244 | // Find report id in the arrray 245 | for(uint8_t i=0; iusage_page == HID_USAGE_PAGE_DESKTOP ) 272 | { 273 | switch (rpt_info->usage) 274 | { 275 | case HID_USAGE_DESKTOP_KEYBOARD: 276 | TU_LOG1("HID receive keyboard report\r\n"); 277 | // Assume keyboard follow boot report layout 278 | process_kbd_report( (hid_keyboard_report_t const*) report ); 279 | break; 280 | 281 | case HID_USAGE_DESKTOP_MOUSE: 282 | TU_LOG1("HID receive mouse report\r\n"); 283 | // Assume mouse follow boot report layout 284 | process_mouse_report( (hid_mouse_report_t const*) report ); 285 | break; 286 | 287 | default: break; 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/kbd.c: -------------------------------------------------------------------------------- 1 | /* HID to Mac keyboard scancode mapping 2 | * 3 | * FIXME: This doesn't do capslock (needs to track toggle), and arrow 4 | * keys don't work. 5 | * 6 | * Copyright 2024 Matt Evans 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation files 10 | * (the "Software"), to deal in the Software without restriction, 11 | * including without limitation the rights to use, copy, modify, merge, 12 | * publish, distribute, sublicense, and/or sell copies of the Software, 13 | * and to permit persons to whom the Software is furnished to do so, 14 | * subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 23 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #include 30 | #include "kbd.h" 31 | 32 | #include "class/hid/hid.h" 33 | #include "keymap.h" 34 | 35 | #define KQ_SIZE 32 36 | #define KQ_MASK (KQ_SIZE-1) 37 | 38 | static uint16_t kbd_queue[KQ_SIZE]; 39 | static unsigned int kbd_queue_prod = 0; 40 | static unsigned int kbd_queue_cons = 0; 41 | 42 | static bool kbd_queue_full() 43 | { 44 | return ((kbd_queue_prod + 1) & KQ_MASK) == kbd_queue_cons; 45 | } 46 | 47 | 48 | bool kbd_queue_empty() 49 | { 50 | return kbd_queue_prod == kbd_queue_cons; 51 | } 52 | 53 | /* If empty, return 0, else return a mac keycode in [7:0] and [15] set if a press (else release) */ 54 | uint16_t kbd_queue_pop() 55 | { 56 | if (kbd_queue_empty()) 57 | return 0; 58 | uint16_t v = kbd_queue[kbd_queue_cons]; 59 | kbd_queue_cons = (kbd_queue_cons + 1) & KQ_MASK; 60 | return v; 61 | } 62 | 63 | static const uint8_t hid_to_mac[256] = { 64 | [HID_KEY_NONE] = 0, 65 | [HID_KEY_A] = 255, // Hack for MKC_A, 66 | [HID_KEY_B] = MKC_B, 67 | [HID_KEY_C] = MKC_C, 68 | [HID_KEY_D] = MKC_D, 69 | [HID_KEY_E] = MKC_E, 70 | [HID_KEY_F] = MKC_F, 71 | [HID_KEY_G] = MKC_G, 72 | [HID_KEY_H] = MKC_H, 73 | [HID_KEY_I] = MKC_I, 74 | [HID_KEY_J] = MKC_J, 75 | [HID_KEY_K] = MKC_K, 76 | [HID_KEY_L] = MKC_L, 77 | [HID_KEY_M] = MKC_M, 78 | [HID_KEY_N] = MKC_N, 79 | [HID_KEY_O] = MKC_O, 80 | [HID_KEY_P] = MKC_P, 81 | [HID_KEY_Q] = MKC_Q, 82 | [HID_KEY_R] = MKC_R, 83 | [HID_KEY_S] = MKC_S, 84 | [HID_KEY_T] = MKC_T, 85 | [HID_KEY_U] = MKC_U, 86 | [HID_KEY_V] = MKC_V, 87 | [HID_KEY_W] = MKC_W, 88 | [HID_KEY_X] = MKC_X, 89 | [HID_KEY_Y] = MKC_Y, 90 | [HID_KEY_Z] = MKC_Z, 91 | [HID_KEY_1] = MKC_1, 92 | [HID_KEY_2] = MKC_2, 93 | [HID_KEY_3] = MKC_3, 94 | [HID_KEY_4] = MKC_4, 95 | [HID_KEY_5] = MKC_5, 96 | [HID_KEY_6] = MKC_6, 97 | [HID_KEY_7] = MKC_7, 98 | [HID_KEY_8] = MKC_8, 99 | [HID_KEY_9] = MKC_9, 100 | [HID_KEY_0] = MKC_0, 101 | [HID_KEY_ENTER] = MKC_Return, 102 | [HID_KEY_ESCAPE] = MKC_Escape, 103 | [HID_KEY_BACKSPACE] = MKC_BackSpace, 104 | [HID_KEY_TAB] = MKC_Tab, 105 | [HID_KEY_SPACE] = MKC_Space, 106 | [HID_KEY_MINUS] = MKC_Minus, 107 | [HID_KEY_EQUAL] = MKC_Equal, 108 | [HID_KEY_BRACKET_LEFT] = MKC_LeftBracket, 109 | [HID_KEY_BRACKET_RIGHT] = MKC_RightBracket, 110 | [HID_KEY_BACKSLASH] = MKC_BackSlash, 111 | [HID_KEY_SEMICOLON] = MKC_SemiColon, 112 | [HID_KEY_APOSTROPHE] = MKC_SingleQuote, 113 | [HID_KEY_GRAVE] = MKC_Grave, 114 | [HID_KEY_COMMA] = MKC_Comma, 115 | [HID_KEY_PERIOD] = MKC_Period, 116 | [HID_KEY_SLASH] = MKC_Slash, 117 | [HID_KEY_CAPS_LOCK] = MKC_CapsLock, 118 | [HID_KEY_F1] = MKC_F1, 119 | [HID_KEY_F2] = MKC_F2, 120 | [HID_KEY_F3] = MKC_F3, 121 | [HID_KEY_F4] = MKC_F4, 122 | [HID_KEY_F5] = MKC_F5, 123 | [HID_KEY_F6] = MKC_F6, 124 | [HID_KEY_F7] = MKC_F7, 125 | [HID_KEY_F8] = MKC_F8, 126 | [HID_KEY_F9] = MKC_F9, 127 | [HID_KEY_F10] = MKC_F10, 128 | [HID_KEY_F11] = MKC_F11, 129 | [HID_KEY_F12] = MKC_F12, 130 | [HID_KEY_PRINT_SCREEN] = MKC_Print, 131 | [HID_KEY_SCROLL_LOCK] = MKC_ScrollLock, 132 | [HID_KEY_PAUSE] = MKC_Pause, 133 | [HID_KEY_INSERT] = MKC_Help, 134 | [HID_KEY_HOME] = MKC_Home, 135 | [HID_KEY_PAGE_UP] = MKC_PageUp, 136 | [HID_KEY_DELETE] = MKC_BackSpace, 137 | [HID_KEY_END] = MKC_End, 138 | [HID_KEY_PAGE_DOWN] = MKC_PageDown, 139 | [HID_KEY_ARROW_RIGHT] = MKC_Right, 140 | [HID_KEY_ARROW_LEFT] = MKC_Left, 141 | [HID_KEY_ARROW_DOWN] = MKC_Down, 142 | [HID_KEY_ARROW_UP] = MKC_Up, 143 | /* [HID_KEY_NUM_LOCK] = MKC_, */ 144 | [HID_KEY_KEYPAD_DIVIDE] = MKC_KPDevide, 145 | [HID_KEY_KEYPAD_MULTIPLY] = MKC_KPMultiply, 146 | [HID_KEY_KEYPAD_SUBTRACT] = MKC_KPSubtract, 147 | [HID_KEY_KEYPAD_ADD] = MKC_KPAdd, 148 | [HID_KEY_KEYPAD_ENTER] = MKC_Enter, 149 | [HID_KEY_KEYPAD_1] = MKC_KP1, 150 | [HID_KEY_KEYPAD_2] = MKC_KP2, 151 | [HID_KEY_KEYPAD_3] = MKC_KP3, 152 | [HID_KEY_KEYPAD_4] = MKC_KP4, 153 | [HID_KEY_KEYPAD_5] = MKC_KP5, 154 | [HID_KEY_KEYPAD_6] = MKC_KP6, 155 | [HID_KEY_KEYPAD_7] = MKC_KP7, 156 | [HID_KEY_KEYPAD_8] = MKC_KP8, 157 | [HID_KEY_KEYPAD_9] = MKC_KP9, 158 | [HID_KEY_KEYPAD_0] = MKC_KP0, 159 | [HID_KEY_KEYPAD_DECIMAL] = MKC_Decimal, 160 | [HID_KEY_KEYPAD_EQUAL] = MKC_Equal, 161 | [HID_KEY_RETURN] = MKC_Return, 162 | /* [HID_KEY_POWER] = MKC_, */ 163 | /* [HID_KEY_KEYPAD_COMMA] = MKC_, */ 164 | /* [HID_KEY_KEYPAD_EQUAL_SIGN] = MKC_, */ 165 | [HID_KEY_CONTROL_LEFT] = MKC_Control, 166 | [HID_KEY_SHIFT_LEFT] = MKC_Shift, 167 | [HID_KEY_ALT_LEFT] = MKC_Option, 168 | [HID_KEY_GUI_LEFT] = MKC_Command, 169 | [HID_KEY_CONTROL_RIGHT] = MKC_Control, 170 | [HID_KEY_SHIFT_RIGHT] = MKC_Shift, 171 | [HID_KEY_ALT_RIGHT] = MKC_Option, 172 | [HID_KEY_GUI_RIGHT] = MKC_Command, 173 | }; 174 | 175 | static bool kbd_map(uint8_t hid_keycode, bool pressed, uint16_t *key_out) 176 | { 177 | uint8_t k = hid_to_mac[hid_keycode]; 178 | if (!k) 179 | return false; 180 | if (k == 255) 181 | k = MKC_A; // Hack, this is zero 182 | k = (k << 1) | 1; // FIXME just do this in the #defines 183 | *key_out = k | (pressed ? 0x8000 : 0); /* Convention w.r.t. main */ 184 | return true; 185 | } 186 | 187 | bool kbd_queue_push(uint8_t hid_keycode, bool pressed) 188 | { 189 | if (kbd_queue_full()) 190 | return false; 191 | 192 | uint16_t v; 193 | if (!kbd_map(hid_keycode, pressed, &v)) 194 | return false; 195 | 196 | kbd_queue[kbd_queue_prod] = v; 197 | kbd_queue_prod = (kbd_queue_prod + 1) & KQ_MASK; 198 | return true; 199 | } 200 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* pico-umac 2 | * 3 | * Main loop to initialise umac, and run main event loop (piping 4 | * keyboard/mouse events in). 5 | * 6 | * Copyright 2024 Matt Evans 7 | * 8 | * Permission is hereby granted, free of charge, to any person 9 | * obtaining a copy of this software and associated documentation files 10 | * (the "Software"), to deal in the Software without restriction, 11 | * including without limitation the rights to use, copy, modify, merge, 12 | * publish, distribute, sublicense, and/or sell copies of the Software, 13 | * and to permit persons to whom the Software is furnished to do so, 14 | * subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be 17 | * included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 23 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | * SOFTWARE. 27 | */ 28 | 29 | #include 30 | #include 31 | #include 32 | #include "hardware/clocks.h" 33 | #include "hardware/gpio.h" 34 | #include "hardware/pio.h" 35 | #include "hardware/sync.h" 36 | #include "pico/multicore.h" 37 | #include "pico/stdlib.h" 38 | #include "pico/time.h" 39 | #include "hw.h" 40 | #include "video.h" 41 | #include "kbd.h" 42 | 43 | #include "bsp/rp2040/board.h" 44 | #include "tusb.h" 45 | 46 | #include "umac.h" 47 | 48 | #if USE_SD 49 | #include "f_util.h" 50 | #include "ff.h" 51 | #include "rtc.h" 52 | #include "hw_config.h" 53 | #endif 54 | 55 | //////////////////////////////////////////////////////////////////////////////// 56 | // Imports and data 57 | 58 | extern void hid_app_task(void); 59 | extern int cursor_x; 60 | extern int cursor_y; 61 | extern int cursor_button; 62 | 63 | // Mac binary data: disc and ROM images 64 | static const uint8_t umac_disc[] = { 65 | #include "umac-disc.h" 66 | }; 67 | static const uint8_t umac_rom[] = { 68 | #include "umac-rom.h" 69 | }; 70 | 71 | static uint8_t umac_ram[RAM_SIZE]; 72 | 73 | //////////////////////////////////////////////////////////////////////////////// 74 | 75 | static void io_init() 76 | { 77 | gpio_init(GPIO_LED_PIN); 78 | gpio_set_dir(GPIO_LED_PIN, GPIO_OUT); 79 | } 80 | 81 | static void poll_led_etc() 82 | { 83 | static int led_on = 0; 84 | static absolute_time_t last = 0; 85 | absolute_time_t now = get_absolute_time(); 86 | 87 | if (absolute_time_diff_us(last, now) > 500*1000) { 88 | last = now; 89 | 90 | led_on ^= 1; 91 | gpio_put(GPIO_LED_PIN, led_on); 92 | } 93 | } 94 | 95 | static int umac_cursor_x = 0; 96 | static int umac_cursor_y = 0; 97 | static int umac_cursor_button = 0; 98 | 99 | static void poll_umac() 100 | { 101 | static absolute_time_t last_1hz = 0; 102 | static absolute_time_t last_vsync = 0; 103 | absolute_time_t now = get_absolute_time(); 104 | 105 | umac_loop(); 106 | 107 | int64_t p_1hz = absolute_time_diff_us(last_1hz, now); 108 | int64_t p_vsync = absolute_time_diff_us(last_vsync, now); 109 | if (p_vsync >= 16667) { 110 | /* FIXME: Trigger this off actual vsync */ 111 | umac_vsync_event(); 112 | last_vsync = now; 113 | } 114 | if (p_1hz >= 1000000) { 115 | umac_1hz_event(); 116 | last_1hz = now; 117 | } 118 | 119 | int update = 0; 120 | int dx = 0; 121 | int dy = 0; 122 | int b = umac_cursor_button; 123 | if (cursor_x != umac_cursor_x) { 124 | dx = cursor_x - umac_cursor_x; 125 | umac_cursor_x = cursor_x; 126 | update = 1; 127 | } 128 | if (cursor_y != umac_cursor_y) { 129 | dy = cursor_y - umac_cursor_y; 130 | umac_cursor_y = cursor_y; 131 | update = 1; 132 | } 133 | if (cursor_button != umac_cursor_button) { 134 | b = cursor_button; 135 | umac_cursor_button = cursor_button; 136 | update = 1; 137 | } 138 | if (update) { 139 | umac_mouse(dx, -dy, b); 140 | } 141 | 142 | if (!kbd_queue_empty()) { 143 | uint16_t k = kbd_queue_pop(); 144 | umac_kbd_event(k & 0xff, !!(k & 0x8000)); 145 | } 146 | } 147 | 148 | #if USE_SD 149 | static int disc_do_read(void *ctx, uint8_t *data, unsigned int offset, unsigned int len) 150 | { 151 | FIL *fp = (FIL *)ctx; 152 | f_lseek(fp, offset); 153 | unsigned int did_read = 0; 154 | FRESULT fr = f_read(fp, data, len, &did_read); 155 | if (fr != FR_OK || len != did_read) { 156 | printf("disc: f_read returned %d, read %u (of %u)\n", fr, did_read, len); 157 | return -1; 158 | } 159 | return 0; 160 | } 161 | 162 | static int disc_do_write(void *ctx, uint8_t *data, unsigned int offset, unsigned int len) 163 | { 164 | FIL *fp = (FIL *)ctx; 165 | f_lseek(fp, offset); 166 | unsigned int did_write = 0; 167 | FRESULT fr = f_write(fp, data, len, &did_write); 168 | if (fr != FR_OK || len != did_write) { 169 | printf("disc: f_write returned %d, read %u (of %u)\n", fr, did_write, len); 170 | return -1; 171 | } 172 | return 0; 173 | } 174 | 175 | static FIL discfp; 176 | #endif 177 | 178 | static void disc_setup(disc_descr_t discs[DISC_NUM_DRIVES]) 179 | { 180 | #if USE_SD 181 | char *disc0_name; 182 | const char *disc0_ro_name = "umac0ro.img"; 183 | const char *disc0_pattern = "umac0*.img"; 184 | 185 | /* Mount SD filesystem */ 186 | printf("Starting SPI/FatFS:\n"); 187 | set_spi_dma_irq_channel(true, false); 188 | sd_card_t *pSD = sd_get_by_num(0); 189 | FRESULT fr = f_mount(&pSD->fatfs, pSD->pcName, 1); 190 | printf(" mount: %d\n", fr); 191 | if (fr != FR_OK) { 192 | printf(" error mounting disc: %s (%d)\n", FRESULT_str(fr), fr); 193 | goto no_sd; 194 | } 195 | 196 | /* Look for a disc image */ 197 | DIR di = {0}; 198 | FILINFO fi = {0}; 199 | fr = f_findfirst(&di, &fi, "/", disc0_pattern); 200 | if (fr != FR_OK) { 201 | printf(" Can't find images %s: %s (%d)\n", disc0_pattern, FRESULT_str(fr), fr); 202 | goto no_sd; 203 | } 204 | disc0_name = fi.fname; 205 | f_closedir(&di); 206 | 207 | int read_only = !strcmp(disc0_name, disc0_ro_name); 208 | printf(" Opening %s (R%c)\n", disc0_name, read_only ? 'O' : 'W'); 209 | 210 | /* Open image, set up disc info: */ 211 | fr = f_open(&discfp, disc0_name, FA_OPEN_EXISTING | FA_READ | FA_WRITE); 212 | if (fr != FR_OK && fr != FR_EXIST) { 213 | printf(" *** Can't open %s: %s (%d)!\n", disc0_name, FRESULT_str(fr), fr); 214 | goto no_sd; 215 | } else { 216 | printf(" Opened, size 0x%x\n", f_size(&discfp)); 217 | if (read_only) 218 | printf(" (disc is read-only)\n"); 219 | discs[0].base = 0; // Means use R/W ops 220 | discs[0].read_only = read_only; 221 | discs[0].size = f_size(&discfp); 222 | discs[0].op_ctx = &discfp; 223 | discs[0].op_read = disc_do_read; 224 | discs[0].op_write = disc_do_write; 225 | } 226 | 227 | /* FIXME: Other files can be stored on SD too, such as logging 228 | * and NVRAM storage. 229 | * 230 | * We could also implement a menu here to select an image, 231 | * writing text to the framebuffer and checking kbd_queue_*() 232 | * for user input. 233 | */ 234 | return; 235 | 236 | no_sd: 237 | #endif 238 | /* If we don't find (or look for) an SD-based image, attempt 239 | * to use in-flash disc image: 240 | */ 241 | discs[0].base = (void *)umac_disc; 242 | discs[0].read_only = 1; 243 | discs[0].size = sizeof(umac_disc); 244 | } 245 | 246 | static void core1_main() 247 | { 248 | disc_descr_t discs[DISC_NUM_DRIVES] = {0}; 249 | 250 | printf("Core 1 started\n"); 251 | disc_setup(discs); 252 | 253 | umac_init(umac_ram, (void *)umac_rom, discs); 254 | /* Video runs on core 1, i.e. IRQs/DMA are unaffected by 255 | * core 0's USB activity. 256 | */ 257 | video_init((uint32_t *)(umac_ram + umac_get_fb_offset())); 258 | 259 | printf("Enjoyable Mac times now begin:\n\n"); 260 | 261 | while (true) { 262 | poll_umac(); 263 | } 264 | } 265 | 266 | int main() 267 | { 268 | set_sys_clock_khz(250*1000, true); 269 | 270 | stdio_init_all(); 271 | io_init(); 272 | 273 | multicore_launch_core1(core1_main); 274 | 275 | printf("Starting, init usb\n"); 276 | tusb_init(); 277 | 278 | /* This happens on core 0: */ 279 | while (true) { 280 | tuh_task(); 281 | hid_app_task(); 282 | poll_led_etc(); 283 | } 284 | 285 | return 0; 286 | } 287 | 288 | -------------------------------------------------------------------------------- /src/pio_video.pio: -------------------------------------------------------------------------------- 1 | ; PIO video output: 2 | ; This scans out video lines, characteristically some number of bits per pixel, 3 | ; a pixel clock, and timing signals HSync, VSync (in future, DE too). 4 | ; 5 | ; Copyright 2024 Matt Evans 6 | ; 7 | ; Permission is hereby granted, free of charge, to any person 8 | ; obtaining a copy of this software and associated documentation files 9 | ; (the "Software"), to deal in the Software without restriction, 10 | ; including without limitation the rights to use, copy, modify, merge, 11 | ; publish, distribute, sublicense, and/or sell copies of the Software, 12 | ; and to permit persons to whom the Software is furnished to do so, 13 | ; subject to the following conditions: 14 | ; 15 | ; The above copyright notice and this permission notice shall be 16 | ; included in all copies or substantial portions of the Software. 17 | ; 18 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | ; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 22 | ; BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 23 | ; ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 24 | ; CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | ; SOFTWARE. 26 | ; 27 | ; 28 | ; The source of image data is the OUT FIFO, and a corresponding C routine 29 | ; needs to fill this with image data. That data might be generated on 30 | ; the fly, or constructed by setting DMA pointers to a framebuffer. 31 | ; 32 | ; A typical usage would be the C routine preparing one scanline of data 33 | ; and setting off DMA. That's a good balance between number of interrupts 34 | ; and amount of buffering/RAM required (a framebuffer is generally pretty 35 | ; large...) 36 | ; 37 | ; Supports a max of 15bpp. That's tons given the number of IO... expecting 38 | ; to use this with 8, 3, etc. 39 | ; 40 | ; The output pins are required to be, in this order, 41 | ; 0: Video data 42 | ; 0+BPP: Vsync 43 | ; 1+BPP: PClk 44 | ; 2+BPP: Hsync 45 | ; 46 | ; FIXME: 1BPP for now 47 | ; 48 | ; The horizontal timing information is embedded in the data read via 49 | ; the FIFO, as follows, shown from the very start of a frame. The vertical 50 | ; timing info is generated entirely from the C side by passing a VSync 51 | ; value in the data stream. The data stream for each line is: 52 | ; 53 | ; ---------- Config information: (offset 0 on each line) ---------- 54 | ; 32b: Timing/sync info: 55 | ; [31] Vsync value for this line 56 | ; [30:23] Hsync width (HSW) 57 | ; [22:15] HBP width minus 3 (FIXME: check) 58 | ; [14:7] HFP width minus 3 59 | ; 32b: Number of visible pixels per line 60 | ; ---------- Pixel data: (offset 8 on each line) ------------------- 61 | ; : video data (padded with zeros for HBP/HFP pixels) 62 | ; ------------------------------------------------------------------- 63 | ; 64 | ; + +-------------------------------------------------- 65 | ; | |HBP- -HFP 66 | ; +-+ 67 | ; +--+ ***************************************************** 68 | ; | ***************************************************** 69 | ; +--+ ***************************************************** 70 | ; |VBP ***************************************************** 71 | ; | &&&&&&&+----------------------------------------+%%%% 72 | ; | &&&&&&&| |%%%% 73 | ; | &&&&&&&| Active area |%%%% 74 | ; | &&&&&&&| |%%%% 75 | ; | &&&&&&&| |%%%% 76 | ; | &&&&&&&| |%%%% 77 | ; | &&&&&&&| |%%%% 78 | ; | &&&&&&&| |%%%% 79 | ; | &&&&&&&| |%%%% 80 | ; | &&&&&&&| |%%%% 81 | ; | &&&&&&&| |%%%% 82 | ; | &&&&&&&| |%%%% 83 | ; | &&&&&&&| |%%%% 84 | ; | &&&&&&&| |%%%% 85 | ; | &&&&&&&+----------------------------------------+%%%% 86 | ; |VFP @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 87 | ; |: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 88 | ; |: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 89 | ; 90 | ; The HFP/HBP pixels should be written zero. Clever DMA programming can 91 | ; provide these from a separate location to the video data. 92 | ; 93 | ; FIXME: Add DE (and therefore full HBP/HFP counters in timing word) 94 | ; 95 | ; There are a couple of pin-mapping tricks going on. We need to be 96 | ; able to change the video without messing wtih VS, and we want to 97 | ; assert HS/VS at the same instant. That means the syncs aren't part 98 | ; of the OUT mapping -- this is only the pixel data. The SET mapping 99 | ; controls VS, and SIDESET controls HS/clk. 100 | ; 101 | ; The advantage of OUT being solely for data is then being able to easily 102 | ; extend to multiple BPP. Note the HS/VS are active-high in terms of 103 | ; programming, but the output signal can be flipped at GPIO using the 104 | ; inversion feature. 105 | 106 | ; .define BPP 123 etc. 107 | 108 | .program pio_video 109 | .side_set 2 ; SS[0] is clk, SS[1] is HS 110 | 111 | frame_start: 112 | ; The first word gives VS/HSW: [31]=vsync; [30:23]=HSW 113 | ; [22:15]=HBP, [14:7]=HFP. (shifted left!) 114 | ; 115 | ; Set VS on the same cycle as asserting HS. 116 | ; Note: these cycles are part of HFP. 117 | out X, 1 side 0 118 | jmp !X, vs_inactive side 1 119 | vs_active: 120 | set pins, 1 side 2 121 | jmp now_read_HSW side 3 122 | vs_inactive: 123 | set pins, 0 side 2 124 | nop side 3 125 | 126 | now_read_HSW: ; X=hsync width: 127 | out X, 8 side 2 128 | hsw_loop: nop side 3 129 | jmp X-- hsw_loop side 2 130 | 131 | ; De-assert hsync (leave Vsync as-is) and shift out HBP: 132 | now_read_HBP: ; X=HBP width: 133 | out X, 8 side 1 134 | hbp_loop: nop side 0 135 | jmp X-- hbp_loop side 1 136 | 137 | now_read_HFP: ; Y=HFP width: 138 | out Y, 8 side 0 139 | 140 | ; Pull, discarding the remainder of OSR. This prepares X pixel count. 141 | ; Note: these cycles (and HFP read) are part of HBP. 142 | pull block side 1 143 | out X, 32 side 0 144 | nop side 1 145 | pixels_loop: ; OSR primed/autopulled 146 | ; FIXME: side-set DE=1 147 | out pins, 1 side 0 ; BPP 148 | jmp X-- pixels_loop side 1 149 | ; FIXME: side-set DE=0 150 | ; Set video BLACK (1) 151 | mov pins, !NULL side 0 152 | nop side 1 153 | ; Now perform HFP delay 154 | hfp_loop: nop side 0 155 | jmp Y-- hfp_loop side 1 156 | 157 | ; A free HFP pixel, to prime for next line: 158 | // Auto-pull gets next line (always a multiple of 32b) 159 | nop side 0 160 | jmp frame_start side 1 161 | 162 | ; HFP 2 min 163 | ; HBP 2 min 164 | 165 | 166 | % c-sdk { 167 | static inline void pio_video_program_init(PIO pio, uint sm, uint offset, 168 | uint video_pin /* then VS, CLK, HS */, 169 | float clk_div) { 170 | /* Outputs are consecutive up from Video data */ 171 | uint vsync_pin = video_pin+1; 172 | uint clk_pin = video_pin+2; 173 | uint hsync_pin = video_pin+3; 174 | /* Init GPIO & directions */ 175 | pio_gpio_init(pio, video_pin); 176 | pio_gpio_init(pio, hsync_pin); 177 | pio_gpio_init(pio, vsync_pin); 178 | pio_gpio_init(pio, clk_pin); 179 | // FIXME: BPP define 180 | pio_sm_set_consecutive_pindirs(pio, sm, video_pin, 4, true /* out */); 181 | 182 | pio_sm_config c = pio_video_program_get_default_config(offset); 183 | sm_config_set_out_pins(&c, video_pin, 1); 184 | sm_config_set_set_pins(&c, vsync_pin, 1); 185 | sm_config_set_sideset_pins(&c, clk_pin); /* CLK + HS */ 186 | /* Sideset bits are configured via .side_set directive above */ 187 | sm_config_set_out_shift(&c, false /* OUT MSBs first */, true /* Autopull */, 32 /* bits */); 188 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 189 | sm_config_set_clkdiv(&c, clk_div); 190 | 191 | pio_sm_init(pio, sm, offset, &c); 192 | pio_sm_set_enabled(pio, sm, true); 193 | } 194 | %} 195 | -------------------------------------------------------------------------------- /src/sd_hw_config.c: -------------------------------------------------------------------------------- 1 | /* hw_config.c 2 | Copyright 2021 Carl John Kugler III 3 | 4 | Licensed under the Apache License, Version 2.0 (the License); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | */ 14 | /* 15 | 16 | This file should be tailored to match the hardware design. 17 | 18 | There should be one element of the spi[] array for each hardware SPI used. 19 | 20 | There should be one element of the sd_cards[] array for each SD card slot. 21 | The name is should correspond to the FatFs "logical drive" identifier. 22 | (See http://elm-chan.org/fsw/ff/doc/filename.html#vol) 23 | The rest of the constants will depend on the type of 24 | socket, which SPI it is driven by, and how it is wired. 25 | 26 | */ 27 | 28 | #include 29 | // 30 | #include "my_debug.h" 31 | // 32 | #include "hw_config.h" 33 | // 34 | #include "ff.h" /* Obtains integer types */ 35 | // 36 | #include "diskio.h" /* Declarations of disk functions */ 37 | 38 | // Hardware Configuration of SPI "objects" 39 | // Note: multiple SD cards can be driven by one SPI if they use different slave 40 | // selects. 41 | static spi_t spis[] = { // One for each SPI. 42 | { 43 | .hw_inst = spi0, // SPI component 44 | .miso_gpio = SD_RX, // GPIO number (not pin number) 45 | .mosi_gpio = SD_TX, 46 | .sck_gpio = SD_SCK, 47 | 48 | .set_drive_strength = true, 49 | .mosi_gpio_drive_strength = GPIO_DRIVE_STRENGTH_8MA, 50 | .sck_gpio_drive_strength = GPIO_DRIVE_STRENGTH_8MA, 51 | 52 | // One of my cards doesn't seem to work beyond 5MHz :( 53 | .baud_rate = SD_MHZ * 1000 * 1000, 54 | } 55 | }; 56 | 57 | // Hardware Configuration of the SD Card "objects" 58 | static sd_card_t sd_cards[] = { // One for each SD card 59 | { 60 | .pcName = "0:", // Name used to mount device 61 | .spi = &spis[0], // Pointer to the SPI driving this card 62 | .ss_gpio = SD_CS, // The SPI slave select GPIO for this SD card 63 | .set_drive_strength = true, 64 | .ss_gpio_drive_strength = GPIO_DRIVE_STRENGTH_8MA, 65 | .use_card_detect = false, 66 | .card_detected_true = -1 // What the GPIO read returns when a card is 67 | // present. Use -1 if there is no card detect. 68 | }}; 69 | 70 | /* ********************************************************************** */ 71 | size_t sd_get_num() { return count_of(sd_cards); } 72 | sd_card_t *sd_get_by_num(size_t num) { 73 | if (num <= sd_get_num()) { 74 | return &sd_cards[num]; 75 | } else { 76 | return NULL; 77 | } 78 | } 79 | size_t spi_get_num() { return count_of(spis); } 80 | spi_t *spi_get_by_num(size_t num) { 81 | if (num <= sd_get_num()) { 82 | return &spis[num]; 83 | } else { 84 | return NULL; 85 | } 86 | } 87 | 88 | /* [] END OF FILE */ 89 | -------------------------------------------------------------------------------- /src/video.c: -------------------------------------------------------------------------------- 1 | /* Video output: 2 | * 3 | * Using PIO[1], output the Mac 512x342 1BPP framebuffer to VGA/pins. This is done 4 | * directly from the Mac framebuffer (without having to reformat in an intermediate 5 | * buffer). The video output is 640x480, with the visible pixel data centred with 6 | * borders: for analog VGA this is easy, as it just means increasing the horizontal 7 | * back porch/front porch (time between syncs and active video) and reducing the 8 | * display portion of a line. 9 | * 10 | * [1]: see pio_video.pio 11 | * 12 | * Copyright 2024 Matt Evans 13 | * 14 | * Permission is hereby granted, free of charge, to any person 15 | * obtaining a copy of this software and associated documentation files 16 | * (the "Software"), to deal in the Software without restriction, 17 | * including without limitation the rights to use, copy, modify, merge, 18 | * publish, distribute, sublicense, and/or sell copies of the Software, 19 | * and to permit persons to whom the Software is furnished to do so, 20 | * subject to the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be 23 | * included in all copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 27 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 29 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 30 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 31 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | * SOFTWARE. 33 | */ 34 | 35 | #include 36 | #include 37 | #include 38 | #include "hardware/clocks.h" 39 | #include "hardware/dma.h" 40 | #include "hardware/gpio.h" 41 | #include "hardware/structs/padsbank0.h" 42 | #include "pio_video.pio.h" 43 | 44 | #include "hw.h" 45 | 46 | //////////////////////////////////////////////////////////////////////////////// 47 | /* VESA VGA mode 640x480@60 */ 48 | 49 | /* The pixel clock _should_ be (125/2/25.175) (about 2.483) but that seems to 50 | * make my VGA-HDMI adapter sample weird, and pixels crawl. Fudge a little, 51 | * looks better: 52 | */ 53 | #define VIDEO_PCLK_MULT (2.5*2) 54 | #define VIDEO_HSW 96 55 | #define VIDEO_HBP 48 56 | #define VIDEO_HRES 640 57 | #define VIDEO_HFP 16 58 | #define VIDEO_H_TOTAL_NOSYNC (VIDEO_HBP + VIDEO_HRES + VIDEO_HFP) 59 | #define VIDEO_VSW 2 60 | #define VIDEO_VBP 33 61 | #define VIDEO_VRES 480 62 | #define VIDEO_VFP 10 63 | #define VIDEO_V_TOTAL (VIDEO_VSW + VIDEO_VBP + VIDEO_VRES + VIDEO_VFP) 64 | /* The visible vertical span in the VGA output, [start, end) lines: */ 65 | #define VIDEO_V_VIS_START (VIDEO_VSW + VIDEO_VBP) 66 | #define VIDEO_V_VIS_END (VIDEO_V_VIS_START + VIDEO_VRES) 67 | 68 | #define VIDEO_FB_HRES DISP_WIDTH 69 | #define VIDEO_FB_VRES DISP_HEIGHT 70 | 71 | /* The lines at which the FB data is actively output: */ 72 | #define VIDEO_FB_V_VIS_START (VIDEO_V_VIS_START + ((VIDEO_VRES - VIDEO_FB_VRES)/2)) 73 | #define VIDEO_FB_V_VIS_END (VIDEO_FB_V_VIS_START + VIDEO_FB_VRES) 74 | 75 | /* Words of 1BPP pixel data per line; this dictates the length of the 76 | * video data DMA transfer: 77 | */ 78 | #define VIDEO_VISIBLE_WPL (VIDEO_FB_HRES / 32) 79 | 80 | #if (VIDEO_HRES & 31) 81 | #error "VIDEO_HRES: must be a multiple of 32b!" 82 | #endif 83 | 84 | //////////////////////////////////////////////////////////////////////////////// 85 | // Video DMA, framebuffer pointers 86 | 87 | static uint32_t video_null[VIDEO_VISIBLE_WPL]; 88 | static uint32_t *video_framebuffer; 89 | 90 | /* DMA buffer containing 2 pairs of per-line config words, for VS and not-VS: */ 91 | static uint32_t video_dma_cfg[4]; 92 | 93 | /* 3 DMA channels are used. The first to transfer data to PIO, and 94 | * the other two to transfer descriptors to the first channel. 95 | */ 96 | static uint8_t video_dmach_tx; 97 | static uint8_t video_dmach_descr_cfg; 98 | static uint8_t video_dmach_descr_data; 99 | 100 | typedef struct { 101 | const void *raddr; 102 | void *waddr; 103 | uint32_t count; 104 | uint32_t ctrl; 105 | } dma_descr_t; 106 | 107 | static dma_descr_t video_dmadescr_cfg; 108 | static dma_descr_t video_dmadescr_data; 109 | 110 | static volatile unsigned int video_current_y = 0; 111 | 112 | static int __not_in_flash_func(video_get_visible_y)(unsigned int y) { 113 | if ((y >= VIDEO_FB_V_VIS_START) && (y < VIDEO_FB_V_VIS_END)) { 114 | return y - VIDEO_FB_V_VIS_START; 115 | } else { 116 | return -1; 117 | } 118 | } 119 | 120 | static const uint32_t *__not_in_flash_func(video_line_addr)(unsigned int y) 121 | { 122 | int vy = video_get_visible_y(y); 123 | if (vy >= 0) 124 | return (const uint32_t *)&video_framebuffer[vy * VIDEO_VISIBLE_WPL]; 125 | else 126 | return (const uint32_t *)video_null; 127 | } 128 | 129 | static const uint32_t *__not_in_flash_func(video_cfg_addr)(unsigned int y) 130 | { 131 | return &video_dma_cfg[(y < VIDEO_VSW) ? 0 : 2]; 132 | } 133 | 134 | static void __not_in_flash_func(video_dma_prep_new)() 135 | { 136 | /* The descriptor DMA read pointers have moved on; reset them. 137 | * The write pointers wrap so should be pointing to the 138 | * correct DMA regs. 139 | */ 140 | dma_hw->ch[video_dmach_descr_cfg].read_addr = (uintptr_t)&video_dmadescr_cfg; 141 | dma_hw->ch[video_dmach_descr_cfg].transfer_count = 4; 142 | dma_hw->ch[video_dmach_descr_data].read_addr = (uintptr_t)&video_dmadescr_data; 143 | dma_hw->ch[video_dmach_descr_data].transfer_count = 4; 144 | 145 | /* Configure the two DMA descriptors, video_dmadescr_cfg and 146 | * video_dmadescr_data, to transfer from video config/data corresponding 147 | * to the current line. 148 | * 149 | * These descriptors will be used to program the video_dmach_tx channel, 150 | * pushing the buffer to PIO. 151 | * 152 | * This can be relatively relaxed, as it's triggered as line data 153 | * starts; we have until the end of the video line (when the descriptors 154 | * are retriggered) to program them. 155 | * 156 | * FIXME: this time could be used for something clever like split-screen 157 | * (e.g. info/text lines) constructed on-the-fly. 158 | */ 159 | video_dmadescr_cfg.raddr = video_cfg_addr(video_current_y); 160 | video_dmadescr_data.raddr = video_line_addr(video_current_y); 161 | 162 | /* Frame done */ 163 | if (++video_current_y >= VIDEO_V_TOTAL) 164 | video_current_y = 0; 165 | } 166 | 167 | static void __not_in_flash_func(video_dma_irq)() 168 | { 169 | /* The DMA IRQ occurs once the video portion of the line has been 170 | * triggered (not when the video transfer completes, but when the 171 | * descriptor transfer (that leads to the video transfer!) completes. 172 | * All we need to do is reconfigure the descriptors; the video DMA will 173 | * re-trigger the descriptors later. 174 | */ 175 | if (dma_channel_get_irq0_status(video_dmach_descr_data)) { 176 | dma_channel_acknowledge_irq0(video_dmach_descr_data); 177 | video_dma_prep_new(); 178 | } 179 | } 180 | 181 | static void video_prep_buffer() 182 | { 183 | memset(video_null, 0xff, VIDEO_VISIBLE_WPL * 4); 184 | 185 | unsigned int porch_padding = (VIDEO_HRES - VIDEO_FB_HRES)/2; 186 | // FIXME: HBP/HFP are prob off by one or so, check 187 | uint32_t timing = ((VIDEO_HSW - 1) << 23) | 188 | ((VIDEO_HBP + porch_padding - 3) << 15) | 189 | ((VIDEO_HFP + porch_padding - 4) << 7); 190 | video_dma_cfg[0] = timing | 0x80000000; 191 | video_dma_cfg[1] = VIDEO_FB_HRES - 1; 192 | video_dma_cfg[2] = timing; 193 | video_dma_cfg[3] = VIDEO_FB_HRES - 1; 194 | } 195 | 196 | static void video_init_dma() 197 | { 198 | /* pio_video expects each display line to be composed of two words of config 199 | * describing the line geometry and whether VS is asserted, followed by 200 | * visible data. 201 | * 202 | * To avoid having to embed config metadata in the display framebuffer, 203 | * we use two DMA transfers to PIO for each line. The first transfers 204 | * the config from a config buffer, and then triggers the second to 205 | * transfer the video data from the framebuffer. (This lets us use a 206 | * flat, regular FB.) 207 | * 208 | * The PIO side emits 1BPP MSB-first. The other advantage of 209 | * using a second DMA transfer is then we can also can 210 | * byteswap the DMA of the video portion to match the Mac 211 | * framebuffer layout. 212 | * 213 | * "Another caveat is that multiple channels should not be connected 214 | * to the same DREQ.": 215 | * The final complexity is that only one DMA channel can do the 216 | * transfers to PIO, because of how the credit-based flow control works. 217 | * So, _only_ channel 0 transfers from $SOME_BUFFER into the PIO FIFO, 218 | * and channel 1+2 are used to reprogram/trigger channel 0 from a DMA 219 | * descriptor list. 220 | * 221 | * Two extra channels are used to manage interrupts; ch1 programs ch0, 222 | * completes, and does nothing. (It programs a descriptor that causes 223 | * ch0 to transfer config, then trigger ch2 when complete.) ch2 then 224 | * programs ch0 with a descriptor to transfer data, then trigger ch1 225 | * when ch0 completes; when ch2 finishes doing that, it produces an IRQ. 226 | * Got that? 227 | * 228 | * The IRQ handler sets up ch1 and ch2 to point to 2 fresh cfg+data 229 | * descriptors; the deadline is by the end of ch0's data transfer 230 | * (i.e. a whole line). When ch0 finishes the data transfer it again 231 | * triggers ch1, and the new config entry is programmed. 232 | */ 233 | video_dmach_tx = dma_claim_unused_channel(true); 234 | video_dmach_descr_cfg = dma_claim_unused_channel(true); 235 | video_dmach_descr_data = dma_claim_unused_channel(true); 236 | 237 | /* Transmit DMA: config+video data */ 238 | /* First, make dmacfg for data to transfer from config buffers + data buffers: */ 239 | dma_channel_config dc_tx_c = dma_channel_get_default_config(video_dmach_tx); 240 | channel_config_set_dreq(&dc_tx_c, DREQ_PIO0_TX0); 241 | channel_config_set_transfer_data_size(&dc_tx_c, DMA_SIZE_32); 242 | channel_config_set_read_increment(&dc_tx_c, true); 243 | channel_config_set_write_increment(&dc_tx_c, false); 244 | channel_config_set_bswap(&dc_tx_c, false); 245 | /* Completion of the config TX triggers the video_dmach_descr_data channel */ 246 | channel_config_set_chain_to(&dc_tx_c, video_dmach_descr_data); 247 | video_dmadescr_cfg.raddr = NULL; /* Reprogrammed each line */ 248 | video_dmadescr_cfg.waddr = (void *)&pio0_hw->txf[0]; 249 | video_dmadescr_cfg.count = 2; /* 2 words of video config */ 250 | video_dmadescr_cfg.ctrl = dc_tx_c.ctrl; 251 | 252 | dma_channel_config dc_tx_d = dma_channel_get_default_config(video_dmach_tx); 253 | channel_config_set_dreq(&dc_tx_d, DREQ_PIO0_TX0); 254 | channel_config_set_transfer_data_size(&dc_tx_d, DMA_SIZE_32); 255 | channel_config_set_read_increment(&dc_tx_d, true); 256 | channel_config_set_write_increment(&dc_tx_d, false); 257 | channel_config_set_bswap(&dc_tx_d, true); /* This channel bswaps */ 258 | /* Completion of the data TX triggers the video_dmach_descr_cfg channel */ 259 | channel_config_set_chain_to(&dc_tx_d, video_dmach_descr_cfg); 260 | video_dmadescr_data.raddr = NULL; /* Reprogrammed each line */ 261 | video_dmadescr_data.waddr = (void *)&pio0_hw->txf[0]; 262 | video_dmadescr_data.count = VIDEO_VISIBLE_WPL; 263 | video_dmadescr_data.ctrl = dc_tx_d.ctrl; 264 | 265 | /* Now, the descr_cfg and descr_data channels transfer _those_ 266 | * descriptors to program the video_dmach_tx channel: 267 | */ 268 | dma_channel_config dcfg = dma_channel_get_default_config(video_dmach_descr_cfg); 269 | channel_config_set_transfer_data_size(&dcfg, DMA_SIZE_32); 270 | channel_config_set_read_increment(&dcfg, true); 271 | channel_config_set_write_increment(&dcfg, true); 272 | /* This channel loops on 16-byte/4-wprd boundary (i.e. writes all config): */ 273 | channel_config_set_ring(&dcfg, true, 4); 274 | /* No completion IRQ or chain: the video_dmach_tx DMA completes and triggers 275 | * the next 'data' descriptor transfer. 276 | */ 277 | dma_channel_configure(video_dmach_descr_cfg, &dcfg, 278 | &dma_hw->ch[video_dmach_tx].read_addr, 279 | &video_dmadescr_cfg, 280 | 4 /* 4 words of config */, 281 | false /* Not yet */); 282 | 283 | dma_channel_config ddata = dma_channel_get_default_config(video_dmach_descr_data); 284 | channel_config_set_transfer_data_size(&ddata, DMA_SIZE_32); 285 | channel_config_set_read_increment(&ddata, true); 286 | channel_config_set_write_increment(&ddata, true); 287 | channel_config_set_ring(&ddata, true, 4); 288 | /* This transfer has a completion IRQ. Receipt of that means that both 289 | * config and data descriptors have been transferred, and should be 290 | * reprogrammed for the next line. 291 | */ 292 | dma_channel_set_irq0_enabled(video_dmach_descr_data, true); 293 | dma_channel_configure(video_dmach_descr_data, &ddata, 294 | &dma_hw->ch[video_dmach_tx].read_addr, 295 | &video_dmadescr_data, 296 | 4 /* 4 words of config */, 297 | false /* Not yet */); 298 | 299 | /* Finally, set up video_dmadescr_cfg.raddr and video_dmadescr_data.raddr to point 300 | * to next line's video cfg/data buffers. Then, video_dmach_descr_cfg can be triggered 301 | * to start video. 302 | */ 303 | } 304 | 305 | //////////////////////////////////////////////////////////////////////////////// 306 | 307 | /* Initialise PIO, DMA, start sending pixels. Passed a pointer to a 512x342x1 308 | * Mac-order framebuffer. 309 | * 310 | * FIXME: Add an API to change the FB base after init live, e.g. for bank 311 | * switching. 312 | */ 313 | void video_init(uint32_t *framebuffer) 314 | { 315 | printf("Video init\n"); 316 | 317 | pio_video_program_init(pio0, 0, 318 | pio_add_program(pio0, &pio_video_program), 319 | GPIO_VID_DATA, /* Followed by HS, VS, CLK */ 320 | VIDEO_PCLK_MULT); 321 | 322 | /* Invert output pins: HS/VS are active-low, also invert video! */ 323 | gpio_set_outover(GPIO_VID_HS, GPIO_OVERRIDE_INVERT); 324 | gpio_set_outover(GPIO_VID_VS, GPIO_OVERRIDE_INVERT); 325 | gpio_set_outover(GPIO_VID_DATA, GPIO_OVERRIDE_INVERT); 326 | /* Highest drive strength (VGA is current-based, innit) */ 327 | hw_write_masked(&padsbank0_hw->io[GPIO_VID_DATA], 328 | PADS_BANK0_GPIO0_DRIVE_VALUE_12MA << PADS_BANK0_GPIO0_DRIVE_LSB, 329 | PADS_BANK0_GPIO0_DRIVE_BITS); 330 | 331 | /* IRQ handlers for DMA_IRQ_0: */ 332 | irq_set_exclusive_handler(DMA_IRQ_0, video_dma_irq); 333 | irq_set_enabled(DMA_IRQ_0, true); 334 | 335 | video_init_dma(); 336 | 337 | /* Init config word buffers */ 338 | video_current_y = 0; 339 | video_framebuffer = framebuffer; 340 | video_prep_buffer(); 341 | 342 | /* Set up pointers to first line, and start DMA */ 343 | video_dma_prep_new(); 344 | dma_channel_start(video_dmach_descr_cfg); 345 | } 346 | --------------------------------------------------------------------------------