Compare commits

...

32 Commits

Author SHA1 Message Date
Robin Jones
88d654129a
[SC64][WEB] FAQ spelling fix (#109)
A minor mistake that I noticed.
2025-03-19 08:59:29 +01:00
Mateusz Faderewski
b88c9a314b [SC64][SW] Added license notice in the sc64deployer help 2025-03-12 23:05:13 +01:00
Mateusz Faderewski
e4c3f34fb0 [SC64][SW] Added warning about unknown variant of IPL3 2025-03-12 22:36:26 +01:00
Luke Stadem
b520f9ace8
[SC64][SW] Added rerun-if-changed for bootloader dependency (#108)
Changes to the bootloader source were not being noticed by cargo when
recompiling sc64deployer.

We can use
[rerun-if-changed](https://doc.rust-lang.org/cargo/reference/build-scripts.html#rerun-if-changed)
to instruct cargo to rerun the `build.rs` script if there were any
changes to any file inside `bootloader/src/` (recursively).
2025-03-12 18:47:50 +01:00
Mateusz Faderewski
d8c4f979cc [SC64][WEB] FAQ cleanup 2025-03-11 22:21:08 +01:00
Robin Jones
d63f5893da
[SC64][WEB] FAQ improvements (#104)
Add some improvements. take what you need.
2025-03-11 22:17:33 +01:00
Mateusz Faderewski
d307e1a5b1 [SC64][SW] Remove use keyword for std::ffi::c_char in ftdi.rs 2025-03-11 22:16:24 +01:00
liffy
65f8fa3cf7
[SC64][SW] Fix invalid variable type (description/serial) for libftdi functions in sc64deployer (#105)
`c_char` isn't `i8` on all architectures (e.g. on ARM it's `u8`)

Co-authored-by: lif <>
Co-authored-by: Mateusz Faderewski <sc@mateuszfaderewski.pl>
2025-03-11 22:14:52 +01:00
Mateusz Faderewski
6c566bd530
[SC64][CI/CD] Fix firmware building (#107) 2025-03-11 22:04:38 +01:00
Mateusz Faderewski
99060bec15 [SC64][SW] Display more meaningful error message for menu load 2025-03-11 21:20:41 +01:00
Mateusz Faderewski
63feaa0c2e [SC64][SW] Add project license information in the bootloader 2025-03-11 14:04:16 +01:00
Mateusz Faderewski
a59ad1d39b [SC64][HW] License PCB project files under CERN-OHL-S-2.0 2025-03-11 14:01:49 +01:00
Mateusz Faderewski
30fb3d0ea6 [SC64][SW] Disable auto new line in bootloader display printing functions 2025-03-09 21:14:54 +01:00
Mateusz Faderewski
a3d4082384 [SC64][SW] Added an option to force load menu when boot mode is ROM/DDIPL and R button is held 2025-03-09 20:50:24 +01:00
Mateusz Faderewski
0739ca624c [SC64][SW] Added controller reading functions in the bootloader 2025-03-09 20:48:15 +01:00
Mateusz Faderewski
bb1ce45dfe [SC64][CI/CD] Move Lattice Diamond license to GitHub secrets 2025-03-09 09:33:17 +01:00
Mateusz Faderewski
9193e9c6f2 [SC64][BUILD] Moved getting commit info to build.sh script 2025-03-08 22:14:35 +01:00
Mateusz Faderewski
3fbb6f3823 [SC64][BUILD] Moved time printing from docker_build.sh to build.sh 2025-03-08 22:12:38 +01:00
Mateusz Faderewski
f546e5d17d [SC64][SW] Implement FlashRAM timings emulation and add a fake variant of FlashRAM 2025-03-07 20:51:56 +01:00
Mateusz Faderewski
8393963650 [SC64][SW] Enable support for GPT partition scheme in FatFs + Update FatFs 2025-02-01 02:19:22 +01:00
Mateusz Faderewski
20a9ec0087 [SC64][BUILD] Force docker image platform to linux/x86_64 2025-01-27 19:50:53 +01:00
Mateusz Faderewski
b3d9e98e68 [SC64][WEB] Added a FAQ page + small updates 2025-01-27 19:49:59 +01:00
Luke Stadem
6698550dbd
[SC64][DOCS] Fix inaccurate identifier string for successful USB packets (#100)
According to my testing and the [existing sc64deployer
software](/Polprzewodnikowy/SummerCart64/blob/main/sw/deployer/src/sc64/link.rs#L186),
the identifier should be `CMP`, not `RSP`.
2024-12-24 21:01:24 +01:00
Mateusz Faderewski
18041e2547 [SC64] v2.20.2 release 2024-11-18 22:58:35 +01:00
Mateusz Faderewski
0538a28f9e [SC64][SW] Fixed regression in the SD card module introduced in the latest refactor 2024-11-18 22:57:14 +01:00
Mateusz Faderewski
1ade3ade8e [SC64] v2.20.1 release 2024-11-16 15:20:12 +01:00
Mateusz Faderewski
80b4aa95cd [SC64] Updated GitHub issue template 2024-11-16 13:59:46 +01:00
Mateusz Faderewski
6eef811cd6 [SC64][SW] Fix SD deinit error when the card is not locked + SD module refactor 2024-11-16 13:47:20 +01:00
Raphaël Tétreault
e2c100ae7f
[SC64] Revise README to include Feature subheadings for easy at-a-glance reading (#77)
I made some minor changes to the README to try and address the most
common read-the-manual moments seen in the N64Brew Discord.

Specifically, I broke out the features listed in the README into
sub-categories, with the non-developer features up at the top for
non-technical people to read first.

The categories are:
- Hardware-Dependent Game Features
- Game Saves
- Menu
- Game Development
- Cartridge Production

Co-authored-by: Mateusz Faderewski <sc@mateuszfaderewski.pl>
2024-10-11 19:09:41 +02:00
Mateusz Faderewski
a6e86587ae [SC64][DOCS][WEB] Updated readme, documentation and website 2024-10-11 18:36:10 +02:00
Mateusz Faderewski
93ab101be4 [SC64] Updated GitHub issue/PR templates 2024-10-11 18:26:46 +02:00
Mateusz Faderewski
cc41652e6f [SC64][DOCS][WEB] Added a link to a video build guide 2024-10-09 11:19:25 +02:00
61 changed files with 1868 additions and 821 deletions

1
.github/FUNDING.yml vendored
View File

@ -1,2 +1 @@
github: polprzewodnikowy
ko_fi: polprzewodnikowy

View File

@ -1,27 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
4.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Possible solution**
Not obligatory, but suggest a fix/reason for the bug.

58
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Bug Report
description: File a bug report
title: "[SC64][BUG] "
labels: ["bug"]
assignees:
- Polprzewodnikowy
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: checkboxes
id: sanity-check
attributes:
label: Is your issue really a bug?
description: |
Issue tracker in this repository is for **BUG REPORTS ONLY**.
Make sure your problem is caused by the firmware/PC app and **not** by the software you're running on the flashcart.
Errors in the documentation are also considered a bug.
If your issue is related to the menu then report it in the [N64FlashcartMenu] repository.
[N64FlashcartMenu]: https://github.com/Polprzewodnikowy/N64FlashcartMenu
options:
- label: I understand the difference between flashcart firmware, N64FlashcartMenu and `sc64deployer` PC app.
required: true
- label: I found a bug in FPGA HDL (`/fw/rtl`)
- label: I found a bug in MCU app (`/sw/controller`)
- label: I found a bug in N64 bootloader (`/sw/bootloader`)
- label: I found a bug in PC app (`/sw/deployer`)
- label: I found a bug in initial programming script (`/sw/tools/primer.py`)
- label: I found an error in documentation (`/docs`)
- label: I found an issue elsewhere
- type: input
id: version
attributes:
label: Firmware version
placeholder: v2.20.2
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: Describe the bug
description: |
Tell us what you noticed as a bug, and what is your expected outcome.
The more detailed the description is the better.
If applicable please attach screenshots and/or video showing the problem.
validations:
required: true
- type: textarea
id: deployer-info
attributes:
label: Output logs from `sc64deployer info`
description: If possible, please copy and paste the output from the command specified above.
render: shell

View File

@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Discussions
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
about: Please use for QUESTIONS, conversations or discussions.
- name: N64brew Discord
url: https://discord.gg/8VNMKhxqQn
about: Alternative channel for asking QUESTIONS.
about: The go-to community to ask about SummerCart64

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,20 +0,0 @@
**Description**
Provide a headline summary of your changes in the Title above.
Describe your changes in detail.
**Related Issue**
This project only accepts pull requests related to open issues.
If suggesting a change, please discuss it in an issue first.
If fixing a bug, there should be an issue describing it with steps to reproduce.
Please link to the issue here:
**Motivation and Context**
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.
**How Has This Been Tested?**
Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to see how your change affects other areas of the code, etc.
**Screenshots**
If applicable, add screenshots to help demonstrate your feature/bugfix.

View File

@ -19,6 +19,8 @@ jobs:
steps:
- name: Download SummerCart64 repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set package version
uses: frabert/replace-string-action@v2
@ -28,9 +30,15 @@ jobs:
string: '${{ github.ref_name }}'
replace-with: '-'
- name: Retrieve the Lattice Diamond license from secrets and decode it
run: echo $LICENSE | base64 --decode > fw/project/lcmxo2/license.dat
env:
LICENSE: ${{ secrets.LATTICE_DIAMOND_LICENSE_BASE64 }}
- name: Build firmware
run: ./docker_build.sh release --force-clean
env:
MAC_ADDRESS: ${{ secrets.LATTICE_DIAMOND_MAC_ADDRESS }}
SC64_VERSION: ${{ steps.version.outputs.replaced }}
- name: Upload artifact

View File

@ -4,25 +4,40 @@
**For non-technical description of the SummerCart64, please head to the https://summercart64.dev website!**
## Features
- 64 MiB SDRAM memory for game and save data
- 16 MiB FLASH memory for bootloader and extended game data
- 8 kiB on-chip buffer for general use
- ~23.8 MiB/s peak transfer rate USB interface for data upload/download and debug functionality
- ~23.8 MiB/s peak transfer rate SD card interface
- EEPROM, SRAM and FlashRAM save types with automatic writeback to SD card
- Battery backed real time clock (RTC)
- Status LED and button for general use
- 64DD add-on emulation
- IS-Viewer 64 debug interface
- N64 bootloader with support for IPL3 registers spoofing and loading menu from SD card
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
- Enhanced [UltraCIC_C](https://github.com/jago85/UltraCIC_C) emulation with automatic region switching and programmable seed/checksum values
- PC app for communicating with flashcart (game/save data upload/download, feature enable control and debug terminal)
- Access to the SD card via USB interface with the use of the PC app
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
- Initial programming via UART header or dedicated JTAG/SWD interfaces
- Software and firmware updatable via USB interface
- 3D printable plastic shell
- **ROM and Save Memory On-board**
- 64 MiB SDRAM memory for game and save data (enough memory to support every retail game without compromise)
- 16 MiB FLASH memory for bootloader and extended game data (with extended memory flashcart supports game ROMs up to 78 MiB)
- **Game Saves**
- EEPROM 4k/16k, SRAM and FlashRAM save types with an automatic writeback to the SD card (no reset button press required)
- **Hardware-Dependent Game Features**
- 64DD add-on emulation
- Battery backed real time clock (RTC)
- **Menu**
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
- **Game Development**
- ~23.8 MiB/s peak transfer rate SD card interface
- ~23.8 MiB/s peak transfer rate USB interface for data upload/download and debug functionality
- PC app to access the flashcart features:
- Game/save data upload/download
- Feature enable control
- Debug terminal
- Access to the SD card
- Firmware update
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
- IS-Viewer 64 debug interface (fixed 64 kiB buffer with a movable base address)
- 8 kiB on-chip buffer for general use
- Status LED and button for general use
- [UltraCIC_C](https://github.com/jago85/UltraCIC_C) emulation with automatic region switching and programmable seed/checksum values
- N64 bootloader with support for IPL3 registers spoofing and loading menu from SD card
- **Cartridge Production**
- Initial programming via UART header or via dedicated JTAG/SWD interfaces
- 3D printable shell
---
@ -38,17 +53,6 @@
---
## Help / Q&A
For any questions related to this project, please use [*Discussions*](https://github.com/Polprzewodnikowy/SummerCart64/discussions) tab in GitHub repository.
Using discussions tab is highly encouraged as it allows to have centralized knowledge database accessible for everyone interested in this project.
I'm also active at [N64brew](https://discord.gg/8VNMKhxqQn) Discord server as `korgeaux` but keep in mind that [*Discussions*](https://github.com/Polprzewodnikowy/SummerCart64/discussions) tab is a preferred option.
Note that my time is limited so I can't answer all questions.
---
## How do I get one?
Most up to date information about purchasing/manufacturing options is available on https://summercart64.dev website!
@ -64,7 +68,7 @@ If you have even slightest doubt about the ordering or programming process, it i
**Full disclosure**: for every order made through [this link](https://www.pcbway.com/project/member/shareproject/?bmbno=1046ED64-8AEE-44) I will receive 10% of PCB manufacturing and PCB assembly service cost (price of the components is not included in the split). This is a great way of supporting further project development.
If you don't need a physical product but still want to support me then check my [GitHub sponsors](https://github.com/sponsors/Polprzewodnikowy) page.
If you don't need a physical product but still want to support me then check the sponsor links on the [official website](https://summercart64.dev).
---

View File

@ -17,6 +17,7 @@ TOP_FILES=(
FILES=(
"./assets/*"
"./docs/*"
"./hw/pcb/LICENSE"
"./hw/pcb/sc64v2_bom.html"
"./hw/pcb/sc64v2.kicad_pcb"
"./hw/pcb/sc64v2.kicad_pro"
@ -27,6 +28,8 @@ FILES=(
"./README.md"
)
HAVE_COMMIT_INFO=false
BUILT_BOOTLOADER=false
BUILT_CONTROLLER=false
BUILT_CIC=false
@ -36,9 +39,24 @@ BUILT_RELEASE=false
FORCE_CLEAN=false
get_last_commit_info () {
if [ "$HAVE_COMMIT_INFO" = true ]; then return; fi
SAFE_DIRECTORY="-c safe.directory=$(pwd)"
GIT_BRANCH=$(git $SAFE_DIRECTORY rev-parse --abbrev-ref HEAD)
GIT_TAG=$(git $SAFE_DIRECTORY describe --tags 2> /dev/null)
GIT_SHA=$(git $SAFE_DIRECTORY rev-parse HEAD)
GIT_MESSAGE=$(git $SAFE_DIRECTORY log --oneline --format=%B -n 1 HEAD | head -n 1)
HAVE_COMMIT_INFO=true
}
build_bootloader () {
if [ "$BUILT_BOOTLOADER" = true ]; then return; fi
get_last_commit_info
pushd sw/bootloader > /dev/null
if [ "$FORCE_CLEAN" = true ]; then
make clean
@ -98,6 +116,8 @@ build_fpga () {
build_update () {
if [ "$BUILT_UPDATE" = true ]; then return; fi
get_last_commit_info
build_bootloader
build_controller
build_cic
@ -163,6 +183,14 @@ if test $# -eq 0; then
exit 1
fi
print_time () {
echo "Build took $SECONDS seconds"
}
trap "echo \"Build failed\"; print_time" ERR
SECONDS=0
TRIGGER_BOOTLOADER=false
TRIGGER_CONTROLLER=false
TRIGGER_CIC=false
@ -213,3 +241,5 @@ if [ "$TRIGGER_CIC" = true ]; then build_cic; fi
if [ "$TRIGGER_FPGA" = true ]; then build_fpga; fi
if [ "$TRIGGER_UPDATE" = true ]; then build_update; fi
if [ "$TRIGGER_RELEASE" = true ]; then build_release; fi
print_time

View File

@ -1,20 +1,14 @@
#!/bin/bash
BUILDER_IMAGE="ghcr.io/polprzewodnikowy/sc64env:v1.10"
BUILDER_PLATFORM="linux/x86_64"
pushd $(dirname $0) > /dev/null
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
GIT_TAG=$(git describe --tags 2> /dev/null)
GIT_SHA=$(git rev-parse HEAD)
GIT_MESSAGE=$(git log --oneline --format=%B -n 1 HEAD | head -n 1)
if [ -t 1 ]; then
DOCKER_OPTIONS="-it"
fi
SECONDS=0
docker run \
$DOCKER_OPTIONS \
--rm \
@ -23,20 +17,13 @@ docker run \
-v "$(pwd)"/fw/project/lcmxo2/license.dat:/flexlm/license.dat \
-v "$(pwd)":/workdir \
-h=`hostname` \
-e GIT_BRANCH="$GIT_BRANCH" \
-e GIT_TAG="$GIT_TAG" \
-e GIT_SHA="$GIT_SHA" \
-e GIT_MESSAGE="$GIT_MESSAGE" \
-e SC64_VERSION=${SC64_VERSION:-""} \
--platform $BUILDER_PLATFORM \
$BUILDER_IMAGE \
./build.sh $@
BUILD_ERROR=$?
echo "Build took $SECONDS seconds"
BUILD_RESULT=$?
popd > /dev/null
if [ $BUILD_ERROR -ne 0 ]; then
exit -1
fi
exit $BUILD_RESULT

View File

@ -3,7 +3,7 @@
- [PC -\> SC64 packets](#pc---sc64-packets)
- [**`CMD`** packet](#cmd-packet)
- [SC64 -\> PC packets](#sc64---pc-packets)
- [**`RSP`/`ERR`** packets](#rsperr-packets)
- [**`CMP`/`ERR`** packets](#cmperr-packets)
- [**`PKT`** packet](#pkt-packet)
- [Supported commands](#supported-commands)
- [`v`: **IDENTIFIER\_GET**](#v-identifier_get)
@ -128,29 +128,29 @@ Packet data length is derived from the argument if specific command supports it.
| identifier | description |
| ---------- | ---------------------------------------- |
| `RSP` | Success response to the received command |
| `CMP` | Success response to the received command |
| `ERR` | Error response to the received command |
| `PKT` | Asynchronous data packet |
SC64 sends response packet `RSP`/`ERR` to almost every command received from the PC.
SC64 sends response packet `CMP`/`ERR` to almost every command received from the PC.
Fourth byte is the same as in the command that triggered the response.
If command execution was not successful, then `RSP` identifier is replaced by the `ERR` identifier.
If command execution was not successful, then `CMP` identifier is replaced by the `ERR` identifier.
SC64 can also send `PKT` packet at any time as a response to action triggered by the N64 or the flashcart itself.
Fourth byte denotes packet ID listed in the [asynchronous packets](#asynchronous-packets) section.
#### **`RSP`/`ERR`** packets
#### **`CMP`/`ERR`** packets
General structure of packet:
| offset | type | description |
| ------ | -------------------- | ---------------------- |
| `0` | char[3] | `RSP`/`ERR` identifier |
| `0` | char[3] | `CMP`/`ERR` identifier |
| `3` | char[1] | Command ID |
| `4` | uint32_t | Response data length |
| `8` | uint8_t[data_length] | Response data (if any) |
`RSP`/`ERR` packet is sent as a response to the command sent by the PC.
`ERR` response might contain no (or undefined) data in the arbitrary data field compared to regular `RSP` packet.
`CMP`/`ERR` packet is sent as a response to the command sent by the PC.
`ERR` response might contain no (or undefined) data in the arbitrary data field compared to regular `CMP` packet.
#### **`PKT`** packet
@ -463,7 +463,7 @@ Writes bytes to the specified memory address. Please refer to the [internal memo
_This command does not send response data._
_This command does not send `RSP`/`ERR` packet response!_
_This command does not send `CMP`/`ERR` packet response!_
This command notifies N64 that data is waiting to be acknowledged.
If N64 side doesn't acknowledge data via [`m` **USB_READ**](./02_n64_commands.md#m-usb_read) N64 command within 1 second then data is flushed and [`G` **DATA_FLUSHED**](#asynchronous-packets) asynchronous packet is sent to the PC.

View File

@ -133,9 +133,10 @@ type: *enum* | default: `0`
- `4` - FlashRAM 1 Mib save is enabled
- `5` - SRAM 768 kib save is enabled
- `6` - SRAM 1 Mib save is enabled
- `7` - FakeFlashRAM 1 Mib save is enabled (write/erase timings are not emulated and erase before write is not required)
Use this setting for selecting save type that will be emulated. Only one save type can be enabled.
Any successful write to this config will disable automatic save writeback to USB or SD card when previously enabled.
Any successful write to this config will disable automatic save writeback to the USB or SD card if previously enabled.
---

View File

@ -10,13 +10,14 @@
Docker method is a preferred option.
Run `./docker_build.sh release` to build all firmware/software and generate release package.
For other options run script without any command to print help.
For other options run script without any command to print help about available options.
#### Lattice Diamond license
Lattice Diamond software is used to build FPGA bitstream, a free 1 year license is necessary to run the build process.
Repository already contains license attached to fake MAC address `F8:12:34:56:78:90` (path to the license: `fw/project/lcmxo2/license.dat`).
If the license expires, it is required to request new license from Lattice webpage.
New license can be attached to aforementioned MAC address or any other address.
In case of non default MAC address it is possible to provide it via `MAC_ADDRESS` environment variable.
For example: `MAC_ADDRESS=AB:00:00:00:00:00 ./docker_build.sh release`.
Lattice Diamond software is used to build the FPGA bitstream.
A free 1 year license is necessary to run the build process.
You can request personal license from the [Lattice website](https://www.latticesemi.com/Support/Licensing).
Build script expects license file to be present in this path: `fw/project/lcmxo2/license.dat`.
Since build is done inside docker container it is required to pass the MAC address, linked with the license file, to a container.
Build script expects it in the `MAC_ADDRESS` environment variable.
For example, run `MAC_ADDRESS=AB:00:00:00:00:00 ./docker_build.sh release` command to start the building process if your license is attached to `AB:00:00:00:00:00` MAC address.

View File

@ -1,3 +1,4 @@
- [Video guide](#video-guide)
- [Step by step guide how to make SC64](#step-by-step-guide-how-to-make-sc64)
- [**PCB manufacturing data**](#pcb-manufacturing-data)
- [**PCB requirements**](#pcb-requirements)
@ -12,6 +13,10 @@
---
## Video guide
[![Video guide](https://img.youtube.com/vi/t6hyCFpwqz8/0.jpg)](https://www.youtube.com/watch?v=t6hyCFpwqz8 "How to build and program the SummerCart64")
## Step by step guide how to make SC64
All necessary manufacturing files are packaged in every `sc64-extra-{version}.zip` file in GitHub releases.
@ -42,7 +47,7 @@ Please download latest release before proceeding with the instructions.
### **Components**
1. Locate interactive BOM file inside `hw/pcb` folder (alternatively, check out this [BOM discussion](https://github.com/Polprzewodnikowy/SummerCart64/discussions/27))
1. Locate the interactive BOM file inside `hw/pcb` folder
2. Order all parts listed in the BOM file or use PCB assembly service together with your PCB order
---
@ -137,5 +142,3 @@ Please use command `python3 primer.py COMx sc64-firmware-{version}.bin --bootloa
Due to multiple possible causes of the problem it's best to start visually inspecting SC64's board for any defects, like bad solder work or chips soldered backwards.
If visual inspection didn't yield any obvious culprits then next step would be to check if everything is connected correctly.
Check if TX/RX signals aren't swapped and if SC64 is getting power from the USB cable. Best place to check supply voltage are the exposed test pads on the left of U8 chip.
If everything at this point was checked and looked fine, then feel free to open new thread in the [*Discussions*](https://github.com/Polprzewodnikowy/SummerCart64/discussions) tab.
Make sure to describe your problem extensively, attach SC64 board photos **from the both sides**, and paste all logs/screenshots from the `primer.py` output.

View File

@ -1,6 +1,7 @@
.recovery
*.dir/
*.ccl
*.dat
*.dmp
*.html
*.ini

View File

@ -1,30 +0,0 @@
FEATURE LSC_BASE lattice 8.0 23-feb-2025 uncounted EEEED0E56442 \
VENDOR_STRING="ispLEVER BASE" HOSTID=f81234567890
FEATURE LSC_SYNPLIFY lattice 8.0 23-feb-2025 uncounted BCE24EFB1CC8 \
VENDOR_STRING="ispLEVER System with Synplicity" \
HOSTID=f81234567890
FEATURE LSC_SYNPLIFYPRO1 lattice 10.0 23-feb-2025 uncounted \
A55E225A8213 VENDOR_STRING="ispLEVER System with Synplicity \
Pro 1" HOSTID=f81234567890
FEATURE LSC_ADVANCED_DSP lattice 10.0 23-feb-2025 uncounted \
670E6D281E0E VENDOR_STRING="ispLEVER DSP" HOSTID=f81234567890
FEATURE LSC_DIAMOND_A lattice 10.0 23-feb-2025 uncounted 75EFC1B810BA \
VENDOR_STRING="Diamond Free" HOSTID=f81234567890
FEATURE LSC_PROGRAMMER_MATURE lattice 10.0 23-feb-2025 uncounted \
46BCF34C2DC7 VENDOR_STRING=Programmer HOSTID=f81234567890
FEATURE LSC_ADVANCED_ORCA lattice 9.0 23-feb-2025 uncounted \
CC2DBC156EE2 VENDOR_STRING="ispORCA System" \
HOSTID=f81234567890
FEATURE LSC_CTL_PROPBLD lattice 2025.02 23-feb-2025 uncounted \
6CCE29206AC9 VENDOR_STRING=LSC_CTL_PROPBLD HOSTID=f81234567890
FEATURE LSC_CTL_PROPSDK_PFR lattice 2025.02 23-feb-2025 uncounted \
7FE203087494 VENDOR_STRING=LSC_CTL_PROPSDK_PFR \
HOSTID=f81234567890
DAEMON mgcld path_to_mgcld
INCREMENT latticemsim mgcld 2024.10 23-feb-2025 uncounted \
FFD823285F6785FED8B2 VENDOR_STRING=7E796EE6 HOSTID=f81234567890 \
ISSUER="ModelSIM/Questa Lattice" SN=290985235 SIGN2="1A3D 76F8 48B1 \
B3C0 D0C7 8C20 1131 C772 AB70 A5D3 D7F0 6B5F F4F7 70F8 F1D6 040C E65F \
8CE4 CF22 E322 D961 988D 6A4C 5806 B9EC D64A 3D9F 1F16 260B AB10"

289
hw/pcb/LICENSE Normal file
View File

@ -0,0 +1,289 @@
CERN Open Hardware Licence Version 2 - Strongly Reciprocal
Preamble
CERN has developed this licence to promote collaboration among
hardware designers and to provide a legal tool which supports the
freedom to use, study, modify, share and distribute hardware designs
and products based on those designs. Version 2 of the CERN Open
Hardware Licence comes in three variants: CERN-OHL-P (permissive); and
two reciprocal licences: CERN-OHL-W (weakly reciprocal) and this
licence, CERN-OHL-S (strongly reciprocal).
The CERN-OHL-S is copyright CERN 2020. Anyone is welcome to use it, in
unmodified form only.
Use of this Licence does not imply any endorsement by CERN of any
Licensor or their designs nor does it imply any involvement by CERN in
their development.
1 Definitions
1.1 'Licence' means this CERN-OHL-S.
1.2 'Compatible Licence' means
a) any earlier version of the CERN Open Hardware licence, or
b) any version of the CERN-OHL-S, or
c) any licence which permits You to treat the Source to which
it applies as licensed under CERN-OHL-S provided that on
Conveyance of any such Source, or any associated Product You
treat the Source in question as being licensed under
CERN-OHL-S.
1.3 'Source' means information such as design materials or digital
code which can be applied to Make or test a Product or to
prepare a Product for use, Conveyance or sale, regardless of its
medium or how it is expressed. It may include Notices.
1.4 'Covered Source' means Source that is explicitly made available
under this Licence.
1.5 'Product' means any device, component, work or physical object,
whether in finished or intermediate form, arising from the use,
application or processing of Covered Source.
1.6 'Make' means to create or configure something, whether by
manufacture, assembly, compiling, loading or applying Covered
Source or another Product or otherwise.
1.7 'Available Component' means any part, sub-assembly, library or
code which:
a) is licensed to You as Complete Source under a Compatible
Licence; or
b) is available, at the time a Product or the Source containing
it is first Conveyed, to You and any other prospective
licensees
i) as a physical part with sufficient rights and
information (including any configuration and
programming files and information about its
characteristics and interfaces) to enable it either to
be Made itself, or to be sourced and used to Make the
Product; or
ii) as part of the normal distribution of a tool used to
design or Make the Product.
1.8 'Complete Source' means the set of all Source necessary to Make
a Product, in the preferred form for making modifications,
including necessary installation and interfacing information
both for the Product, and for any included Available Components.
If the format is proprietary, it must also be made available in
a format (if the proprietary tool can create it) which is
viewable with a tool available to potential licensees and
licensed under a licence approved by the Free Software
Foundation or the Open Source Initiative. Complete Source need
not include the Source of any Available Component, provided that
You include in the Complete Source sufficient information to
enable a recipient to Make or source and use the Available
Component to Make the Product.
1.9 'Source Location' means a location where a Licensor has placed
Covered Source, and which that Licensor reasonably believes will
remain easily accessible for at least three years for anyone to
obtain a digital copy.
1.10 'Notice' means copyright, acknowledgement and trademark notices,
Source Location references, modification notices (subsection
3.3(b)) and all notices that refer to this Licence and to the
disclaimer of warranties that are included in the Covered
Source.
1.11 'Licensee' or 'You' means any person exercising rights under
this Licence.
1.12 'Licensor' means a natural or legal person who creates or
modifies Covered Source. A person may be a Licensee and a
Licensor at the same time.
1.13 'Convey' means to communicate to the public or distribute.
2 Applicability
2.1 This Licence governs the use, copying, modification, Conveying
of Covered Source and Products, and the Making of Products. By
exercising any right granted under this Licence, You irrevocably
accept these terms and conditions.
2.2 This Licence is granted by the Licensor directly to You, and
shall apply worldwide and without limitation in time.
2.3 You shall not attempt to restrict by contract or otherwise the
rights granted under this Licence to other Licensees.
2.4 This Licence is not intended to restrict fair use, fair dealing,
or any other similar right.
3 Copying, Modifying and Conveying Covered Source
3.1 You may copy and Convey verbatim copies of Covered Source, in
any medium, provided You retain all Notices.
3.2 You may modify Covered Source, other than Notices, provided that
You irrevocably undertake to make that modified Covered Source
available from a Source Location should You Convey a Product in
circumstances where the recipient does not otherwise receive a
copy of the modified Covered Source. In each case subsection 3.3
shall apply.
You may only delete Notices if they are no longer applicable to
the corresponding Covered Source as modified by You and You may
add additional Notices applicable to Your modifications.
Including Covered Source in a larger work is modifying the
Covered Source, and the larger work becomes modified Covered
Source.
3.3 You may Convey modified Covered Source (with the effect that You
shall also become a Licensor) provided that You:
a) retain Notices as required in subsection 3.2;
b) add a Notice to the modified Covered Source stating that You
have modified it, with the date and brief description of how
You have modified it;
c) add a Source Location Notice for the modified Covered Source
if You Convey in circumstances where the recipient does not
otherwise receive a copy of the modified Covered Source; and
d) license the modified Covered Source under the terms and
conditions of this Licence (or, as set out in subsection
8.3, a later version, if permitted by the licence of the
original Covered Source). Such modified Covered Source must
be licensed as a whole, but excluding Available Components
contained in it, which remain licensed under their own
applicable licences.
4 Making and Conveying Products
You may Make Products, and/or Convey them, provided that You either
provide each recipient with a copy of the Complete Source or ensure
that each recipient is notified of the Source Location of the Complete
Source. That Complete Source is Covered Source, and You must
accordingly satisfy Your obligations set out in subsection 3.3. If
specified in a Notice, the Product must visibly and securely display
the Source Location on it or its packaging or documentation in the
manner specified in that Notice.
5 Research and Development
You may Convey Covered Source, modified Covered Source or Products to
a legal entity carrying out development, testing or quality assurance
work on Your behalf provided that the work is performed on terms which
prevent the entity from both using the Source or Products for its own
internal purposes and Conveying the Source or Products or any
modifications to them to any person other than You. Any modifications
made by the entity shall be deemed to be made by You pursuant to
subsection 3.2.
6 DISCLAIMER AND LIABILITY
6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products
are provided 'as is' and any express or implied warranties,
including, but not limited to, implied warranties of
merchantability, of satisfactory quality, non-infringement of
third party rights, and fitness for a particular purpose or use
are disclaimed in respect of any Source or Product to the
maximum extent permitted by law. The Licensor makes no
representation that any Source or Product does not or will not
infringe any patent, copyright, trade secret or other
proprietary right. The entire risk as to the use, quality, and
performance of any Source or Product shall be with You and not
the Licensor. This disclaimer of warranty is an essential part
of this Licence and a condition for the grant of any rights
granted under this Licence.
6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to
the maximum extent permitted by law, have no liability for
direct, indirect, special, incidental, consequential, exemplary,
punitive or other damages of any character including, without
limitation, procurement of substitute goods or services, loss of
use, data or profits, or business interruption, however caused
and on any theory of contract, warranty, tort (including
negligence), product liability or otherwise, arising in any way
in relation to the Covered Source, modified Covered Source
and/or the Making or Conveyance of a Product, even if advised of
the possibility of such damages, and You shall hold the
Licensor(s) free and harmless from any liability, costs,
damages, fees and expenses, including claims by third parties,
in relation to such use.
7 Patents
7.1 Subject to the terms and conditions of this Licence, each
Licensor hereby grants to You a perpetual, worldwide,
non-exclusive, no-charge, royalty-free, irrevocable (except as
stated in subsections 7.2 and 8.4) patent licence to Make, have
Made, use, offer to sell, sell, import, and otherwise transfer
the Covered Source and Products, where such licence applies only
to those patent claims licensable by such Licensor that are
necessarily infringed by exercising rights under the Covered
Source as Conveyed by that Licensor.
7.2 If You institute patent litigation against any entity (including
a cross-claim or counterclaim in a lawsuit) alleging that the
Covered Source or a Product constitutes direct or contributory
patent infringement, or You seek any declaration that a patent
licensed to You under this Licence is invalid or unenforceable
then any rights granted to You under this Licence shall
terminate as of the date such process is initiated.
8 General
8.1 If any provisions of this Licence are or subsequently become
invalid or unenforceable for any reason, the remaining
provisions shall remain effective.
8.2 You shall not use any of the name (including acronyms and
abbreviations), image, or logo by which the Licensor or CERN is
known, except where needed to comply with section 3, or where
the use is otherwise allowed by law. Any such permitted use
shall be factual and shall not be made so as to suggest any kind
of endorsement or implication of involvement by the Licensor or
its personnel.
8.3 CERN may publish updated versions and variants of this Licence
which it considers to be in the spirit of this version, but may
differ in detail to address new problems or concerns. New
versions will be published with a unique version number and a
variant identifier specifying the variant. If the Licensor has
specified that a given variant applies to the Covered Source
without specifying a version, You may treat that Covered Source
as being released under any version of the CERN-OHL with that
variant. If no variant is specified, the Covered Source shall be
treated as being released under CERN-OHL-S. The Licensor may
also specify that the Covered Source is subject to a specific
version of the CERN-OHL or any later version in which case You
may apply this or any later version of CERN-OHL with the same
variant identifier published by CERN.
8.4 This Licence shall terminate with immediate effect if You fail
to comply with any of its terms and conditions.
8.5 However, if You cease all breaches of this Licence, then Your
Licence from any Licensor is reinstated unless such Licensor has
terminated this Licence by giving You, while You remain in
breach, a notice specifying the breach and requiring You to cure
it within 30 days, and You have failed to come into compliance
in all material respects by the end of the 30 day period. Should
You repeat the breach after receipt of a cure notice and
subsequent reinstatement, this Licence will terminate
immediately and permanently. Section 6 shall continue to apply
after any termination.
8.6 This Licence shall not be enforceable except by a Licensor
acting as such, and third party beneficiary rights are
specifically excluded.

View File

@ -9,8 +9,8 @@
(paper "A4")
(title_block
(title "SummerCart64")
(date "2024-08-11")
(rev "2.1")
(date "2025-03-10")
(rev "2.1a")
(company "Mateusz Faderewski")
)
(layers
@ -26785,6 +26785,18 @@
(layer "User.1")
(uuid "e23fcef0-be99-4588-9e98-11fe6a1e9f26")
)
(gr_text "Designed by Mateusz Faderewski\nLicensed under CERN-OHL-S-2.0\nhttps://summercart64.dev"
(at 198 122.2 0)
(layer "B.SilkS")
(uuid "9c42ed30-fd86-4d8a-bb4f-89dcb0e60e6e")
(effects
(font
(size 1 1)
(thickness 0.15)
)
(justify left bottom mirror)
)
)
(gr_text "RX"
(at 123 84.75 90)
(layer "F.SilkS")
@ -26807,7 +26819,7 @@
)
)
)
(gr_text "HW ver: 2.1"
(gr_text "HW ver: 2.1a"
(at 100.25 83.5 0)
(layer "F.SilkS")
(uuid "4ab65223-ff63-4a7c-853c-e7b8d8e12e76")
@ -26819,7 +26831,7 @@
(justify left)
)
)
(gr_text "11.08.2024"
(gr_text "10.03.2025"
(at 100.25 85.25 0)
(layer "F.SilkS")
(uuid "4ebec920-49dd-4966-8b6b-a500a99cc601")
@ -26842,17 +26854,6 @@
)
)
)
(gr_text "Licensed under GNU GPLv3\nhttps://summercart64.dev"
(at 137 120 0)
(layer "F.SilkS")
(uuid "77e7f556-c63c-46bb-bff0-b858da0827cb")
(effects
(font
(size 0.95 0.95)
(thickness 0.15)
)
)
)
(gr_text "3V3"
(at 135 116 0)
(layer "F.SilkS")
@ -26887,19 +26888,6 @@
(justify left)
)
)
(gr_text "© 2020 - 2024\nMateusz Faderewski"
(at 200.1 78.8 0)
(layer "F.Mask")
(uuid "9c42ed30-fd86-4d8a-bb4f-89dcb0e60e6e")
(effects
(font
(size 1 1)
(thickness 0.25)
(bold yes)
)
(justify right top)
)
)
(segment
(start 172.325 85.075)
(end 172.25 85)

View File

@ -6,8 +6,8 @@
(paper "A2")
(title_block
(title "SummerCart64")
(date "2024-08-11")
(rev "2.1")
(date "2025-03-10")
(rev "2.1a")
(company "Mateusz Faderewski")
)
(lib_symbols
@ -19434,7 +19434,6 @@
)
(label "USB_D+"
(at 97.79 124.46 0)
(fields_autoplaced yes)
(effects
(font
(size 1.27 1.27)
@ -19445,7 +19444,6 @@
)
(label "USB_D-"
(at 97.79 121.92 0)
(fields_autoplaced yes)
(effects
(font
(size 1.27 1.27)

File diff suppressed because one or more lines are too long

View File

@ -34,6 +34,7 @@ SRC_FILES = \
interrupts.c \
interrupts.S \
io.c \
joybus.c \
main.c \
menu.c \
reboot.S \

View File

@ -79,6 +79,7 @@ void boot (boot_params_t *params) {
while (cpu_io_read(&SP->DMA_BUSY));
cpu_io_write(&PI->SR, PI_SR_CLR_INTR | PI_SR_RESET);
cpu_io_write(&SI->SR, 0);
while ((cpu_io_read(&VI->CURR_LINE) & ~(VI_CURR_LINE_FIELD)) != 0);
cpu_io_write(&VI->V_INTR, 0x3FF);
cpu_io_write(&VI->H_LIMITS, 0);

View File

@ -116,8 +116,8 @@ static void display_draw_character (char c) {
}
if ((char_x + FONT_WIDTH) > (SCREEN_WIDTH - BORDER_WIDTH)) {
char_x = BORDER_WIDTH;
char_y += FONT_HEIGHT + LINE_SPACING;
char_x -= FONT_WIDTH;
c = '\x7F';
}
if ((c < ' ') || (c > '~')) {
@ -185,7 +185,7 @@ bool display_ready (void) {
}
void display_vprintf (const char *fmt, va_list args) {
char line[256];
char line[1024];
vsniprintf(line, sizeof(line), fmt, args);
display_draw_string(line);

View File

@ -48,8 +48,7 @@ void exception_fatal_handler (uint32_t exception_code, exception_t *e) {
display_printf(" s4: 0x%08lX s5: 0x%08lX s6: 0x%08lX s7: 0x%08lX\n", e->s4.u32, e->s5.u32, e->s6.u32, e->s7.u32);
display_printf(" t8: 0x%08lX t9: 0x%08lX k0: 0x%08lX k1: 0x%08lX\n", e->t8.u32, e->t9.u32, e->k0.u32, e->k1.u32);
display_printf(" gp: 0x%08lX sp: 0x%08lX s8: 0x%08lX ra: 0x%08lX\n", e->gp.u32, e->sp.u32, e->s8.u32, e->ra.u32);
display_printf(" hi: 0x%016lX\n", e->hi.u64);
display_printf(" lo: 0x%016lX\n", e->lo.u64);
display_printf(" hi: 0x%016lX lo: 0x%016lX\n", e->hi.u64, e->lo.u64);
while (true);
}

View File

@ -367,3 +367,19 @@ R0.15 (November 6, 2022)
Fixed string functions cannot write the unicode characters not in BMP when FF_LFN_UNICODE == 2 (UTF-8).
Fixed a compatibility issue in identification of GPT header.
R0.15a (November 22, 2024)
Fixed a complie error when FF_FS_LOCK != 0.
Fixed a potential issue when work FatFs concurrency with FF_FS_REENTRANT, FF_VOLUMES >= 2 and FF_FS_LOCK > 0.
Made f_setlabel() accept a volume label in Unix style volume ID when FF_STR_VOLUME_ID == 2.
Made FatFs update PercInUse field in exFAT VBR. (A preceding f_getfree() is needed for the accuracy)
1. January 9, 2025
--------------------------------------------------------------------------------------------------
FatFs fails to load the FsInfo in FAT32 volumes and the f_getfree function will always be forced
a full FAT scan which takes a long time.
This problem was appeared at R0.15a and reported via an e-mail.
To fix this problem, apply ff15a_p1.diff to the ff.c.

View File

@ -50,6 +50,9 @@ DRESULT disk_read (BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
if (pdrv > 0) {
return RES_PARERR;
}
if ((sector + count) > 0x100000000ULL) {
return RES_PARERR;
}
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));
@ -82,6 +85,9 @@ DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count) {
if (pdrv > 0) {
return RES_PARERR;
}
if ((sector + count) > 0x100000000ULL) {
return RES_PARERR;
}
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.15 /
/ FatFs - Generic FAT Filesystem module R0.15a /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2022, ChaN, all right reserved.
/ Copyright (C) 2024, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
@ -20,14 +20,15 @@
#ifndef FF_DEFINED
#define FF_DEFINED 80286 /* Revision ID */
#define FF_DEFINED 5380 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(FFCONF_DEF)
#include "ffconf.h" /* FatFs configuration options */
#endif
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
@ -48,18 +49,18 @@ typedef unsigned __int64 QWORD;
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
typedef uint16_t WORD; /* 16-bit unsigned */
typedef uint32_t DWORD; /* 32-bit unsigned */
typedef uint64_t QWORD; /* 64-bit unsigned */
typedef WORD WCHAR; /* UTF-16 code unit */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
typedef unsigned short WORD; /* short must be 16-bit */
typedef unsigned long DWORD; /* long must be 32-bit */
typedef WORD WCHAR; /* UTF-16 code unit */
#endif
@ -113,15 +114,15 @@ typedef char TCHAR;
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
BYTE pd; /* Associated physical drive */
BYTE pt; /* Associated partition (0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
extern PARTITION VolToPart[]; /* Volume to partition mapping table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
extern const char* VolumeStr[FF_VOLUMES]; /* User defined volume ID table */
#endif
#endif
@ -130,12 +131,12 @@ extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE fs_type; /* Filesystem type (0:blank filesystem object) */
BYTE pdrv; /* Volume hosting physical drive */
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] status (b0:dirty) */
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
BYTE wflag; /* win[] status (1:dirty) */
BYTE fsi_flag; /* Allocation information control (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
@ -146,11 +147,11 @@ typedef struct {
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
BYTE* dirbuf; /* Directory entry block scratch pad buffer for exFAT */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
DWORD last_clst; /* Last allocated cluster (Unknown if >= n_fatent) */
DWORD free_clst; /* Number of free clusters (Unknown if >= n_fatent-2) */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
@ -272,24 +273,24 @@ typedef struct {
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_OK = 0, /* (0) Function succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NOT_READY, /* (3) The physical drive does not work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_DENIED, /* (7) Access denied due to a prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to a prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_NO_FILESYSTEM, /* (13) Could not find a valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs function aborted due to some problem */
FR_TIMEOUT, /* (15) Could not take control of the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated or given buffer is insufficient in size */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
@ -375,7 +376,7 @@ DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
#if FF_FS_REENTRANT /* Sync functions */
#if FF_FS_REENTRANT /* Sync functions */
int ff_mutex_create (int vol); /* Create a sync object */
void ff_mutex_delete (int vol); /* Delete a sync object */
int ff_mutex_take (int vol); /* Lock sync object */
@ -389,7 +390,7 @@ void ff_mutex_give (int vol); /* Unlock sync object */
/* Flags and Offset Address */
/*--------------------------------------------------------------*/
/* File access mode and open method flags (3rd argument of f_open) */
/* File access mode and open method flags (3rd argument of f_open function) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
@ -398,10 +399,10 @@ void ff_mutex_give (int vol); /* Unlock sync object */
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
/* Fast seek controls (2nd argument of f_lseek function) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
/* Format options (2nd argument of f_mkfs function) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04

View File

@ -2,7 +2,7 @@
/ Configurations of FatFs Module
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 80286 /* Revision ID */
#define FFCONF_DEF 5380 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
@ -31,45 +31,45 @@
#define FF_USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
/* This option switches f_mkfs(). (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 0
/* This option switches fast seek function. (0:Disable or 1:Enable) */
/* This option switches fast seek feature. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
/* This option switches f_expand(). (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/* This option switches attribute control API functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 1
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/* This option switches volume label API functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/* This option switches f_forward(). (0:Disable or 1:Enable) */
#define FF_USE_STRFUNC 0
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_PRINT_LLI 0
#define FF_PRINT_FLOAT 0
#define FF_STRF_ENCODE 3
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/* FF_USE_STRFUNC switches the string API functions, f_gets(), f_putc(), f_puts()
/ and f_printf().
/
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion.
/ 1: Enable without LF - CRLF conversion.
/ 2: Enable with LF - CRLF conversion.
/
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
/ makes f_printf() support floating point argument. These features want C99 or later.
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/ to be read/written via those functions.
/
@ -118,15 +118,15 @@
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
@ -145,7 +145,7 @@
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 1023
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
@ -156,9 +156,9 @@
#define FF_FS_RPATH 0
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 0: Disable relative path and remove related API functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
/ 2: f_getcwd() is available in addition to 1.
*/
@ -175,7 +175,7 @@
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ logical drive. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table is needed as:
@ -188,9 +188,9 @@
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ When this feature is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ function will be available. */
/ will be available. */
#define FF_MIN_SS 512
@ -198,25 +198,25 @@
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk, but a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
/ configured for variable sector size mode and disk_ioctl() needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_LBA64 0
#define FF_LBA64 1
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
#define FF_MIN_GPT 0x10000000
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and
/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
/ To enable this feature, also CTRL_TRIM command should be implemented to
/ the disk_ioctl(). */
@ -240,20 +240,20 @@
#define FF_FS_NORTC 0
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2022
#define FF_NORTC_YEAR 2025
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
/ to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at the first time after volume mount will force
/ option, and f_getfree() at the first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
@ -280,13 +280,13 @@
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this featuer.
/ and f_fdisk(), are always not re-entrant. Only file/directory access to
/ the same volume is under control of this featuer.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
/ function, must be added to the project. Samples are available in ffsystem.c.
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
/ must be added to the project. Samples are available in ffsystem.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/

View File

@ -3,7 +3,7 @@
#include "vr4300.h"
static void cache_operation (uint8_t operation, uint8_t line_size, void *address, size_t length) {
static inline void cache_operation (const uint8_t operation, uint8_t line_size, void *address, size_t length) {
uint32_t cache_address = (((uint32_t) (address)) & (~(line_size - 1)));
while (cache_address < ((uint32_t) (address) + length)) {
asm volatile (
@ -109,3 +109,27 @@ void pi_dma_write (io32_t *address, void *buffer, size_t length) {
});
while (pi_busy());
}
uint32_t si_busy (void) {
return (cpu_io_read(&SI->SR) & (SI_SR_IO_BUSY | SI_SR_DMA_BUSY));
}
void si_dma_read (void *buffer) {
cache_data_hit_writeback_invalidate(buffer, PIF_RAM_LENGTH);
WITH_INTERRUPTS_DISABLED({
while (si_busy());
cpu_io_write(&SI->MADDR, (uint32_t) (PHYSICAL(buffer)));
cpu_io_write(&SI->RDMA, (uint32_t) (PIF_RAM));
});
while (si_busy());
}
void si_dma_write (void *buffer) {
cache_data_hit_writeback(buffer, PIF_RAM_LENGTH);
WITH_INTERRUPTS_DISABLED({
while (si_busy());
cpu_io_write(&SI->MADDR, (uint32_t) (PHYSICAL(buffer)));
cpu_io_write(&SI->WDMA, (uint32_t) (PIF_RAM));
});
while (si_busy());
}

View File

@ -202,6 +202,24 @@ typedef struct {
#define PI_SR_CLR_INTR (1 << 1)
typedef struct {
io32_t MADDR;
io32_t RDMA;
io32_t __reserved_0[2];
io32_t WDMA;
io32_t __reserved_1[1];
io32_t SR;
} si_regs_t;
#define SI_BASE (0x04800000UL)
#define SI ((si_regs_t *) SI_BASE)
#define SI_SR_DMA_BUSY (1 << 0)
#define SI_SR_IO_BUSY (1 << 1)
#define SI_SR_DMA_ERROR (1 << 3)
#define SI_SR_INTERRUPT (1 << 12)
#define ROM_DDIPL_BASE (0x06000000UL)
#define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE)
@ -210,6 +228,14 @@ typedef struct {
#define ROM_CART ((io32_t *) ROM_CART_BASE)
#define PIF_RAM_BASE (0x1FC007C0)
#define PIF_RAM_LENGTH (64)
#define PIF_RAM ((io32_t *) PIF_RAM_BASE)
void cache_data_hit_writeback_invalidate (void *address, size_t length);
void cache_data_hit_writeback (void *address, size_t length);
void cache_inst_hit_invalidate (void *address, size_t length);
uint32_t c0_count (void);
void delay_ms (int ms);
uint32_t cpu_io_read (io32_t *address);
@ -220,9 +246,9 @@ uint32_t pi_io_read (io32_t *address);
void pi_io_write (io32_t *address, uint32_t value);
void pi_dma_read (io32_t *address, void *buffer, size_t length);
void pi_dma_write (io32_t *address, void *buffer, size_t length);
void cache_data_hit_writeback_invalidate (void *address, size_t length);
void cache_data_hit_writeback (void *address, size_t length);
void cache_inst_hit_invalidate (void *address, size_t length);
uint32_t si_busy (void);
void si_dma_read (void *buffer);
void si_dma_write (void *buffer);
#endif

169
sw/bootloader/src/joybus.c Normal file
View File

@ -0,0 +1,169 @@
#include <string.h>
#include "io.h"
#include "joybus.h"
#define PIF_ESCAPE_SKIP_CHANNEL (0x00)
#define PIF_ESCAPE_END_OF_FRAME (0xFE)
#define PIF_COMMAND_JOYBUS_START (1 << 0)
#define JOYBUS_CMD_INFO (0x00)
#define JOYBUS_CMD_STATE (0x01)
#define JOYBUS_CMD_RESET (0xFF)
typedef struct __attribute__((packed)) {
struct __attribute__((packed)) {
uint8_t skip : 1;
uint8_t reset : 1;
uint8_t length : 6;
} tx;
struct __attribute__((packed)) {
uint8_t no_device : 1;
uint8_t timeout : 1;
uint8_t length : 6;
} rx;
uint8_t cmd;
} joybus_trx_info_t;
typedef struct __attribute__((packed)) {
joybus_trx_info_t info;
struct __attribute__((packed)) {
uint16_t id;
uint8_t flags;
} rx;
} joybus_cmd_info_t;
typedef struct __attribute__((packed)) {
joybus_trx_info_t info;
struct __attribute__((packed)) {
uint8_t a : 1;
uint8_t b : 1;
uint8_t z : 1;
uint8_t start : 1;
uint8_t up : 1;
uint8_t down : 1;
uint8_t left : 1;
uint8_t right : 1;
uint8_t reset : 1;
uint8_t __unused : 1;
uint8_t l : 1;
uint8_t r : 1;
uint8_t c_up : 1;
uint8_t c_down : 1;
uint8_t c_left : 1;
uint8_t c_right : 1;
int8_t x;
int8_t y;
} rx;
} joybus_cmd_state_t;
static void joybus_clear_buffer (uint8_t *buffer) {
for (int i = 0; i < PIF_RAM_LENGTH; i++) {
buffer[i] = 0;
}
}
static void joybus_set_starting_channel (uint8_t **ptr, int channel) {
for (int i = 0; i < channel; i++) {
**ptr = PIF_ESCAPE_SKIP_CHANNEL;
*ptr += 1;
}
}
static uint8_t *joybus_append_command (uint8_t **ptr, void *cmd, size_t length) {
((joybus_trx_info_t *) (cmd))->tx.length += 1;
uint8_t *cmd_ptr = *ptr;
memcpy(cmd_ptr, cmd, length);
*ptr += length;
return cmd_ptr;
}
static void joybus_load_result (uint8_t *cmd_ptr, void *cmd, size_t length) {
memcpy(cmd, cmd_ptr, length);
}
static void joybus_finish_frame (uint8_t **ptr) {
**ptr = PIF_ESCAPE_END_OF_FRAME;
*ptr += 1;
}
static void joybus_perform_transaction (uint8_t *buffer) {
buffer[PIF_RAM_LENGTH - 1] = PIF_COMMAND_JOYBUS_START;
si_dma_write(buffer);
si_dma_read(buffer);
}
static bool joybus_device_present (joybus_trx_info_t *trx_info) {
return !(trx_info->rx.no_device || trx_info->rx.timeout);
}
static bool joybus_execute_command (int port, void *cmd, size_t length) {
uint8_t buffer[PIF_RAM_LENGTH] __attribute__((aligned(8)));
uint8_t *ptr = buffer;
joybus_clear_buffer(buffer);
joybus_set_starting_channel(&ptr, port);
uint8_t *cmd_ptr = joybus_append_command(&ptr, cmd, length);
joybus_finish_frame(&ptr);
joybus_perform_transaction(buffer);
joybus_load_result(cmd_ptr, cmd, length);
return joybus_device_present((joybus_trx_info_t *) (cmd_ptr));
}
static void joybus_copy_controller_info (joybus_cmd_info_t *cmd, joybus_controller_info_t *info) {
info->id = cmd->rx.id;
info->flags = cmd->rx.flags;
}
static void joybus_copy_controller_state (joybus_cmd_state_t *cmd, joybus_controller_state_t *state) {
state->a = cmd->rx.a;
state->b = cmd->rx.b;
state->z = cmd->rx.z;
state->start = cmd->rx.start;
state->up = cmd->rx.up;
state->down = cmd->rx.down;
state->left = cmd->rx.left;
state->right = cmd->rx.right;
state->reset = cmd->rx.reset;
state->l = cmd->rx.l;
state->r = cmd->rx.r;
state->c_up = cmd->rx.c_up;
state->c_down = cmd->rx.c_down;
state->c_left = cmd->rx.c_left;
state->c_right = cmd->rx.c_right;
state->x = cmd->rx.x;
state->y = cmd->rx.y;
}
bool joybus_get_controller_info (int port, joybus_controller_info_t *info, bool reset) {
joybus_cmd_info_t cmd = { .info = {
.cmd = reset ? JOYBUS_CMD_RESET : JOYBUS_CMD_INFO,
.rx = { .length = sizeof(cmd.rx) }
} };
if (joybus_execute_command(port, &cmd, sizeof(cmd))) {
joybus_copy_controller_info(&cmd, info);
return true;
}
return false;
}
bool joybus_get_controller_state (int port, joybus_controller_state_t *state) {
joybus_cmd_state_t cmd = { .info = {
.cmd = JOYBUS_CMD_STATE,
.rx = { .length = sizeof(cmd.rx) }
} };
if (joybus_execute_command(port, &cmd, sizeof(cmd))) {
joybus_copy_controller_state(&cmd, state);
return true;
}
return false;
}

View File

@ -0,0 +1,39 @@
#ifndef JOYBUS_H__
#define JOYBUS_H__
#include <stdbool.h>
#include <stdint.h>
typedef struct {
uint16_t id;
uint8_t flags;
} joybus_controller_info_t;
typedef struct {
bool a;
bool b;
bool z;
bool start;
bool up;
bool down;
bool left;
bool right;
bool reset;
bool l;
bool r;
bool c_up;
bool c_down;
bool c_left;
bool c_right;
int8_t x;
int8_t y;
} joybus_controller_state_t;
bool joybus_get_controller_info (int port, joybus_controller_info_t *info, bool reset);
bool joybus_get_controller_state (int port, joybus_controller_state_t *state);
#endif

View File

@ -3,6 +3,7 @@
#include "error.h"
#include "init.h"
#include "io.h"
#include "joybus.h"
#include "menu.h"
#include "sc64.h"
@ -22,6 +23,20 @@ void main (void) {
boot_params.cic_seed = (sc64_boot_params.cic_seed & 0xFF);
boot_params.detect_cic_seed = (sc64_boot_params.cic_seed == CIC_SEED_AUTO);
switch (sc64_boot_params.boot_mode) {
case BOOT_MODE_ROM:
case BOOT_MODE_DDIPL: {
joybus_controller_state_t controller_state;
if (joybus_get_controller_state(0, &controller_state) && controller_state.r) {
sc64_boot_params.boot_mode = BOOT_MODE_MENU;
}
break;
}
default:
break;
}
switch (sc64_boot_params.boot_mode) {
case BOOT_MODE_MENU:
menu_load();

View File

@ -15,49 +15,61 @@ static const char *fatfs_error_codes[] = {
"No error",
"A hard error occurred in the low level disk I/O layer",
"Assertion failed",
"The physical drive cannot work",
"The physical drive does not work",
"Could not find the file",
"Could not find the path",
"The path name format is invalid",
"Access denied due to prohibited access or directory full",
"Access denied due to prohibited access",
"Access denied due to a prohibited access or directory full",
"Access denied due to a prohibited access",
"The file/directory object is invalid",
"The physical drive is write protected",
"The logical drive number is invalid",
"The volume has no work area",
"There is no valid FAT volume",
"The f_mkfs() aborted due to any problem",
"Could not get a grant to access the volume within defined period",
"Could not find a valid FAT volume",
"The f_mkfs function aborted due to some problem",
"Could not take control of the volume within defined period",
"The operation is rejected according to the file sharing policy",
"LFN working buffer could not be allocated",
"LFN working buffer could not be allocated or given buffer is insufficient in size",
"Number of open files > FF_FS_LOCK",
"Given parameter is invalid",
};
#define FF_CHECK(x, message, ...) { \
fresult = x; \
if (fresult != FR_OK) { \
error_display( \
message "\n" \
" > FatFs error: %s\n" \
" > SD card error: %s (%08X)", \
__VA_ARGS__ __VA_OPT__(,) \
fatfs_error_codes[fresult], \
sc64_error_description(sc64_error_fatfs), sc64_error_fatfs \
); \
} \
static void menu_fix_file_size (FIL *fil) {
fil->obj.objsize = ALIGN(f_size(fil), FF_MAX_SS);
}
static void menu_error_display (const char *message, FRESULT fresult) {
error_display(
" > FatFs error: %s\n"
" > SD card error: %s (%08X)\n"
"\n"
"[ %s ]\n"
" > Please insert a FAT32/exFAT formatted SD card.\n"
" > To start the menu please put \"sc64menu.n64\" file\n"
" in the top directory on the SD card.\n"
" > Latest menu version is available on the\n"
" https://menu.summercart64.dev website.\n",
fatfs_error_codes[fresult],
sc64_error_description(sc64_error_fatfs),
sc64_error_fatfs,
message
);
}
static void fix_menu_file_size (FIL *fil) {
fil->obj.objsize = ALIGN(f_size(fil), FF_MAX_SS);
#define FF_CHECK(x, message) { \
fresult = x; \
if (fresult != FR_OK) { \
menu_error_display(message, fresult); \
} \
}
void menu_load (void) {
sc64_error_t error;
bool writeback_pending;
sc64_sd_card_status_t sd_card_status;
FRESULT fresult;
FATFS fs;
FIL fil;
@ -73,9 +85,17 @@ void menu_load (void) {
error_display("Could not disable save writeback\n (%08X) - %s", error, sc64_error_description(error));
}
if ((error = sc64_sd_card_get_status(&sd_card_status)) != SC64_OK) {
error_display("Could not get SD card status\n (%08X) - %s", error, sc64_error_description(error));
}
if (!(sd_card_status & SD_CARD_STATUS_INSERTED)) {
menu_error_display("No SD card detected in the slot", FR_OK);
}
FF_CHECK(f_mount(&fs, "", 1), "SD card initialize error");
FF_CHECK(f_open(&fil, "sc64menu.n64", FA_READ), "Could not open menu executable (sc64menu.n64)");
fix_menu_file_size(&fil);
FF_CHECK(f_open(&fil, "sc64menu.n64", FA_READ), "Could not open menu executable");
menu_fix_file_size(&fil);
FF_CHECK(f_read(&fil, (void *) (ROM_ADDRESS), f_size(&fil), &bytes_read), "Could not read menu file");
FF_CHECK((bytes_read != f_size(&fil)) ? FR_INT_ERR : FR_OK, "Read size is different than expected");
FF_CHECK(f_close(&fil), "Could not close menu file");

View File

@ -104,7 +104,8 @@ typedef enum {
SAVE_TYPE_SRAM = 3,
SAVE_TYPE_FLASHRAM = 4,
SAVE_TYPE_SRAM_BANKED = 5,
SAVE_TYPE_SRAM_1M = 6
SAVE_TYPE_SRAM_1M = 6,
SAVE_TYPE_FLASHRAM_FAKE = 7,
} sc64_save_type_t;
typedef enum {

View File

@ -5,6 +5,7 @@
#include "fatfs/ff.h"
#include "init.h"
#include "io.h"
#include "joybus.h"
#include "sc64.h"
#include "test.h"
@ -55,6 +56,7 @@ static void fill_random (uint32_t *buffer, int size, uint32_t pattern, uint32_t
static void test_sc64_cfg (void) {
sc64_error_t error;
uint32_t button_state;
joybus_controller_info_t controller_info;
uint32_t identifier;
uint16_t major;
uint16_t minor;
@ -71,6 +73,10 @@ static void test_sc64_cfg (void) {
display_printf("done\n\n");
if (joybus_get_controller_info(0, &controller_info, false)) {
display_printf("Found controller on port 0: 0x%04X | 0x%02X\n\n", controller_info.id, controller_info.flags);
}
if ((error = sc64_get_identifier(&identifier)) != SC64_OK) {
error_display("Command IDENTIFIER_GET failed\n (%08X) - %s", error, sc64_error_description(error));
}
@ -178,7 +184,7 @@ static void test_sd_card_io (void) {
uint8_t sector[512] __attribute__((aligned(8)));
if ((error = sc64_sd_card_get_status(&card_status)) != SC64_OK) {
error_display("Could not get SD card info\n (%08X) - %s", error, sc64_error_description(error));
error_display("Could not get SD card status\n (%08X) - %s", error, sc64_error_description(error));
}
if (card_status & SD_CARD_STATUS_INSERTED) {
@ -188,11 +194,11 @@ static void test_sd_card_io (void) {
}
if ((error = sc64_sd_card_deinit()) != SC64_OK) {
error_display("SD card deinit error\n (%08X) - %s", error, sc64_error_description(error));
return display_printf("SD card deinit error, skipping test\n (%08X) - %s", error, sc64_error_description(error));
}
if ((error = sc64_sd_card_init()) != SC64_OK) {
return display_printf("SD card init error\n (%08X) - %s\n", error, sc64_error_description(error));
return display_printf("SD card init error, skipping test\n (%08X) - %s\n", error, sc64_error_description(error));
}
if ((error = sc64_sd_card_get_status(&card_status)) != SC64_OK) {
@ -261,7 +267,7 @@ static void test_sd_card_fatfs (void) {
UINT bytes;
if ((error = sc64_sd_card_deinit()) != SC64_OK) {
error_display("SD card deinit error\n (%08X) - %s", error, sc64_error_description(error));
return display_printf("SD card deinit error, skipping test\n (%08X) - %s", error, sc64_error_description(error));
}
if ((fresult = f_mount(&fs, "", 1)) != FR_OK) {

View File

@ -35,9 +35,10 @@ const struct {
void version_print (void) {
display_printf("[ SC64 bootloader metadata ]\n");
display_printf("branch: %s | tag: %s\n", version.git_branch, version.git_tag);
display_printf("[ SC64 btldr ] %s | %s\n", version.git_branch, version.git_tag);
display_printf("sha: %s\n", version.git_sha);
display_printf("msg: %s\n", version.git_message);
display_printf("\n");
display_printf("Copyright 2020 - 2025 Mateusz Faderewski\n");
display_printf("Licensed under | FW/SW: GPL-3.0 | HW: CERN-OHL-S-2.0\n");
display_printf("Official website: https://summercart64.dev\n\n");
}

View File

@ -92,15 +92,6 @@ typedef enum {
TV_TYPE_PASSTHROUGH = 3
} tv_type_t;
typedef enum {
SD_CARD_OP_DEINIT = 0,
SD_CARD_OP_INIT = 1,
SD_CARD_OP_GET_STATUS = 2,
SD_CARD_OP_GET_INFO = 3,
SD_CARD_OP_BYTE_SWAP_ON = 4,
SD_CARD_OP_BYTE_SWAP_OFF = 5,
} sd_card_op_t;
typedef enum {
DIAGNOSTIC_ID_VOLTAGE_TEMPERATURE = 0,
} diagnostic_id_t;
@ -287,6 +278,9 @@ static bool cfg_set_save_type (save_type_t save_type) {
case SAVE_TYPE_SRAM_1M:
cfg_change_scr_bits(CFG_SCR_SRAM_ENABLED, true);
break;
case SAVE_TYPE_FLASHRAM_FAKE:
cfg_change_scr_bits(CFG_SCR_FLASHRAM_ENABLED, true);
break;
default:
save_type = SAVE_TYPE_NONE;
break;
@ -641,15 +635,15 @@ void cfg_process (void) {
case CMD_ID_SD_CARD_OP: {
sd_error_t error = SD_OK;
switch (p.data[1]) {
case SD_CARD_OP_DEINIT:
error = sd_get_lock(SD_LOCK_N64);
case SD_OP_DEINIT:
error = sd_try_lock(SD_LOCK_N64);
if (error == SD_OK) {
sd_card_deinit();
sd_release_lock(SD_LOCK_N64);
}
break;
case SD_CARD_OP_INIT:
case SD_OP_INIT:
error = sd_try_lock(SD_LOCK_N64);
if (error == SD_OK) {
led_activity_on();
@ -661,11 +655,11 @@ void cfg_process (void) {
}
break;
case SD_CARD_OP_GET_STATUS:
case SD_OP_GET_STATUS:
p.data[1] = sd_card_get_status();
break;
case SD_CARD_OP_GET_INFO:
case SD_OP_GET_INFO:
if (cfg_translate_address(&p.data[0], SD_CARD_INFO_SIZE, (SDRAM | BRAM))) {
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
}
@ -675,14 +669,14 @@ void cfg_process (void) {
}
break;
case SD_CARD_OP_BYTE_SWAP_ON:
case SD_OP_BYTE_SWAP_ON:
error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
error = sd_set_byte_swap(true);
}
break;
case SD_CARD_OP_BYTE_SWAP_OFF:
case SD_OP_BYTE_SWAP_OFF:
error = sd_get_lock(SD_LOCK_N64);
if (error == SD_OK) {
error = sd_set_byte_swap(false);

View File

@ -14,6 +14,7 @@ typedef enum {
SAVE_TYPE_FLASHRAM = 4,
SAVE_TYPE_SRAM_BANKED = 5,
SAVE_TYPE_SRAM_1M = 6,
SAVE_TYPE_FLASHRAM_FAKE = 7,
__SAVE_TYPE_COUNT
} save_type_t;

View File

@ -1,12 +1,18 @@
#include <stdint.h>
#include "cfg.h"
#include "fpga.h"
#include "hw.h"
#include "timer.h"
#define FLASHRAM_SIZE (128 * 1024)
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
#define FLASHRAM_PAGE_SIZE (128)
#define FLASHRAM_ADDRESS (0x03FE0000UL)
#define FLASHRAM_BUFFER_ADDRESS (0x05002C00UL)
#define FLASHRAM_SIZE (128 * 1024)
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
#define FLASHRAM_PAGE_SIZE (128)
#define FLASHRAM_ADDRESS (0x03FE0000UL)
#define FLASHRAM_BUFFER_ADDRESS (0x05002C00UL)
#define FLASHRAM_WRITE_TIMING_MS (3)
#define FLASHRAM_ERASE_TIMING_MS (200)
typedef enum {
@ -16,6 +22,13 @@ typedef enum {
OP_WRITE_PAGE
} flashram_op_t;
struct process {
bool pending;
};
static struct process p;
static flashram_op_t flashram_operation_type (uint32_t scr) {
if (!(scr & FLASHRAM_SCR_PENDING)) {
@ -38,6 +51,7 @@ void flashram_init (void) {
if (fpga_reg_get(REG_FLASHRAM_SCR) & FLASHRAM_SCR_PENDING) {
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);
}
p.pending = false;
}
@ -53,8 +67,15 @@ void flashram_process (void) {
uint8_t write_buffer[FLASHRAM_PAGE_SIZE];
uint32_t page = ((scr & FLASHRAM_SCR_PAGE_MASK) >> FLASHRAM_SCR_PAGE_BIT);
const bool full_emulation = (cfg_get_save_type() != SAVE_TYPE_FLASHRAM_FAKE);
if (op == OP_WRITE_PAGE) {
if (p.pending) {
if (timer_countdown_elapsed(TIMER_ID_FLASHRAM)) {
p.pending = false;
} else {
return;
}
} else if (op == OP_WRITE_PAGE) {
uint8_t page_buffer[FLASHRAM_PAGE_SIZE];
uint32_t address = (FLASHRAM_ADDRESS + (page * FLASHRAM_PAGE_SIZE));
@ -63,11 +84,24 @@ void flashram_process (void) {
fpga_mem_read(address, FLASHRAM_PAGE_SIZE, write_buffer);
for (int i = 0; i < FLASHRAM_PAGE_SIZE; i++) {
write_buffer[i] &= page_buffer[i];
if (full_emulation) {
write_buffer[i] &= page_buffer[i];
} else {
write_buffer[i] = page_buffer[i];
}
}
fpga_mem_write(address, FLASHRAM_PAGE_SIZE, write_buffer);
} else if ((op == OP_ERASE_SECTOR) || (op == OP_ERASE_ALL)) {
if (full_emulation) {
hw_delay_ms(FLASHRAM_WRITE_TIMING_MS);
}
} else if ((op == OP_ERASE_SECTOR) || (op == OP_ERASE_ALL)) {
if (full_emulation) {
p.pending = true;
timer_countdown_start(TIMER_ID_FLASHRAM, FLASHRAM_ERASE_TIMING_MS);
}
for (int i = 0; i < FLASHRAM_PAGE_SIZE; i++) {
write_buffer[i] = 0xFF;
}
@ -80,6 +114,10 @@ void flashram_process (void) {
for (uint32_t offset = 0; offset < erase_size; offset += FLASHRAM_PAGE_SIZE) {
fpga_mem_write(address + offset, FLASHRAM_PAGE_SIZE, write_buffer);
}
if (full_emulation) {
return;
}
}
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);

View File

@ -4,7 +4,7 @@
#include "timer.h"
#define SD_INIT_BUFFER_ADDRESS (0x05002BB8UL)
#define SD_INIT_BUFFER_ADDRESS (0x05002A00UL)
#define BYTE_SWAP_ADDRESS_END (0x05000000UL)
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
@ -35,11 +35,10 @@
#define R7_CHECK_PATTERN (0xAA << 0)
#define TIMEOUT_INIT_MS (1000)
#define TIMEOUT_DATA_MS (5000)
#define DAT_CRC16_LENGTH (8)
#define DAT_BLOCK_MAX_COUNT (256)
#define DAT_TIMEOUT_INIT_MS (2000)
#define DAT_TIMEOUT_DATA_MS (5000)
typedef enum {
@ -59,16 +58,12 @@ typedef enum {
RSP_R7,
} rsp_type_t;
typedef enum {
DAT_READ,
DAT_WRITE,
} dat_mode_t;
typedef enum {
DAT_OK,
DAT_BUSY,
DAT_ERROR_IO,
DAT_ERROR_TIMEOUT,
} dat_error_t;
} dat_status_t;
typedef enum {
CMD6_OK,
@ -111,6 +106,7 @@ static void sd_set_clock (sd_clock_t mode) {
}
}
static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
uint32_t scr;
uint32_t cmd_data;
@ -176,52 +172,109 @@ static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp)
return false;
}
static void sd_dat_prepare (uint32_t address, uint32_t count, dat_mode_t mode) {
uint32_t length = (count * SD_SECTOR_SIZE);
uint32_t sd_dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_FIFO_FLUSH);
uint32_t sd_dma_scr = DMA_SCR_START;
if (mode == DAT_READ) {
sd_dat |= SD_DAT_START_READ;
sd_dma_scr |= DMA_SCR_DIRECTION;
if (p.byte_swap && (address < BYTE_SWAP_ADDRESS_END)) {
sd_dma_scr |= DMA_SCR_BYTE_SWAP;
}
} else {
sd_dat |= SD_DAT_START_WRITE;
static void sd_dat_start_write (uint32_t count) {
uint32_t dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_START_WRITE | SD_DAT_FIFO_FLUSH);
fpga_reg_set(REG_SD_DAT, dat);
}
static void sd_dat_start_read (uint32_t count) {
uint32_t dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_START_READ | SD_DAT_FIFO_FLUSH);
fpga_reg_set(REG_SD_DAT, dat);
}
static dat_status_t sd_dat_status (void) {
uint32_t dat = fpga_reg_get(REG_SD_DAT);
if (dat & SD_DAT_BUSY) {
return DAT_BUSY;
}
fpga_reg_set(REG_SD_DAT, sd_dat);
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
fpga_reg_set(REG_SD_DMA_LENGTH, length);
fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr);
if (dat & SD_DAT_ERROR) {
return DAT_ERROR_IO;
}
return DAT_OK;
}
static void sd_dat_abort (void) {
fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP);
fpga_reg_set(REG_SD_DAT, SD_DAT_STOP | SD_DAT_FIFO_FLUSH);
}
static dat_error_t sd_dat_wait (uint16_t timeout_ms) {
static void sd_dma_start_write (uint32_t address, uint32_t count) {
uint32_t length = (count * SD_SECTOR_SIZE);
uint32_t scr = DMA_SCR_START;
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
fpga_reg_set(REG_SD_DMA_LENGTH, length);
fpga_reg_set(REG_SD_DMA_SCR, scr);
}
static void sd_dma_start_read (uint32_t address, uint32_t count) {
uint32_t length = (count * SD_SECTOR_SIZE);
uint32_t scr = (DMA_SCR_DIRECTION | DMA_SCR_START);
if (p.byte_swap && (address < BYTE_SWAP_ADDRESS_END)) {
scr |= DMA_SCR_BYTE_SWAP;
}
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
fpga_reg_set(REG_SD_DMA_LENGTH, length);
fpga_reg_set(REG_SD_DMA_SCR, scr);
}
static bool sd_dma_is_busy (void) {
return (fpga_reg_get(REG_SD_DMA_SCR) & DMA_SCR_BUSY);
}
static void sd_dma_abort (void) {
fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP);
while (sd_dma_is_busy());
}
static void sd_start_write (uint32_t address, uint32_t count) {
sd_dat_start_write(count);
sd_dma_start_write(address, count);
}
static void sd_start_read (uint32_t address, uint32_t count) {
sd_dat_start_read(count);
sd_dma_start_read(address, count);
}
static void sd_abort (void) {
sd_dma_abort();
sd_dat_abort();
}
static dat_status_t sd_sync (uint16_t timeout_ms) {
timer_countdown_start(TIMER_ID_SD, timeout_ms);
do {
uint32_t sd_dat = fpga_reg_get(REG_SD_DAT);
uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR);
if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) {
if (sd_dat & SD_DAT_ERROR) {
sd_dat_abort();
return DAT_ERROR_IO;
}
return DAT_OK;
while (true) {
dat_status_t status = sd_dat_status();
switch (status) {
case DAT_BUSY:
break;
case DAT_OK:
if (!sd_dma_is_busy()) {
return DAT_OK;
}
break;
default:
sd_abort();
return status;
}
} while (!timer_countdown_elapsed(TIMER_ID_SD));
sd_dat_abort();
return DAT_ERROR_TIMEOUT;
if (timer_countdown_elapsed(TIMER_ID_SD)) {
sd_abort();
return DAT_ERROR_TIMEOUT;
}
}
}
static bool sd_dat_check_crc16 (uint8_t *data, uint32_t length) {
uint16_t device_crc[4];
uint16_t controller_crc[4];
@ -266,15 +319,15 @@ static bool sd_dat_check_crc16 (uint8_t *data, uint32_t length) {
static cmd6_error_t sd_cmd6 (uint32_t arg, uint8_t *buffer) {
uint32_t rsp;
sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ);
sd_start_read(SD_INIT_BUFFER_ADDRESS, 1);
if (sd_cmd(6, arg, RSP_R1, NULL)) {
sd_dat_abort();
sd_abort();
if ((!sd_cmd(13, p.rca, RSP_R1, &rsp)) && (rsp & R1_ILLEGAL_COMMAND)) {
return CMD6_ERROR_ILLEGAL_CMD;
}
return CMD6_ERROR_IO;
}
if (sd_dat_wait(DAT_TIMEOUT_INIT_MS) == DAT_ERROR_TIMEOUT) {
if (sd_sync(TIMEOUT_DATA_MS) == DAT_ERROR_TIMEOUT) {
return CMD6_ERROR_TIMEOUT;
}
fpga_mem_read(SD_INIT_BUFFER_ADDRESS, CMD6_DATA_LENGTH + DAT_CRC16_LENGTH, buffer);
@ -495,13 +548,12 @@ sd_error_t sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count)
if (sd_cmd(25, sector, RSP_R1, NULL)) {
return SD_ERROR_CMD25_IO;
}
sd_dat_prepare(address, blocks, DAT_WRITE);
dat_error_t error = sd_dat_wait(DAT_TIMEOUT_DATA_MS);
if (error != DAT_OK) {
sd_cmd(12, 0, RSP_R1b, NULL);
return (error == DAT_ERROR_IO) ? SD_ERROR_CMD25_CRC : SD_ERROR_CMD25_TIMEOUT;
}
sd_start_write(address, blocks);
dat_status_t status = sd_sync(TIMEOUT_DATA_MS);
sd_cmd(12, 0, RSP_R1b, NULL);
if (status != DAT_OK) {
return (status == DAT_ERROR_IO) ? SD_ERROR_CMD25_CRC : SD_ERROR_CMD25_TIMEOUT;
}
address += (blocks * SD_SECTOR_SIZE);
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
count -= blocks;
@ -529,17 +581,16 @@ sd_error_t sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) {
while (count > 0) {
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
sd_dat_prepare(address, blocks, DAT_READ);
sd_start_read(address, blocks);
if (sd_cmd(18, sector, RSP_R1, NULL)) {
sd_dat_abort();
sd_abort();
return SD_ERROR_CMD18_IO;
}
dat_error_t error = sd_dat_wait(DAT_TIMEOUT_DATA_MS);
if (error != DAT_OK) {
sd_cmd(12, 0, RSP_R1b, NULL);
return (error == DAT_ERROR_IO) ? SD_ERROR_CMD18_CRC : SD_ERROR_CMD18_TIMEOUT;
}
dat_status_t status = sd_sync(TIMEOUT_DATA_MS);
sd_cmd(12, 0, RSP_R1b, NULL);
if (status != DAT_OK) {
return (status == DAT_ERROR_IO) ? SD_ERROR_CMD18_CRC : SD_ERROR_CMD18_TIMEOUT;
}
address += (blocks * SD_SECTOR_SIZE);
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
count -= blocks;
@ -577,6 +628,7 @@ sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32
return SD_OK;
}
sd_error_t sd_get_lock (sd_lock_t lock) {
if (p.lock == lock) {
return SD_OK;

View File

@ -52,6 +52,15 @@ typedef enum {
SD_LOCK_USB,
} sd_lock_t;
typedef enum {
SD_OP_DEINIT = 0,
SD_OP_INIT = 1,
SD_OP_GET_STATUS = 2,
SD_OP_GET_INFO = 3,
SD_OP_BYTE_SWAP_ON = 4,
SD_OP_BYTE_SWAP_OFF = 5,
} sd_op_t;
sd_error_t sd_card_init (void);
void sd_card_deinit (void);

View File

@ -13,6 +13,7 @@ typedef enum {
TIMER_ID_SD,
TIMER_ID_USB,
TIMER_ID_WRITEBACK,
TIMER_ID_FLASHRAM,
__TIMER_ID_COUNT
} timer_id_t;

View File

@ -390,15 +390,15 @@ static void usb_rx_process (void) {
case 'i': {
sd_error_t error = SD_OK;
switch (p.rx_args[1]) {
case 0:
error = sd_get_lock(SD_LOCK_USB);
case SD_OP_DEINIT:
error = sd_try_lock(SD_LOCK_USB);
if (error == SD_OK) {
sd_card_deinit();
sd_release_lock(SD_LOCK_USB);
}
break;
case 1:
case SD_OP_INIT:
error = sd_try_lock(SD_LOCK_USB);
if (error == SD_OK) {
led_activity_on();
@ -410,10 +410,10 @@ static void usb_rx_process (void) {
}
break;
case 2:
case SD_OP_GET_STATUS:
break;
case 3:
case SD_OP_GET_INFO:
if (usb_validate_address_length(p.rx_args[0], SD_CARD_INFO_SIZE, true)) {
error = SD_ERROR_INVALID_ADDRESS;
} else {
@ -424,14 +424,14 @@ static void usb_rx_process (void) {
}
break;
case 4:
case SD_OP_BYTE_SWAP_ON:
error = sd_get_lock(SD_LOCK_USB);
if (error == SD_OK) {
error = sd_set_byte_swap(true);
}
break;
case 5:
case SD_OP_BYTE_SWAP_OFF:
error = sd_get_lock(SD_LOCK_USB);
if (error == SD_OK) {
error = sd_set_byte_swap(false);

View File

@ -3,7 +3,7 @@
#define VERSION_MAJOR (2)
#define VERSION_MINOR (20)
#define VERSION_REVISION (0)
#define VERSION_REVISION (2)
void version_firmware (uint32_t *version, uint32_t *revision) {

View File

@ -62,6 +62,10 @@ static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *le
*address = SRAM_FLASHRAM_ADDRESS;
*length = SRAM_1M_LENGTH;
break;
case SAVE_TYPE_FLASHRAM_FAKE:
*address = SRAM_FLASHRAM_ADDRESS;
*length = FLASHRAM_LENGTH;
break;
default:
*address = 0;
*length = 0;

View File

@ -1293,7 +1293,7 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "sc64deployer"
version = "2.20.0"
version = "2.20.2"
dependencies = [
"bindgen",
"cc",

View File

@ -1,8 +1,8 @@
[package]
name = "sc64deployer"
version = "2.20.0"
version = "2.20.2"
edition = "2021"
authors = ["Polprzewodnikowy"]
authors = ["Mateusz Faderewski (Polprzewodnikowy)"]
description = "SummerCart64 loader and control software"
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"

View File

@ -1,4 +1,6 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo::rerun-if-changed=../bootloader/src/");
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
cc::Build::new()

View File

@ -21,7 +21,36 @@ use std::{
};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(
author,
version,
about,
help_template = "\
{before-help}{name} v{version} - {about}
Copyright (C) 2020 - 2025 {author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Latest information about SummerCart64 is available
on the official website: <https://summercart64.dev/>.
{usage-heading} {usage}
{all-args}{after-help}"
)]
struct Cli {
#[command(subcommand)]
command: Commands,
@ -330,6 +359,7 @@ enum SaveType {
SramBanked,
Sram1m,
Flashram,
FlashramFake,
}
impl From<n64::SaveType> for SaveType {
@ -356,6 +386,7 @@ impl From<SaveType> for sc64::SaveType {
SaveType::SramBanked => Self::SramBanked,
SaveType::Sram1m => Self::Sram1m,
SaveType::Flashram => Self::Flashram,
SaveType::FlashramFake => Self::FlashramFake,
}
}
}
@ -499,7 +530,16 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<()
sc64.set_tv_type(tv_type)?;
}
sc64.calculate_cic_parameters(args.cic_seed)?;
let (seed, checksum, matched) = sc64.calculate_cic_parameters(args.cic_seed)?;
if !matched {
println!(
"{}",
"Warning: IPL3 in the ROM does not match any known variant. It may fail to boot."
.bright_yellow(),
);
println!("IPL3 has been automatically signed with the parameters listed below:");
println!("[seed = 0x{seed:02X} | checksum = 0x{checksum:012X}]");
}
if args.reboot && !sc64.try_notify_via_aux(sc64::AuxMessage::Reboot)? {
println!(
@ -614,7 +654,16 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
sc64.set_tv_type(tv_type)?;
}
sc64.calculate_cic_parameters(args.cic_seed)?;
let (seed, checksum, matched) = sc64.calculate_cic_parameters(args.cic_seed)?;
if !matched {
println!(
"{}",
"Warning: IPL3 in the ROM does not match any known variant. It may fail to boot."
.bright_yellow(),
);
println!("IPL3 has been automatically signed with the parameters listed below:");
println!("[seed = 0x{seed:02X} | checksum = 0x{checksum:012X}]");
}
if args.disk.len() == 0 {
let dd_mode = sc64::DdMode::DdIpl;

View File

@ -110,35 +110,35 @@ fn calculate_ipl3_checksum(ipl3: &[u8], seed: u8) -> Result<u64, Error> {
Ok(checksum)
}
pub fn sign_ipl3(ipl3: &[u8], custom_seed: Option<u8>) -> Result<(u8, u64), Error> {
pub fn sign_ipl3(ipl3: &[u8], custom_seed: Option<u8>) -> Result<(u8, u64, bool), Error> {
if let Some(seed) = custom_seed {
Ok((seed, calculate_ipl3_checksum(ipl3, seed)?))
} else {
let known_seed_checksum_pairs = [
(0xDD, 0x083C6C77E0B1u64), // 5167
(0x3F, 0x45CC73EE317Au64), // 6101
(0x3F, 0x44160EC5D9AFu64), // 7102
(0x3F, 0xA536C0F1D859u64), // 6102/7102
(0x78, 0x586FD4709867u64), // 6103/7103
(0x91, 0x8618A45BC2D3u64), // 6105/7105
(0x85, 0x2BBAD4E6EB74u64), // 6106/7106
(0xDD, 0x6EE8D9E84970u64), // NDXJ0
(0xDD, 0x6C216495C8B9u64), // NDDJ0
(0xDD, 0xE27F43BA93ACu64), // NDDJ1
(0xDD, 0x32B294E2AB90u64), // NDDJ2
(0xDE, 0x05BA2EF0A5F1u64), // NDDE0
];
for (seed, checksum) in known_seed_checksum_pairs {
if calculate_ipl3_checksum(ipl3, seed)? == checksum {
return Ok((seed, checksum));
}
}
// Unknown IPL3 detected, sign it with arbitrary seed (CIC6102/7101 value is used here)
const DEFAULT_SEED: u8 = 0x3F;
let checksum = calculate_ipl3_checksum(ipl3, DEFAULT_SEED)?;
Ok((DEFAULT_SEED, checksum))
return Ok((seed, calculate_ipl3_checksum(ipl3, seed)?, true));
}
let known_seed_checksum_pairs = [
(0xDD, 0x083C6C77E0B1u64), // 5167
(0x3F, 0x45CC73EE317Au64), // 6101
(0x3F, 0x44160EC5D9AFu64), // 7102
(0x3F, 0xA536C0F1D859u64), // 6102/7102
(0x78, 0x586FD4709867u64), // 6103/7103
(0x91, 0x8618A45BC2D3u64), // 6105/7105
(0x85, 0x2BBAD4E6EB74u64), // 6106/7106
(0xDD, 0x6EE8D9E84970u64), // NDXJ0
(0xDD, 0x6C216495C8B9u64), // NDDJ0
(0xDD, 0xE27F43BA93ACu64), // NDDJ1
(0xDD, 0x32B294E2AB90u64), // NDDJ2
(0xDE, 0x05BA2EF0A5F1u64), // NDDE0
];
for (seed, checksum) in known_seed_checksum_pairs {
if calculate_ipl3_checksum(ipl3, seed)? == checksum {
return Ok((seed, checksum, true));
}
}
// Unknown IPL3 detected, sign it with arbitrary seed (CIC6102/7101 value is used here)
const DEFAULT_SEED: u8 = 0x3F;
let checksum = calculate_ipl3_checksum(ipl3, DEFAULT_SEED)?;
Ok((DEFAULT_SEED, checksum, false))
}

View File

@ -57,21 +57,21 @@ mod fatfs {
f.write_str(match self {
Self::DiskErr => "A hard error occurred in the low level disk I/O layer",
Self::IntErr => "Assertion failed",
Self::NotReady => "The physical drive cannot work",
Self::NotReady => "The physical drive does not work",
Self::NoFile => "Could not find the file",
Self::NoPath => "Could not find the path",
Self::InvalidName => "The path name format is invalid",
Self::Denied => "Access denied due to prohibited access or directory full",
Self::Exist => "Access denied due to prohibited access",
Self::Denied => "Access denied due to a prohibited access or directory full",
Self::Exist => "Access denied due to a prohibited access",
Self::InvalidObject => "The file/directory object is invalid",
Self::WriteProtected => "The physical drive is write protected",
Self::InvalidDrive => "The logical drive number is invalid",
Self::NotEnabled => "The volume has no work area",
Self::NoFilesystem => "There is no valid FAT volume",
Self::MkfsAborted => "The f_mkfs() aborted due to any problem",
Self::Timeout => "Could not get a grant to access the volume within defined period",
Self::NoFilesystem => "Could not find a valid FAT volume",
Self::MkfsAborted => "The f_mkfs function aborted due to some problem",
Self::Timeout => "Could not take control of the volume within defined period",
Self::Locked => "The operation is rejected according to the file sharing policy",
Self::NotEnoughCore => "LFN working buffer could not be allocated",
Self::NotEnoughCore => "LFN working buffer could not be allocated or given buffer is insufficient in size",
Self::TooManyOpenFiles => "Number of open files > FF_FS_LOCK",
Self::InvalidParameter => "Given parameter is invalid",
Self::DriverInstalled => "FatFs driver is already installed",
@ -290,14 +290,20 @@ impl FFDriver for SC64 {
}
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
if let Ok(SdCardResult::OK) = self.read_sd_card(buffer, sector) {
if (sector + ((buffer.len() / SD_CARD_SECTOR_SIZE) as fatfs::LBA_t)) > 0x1_0000_0000 {
return fatfs::DRESULT_RES_PARERR;
}
if let Ok(SdCardResult::OK) = self.read_sd_card(buffer, sector as u32) {
return fatfs::DRESULT_RES_OK;
}
fatfs::DRESULT_RES_ERROR
}
fn write(&mut self, buffer: &[u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
if let Ok(SdCardResult::OK) = self.write_sd_card(buffer, sector) {
if (sector + ((buffer.len() / SD_CARD_SECTOR_SIZE) as fatfs::LBA_t)) > 0x1_0000_0000 {
return fatfs::DRESULT_RES_PARERR;
}
if let Ok(SdCardResult::OK) = self.write_sd_card(buffer, sector as u32) {
return fatfs::DRESULT_RES_OK;
}
fatfs::DRESULT_RES_ERROR

View File

@ -73,8 +73,8 @@ impl Wrapper {
let result = if devices > 0 {
let mut list: Vec<DeviceInfo> = vec![];
let mut description = [0i8; 128];
let mut serial = [0i8; 128];
let mut description = [std::ffi::c_char::from(0); 128];
let mut serial = [std::ffi::c_char::from(0); 128];
let mut device = device_list;
let mut index = 0;

View File

@ -445,6 +445,7 @@ impl SC64 {
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
SaveType::FlashramFake => (SAVE_ADDRESS, FLASHRAM_LENGTH),
};
if length != save_length {
@ -469,6 +470,7 @@ impl SC64 {
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
SaveType::FlashramFake => (SAVE_ADDRESS, FLASHRAM_LENGTH),
};
self.memory_read_chunked(writer, address, save_length)
@ -486,7 +488,10 @@ impl SC64 {
self.memory_read_chunked(writer, address, length)
}
pub fn calculate_cic_parameters(&mut self, custom_seed: Option<u8>) -> Result<(), Error> {
pub fn calculate_cic_parameters(
&mut self,
custom_seed: Option<u8>,
) -> Result<(u8, u64, bool), Error> {
let (address, cic_seed, boot_seed) = match get_config!(self, BootMode)? {
BootMode::Menu => (BOOTLOADER_ADDRESS, None, None),
BootMode::Rom => (BOOTLOADER_ADDRESS, None, custom_seed),
@ -495,12 +500,12 @@ impl SC64 {
BootMode::DirectDdIpl => (DDIPL_ADDRESS, custom_seed, None),
};
let ipl3 = self.command_memory_read(address + IPL3_OFFSET, IPL3_LENGTH)?;
let (seed, checksum) = sign_ipl3(&ipl3, cic_seed)?;
let (seed, checksum, matched) = sign_ipl3(&ipl3, cic_seed)?;
self.command_cic_params_set(false, seed, checksum)?;
if let Some(seed) = boot_seed {
self.command_config_set(Config::CicSeed(CicSeed::Seed(seed)))?;
}
Ok(())
Ok((seed, checksum, matched))
}
pub fn set_boot_mode(&mut self, boot_mode: BootMode) -> Result<(), Error> {

View File

@ -288,6 +288,7 @@ pub enum SaveType {
Flashram,
SramBanked,
Sram1m,
FlashramFake,
}
impl Display for SaveType {
@ -300,6 +301,7 @@ impl Display for SaveType {
Self::SramBanked => "SRAM 768k",
Self::Flashram => "FlashRAM 1M",
Self::Sram1m => "SRAM 1M",
Self::FlashramFake => "FakeFlashRAM 1M"
})
}
}
@ -315,6 +317,7 @@ impl TryFrom<u32> for SaveType {
4 => Self::Flashram,
5 => Self::SramBanked,
6 => Self::Sram1m,
7 => Self::FlashramFake,
_ => return Err(Error::new("Unknown save type code")),
})
}
@ -330,6 +333,7 @@ impl From<SaveType> for u32 {
SaveType::Flashram => 4,
SaveType::SramBanked => 5,
SaveType::Sram1m => 6,
SaveType::FlashramFake => 7,
}
}
}

View File

@ -28,6 +28,7 @@
<menu class="mobile-hidden">
<li><a href="/">Home</a></li>
<li><a href="/features.html">Features</a></li>
<li><a href="/faq.html">FAQ</a></li>
<li><a href="https://menu.summercart64.dev">Menu</a></li>
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
<li class="active"><a href="/bom.html">Bill of materials</a></li>
@ -39,7 +40,7 @@
<iframe class="bom" src="/sc64v2_bom.html"></iframe>
<footer>
<span>© 2020 - 2024 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
</footer>
</body>

247
web/faq.html Normal file
View File

@ -0,0 +1,247 @@
<!DOCTYPE html>
<html>
<head>
<title>SummerCart64 - Features</title>
<meta property="og:title" content="SummerCart64 - FAQ" />
<meta property="og:description" content="SummerCart64 - a fully open source N64 flashcart" />
<meta property="og:url" content="https://summercart64.dev/faq.html" />
<meta property="og:image" content="https://summercart64.dev/sc64-embed.png" />
<meta property="og:type" content="website" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="favicon.svg" sizes="any" type="image/svg+xml">
<link rel="stylesheet" href="./styles.css">
<script src="./script.js"></script>
</head>
<body>
<div class="menu-container">
<div class="menu-bar">
<div class="menu-buttons">
<a href="/"><img src="sc64.svg"></a>
<div class="menu-button" onclick="showMenu(event)">
<div class="menu-button-line"></div>
<div class="menu-button-line"></div>
<div class="menu-button-line"></div>
</div>
</div>
<menu class="mobile-hidden">
<li><a href="/">Home</a></li>
<li><a href="/features.html">Features</a></li>
<li class="active"><a href="/faq.html">FAQ</a></li>
<li><a href="https://menu.summercart64.dev">Menu</a></li>
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
<li><a href="/bom.html">Bill of materials</a></li>
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64">GitHub</a></li>
</menu>
</div>
</div>
<div class="main-container">
<main>
<h1>Frequently Asked Questions</h1>
<section>
<details>
<summary>What is the SummerCart64?</summary>
<div>
<p>SummerCart64 is a device that lets you develop and test games on the N64 console. It is a
similar device to the <a href="https://64drive.retroactive.be/">64drive</a>, or <a
href="https://krikzz.com/our-products/cartridges/ed64x7.html">EverDrive-64 X7</a>. Its
main strengths are fast I/O, developer oriented features, full 64DD emulation and being
fully open source.</p>
</div>
</details>
<details>
<summary>Is this flashcart for game developers only, or for anyone?</summary>
<div>
<p>SummerCart64 started its life as a device to aid game development and testing on a real N64
console. Over time scope of the project has expanded and currently it's a great choice for
casual gamers and people interested in tinkering with the N64.</p>
</div>
</details>
<details>
<summary>Where can I get SummerCart64?</summary>
<div>
<p>For latest information check the <strong>Where to buy</strong> section on the <a
href="/">home page</a>.</p>
</div>
</details>
<details>
<summary>Which SD cards are supported? Should I format the card in a specific way?</summary>
<div>
<p>SummerCart64 currently supports cards up to 2 TB in size. Supported filesystems are FAT32 and
exFAT. Card can be formatted with any tool but it's recommended to use builtin tools
available in your system - for Windows OS just right click on the SD card drive and select
<strong>Format</strong> option. For other OS refer to the system's included help.
</p>
</div>
</details>
<details>
<summary>I don't care about developer features and just want to play games. How do I setup the SD
card?</summary>
<div>
<p>All necessary information about installing menu and preparing SD card is available on the <a
href="https://menu.summercart64.dev">N64FlashcartMenu official website</a>.</p>
</div>
</details>
<details>
<summary>SummerCart64 won't boot and it blinks once every second. What's happening?</summary>
<div>
<p>Due to technical reasons N64 region lockout can be bypassed only after detecting if the
current boot failed. To fix this just power off the console, and then power it on again. If
the issue persist then check the next question.</p>
</div>
</details>
<details>
<summary>Why is there a battery on the board? Do I need it?</summary>
<div>
<p>Yes, battery is required for full operation. Battery keeps the time and persistent settings.
This includes last detected console region. If your console region is PAL and SummerCart64
doesn't boot while LED is blinking once every second then try inserting a battery into the
holder.</p>
</div>
</details>
<details>
<summary>Which battery model do I need?</summary>
<div>
<p>Just a standard CR2032 button cell battery.</p>
</div>
</details>
<details>
<summary>Do I need an Expansion Pak?</summary>
<div>
<p>It depends. A common misconception is that the flashcart is able to expand N64 memory. This
is technically not possible. The SummerCart64 on its own do not require an Expansion Pak to
work. However, for some retail games the Expansion Pak is mandatory to even run the game.
Cheat support and most ROM hacks are also dependent on the Expansion Pak.</p>
</div>
</details>
<details>
<summary>I've tried to play a ROM hack / homebrew and it doesn't work. Why?</summary>
<div>
<p>Most of the ROM hacks available for the N64 absolutely <strong>require</strong> an Expansion
Pak to be installed in the console. If the screen stays black after loading the game then
either you don't have an Expansion Pak, or the ROM hack is not console compatible. Always
verify if the game you're trying to run is designed to run on real hardware, and not just in
an emulator.</p>
</div>
</details>
<details>
<summary>Can SummerCart64 replace a Controller Pak / Transfer Pak / Rumble Pak?</summary>
<div>
<p>No. SummerCart64 (and any other thing that is plugged in the cartridge slot) can't replace a
device that plugs in the bottom slot of the controller.</p>
</div>
</details>
<details>
<summary>Do I need to press the reset button to save the game progress?</summary>
<div>
<p>No. SummerCart64 watches the console activity and automatically writes save data to the SD
card <strong>while you play the game</strong>. Automatic writeback happens about 1 second
after game finishes saving the progress.</p>
</div>
</details>
<details>
<summary>Where do I get games to play?</summary>
<div>
<p>You can check entries from the <a href="https://n64brew.dev/wiki/Category:Game_Jams">N64brew
game jams</a>. There are plenty of gems to try out!</p>
</div>
</details>
<details>
<summary>How can I update firmware?</summary>
<div>
<p>Instructions are available in the <a
href="https://github.com/Polprzewodnikowy/SummerCart64/blob/main/docs/00_quick_startup_guide.md#firmware-backupupdate">official
documentation</a>.
</p>
</div>
</details>
<details>
<summary><code>sc64deployer</code> can't find my cart, why?</summary>
<div>
<p>On Windows OS please check if you have latest FTDI drivers installed, either through Windows
Update or directly from <a href="https://ftdichip.com/drivers/vcp-drivers">chip
manufacturer's website</a>. On Linux OS check if you have proper permissions to the USB
device.</p>
</div>
</details>
<details>
<summary>Can I use software other than <code>sc64deployer</code>?</summary>
<div>
<p>Yes. <a href="https://github.com/buu342/N64-UNFLoader">UNFLoader</a> is also supported.</p>
</div>
</details>
<details>
<summary>Real time clock is not keeping the time, what's wrong?</summary>
<div>
<p>Make sure that the battery is installed in the correct orientation. Don't forget to peel all
protective plastic stickers from the battery before installation.</p>
</div>
</details>
<details>
<summary>What is the purpose for button on the back?</summary>
<div>
<p>It has several uses:</p>
<ol>
<li>To eject/swap 64DD disks - when there are multiple virtual 64DD disks "inserted".</li>
<li>To run the built-in test ROM - hold the button while switching the power on.</li>
<li>For game developers as additional input.</li>
</ol>
</div>
</details>
<details>
<summary>Can I access files on the microSD card via USB port on the PC?</summary>
<div>
<p>Yes. <code>sc64deployer</code> has a <code>sd</code> command to access files on the SD card.
</p>
</div>
</details>
<details>
<summary>Does SummerCart64 come with a warranty?</summary>
<div>
<p>As a DIY product and an open-source project, The SummerCart64 (design/software and any other
asset contained as part of this project) have no warranty or liability whatsoever. For any
product that is distributed from an online or retail store, please seek warranty and/or help
(as required by your country law) from them, not from the developers of the SummerCart64
project.</p>
</div>
</details>
<details>
<summary>I bought SummerCart64 from XXX store. It doesn't work or there are weird problems. What
should I do?</summary>
<div>
<p>Always contact the seller you've bought the device to resolve the issues. Developers and
community are not responsible for low quality hardware and lack of quality control.</p>
</div>
</details>
</section>
</main>
</div>
<footer>
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
</footer>
</body>
</html>

View File

@ -28,6 +28,7 @@
<menu class="mobile-hidden">
<li><a href="/">Home</a></li>
<li class="active"><a href="/features.html">Features</a></li>
<li><a href="/faq.html">FAQ</a></li>
<li><a href="https://menu.summercart64.dev">Menu</a></li>
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
<li><a href="/bom.html">Bill of materials</a></li>
@ -181,7 +182,7 @@
</div>
<footer>
<span>© 2020 - 2024 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
</footer>
</body>

View File

@ -28,6 +28,7 @@
<menu class="mobile-hidden">
<li class="active"><a href="/">Home</a></li>
<li><a href="/features.html">Features</a></li>
<li><a href="/faq.html">FAQ</a></li>
<li><a href="https://menu.summercart64.dev">Menu</a></li>
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
<li><a href="/bom.html">Bill of materials</a></li>
@ -38,12 +39,12 @@
<div class="main-container">
<main>
<h1>What is SummerCart64?</h1>
<h1>What is the SummerCart64?</h1>
<section>
<img class="sc64-logo" src="sc64.svg">
<p>SummerCart64 is a custom made cartridge (commonly referred as a flashcart) that allows you to develop
and play games on the N64 console. It is the first N64 flashcart that is feature complete and
completely open source.</p>
and play games on the N64 console. It is the first N64 flashcart that is feature complete and, at
the same time, completely open source.</p>
<p>With fast I/O, integrated support in <a href="https://libdragon.dev">libdragon</a> / <a
href="https://github.com/devwizard64/libcart">libcart</a> / <a
href="https://github.com/buu342/N64-UNFLoader">UNFLoader</a>, and well documented API, testing
@ -71,7 +72,9 @@
check the <code><strong>#sc64-forum</strong></code> channel.</li>
<li><a href="https://www.pcbway.com/project/member/shareproject/?bmbno=1046ED64-8AEE-44">PCBWay
Shared Project</a> page - both assembled boards and bare PCBs are available.</li>
<li>Manual PCB and components order - DIY friendly option.</li>
<li>Manual PCB and components order - DIY friendly option. For more details check out the <a
href="https://github.com/Polprzewodnikowy/SummerCart64/blob/main/docs/06_build_guide.md">build
guide</a>.</li>
</ul>
</section>
@ -94,13 +97,10 @@
<h1>Support the project</h1>
<section>
<p>Like the project? Consider supporting it on the <a
href="https://github.com/sponsors/Polprzewodnikowy">GitHub Sponsors</a>, <a
href="https://ko-fi.com/polprzewodnikowy">Ko-fi</a>, or click the buttons below. Not necessary
but greatly appreciated.</p>
<p>Like the project? Consider supporting the effort on <a
href="https://ko-fi.com/polprzewodnikowy">Ko-fi</a>, or click a button below. Not necessary
but greatly appreciated. Tips are non-refundable, support responsibly.</p>
<div class="sponsor-buttons">
<iframe src="https://github.com/sponsors/Polprzewodnikowy/button" title="Sponsor Polprzewodnikowy"
height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>
<a href='https://ko-fi.com/R5R13I07C' target='_blank'><img height='32'
src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' alt='Buy Me a Coffee at ko-fi.com' /></a>
</div>
@ -109,7 +109,7 @@
</div>
<footer>
<span>© 2020 - 2024 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
</footer>
</body>

View File

@ -23,6 +23,8 @@
--sponsor-buttons-height: 32px;
--details-bg-color: rgb(56, 56, 128);
--footer-padding: 16px 32px 16px 32px;
--footer-font-size: 14px;
}
@ -57,6 +59,7 @@ a:visited {
a:active,
a:hover {
color: var(--text-color);
text-decoration: underline;
}
.menu-container {
@ -259,6 +262,22 @@ main .separator {
margin: var(--main-separator-margin) 0;
}
details {
margin: var(--main-paragraph-margin) 0;
}
details summary {
cursor: pointer;
font-weight: bold;
}
details > div {
overflow: auto;
margin-top: var(--main-paragraph-margin);
padding: 0 var(--main-paragraph-margin);
background-color: var(--details-bg-color);
}
iframe.bom {
display: flex;
flex-grow: 1;