Initial release
Squashed, from v0.1
This commit is contained in:
commit
bd30ffe141
14 changed files with 1822 additions and 0 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "external/umac"]
|
||||||
|
url = https://github.com/evansm7/umac
|
||||||
|
path = external/umac
|
||||||
110
CMakeLists.txt
Normal file
110
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# CMakeLists
|
||||||
|
#
|
||||||
|
# MIT License
|
||||||
|
#
|
||||||
|
# Copyright (c) 2021, 2024 Matt Evans
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
# initialize the SDK based on PICO_SDK_PATH
|
||||||
|
# note: this must happen before project()
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(firmware)
|
||||||
|
|
||||||
|
# initialize the Raspberry Pi Pico SDK
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
# For TUSB host stuff:
|
||||||
|
set(FAMILY rp2040)
|
||||||
|
set(BOARD raspberry_pi_pico)
|
||||||
|
|
||||||
|
set(TINYUSB_PATH ${PICO_SDK_PATH}/lib/tinyusb)
|
||||||
|
|
||||||
|
# umac subproject (and Musashi sub-subproject)
|
||||||
|
set(UMAC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external/umac)
|
||||||
|
set(UMAC_MUSASHI_PATH ${UMAC_PATH}/external/Musashi)
|
||||||
|
set(UMAC_INCLUDE_PATHS ${UMAC_PATH}/include ${UMAC_MUSASHI_PATH})
|
||||||
|
|
||||||
|
# This isn't very nice, but hey it's Sunday :p
|
||||||
|
set(UMAC_SOURCES
|
||||||
|
${UMAC_PATH}/src/disc.c
|
||||||
|
${UMAC_PATH}/src/main.c
|
||||||
|
${UMAC_PATH}/src/rom.c
|
||||||
|
${UMAC_PATH}/src/scc.c
|
||||||
|
${UMAC_PATH}/src/via.c
|
||||||
|
${UMAC_MUSASHI_PATH}/m68kcpu.c
|
||||||
|
${UMAC_MUSASHI_PATH}/m68kdasm.c
|
||||||
|
${UMAC_MUSASHI_PATH}/m68kops.c
|
||||||
|
${UMAC_MUSASHI_PATH}/softfloat/softfloat.c
|
||||||
|
)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DPICO -DMUSASHI_CNF=\\\"../include/m68kconf.h\\\"")
|
||||||
|
|
||||||
|
if (TARGET tinyusb_device)
|
||||||
|
add_executable(firmware
|
||||||
|
src/main.c
|
||||||
|
src/video.c
|
||||||
|
src/kbd.c
|
||||||
|
src/hid.c
|
||||||
|
|
||||||
|
${UMAC_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
# The umac sources need to prepare Musashi (some sources are generated):
|
||||||
|
add_custom_command(OUTPUT ${UMAC_MUSASHI_PATH}/m68kops.c
|
||||||
|
COMMAND echo "*** Preparing umac source ***"
|
||||||
|
COMMAND make -C ${UMAC_PATH} prepare
|
||||||
|
)
|
||||||
|
add_custom_target(prepare_umac
|
||||||
|
DEPENDS ${UMAC_MUSASHI_PATH}/m68kops.c
|
||||||
|
)
|
||||||
|
add_dependencies(firmware prepare_umac)
|
||||||
|
|
||||||
|
target_link_libraries(firmware
|
||||||
|
pico_stdlib
|
||||||
|
pico_multicore
|
||||||
|
tinyusb_host
|
||||||
|
tinyusb_board
|
||||||
|
hardware_dma
|
||||||
|
hardware_pio
|
||||||
|
hardware_sync
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(firmware PRIVATE
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/include
|
||||||
|
${TINYUSB_PATH}/hw
|
||||||
|
${TINYUSB_PATH}/src
|
||||||
|
${UMAC_INCLUDE_PATHS}
|
||||||
|
incbin
|
||||||
|
)
|
||||||
|
|
||||||
|
pico_generate_pio_header(firmware ${CMAKE_CURRENT_LIST_DIR}/src/pio_video.pio)
|
||||||
|
|
||||||
|
pico_enable_stdio_uart(firmware 1)
|
||||||
|
|
||||||
|
# Needed for UF2:
|
||||||
|
pico_add_extra_outputs(firmware)
|
||||||
|
|
||||||
|
elseif(PICO_ON_DEVICE)
|
||||||
|
message(WARNING "not building firmware because TinyUSB submodule is not initialized in the SDK")
|
||||||
|
endif()
|
||||||
|
|
||||||
234
README.md
Normal file
234
README.md
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
# Pico Micro Mac (pico-umac)
|
||||||
|
|
||||||
|
v0.1 15 June 2024
|
||||||
|
|
||||||
|
|
||||||
|
This project embeds the [umac Mac 128K
|
||||||
|
emulator](https://github.com/evansm7/umac) project into a Raspberry Pi
|
||||||
|
Pico microcontroller. At long last, the worst Macintosh in a cheap,
|
||||||
|
portable form factor!
|
||||||
|
|
||||||
|
It has features, many features, the best features:
|
||||||
|
|
||||||
|
* Outputs VGA 640x480@60Hz, monochrome, using three resistors
|
||||||
|
* USB HID keyboard and mouse
|
||||||
|
* Read-only disc image in flash (your creations are ephemeral, like life itself)
|
||||||
|
|
||||||
|
Great features. It even doesn't hang at random! (Anymore.)
|
||||||
|
|
||||||
|
So anyway, you can build this project yourself for less than the cost
|
||||||
|
of a beer! You'll need at least a RPi Pico board, a VGA monitor (or
|
||||||
|
VGA-HDMI adapter), a USB mouse (and maybe a USB keyboard/hub), plus a
|
||||||
|
couple of cheap components.
|
||||||
|
|
||||||
|
# Software prerequisites
|
||||||
|
|
||||||
|
Install and build `umac` first. It'll give you a preview of the fun
|
||||||
|
to come, plus is required to generate a patched ROM image.
|
||||||
|
|
||||||
|
## Build essentials
|
||||||
|
|
||||||
|
* git submodules
|
||||||
|
* Clone the repo with `--recursive`, or `git submodule update --init --recursive`
|
||||||
|
* Install/set up the [Pico/RP2040 SDK](https://github.com/raspberrypi/pico-sdk)
|
||||||
|
|
||||||
|
Do the initial Pico SDK `cmake` setup into an out-of-tree build dir:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
(cd build ; PICO_SDK_PATH=/path/to/sdk cmake ..)
|
||||||
|
```
|
||||||
|
|
||||||
|
## ROM image
|
||||||
|
|
||||||
|
The flow is to use `umac` installed on your workstation (e.g. Linux,
|
||||||
|
but WSL may work too) to prepare a patched ROM image.
|
||||||
|
|
||||||
|
`umac` is passed the 4D1F8172 MacPlusv3 ROM, and `-W` to write the
|
||||||
|
post-patching binary out:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/code/umac$ ./main -r '4D1F8172 - MacPlus v3.ROM' -W rom.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disc image
|
||||||
|
|
||||||
|
Grab a Macintosh system disc from somewhere. A 400K or 800K floppy
|
||||||
|
image works just fine, up to System 3.2 (the last version to support
|
||||||
|
Mac128Ks). I've used images from
|
||||||
|
<https://winworldpc.com/product/mac-os-0-6/system-3x> but also check
|
||||||
|
the various forums and MacintoshRepository. See the `umac` README for
|
||||||
|
info on formats (it needs to be raw data without header).
|
||||||
|
|
||||||
|
Let's call this `disc.bin`.
|
||||||
|
|
||||||
|
## Putting it together, and building
|
||||||
|
|
||||||
|
Given the `rom.bin` and `disc.bin` prepared above, you can now
|
||||||
|
generate includes from them and perform the build:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir incbin
|
||||||
|
xxd -i < rom.bin > incbin/umac-rom.h
|
||||||
|
xxd -i < disc.bin > incbin/umac-disc.h
|
||||||
|
make -C build
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll get a `build/firmware.uf2` out the other end. Flash this to
|
||||||
|
your Pico: e.g. plug it in with button held/drag/drop. (When
|
||||||
|
iterating/testing during development, unplugging the OTG cable each
|
||||||
|
time is a pain – I ended up moving to SWD probe programming.)
|
||||||
|
|
||||||
|
The LED should flash at about 2Hz once powered up.
|
||||||
|
|
||||||
|
# Hardware contruction
|
||||||
|
|
||||||
|
It's a simple circuit in terms of having few components: just the
|
||||||
|
Pico, with three series resistors and a VGA connection, and DC power.
|
||||||
|
However, if you're not comfortable soldering then don't choose this as
|
||||||
|
your first project: I don't want you to zap your mouse
|
||||||
|
|
||||||
|
Disclaimer: This is a hardware project with zero warranty. All due
|
||||||
|
care has been taken in design/docs, but if you choose to build it then
|
||||||
|
I disclaim any responsibility for your hardware or personal safety.
|
||||||
|
|
||||||
|
With that out of the way...
|
||||||
|
|
||||||
|
## Theory of operation
|
||||||
|
|
||||||
|
Three 3.3V GPIO pins are driven by PIO to give VSYNC, HSYNC, and video
|
||||||
|
out signals.
|
||||||
|
|
||||||
|
The syncs are in many similar projects driven directly from GPIO, but
|
||||||
|
here I suggest a 66Ω series resistor on each in order to keep the
|
||||||
|
voltages at the VGA end (presumably into 75Ω termination?) in the
|
||||||
|
correct range.
|
||||||
|
|
||||||
|
For the video output, one GPIO drives R,G,B channels for mono/white
|
||||||
|
output. A 100Ω resistor gives roughly 0.7V (max intensity) into 3*75Ω
|
||||||
|
signals.
|
||||||
|
|
||||||
|
That's it... power in, USB adapter.
|
||||||
|
|
||||||
|
|
||||||
|
## Pinout and circuit
|
||||||
|
|
||||||
|
Parts needed:
|
||||||
|
|
||||||
|
* Pico/RP2040 board
|
||||||
|
* USB OTG micro-B to A adapter
|
||||||
|
* USB keyboard, mouse (and hub, if not integrated)
|
||||||
|
* 5V DC supply (600mA+), and maybe a DC jack
|
||||||
|
* 100Ω resistor
|
||||||
|
* 2x 66Ω resistors
|
||||||
|
* VGA DB15 connector, or janky chopped VGA cable
|
||||||
|
|
||||||
|
Pins are given for a RPi Pico board, but this will work on any RP2040
|
||||||
|
board with 2MB+ flash as long as all required GPIOs are pinned out:
|
||||||
|
|
||||||
|
| GPIO/pin | Pico pin | Usage |
|
||||||
|
| ------------ | ------------ | -------------- |
|
||||||
|
| GP0 | 1 | UART0 TX |
|
||||||
|
| GP1 | 2 | UART0 RX |
|
||||||
|
| GP18 | 24 | Video output |
|
||||||
|
| GP19 | 25 | VSYNC |
|
||||||
|
| GP21 | 27 | HSYNC |
|
||||||
|
| Gnd | 23, 28 | Video ground |
|
||||||
|
| VBUS (5V) | 40 | +5V supply |
|
||||||
|
| Gnd | 38 | Supply ground |
|
||||||
|
|
||||||
|
Method:
|
||||||
|
|
||||||
|
* Wire 5V supply to VBUS/Gnd
|
||||||
|
* Video output --> 100Ω --> VGA RGB (pins 1,2,3) all connected together
|
||||||
|
* HSYNC --> 66Ω --> VGA pin 13
|
||||||
|
* VSYNC --> 66Ω --> VGA pin 14
|
||||||
|
* Video ground --> VGA grounds (pins 5-8, 10)
|
||||||
|
|
||||||
|
If you don't have exactly 100Ω, using slightly more is OK but display
|
||||||
|
will be dimmer. If you don't have 66Ω for the syncs, connecting them
|
||||||
|
directly is "probably OK", but YMMV.
|
||||||
|
|
||||||
|
Test your connections: the key part is not getting over 0.7V into your
|
||||||
|
VGA connector's signals.
|
||||||
|
|
||||||
|
Connect USB mouse, and keyboard if you like, and power up.
|
||||||
|
|
||||||
|
# Software
|
||||||
|
|
||||||
|
Both CPU cores are used, and are overclocked (blush) to 250MHz so that
|
||||||
|
Missile Command is enjoyable to play.
|
||||||
|
|
||||||
|
The `umac` emulator and video output runs on core 1, and core 0 deals
|
||||||
|
with USB HID input. Video DMA is initialised pointing to the
|
||||||
|
framebuffer in the Mac's RAM.
|
||||||
|
|
||||||
|
Other than that, it's just a main loop in `main.c` shuffling things
|
||||||
|
into `umac`.
|
||||||
|
|
||||||
|
Quite a lot of optimisation has been done in `umac` and `Musashi` to
|
||||||
|
get performance up on Cortex-M0+ and the RP2040, like careful location
|
||||||
|
of certain routines in RAM, ensuring inlining/constants can be
|
||||||
|
foldeed, etc. It's 5x faster than it was at the beginning.
|
||||||
|
|
||||||
|
The top-level project might be a useful framework for other emulators,
|
||||||
|
or other projects that need USB HID input and a framebuffer (e.g. a
|
||||||
|
VT220 emulator!).
|
||||||
|
|
||||||
|
The USB HID code is largely stolen from the TinyUSB example, but shows
|
||||||
|
how in practice you might capture keypresses/deal with mouse events.
|
||||||
|
|
||||||
|
## Video
|
||||||
|
|
||||||
|
The video system is pretty good and IMHO worth stealing for other
|
||||||
|
projects: It uses one PIO state machine and 3 DMA channels to provide
|
||||||
|
a rock-solid bitmapped 1BPP 640x480 video output. The Mac 512x342
|
||||||
|
framebuffer is centred inside this by using horizontal blanking
|
||||||
|
regions (programmed into the line scan-out) and vertical blanking
|
||||||
|
areas from a dummy "always black" mini-framebuffer.
|
||||||
|
|
||||||
|
It supports (at build time) flexible resolutions/timings. The one
|
||||||
|
caveat (or advantage?) is that it uses an HSYNC IRQ routine to
|
||||||
|
recalculate the next DMA buffer pointer; doing this at scan-time costs
|
||||||
|
about 1% of the CPU time (on core 1). However, it could be used to
|
||||||
|
generate video on-the-fly from characters/tiles without a true
|
||||||
|
framebuffer.
|
||||||
|
|
||||||
|
I'm considering improvements to the video system:
|
||||||
|
|
||||||
|
* Supporting multiple BPP/colour output
|
||||||
|
* Implement the rest of `DE`/display valid strobe support, making
|
||||||
|
driving LCDs possible.
|
||||||
|
* Using a video DMA address list and another DMA channel to reduce
|
||||||
|
the IRQ frequency (CPU overhead) to per-frame, at the cost of a
|
||||||
|
couple of KB of RAM.
|
||||||
|
|
||||||
|
|
||||||
|
# Licence
|
||||||
|
|
||||||
|
`hid.c` and `tusb_config.h` are based on code from the TinyUSB
|
||||||
|
project, which is Copyright (c) 2019, 2021 Ha Thach (tinyusb.org) and
|
||||||
|
released under the MIT licence.
|
||||||
|
|
||||||
|
The remainder of the code is released under the MIT licence:
|
||||||
|
|
||||||
|
Copyright (c) 2024 Matt Evans:
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
1
external/umac
vendored
Submodule
1
external/umac
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 563626a38273656ff08d13a4799fa5d1028b215d
|
||||||
37
include/hw.h
Normal file
37
include/hw.h
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* pico-umac pin definitions
|
||||||
|
*
|
||||||
|
* Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HW_H
|
||||||
|
#define HW_H
|
||||||
|
|
||||||
|
#define GPIO_LED_PIN PICO_DEFAULT_LED_PIN
|
||||||
|
|
||||||
|
#define GPIO_VID_DATA 18
|
||||||
|
#define GPIO_VID_VS 19
|
||||||
|
#define GPIO_VID_CLK 20
|
||||||
|
#define GPIO_VID_HS 21
|
||||||
|
|
||||||
|
#endif
|
||||||
40
include/kbd.h
Normal file
40
include/kbd.h
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* pico-umac keyboard scancode mapping
|
||||||
|
*
|
||||||
|
* Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KBD_H
|
||||||
|
#define KBD_H
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool kbd_queue_empty();
|
||||||
|
/* If empty, return 0, else return a mac keycode in [7:0] and [15] set if a press (else release) */
|
||||||
|
uint16_t kbd_queue_pop();
|
||||||
|
|
||||||
|
/* FIXME: map modifiers */
|
||||||
|
bool kbd_queue_push(uint8_t hid_keycode, bool pressed);
|
||||||
|
|
||||||
|
#endif
|
||||||
94
include/tusb_config.h
Normal file
94
include/tusb_config.h
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _TUSB_CONFIG_H_
|
||||||
|
#define _TUSB_CONFIG_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// COMMON CONFIGURATION
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
// defined by compiler flags for flexibility
|
||||||
|
#ifndef CFG_TUSB_MCU
|
||||||
|
#error CFG_TUSB_MCU must be defined
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX
|
||||||
|
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_HOST | OPT_MODE_HIGH_SPEED)
|
||||||
|
#else
|
||||||
|
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_HOST
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CFG_TUSB_OS
|
||||||
|
#define CFG_TUSB_OS OPT_OS_NONE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
|
||||||
|
// #define CFG_TUSB_DEBUG 0
|
||||||
|
|
||||||
|
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
|
||||||
|
* Tinyusb use follows macros to declare transferring memory so that they can be put
|
||||||
|
* into those specific section.
|
||||||
|
* e.g
|
||||||
|
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||||
|
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||||
|
*/
|
||||||
|
#ifndef CFG_TUSB_MEM_SECTION
|
||||||
|
#define CFG_TUSB_MEM_SECTION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CFG_TUSB_MEM_ALIGN
|
||||||
|
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// CONFIGURATION
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Size of buffer to hold descriptors and other data used for enumeration
|
||||||
|
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||||
|
|
||||||
|
#define CFG_TUH_HUB 1
|
||||||
|
#define CFG_TUH_CDC 0
|
||||||
|
#define CFG_TUH_HID 4 // typical keyboard + mouse device can have 3-4 HID interfaces
|
||||||
|
#define CFG_TUH_MSC 0
|
||||||
|
#define CFG_TUH_VENDOR 0
|
||||||
|
|
||||||
|
// max device support (excluding hub device)
|
||||||
|
#define CFG_TUH_DEVICE_MAX (CFG_TUH_HUB ? 4 : 1) // hub typically has 4 ports
|
||||||
|
|
||||||
|
//------------- HID -------------//
|
||||||
|
#define CFG_TUH_HID_EPIN_BUFSIZE 64
|
||||||
|
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _TUSB_CONFIG_H_ */
|
||||||
33
include/video.h
Normal file
33
include/video.h
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* PIO video output
|
||||||
|
*
|
||||||
|
* Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VIDEO_H
|
||||||
|
#define VIDEO_H
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
void video_init(uint32_t *framebuffer);
|
||||||
|
|
||||||
|
#endif
|
||||||
62
pico_sdk_import.cmake
Normal file
62
pico_sdk_import.cmake
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
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")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
290
src/hid.c
Normal file
290
src/hid.c
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
/*
|
||||||
|
* Derived from pico-examples/usb/host/host_cdc_msc_hid/hid_app.c, which is
|
||||||
|
* Copyright (c) 2021, Ha Thach (tinyusb.org)
|
||||||
|
* Further changes are Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* 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 "bsp/rp2040/board.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
#include "kbd.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// MACRO TYPEDEF CONSTANT ENUM DECLARATION
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// If your host terminal support ansi escape code such as TeraTerm
|
||||||
|
// it can be use to simulate mouse cursor movement within terminal
|
||||||
|
#define USE_ANSI_ESCAPE 0
|
||||||
|
|
||||||
|
#define MAX_REPORT 4
|
||||||
|
|
||||||
|
static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII };
|
||||||
|
|
||||||
|
// Each HID instance can has multiple reports
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
uint8_t report_count;
|
||||||
|
tuh_hid_report_info_t report_info[MAX_REPORT];
|
||||||
|
} hid_info[CFG_TUH_HID];
|
||||||
|
|
||||||
|
static void process_kbd_report(hid_keyboard_report_t const *report);
|
||||||
|
static void process_mouse_report(hid_mouse_report_t const * report);
|
||||||
|
static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);
|
||||||
|
|
||||||
|
void hid_app_task(void)
|
||||||
|
{
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// TinyUSB Callbacks
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// Invoked when device with hid interface is mounted
|
||||||
|
// Report descriptor is also available for use. tuh_hid_parse_report_descriptor()
|
||||||
|
// can be used to parse common/simple enough descriptor.
|
||||||
|
// Note: if report descriptor length > CFG_TUH_ENUMERATION_BUFSIZE, it will be skipped
|
||||||
|
// therefore report_desc = NULL, desc_len = 0
|
||||||
|
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
|
||||||
|
{
|
||||||
|
printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
|
||||||
|
|
||||||
|
// Interface protocol (hid_interface_protocol_enum_t)
|
||||||
|
const char* protocol_str[] = { "None", "Keyboard", "Mouse" };
|
||||||
|
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||||
|
|
||||||
|
printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]);
|
||||||
|
|
||||||
|
// By default host stack will use activate boot protocol on supported interface.
|
||||||
|
// Therefore for this simple example, we only need to parse generic report descriptor (with built-in parser)
|
||||||
|
if ( itf_protocol == HID_ITF_PROTOCOL_NONE )
|
||||||
|
{
|
||||||
|
hid_info[instance].report_count = tuh_hid_parse_report_descriptor(hid_info[instance].report_info, MAX_REPORT, desc_report, desc_len);
|
||||||
|
printf("HID has %u reports \r\n", hid_info[instance].report_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// request to receive report
|
||||||
|
// tuh_hid_report_received_cb() will be invoked when report is available
|
||||||
|
if ( !tuh_hid_receive_report(dev_addr, instance) )
|
||||||
|
{
|
||||||
|
printf("Error: cannot request to receive report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when device with hid interface is un-mounted
|
||||||
|
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
|
||||||
|
{
|
||||||
|
printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received report from device via interrupt endpoint
|
||||||
|
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
|
||||||
|
{
|
||||||
|
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||||
|
|
||||||
|
switch (itf_protocol)
|
||||||
|
{
|
||||||
|
case HID_ITF_PROTOCOL_KEYBOARD:
|
||||||
|
TU_LOG2("HID receive boot keyboard report\r\n");
|
||||||
|
process_kbd_report( (hid_keyboard_report_t const*) report );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HID_ITF_PROTOCOL_MOUSE:
|
||||||
|
TU_LOG2("HID receive boot mouse report\r\n");
|
||||||
|
process_mouse_report( (hid_mouse_report_t const*) report );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Generic report requires matching ReportID and contents with previous parsed report info
|
||||||
|
process_generic_report(dev_addr, instance, report, len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue to request to receive report
|
||||||
|
if ( !tuh_hid_receive_report(dev_addr, instance) )
|
||||||
|
{
|
||||||
|
printf("Error: cannot request to receive report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// Keyboard
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
static inline bool find_key_in_report(hid_keyboard_report_t const *report, uint8_t keycode)
|
||||||
|
{
|
||||||
|
for(uint8_t i=0; i<6; i++)
|
||||||
|
{
|
||||||
|
if (report->keycode[i] == keycode) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_kbd_report(hid_keyboard_report_t const *report)
|
||||||
|
{
|
||||||
|
/* Previous report is stored to compare against for key release: */
|
||||||
|
static hid_keyboard_report_t prev_report = { 0, 0, {0} };
|
||||||
|
|
||||||
|
for(uint8_t i=0; i<6; i++) {
|
||||||
|
if (report->keycode[i]) {
|
||||||
|
if (find_key_in_report(&prev_report, report->keycode[i])) {
|
||||||
|
/* Key held */
|
||||||
|
} else {
|
||||||
|
/* printf("Key pressed: %02x\n", report->keycode[i]); */
|
||||||
|
kbd_queue_push(report->keycode[i], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prev_report.keycode[i] && !find_key_in_report(report, prev_report.keycode[i])) {
|
||||||
|
/* printf("Key released: %02x\n", prev_report.keycode[i]); */
|
||||||
|
kbd_queue_push(prev_report.keycode[i], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t mod_change = report->modifier ^ prev_report.modifier;
|
||||||
|
if (mod_change) {
|
||||||
|
uint8_t mp = mod_change & report->modifier;
|
||||||
|
uint8_t mr = mod_change & prev_report.modifier;
|
||||||
|
if (mp) {
|
||||||
|
/* printf("Modifiers pressed %02x\n", mp); */
|
||||||
|
mp = (mp | (mp >> 4)) & 0xf; /* Don't care if left or right :P */
|
||||||
|
if (mp & 1)
|
||||||
|
kbd_queue_push(HID_KEY_CONTROL_LEFT, true);
|
||||||
|
if (mp & 2)
|
||||||
|
kbd_queue_push(HID_KEY_SHIFT_LEFT, true);
|
||||||
|
if (mp & 4)
|
||||||
|
kbd_queue_push(HID_KEY_ALT_LEFT, true);
|
||||||
|
if (mp & 8)
|
||||||
|
kbd_queue_push(HID_KEY_GUI_LEFT, true);
|
||||||
|
}
|
||||||
|
if (mr) {
|
||||||
|
/* printf("Modifiers released %02x\n", mr); */
|
||||||
|
mr = (mr | (mr >> 4)) & 0xf;
|
||||||
|
if (mr & 1)
|
||||||
|
kbd_queue_push(HID_KEY_CONTROL_LEFT, false);
|
||||||
|
if (mr & 2)
|
||||||
|
kbd_queue_push(HID_KEY_SHIFT_LEFT, false);
|
||||||
|
if (mr & 4)
|
||||||
|
kbd_queue_push(HID_KEY_ALT_LEFT, false);
|
||||||
|
if (mr & 8)
|
||||||
|
kbd_queue_push(HID_KEY_GUI_LEFT, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prev_report = *report;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// Mouse
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
|
||||||
|
/* Exported for use by other thread! */
|
||||||
|
int cursor_x = 0;
|
||||||
|
int cursor_y = 0;
|
||||||
|
int cursor_button = 0;
|
||||||
|
|
||||||
|
#define MAX_DELTA 8
|
||||||
|
|
||||||
|
static int clamp(int i)
|
||||||
|
{
|
||||||
|
return (i >= 0) ? (i > MAX_DELTA ? MAX_DELTA : i) :
|
||||||
|
(i < -MAX_DELTA ? -MAX_DELTA : i);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_mouse_report(hid_mouse_report_t const * report)
|
||||||
|
{
|
||||||
|
static hid_mouse_report_t prev_report = { 0 };
|
||||||
|
uint8_t button_changed_mask = report->buttons ^ prev_report.buttons;
|
||||||
|
/* report->wheel can be used too... */
|
||||||
|
|
||||||
|
cursor_button = !!(report->buttons & MOUSE_BUTTON_LEFT);
|
||||||
|
cursor_x += clamp(report->x);
|
||||||
|
cursor_y += clamp(report->y);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
// Generic Report
|
||||||
|
//--------------------------------------------------------------------+
|
||||||
|
static void process_generic_report(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
|
||||||
|
{
|
||||||
|
(void) dev_addr;
|
||||||
|
|
||||||
|
uint8_t const rpt_count = hid_info[instance].report_count;
|
||||||
|
tuh_hid_report_info_t* rpt_info_arr = hid_info[instance].report_info;
|
||||||
|
tuh_hid_report_info_t* rpt_info = NULL;
|
||||||
|
|
||||||
|
if ( rpt_count == 1 && rpt_info_arr[0].report_id == 0)
|
||||||
|
{
|
||||||
|
// Simple report without report ID as 1st byte
|
||||||
|
rpt_info = &rpt_info_arr[0];
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
// Composite report, 1st byte is report ID, data starts from 2nd byte
|
||||||
|
uint8_t const rpt_id = report[0];
|
||||||
|
|
||||||
|
// Find report id in the arrray
|
||||||
|
for(uint8_t i=0; i<rpt_count; i++)
|
||||||
|
{
|
||||||
|
if (rpt_id == rpt_info_arr[i].report_id )
|
||||||
|
{
|
||||||
|
rpt_info = &rpt_info_arr[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
report++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rpt_info)
|
||||||
|
{
|
||||||
|
printf("Couldn't find the report info for this report !\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For complete list of Usage Page & Usage checkout src/class/hid/hid.h. For examples:
|
||||||
|
// - Keyboard : Desktop, Keyboard
|
||||||
|
// - Mouse : Desktop, Mouse
|
||||||
|
// - Gamepad : Desktop, Gamepad
|
||||||
|
// - Consumer Control (Media Key) : Consumer, Consumer Control
|
||||||
|
// - System Control (Power key) : Desktop, System Control
|
||||||
|
// - Generic (vendor) : 0xFFxx, xx
|
||||||
|
if ( rpt_info->usage_page == HID_USAGE_PAGE_DESKTOP )
|
||||||
|
{
|
||||||
|
switch (rpt_info->usage)
|
||||||
|
{
|
||||||
|
case HID_USAGE_DESKTOP_KEYBOARD:
|
||||||
|
TU_LOG1("HID receive keyboard report\r\n");
|
||||||
|
// Assume keyboard follow boot report layout
|
||||||
|
process_kbd_report( (hid_keyboard_report_t const*) report );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HID_USAGE_DESKTOP_MOUSE:
|
||||||
|
TU_LOG1("HID receive mouse report\r\n");
|
||||||
|
// Assume mouse follow boot report layout
|
||||||
|
process_mouse_report( (hid_mouse_report_t const*) report );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
199
src/kbd.c
Normal file
199
src/kbd.c
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
/* HID to Mac keyboard scancode mapping
|
||||||
|
*
|
||||||
|
* FIXME: This doesn't do capslock (needs to track toggle), and arrow
|
||||||
|
* keys don't work.
|
||||||
|
*
|
||||||
|
* Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include "kbd.h"
|
||||||
|
|
||||||
|
#include "class/hid/hid.h"
|
||||||
|
#include "keymap.h"
|
||||||
|
|
||||||
|
#define KQ_SIZE 32
|
||||||
|
#define KQ_MASK (KQ_SIZE-1)
|
||||||
|
|
||||||
|
static uint16_t kbd_queue[KQ_SIZE];
|
||||||
|
static unsigned int kbd_queue_prod = 0;
|
||||||
|
static unsigned int kbd_queue_cons = 0;
|
||||||
|
|
||||||
|
static bool kbd_queue_full()
|
||||||
|
{
|
||||||
|
return ((kbd_queue_prod + 1) & KQ_MASK) == kbd_queue_cons;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool kbd_queue_empty()
|
||||||
|
{
|
||||||
|
return kbd_queue_prod == kbd_queue_cons;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If empty, return 0, else return a mac keycode in [7:0] and [15] set if a press (else release) */
|
||||||
|
uint16_t kbd_queue_pop()
|
||||||
|
{
|
||||||
|
if (kbd_queue_empty())
|
||||||
|
return 0;
|
||||||
|
uint16_t v = kbd_queue[kbd_queue_cons];
|
||||||
|
kbd_queue_cons = (kbd_queue_cons + 1) & KQ_MASK;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t hid_to_mac[256] = {
|
||||||
|
[HID_KEY_NONE] = 0,
|
||||||
|
[HID_KEY_A] = 255, // Hack for MKC_A,
|
||||||
|
[HID_KEY_B] = MKC_B,
|
||||||
|
[HID_KEY_C] = MKC_C,
|
||||||
|
[HID_KEY_D] = MKC_D,
|
||||||
|
[HID_KEY_E] = MKC_E,
|
||||||
|
[HID_KEY_F] = MKC_F,
|
||||||
|
[HID_KEY_G] = MKC_G,
|
||||||
|
[HID_KEY_H] = MKC_H,
|
||||||
|
[HID_KEY_I] = MKC_I,
|
||||||
|
[HID_KEY_J] = MKC_J,
|
||||||
|
[HID_KEY_K] = MKC_K,
|
||||||
|
[HID_KEY_L] = MKC_L,
|
||||||
|
[HID_KEY_M] = MKC_M,
|
||||||
|
[HID_KEY_N] = MKC_N,
|
||||||
|
[HID_KEY_O] = MKC_O,
|
||||||
|
[HID_KEY_P] = MKC_P,
|
||||||
|
[HID_KEY_Q] = MKC_Q,
|
||||||
|
[HID_KEY_R] = MKC_R,
|
||||||
|
[HID_KEY_S] = MKC_S,
|
||||||
|
[HID_KEY_T] = MKC_T,
|
||||||
|
[HID_KEY_U] = MKC_U,
|
||||||
|
[HID_KEY_V] = MKC_V,
|
||||||
|
[HID_KEY_W] = MKC_W,
|
||||||
|
[HID_KEY_X] = MKC_X,
|
||||||
|
[HID_KEY_Y] = MKC_Y,
|
||||||
|
[HID_KEY_Z] = MKC_Z,
|
||||||
|
[HID_KEY_1] = MKC_1,
|
||||||
|
[HID_KEY_2] = MKC_2,
|
||||||
|
[HID_KEY_3] = MKC_3,
|
||||||
|
[HID_KEY_4] = MKC_4,
|
||||||
|
[HID_KEY_5] = MKC_5,
|
||||||
|
[HID_KEY_6] = MKC_6,
|
||||||
|
[HID_KEY_7] = MKC_7,
|
||||||
|
[HID_KEY_8] = MKC_8,
|
||||||
|
[HID_KEY_9] = MKC_9,
|
||||||
|
[HID_KEY_0] = MKC_0,
|
||||||
|
[HID_KEY_ENTER] = MKC_Return,
|
||||||
|
[HID_KEY_ESCAPE] = MKC_Escape,
|
||||||
|
[HID_KEY_BACKSPACE] = MKC_BackSpace,
|
||||||
|
[HID_KEY_TAB] = MKC_Tab,
|
||||||
|
[HID_KEY_SPACE] = MKC_Space,
|
||||||
|
[HID_KEY_MINUS] = MKC_Minus,
|
||||||
|
[HID_KEY_EQUAL] = MKC_Equal,
|
||||||
|
[HID_KEY_BRACKET_LEFT] = MKC_LeftBracket,
|
||||||
|
[HID_KEY_BRACKET_RIGHT] = MKC_RightBracket,
|
||||||
|
[HID_KEY_BACKSLASH] = MKC_BackSlash,
|
||||||
|
[HID_KEY_SEMICOLON] = MKC_SemiColon,
|
||||||
|
[HID_KEY_APOSTROPHE] = MKC_SingleQuote,
|
||||||
|
[HID_KEY_GRAVE] = MKC_Grave,
|
||||||
|
[HID_KEY_COMMA] = MKC_Comma,
|
||||||
|
[HID_KEY_PERIOD] = MKC_Period,
|
||||||
|
[HID_KEY_SLASH] = MKC_Slash,
|
||||||
|
[HID_KEY_CAPS_LOCK] = MKC_CapsLock,
|
||||||
|
[HID_KEY_F1] = MKC_F1,
|
||||||
|
[HID_KEY_F2] = MKC_F2,
|
||||||
|
[HID_KEY_F3] = MKC_F3,
|
||||||
|
[HID_KEY_F4] = MKC_F4,
|
||||||
|
[HID_KEY_F5] = MKC_F5,
|
||||||
|
[HID_KEY_F6] = MKC_F6,
|
||||||
|
[HID_KEY_F7] = MKC_F7,
|
||||||
|
[HID_KEY_F8] = MKC_F8,
|
||||||
|
[HID_KEY_F9] = MKC_F9,
|
||||||
|
[HID_KEY_F10] = MKC_F10,
|
||||||
|
[HID_KEY_F11] = MKC_F11,
|
||||||
|
[HID_KEY_F12] = MKC_F12,
|
||||||
|
[HID_KEY_PRINT_SCREEN] = MKC_Print,
|
||||||
|
[HID_KEY_SCROLL_LOCK] = MKC_ScrollLock,
|
||||||
|
[HID_KEY_PAUSE] = MKC_Pause,
|
||||||
|
[HID_KEY_INSERT] = MKC_Help,
|
||||||
|
[HID_KEY_HOME] = MKC_Home,
|
||||||
|
[HID_KEY_PAGE_UP] = MKC_PageUp,
|
||||||
|
[HID_KEY_DELETE] = MKC_BackSpace,
|
||||||
|
[HID_KEY_END] = MKC_End,
|
||||||
|
[HID_KEY_PAGE_DOWN] = MKC_PageDown,
|
||||||
|
[HID_KEY_ARROW_RIGHT] = MKC_Right,
|
||||||
|
[HID_KEY_ARROW_LEFT] = MKC_Left,
|
||||||
|
[HID_KEY_ARROW_DOWN] = MKC_Down,
|
||||||
|
[HID_KEY_ARROW_UP] = MKC_Up,
|
||||||
|
/* [HID_KEY_NUM_LOCK] = MKC_, */
|
||||||
|
[HID_KEY_KEYPAD_DIVIDE] = MKC_KPDevide,
|
||||||
|
[HID_KEY_KEYPAD_MULTIPLY] = MKC_KPMultiply,
|
||||||
|
[HID_KEY_KEYPAD_SUBTRACT] = MKC_KPSubtract,
|
||||||
|
[HID_KEY_KEYPAD_ADD] = MKC_KPAdd,
|
||||||
|
[HID_KEY_KEYPAD_ENTER] = MKC_Enter,
|
||||||
|
[HID_KEY_KEYPAD_1] = MKC_KP1,
|
||||||
|
[HID_KEY_KEYPAD_2] = MKC_KP2,
|
||||||
|
[HID_KEY_KEYPAD_3] = MKC_KP3,
|
||||||
|
[HID_KEY_KEYPAD_4] = MKC_KP4,
|
||||||
|
[HID_KEY_KEYPAD_5] = MKC_KP5,
|
||||||
|
[HID_KEY_KEYPAD_6] = MKC_KP6,
|
||||||
|
[HID_KEY_KEYPAD_7] = MKC_KP7,
|
||||||
|
[HID_KEY_KEYPAD_8] = MKC_KP8,
|
||||||
|
[HID_KEY_KEYPAD_9] = MKC_KP9,
|
||||||
|
[HID_KEY_KEYPAD_0] = MKC_KP0,
|
||||||
|
[HID_KEY_KEYPAD_DECIMAL] = MKC_Decimal,
|
||||||
|
[HID_KEY_KEYPAD_EQUAL] = MKC_Equal,
|
||||||
|
[HID_KEY_RETURN] = MKC_Return,
|
||||||
|
/* [HID_KEY_POWER] = MKC_, */
|
||||||
|
/* [HID_KEY_KEYPAD_COMMA] = MKC_, */
|
||||||
|
/* [HID_KEY_KEYPAD_EQUAL_SIGN] = MKC_, */
|
||||||
|
[HID_KEY_CONTROL_LEFT] = MKC_Control,
|
||||||
|
[HID_KEY_SHIFT_LEFT] = MKC_Shift,
|
||||||
|
[HID_KEY_ALT_LEFT] = MKC_Option,
|
||||||
|
[HID_KEY_GUI_LEFT] = MKC_Command,
|
||||||
|
[HID_KEY_CONTROL_RIGHT] = MKC_Control,
|
||||||
|
[HID_KEY_SHIFT_RIGHT] = MKC_Shift,
|
||||||
|
[HID_KEY_ALT_RIGHT] = MKC_Option,
|
||||||
|
[HID_KEY_GUI_RIGHT] = MKC_Command,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool kbd_map(uint8_t hid_keycode, bool pressed, uint16_t *key_out)
|
||||||
|
{
|
||||||
|
uint8_t k = hid_to_mac[hid_keycode];
|
||||||
|
if (!k)
|
||||||
|
return false;
|
||||||
|
if (k == 255)
|
||||||
|
k = MKC_A; // Hack, this is zero
|
||||||
|
k = (k << 1) | 1; // FIXME just do this in the #defines
|
||||||
|
*key_out = k | (pressed ? 0x8000 : 0); /* Convention w.r.t. main */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool kbd_queue_push(uint8_t hid_keycode, bool pressed)
|
||||||
|
{
|
||||||
|
if (kbd_queue_full())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
uint16_t v;
|
||||||
|
if (!kbd_map(hid_keycode, pressed, &v))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
kbd_queue[kbd_queue_prod] = v;
|
||||||
|
kbd_queue_prod = (kbd_queue_prod + 1) & KQ_MASK;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
181
src/main.c
Normal file
181
src/main.c
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
/* pico-umac
|
||||||
|
*
|
||||||
|
* Main loop to initialise umac, and run main event loop (piping
|
||||||
|
* keyboard/mouse events in).
|
||||||
|
*
|
||||||
|
* Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/pio.h"
|
||||||
|
#include "hardware/sync.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/time.h"
|
||||||
|
#include "hw.h"
|
||||||
|
#include "video.h"
|
||||||
|
#include "kbd.h"
|
||||||
|
|
||||||
|
#include "bsp/rp2040/board.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
#include "umac.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Imports and data
|
||||||
|
|
||||||
|
extern void hid_app_task(void);
|
||||||
|
extern int cursor_x;
|
||||||
|
extern int cursor_y;
|
||||||
|
extern int cursor_button;
|
||||||
|
|
||||||
|
// Mac binary data: disc and ROM images
|
||||||
|
static const uint8_t umac_disc[] = {
|
||||||
|
#include "umac-disc.h"
|
||||||
|
};
|
||||||
|
static const uint8_t umac_rom[] = {
|
||||||
|
#include "umac-rom.h"
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t umac_ram[RAM_SIZE];
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void io_init()
|
||||||
|
{
|
||||||
|
gpio_init(GPIO_LED_PIN);
|
||||||
|
gpio_set_dir(GPIO_LED_PIN, GPIO_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void poll_led_etc()
|
||||||
|
{
|
||||||
|
static int led_on = 0;
|
||||||
|
static absolute_time_t last = 0;
|
||||||
|
absolute_time_t now = get_absolute_time();
|
||||||
|
|
||||||
|
if (absolute_time_diff_us(last, now) > 500*1000) {
|
||||||
|
last = now;
|
||||||
|
|
||||||
|
led_on ^= 1;
|
||||||
|
gpio_put(GPIO_LED_PIN, led_on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int umac_cursor_x = 0;
|
||||||
|
static int umac_cursor_y = 0;
|
||||||
|
static int umac_cursor_button = 0;
|
||||||
|
|
||||||
|
static void poll_umac()
|
||||||
|
{
|
||||||
|
static absolute_time_t last_1hz = 0;
|
||||||
|
static absolute_time_t last_vsync = 0;
|
||||||
|
absolute_time_t now = get_absolute_time();
|
||||||
|
|
||||||
|
umac_loop();
|
||||||
|
|
||||||
|
int64_t p_1hz = absolute_time_diff_us(last_1hz, now);
|
||||||
|
int64_t p_vsync = absolute_time_diff_us(last_vsync, now);
|
||||||
|
if (p_vsync >= 16667) {
|
||||||
|
/* FIXME: Trigger this off actual vsync */
|
||||||
|
umac_vsync_event();
|
||||||
|
last_vsync = now;
|
||||||
|
}
|
||||||
|
if (p_1hz >= 1000000) {
|
||||||
|
umac_1hz_event();
|
||||||
|
last_1hz = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
int update = 0;
|
||||||
|
int dx = 0;
|
||||||
|
int dy = 0;
|
||||||
|
int b = umac_cursor_button;
|
||||||
|
if (cursor_x != umac_cursor_x) {
|
||||||
|
dx = cursor_x - umac_cursor_x;
|
||||||
|
umac_cursor_x = cursor_x;
|
||||||
|
update = 1;
|
||||||
|
}
|
||||||
|
if (cursor_y != umac_cursor_y) {
|
||||||
|
dy = cursor_y - umac_cursor_y;
|
||||||
|
umac_cursor_y = cursor_y;
|
||||||
|
update = 1;
|
||||||
|
}
|
||||||
|
if (cursor_button != umac_cursor_button) {
|
||||||
|
b = cursor_button;
|
||||||
|
umac_cursor_button = cursor_button;
|
||||||
|
update = 1;
|
||||||
|
}
|
||||||
|
if (update) {
|
||||||
|
umac_mouse(dx, -dy, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!kbd_queue_empty()) {
|
||||||
|
uint16_t k = kbd_queue_pop();
|
||||||
|
umac_kbd_event(k & 0xff, !!(k & 0x8000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void core1_main()
|
||||||
|
{
|
||||||
|
printf("Core 1 started\n");
|
||||||
|
|
||||||
|
disc_descr_t discs[DISC_NUM_DRIVES] = {0};
|
||||||
|
discs[0].base = (void *)umac_disc;
|
||||||
|
discs[0].read_only = 1;
|
||||||
|
discs[0].size = sizeof(umac_disc);
|
||||||
|
|
||||||
|
umac_init(umac_ram, (void *)umac_rom, discs);
|
||||||
|
/* Video runs on core 1, i.e. IRQs/DMA are unaffected by
|
||||||
|
* core 0's USB activity.
|
||||||
|
*/
|
||||||
|
video_init((uint32_t *)(umac_ram + umac_get_fb_offset()));
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
poll_umac();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
set_sys_clock_khz(250*1000, true);
|
||||||
|
|
||||||
|
stdio_init_all();
|
||||||
|
io_init();
|
||||||
|
|
||||||
|
multicore_launch_core1(core1_main);
|
||||||
|
|
||||||
|
printf("Starting, init usb\n");
|
||||||
|
tusb_init();
|
||||||
|
|
||||||
|
/* This happens on core 0: */
|
||||||
|
while (true) {
|
||||||
|
tuh_task();
|
||||||
|
hid_app_task();
|
||||||
|
poll_led_etc();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
194
src/pio_video.pio
Normal file
194
src/pio_video.pio
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
; PIO video output:
|
||||||
|
; This scans out video lines, characteristically some number of bits per pixel,
|
||||||
|
; a pixel clock, and timing signals HSync, VSync (in future, DE too).
|
||||||
|
;
|
||||||
|
; Copyright 2024 Matt Evans
|
||||||
|
;
|
||||||
|
; 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.
|
||||||
|
;
|
||||||
|
;
|
||||||
|
; The source of image data is the OUT FIFO, and a corresponding C routine
|
||||||
|
; needs to fill this with image data. That data might be generated on
|
||||||
|
; the fly, or constructed by setting DMA pointers to a framebuffer.
|
||||||
|
;
|
||||||
|
; A typical usage would be the C routine preparing one scanline of data
|
||||||
|
; and setting off DMA. That's a good balance between number of interrupts
|
||||||
|
; and amount of buffering/RAM required (a framebuffer is generally pretty
|
||||||
|
; large...)
|
||||||
|
;
|
||||||
|
; Supports a max of 15bpp. That's tons given the number of IO... expecting
|
||||||
|
; to use this with 8, 3, etc.
|
||||||
|
;
|
||||||
|
; The output pins are required to be, in this order,
|
||||||
|
; 0: Video data
|
||||||
|
; 0+BPP: Vsync
|
||||||
|
; 1+BPP: PClk
|
||||||
|
; 2+BPP: Hsync
|
||||||
|
;
|
||||||
|
; FIXME: 1BPP for now
|
||||||
|
;
|
||||||
|
; The horizontal timing information is embedded in the data read via
|
||||||
|
; the FIFO, as follows, shown from the very start of a frame. The vertical
|
||||||
|
; timing info is generated entirely from the C side by passing a VSync
|
||||||
|
; value in the data stream. The data stream for each line is:
|
||||||
|
;
|
||||||
|
; ---------- Config information: (offset 0 on each line) ----------
|
||||||
|
; 32b: Timing/sync info:
|
||||||
|
; [31] Vsync value for this line
|
||||||
|
; [30:23] Hsync width (HSW)
|
||||||
|
; [22:15] HBP width minus 3 (FIXME: check)
|
||||||
|
; [14:7] HFP width minus 3
|
||||||
|
; 32b: Number of visible pixels per line
|
||||||
|
; ---------- Pixel data: (offset 8 on each line) -------------------
|
||||||
|
; <X * bytes_per_pixel>: video data (padded with zeros for HBP/HFP pixels)
|
||||||
|
; -------------------------------------------------------------------
|
||||||
|
;
|
||||||
|
; + +--------------------------------------------------
|
||||||
|
; | |HBP- -HFP
|
||||||
|
; +-+
|
||||||
|
; +--+ *****************************************************
|
||||||
|
; | *****************************************************
|
||||||
|
; +--+ *****************************************************
|
||||||
|
; |VBP *****************************************************
|
||||||
|
; | &&&&&&&+----------------------------------------+%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| Active area |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&| |%%%%
|
||||||
|
; | &&&&&&&+----------------------------------------+%%%%
|
||||||
|
; |VFP @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
; |: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
; |: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
||||||
|
;
|
||||||
|
; The HFP/HBP pixels should be written zero. Clever DMA programming can
|
||||||
|
; provide these from a separate location to the video data.
|
||||||
|
;
|
||||||
|
; FIXME: Add DE (and therefore full HBP/HFP counters in timing word)
|
||||||
|
;
|
||||||
|
; There are a couple of pin-mapping tricks going on. We need to be
|
||||||
|
; able to change the video without messing wtih VS, and we want to
|
||||||
|
; assert HS/VS at the same instant. That means the syncs aren't part
|
||||||
|
; of the OUT mapping -- this is only the pixel data. The SET mapping
|
||||||
|
; controls VS, and SIDESET controls HS/clk.
|
||||||
|
;
|
||||||
|
; The advantage of OUT being solely for data is then being able to easily
|
||||||
|
; extend to multiple BPP. Note the HS/VS are active-high in terms of
|
||||||
|
; programming, but the output signal can be flipped at GPIO using the
|
||||||
|
; inversion feature.
|
||||||
|
|
||||||
|
; .define BPP 123 etc.
|
||||||
|
|
||||||
|
.program pio_video
|
||||||
|
.side_set 2 ; SS[0] is clk, SS[1] is HS
|
||||||
|
|
||||||
|
frame_start:
|
||||||
|
; The first word gives VS/HSW: [31]=vsync; [30:23]=HSW
|
||||||
|
; [22:15]=HBP, [14:7]=HFP. (shifted left!)
|
||||||
|
;
|
||||||
|
; Set VS on the same cycle as asserting HS.
|
||||||
|
; Note: these cycles are part of HFP.
|
||||||
|
out X, 1 side 0
|
||||||
|
jmp !X, vs_inactive side 1
|
||||||
|
vs_active:
|
||||||
|
set pins, 1 side 2
|
||||||
|
jmp now_read_HSW side 3
|
||||||
|
vs_inactive:
|
||||||
|
set pins, 0 side 2
|
||||||
|
nop side 3
|
||||||
|
|
||||||
|
now_read_HSW: ; X=hsync width:
|
||||||
|
out X, 8 side 2
|
||||||
|
hsw_loop: nop side 3
|
||||||
|
jmp X-- hsw_loop side 2
|
||||||
|
|
||||||
|
; De-assert hsync (leave Vsync as-is) and shift out HBP:
|
||||||
|
now_read_HBP: ; X=HBP width:
|
||||||
|
out X, 8 side 1
|
||||||
|
hbp_loop: nop side 0
|
||||||
|
jmp X-- hbp_loop side 1
|
||||||
|
|
||||||
|
now_read_HFP: ; Y=HFP width:
|
||||||
|
out Y, 8 side 0
|
||||||
|
|
||||||
|
; Pull, discarding the remainder of OSR. This prepares X pixel count.
|
||||||
|
; Note: these cycles (and HFP read) are part of HBP.
|
||||||
|
pull block side 1
|
||||||
|
out X, 32 side 0
|
||||||
|
nop side 1
|
||||||
|
pixels_loop: ; OSR primed/autopulled
|
||||||
|
; FIXME: side-set DE=1
|
||||||
|
out pins, 1 side 0 ; BPP
|
||||||
|
jmp X-- pixels_loop side 1
|
||||||
|
; FIXME: side-set DE=0
|
||||||
|
; Set video BLACK (1)
|
||||||
|
mov pins, !NULL side 0
|
||||||
|
nop side 1
|
||||||
|
; Now perform HFP delay
|
||||||
|
hfp_loop: nop side 0
|
||||||
|
jmp Y-- hfp_loop side 1
|
||||||
|
|
||||||
|
; A free HFP pixel, to prime for next line:
|
||||||
|
// Auto-pull gets next line (always a multiple of 32b)
|
||||||
|
nop side 0
|
||||||
|
jmp frame_start side 1
|
||||||
|
|
||||||
|
; HFP 2 min
|
||||||
|
; HBP 2 min
|
||||||
|
|
||||||
|
|
||||||
|
% c-sdk {
|
||||||
|
static inline void pio_video_program_init(PIO pio, uint sm, uint offset,
|
||||||
|
uint video_pin /* then VS, CLK, HS */,
|
||||||
|
float clk_div) {
|
||||||
|
/* Outputs are consecutive up from Video data */
|
||||||
|
uint vsync_pin = video_pin+1;
|
||||||
|
uint clk_pin = video_pin+2;
|
||||||
|
uint hsync_pin = video_pin+3;
|
||||||
|
/* Init GPIO & directions */
|
||||||
|
pio_gpio_init(pio, video_pin);
|
||||||
|
pio_gpio_init(pio, hsync_pin);
|
||||||
|
pio_gpio_init(pio, vsync_pin);
|
||||||
|
pio_gpio_init(pio, clk_pin);
|
||||||
|
// FIXME: BPP define
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, video_pin, 4, true /* out */);
|
||||||
|
|
||||||
|
pio_sm_config c = pio_video_program_get_default_config(offset);
|
||||||
|
sm_config_set_out_pins(&c, video_pin, 1);
|
||||||
|
sm_config_set_set_pins(&c, vsync_pin, 1);
|
||||||
|
sm_config_set_sideset_pins(&c, clk_pin); /* CLK + HS */
|
||||||
|
/* Sideset bits are configured via .side_set directive above */
|
||||||
|
sm_config_set_out_shift(&c, false /* OUT MSBs first */, true /* Autopull */, 32 /* bits */);
|
||||||
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||||
|
sm_config_set_clkdiv(&c, clk_div);
|
||||||
|
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
pio_sm_set_enabled(pio, sm, true);
|
||||||
|
}
|
||||||
|
%}
|
||||||
344
src/video.c
Normal file
344
src/video.c
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
/* Video output:
|
||||||
|
*
|
||||||
|
* Using PIO[1], output the Mac 512x342 1BPP framebuffer to VGA/pins. This is done
|
||||||
|
* directly from the Mac framebuffer (without having to reformat in an intermediate
|
||||||
|
* buffer). The video output is 640x480, with the visible pixel data centred with
|
||||||
|
* borders: for analog VGA this is easy, as it just means increasing the horizontal
|
||||||
|
* back porch/front porch (time between syncs and active video) and reducing the
|
||||||
|
* display portion of a line.
|
||||||
|
*
|
||||||
|
* [1]: see pio_video.pio
|
||||||
|
*
|
||||||
|
* Copyright 2024 Matt Evans
|
||||||
|
*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "hardware/dma.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "pio_video.pio.h"
|
||||||
|
|
||||||
|
#include "hw.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/* VESA VGA mode 640x480@60 */
|
||||||
|
|
||||||
|
/* The pixel clock _should_ be (125/2/25.175) (about 2.483) but that seems to
|
||||||
|
* make my VGA-HDMI adapter sample weird, and pixels crawl. Fudge a little,
|
||||||
|
* looks better:
|
||||||
|
*/
|
||||||
|
#define VIDEO_PCLK_MULT (2.5*2)
|
||||||
|
#define VIDEO_HSW 96
|
||||||
|
#define VIDEO_HBP 48
|
||||||
|
#define VIDEO_HRES 640
|
||||||
|
#define VIDEO_HFP 16
|
||||||
|
#define VIDEO_H_TOTAL_NOSYNC (VIDEO_HBP + VIDEO_HRES + VIDEO_HFP)
|
||||||
|
#define VIDEO_VSW 2
|
||||||
|
#define VIDEO_VBP 33
|
||||||
|
#define VIDEO_VRES 480
|
||||||
|
#define VIDEO_VFP 10
|
||||||
|
#define VIDEO_V_TOTAL (VIDEO_VSW + VIDEO_VBP + VIDEO_VRES + VIDEO_VFP)
|
||||||
|
/* The visible vertical span in the VGA output, [start, end) lines: */
|
||||||
|
#define VIDEO_V_VIS_START (VIDEO_VSW + VIDEO_VBP)
|
||||||
|
#define VIDEO_V_VIS_END (VIDEO_V_VIS_START + VIDEO_VRES)
|
||||||
|
|
||||||
|
#define VIDEO_FB_HRES 512
|
||||||
|
#define VIDEO_FB_VRES 342
|
||||||
|
|
||||||
|
/* The lines at which the FB data is actively output: */
|
||||||
|
#define VIDEO_FB_V_VIS_START (VIDEO_V_VIS_START + ((VIDEO_VRES - VIDEO_FB_VRES)/2))
|
||||||
|
#define VIDEO_FB_V_VIS_END (VIDEO_FB_V_VIS_START + VIDEO_FB_VRES)
|
||||||
|
|
||||||
|
/* Words of 1BPP pixel data per line; this dictates the length of the
|
||||||
|
* video data DMA transfer:
|
||||||
|
*/
|
||||||
|
#define VIDEO_VISIBLE_WPL (VIDEO_FB_HRES / 32)
|
||||||
|
|
||||||
|
#if (VIDEO_HRES & 31)
|
||||||
|
#error "VIDEO_HRES: must be a multiple of 32b!"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Video DMA, framebuffer pointers
|
||||||
|
|
||||||
|
static uint32_t video_null[VIDEO_VISIBLE_WPL];
|
||||||
|
static uint32_t *video_framebuffer;
|
||||||
|
|
||||||
|
/* DMA buffer containing 2 pairs of per-line config words, for VS and not-VS: */
|
||||||
|
static uint32_t video_dma_cfg[4];
|
||||||
|
|
||||||
|
/* 3 DMA channels are used. The first to transfer data to PIO, and
|
||||||
|
* the other two to transfer descriptors to the first channel.
|
||||||
|
*/
|
||||||
|
static uint8_t video_dmach_tx;
|
||||||
|
static uint8_t video_dmach_descr_cfg;
|
||||||
|
static uint8_t video_dmach_descr_data;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const void *raddr;
|
||||||
|
void *waddr;
|
||||||
|
uint32_t count;
|
||||||
|
uint32_t ctrl;
|
||||||
|
} dma_descr_t;
|
||||||
|
|
||||||
|
static dma_descr_t video_dmadescr_cfg;
|
||||||
|
static dma_descr_t video_dmadescr_data;
|
||||||
|
|
||||||
|
static volatile unsigned int video_current_y = 0;
|
||||||
|
|
||||||
|
static int __not_in_flash_func(video_get_visible_y)(unsigned int y) {
|
||||||
|
if ((y >= VIDEO_FB_V_VIS_START) && (y < VIDEO_FB_V_VIS_END)) {
|
||||||
|
return y - VIDEO_FB_V_VIS_START;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint32_t *__not_in_flash_func(video_line_addr)(unsigned int y)
|
||||||
|
{
|
||||||
|
int vy = video_get_visible_y(y);
|
||||||
|
if (vy >= 0)
|
||||||
|
return (const uint32_t *)&video_framebuffer[vy * VIDEO_VISIBLE_WPL];
|
||||||
|
else
|
||||||
|
return (const uint32_t *)video_null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint32_t *__not_in_flash_func(video_cfg_addr)(unsigned int y)
|
||||||
|
{
|
||||||
|
return &video_dma_cfg[(y < VIDEO_VSW) ? 0 : 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __not_in_flash_func(video_dma_prep_new)()
|
||||||
|
{
|
||||||
|
/* The descriptor DMA read pointers have moved on; reset them.
|
||||||
|
* The write pointers wrap so should be pointing to the
|
||||||
|
* correct DMA regs.
|
||||||
|
*/
|
||||||
|
dma_hw->ch[video_dmach_descr_cfg].read_addr = (uintptr_t)&video_dmadescr_cfg;
|
||||||
|
dma_hw->ch[video_dmach_descr_cfg].transfer_count = 4;
|
||||||
|
dma_hw->ch[video_dmach_descr_data].read_addr = (uintptr_t)&video_dmadescr_data;
|
||||||
|
dma_hw->ch[video_dmach_descr_data].transfer_count = 4;
|
||||||
|
|
||||||
|
/* Configure the two DMA descriptors, video_dmadescr_cfg and
|
||||||
|
* video_dmadescr_data, to transfer from video config/data corresponding
|
||||||
|
* to the current line.
|
||||||
|
*
|
||||||
|
* These descriptors will be used to program the video_dmach_tx channel,
|
||||||
|
* pushing the buffer to PIO.
|
||||||
|
*
|
||||||
|
* This can be relatively relaxed, as it's triggered as line data
|
||||||
|
* starts; we have until the end of the video line (when the descriptors
|
||||||
|
* are retriggered) to program them.
|
||||||
|
*
|
||||||
|
* FIXME: this time could be used for something clever like split-screen
|
||||||
|
* (e.g. info/text lines) constructed on-the-fly.
|
||||||
|
*/
|
||||||
|
video_dmadescr_cfg.raddr = video_cfg_addr(video_current_y);
|
||||||
|
video_dmadescr_data.raddr = video_line_addr(video_current_y);
|
||||||
|
|
||||||
|
/* Frame done */
|
||||||
|
if (++video_current_y >= VIDEO_V_TOTAL)
|
||||||
|
video_current_y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __not_in_flash_func(video_dma_irq)()
|
||||||
|
{
|
||||||
|
/* The DMA IRQ occurs once the video portion of the line has been
|
||||||
|
* triggered (not when the video transfer completes, but when the
|
||||||
|
* descriptor transfer (that leads to the video transfer!) completes.
|
||||||
|
* All we need to do is reconfigure the descriptors; the video DMA will
|
||||||
|
* re-trigger the descriptors later.
|
||||||
|
*/
|
||||||
|
if (dma_channel_get_irq0_status(video_dmach_descr_data)) {
|
||||||
|
dma_channel_acknowledge_irq0(video_dmach_descr_data);
|
||||||
|
video_dma_prep_new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void video_prep_buffer()
|
||||||
|
{
|
||||||
|
memset(video_null, 0xff, VIDEO_VISIBLE_WPL * 4);
|
||||||
|
|
||||||
|
unsigned int porch_padding = (VIDEO_HRES - VIDEO_FB_HRES)/2;
|
||||||
|
// FIXME: HBP/HFP are prob off by one or so, check
|
||||||
|
uint32_t timing = ((VIDEO_HSW - 1) << 23) |
|
||||||
|
((VIDEO_HBP + porch_padding - 3) << 15) |
|
||||||
|
((VIDEO_HFP + porch_padding - 4) << 7);
|
||||||
|
video_dma_cfg[0] = timing | 0x80000000;
|
||||||
|
video_dma_cfg[1] = VIDEO_FB_HRES - 1;
|
||||||
|
video_dma_cfg[2] = timing;
|
||||||
|
video_dma_cfg[3] = VIDEO_FB_HRES - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void video_init_dma()
|
||||||
|
{
|
||||||
|
/* pio_video expects each display line to be composed of two words of config
|
||||||
|
* describing the line geometry and whether VS is asserted, followed by
|
||||||
|
* visible data.
|
||||||
|
*
|
||||||
|
* To avoid having to embed config metadata in the display framebuffer,
|
||||||
|
* we use two DMA transfers to PIO for each line. The first transfers
|
||||||
|
* the config from a config buffer, and then triggers the second to
|
||||||
|
* transfer the video data from the framebuffer. (This lets us use a
|
||||||
|
* flat, regular FB.)
|
||||||
|
*
|
||||||
|
* The PIO side emits 1BPP MSB-first. The other advantage of
|
||||||
|
* using a second DMA transfer is then we can also can
|
||||||
|
* byteswap the DMA of the video portion to match the Mac
|
||||||
|
* framebuffer layout.
|
||||||
|
*
|
||||||
|
* "Another caveat is that multiple channels should not be connected
|
||||||
|
* to the same DREQ.":
|
||||||
|
* The final complexity is that only one DMA channel can do the
|
||||||
|
* transfers to PIO, because of how the credit-based flow control works.
|
||||||
|
* So, _only_ channel 0 transfers from $SOME_BUFFER into the PIO FIFO,
|
||||||
|
* and channel 1+2 are used to reprogram/trigger channel 0 from a DMA
|
||||||
|
* descriptor list.
|
||||||
|
*
|
||||||
|
* Two extra channels are used to manage interrupts; ch1 programs ch0,
|
||||||
|
* completes, and does nothing. (It programs a descriptor that causes
|
||||||
|
* ch0 to transfer config, then trigger ch2 when complete.) ch2 then
|
||||||
|
* programs ch0 with a descriptor to transfer data, then trigger ch1
|
||||||
|
* when ch0 completes; when ch2 finishes doing that, it produces an IRQ.
|
||||||
|
* Got that?
|
||||||
|
*
|
||||||
|
* The IRQ handler sets up ch1 and ch2 to point to 2 fresh cfg+data
|
||||||
|
* descriptors; the deadline is by the end of ch0's data transfer
|
||||||
|
* (i.e. a whole line). When ch0 finishes the data transfer it again
|
||||||
|
* triggers ch1, and the new config entry is programmed.
|
||||||
|
*/
|
||||||
|
video_dmach_tx = dma_claim_unused_channel(true);
|
||||||
|
video_dmach_descr_cfg = dma_claim_unused_channel(true);
|
||||||
|
video_dmach_descr_data = dma_claim_unused_channel(true);
|
||||||
|
|
||||||
|
/* Transmit DMA: config+video data */
|
||||||
|
/* First, make dmacfg for data to transfer from config buffers + data buffers: */
|
||||||
|
dma_channel_config dc_tx_c = dma_channel_get_default_config(video_dmach_tx);
|
||||||
|
channel_config_set_dreq(&dc_tx_c, DREQ_PIO0_TX0);
|
||||||
|
channel_config_set_transfer_data_size(&dc_tx_c, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&dc_tx_c, true);
|
||||||
|
channel_config_set_write_increment(&dc_tx_c, false);
|
||||||
|
channel_config_set_bswap(&dc_tx_c, false);
|
||||||
|
/* Completion of the config TX triggers the video_dmach_descr_data channel */
|
||||||
|
channel_config_set_chain_to(&dc_tx_c, video_dmach_descr_data);
|
||||||
|
video_dmadescr_cfg.raddr = NULL; /* Reprogrammed each line */
|
||||||
|
video_dmadescr_cfg.waddr = (void *)&pio0_hw->txf[0];
|
||||||
|
video_dmadescr_cfg.count = 2; /* 2 words of video config */
|
||||||
|
video_dmadescr_cfg.ctrl = dc_tx_c.ctrl;
|
||||||
|
|
||||||
|
dma_channel_config dc_tx_d = dma_channel_get_default_config(video_dmach_tx);
|
||||||
|
channel_config_set_dreq(&dc_tx_d, DREQ_PIO0_TX0);
|
||||||
|
channel_config_set_transfer_data_size(&dc_tx_d, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&dc_tx_d, true);
|
||||||
|
channel_config_set_write_increment(&dc_tx_d, false);
|
||||||
|
channel_config_set_bswap(&dc_tx_d, true); /* This channel bswaps */
|
||||||
|
/* Completion of the data TX triggers the video_dmach_descr_cfg channel */
|
||||||
|
channel_config_set_chain_to(&dc_tx_d, video_dmach_descr_cfg);
|
||||||
|
video_dmadescr_data.raddr = NULL; /* Reprogrammed each line */
|
||||||
|
video_dmadescr_data.waddr = (void *)&pio0_hw->txf[0];
|
||||||
|
video_dmadescr_data.count = VIDEO_VISIBLE_WPL;
|
||||||
|
video_dmadescr_data.ctrl = dc_tx_d.ctrl;
|
||||||
|
|
||||||
|
/* Now, the descr_cfg and descr_data channels transfer _those_
|
||||||
|
* descriptors to program the video_dmach_tx channel:
|
||||||
|
*/
|
||||||
|
dma_channel_config dcfg = dma_channel_get_default_config(video_dmach_descr_cfg);
|
||||||
|
channel_config_set_transfer_data_size(&dcfg, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&dcfg, true);
|
||||||
|
channel_config_set_write_increment(&dcfg, true);
|
||||||
|
/* This channel loops on 16-byte/4-wprd boundary (i.e. writes all config): */
|
||||||
|
channel_config_set_ring(&dcfg, true, 4);
|
||||||
|
/* No completion IRQ or chain: the video_dmach_tx DMA completes and triggers
|
||||||
|
* the next 'data' descriptor transfer.
|
||||||
|
*/
|
||||||
|
dma_channel_configure(video_dmach_descr_cfg, &dcfg,
|
||||||
|
&dma_hw->ch[video_dmach_tx].read_addr,
|
||||||
|
&video_dmadescr_cfg,
|
||||||
|
4 /* 4 words of config */,
|
||||||
|
false /* Not yet */);
|
||||||
|
|
||||||
|
dma_channel_config ddata = dma_channel_get_default_config(video_dmach_descr_data);
|
||||||
|
channel_config_set_transfer_data_size(&ddata, DMA_SIZE_32);
|
||||||
|
channel_config_set_read_increment(&ddata, true);
|
||||||
|
channel_config_set_write_increment(&ddata, true);
|
||||||
|
channel_config_set_ring(&ddata, true, 4);
|
||||||
|
/* This transfer has a completion IRQ. Receipt of that means that both
|
||||||
|
* config and data descriptors have been transferred, and should be
|
||||||
|
* reprogrammed for the next line.
|
||||||
|
*/
|
||||||
|
dma_channel_set_irq0_enabled(video_dmach_descr_data, true);
|
||||||
|
dma_channel_configure(video_dmach_descr_data, &ddata,
|
||||||
|
&dma_hw->ch[video_dmach_tx].read_addr,
|
||||||
|
&video_dmadescr_data,
|
||||||
|
4 /* 4 words of config */,
|
||||||
|
false /* Not yet */);
|
||||||
|
|
||||||
|
/* Finally, set up video_dmadescr_cfg.raddr and video_dmadescr_data.raddr to point
|
||||||
|
* to next line's video cfg/data buffers. Then, video_dmach_descr_cfg can be triggered
|
||||||
|
* to start video.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/* Initialise PIO, DMA, start sending pixels. Passed a pointer to a 512x342x1
|
||||||
|
* Mac-order framebuffer.
|
||||||
|
*
|
||||||
|
* FIXME: Add an API to change the FB base after init live, e.g. for bank
|
||||||
|
* switching.
|
||||||
|
*/
|
||||||
|
void video_init(uint32_t *framebuffer)
|
||||||
|
{
|
||||||
|
printf("Video init\n");
|
||||||
|
|
||||||
|
pio_video_program_init(pio0, 0,
|
||||||
|
pio_add_program(pio0, &pio_video_program),
|
||||||
|
GPIO_VID_DATA, /* Followed by HS, VS, CLK */
|
||||||
|
VIDEO_PCLK_MULT);
|
||||||
|
|
||||||
|
/* Invert output pins: HS/VS are active-low, also invert video! */
|
||||||
|
gpio_set_outover(GPIO_VID_HS, GPIO_OVERRIDE_INVERT);
|
||||||
|
gpio_set_outover(GPIO_VID_VS, GPIO_OVERRIDE_INVERT);
|
||||||
|
gpio_set_outover(GPIO_VID_DATA, GPIO_OVERRIDE_INVERT);
|
||||||
|
/* Highest drive strength (VGA is current-based, innit) */
|
||||||
|
hw_write_masked(&padsbank0_hw->io[GPIO_VID_DATA],
|
||||||
|
PADS_BANK0_GPIO0_DRIVE_VALUE_12MA << PADS_BANK0_GPIO0_DRIVE_LSB,
|
||||||
|
PADS_BANK0_GPIO0_DRIVE_BITS);
|
||||||
|
|
||||||
|
/* IRQ handlers for DMA_IRQ_0: */
|
||||||
|
irq_set_exclusive_handler(DMA_IRQ_0, video_dma_irq);
|
||||||
|
irq_set_enabled(DMA_IRQ_0, true);
|
||||||
|
|
||||||
|
video_init_dma();
|
||||||
|
|
||||||
|
/* Init config word buffers */
|
||||||
|
video_current_y = 0;
|
||||||
|
video_framebuffer = framebuffer;
|
||||||
|
video_prep_buffer();
|
||||||
|
|
||||||
|
/* Set up pointers to first line, and start DMA */
|
||||||
|
video_dma_prep_new();
|
||||||
|
dma_channel_start(video_dmach_descr_cfg);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue