mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2025-04-18 09:51:20 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
88d654129a | ||
![]() |
b88c9a314b | ||
![]() |
e4c3f34fb0 | ||
![]() |
b520f9ace8 | ||
![]() |
d8c4f979cc | ||
![]() |
d63f5893da | ||
![]() |
d307e1a5b1 | ||
![]() |
65f8fa3cf7 | ||
![]() |
6c566bd530 | ||
![]() |
99060bec15 | ||
![]() |
63feaa0c2e | ||
![]() |
a59ad1d39b | ||
![]() |
30fb3d0ea6 | ||
![]() |
a3d4082384 | ||
![]() |
0739ca624c | ||
![]() |
bb1ce45dfe | ||
![]() |
9193e9c6f2 | ||
![]() |
3fbb6f3823 | ||
![]() |
f546e5d17d | ||
![]() |
8393963650 | ||
![]() |
20a9ec0087 | ||
![]() |
b3d9e98e68 | ||
![]() |
6698550dbd | ||
![]() |
18041e2547 | ||
![]() |
0538a28f9e | ||
![]() |
1ade3ade8e | ||
![]() |
80b4aa95cd | ||
![]() |
6eef811cd6 | ||
![]() |
e2c100ae7f | ||
![]() |
a6e86587ae | ||
![]() |
93ab101be4 | ||
![]() |
cc41652e6f | ||
![]() |
ed63eb3e8c | ||
![]() |
db4f16754f | ||
![]() |
74e20cb8cc |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,2 +1 @@
|
|||||||
github: polprzewodnikowy
|
|
||||||
ko_fi: polprzewodnikowy
|
ko_fi: polprzewodnikowy
|
||||||
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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
58
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal 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
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +1,5 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: GitHub Discussions
|
|
||||||
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
|
|
||||||
about: Please use for QUESTIONS, conversations or discussions.
|
|
||||||
- name: N64brew Discord
|
- name: N64brew Discord
|
||||||
url: https://discord.gg/8VNMKhxqQn
|
url: https://discord.gg/8VNMKhxqQn
|
||||||
about: Alternative channel for asking QUESTIONS.
|
about: The go-to community to ask about SummerCart64
|
||||||
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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.
|
|
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
@ -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.
|
|
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -19,6 +19,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Download SummerCart64 repository
|
- name: Download SummerCart64 repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set package version
|
- name: Set package version
|
||||||
uses: frabert/replace-string-action@v2
|
uses: frabert/replace-string-action@v2
|
||||||
@ -28,9 +30,15 @@ jobs:
|
|||||||
string: '${{ github.ref_name }}'
|
string: '${{ github.ref_name }}'
|
||||||
replace-with: '-'
|
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
|
- name: Build firmware
|
||||||
run: ./docker_build.sh release --force-clean
|
run: ./docker_build.sh release --force-clean
|
||||||
env:
|
env:
|
||||||
|
MAC_ADDRESS: ${{ secrets.LATTICE_DIAMOND_MAC_ADDRESS }}
|
||||||
SC64_VERSION: ${{ steps.version.outputs.replaced }}
|
SC64_VERSION: ${{ steps.version.outputs.replaced }}
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
|
65
README.md
65
README.md
@ -4,24 +4,40 @@
|
|||||||
**For non-technical description of the SummerCart64, please head to the https://summercart64.dev website!**
|
**For non-technical description of the SummerCart64, please head to the https://summercart64.dev website!**
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- 64 MiB SDRAM memory for game and save data
|
|
||||||
- 16 MiB FLASH memory for bootloader and extended game data
|
- **ROM and Save Memory On-board**
|
||||||
- 8 kiB on-chip buffer for general use
|
- 64 MiB SDRAM memory for game and save data (enough memory to support every retail game without compromise)
|
||||||
- ~23.8 MiB/s peak transfer rate USB interface for data upload/download and debug functionality
|
- 16 MiB FLASH memory for bootloader and extended game data (with extended memory flashcart supports game ROMs up to 78 MiB)
|
||||||
- ~23.8 MiB/s peak transfer rate SD card interface
|
|
||||||
- EEPROM, SRAM and FlashRAM save types with automatic writeback to SD card
|
- **Game Saves**
|
||||||
- Battery backed real time clock (RTC)
|
- EEPROM 4k/16k, SRAM and FlashRAM save types with an automatic writeback to the SD card (no reset button press required)
|
||||||
- Status LED and button for general use
|
|
||||||
- 64DD add-on emulation
|
- **Hardware-Dependent Game Features**
|
||||||
- IS-Viewer 64 debug interface
|
- 64DD add-on emulation
|
||||||
- N64 bootloader with support for IPL3 registers spoofing and loading menu from SD card
|
- Battery backed real time clock (RTC)
|
||||||
- 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
|
- **Menu**
|
||||||
- PC app for communicating with flashcart (game/save data upload/download, feature enable control and debug terminal)
|
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
|
||||||
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
|
|
||||||
- Initial programming via UART header or dedicated JTAG/SWD interfaces
|
- **Game Development**
|
||||||
- Software and firmware updatable via USB interface
|
- ~23.8 MiB/s peak transfer rate SD card interface
|
||||||
- 3D printable plastic shell
|
- ~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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -37,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?
|
## How do I get one?
|
||||||
|
|
||||||
Most up to date information about purchasing/manufacturing options is available on https://summercart64.dev website!
|
Most up to date information about purchasing/manufacturing options is available on https://summercart64.dev website!
|
||||||
@ -63,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.
|
**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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
30
build.sh
30
build.sh
@ -17,6 +17,7 @@ TOP_FILES=(
|
|||||||
FILES=(
|
FILES=(
|
||||||
"./assets/*"
|
"./assets/*"
|
||||||
"./docs/*"
|
"./docs/*"
|
||||||
|
"./hw/pcb/LICENSE"
|
||||||
"./hw/pcb/sc64v2_bom.html"
|
"./hw/pcb/sc64v2_bom.html"
|
||||||
"./hw/pcb/sc64v2.kicad_pcb"
|
"./hw/pcb/sc64v2.kicad_pcb"
|
||||||
"./hw/pcb/sc64v2.kicad_pro"
|
"./hw/pcb/sc64v2.kicad_pro"
|
||||||
@ -27,6 +28,8 @@ FILES=(
|
|||||||
"./README.md"
|
"./README.md"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
HAVE_COMMIT_INFO=false
|
||||||
|
|
||||||
BUILT_BOOTLOADER=false
|
BUILT_BOOTLOADER=false
|
||||||
BUILT_CONTROLLER=false
|
BUILT_CONTROLLER=false
|
||||||
BUILT_CIC=false
|
BUILT_CIC=false
|
||||||
@ -36,9 +39,24 @@ BUILT_RELEASE=false
|
|||||||
|
|
||||||
FORCE_CLEAN=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 () {
|
build_bootloader () {
|
||||||
if [ "$BUILT_BOOTLOADER" = true ]; then return; fi
|
if [ "$BUILT_BOOTLOADER" = true ]; then return; fi
|
||||||
|
|
||||||
|
get_last_commit_info
|
||||||
|
|
||||||
pushd sw/bootloader > /dev/null
|
pushd sw/bootloader > /dev/null
|
||||||
if [ "$FORCE_CLEAN" = true ]; then
|
if [ "$FORCE_CLEAN" = true ]; then
|
||||||
make clean
|
make clean
|
||||||
@ -98,6 +116,8 @@ build_fpga () {
|
|||||||
build_update () {
|
build_update () {
|
||||||
if [ "$BUILT_UPDATE" = true ]; then return; fi
|
if [ "$BUILT_UPDATE" = true ]; then return; fi
|
||||||
|
|
||||||
|
get_last_commit_info
|
||||||
|
|
||||||
build_bootloader
|
build_bootloader
|
||||||
build_controller
|
build_controller
|
||||||
build_cic
|
build_cic
|
||||||
@ -163,6 +183,14 @@ if test $# -eq 0; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
print_time () {
|
||||||
|
echo "Build took $SECONDS seconds"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap "echo \"Build failed\"; print_time" ERR
|
||||||
|
|
||||||
|
SECONDS=0
|
||||||
|
|
||||||
TRIGGER_BOOTLOADER=false
|
TRIGGER_BOOTLOADER=false
|
||||||
TRIGGER_CONTROLLER=false
|
TRIGGER_CONTROLLER=false
|
||||||
TRIGGER_CIC=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_FPGA" = true ]; then build_fpga; fi
|
||||||
if [ "$TRIGGER_UPDATE" = true ]; then build_update; fi
|
if [ "$TRIGGER_UPDATE" = true ]; then build_update; fi
|
||||||
if [ "$TRIGGER_RELEASE" = true ]; then build_release; fi
|
if [ "$TRIGGER_RELEASE" = true ]; then build_release; fi
|
||||||
|
|
||||||
|
print_time
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
BUILDER_IMAGE="ghcr.io/polprzewodnikowy/sc64env:v1.10"
|
BUILDER_IMAGE="ghcr.io/polprzewodnikowy/sc64env:v1.10"
|
||||||
|
BUILDER_PLATFORM="linux/x86_64"
|
||||||
|
|
||||||
pushd $(dirname $0) > /dev/null
|
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
|
if [ -t 1 ]; then
|
||||||
DOCKER_OPTIONS="-it"
|
DOCKER_OPTIONS="-it"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SECONDS=0
|
|
||||||
|
|
||||||
docker run \
|
docker run \
|
||||||
$DOCKER_OPTIONS \
|
$DOCKER_OPTIONS \
|
||||||
--rm \
|
--rm \
|
||||||
@ -23,20 +17,13 @@ docker run \
|
|||||||
-v "$(pwd)"/fw/project/lcmxo2/license.dat:/flexlm/license.dat \
|
-v "$(pwd)"/fw/project/lcmxo2/license.dat:/flexlm/license.dat \
|
||||||
-v "$(pwd)":/workdir \
|
-v "$(pwd)":/workdir \
|
||||||
-h=`hostname` \
|
-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:-""} \
|
-e SC64_VERSION=${SC64_VERSION:-""} \
|
||||||
|
--platform $BUILDER_PLATFORM \
|
||||||
$BUILDER_IMAGE \
|
$BUILDER_IMAGE \
|
||||||
./build.sh $@
|
./build.sh $@
|
||||||
|
|
||||||
BUILD_ERROR=$?
|
BUILD_RESULT=$?
|
||||||
|
|
||||||
echo "Build took $SECONDS seconds"
|
|
||||||
|
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
|
|
||||||
if [ $BUILD_ERROR -ne 0 ]; then
|
exit $BUILD_RESULT
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
@ -1,50 +1,38 @@
|
|||||||
- [First time setup on PC](#first-time-setup-on-pc)
|
- [First time setup](#first-time-setup)
|
||||||
- [Firmware backup/update](#firmware-backupupdate)
|
- [Standalone mode (Running menu and games on the N64)](#standalone-mode-running-menu-and-games-on-the-n64)
|
||||||
- [Running menu in standalone mode](#running-menu-in-standalone-mode)
|
- [Developer mode (Uploading ROMs from the PC, and more)](#developer-mode-uploading-roms-from-the-pc-and-more)
|
||||||
- [Uploading game and/or save from PC](#uploading-game-andor-save-from-pc)
|
- [Uploading game and/or save from PC](#uploading-game-andor-save-from-pc)
|
||||||
- [Downloading save to PC](#downloading-save-to-pc)
|
- [Downloading save to PC](#downloading-save-to-pc)
|
||||||
- [Running 64DD games from PC](#running-64dd-games-from-pc)
|
- [Running 64DD games from PC](#running-64dd-games-from-pc)
|
||||||
- [Direct boot option](#direct-boot-option)
|
- [Direct boot option](#direct-boot-option)
|
||||||
- [Debug terminal on PC](#debug-terminal-on-pc)
|
- [Debug terminal on PC](#debug-terminal-on-pc)
|
||||||
|
- [Firmware backup/update](#firmware-backupupdate)
|
||||||
- [LED blink patters](#led-blink-patters)
|
- [LED blink patters](#led-blink-patters)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## First time setup on PC
|
# First time setup
|
||||||
|
|
||||||
**Windows platform: replace `./sc64deployer` in examples below with `sc64deployer.exe`**
|
## Standalone mode (Running menu and games on the N64)
|
||||||
|
|
||||||
1. Download the latest deployer tool (`sc64-deployer-{os}-{version}.{ext}`) and firmware (`sc64-firmware-{version}.bin`) from GitHub releases page
|
|
||||||
2. Extract deployer tool package contents to a folder and place firmware file inside it
|
|
||||||
3. Connect SC64 device to your computer with USB type C cable
|
|
||||||
4. Run `./sc64deployer list` to check if device is detected in the system
|
|
||||||
5. Update SC64 firmware to the latest version with `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
|
||||||
6. Run `./sc64deployer info` to check if update process finished successfully and SC64 is detected correctly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Firmware backup/update
|
|
||||||
|
|
||||||
Keeping SC64 firmware up to date is highly recommended.
|
|
||||||
`sc64deployer` application is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
|
||||||
|
|
||||||
To download and backup current version of the SC64 firmware run `./sc64deployer firmware backup sc64-firmware-backup.bin`
|
|
||||||
|
|
||||||
To update SC64 firmware run `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
|
||||||
|
|
||||||
To print firmware metadata run `./sc64deployer firmware info sc64-firmware-{version}.bin`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Running menu in standalone mode
|
|
||||||
|
|
||||||
Menu, as known from 64drive or EverDrive-64, is developed in another repository: [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu).
|
Menu, as known from 64drive or EverDrive-64, is developed in another repository: [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu).
|
||||||
Download latest version from [here](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases) and put `sc64menu.n64` file in the root directory of the SD card.
|
Download latest version from [here](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases) and put `sc64menu.n64` file in the root directory of the SD card.
|
||||||
|
Additionally, follow the instructions in the N64FlashcartMenu repository for more information about thr SD card setup and extra functionality.
|
||||||
When N64 is powered on menu is automatically loaded from the SD card. Supported file system formats are FAT32 and exFAT.
|
When N64 is powered on menu is automatically loaded from the SD card. Supported file system formats are FAT32 and exFAT.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Uploading game and/or save from PC
|
## Developer mode (Uploading ROMs from the PC, and more)
|
||||||
|
|
||||||
|
**Windows platform: replace `./sc64deployer` in examples below with `sc64deployer.exe`**
|
||||||
|
|
||||||
|
1. Download the latest deployer tool (`sc64-deployer-{os}-{version}.{ext}`) from the GitHub releases page
|
||||||
|
2. Extract deployer tool package contents to a folder
|
||||||
|
3. Connect SC64 device to your computer with USB type C cable
|
||||||
|
4. Run `./sc64deployer list` to check if device is detected in the system
|
||||||
|
5. Follow instructions below for specific use cases
|
||||||
|
|
||||||
|
### Uploading game and/or save from PC
|
||||||
|
|
||||||
`./sc64deployer upload path_to_rom.n64 --save-type eeprom4k --save path_to_save.sav`
|
`./sc64deployer upload path_to_rom.n64 --save-type eeprom4k --save path_to_save.sav`
|
||||||
|
|
||||||
@ -53,18 +41,14 @@ Application will try to autodetect used save type so explicitly setting save typ
|
|||||||
Check included help in the application to list available save types.
|
Check included help in the application to list available save types.
|
||||||
Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save or you want to start with fresh save file.
|
Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save or you want to start with fresh save file.
|
||||||
|
|
||||||
---
|
### Downloading save to PC
|
||||||
|
|
||||||
## Downloading save to PC
|
|
||||||
|
|
||||||
`./sc64deployer download save path_to_save.sav`
|
`./sc64deployer download save path_to_save.sav`
|
||||||
|
|
||||||
Replace `path_to_save.sav` with appropriate value.
|
Replace `path_to_save.sav` with appropriate value.
|
||||||
Command will raise error when no save type is currently enabled in the SC64 device.
|
Command will raise error when no save type is currently enabled in the SC64 device.
|
||||||
|
|
||||||
---
|
### Running 64DD games from PC
|
||||||
|
|
||||||
## Running 64DD games from PC
|
|
||||||
|
|
||||||
64DD games require DDIPL ROM and disk images.
|
64DD games require DDIPL ROM and disk images.
|
||||||
To run disk game type `./sc64deployer 64dd path_to_ddipl.n64 path_to_disk_1.ndd path_to_disk_2.ndd`.
|
To run disk game type `./sc64deployer 64dd path_to_ddipl.n64 path_to_disk_1.ndd path_to_disk_2.ndd`.
|
||||||
@ -79,31 +63,38 @@ Make sure retail and development disks formats aren't mixed together.
|
|||||||
If disk game supports running in conjunction with cartridge game then `--rom path_to_rom.n64` argument can be added to command above.
|
If disk game supports running in conjunction with cartridge game then `--rom path_to_rom.n64` argument can be added to command above.
|
||||||
N64 will boot cartridge game instead of 64DD IPL.
|
N64 will boot cartridge game instead of 64DD IPL.
|
||||||
|
|
||||||
---
|
### Direct boot option
|
||||||
|
|
||||||
## Direct boot option
|
|
||||||
|
|
||||||
If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step.
|
If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step.
|
||||||
Pass `--direct` option in `upload` or `64dd` command to disable bootloader during boot and console reset.
|
Pass `--direct` option in `upload` or `64dd` command to disable bootloader during boot and console reset.
|
||||||
This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark).
|
This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark).
|
||||||
TV type cannot be forced when direct boot mode is enabled.
|
TV type cannot be forced when direct boot mode is enabled.
|
||||||
|
|
||||||
---
|
### Debug terminal on PC
|
||||||
|
|
||||||
## Debug terminal on PC
|
|
||||||
|
|
||||||
`sc64deployer` application supports UNFLoader protocol and has same functionality implemented as aforementioned program.
|
`sc64deployer` application supports UNFLoader protocol and has same functionality implemented as aforementioned program.
|
||||||
Type `./sc64deployer debug` to activate it.
|
Type `./sc64deployer debug` to activate it.
|
||||||
|
|
||||||
|
### Firmware backup/update
|
||||||
|
|
||||||
|
Keeping SC64 firmware up to date is strongly recommended.
|
||||||
|
`sc64deployer` application is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||||
|
|
||||||
|
To download and backup current version of the SC64 firmware run `./sc64deployer firmware backup sc64-firmware-backup.bin`
|
||||||
|
|
||||||
|
To update SC64 firmware run `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
||||||
|
|
||||||
|
To print firmware metadata run `./sc64deployer firmware info sc64-firmware-{version}.bin`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## LED blink patters
|
# LED blink patters
|
||||||
|
|
||||||
LED on SC64 board can blink in certain situations. Most of them during normal use are related to SD card access. Here's list of blink patterns:
|
LED on SC64 board can blink in certain situations. Most of them during normal use are related to SD card access. Here's list of blink patterns:
|
||||||
|
|
||||||
| Pattern | Meaning |
|
| Pattern | Meaning |
|
||||||
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||||
| Nx [Short ON - Short OFF] | SD card access is in progress (initialization or data read/write) or save writeback is in progress |
|
| Irregular | SD card access is in progress (initialization or data read/write) or save writeback was finished |
|
||||||
| Nx [Medium ON - Long OFF] | CIC region did not match, please power off console and power on again |
|
| Nx [Medium ON - Long OFF] | CIC region did not match, please power off console and power on again |
|
||||||
| 2x [Very short ON - Short OFF] | Pattern used during firmware update process, it means that specific part of firmware has started programming |
|
| 2x [Very short ON - Short OFF] | Pattern used during firmware update process, it means that specific part of firmware has started programming |
|
||||||
| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated |
|
| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated |
|
||||||
@ -112,4 +103,4 @@ LED on SC64 board can blink in certain situations. Most of them during normal us
|
|||||||
Nx means that blink count is varied.
|
Nx means that blink count is varied.
|
||||||
|
|
||||||
LED blinking on SD card access can be disabled through `sc64deployer` application.
|
LED blinking on SD card access can be disabled through `sc64deployer` application.
|
||||||
Please refer to included help for option to change the LED behavior.
|
Please refer to the included help for option to change the LED behavior.
|
||||||
|
@ -25,9 +25,9 @@ This mapping is used internally by FPGA/μC and when accessing flashcart from US
|
|||||||
| Flash [1] | `0x0400_0000` | 16 MiB | RW/R | Flash |
|
| Flash [1] | `0x0400_0000` | 16 MiB | RW/R | Flash |
|
||||||
| Data buffer | `0x0500_0000` | 8 kiB | RW | BlockRAM |
|
| Data buffer | `0x0500_0000` | 8 kiB | RW | BlockRAM |
|
||||||
| EEPROM | `0x0500_2000` | 2 kiB | RW | BlockRAM |
|
| EEPROM | `0x0500_2000` | 2 kiB | RW | BlockRAM |
|
||||||
| 64DD buffer | `0x0500_2800` | 256 bytes | RW | BlockRAM |
|
| 64DD/MCU buffer | `0x0500_2800` | 1 kiB | RW | BlockRAM |
|
||||||
| FlashRAM buffer [2] | `0x0500_2900` | 128 bytes | R | BlockRAM |
|
| FlashRAM buffer [2] | `0x0500_2C00` | 128 bytes | R | BlockRAM |
|
||||||
| N/A [3] | `0x0500_2980` | to `0x07FF_FFFF` | R | N/A |
|
| N/A [3] | `0x0500_2C80` | to `0x07FF_FFFF` | R | N/A |
|
||||||
|
|
||||||
- Note [1]: Flash memory region `0x04E0_0000` - `0x04FD_FFFF` is write protected as it contains N64 bootloader. This section can be overwritten only via firmware update process.
|
- Note [1]: Flash memory region `0x04E0_0000` - `0x04FD_FFFF` is write protected as it contains N64 bootloader. This section can be overwritten only via firmware update process.
|
||||||
- Note [2]: Due to BlockRAM usage optimization this section is read only.
|
- Note [2]: Due to BlockRAM usage optimization this section is read only.
|
||||||
@ -53,8 +53,8 @@ This mapping is used when accessing flashcart from N64 side.
|
|||||||
| ROM shadow [7] | `0x1FFC_0000` | 128 kiB | R | `0x04FE_0000` | Flash | mem bus | SC64 register access is enabled |
|
| ROM shadow [7] | `0x1FFC_0000` | 128 kiB | R | `0x04FE_0000` | Flash | mem bus | SC64 register access is enabled |
|
||||||
| Data buffer | `0x1FFE_0000` | 8 kiB | RW | `0x0500_0000` | Block RAM | mem bus | SC64 register access is enabled |
|
| Data buffer | `0x1FFE_0000` | 8 kiB | RW | `0x0500_0000` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| EEPROM | `0x1FFE_2000` | 2 kiB | RW | `0x0500_2000` | Block RAM | mem bus | SC64 register access is enabled |
|
| EEPROM | `0x1FFE_2000` | 2 kiB | RW | `0x0500_2000` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| 64DD buffer [8] | `0x1FFE_2800` | 256 bytes | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
|
| 64DD/MCU buffer [8] | `0x1FFE_2800` | 1 kiB | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| FlashRAM buffer [8] | `0x1FFE_2900` | 128 bytes | R | `0x0500_2900` | Block RAM | mem bus | SC64 register access is enabled |
|
| FlashRAM buffer [8] | `0x1FFE_2C00` | 128 bytes | R | `0x0500_2C00` | Block RAM | mem bus | SC64 register access is enabled |
|
||||||
| SC64 registers | `0x1FFF_0000` | 28 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
|
| SC64 registers | `0x1FFF_0000` | 28 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
|
||||||
|
|
||||||
- Note [1]: 64DD IPL share SDRAM memory space with ROM (last 4 MiB minus 128 kiB for saves). Write access is always disabled for this section.
|
- Note [1]: 64DD IPL share SDRAM memory space with ROM (last 4 MiB minus 128 kiB for saves). Write access is always disabled for this section.
|
||||||
|
@ -4,28 +4,28 @@
|
|||||||
|
|
||||||
## N64 commands
|
## N64 commands
|
||||||
|
|
||||||
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
||||||
| --- | --------------------- | ------------- | ------------ | ---------------- | -------------- | ---------------------------------------------------------- |
|
| --- | --------------------- | ------------- | ------------ | ---------------- | -------------- | ------------------------------------------------------------ |
|
||||||
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
||||||
| `V` | **VERSION_GET** | --- | --- | major/minor | revision | Get flashcart firmware version |
|
| `V` | **VERSION_GET** | --- | --- | major/minor | revision | Get flashcart firmware version |
|
||||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value |
|
| `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value |
|
||||||
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||||
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||||
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
||||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||||
| `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart |
|
| `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart |
|
||||||
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
||||||
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
||||||
| `U` | **USB_WRITE_STATUS** | --- | --- | write_status | --- | Get USB write status |
|
| `U` | **USB_WRITE_STATUS** | --- | --- | write_status | --- | Get USB write status |
|
||||||
| `i` | **SD_CARD_OP** | pi_address | operation | --- | return_data | Perform special operation on SD card |
|
| `i` | **SD_CARD_OP** | pi_address | operation | --- | return_data | Perform special operation on the SD card |
|
||||||
| `I` | **SD_SECTOR_SET** | sector | --- | --- | --- | Set starting sector for next SD card R/W operation |
|
| `I` | **SD_SECTOR_SET** | sector | --- | --- | --- | Set starting sector for next SD card R/W operation |
|
||||||
| `s` | **SD_READ** | pi_address | sector_count | --- | --- | Read sectors from SD card to flashcart |
|
| `s` | **SD_READ** | pi_address | sector_count | --- | --- | Read sectors from the SD card to flashcart memory space |
|
||||||
| `S` | **SD_WRITE** | pi_address | sector_count | --- | --- | Write sectors from flashcart to SD card |
|
| `S` | **SD_WRITE** | pi_address | sector_count | --- | --- | Write sectors from the flashcart memory space to the SD card |
|
||||||
| `D` | **DISK_MAPPING_SET** | pi_address | table_size | --- | --- | Set 64DD disk mapping for SD mode |
|
| `D` | **DISK_MAPPING_SET** | pi_address | table_size | --- | --- | Set 64DD disk mapping for SD mode |
|
||||||
| `w` | **WRITEBACK_PENDING** | --- | --- | pending_status | --- | Get save writeback status (is write queued to the SD card) |
|
| `w` | **WRITEBACK_PENDING** | --- | --- | pending_status | --- | Get save writeback status (is write queued to the SD card) |
|
||||||
| `W` | **WRITEBACK_SD_INFO** | pi_address | --- | --- | --- | Load writeback SD sector table and enable it |
|
| `W` | **WRITEBACK_SD_INFO** | pi_address | --- | --- | --- | Load writeback SD sector table and enable it |
|
||||||
| `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer |
|
| `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer |
|
||||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size |
|
| `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size |
|
||||||
| `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
| `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
||||||
| `%` | **DIAGNOSTIC_GET** | diagnostic_id | --- | --- | value | Get diagnostic data |
|
| `%` | **DIAGNOSTIC_GET** | diagnostic_id | --- | --- | value | Get diagnostic data |
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
- [PC -\> SC64 packets](#pc---sc64-packets)
|
- [PC -\> SC64 packets](#pc---sc64-packets)
|
||||||
- [**`CMD`** packet](#cmd-packet)
|
- [**`CMD`** packet](#cmd-packet)
|
||||||
- [SC64 -\> PC packets](#sc64---pc-packets)
|
- [SC64 -\> PC packets](#sc64---pc-packets)
|
||||||
- [**`RSP`/`ERR`** packets](#rsperr-packets)
|
- [**`CMP`/`ERR`** packets](#cmperr-packets)
|
||||||
- [**`PKT`** packet](#pkt-packet)
|
- [**`PKT`** packet](#pkt-packet)
|
||||||
- [Supported commands](#supported-commands)
|
- [Supported commands](#supported-commands)
|
||||||
- [`v`: **IDENTIFIER\_GET**](#v-identifier_get)
|
- [`v`: **IDENTIFIER\_GET**](#v-identifier_get)
|
||||||
@ -45,6 +45,22 @@
|
|||||||
- [`data` (data)](#data-data-1)
|
- [`data` (data)](#data-data-1)
|
||||||
- [`X`: **AUX\_WRITE**](#x-aux_write)
|
- [`X`: **AUX\_WRITE**](#x-aux_write)
|
||||||
- [`arg0` (data)](#arg0-data)
|
- [`arg0` (data)](#arg0-data)
|
||||||
|
- [`i`: **SD\_CARD\_OP**](#i-sd_card_op)
|
||||||
|
- [`arg0` (address)](#arg0-address-2)
|
||||||
|
- [`arg1` (operation)](#arg1-operation)
|
||||||
|
- [`response` (result/status)](#response-resultstatus)
|
||||||
|
- [Available SD card operations](#available-sd-card-operations)
|
||||||
|
- [SD card status](#sd-card-status)
|
||||||
|
- [`s`: **SD\_READ**](#s-sd_read)
|
||||||
|
- [`arg0` (address)](#arg0-address-3)
|
||||||
|
- [`arg1` (sector\_count)](#arg1-sector_count)
|
||||||
|
- [`data` (sector)](#data-sector)
|
||||||
|
- [`response` (result)](#response-result)
|
||||||
|
- [`S`: **SD\_WRITE**](#s-sd_write)
|
||||||
|
- [`arg0` (address)](#arg0-address-4)
|
||||||
|
- [`arg1` (sector\_count)](#arg1-sector_count-1)
|
||||||
|
- [`data` (sector)](#data-sector-1)
|
||||||
|
- [`response` (result)](#response-result-1)
|
||||||
- [`D`: **DD\_SET\_BLOCK\_READY**](#d-dd_set_block_ready)
|
- [`D`: **DD\_SET\_BLOCK\_READY**](#d-dd_set_block_ready)
|
||||||
- [`arg0` (error)](#arg0-error)
|
- [`arg0` (error)](#arg0-error)
|
||||||
- [`W`: **WRITEBACK\_ENABLE**](#w-writeback_enable)
|
- [`W`: **WRITEBACK\_ENABLE**](#w-writeback_enable)
|
||||||
@ -112,29 +128,29 @@ Packet data length is derived from the argument if specific command supports it.
|
|||||||
|
|
||||||
| identifier | description |
|
| identifier | description |
|
||||||
| ---------- | ---------------------------------------- |
|
| ---------- | ---------------------------------------- |
|
||||||
| `RSP` | Success response to the received command |
|
| `CMP` | Success response to the received command |
|
||||||
| `ERR` | Error response to the received command |
|
| `ERR` | Error response to the received command |
|
||||||
| `PKT` | Asynchronous data packet |
|
| `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.
|
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.
|
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.
|
Fourth byte denotes packet ID listed in the [asynchronous packets](#asynchronous-packets) section.
|
||||||
|
|
||||||
#### **`RSP`/`ERR`** packets
|
#### **`CMP`/`ERR`** packets
|
||||||
|
|
||||||
General structure of packet:
|
General structure of packet:
|
||||||
| offset | type | description |
|
| offset | type | description |
|
||||||
| ------ | -------------------- | ---------------------- |
|
| ------ | -------------------- | ---------------------- |
|
||||||
| `0` | char[3] | `RSP`/`ERR` identifier |
|
| `0` | char[3] | `CMP`/`ERR` identifier |
|
||||||
| `3` | char[1] | Command ID |
|
| `3` | char[1] | Command ID |
|
||||||
| `4` | uint32_t | Response data length |
|
| `4` | uint32_t | Response data length |
|
||||||
| `8` | uint8_t[data_length] | Response data (if any) |
|
| `8` | uint8_t[data_length] | Response data (if any) |
|
||||||
|
|
||||||
`RSP`/`ERR` packet is sent as a response to the command sent by the PC.
|
`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 `RSP` packet.
|
`ERR` response might contain no (or undefined) data in the arbitrary data field compared to regular `CMP` packet.
|
||||||
|
|
||||||
#### **`PKT`** packet
|
#### **`PKT`** packet
|
||||||
|
|
||||||
@ -152,30 +168,33 @@ Available packet IDs are listed in the [asynchronous packets](#asynchronous-pack
|
|||||||
|
|
||||||
## Supported commands
|
## Supported commands
|
||||||
|
|
||||||
| id | name | arg0 | arg1 | data | response | description |
|
| id | name | arg0 | arg1 | data | response | description |
|
||||||
| --- | ----------------------------------------------- | ------------ | ------------- | ---- | ---------------- | -------------------------------------------------------------- |
|
| --- | ----------------------------------------------- | ------------ | ------------- | ------ | ---------------- | -------------------------------------------------------------- |
|
||||||
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
||||||
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
|
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
|
||||||
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
||||||
| `B` | [**CIC_PARAMS_SET**](#b-cic_params_set) | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
|
| `B` | [**CIC_PARAMS_SET**](#b-cic_params_set) | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
|
||||||
| `c` | [**CONFIG_GET**](#c-config_get) | config_id | --- | --- | config_value | Get config option |
|
| `c` | [**CONFIG_GET**](#c-config_get) | config_id | --- | --- | config_value | Get config option |
|
||||||
| `C` | [**CONFIG_SET**](#c-config_set) | config_id | config_value | --- | --- | Set config option |
|
| `C` | [**CONFIG_SET**](#c-config_set) | config_id | config_value | --- | --- | Set config option |
|
||||||
| `a` | [**SETTING_GET**](#a-setting_get) | setting_id | --- | --- | setting_value | Get persistent setting option |
|
| `a` | [**SETTING_GET**](#a-setting_get) | setting_id | --- | --- | setting_value | Get persistent setting option |
|
||||||
| `A` | [**SETTING_SET**](#a-setting_set) | setting_id | setting_value | --- | --- | Set persistent setting option |
|
| `A` | [**SETTING_SET**](#a-setting_set) | setting_id | setting_value | --- | --- | Set persistent setting option |
|
||||||
| `t` | [**TIME_GET**](#t-time_get) | --- | --- | --- | time | Get current RTC value |
|
| `t` | [**TIME_GET**](#t-time_get) | --- | --- | --- | time | Get current RTC value |
|
||||||
| `T` | [**TIME_SET**](#t-time_set) | time_0 | time_1 | --- | --- | Set new RTC value |
|
| `T` | [**TIME_SET**](#t-time_set) | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||||
| `m` | [**MEMORY_READ**](#m-memory_read) | address | length | --- | data | Read data from specified memory address |
|
| `m` | [**MEMORY_READ**](#m-memory_read) | address | length | --- | data | Read data from specified memory address |
|
||||||
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
|
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
|
||||||
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||||
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
|
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
|
||||||
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
| `i` | [**SD_CARD_OP**](#i-sd_card_op) | address | operation | --- | result/status | Perform special operation on the SD card |
|
||||||
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
|
| `s` | [**SD_READ**](#s-sd_read) | address | sector_count | sector | result | Read sectors from the SD card to flashcart memory space |
|
||||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
| `S` | [**SD_WRITE**](#s-sd_write) | address | sector_count | sector | result | Write sectors from the flashcart memory space to the SD card |
|
||||||
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||||
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
|
||||||
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
|
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
||||||
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||||
| `%` | **DIAGNOSTIC_GET** | --- | --- | --- | diagnostic_data | Get diagnostic data |
|
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
||||||
|
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
|
||||||
|
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
||||||
|
| `%` | **DIAGNOSTIC_GET** | --- | --- | --- | diagnostic_data | Get diagnostic data |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -223,7 +242,7 @@ _This command does not require arguments or data._
|
|||||||
_This command does not send response data._
|
_This command does not send response data._
|
||||||
|
|
||||||
This command is used to reset most of the config options to default state (same as on power-up).
|
This command is used to reset most of the config options to default state (same as on power-up).
|
||||||
Additionally, CIC emulation is enabled and 6102/7101 seed/checksum values are set.
|
Additionally, CIC emulation is enabled, 6102/7101 seed/checksum values are set and SD card lock is released.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -444,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 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.
|
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.
|
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.
|
||||||
@ -467,6 +486,110 @@ This command puts 32 bits of data to the AUX register accessible from the N64 si
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `i`: **SD_CARD_OP**
|
||||||
|
|
||||||
|
**Perform special operation on the SD card**
|
||||||
|
|
||||||
|
#### `arg0` (address)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Address |
|
||||||
|
|
||||||
|
#### `arg1` (operation)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Operation |
|
||||||
|
|
||||||
|
#### `response` (result/status)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||||
|
| `4` | uint32_t | SD card status (always returned and valid regardless of the SD card operation result) |
|
||||||
|
|
||||||
|
This command performs special operation on the SD card. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||||
|
PC and N64 cannot use the SD card interface at the same time. Lock mechanism is implemented to prevent data corruption.
|
||||||
|
SD card access is locked when PC or N64 requests SD card initialization, and unlocked when the card is uninitialized.
|
||||||
|
Lock is also released when certain conditions occur - when N64 side lock is active then it is released when the console is powered off
|
||||||
|
and save writeback operation is not pending. PC side lock is released when USB interface is in reset state, or the USB cable is unplugged.
|
||||||
|
|
||||||
|
#### Available SD card operations
|
||||||
|
| operation | description |
|
||||||
|
| --------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | Init SD card |
|
||||||
|
| `1` | Deinit SD card |
|
||||||
|
| `2` | Get SD card status |
|
||||||
|
| `3` | Get SD card info (loads CSD and CID registers to a specified address, data length is 32 bytes) |
|
||||||
|
| `4` | Turn on byte swap |
|
||||||
|
| `5` | Turn off byte swap |
|
||||||
|
|
||||||
|
#### SD card status
|
||||||
|
| bits | description |
|
||||||
|
| -------- | -------------------------------------------------------------------------- |
|
||||||
|
| `[31:5]` | _Unused_ |
|
||||||
|
| `[4]` | `0` - Byte swap disabled, `1` - Byte swap enabled (valid when initialized) |
|
||||||
|
| `[3]` | `0` - 25 MHz clock, `1` - 50 MHz clock (valid when initialized) |
|
||||||
|
| `[2]` | `0` - Byte addressed, `1` - Sector addressed (valid when initialized) |
|
||||||
|
| `[1]` | `0` - SD card not initialized, `1` - SD card initialized |
|
||||||
|
| `[0]` | `0` - SD card not inserted, `1` - SD card inserted |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `s`: **SD_READ**
|
||||||
|
|
||||||
|
**Read sectors from the SD card to flashcart memory space**
|
||||||
|
|
||||||
|
#### `arg0` (address)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Address |
|
||||||
|
|
||||||
|
#### `arg1` (sector_count)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ------------ |
|
||||||
|
| `[31:0]` | Sector count |
|
||||||
|
|
||||||
|
#### `data` (sector)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | --------------- |
|
||||||
|
| `0` | uint32_t | Starting sector |
|
||||||
|
|
||||||
|
#### `response` (result)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||||
|
|
||||||
|
This command reads sectors from the SD card to a specified memory address. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `S`: **SD_WRITE**
|
||||||
|
|
||||||
|
**Write sectors from the flashcart memory space to the SD card**
|
||||||
|
|
||||||
|
#### `arg0` (address)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ----------- |
|
||||||
|
| `[31:0]` | Address |
|
||||||
|
|
||||||
|
#### `arg1` (sector_count)
|
||||||
|
| bits | description |
|
||||||
|
| -------- | ------------ |
|
||||||
|
| `[31:0]` | Sector count |
|
||||||
|
|
||||||
|
#### `data` (sector)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | --------------- |
|
||||||
|
| `0` | uint32_t | Starting sector |
|
||||||
|
|
||||||
|
#### `response` (result)
|
||||||
|
| offset | type | description |
|
||||||
|
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||||
|
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||||
|
|
||||||
|
This command writes sectors from a specified memory address to the SD card. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `D`: **DD_SET_BLOCK_READY**
|
### `D`: **DD_SET_BLOCK_READY**
|
||||||
|
|
||||||
**Notify flashcart about 64DD block readiness**
|
**Notify flashcart about 64DD block readiness**
|
||||||
|
@ -133,9 +133,10 @@ type: *enum* | default: `0`
|
|||||||
- `4` - FlashRAM 1 Mib save is enabled
|
- `4` - FlashRAM 1 Mib save is enabled
|
||||||
- `5` - SRAM 768 kib save is enabled
|
- `5` - SRAM 768 kib save is enabled
|
||||||
- `6` - SRAM 1 Mib 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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -10,13 +10,14 @@
|
|||||||
|
|
||||||
Docker method is a preferred option.
|
Docker method is a preferred option.
|
||||||
Run `./docker_build.sh release` to build all firmware/software and generate release package.
|
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 license
|
||||||
|
|
||||||
Lattice Diamond software is used to build FPGA bitstream, a free 1 year license is necessary to run the build process.
|
Lattice Diamond software is used to build the FPGA bitstream.
|
||||||
Repository already contains license attached to fake MAC address `F8:12:34:56:78:90` (path to the license: `fw/project/lcmxo2/license.dat`).
|
A free 1 year license is necessary to run the build process.
|
||||||
If the license expires, it is required to request new license from Lattice webpage.
|
You can request personal license from the [Lattice website](https://www.latticesemi.com/Support/Licensing).
|
||||||
New license can be attached to aforementioned MAC address or any other address.
|
Build script expects license file to be present in this path: `fw/project/lcmxo2/license.dat`.
|
||||||
In case of non default MAC address it is possible to provide it via `MAC_ADDRESS` environment variable.
|
Since build is done inside docker container it is required to pass the MAC address, linked with the license file, to a container.
|
||||||
For example: `MAC_ADDRESS=AB:00:00:00:00:00 ./docker_build.sh release`.
|
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.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
- [Video guide](#video-guide)
|
||||||
- [Step by step guide how to make SC64](#step-by-step-guide-how-to-make-sc64)
|
- [Step by step guide how to make SC64](#step-by-step-guide-how-to-make-sc64)
|
||||||
- [**PCB manufacturing data**](#pcb-manufacturing-data)
|
- [**PCB manufacturing data**](#pcb-manufacturing-data)
|
||||||
- [**PCB requirements**](#pcb-requirements)
|
- [**PCB requirements**](#pcb-requirements)
|
||||||
@ -12,6 +13,10 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Video guide
|
||||||
|
|
||||||
|
[](https://www.youtube.com/watch?v=t6hyCFpwqz8 "How to build and program the SummerCart64")
|
||||||
|
|
||||||
## Step by step guide how to make SC64
|
## Step by step guide how to make SC64
|
||||||
|
|
||||||
All necessary manufacturing files are packaged in every `sc64-extra-{version}.zip` file in GitHub releases.
|
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**
|
### **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
|
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.
|
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.
|
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.
|
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.
|
|
||||||
|
1
fw/project/lcmxo2/.gitignore
vendored
1
fw/project/lcmxo2/.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
.recovery
|
.recovery
|
||||||
*.dir/
|
*.dir/
|
||||||
*.ccl
|
*.ccl
|
||||||
|
*.dat
|
||||||
*.dmp
|
*.dmp
|
||||||
*.html
|
*.html
|
||||||
*.ini
|
*.ini
|
||||||
|
@ -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"
|
|
||||||
|
|
@ -36,11 +36,11 @@ module memory_bram (
|
|||||||
eeprom_selected = 1'b0;
|
eeprom_selected = 1'b0;
|
||||||
dd_selected = 1'b0;
|
dd_selected = 1'b0;
|
||||||
flashram_selected = 1'b0;
|
flashram_selected = 1'b0;
|
||||||
if (mem_bus.address[25:24] == 2'b01 && mem_bus.address[23:14] == 10'd0) begin
|
if (mem_bus.address[26:24] == 3'h5) begin
|
||||||
buffer_selected = mem_bus.address[13] == 1'b0;
|
buffer_selected = (mem_bus.address[23:0] >= 24'h00_0000 && mem_bus.address[23:0] < 24'h00_2000);
|
||||||
eeprom_selected = mem_bus.address[13:11] == 3'b100;
|
eeprom_selected = (mem_bus.address[23:0] >= 24'h00_2000 && mem_bus.address[23:0] < 24'h00_2800);
|
||||||
dd_selected = mem_bus.address[13:8] == 6'b101000;
|
dd_selected = (mem_bus.address[23:0] >= 24'h00_2800 && mem_bus.address[23:0] < 24'h00_2C00);
|
||||||
flashram_selected = mem_bus.address[13:7] == 7'b1010010;
|
flashram_selected = (mem_bus.address[23:0] >= 24'h00_2C00 && mem_bus.address[23:0] < 24'h00_2C80);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -112,26 +112,26 @@ module memory_bram (
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
// DD memory
|
// 64DD/MCU buffer memory
|
||||||
|
|
||||||
logic [15:0] dd_bram [0:127];
|
logic [15:0] dd_bram [0:511];
|
||||||
logic [15:0] dd_bram_rdata;
|
logic [15:0] dd_bram_rdata;
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
if (write && dd_selected) begin
|
if (write && dd_selected) begin
|
||||||
dd_bram[mem_bus.address[7:1]] <= mem_bus.wdata;
|
dd_bram[mem_bus.address[9:1]] <= mem_bus.wdata;
|
||||||
end
|
end
|
||||||
if (n64_scb.dd_write) begin
|
if (n64_scb.dd_write) begin
|
||||||
dd_bram[n64_scb.dd_address] <= n64_scb.dd_wdata;
|
dd_bram[{2'b00, n64_scb.dd_address}] <= n64_scb.dd_wdata;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
dd_bram_rdata <= dd_bram[mem_bus.address[7:1]];
|
dd_bram_rdata <= dd_bram[mem_bus.address[9:1]];
|
||||||
end
|
end
|
||||||
|
|
||||||
always_ff @(posedge clk) begin
|
always_ff @(posedge clk) begin
|
||||||
n64_scb.dd_rdata <= dd_bram[n64_scb.dd_address];
|
n64_scb.dd_rdata <= dd_bram[{2'b00, n64_scb.dd_address}];
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
289
hw/pcb/LICENSE
Normal file
289
hw/pcb/LICENSE
Normal 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.
|
@ -9,8 +9,8 @@
|
|||||||
(paper "A4")
|
(paper "A4")
|
||||||
(title_block
|
(title_block
|
||||||
(title "SummerCart64")
|
(title "SummerCart64")
|
||||||
(date "2024-08-11")
|
(date "2025-03-10")
|
||||||
(rev "2.1")
|
(rev "2.1a")
|
||||||
(company "Mateusz Faderewski")
|
(company "Mateusz Faderewski")
|
||||||
)
|
)
|
||||||
(layers
|
(layers
|
||||||
@ -26785,6 +26785,18 @@
|
|||||||
(layer "User.1")
|
(layer "User.1")
|
||||||
(uuid "e23fcef0-be99-4588-9e98-11fe6a1e9f26")
|
(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"
|
(gr_text "RX"
|
||||||
(at 123 84.75 90)
|
(at 123 84.75 90)
|
||||||
(layer "F.SilkS")
|
(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)
|
(at 100.25 83.5 0)
|
||||||
(layer "F.SilkS")
|
(layer "F.SilkS")
|
||||||
(uuid "4ab65223-ff63-4a7c-853c-e7b8d8e12e76")
|
(uuid "4ab65223-ff63-4a7c-853c-e7b8d8e12e76")
|
||||||
@ -26819,7 +26831,7 @@
|
|||||||
(justify left)
|
(justify left)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
(gr_text "11.08.2024"
|
(gr_text "10.03.2025"
|
||||||
(at 100.25 85.25 0)
|
(at 100.25 85.25 0)
|
||||||
(layer "F.SilkS")
|
(layer "F.SilkS")
|
||||||
(uuid "4ebec920-49dd-4966-8b6b-a500a99cc601")
|
(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"
|
(gr_text "3V3"
|
||||||
(at 135 116 0)
|
(at 135 116 0)
|
||||||
(layer "F.SilkS")
|
(layer "F.SilkS")
|
||||||
@ -26887,19 +26888,6 @@
|
|||||||
(justify left)
|
(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
|
(segment
|
||||||
(start 172.325 85.075)
|
(start 172.325 85.075)
|
||||||
(end 172.25 85)
|
(end 172.25 85)
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
(paper "A2")
|
(paper "A2")
|
||||||
(title_block
|
(title_block
|
||||||
(title "SummerCart64")
|
(title "SummerCart64")
|
||||||
(date "2024-08-11")
|
(date "2025-03-10")
|
||||||
(rev "2.1")
|
(rev "2.1a")
|
||||||
(company "Mateusz Faderewski")
|
(company "Mateusz Faderewski")
|
||||||
)
|
)
|
||||||
(lib_symbols
|
(lib_symbols
|
||||||
@ -19434,7 +19434,6 @@
|
|||||||
)
|
)
|
||||||
(label "USB_D+"
|
(label "USB_D+"
|
||||||
(at 97.79 124.46 0)
|
(at 97.79 124.46 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
@ -19445,7 +19444,6 @@
|
|||||||
)
|
)
|
||||||
(label "USB_D-"
|
(label "USB_D-"
|
||||||
(at 97.79 121.92 0)
|
(at 97.79 121.92 0)
|
||||||
(fields_autoplaced yes)
|
|
||||||
(effects
|
(effects
|
||||||
(font
|
(font
|
||||||
(size 1.27 1.27)
|
(size 1.27 1.27)
|
||||||
|
2
hw/pcb/sc64v2_bom.html
generated
2
hw/pcb/sc64v2_bom.html
generated
File diff suppressed because one or more lines are too long
@ -34,6 +34,7 @@ SRC_FILES = \
|
|||||||
interrupts.c \
|
interrupts.c \
|
||||||
interrupts.S \
|
interrupts.S \
|
||||||
io.c \
|
io.c \
|
||||||
|
joybus.c \
|
||||||
main.c \
|
main.c \
|
||||||
menu.c \
|
menu.c \
|
||||||
reboot.S \
|
reboot.S \
|
||||||
|
@ -79,6 +79,7 @@ void boot (boot_params_t *params) {
|
|||||||
while (cpu_io_read(&SP->DMA_BUSY));
|
while (cpu_io_read(&SP->DMA_BUSY));
|
||||||
|
|
||||||
cpu_io_write(&PI->SR, PI_SR_CLR_INTR | PI_SR_RESET);
|
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);
|
while ((cpu_io_read(&VI->CURR_LINE) & ~(VI_CURR_LINE_FIELD)) != 0);
|
||||||
cpu_io_write(&VI->V_INTR, 0x3FF);
|
cpu_io_write(&VI->V_INTR, 0x3FF);
|
||||||
cpu_io_write(&VI->H_LIMITS, 0);
|
cpu_io_write(&VI->H_LIMITS, 0);
|
||||||
|
@ -116,8 +116,8 @@ static void display_draw_character (char c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((char_x + FONT_WIDTH) > (SCREEN_WIDTH - BORDER_WIDTH)) {
|
if ((char_x + FONT_WIDTH) > (SCREEN_WIDTH - BORDER_WIDTH)) {
|
||||||
char_x = BORDER_WIDTH;
|
char_x -= FONT_WIDTH;
|
||||||
char_y += FONT_HEIGHT + LINE_SPACING;
|
c = '\x7F';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((c < ' ') || (c > '~')) {
|
if ((c < ' ') || (c > '~')) {
|
||||||
@ -185,7 +185,7 @@ bool display_ready (void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void display_vprintf (const char *fmt, va_list args) {
|
void display_vprintf (const char *fmt, va_list args) {
|
||||||
char line[256];
|
char line[1024];
|
||||||
|
|
||||||
vsniprintf(line, sizeof(line), fmt, args);
|
vsniprintf(line, sizeof(line), fmt, args);
|
||||||
display_draw_string(line);
|
display_draw_string(line);
|
||||||
|
@ -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(" 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(" 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(" 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(" hi: 0x%016lX lo: 0x%016lX\n", e->hi.u64, e->lo.u64);
|
||||||
display_printf(" lo: 0x%016lX\n", e->lo.u64);
|
|
||||||
|
|
||||||
while (true);
|
while (true);
|
||||||
}
|
}
|
||||||
|
16
sw/bootloader/src/fatfs/00history.txt
vendored
16
sw/bootloader/src/fatfs/00history.txt
vendored
@ -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 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.
|
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.
|
||||||
|
@ -50,6 +50,9 @@ DRESULT disk_read (BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
|
|||||||
if (pdrv > 0) {
|
if (pdrv > 0) {
|
||||||
return RES_PARERR;
|
return RES_PARERR;
|
||||||
}
|
}
|
||||||
|
if ((sector + count) > 0x100000000ULL) {
|
||||||
|
return RES_PARERR;
|
||||||
|
}
|
||||||
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
|
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
|
||||||
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
|
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
|
||||||
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));
|
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) {
|
if (pdrv > 0) {
|
||||||
return RES_PARERR;
|
return RES_PARERR;
|
||||||
}
|
}
|
||||||
|
if ((sector + count) > 0x100000000ULL) {
|
||||||
|
return RES_PARERR;
|
||||||
|
}
|
||||||
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
|
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
|
||||||
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
|
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
|
||||||
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));
|
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));
|
||||||
|
734
sw/bootloader/src/fatfs/ff.c
vendored
734
sw/bootloader/src/fatfs/ff.c
vendored
File diff suppressed because it is too large
Load Diff
67
sw/bootloader/src/fatfs/ff.h
vendored
67
sw/bootloader/src/fatfs/ff.h
vendored
@ -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
|
/ FatFs module is an open source software. Redistribution and use of FatFs in
|
||||||
/ source and binary forms, with or without modification, are permitted provided
|
/ source and binary forms, with or without modification, are permitted provided
|
||||||
@ -20,14 +20,15 @@
|
|||||||
|
|
||||||
|
|
||||||
#ifndef FF_DEFINED
|
#ifndef FF_DEFINED
|
||||||
#define FF_DEFINED 80286 /* Revision ID */
|
#define FF_DEFINED 5380 /* Revision ID */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(FFCONF_DEF)
|
||||||
#include "ffconf.h" /* FatFs configuration options */
|
#include "ffconf.h" /* FatFs configuration options */
|
||||||
|
#endif
|
||||||
#if FF_DEFINED != FFCONF_DEF
|
#if FF_DEFINED != FFCONF_DEF
|
||||||
#error Wrong configuration file (ffconf.h).
|
#error Wrong configuration file (ffconf.h).
|
||||||
#endif
|
#endif
|
||||||
@ -48,18 +49,18 @@ typedef unsigned __int64 QWORD;
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||||
typedef unsigned char BYTE; /* char must be 8-bit */
|
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||||
typedef uint16_t WORD; /* 16-bit unsigned integer */
|
typedef uint16_t WORD; /* 16-bit unsigned */
|
||||||
typedef uint32_t DWORD; /* 32-bit unsigned integer */
|
typedef uint32_t DWORD; /* 32-bit unsigned */
|
||||||
typedef uint64_t QWORD; /* 64-bit unsigned integer */
|
typedef uint64_t QWORD; /* 64-bit unsigned */
|
||||||
typedef WORD WCHAR; /* UTF-16 character type */
|
typedef WORD WCHAR; /* UTF-16 code unit */
|
||||||
|
|
||||||
#else /* Earlier than C99 */
|
#else /* Earlier than C99 */
|
||||||
#define FF_INTDEF 1
|
#define FF_INTDEF 1
|
||||||
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||||
typedef unsigned char BYTE; /* char must be 8-bit */
|
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||||
typedef unsigned short WORD; /* 16-bit unsigned integer */
|
typedef unsigned short WORD; /* short must be 16-bit */
|
||||||
typedef unsigned long DWORD; /* 32-bit unsigned integer */
|
typedef unsigned long DWORD; /* long must be 32-bit */
|
||||||
typedef WORD WCHAR; /* UTF-16 character type */
|
typedef WORD WCHAR; /* UTF-16 code unit */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -113,15 +114,15 @@ typedef char TCHAR;
|
|||||||
|
|
||||||
#if FF_MULTI_PARTITION /* Multiple partition configuration */
|
#if FF_MULTI_PARTITION /* Multiple partition configuration */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BYTE pd; /* Physical drive number */
|
BYTE pd; /* Associated physical drive */
|
||||||
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
|
BYTE pt; /* Associated partition (0:Auto detect, 1-4:Forced partition) */
|
||||||
} PARTITION;
|
} PARTITION;
|
||||||
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
|
extern PARTITION VolToPart[]; /* Volume to partition mapping table */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FF_STR_VOLUME_ID
|
#if FF_STR_VOLUME_ID
|
||||||
#ifndef FF_VOLUME_STRS
|
#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
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -130,12 +131,12 @@ extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
|
|||||||
/* Filesystem object structure (FATFS) */
|
/* Filesystem object structure (FATFS) */
|
||||||
|
|
||||||
typedef struct {
|
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 pdrv; /* Volume hosting physical drive */
|
||||||
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
|
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
|
||||||
BYTE n_fats; /* Number of FATs (1 or 2) */
|
BYTE n_fats; /* Number of FATs (1 or 2) */
|
||||||
BYTE wflag; /* win[] status (b0:dirty) */
|
BYTE wflag; /* win[] status (1:dirty) */
|
||||||
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
|
BYTE fsi_flag; /* Allocation information control (b7:disabled, b0:dirty) */
|
||||||
WORD id; /* Volume mount ID */
|
WORD id; /* Volume mount ID */
|
||||||
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
|
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
|
||||||
WORD csize; /* Cluster size [sectors] */
|
WORD csize; /* Cluster size [sectors] */
|
||||||
@ -146,11 +147,11 @@ typedef struct {
|
|||||||
WCHAR* lfnbuf; /* LFN working buffer */
|
WCHAR* lfnbuf; /* LFN working buffer */
|
||||||
#endif
|
#endif
|
||||||
#if FF_FS_EXFAT
|
#if FF_FS_EXFAT
|
||||||
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
|
BYTE* dirbuf; /* Directory entry block scratch pad buffer for exFAT */
|
||||||
#endif
|
#endif
|
||||||
#if !FF_FS_READONLY
|
#if !FF_FS_READONLY
|
||||||
DWORD last_clst; /* Last allocated cluster */
|
DWORD last_clst; /* Last allocated cluster (Unknown if >= n_fatent) */
|
||||||
DWORD free_clst; /* Number of free clusters */
|
DWORD free_clst; /* Number of free clusters (Unknown if >= n_fatent-2) */
|
||||||
#endif
|
#endif
|
||||||
#if FF_FS_RPATH
|
#if FF_FS_RPATH
|
||||||
DWORD cdir; /* Current directory start cluster (0:root) */
|
DWORD cdir; /* Current directory start cluster (0:root) */
|
||||||
@ -272,24 +273,24 @@ typedef struct {
|
|||||||
/* File function return code (FRESULT) */
|
/* File function return code (FRESULT) */
|
||||||
|
|
||||||
typedef enum {
|
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_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
|
||||||
FR_INT_ERR, /* (2) Assertion failed */
|
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_FILE, /* (4) Could not find the file */
|
||||||
FR_NO_PATH, /* (5) Could not find the path */
|
FR_NO_PATH, /* (5) Could not find the path */
|
||||||
FR_INVALID_NAME, /* (6) The path name format is invalid */
|
FR_INVALID_NAME, /* (6) The path name format is invalid */
|
||||||
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
|
FR_DENIED, /* (7) Access denied due to a prohibited access or directory full */
|
||||||
FR_EXIST, /* (8) Access denied due to prohibited access */
|
FR_EXIST, /* (8) Access denied due to a prohibited access */
|
||||||
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
|
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
|
||||||
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
|
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
|
||||||
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
|
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
|
||||||
FR_NOT_ENABLED, /* (12) The volume has no work area */
|
FR_NOT_ENABLED, /* (12) The volume has no work area */
|
||||||
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
|
FR_NO_FILESYSTEM, /* (13) Could not find a valid FAT volume */
|
||||||
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
|
FR_MKFS_ABORTED, /* (14) The f_mkfs function aborted due to some problem */
|
||||||
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
|
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_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_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
|
||||||
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
|
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
|
||||||
} FRESULT;
|
} FRESULT;
|
||||||
@ -375,7 +376,7 @@ DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
|
|||||||
void* ff_memalloc (UINT msize); /* Allocate memory block */
|
void* ff_memalloc (UINT msize); /* Allocate memory block */
|
||||||
void ff_memfree (void* mblock); /* Free memory block */
|
void ff_memfree (void* mblock); /* Free memory block */
|
||||||
#endif
|
#endif
|
||||||
#if FF_FS_REENTRANT /* Sync functions */
|
#if FF_FS_REENTRANT /* Sync functions */
|
||||||
int ff_mutex_create (int vol); /* Create a sync object */
|
int ff_mutex_create (int vol); /* Create a sync object */
|
||||||
void ff_mutex_delete (int vol); /* Delete a sync object */
|
void ff_mutex_delete (int vol); /* Delete a sync object */
|
||||||
int ff_mutex_take (int vol); /* Lock 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 */
|
/* 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_READ 0x01
|
||||||
#define FA_WRITE 0x02
|
#define FA_WRITE 0x02
|
||||||
#define FA_OPEN_EXISTING 0x00
|
#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_ALWAYS 0x10
|
||||||
#define FA_OPEN_APPEND 0x30
|
#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)
|
#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_FAT 0x01
|
||||||
#define FM_FAT32 0x02
|
#define FM_FAT32 0x02
|
||||||
#define FM_EXFAT 0x04
|
#define FM_EXFAT 0x04
|
||||||
|
78
sw/bootloader/src/fatfs/ffconf.h
vendored
78
sw/bootloader/src/fatfs/ffconf.h
vendored
@ -2,7 +2,7 @@
|
|||||||
/ Configurations of FatFs Module
|
/ Configurations of FatFs Module
|
||||||
/---------------------------------------------------------------------------*/
|
/---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
#define FFCONF_DEF 80286 /* Revision ID */
|
#define FFCONF_DEF 5380 /* Revision ID */
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------/
|
/*---------------------------------------------------------------------------/
|
||||||
/ Function Configurations
|
/ Function Configurations
|
||||||
@ -30,46 +30,46 @@
|
|||||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||||
|
|
||||||
|
|
||||||
#define FF_USE_MKFS 0
|
#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
|
#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
|
#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
|
#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. */
|
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||||
|
|
||||||
|
|
||||||
#define FF_USE_LABEL 1
|
#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) */
|
/ (0:Disable or 1:Enable) */
|
||||||
|
|
||||||
|
|
||||||
#define FF_USE_FORWARD 0
|
#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_USE_STRFUNC 0
|
||||||
#define FF_PRINT_LLI 1
|
#define FF_PRINT_LLI 0
|
||||||
#define FF_PRINT_FLOAT 1
|
#define FF_PRINT_FLOAT 0
|
||||||
#define FF_STRF_ENCODE 3
|
#define FF_STRF_ENCODE 3
|
||||||
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
|
/* FF_USE_STRFUNC switches the string API functions, f_gets(), f_putc(), f_puts()
|
||||||
/ f_printf().
|
/ and f_printf().
|
||||||
/
|
/
|
||||||
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||||
/ 1: Enable without LF-CRLF conversion.
|
/ 1: Enable without LF - CRLF conversion.
|
||||||
/ 2: Enable with 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
|
/ 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.
|
/ 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
|
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||||
/ to be read/written via those functions.
|
/ to be read/written via those functions.
|
||||||
/
|
/
|
||||||
@ -118,22 +118,22 @@
|
|||||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||||
/
|
/
|
||||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
/ 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.
|
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
/ 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
|
/ 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.
|
/ 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
|
/ 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.
|
/ specification.
|
||||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
/ 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
|
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||||
|
|
||||||
|
|
||||||
#define FF_LFN_UNICODE 0
|
#define FF_LFN_UNICODE 2
|
||||||
/* This option switches the character encoding on the API when LFN is enabled.
|
/* This option switches the character encoding on the API when LFN is enabled.
|
||||||
/
|
/
|
||||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||||
@ -156,9 +156,9 @@
|
|||||||
#define FF_FS_RPATH 0
|
#define FF_FS_RPATH 0
|
||||||
/* This option configures support for relative path.
|
/* 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.
|
/ 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.
|
/* 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
|
/ 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
|
/ 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
|
/ 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
|
/ 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:
|
/ 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.
|
/* 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
|
/ 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.
|
/ 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()
|
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||||
/ function will be available. */
|
/ will be available. */
|
||||||
|
|
||||||
|
|
||||||
#define FF_MIN_SS 512
|
#define FF_MIN_SS 512
|
||||||
@ -198,25 +198,25 @@
|
|||||||
/* This set of options configures the range of sector size to be supported. (512,
|
/* 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
|
/ 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
|
/ 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
|
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
|
||||||
/ for variable sector size mode and disk_ioctl() function needs to implement
|
/ configured for variable sector size mode and disk_ioctl() needs to implement
|
||||||
/ GET_SECTOR_SIZE command. */
|
/ 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)
|
/* 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) */
|
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||||
|
|
||||||
|
|
||||||
#define FF_MIN_GPT 0x10000000
|
#define FF_MIN_GPT 0x10000000
|
||||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
|
/* 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. */
|
/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
|
||||||
|
|
||||||
|
|
||||||
#define FF_USE_TRIM 0
|
#define FF_USE_TRIM 0
|
||||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
/* 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
|
/ To enable this feature, also CTRL_TRIM command should be implemented to
|
||||||
/ disk_ioctl() function. */
|
/ the disk_ioctl(). */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -240,20 +240,20 @@
|
|||||||
#define FF_FS_NORTC 0
|
#define FF_FS_NORTC 0
|
||||||
#define FF_NORTC_MON 1
|
#define FF_NORTC_MON 1
|
||||||
#define FF_NORTC_MDAY 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
|
/* 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
|
/ 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
|
/ 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.
|
/ 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
|
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
|
||||||
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
|
/ to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||||
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
||||||
|
|
||||||
|
|
||||||
#define FF_FS_NOFSINFO 0
|
#define FF_FS_NOFSINFO 0
|
||||||
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
/* 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.
|
/ 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.
|
/ 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
|
/* 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
|
/ 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()
|
/ 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
|
/ and f_fdisk(), are always not re-entrant. Only file/directory access to
|
||||||
/ to the same volume is under control of this featuer.
|
/ the same volume is under control of this featuer.
|
||||||
/
|
/
|
||||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
|
/ 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.
|
/ 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.
|
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||||
*/
|
*/
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
#include "vr4300.h"
|
#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)));
|
uint32_t cache_address = (((uint32_t) (address)) & (~(line_size - 1)));
|
||||||
while (cache_address < ((uint32_t) (address) + length)) {
|
while (cache_address < ((uint32_t) (address) + length)) {
|
||||||
asm volatile (
|
asm volatile (
|
||||||
@ -109,3 +109,27 @@ void pi_dma_write (io32_t *address, void *buffer, size_t length) {
|
|||||||
});
|
});
|
||||||
while (pi_busy());
|
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());
|
||||||
|
}
|
||||||
|
@ -202,6 +202,24 @@ typedef struct {
|
|||||||
#define PI_SR_CLR_INTR (1 << 1)
|
#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_BASE (0x06000000UL)
|
||||||
#define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE)
|
#define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE)
|
||||||
|
|
||||||
@ -210,6 +228,14 @@ typedef struct {
|
|||||||
#define ROM_CART ((io32_t *) ROM_CART_BASE)
|
#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);
|
uint32_t c0_count (void);
|
||||||
void delay_ms (int ms);
|
void delay_ms (int ms);
|
||||||
uint32_t cpu_io_read (io32_t *address);
|
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_io_write (io32_t *address, uint32_t value);
|
||||||
void pi_dma_read (io32_t *address, void *buffer, size_t length);
|
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 pi_dma_write (io32_t *address, void *buffer, size_t length);
|
||||||
void cache_data_hit_writeback_invalidate (void *address, size_t length);
|
uint32_t si_busy (void);
|
||||||
void cache_data_hit_writeback (void *address, size_t length);
|
void si_dma_read (void *buffer);
|
||||||
void cache_inst_hit_invalidate (void *address, size_t length);
|
void si_dma_write (void *buffer);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
169
sw/bootloader/src/joybus.c
Normal file
169
sw/bootloader/src/joybus.c
Normal 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;
|
||||||
|
}
|
39
sw/bootloader/src/joybus.h
Normal file
39
sw/bootloader/src/joybus.h
Normal 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
|
@ -3,6 +3,7 @@
|
|||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "init.h"
|
#include "init.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
#include "joybus.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "sc64.h"
|
#include "sc64.h"
|
||||||
|
|
||||||
@ -22,6 +23,20 @@ void main (void) {
|
|||||||
boot_params.cic_seed = (sc64_boot_params.cic_seed & 0xFF);
|
boot_params.cic_seed = (sc64_boot_params.cic_seed & 0xFF);
|
||||||
boot_params.detect_cic_seed = (sc64_boot_params.cic_seed == CIC_SEED_AUTO);
|
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) {
|
switch (sc64_boot_params.boot_mode) {
|
||||||
case BOOT_MODE_MENU:
|
case BOOT_MODE_MENU:
|
||||||
menu_load();
|
menu_load();
|
||||||
|
@ -15,49 +15,61 @@ static const char *fatfs_error_codes[] = {
|
|||||||
"No error",
|
"No error",
|
||||||
"A hard error occurred in the low level disk I/O layer",
|
"A hard error occurred in the low level disk I/O layer",
|
||||||
"Assertion failed",
|
"Assertion failed",
|
||||||
"The physical drive cannot work",
|
"The physical drive does not work",
|
||||||
"Could not find the file",
|
"Could not find the file",
|
||||||
"Could not find the path",
|
"Could not find the path",
|
||||||
"The path name format is invalid",
|
"The path name format is invalid",
|
||||||
"Access denied due to prohibited access or directory full",
|
"Access denied due to a prohibited access or directory full",
|
||||||
"Access denied due to prohibited access",
|
"Access denied due to a prohibited access",
|
||||||
"The file/directory object is invalid",
|
"The file/directory object is invalid",
|
||||||
"The physical drive is write protected",
|
"The physical drive is write protected",
|
||||||
"The logical drive number is invalid",
|
"The logical drive number is invalid",
|
||||||
"The volume has no work area",
|
"The volume has no work area",
|
||||||
"There is no valid FAT volume",
|
"Could not find a valid FAT volume",
|
||||||
"The f_mkfs() aborted due to any problem",
|
"The f_mkfs function aborted due to some problem",
|
||||||
"Could not get a grant to access the volume within defined period",
|
"Could not take control of the volume within defined period",
|
||||||
"The operation is rejected according to the file sharing policy",
|
"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",
|
"Number of open files > FF_FS_LOCK",
|
||||||
"Given parameter is invalid",
|
"Given parameter is invalid",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#define FF_CHECK(x, message, ...) { \
|
static void menu_fix_file_size (FIL *fil) {
|
||||||
fresult = x; \
|
fil->obj.objsize = ALIGN(f_size(fil), FF_MAX_SS);
|
||||||
if (fresult != FR_OK) { \
|
}
|
||||||
error_display( \
|
|
||||||
message "\n" \
|
static void menu_error_display (const char *message, FRESULT fresult) {
|
||||||
" > FatFs error: %s\n" \
|
error_display(
|
||||||
" > SD card error: %s (%08X)", \
|
" > FatFs error: %s\n"
|
||||||
__VA_ARGS__ __VA_OPT__(,) \
|
" > SD card error: %s (%08X)\n"
|
||||||
fatfs_error_codes[fresult], \
|
"\n"
|
||||||
sc64_error_description(sc64_error_fatfs), sc64_error_fatfs \
|
"[ %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) {
|
#define FF_CHECK(x, message) { \
|
||||||
fil->obj.objsize = ALIGN(f_size(fil), FF_MAX_SS);
|
fresult = x; \
|
||||||
|
if (fresult != FR_OK) { \
|
||||||
|
menu_error_display(message, fresult); \
|
||||||
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void menu_load (void) {
|
void menu_load (void) {
|
||||||
sc64_error_t error;
|
sc64_error_t error;
|
||||||
bool writeback_pending;
|
bool writeback_pending;
|
||||||
|
sc64_sd_card_status_t sd_card_status;
|
||||||
FRESULT fresult;
|
FRESULT fresult;
|
||||||
FATFS fs;
|
FATFS fs;
|
||||||
FIL fil;
|
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));
|
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_mount(&fs, "", 1), "SD card initialize error");
|
||||||
FF_CHECK(f_open(&fil, "sc64menu.n64", FA_READ), "Could not open menu executable (sc64menu.n64)");
|
FF_CHECK(f_open(&fil, "sc64menu.n64", FA_READ), "Could not open menu executable");
|
||||||
fix_menu_file_size(&fil);
|
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(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((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");
|
FF_CHECK(f_close(&fil), "Could not close menu file");
|
||||||
|
@ -194,6 +194,7 @@ const char *sc64_error_description (sc64_error_t error) {
|
|||||||
case SD_ERROR_ACMD41_IO: return "ACMD41 I/O";
|
case SD_ERROR_ACMD41_IO: return "ACMD41 I/O";
|
||||||
case SD_ERROR_ACMD41_OCR: return "ACMD41 OCR";
|
case SD_ERROR_ACMD41_OCR: return "ACMD41 OCR";
|
||||||
case SD_ERROR_ACMD41_TIMEOUT: return "ACMD41 timeout";
|
case SD_ERROR_ACMD41_TIMEOUT: return "ACMD41 timeout";
|
||||||
|
case SD_ERROR_LOCKED: return "SD card is locked by the PC side";
|
||||||
default: return "Unknown error (SD)";
|
default: return "Unknown error (SD)";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ typedef enum {
|
|||||||
SD_ERROR_ACMD41_IO = 27,
|
SD_ERROR_ACMD41_IO = 27,
|
||||||
SD_ERROR_ACMD41_OCR = 28,
|
SD_ERROR_ACMD41_OCR = 28,
|
||||||
SD_ERROR_ACMD41_TIMEOUT = 29,
|
SD_ERROR_ACMD41_TIMEOUT = 29,
|
||||||
|
SD_ERROR_LOCKED = 30,
|
||||||
} sc64_sd_error_t;
|
} sc64_sd_error_t;
|
||||||
|
|
||||||
typedef uint32_t sc64_error_t;
|
typedef uint32_t sc64_error_t;
|
||||||
@ -103,7 +104,8 @@ typedef enum {
|
|||||||
SAVE_TYPE_SRAM = 3,
|
SAVE_TYPE_SRAM = 3,
|
||||||
SAVE_TYPE_FLASHRAM = 4,
|
SAVE_TYPE_FLASHRAM = 4,
|
||||||
SAVE_TYPE_SRAM_BANKED = 5,
|
SAVE_TYPE_SRAM_BANKED = 5,
|
||||||
SAVE_TYPE_SRAM_1M = 6
|
SAVE_TYPE_SRAM_1M = 6,
|
||||||
|
SAVE_TYPE_FLASHRAM_FAKE = 7,
|
||||||
} sc64_save_type_t;
|
} sc64_save_type_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "fatfs/ff.h"
|
#include "fatfs/ff.h"
|
||||||
#include "init.h"
|
#include "init.h"
|
||||||
#include "io.h"
|
#include "io.h"
|
||||||
|
#include "joybus.h"
|
||||||
#include "sc64.h"
|
#include "sc64.h"
|
||||||
#include "test.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) {
|
static void test_sc64_cfg (void) {
|
||||||
sc64_error_t error;
|
sc64_error_t error;
|
||||||
uint32_t button_state;
|
uint32_t button_state;
|
||||||
|
joybus_controller_info_t controller_info;
|
||||||
uint32_t identifier;
|
uint32_t identifier;
|
||||||
uint16_t major;
|
uint16_t major;
|
||||||
uint16_t minor;
|
uint16_t minor;
|
||||||
@ -71,6 +73,10 @@ static void test_sc64_cfg (void) {
|
|||||||
|
|
||||||
display_printf("done\n\n");
|
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) {
|
if ((error = sc64_get_identifier(&identifier)) != SC64_OK) {
|
||||||
error_display("Command IDENTIFIER_GET failed\n (%08X) - %s", error, sc64_error_description(error));
|
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)));
|
uint8_t sector[512] __attribute__((aligned(8)));
|
||||||
|
|
||||||
if ((error = sc64_sd_card_get_status(&card_status)) != SC64_OK) {
|
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) {
|
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) {
|
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) {
|
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) {
|
if ((error = sc64_sd_card_get_status(&card_status)) != SC64_OK) {
|
||||||
@ -261,7 +267,7 @@ static void test_sd_card_fatfs (void) {
|
|||||||
UINT bytes;
|
UINT bytes;
|
||||||
|
|
||||||
if ((error = sc64_sd_card_deinit()) != SC64_OK) {
|
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) {
|
if ((fresult = f_mount(&fs, "", 1)) != FR_OK) {
|
||||||
|
@ -35,9 +35,10 @@ const struct {
|
|||||||
|
|
||||||
|
|
||||||
void version_print (void) {
|
void version_print (void) {
|
||||||
display_printf("[ SC64 bootloader metadata ]\n");
|
display_printf("[ SC64 btldr ] %s | %s\n", version.git_branch, version.git_tag);
|
||||||
display_printf("branch: %s | tag: %s\n", version.git_branch, version.git_tag);
|
|
||||||
display_printf("sha: %s\n", version.git_sha);
|
display_printf("sha: %s\n", version.git_sha);
|
||||||
display_printf("msg: %s\n", version.git_message);
|
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");
|
||||||
}
|
}
|
||||||
|
@ -92,15 +92,6 @@ typedef enum {
|
|||||||
TV_TYPE_PASSTHROUGH = 3
|
TV_TYPE_PASSTHROUGH = 3
|
||||||
} tv_type_t;
|
} 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 {
|
typedef enum {
|
||||||
DIAGNOSTIC_ID_VOLTAGE_TEMPERATURE = 0,
|
DIAGNOSTIC_ID_VOLTAGE_TEMPERATURE = 0,
|
||||||
} diagnostic_id_t;
|
} diagnostic_id_t;
|
||||||
@ -141,6 +132,10 @@ static struct process p;
|
|||||||
|
|
||||||
|
|
||||||
static bool cfg_cmd_check (void) {
|
static bool cfg_cmd_check (void) {
|
||||||
|
if (!writeback_pending() && !hw_gpio_get(GPIO_ID_N64_RESET)) {
|
||||||
|
sd_release_lock(SD_LOCK_N64);
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
|
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
|
||||||
|
|
||||||
if (reg & CFG_CMD_AUX_PENDING) {
|
if (reg & CFG_CMD_AUX_PENDING) {
|
||||||
@ -283,6 +278,9 @@ static bool cfg_set_save_type (save_type_t save_type) {
|
|||||||
case SAVE_TYPE_SRAM_1M:
|
case SAVE_TYPE_SRAM_1M:
|
||||||
cfg_change_scr_bits(CFG_SCR_SRAM_ENABLED, true);
|
cfg_change_scr_bits(CFG_SCR_SRAM_ENABLED, true);
|
||||||
break;
|
break;
|
||||||
|
case SAVE_TYPE_FLASHRAM_FAKE:
|
||||||
|
cfg_change_scr_bits(CFG_SCR_FLASHRAM_ENABLED, true);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
save_type = SAVE_TYPE_NONE;
|
save_type = SAVE_TYPE_NONE;
|
||||||
break;
|
break;
|
||||||
@ -634,60 +632,75 @@ void cfg_process (void) {
|
|||||||
p.data[0] = p.usb_output_ready ? 0 : (1 << 31);
|
p.data[0] = p.usb_output_ready ? 0 : (1 << 31);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_ID_SD_CARD_OP:
|
case CMD_ID_SD_CARD_OP: {
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
switch (p.data[1]) {
|
switch (p.data[1]) {
|
||||||
case SD_CARD_OP_DEINIT:
|
case SD_OP_DEINIT:
|
||||||
sd_card_deinit();
|
error = sd_try_lock(SD_LOCK_N64);
|
||||||
break;
|
if (error == SD_OK) {
|
||||||
|
sd_card_deinit();
|
||||||
case SD_CARD_OP_INIT: {
|
sd_release_lock(SD_LOCK_N64);
|
||||||
led_activity_on();
|
|
||||||
sd_error_t error = sd_card_init();
|
|
||||||
led_activity_off();
|
|
||||||
if (error != SD_OK) {
|
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case SD_CARD_OP_GET_STATUS:
|
case SD_OP_INIT:
|
||||||
|
error = sd_try_lock(SD_LOCK_N64);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_card_init();
|
||||||
|
led_activity_off();
|
||||||
|
if (error != SD_OK) {
|
||||||
|
sd_release_lock(SD_LOCK_N64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SD_OP_GET_STATUS:
|
||||||
p.data[1] = sd_card_get_status();
|
p.data[1] = sd_card_get_status();
|
||||||
break;
|
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))) {
|
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);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||||
}
|
}
|
||||||
sd_error_t error = sd_card_get_info(p.data[0]);
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
if (error != SD_OK) {
|
if (error == SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
error = sd_card_get_info(p.data[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SD_CARD_OP_BYTE_SWAP_ON: {
|
case SD_OP_BYTE_SWAP_ON:
|
||||||
sd_error_t error = sd_set_byte_swap(true);
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
if (error != SD_OK) {
|
if (error == SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
error = sd_set_byte_swap(true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case SD_CARD_OP_BYTE_SWAP_OFF: {
|
case SD_OP_BYTE_SWAP_OFF:
|
||||||
sd_error_t error = sd_set_byte_swap(false);
|
error = sd_get_lock(SD_LOCK_N64);
|
||||||
if (error != SD_OK) {
|
if (error == SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
error = sd_set_byte_swap(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_OPERATION);
|
error = SD_ERROR_INVALID_OPERATION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (error != SD_OK) {
|
||||||
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case CMD_ID_SD_SECTOR_SET:
|
case CMD_ID_SD_SECTOR_SET: {
|
||||||
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
|
if (error != SD_OK) {
|
||||||
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
|
}
|
||||||
p.sd_card_sector = p.data[0];
|
p.sd_card_sector = p.data[0];
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case CMD_ID_SD_READ: {
|
case CMD_ID_SD_READ: {
|
||||||
if (p.data[1] >= 0x800000) {
|
if (p.data[1] >= 0x800000) {
|
||||||
@ -696,9 +709,12 @@ void cfg_process (void) {
|
|||||||
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||||
}
|
}
|
||||||
led_activity_on();
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
sd_error_t error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
if (error == SD_OK) {
|
||||||
led_activity_off();
|
led_activity_on();
|
||||||
|
error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
if (error != SD_OK) {
|
if (error != SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
}
|
}
|
||||||
@ -713,9 +729,12 @@ void cfg_process (void) {
|
|||||||
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||||
}
|
}
|
||||||
led_activity_on();
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
sd_error_t error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
if (error == SD_OK) {
|
||||||
led_activity_off();
|
led_activity_on();
|
||||||
|
error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
if (error != SD_OK) {
|
if (error != SD_OK) {
|
||||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ typedef enum {
|
|||||||
SAVE_TYPE_FLASHRAM = 4,
|
SAVE_TYPE_FLASHRAM = 4,
|
||||||
SAVE_TYPE_SRAM_BANKED = 5,
|
SAVE_TYPE_SRAM_BANKED = 5,
|
||||||
SAVE_TYPE_SRAM_1M = 6,
|
SAVE_TYPE_SRAM_1M = 6,
|
||||||
|
SAVE_TYPE_FLASHRAM_FAKE = 7,
|
||||||
__SAVE_TYPE_COUNT
|
__SAVE_TYPE_COUNT
|
||||||
} save_type_t;
|
} save_type_t;
|
||||||
|
|
||||||
|
@ -134,11 +134,14 @@ static bool dd_block_read_request (void) {
|
|||||||
uint16_t index = dd_track_head_block();
|
uint16_t index = dd_track_head_block();
|
||||||
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
||||||
if (p.sd_mode) {
|
if (p.sd_mode) {
|
||||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
|
if (error == SD_OK) {
|
||||||
led_activity_on();
|
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||||
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
|
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
|
||||||
led_activity_off();
|
led_activity_on();
|
||||||
|
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
dd_set_block_ready(error == SD_OK);
|
dd_set_block_ready(error == SD_OK);
|
||||||
} else {
|
} else {
|
||||||
usb_tx_info_t packet_info;
|
usb_tx_info_t packet_info;
|
||||||
@ -158,11 +161,14 @@ static bool dd_block_write_request (void) {
|
|||||||
uint32_t index = dd_track_head_block();
|
uint32_t index = dd_track_head_block();
|
||||||
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
||||||
if (p.sd_mode) {
|
if (p.sd_mode) {
|
||||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
|
if (error == SD_OK) {
|
||||||
led_activity_on();
|
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||||
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
|
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
|
||||||
led_activity_off();
|
led_activity_on();
|
||||||
|
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
dd_set_block_ready(error == SD_OK);
|
dd_set_block_ready(error == SD_OK);
|
||||||
} else {
|
} else {
|
||||||
usb_tx_info_t packet_info;
|
usb_tx_info_t packet_info;
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "cfg.h"
|
||||||
#include "fpga.h"
|
#include "fpga.h"
|
||||||
|
#include "hw.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
|
||||||
#define FLASHRAM_SIZE (128 * 1024)
|
#define FLASHRAM_SIZE (128 * 1024)
|
||||||
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
|
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
|
||||||
#define FLASHRAM_PAGE_SIZE (128)
|
#define FLASHRAM_PAGE_SIZE (128)
|
||||||
#define FLASHRAM_ADDRESS (0x03FE0000UL)
|
#define FLASHRAM_ADDRESS (0x03FE0000UL)
|
||||||
#define FLASHRAM_BUFFER_ADDRESS (0x05002900UL)
|
#define FLASHRAM_BUFFER_ADDRESS (0x05002C00UL)
|
||||||
|
|
||||||
|
#define FLASHRAM_WRITE_TIMING_MS (3)
|
||||||
|
#define FLASHRAM_ERASE_TIMING_MS (200)
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -16,6 +22,13 @@ typedef enum {
|
|||||||
OP_WRITE_PAGE
|
OP_WRITE_PAGE
|
||||||
} flashram_op_t;
|
} flashram_op_t;
|
||||||
|
|
||||||
|
struct process {
|
||||||
|
bool pending;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static struct process p;
|
||||||
|
|
||||||
|
|
||||||
static flashram_op_t flashram_operation_type (uint32_t scr) {
|
static flashram_op_t flashram_operation_type (uint32_t scr) {
|
||||||
if (!(scr & FLASHRAM_SCR_PENDING)) {
|
if (!(scr & FLASHRAM_SCR_PENDING)) {
|
||||||
@ -38,6 +51,7 @@ void flashram_init (void) {
|
|||||||
if (fpga_reg_get(REG_FLASHRAM_SCR) & FLASHRAM_SCR_PENDING) {
|
if (fpga_reg_get(REG_FLASHRAM_SCR) & FLASHRAM_SCR_PENDING) {
|
||||||
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);
|
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];
|
uint8_t write_buffer[FLASHRAM_PAGE_SIZE];
|
||||||
|
|
||||||
uint32_t page = ((scr & FLASHRAM_SCR_PAGE_MASK) >> FLASHRAM_SCR_PAGE_BIT);
|
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];
|
uint8_t page_buffer[FLASHRAM_PAGE_SIZE];
|
||||||
|
|
||||||
uint32_t address = (FLASHRAM_ADDRESS + (page * 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);
|
fpga_mem_read(address, FLASHRAM_PAGE_SIZE, write_buffer);
|
||||||
|
|
||||||
for (int i = 0; i < FLASHRAM_PAGE_SIZE; i++) {
|
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);
|
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++) {
|
for (int i = 0; i < FLASHRAM_PAGE_SIZE; i++) {
|
||||||
write_buffer[i] = 0xFF;
|
write_buffer[i] = 0xFF;
|
||||||
}
|
}
|
||||||
@ -80,6 +114,10 @@ void flashram_process (void) {
|
|||||||
for (uint32_t offset = 0; offset < erase_size; offset += FLASHRAM_PAGE_SIZE) {
|
for (uint32_t offset = 0; offset < erase_size; offset += FLASHRAM_PAGE_SIZE) {
|
||||||
fpga_mem_write(address + offset, FLASHRAM_PAGE_SIZE, write_buffer);
|
fpga_mem_write(address + offset, FLASHRAM_PAGE_SIZE, write_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (full_emulation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);
|
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
|
|
||||||
|
|
||||||
#define SD_INIT_BUFFER_ADDRESS (0x05002800UL)
|
#define SD_INIT_BUFFER_ADDRESS (0x05002A00UL)
|
||||||
#define BYTE_SWAP_ADDRESS_END (0x05000000UL)
|
#define BYTE_SWAP_ADDRESS_END (0x05000000UL)
|
||||||
|
|
||||||
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
||||||
@ -35,11 +35,10 @@
|
|||||||
#define R7_CHECK_PATTERN (0xAA << 0)
|
#define R7_CHECK_PATTERN (0xAA << 0)
|
||||||
|
|
||||||
#define TIMEOUT_INIT_MS (1000)
|
#define TIMEOUT_INIT_MS (1000)
|
||||||
|
#define TIMEOUT_DATA_MS (5000)
|
||||||
|
|
||||||
#define DAT_CRC16_LENGTH (8)
|
#define DAT_CRC16_LENGTH (8)
|
||||||
#define DAT_BLOCK_MAX_COUNT (256)
|
#define DAT_BLOCK_MAX_COUNT (256)
|
||||||
#define DAT_TIMEOUT_INIT_MS (2000)
|
|
||||||
#define DAT_TIMEOUT_DATA_MS (5000)
|
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -59,16 +58,12 @@ typedef enum {
|
|||||||
RSP_R7,
|
RSP_R7,
|
||||||
} rsp_type_t;
|
} rsp_type_t;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
DAT_READ,
|
|
||||||
DAT_WRITE,
|
|
||||||
} dat_mode_t;
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
DAT_OK,
|
DAT_OK,
|
||||||
|
DAT_BUSY,
|
||||||
DAT_ERROR_IO,
|
DAT_ERROR_IO,
|
||||||
DAT_ERROR_TIMEOUT,
|
DAT_ERROR_TIMEOUT,
|
||||||
} dat_error_t;
|
} dat_status_t;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
CMD6_OK,
|
CMD6_OK,
|
||||||
@ -86,6 +81,7 @@ struct process {
|
|||||||
uint8_t csd[16];
|
uint8_t csd[16];
|
||||||
uint8_t cid[16];
|
uint8_t cid[16];
|
||||||
bool byte_swap;
|
bool byte_swap;
|
||||||
|
sd_lock_t lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -110,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) {
|
static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
|
||||||
uint32_t scr;
|
uint32_t scr;
|
||||||
uint32_t cmd_data;
|
uint32_t cmd_data;
|
||||||
@ -175,52 +172,109 @@ static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp)
|
|||||||
return false;
|
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) {
|
static void sd_dat_start_write (uint32_t count) {
|
||||||
sd_dat |= SD_DAT_START_READ;
|
uint32_t dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_START_WRITE | SD_DAT_FIFO_FLUSH);
|
||||||
sd_dma_scr |= DMA_SCR_DIRECTION;
|
fpga_reg_set(REG_SD_DAT, dat);
|
||||||
if (p.byte_swap && (address < BYTE_SWAP_ADDRESS_END)) {
|
}
|
||||||
sd_dma_scr |= DMA_SCR_BYTE_SWAP;
|
|
||||||
}
|
static void sd_dat_start_read (uint32_t count) {
|
||||||
} else {
|
uint32_t dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_START_READ | SD_DAT_FIFO_FLUSH);
|
||||||
sd_dat |= SD_DAT_START_WRITE;
|
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;
|
||||||
}
|
}
|
||||||
|
if (dat & SD_DAT_ERROR) {
|
||||||
fpga_reg_set(REG_SD_DAT, sd_dat);
|
return DAT_ERROR_IO;
|
||||||
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
|
}
|
||||||
fpga_reg_set(REG_SD_DMA_LENGTH, length);
|
return DAT_OK;
|
||||||
fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sd_dat_abort (void) {
|
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);
|
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);
|
timer_countdown_start(TIMER_ID_SD, timeout_ms);
|
||||||
|
|
||||||
do {
|
while (true) {
|
||||||
uint32_t sd_dat = fpga_reg_get(REG_SD_DAT);
|
dat_status_t status = sd_dat_status();
|
||||||
uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR);
|
|
||||||
if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) {
|
switch (status) {
|
||||||
if (sd_dat & SD_DAT_ERROR) {
|
case DAT_BUSY:
|
||||||
sd_dat_abort();
|
break;
|
||||||
return DAT_ERROR_IO;
|
|
||||||
}
|
case DAT_OK:
|
||||||
return 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();
|
if (timer_countdown_elapsed(TIMER_ID_SD)) {
|
||||||
|
sd_abort();
|
||||||
return DAT_ERROR_TIMEOUT;
|
return DAT_ERROR_TIMEOUT;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool sd_dat_check_crc16 (uint8_t *data, uint32_t length) {
|
static bool sd_dat_check_crc16 (uint8_t *data, uint32_t length) {
|
||||||
uint16_t device_crc[4];
|
uint16_t device_crc[4];
|
||||||
uint16_t controller_crc[4];
|
uint16_t controller_crc[4];
|
||||||
@ -265,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) {
|
static cmd6_error_t sd_cmd6 (uint32_t arg, uint8_t *buffer) {
|
||||||
uint32_t rsp;
|
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)) {
|
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)) {
|
if ((!sd_cmd(13, p.rca, RSP_R1, &rsp)) && (rsp & R1_ILLEGAL_COMMAND)) {
|
||||||
return CMD6_ERROR_ILLEGAL_CMD;
|
return CMD6_ERROR_ILLEGAL_CMD;
|
||||||
}
|
}
|
||||||
return CMD6_ERROR_IO;
|
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;
|
return CMD6_ERROR_TIMEOUT;
|
||||||
}
|
}
|
||||||
fpga_mem_read(SD_INIT_BUFFER_ADDRESS, CMD6_DATA_LENGTH + DAT_CRC16_LENGTH, buffer);
|
fpga_mem_read(SD_INIT_BUFFER_ADDRESS, CMD6_DATA_LENGTH + DAT_CRC16_LENGTH, buffer);
|
||||||
@ -428,6 +482,7 @@ sd_error_t sd_card_init (void) {
|
|||||||
void sd_card_deinit (void) {
|
void sd_card_deinit (void) {
|
||||||
if (p.card_initialized) {
|
if (p.card_initialized) {
|
||||||
p.card_initialized = false;
|
p.card_initialized = false;
|
||||||
|
p.card_type_block = false;
|
||||||
p.byte_swap = false;
|
p.byte_swap = false;
|
||||||
sd_set_clock(CLOCK_400KHZ);
|
sd_set_clock(CLOCK_400KHZ);
|
||||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||||
@ -493,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)) {
|
if (sd_cmd(25, sector, RSP_R1, NULL)) {
|
||||||
return SD_ERROR_CMD25_IO;
|
return SD_ERROR_CMD25_IO;
|
||||||
}
|
}
|
||||||
sd_dat_prepare(address, blocks, DAT_WRITE);
|
sd_start_write(address, blocks);
|
||||||
dat_error_t error = sd_dat_wait(DAT_TIMEOUT_DATA_MS);
|
dat_status_t status = sd_sync(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_cmd(12, 0, RSP_R1b, NULL);
|
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);
|
address += (blocks * SD_SECTOR_SIZE);
|
||||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||||
count -= blocks;
|
count -= blocks;
|
||||||
@ -527,17 +581,16 @@ sd_error_t sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
|||||||
|
|
||||||
while (count > 0) {
|
while (count > 0) {
|
||||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
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)) {
|
if (sd_cmd(18, sector, RSP_R1, NULL)) {
|
||||||
sd_dat_abort();
|
sd_abort();
|
||||||
return SD_ERROR_CMD18_IO;
|
return SD_ERROR_CMD18_IO;
|
||||||
}
|
}
|
||||||
dat_error_t error = sd_dat_wait(DAT_TIMEOUT_DATA_MS);
|
dat_status_t status = sd_sync(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;
|
|
||||||
}
|
|
||||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
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);
|
address += (blocks * SD_SECTOR_SIZE);
|
||||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||||
count -= blocks;
|
count -= blocks;
|
||||||
@ -576,9 +629,32 @@ sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sd_error_t sd_get_lock (sd_lock_t lock) {
|
||||||
|
if (p.lock == lock) {
|
||||||
|
return SD_OK;
|
||||||
|
}
|
||||||
|
return SD_ERROR_LOCKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
sd_error_t sd_try_lock (sd_lock_t lock) {
|
||||||
|
if (p.lock == SD_LOCK_NONE) {
|
||||||
|
p.lock = lock;
|
||||||
|
return SD_OK;
|
||||||
|
}
|
||||||
|
return sd_get_lock(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sd_release_lock (sd_lock_t lock) {
|
||||||
|
if (p.lock == lock) {
|
||||||
|
p.lock = SD_LOCK_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void sd_init (void) {
|
void sd_init (void) {
|
||||||
p.card_initialized = false;
|
p.card_initialized = false;
|
||||||
p.byte_swap = false;
|
p.byte_swap = false;
|
||||||
|
p.lock = SD_LOCK_NONE;
|
||||||
sd_set_clock(CLOCK_STOP);
|
sd_set_clock(CLOCK_STOP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +41,26 @@ typedef enum {
|
|||||||
SD_ERROR_ACMD41_IO = 27,
|
SD_ERROR_ACMD41_IO = 27,
|
||||||
SD_ERROR_ACMD41_OCR = 28,
|
SD_ERROR_ACMD41_OCR = 28,
|
||||||
SD_ERROR_ACMD41_TIMEOUT = 29,
|
SD_ERROR_ACMD41_TIMEOUT = 29,
|
||||||
|
SD_ERROR_LOCKED = 30,
|
||||||
} sd_error_t;
|
} sd_error_t;
|
||||||
|
|
||||||
typedef sd_error_t sd_process_sectors_t (uint32_t address, uint32_t sector, uint32_t count);
|
typedef sd_error_t sd_process_sectors_t (uint32_t address, uint32_t sector, uint32_t count);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SD_LOCK_NONE,
|
||||||
|
SD_LOCK_N64,
|
||||||
|
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);
|
sd_error_t sd_card_init (void);
|
||||||
void sd_card_deinit (void);
|
void sd_card_deinit (void);
|
||||||
@ -58,6 +74,10 @@ sd_error_t sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count);
|
|||||||
|
|
||||||
sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors);
|
sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors);
|
||||||
|
|
||||||
|
sd_error_t sd_get_lock (sd_lock_t lock);
|
||||||
|
sd_error_t sd_try_lock (sd_lock_t lock);
|
||||||
|
void sd_release_lock (sd_lock_t lock);
|
||||||
|
|
||||||
void sd_init (void);
|
void sd_init (void);
|
||||||
|
|
||||||
void sd_process (void);
|
void sd_process (void);
|
||||||
|
@ -13,6 +13,7 @@ typedef enum {
|
|||||||
TIMER_ID_SD,
|
TIMER_ID_SD,
|
||||||
TIMER_ID_USB,
|
TIMER_ID_USB,
|
||||||
TIMER_ID_WRITEBACK,
|
TIMER_ID_WRITEBACK,
|
||||||
|
TIMER_ID_FLASHRAM,
|
||||||
__TIMER_ID_COUNT
|
__TIMER_ID_COUNT
|
||||||
} timer_id_t;
|
} timer_id_t;
|
||||||
|
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
#include "flash.h"
|
#include "flash.h"
|
||||||
#include "fpga.h"
|
#include "fpga.h"
|
||||||
#include "hw.h"
|
#include "hw.h"
|
||||||
|
#include "led.h"
|
||||||
#include "rtc.h"
|
#include "rtc.h"
|
||||||
|
#include "sd.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
#include "update.h"
|
#include "update.h"
|
||||||
#include "usb.h"
|
#include "usb.h"
|
||||||
@ -15,7 +17,7 @@
|
|||||||
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
||||||
#define BOOTLOADER_LENGTH (1920 * 1024)
|
#define BOOTLOADER_LENGTH (1920 * 1024)
|
||||||
|
|
||||||
#define MEMORY_LENGTH (0x05002980UL)
|
#define MEMORY_LENGTH (0x05002C80UL)
|
||||||
|
|
||||||
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
||||||
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
||||||
@ -268,6 +270,7 @@ static void usb_rx_process (void) {
|
|||||||
case 'R':
|
case 'R':
|
||||||
cfg_reset_state();
|
cfg_reset_state();
|
||||||
cic_reset_parameters();
|
cic_reset_parameters();
|
||||||
|
sd_release_lock(SD_LOCK_USB);
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
break;
|
break;
|
||||||
@ -384,6 +387,122 @@ static void usb_rx_process (void) {
|
|||||||
p.response_pending = true;
|
p.response_pending = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'i': {
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
|
switch (p.rx_args[1]) {
|
||||||
|
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 SD_OP_INIT:
|
||||||
|
error = sd_try_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_card_init();
|
||||||
|
led_activity_off();
|
||||||
|
if (error != SD_OK) {
|
||||||
|
sd_release_lock(SD_LOCK_USB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SD_OP_GET_STATUS:
|
||||||
|
break;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
error = sd_card_get_info(p.rx_args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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 SD_OP_BYTE_SWAP_OFF:
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
error = sd_set_byte_swap(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error = SD_ERROR_INVALID_OPERATION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = (error != SD_OK);
|
||||||
|
p.response_info.data_length = 8;
|
||||||
|
p.response_info.data[0] = error;
|
||||||
|
p.response_info.data[1] = sd_card_get_status();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 's': {
|
||||||
|
uint32_t sector = 0;
|
||||||
|
if (!usb_rx_word(§or)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
|
if (p.rx_args[1] >= 0x800000) {
|
||||||
|
error = SD_ERROR_INVALID_ARGUMENT;
|
||||||
|
} else if (usb_validate_address_length(p.rx_args[0], (p.rx_args[1] * SD_SECTOR_SIZE), true)) {
|
||||||
|
error = SD_ERROR_INVALID_ADDRESS;
|
||||||
|
} else {
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_read_sectors(p.rx_args[0], sector, p.rx_args[1]);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = (error != SD_OK);
|
||||||
|
p.response_info.data_length = 4;
|
||||||
|
p.response_info.data[0] = error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'S': {
|
||||||
|
uint32_t sector = 0;
|
||||||
|
if (!usb_rx_word(§or)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sd_error_t error = SD_OK;
|
||||||
|
if (p.rx_args[1] >= 0x800000) {
|
||||||
|
error = SD_ERROR_INVALID_ARGUMENT;
|
||||||
|
} else if (usb_validate_address_length(p.rx_args[0], (p.rx_args[1] * SD_SECTOR_SIZE), true)) {
|
||||||
|
error = SD_ERROR_INVALID_ADDRESS;
|
||||||
|
} else {
|
||||||
|
error = sd_get_lock(SD_LOCK_USB);
|
||||||
|
if (error == SD_OK) {
|
||||||
|
led_activity_on();
|
||||||
|
error = sd_write_sectors(p.rx_args[0], sector, p.rx_args[1]);
|
||||||
|
led_activity_off();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.rx_state = RX_STATE_IDLE;
|
||||||
|
p.response_pending = true;
|
||||||
|
p.response_error = (error != SD_OK);
|
||||||
|
p.response_info.data_length = 4;
|
||||||
|
p.response_info.data[0] = error;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'D':
|
case 'D':
|
||||||
dd_set_block_ready(p.rx_args[0] == 0);
|
dd_set_block_ready(p.rx_args[0] == 0);
|
||||||
p.rx_state = RX_STATE_IDLE;
|
p.rx_state = RX_STATE_IDLE;
|
||||||
@ -635,5 +754,6 @@ void usb_process (void) {
|
|||||||
usb_tx_process();
|
usb_tx_process();
|
||||||
} else {
|
} else {
|
||||||
usb_flush_packet();
|
usb_flush_packet();
|
||||||
|
sd_release_lock(SD_LOCK_USB);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
#define VERSION_MAJOR (2)
|
#define VERSION_MAJOR (2)
|
||||||
#define VERSION_MINOR (19)
|
#define VERSION_MINOR (20)
|
||||||
#define VERSION_REVISION (0)
|
#define VERSION_REVISION (2)
|
||||||
|
|
||||||
|
|
||||||
void version_firmware (uint32_t *version, uint32_t *revision) {
|
void version_firmware (uint32_t *version, uint32_t *revision) {
|
||||||
|
@ -62,6 +62,10 @@ static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *le
|
|||||||
*address = SRAM_FLASHRAM_ADDRESS;
|
*address = SRAM_FLASHRAM_ADDRESS;
|
||||||
*length = SRAM_1M_LENGTH;
|
*length = SRAM_1M_LENGTH;
|
||||||
break;
|
break;
|
||||||
|
case SAVE_TYPE_FLASHRAM_FAKE:
|
||||||
|
*address = SRAM_FLASHRAM_ADDRESS;
|
||||||
|
*length = FLASHRAM_LENGTH;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
*address = 0;
|
*address = 0;
|
||||||
*length = 0;
|
*length = 0;
|
||||||
@ -72,19 +76,20 @@ static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *le
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void writeback_save_to_sd (void) {
|
static void writeback_save_to_sd (void) {
|
||||||
save_type_t save;
|
|
||||||
uint32_t address;
|
uint32_t address;
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
|
|
||||||
save = writeback_get_address_length(&address, &length);
|
if (writeback_get_address_length(&address, &length) == SAVE_TYPE_NONE) {
|
||||||
if (save == SAVE_TYPE_NONE) {
|
|
||||||
writeback_disable();
|
writeback_disable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sd_error_t error = sd_optimize_sectors(address, p.sectors, (length / SD_SECTOR_SIZE), sd_write_sectors);
|
if (sd_get_lock(SD_LOCK_N64) != SD_OK) {
|
||||||
|
writeback_disable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (error != SD_OK) {
|
if (sd_optimize_sectors(address, p.sectors, (length / SD_SECTOR_SIZE), sd_write_sectors) != SD_OK) {
|
||||||
writeback_disable();
|
writeback_disable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
126
sw/deployer/Cargo.lock
generated
126
sw/deployer/Cargo.lock
generated
@ -124,7 +124,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -162,6 +162,26 @@ dependencies = [
|
|||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools",
|
||||||
|
"log",
|
||||||
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex",
|
||||||
|
"syn 2.0.77",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@ -218,13 +238,22 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.103"
|
version = "1.1.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410"
|
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -263,6 +292,17 @@ dependencies = [
|
|||||||
"windows-targets 0.52.5",
|
"windows-targets 0.52.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.8"
|
version = "4.5.8"
|
||||||
@ -303,7 +343,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -482,6 +522,12 @@ dependencies = [
|
|||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
@ -633,7 +679,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -755,6 +801,16 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libudev"
|
name = "libudev"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -922,7 +978,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1007,6 +1063,16 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prettyplease"
|
||||||
|
version = "0.2.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"syn 2.0.77",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
version = "0.5.20+deprecated"
|
version = "0.5.20+deprecated"
|
||||||
@ -1038,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1219,10 +1285,18 @@ dependencies = [
|
|||||||
"ordered-multimap",
|
"ordered-multimap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sc64deployer"
|
name = "sc64deployer"
|
||||||
version = "2.19.0"
|
version = "2.20.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
"cc",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap-num",
|
"clap-num",
|
||||||
@ -1266,14 +1340,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.6"
|
version = "0.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -1308,6 +1382,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -1357,9 +1437,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.68"
|
version = "2.0.77"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
|
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1402,7 +1482,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1430,18 +1510,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.6"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.14"
|
version = "0.22.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@ -1527,7 +1607,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1549,7 +1629,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.68",
|
"syn 2.0.77",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@ -1738,9 +1818,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.13"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "sc64deployer"
|
name = "sc64deployer"
|
||||||
version = "2.19.0"
|
version = "2.20.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Polprzewodnikowy"]
|
authors = ["Mateusz Faderewski (Polprzewodnikowy)"]
|
||||||
description = "SummerCart64 loader and control software"
|
description = "SummerCart64 loader and control software"
|
||||||
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
bindgen = "0.70.1"
|
||||||
|
cc = "1.1.18"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.8", features = ["derive"] }
|
clap = { version = "4.5.8", features = ["derive"] }
|
||||||
|
21
sw/deployer/build.rs
Normal file
21
sw/deployer/build.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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()
|
||||||
|
.file("../bootloader/src/fatfs/ff.c")
|
||||||
|
.file("../bootloader/src/fatfs/ffsystem.c")
|
||||||
|
.file("../bootloader/src/fatfs/ffunicode.c")
|
||||||
|
.compile("fatfs");
|
||||||
|
|
||||||
|
bindgen::Builder::default()
|
||||||
|
.header("../bootloader/src/fatfs/ff.h")
|
||||||
|
.blocklist_function("get_fattime")
|
||||||
|
.generate()
|
||||||
|
.expect("Unable to generate FatFs bindings")
|
||||||
|
.write_to_file(out_dir.join("fatfs_bindings.rs"))
|
||||||
|
.expect("Unable to write FatFs bindings");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -11,16 +11,46 @@ use panic_message::panic_message;
|
|||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{stdin, stdout, Read, Write},
|
io::{stdin, stdout, Read, Write},
|
||||||
|
panic,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
process,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
{panic, process},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[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 {
|
struct Cli {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Commands,
|
command: Commands,
|
||||||
@ -57,6 +87,12 @@ enum Commands {
|
|||||||
/// Dump data from arbitrary location in SC64 memory space
|
/// Dump data from arbitrary location in SC64 memory space
|
||||||
Dump(DumpArgs),
|
Dump(DumpArgs),
|
||||||
|
|
||||||
|
/// Perform operations on the SD card
|
||||||
|
SD {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: SDCommands,
|
||||||
|
},
|
||||||
|
|
||||||
/// Print information about connected SC64 device
|
/// Print information about connected SC64 device
|
||||||
Info,
|
Info,
|
||||||
|
|
||||||
@ -202,6 +238,71 @@ struct DumpArgs {
|
|||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum SDCommands {
|
||||||
|
/// List a directory on the SD card
|
||||||
|
#[command(name = "ls")]
|
||||||
|
List {
|
||||||
|
/// Path to the directory
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Display a file or directory status
|
||||||
|
#[command(name = "stat")]
|
||||||
|
Stat {
|
||||||
|
/// Path to the file or directory
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Move or rename a file or directory
|
||||||
|
#[command(name = "mv")]
|
||||||
|
Move {
|
||||||
|
/// Path to the current file or directory
|
||||||
|
src: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the new file or directory
|
||||||
|
dst: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Remove a file or empty directory
|
||||||
|
#[command(name = "rm")]
|
||||||
|
Delete {
|
||||||
|
/// Path to the file or directory
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Create a new directory
|
||||||
|
#[command(name = "mkdir")]
|
||||||
|
CreateDirectory {
|
||||||
|
/// Path to the directory
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Download a file to the PC
|
||||||
|
#[command(name = "download")]
|
||||||
|
Download {
|
||||||
|
/// Path to the file on the SD card
|
||||||
|
src: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the file on the PC
|
||||||
|
dst: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Upload a file to the SD card
|
||||||
|
#[command(name = "upload")]
|
||||||
|
Upload {
|
||||||
|
/// Path to the file on the PC
|
||||||
|
src: PathBuf,
|
||||||
|
|
||||||
|
/// Path to the file on the SD card
|
||||||
|
dst: Option<PathBuf>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Format the SD card
|
||||||
|
#[command(name = "mkfs")]
|
||||||
|
Format,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum SetCommands {
|
enum SetCommands {
|
||||||
/// Synchronize real time clock (RTC) on the SC64 with local system time
|
/// Synchronize real time clock (RTC) on the SC64 with local system time
|
||||||
@ -258,6 +359,7 @@ enum SaveType {
|
|||||||
SramBanked,
|
SramBanked,
|
||||||
Sram1m,
|
Sram1m,
|
||||||
Flashram,
|
Flashram,
|
||||||
|
FlashramFake,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<n64::SaveType> for SaveType {
|
impl From<n64::SaveType> for SaveType {
|
||||||
@ -284,6 +386,7 @@ impl From<SaveType> for sc64::SaveType {
|
|||||||
SaveType::SramBanked => Self::SramBanked,
|
SaveType::SramBanked => Self::SramBanked,
|
||||||
SaveType::Sram1m => Self::Sram1m,
|
SaveType::Sram1m => Self::Sram1m,
|
||||||
SaveType::Flashram => Self::Flashram,
|
SaveType::Flashram => Self::Flashram,
|
||||||
|
SaveType::FlashramFake => Self::FlashramFake,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,6 +421,9 @@ fn main() {
|
|||||||
panic::set_hook(Box::new(|_| {}));
|
panic::set_hook(Box::new(|_| {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
colored::control::set_virtual_terminal(true).ok();
|
||||||
|
|
||||||
match panic::catch_unwind(|| handle_command(&cli.command, cli.port, cli.remote)) {
|
match panic::catch_unwind(|| handle_command(&cli.command, cli.port, cli.remote)) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(payload) => {
|
Err(payload) => {
|
||||||
@ -340,6 +446,7 @@ fn handle_command(command: &Commands, port: Option<String>, remote: Option<Strin
|
|||||||
Commands::_64DD(args) => handle_64dd_command(connection, args),
|
Commands::_64DD(args) => handle_64dd_command(connection, args),
|
||||||
Commands::Debug(args) => handle_debug_command(connection, args),
|
Commands::Debug(args) => handle_debug_command(connection, args),
|
||||||
Commands::Dump(args) => handle_dump_command(connection, args),
|
Commands::Dump(args) => handle_dump_command(connection, args),
|
||||||
|
Commands::SD { command } => handle_sd_command(connection, command),
|
||||||
Commands::Info => handle_info_command(connection),
|
Commands::Info => handle_info_command(connection),
|
||||||
Commands::Reset => handle_reset_command(connection),
|
Commands::Reset => handle_reset_command(connection),
|
||||||
Commands::Set { command } => handle_set_command(connection, command),
|
Commands::Set { command } => handle_set_command(connection, command),
|
||||||
@ -423,7 +530,16 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<()
|
|||||||
sc64.set_tv_type(tv_type)?;
|
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)? {
|
if args.reboot && !sc64.try_notify_via_aux(sc64::AuxMessage::Reboot)? {
|
||||||
println!(
|
println!(
|
||||||
@ -538,7 +654,16 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
|
|||||||
sc64.set_tv_type(tv_type)?;
|
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 {
|
if args.disk.len() == 0 {
|
||||||
let dd_mode = sc64::DdMode::DdIpl;
|
let dd_mode = sc64::DdMode::DdIpl;
|
||||||
@ -767,16 +892,167 @@ fn handle_dump_command(connection: Connection, args: &DumpArgs) -> Result<(), sc
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_sd_command(connection: Connection, command: &SDCommands) -> Result<(), sc64::Error> {
|
||||||
|
let mut sc64 = init_sc64(connection, true)?;
|
||||||
|
|
||||||
|
match sc64.init_sd_card()? {
|
||||||
|
sc64::SdCardResult::OK => {}
|
||||||
|
error => {
|
||||||
|
return Err(sc64::Error::new(
|
||||||
|
format!("Couldn't init the SD card: {error}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc64.is_console_powered_on()? {
|
||||||
|
println!(
|
||||||
|
"{}\n{}\n{}",
|
||||||
|
"========== [WARNING] ==========".bold().bright_yellow(),
|
||||||
|
"The console is powered on. To avoid potential data corruption it's strongly"
|
||||||
|
.bright_yellow(),
|
||||||
|
"recommended to access the SD card only when the console is powered off."
|
||||||
|
.bright_yellow()
|
||||||
|
);
|
||||||
|
let answer = prompt(format!("{}", "Continue anyways? [y/N] ".bold()));
|
||||||
|
if answer.to_ascii_lowercase() != "y" {
|
||||||
|
sc64.deinit_sd_card()?;
|
||||||
|
println!("{}", "SD card access aborted".red());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc64.reset_state()?;
|
||||||
|
|
||||||
|
let mut ff = sc64::ff::FatFs::new(sc64)?;
|
||||||
|
|
||||||
|
match command {
|
||||||
|
SDCommands::List { path } => {
|
||||||
|
for item in ff.list(path.clone().unwrap_or(PathBuf::from("/")))? {
|
||||||
|
let sc64::ff::Entry {
|
||||||
|
info,
|
||||||
|
datetime,
|
||||||
|
name,
|
||||||
|
} = item;
|
||||||
|
let name = match info {
|
||||||
|
sc64::ff::EntryInfo::Directory => ("/".to_owned() + &name).bright_blue(),
|
||||||
|
sc64::ff::EntryInfo::File { size: _ } => name.bright_green(),
|
||||||
|
};
|
||||||
|
println!("{info} {datetime} | {}", name.bold());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDCommands::Stat { path } => {
|
||||||
|
let sc64::ff::Entry {
|
||||||
|
info,
|
||||||
|
datetime,
|
||||||
|
name,
|
||||||
|
} = ff.stat(path)?;
|
||||||
|
let name = match info {
|
||||||
|
sc64::ff::EntryInfo::Directory => ("/".to_owned() + &name).bright_blue(),
|
||||||
|
sc64::ff::EntryInfo::File { size: _ } => name.bright_green(),
|
||||||
|
};
|
||||||
|
println!("{info} {datetime} | {}", name.bold());
|
||||||
|
}
|
||||||
|
SDCommands::Move { src, dst } => {
|
||||||
|
ff.rename(src, dst)?;
|
||||||
|
println!(
|
||||||
|
"Successfully moved {} to {}",
|
||||||
|
src.to_str().unwrap_or_default().bright_green(),
|
||||||
|
dst.to_str().unwrap_or_default().bright_green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SDCommands::Delete { path } => {
|
||||||
|
ff.delete(path)?;
|
||||||
|
println!(
|
||||||
|
"Successfully deleted {}",
|
||||||
|
path.to_str().unwrap_or_default().bright_green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SDCommands::CreateDirectory { path } => {
|
||||||
|
ff.mkdir(path)?;
|
||||||
|
println!(
|
||||||
|
"Successfully created {}",
|
||||||
|
path.to_str().unwrap_or_default().bright_green()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
SDCommands::Download { src, dst } => {
|
||||||
|
let dst = &dst.clone().unwrap_or(
|
||||||
|
src.file_name()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.ok_or(sc64::ff::Error::InvalidParameter)?,
|
||||||
|
);
|
||||||
|
let mut src_file = ff.open(src)?;
|
||||||
|
let mut dst_file = std::fs::File::create(dst)?;
|
||||||
|
let mut buffer = vec![0; 128 * 1024];
|
||||||
|
log_wait(
|
||||||
|
format!(
|
||||||
|
"Downloading {} to {}",
|
||||||
|
src.to_str().unwrap_or_default().bright_green(),
|
||||||
|
dst.to_str().unwrap_or_default().bright_green()
|
||||||
|
),
|
||||||
|
|| loop {
|
||||||
|
match src_file.read(&mut buffer)? {
|
||||||
|
0 => return Ok(()),
|
||||||
|
bytes => {
|
||||||
|
if let Err(e) = dst_file.write_all(&buffer[0..bytes]) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
SDCommands::Upload { src, dst } => {
|
||||||
|
let dst = &dst.clone().unwrap_or(
|
||||||
|
src.file_name()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.ok_or(sc64::ff::Error::InvalidParameter)?,
|
||||||
|
);
|
||||||
|
let mut src_file = std::fs::File::open(src)?;
|
||||||
|
let mut dst_file = ff.create(dst)?;
|
||||||
|
let mut buffer = vec![0; 128 * 1024];
|
||||||
|
log_wait(
|
||||||
|
format!(
|
||||||
|
"Uploading {} to {}",
|
||||||
|
src.to_str().unwrap_or_default().bright_green(),
|
||||||
|
dst.to_str().unwrap_or_default().bright_green()
|
||||||
|
),
|
||||||
|
|| loop {
|
||||||
|
match src_file.read(&mut buffer)? {
|
||||||
|
0 => return Ok(()),
|
||||||
|
bytes => {
|
||||||
|
if let Err(e) = dst_file.write_all(&buffer[0..bytes]) {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
SDCommands::Format => {
|
||||||
|
let answer = prompt(format!(
|
||||||
|
"{}",
|
||||||
|
"Do you really want to format the SD card? [y/N] ".bold()
|
||||||
|
));
|
||||||
|
if answer.to_ascii_lowercase() != "y" {
|
||||||
|
println!("{}", "Format operation aborted".red());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
log_wait(format!("Formatting the SD card"), || ff.mkfs())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||||
let mut sc64 = init_sc64(connection, true)?;
|
let mut sc64 = init_sc64(connection, true)?;
|
||||||
|
|
||||||
let (major, minor, revision) = sc64.check_firmware_version()?;
|
let (major, minor, revision) = sc64.check_firmware_version()?;
|
||||||
let state = sc64.get_device_state()?;
|
let state = sc64.get_device_state()?;
|
||||||
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
|
|
||||||
|
|
||||||
println!("{}", "SummerCart64 state information:".bold());
|
println!("{}", "SummerCart64 state information:".bold());
|
||||||
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
||||||
println!(" RTC datetime: {}", datetime);
|
println!(" RTC datetime: {}", state.datetime);
|
||||||
println!(" Boot mode: {}", state.boot_mode);
|
println!(" Boot mode: {}", state.boot_mode);
|
||||||
println!(" Save type: {}", state.save_type);
|
println!(" Save type: {}", state.save_type);
|
||||||
println!(" CIC seed: {}", state.cic_seed);
|
println!(" CIC seed: {}", state.cic_seed);
|
||||||
@ -793,6 +1069,7 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
|||||||
println!(" Button state: {}", state.button_state);
|
println!(" Button state: {}", state.button_state);
|
||||||
println!(" LED blink: {}", state.led_enable);
|
println!(" LED blink: {}", state.led_enable);
|
||||||
println!(" IS-Viewer 64: {}", state.isviewer);
|
println!(" IS-Viewer 64: {}", state.isviewer);
|
||||||
|
println!(" SD card status: {}", state.sd_card_status);
|
||||||
println!("{}", "SummerCart64 diagnostic information:".bold());
|
println!("{}", "SummerCart64 diagnostic information:".bold());
|
||||||
println!(" PI I/O access: {}", state.fpga_debug_data.pi_io_access);
|
println!(" PI I/O access: {}", state.fpga_debug_data.pi_io_access);
|
||||||
println!(
|
println!(
|
||||||
@ -921,19 +1198,30 @@ fn handle_firmware_command(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_test_command(connection: Connection) -> Result<(), sc64::Error> {
|
fn handle_test_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||||
let mut sc64 = init_sc64(connection, false)?;
|
let mut sc64 = init_sc64(connection, true)?;
|
||||||
|
|
||||||
|
sc64.reset_state()?;
|
||||||
|
|
||||||
println!("{}: USB", "[SC64 Tests]".bold());
|
println!("{}: USB", "[SC64 Tests]".bold());
|
||||||
|
|
||||||
print!(" Performing USB read speed test... ");
|
print!(" Performing USB read speed test... ");
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
let read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
|
let usb_read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
|
||||||
println!("{}", format!("{read_speed:.2} MiB/s",).bright_green());
|
println!("{}", format!("{usb_read_speed:.2} MiB/s",).bright_green());
|
||||||
|
|
||||||
print!(" Performing USB write speed test... ");
|
print!(" Performing USB write speed test... ");
|
||||||
stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
let write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
|
let usb_write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
|
||||||
println!("{}", format!("{write_speed:.2} MiB/s",).bright_green());
|
println!("{}", format!("{usb_write_speed:.2} MiB/s",).bright_green());
|
||||||
|
|
||||||
|
println!("{}: SD card", "[SC64 Tests]".bold());
|
||||||
|
|
||||||
|
print!(" Performing SD card read speed test... ");
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
match sc64.test_sd_card() {
|
||||||
|
Ok(sd_read_speed) => println!("{}", format!("{sd_read_speed:.2} MiB/s",).bright_green()),
|
||||||
|
Err(result) => println!("{}", format!("error! {result}").bright_red()),
|
||||||
|
}
|
||||||
|
|
||||||
println!("{}: SDRAM (pattern)", "[SC64 Tests]".bold());
|
println!("{}: SDRAM (pattern)", "[SC64 Tests]".bold());
|
||||||
|
|
||||||
|
@ -110,35 +110,35 @@ fn calculate_ipl3_checksum(ipl3: &[u8], seed: u8) -> Result<u64, Error> {
|
|||||||
Ok(checksum)
|
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 {
|
if let Some(seed) = custom_seed {
|
||||||
Ok((seed, calculate_ipl3_checksum(ipl3, seed)?))
|
return Ok((seed, calculate_ipl3_checksum(ipl3, seed)?, true));
|
||||||
} 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
@ -26,3 +26,9 @@ impl From<std::io::Error> for Error {
|
|||||||
Error::new(format!("IO error: {}", value).as_str())
|
Error::new(format!("IO error: {}", value).as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<super::ff::Error> for Error {
|
||||||
|
fn from(value: super::ff::Error) -> Self {
|
||||||
|
Error::new(format!("FatFs error: {}", value).as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
637
sw/deployer/src/sc64/ff.rs
Normal file
637
sw/deployer/src/sc64/ff.rs
Normal file
@ -0,0 +1,637 @@
|
|||||||
|
use super::{SdCardResult, SC64, SD_CARD_SECTOR_SIZE};
|
||||||
|
use chrono::{Datelike, Timelike};
|
||||||
|
mod fatfs {
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/fatfs_bindings.rs"));
|
||||||
|
|
||||||
|
pub type DSTATUS = BYTE;
|
||||||
|
pub const DSTATUS_STA_OK: DSTATUS = 0;
|
||||||
|
pub const DSTATUS_STA_NOINIT: DSTATUS = 1 << 0;
|
||||||
|
pub const DSTATUS_STA_NODISK: DSTATUS = 1 << 2;
|
||||||
|
pub const DSTATUS_STA_PROTECT: DSTATUS = 1 << 3;
|
||||||
|
|
||||||
|
pub type DRESULT = std::os::raw::c_uint;
|
||||||
|
pub const DRESULT_RES_OK: DRESULT = 0;
|
||||||
|
pub const DRESULT_RES_ERROR: DRESULT = 1;
|
||||||
|
pub const DRESULT_RES_WRPRT: DRESULT = 2;
|
||||||
|
pub const DRESULT_RES_NOTRDY: DRESULT = 3;
|
||||||
|
pub const DRESULT_RES_PARERR: DRESULT = 4;
|
||||||
|
|
||||||
|
pub const CTRL_SYNC: BYTE = 0;
|
||||||
|
pub const GET_SECTOR_COUNT: BYTE = 1;
|
||||||
|
pub const GET_SECTOR_SIZE: BYTE = 2;
|
||||||
|
pub const GET_BLOCK_SIZE: BYTE = 3;
|
||||||
|
pub const CTRL_TRIM: BYTE = 4;
|
||||||
|
|
||||||
|
pub enum Error {
|
||||||
|
DiskErr,
|
||||||
|
IntErr,
|
||||||
|
NotReady,
|
||||||
|
NoFile,
|
||||||
|
NoPath,
|
||||||
|
InvalidName,
|
||||||
|
Denied,
|
||||||
|
Exist,
|
||||||
|
InvalidObject,
|
||||||
|
WriteProtected,
|
||||||
|
InvalidDrive,
|
||||||
|
NotEnabled,
|
||||||
|
NoFilesystem,
|
||||||
|
MkfsAborted,
|
||||||
|
Timeout,
|
||||||
|
Locked,
|
||||||
|
NotEnoughCore,
|
||||||
|
TooManyOpenFiles,
|
||||||
|
InvalidParameter,
|
||||||
|
DriverInstalled,
|
||||||
|
DriverNotInstalled,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
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 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 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 => "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 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",
|
||||||
|
Self::DriverNotInstalled => "FatFs driver is not installed",
|
||||||
|
Self::Unknown => "Unknown error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<FRESULT> for Error {
|
||||||
|
fn from(value: FRESULT) -> Self {
|
||||||
|
match value {
|
||||||
|
FRESULT_FR_DISK_ERR => Error::DiskErr,
|
||||||
|
FRESULT_FR_INT_ERR => Error::IntErr,
|
||||||
|
FRESULT_FR_NOT_READY => Error::NotReady,
|
||||||
|
FRESULT_FR_NO_FILE => Error::NoFile,
|
||||||
|
FRESULT_FR_NO_PATH => Error::NoPath,
|
||||||
|
FRESULT_FR_INVALID_NAME => Error::InvalidName,
|
||||||
|
FRESULT_FR_DENIED => Error::Denied,
|
||||||
|
FRESULT_FR_EXIST => Error::Exist,
|
||||||
|
FRESULT_FR_INVALID_OBJECT => Error::InvalidObject,
|
||||||
|
FRESULT_FR_WRITE_PROTECTED => Error::WriteProtected,
|
||||||
|
FRESULT_FR_INVALID_DRIVE => Error::InvalidDrive,
|
||||||
|
FRESULT_FR_NOT_ENABLED => Error::NotEnabled,
|
||||||
|
FRESULT_FR_NO_FILESYSTEM => Error::NoFilesystem,
|
||||||
|
FRESULT_FR_MKFS_ABORTED => Error::MkfsAborted,
|
||||||
|
FRESULT_FR_TIMEOUT => Error::Timeout,
|
||||||
|
FRESULT_FR_LOCKED => Error::Locked,
|
||||||
|
FRESULT_FR_NOT_ENOUGH_CORE => Error::NotEnoughCore,
|
||||||
|
FRESULT_FR_TOO_MANY_OPEN_FILES => Error::TooManyOpenFiles,
|
||||||
|
FRESULT_FR_INVALID_PARAMETER => Error::InvalidParameter,
|
||||||
|
_ => Error::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path<P: AsRef<std::path::Path>>(path: P) -> Result<std::ffi::CString, Error> {
|
||||||
|
match path.as_ref().to_str() {
|
||||||
|
Some(path) => Ok(std::ffi::CString::new(path).map_err(|_| Error::InvalidParameter)?),
|
||||||
|
None => Err(Error::InvalidParameter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Error = fatfs::Error;
|
||||||
|
|
||||||
|
static mut DRIVER: std::sync::Mutex<Option<Box<dyn FFDriver>>> = std::sync::Mutex::new(None);
|
||||||
|
|
||||||
|
fn install_driver(driver: impl FFDriver + 'static) -> Result<(), Error> {
|
||||||
|
let mut d = unsafe { DRIVER.lock().unwrap() };
|
||||||
|
if d.is_some() {
|
||||||
|
return Err(Error::DriverInstalled);
|
||||||
|
}
|
||||||
|
d.replace(Box::new(driver));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uninstall_driver() -> Result<(), Error> {
|
||||||
|
let mut d = unsafe { DRIVER.lock().unwrap() };
|
||||||
|
if d.is_none() {
|
||||||
|
return Err(Error::DriverNotInstalled);
|
||||||
|
}
|
||||||
|
d.take().unwrap().deinit();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FatFs {
|
||||||
|
fs: Box<fatfs::FATFS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FatFs {
|
||||||
|
pub fn new(driver: impl FFDriver + 'static) -> Result<Self, Error> {
|
||||||
|
install_driver(driver)?;
|
||||||
|
let mut ff = Self {
|
||||||
|
fs: Box::new(unsafe { std::mem::zeroed() }),
|
||||||
|
};
|
||||||
|
ff.mount(false)?;
|
||||||
|
Ok(ff)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount(&mut self, force: bool) -> Result<(), Error> {
|
||||||
|
let opt = if force { 1 } else { 0 };
|
||||||
|
match unsafe { fatfs::f_mount(&mut *self.fs, fatfs::path("")?.as_ptr(), opt) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmount(&mut self) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_mount(std::ptr::null_mut(), fatfs::path("")?.as_ptr(), 0) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<File, Error> {
|
||||||
|
File::open(
|
||||||
|
path,
|
||||||
|
fatfs::FA_OPEN_EXISTING | fatfs::FA_READ | fatfs::FA_WRITE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<File, Error> {
|
||||||
|
File::open(
|
||||||
|
path,
|
||||||
|
fatfs::FA_CREATE_ALWAYS | fatfs::FA_READ | fatfs::FA_WRITE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stat<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Entry, Error> {
|
||||||
|
let mut fno = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_stat(fatfs::path(path)?.as_ptr(), &mut fno) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(fno.into()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opendir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Directory, Error> {
|
||||||
|
Directory::open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Vec<Entry>, Error> {
|
||||||
|
let mut dir = self.opendir(path)?;
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
|
||||||
|
while let Some(entry) = dir.read()? {
|
||||||
|
list.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort_by_key(|k| (k.info, k.name.to_lowercase()));
|
||||||
|
|
||||||
|
Ok(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mkdir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_mkdir(fatfs::path(path)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_unlink(fatfs::path(path)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rename<P: AsRef<std::path::Path>>(&mut self, old: P, new: P) -> Result<(), Error> {
|
||||||
|
match unsafe { fatfs::f_rename(fatfs::path(old)?.as_ptr(), fatfs::path(new)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mkfs(&mut self) -> Result<(), Error> {
|
||||||
|
let mut work = [0u8; 16 * 1024];
|
||||||
|
match unsafe {
|
||||||
|
fatfs::f_mkfs(
|
||||||
|
fatfs::path("")?.as_ptr(),
|
||||||
|
std::ptr::null(),
|
||||||
|
work.as_mut_ptr().cast(),
|
||||||
|
size_of_val(&work) as u32,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for FatFs {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.unmount().ok();
|
||||||
|
uninstall_driver().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum IOCtl {
|
||||||
|
Sync,
|
||||||
|
GetSectorCount(fatfs::LBA_t),
|
||||||
|
GetSectorSize(fatfs::WORD),
|
||||||
|
GetBlockSize(fatfs::DWORD),
|
||||||
|
Trim,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FFDriver {
|
||||||
|
fn init(&mut self) -> fatfs::DSTATUS;
|
||||||
|
fn deinit(&mut self);
|
||||||
|
fn status(&mut self) -> fatfs::DSTATUS;
|
||||||
|
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT;
|
||||||
|
fn write(&mut self, buffer: &[u8], sector: fatfs::LBA_t) -> fatfs::DRESULT;
|
||||||
|
fn ioctl(&mut self, ioctl: &mut IOCtl) -> fatfs::DRESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FFDriver for SC64 {
|
||||||
|
fn init(&mut self) -> fatfs::DSTATUS {
|
||||||
|
if let Ok(SdCardResult::OK) = self.init_sd_card() {
|
||||||
|
return fatfs::DSTATUS_STA_OK;
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(&mut self) {
|
||||||
|
self.deinit_sd_card().ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&mut self) -> fatfs::DSTATUS {
|
||||||
|
if let Ok(status) = self.get_sd_card_status() {
|
||||||
|
if status.card_initialized {
|
||||||
|
return fatfs::DSTATUS_STA_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
|
||||||
|
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 (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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ioctl(&mut self, ioctl: &mut IOCtl) -> fatfs::DRESULT {
|
||||||
|
match ioctl {
|
||||||
|
IOCtl::Sync => {}
|
||||||
|
IOCtl::GetSectorCount(_) => {
|
||||||
|
match self.get_sd_card_info() {
|
||||||
|
Ok(info) => *ioctl = IOCtl::GetSectorCount(info.sectors as fatfs::LBA_t),
|
||||||
|
Err(_) => return fatfs::DRESULT_RES_ERROR,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
IOCtl::GetSectorSize(_) => {
|
||||||
|
*ioctl = IOCtl::GetSectorSize(SD_CARD_SECTOR_SIZE as fatfs::WORD)
|
||||||
|
}
|
||||||
|
IOCtl::GetBlockSize(_) => {
|
||||||
|
*ioctl = IOCtl::GetBlockSize(1);
|
||||||
|
}
|
||||||
|
IOCtl::Trim => {}
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_status(pdrv: fatfs::BYTE) -> fatfs::DSTATUS {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DSTATUS_STA_NOINIT;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.status();
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_initialize(pdrv: fatfs::BYTE) -> fatfs::DSTATUS {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DSTATUS_STA_NOINIT;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.init();
|
||||||
|
}
|
||||||
|
fatfs::DSTATUS_STA_NOINIT
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_read(
|
||||||
|
pdrv: fatfs::BYTE,
|
||||||
|
buff: *mut fatfs::BYTE,
|
||||||
|
sector: fatfs::LBA_t,
|
||||||
|
count: fatfs::UINT,
|
||||||
|
) -> fatfs::DRESULT {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DRESULT_RES_PARERR;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.read(
|
||||||
|
&mut *std::ptr::slice_from_raw_parts_mut(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
|
||||||
|
sector,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_NOTRDY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_write(
|
||||||
|
pdrv: fatfs::BYTE,
|
||||||
|
buff: *mut fatfs::BYTE,
|
||||||
|
sector: fatfs::LBA_t,
|
||||||
|
count: fatfs::UINT,
|
||||||
|
) -> fatfs::DRESULT {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DRESULT_RES_PARERR;
|
||||||
|
}
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
return d.write(
|
||||||
|
&*std::ptr::slice_from_raw_parts(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
|
||||||
|
sector,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_NOTRDY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn disk_ioctl(
|
||||||
|
pdrv: fatfs::BYTE,
|
||||||
|
cmd: fatfs::BYTE,
|
||||||
|
buff: *mut std::os::raw::c_void,
|
||||||
|
) -> fatfs::DRESULT {
|
||||||
|
if pdrv != 0 {
|
||||||
|
return fatfs::DRESULT_RES_PARERR;
|
||||||
|
}
|
||||||
|
let mut ioctl = match cmd {
|
||||||
|
fatfs::CTRL_SYNC => IOCtl::Sync,
|
||||||
|
fatfs::GET_SECTOR_COUNT => IOCtl::GetSectorCount(0),
|
||||||
|
fatfs::GET_SECTOR_SIZE => IOCtl::GetSectorSize(0),
|
||||||
|
fatfs::GET_BLOCK_SIZE => IOCtl::GetBlockSize(0),
|
||||||
|
fatfs::CTRL_TRIM => IOCtl::Trim,
|
||||||
|
_ => return fatfs::DRESULT_RES_PARERR,
|
||||||
|
};
|
||||||
|
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||||
|
let result = d.ioctl(&mut ioctl);
|
||||||
|
if result == fatfs::DRESULT_RES_OK {
|
||||||
|
match ioctl {
|
||||||
|
IOCtl::GetSectorCount(count) => {
|
||||||
|
buff.copy_from(std::ptr::addr_of!(count).cast(), size_of_val(&count))
|
||||||
|
}
|
||||||
|
IOCtl::GetSectorSize(size) => {
|
||||||
|
buff.copy_from(std::ptr::addr_of!(size).cast(), size_of_val(&size))
|
||||||
|
}
|
||||||
|
IOCtl::GetBlockSize(size) => {
|
||||||
|
buff.copy_from(std::ptr::addr_of!(size).cast(), size_of_val(&size))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
fatfs::DRESULT_RES_NOTRDY
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn get_fattime() -> fatfs::DWORD {
|
||||||
|
let now = chrono::Local::now();
|
||||||
|
let year = now.year() as u32;
|
||||||
|
let month = now.month();
|
||||||
|
let day = now.day();
|
||||||
|
let hour = now.hour();
|
||||||
|
let minute = now.minute();
|
||||||
|
let second = now.second();
|
||||||
|
((year - 1980) << 25)
|
||||||
|
| (month << 21)
|
||||||
|
| (day << 16)
|
||||||
|
| (hour << 11)
|
||||||
|
| (minute << 5)
|
||||||
|
| (second << 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct Entry {
|
||||||
|
pub info: EntryInfo,
|
||||||
|
pub name: String,
|
||||||
|
pub datetime: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Ord)]
|
||||||
|
pub enum EntryInfo {
|
||||||
|
Directory,
|
||||||
|
File { size: u64 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for EntryInfo {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(match (self, other) {
|
||||||
|
(EntryInfo::Directory, EntryInfo::File { size: _ }) => std::cmp::Ordering::Less,
|
||||||
|
(EntryInfo::File { size: _ }, EntryInfo::Directory) => std::cmp::Ordering::Greater,
|
||||||
|
_ => std::cmp::Ordering::Equal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EntryInfo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Ok(match self {
|
||||||
|
EntryInfo::Directory => f.write_fmt(format_args!("d ----",))?,
|
||||||
|
EntryInfo::File { size } => {
|
||||||
|
const UNITS: [&str; 4] = ["K", "M", "G", "T"];
|
||||||
|
let mut reduced_size = *size as f64;
|
||||||
|
let mut reduced_unit = "B";
|
||||||
|
for unit in UNITS {
|
||||||
|
if reduced_size >= 1000.0 {
|
||||||
|
reduced_size /= 1024.0;
|
||||||
|
reduced_unit = unit;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let formatted_size = if *size >= 1000 && reduced_size <= 9.9 {
|
||||||
|
format!("{:>1.1}{}", reduced_size, reduced_unit)
|
||||||
|
} else {
|
||||||
|
format!("{:>3.0}{}", reduced_size, reduced_unit)
|
||||||
|
};
|
||||||
|
f.write_fmt(format_args!("f {formatted_size}"))?
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<fatfs::FILINFO> for Entry {
|
||||||
|
fn from(value: fatfs::FILINFO) -> Self {
|
||||||
|
let name = unsafe { std::ffi::CStr::from_ptr(value.fname.as_ptr()) }
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string();
|
||||||
|
let datetime = chrono::NaiveDateTime::new(
|
||||||
|
chrono::NaiveDate::from_ymd_opt(
|
||||||
|
(1980 + ((value.fdate >> 9) & 0x7F)).into(),
|
||||||
|
((value.fdate >> 5) & 0xF).into(),
|
||||||
|
(value.fdate & 0x1F).into(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
chrono::NaiveTime::from_hms_opt(
|
||||||
|
((value.ftime >> 11) & 0x1F).into(),
|
||||||
|
((value.ftime >> 5) & 0x3F).into(),
|
||||||
|
((value.ftime & 0x1F) * 2).into(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
let info = if (value.fattrib as u32 & fatfs::AM_DIR) == 0 {
|
||||||
|
EntryInfo::File { size: value.fsize }
|
||||||
|
} else {
|
||||||
|
EntryInfo::Directory
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
datetime,
|
||||||
|
info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Directory {
|
||||||
|
dir: fatfs::DIR,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Directory {
|
||||||
|
fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
|
||||||
|
let mut dir = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_opendir(&mut dir, fatfs::path(path)?.as_ptr()) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(Self { dir }),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self) -> Result<Option<Entry>, Error> {
|
||||||
|
let mut fno = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_readdir(&mut self.dir, &mut fno) } {
|
||||||
|
fatfs::FRESULT_FR_OK => {
|
||||||
|
if fno.fname[0] == 0 {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(fno.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Directory {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { fatfs::f_closedir(&mut self.dir) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct File {
|
||||||
|
fil: fatfs::FIL,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl File {
|
||||||
|
fn open<P: AsRef<std::path::Path>>(path: P, mode: u32) -> Result<File, Error> {
|
||||||
|
let mut fil = unsafe { std::mem::zeroed() };
|
||||||
|
match unsafe { fatfs::f_open(&mut fil, fatfs::path(path)?.as_ptr(), mode as u8) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(File { fil }),
|
||||||
|
error => Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Read for File {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
let mut bytes_read = 0;
|
||||||
|
match unsafe {
|
||||||
|
fatfs::f_read(
|
||||||
|
&mut self.fil,
|
||||||
|
buf.as_mut_ptr().cast(),
|
||||||
|
buf.len() as fatfs::UINT,
|
||||||
|
&mut bytes_read,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(bytes_read as usize),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Write for File {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let mut bytes_written = 0;
|
||||||
|
match unsafe {
|
||||||
|
fatfs::f_write(
|
||||||
|
&mut self.fil,
|
||||||
|
buf.as_ptr().cast(),
|
||||||
|
buf.len() as fatfs::UINT,
|
||||||
|
&mut bytes_written,
|
||||||
|
)
|
||||||
|
} {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(bytes_written as usize),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
match unsafe { fatfs::f_sync(&mut self.fil) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(()),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Seek for File {
|
||||||
|
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||||
|
let ofs = match pos {
|
||||||
|
std::io::SeekFrom::Current(offset) => self.fil.fptr.saturating_add_signed(offset),
|
||||||
|
std::io::SeekFrom::Start(offset) => offset,
|
||||||
|
std::io::SeekFrom::End(offset) => self.fil.obj.objsize.saturating_add_signed(offset),
|
||||||
|
};
|
||||||
|
match unsafe { fatfs::f_lseek(&mut self.fil, ofs) } {
|
||||||
|
fatfs::FRESULT_FR_OK => Ok(self.fil.fptr),
|
||||||
|
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for File {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { fatfs::f_close(&mut self.fil) };
|
||||||
|
}
|
||||||
|
}
|
@ -73,8 +73,8 @@ impl Wrapper {
|
|||||||
let result = if devices > 0 {
|
let result = if devices > 0 {
|
||||||
let mut list: Vec<DeviceInfo> = vec![];
|
let mut list: Vec<DeviceInfo> = vec![];
|
||||||
|
|
||||||
let mut description = [0i8; 128];
|
let mut description = [std::ffi::c_char::from(0); 128];
|
||||||
let mut serial = [0i8; 128];
|
let mut serial = [std::ffi::c_char::from(0); 128];
|
||||||
|
|
||||||
let mut device = device_list;
|
let mut device = device_list;
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
mod cic;
|
mod cic;
|
||||||
mod error;
|
mod error;
|
||||||
|
pub mod ff;
|
||||||
pub mod firmware;
|
pub mod firmware;
|
||||||
mod ftdi;
|
mod ftdi;
|
||||||
mod link;
|
mod link;
|
||||||
@ -13,10 +14,11 @@ pub use self::{
|
|||||||
link::list_local_devices,
|
link::list_local_devices,
|
||||||
server::ServerEvent,
|
server::ServerEvent,
|
||||||
types::{
|
types::{
|
||||||
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState,
|
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, CicStep, DataPacket, DdDiskState,
|
||||||
DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
|
DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
|
||||||
FpgaDebugData, ISViewer, MemoryTestPattern, MemoryTestPatternResult, SaveType,
|
FpgaDebugData, ISViewer, MemoryTestPattern, MemoryTestPatternResult, SaveType,
|
||||||
SaveWriteback, SpeedTestDirection, Switch, TvType,
|
SaveWriteback, SdCardInfo, SdCardOpPacket, SdCardResult, SdCardStatus, SpeedTestDirection,
|
||||||
|
Switch, TvType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,7 +27,8 @@ use self::{
|
|||||||
link::Link,
|
link::Link,
|
||||||
time::{convert_from_datetime, convert_to_datetime},
|
time::{convert_from_datetime, convert_to_datetime},
|
||||||
types::{
|
types::{
|
||||||
get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus,
|
get_config, get_setting, Config, ConfigId, FirmwareStatus, SdCardOp, Setting, SettingId,
|
||||||
|
UpdateStatus,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
@ -58,6 +61,7 @@ pub struct DeviceState {
|
|||||||
pub button_mode: ButtonMode,
|
pub button_mode: ButtonMode,
|
||||||
pub rom_extended_enable: Switch,
|
pub rom_extended_enable: Switch,
|
||||||
pub led_enable: Switch,
|
pub led_enable: Switch,
|
||||||
|
pub sd_card_status: SdCardStatus,
|
||||||
pub datetime: NaiveDateTime,
|
pub datetime: NaiveDateTime,
|
||||||
pub fpga_debug_data: FpgaDebugData,
|
pub fpga_debug_data: FpgaDebugData,
|
||||||
pub diagnostic_data: DiagnosticData,
|
pub diagnostic_data: DiagnosticData,
|
||||||
@ -66,7 +70,7 @@ pub struct DeviceState {
|
|||||||
const SC64_V2_IDENTIFIER: &[u8; 4] = b"SCv2";
|
const SC64_V2_IDENTIFIER: &[u8; 4] = b"SCv2";
|
||||||
|
|
||||||
const SUPPORTED_MAJOR_VERSION: u16 = 2;
|
const SUPPORTED_MAJOR_VERSION: u16 = 2;
|
||||||
const SUPPORTED_MINOR_VERSION: u16 = 19;
|
const SUPPORTED_MINOR_VERSION: u16 = 20;
|
||||||
|
|
||||||
const SDRAM_ADDRESS: u32 = 0x0000_0000;
|
const SDRAM_ADDRESS: u32 = 0x0000_0000;
|
||||||
const SDRAM_LENGTH: usize = 64 * 1024 * 1024;
|
const SDRAM_LENGTH: usize = 64 * 1024 * 1024;
|
||||||
@ -94,13 +98,18 @@ const SRAM_1M_LENGTH: usize = 128 * 1024;
|
|||||||
|
|
||||||
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
|
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
|
||||||
|
|
||||||
|
const SD_CARD_BUFFER_ADDRESS: u32 = 0x03FE_0000; // Arbitrary offset in SDRAM memory
|
||||||
|
const SD_CARD_BUFFER_LENGTH: usize = 128 * 1024; // Arbitrary length in SDRAM memory
|
||||||
|
|
||||||
|
pub const SD_CARD_SECTOR_SIZE: usize = 512;
|
||||||
|
|
||||||
const FIRMWARE_ADDRESS_SDRAM: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
|
const FIRMWARE_ADDRESS_SDRAM: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
|
||||||
const FIRMWARE_ADDRESS_FLASH: u32 = 0x0410_0000; // Arbitrary offset in Flash memory
|
const FIRMWARE_ADDRESS_FLASH: u32 = 0x0410_0000; // Arbitrary offset in Flash memory
|
||||||
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
|
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
|
||||||
|
|
||||||
const ISV_BUFFER_LENGTH: usize = 64 * 1024;
|
const ISV_BUFFER_LENGTH: usize = 64 * 1024;
|
||||||
|
|
||||||
pub const MEMORY_LENGTH: usize = 0x0500_2980;
|
pub const MEMORY_LENGTH: usize = 0x0500_2C80;
|
||||||
|
|
||||||
const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
|
const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
|
||||||
|
|
||||||
@ -235,6 +244,53 @@ impl SC64 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn command_sd_card_operation(&mut self, op: SdCardOp) -> Result<SdCardOpPacket, Error> {
|
||||||
|
let data = self
|
||||||
|
.link
|
||||||
|
.execute_command_raw(b'i', op.into(), &[], false, true)?;
|
||||||
|
if data.len() != 8 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Invalid data length received for SD card operation command",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(SdCardOpPacket {
|
||||||
|
result: data.clone().try_into()?,
|
||||||
|
status: data.clone().try_into()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_sd_card_read(
|
||||||
|
&mut self,
|
||||||
|
address: u32,
|
||||||
|
sector: u32,
|
||||||
|
count: u32,
|
||||||
|
) -> Result<SdCardResult, Error> {
|
||||||
|
let data = self.link.execute_command_raw(
|
||||||
|
b's',
|
||||||
|
[address, count],
|
||||||
|
§or.to_be_bytes(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
Ok(data.try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn command_sd_card_write(
|
||||||
|
&mut self,
|
||||||
|
address: u32,
|
||||||
|
sector: u32,
|
||||||
|
count: u32,
|
||||||
|
) -> Result<SdCardResult, Error> {
|
||||||
|
let data = self.link.execute_command_raw(
|
||||||
|
b'S',
|
||||||
|
[address, count],
|
||||||
|
§or.to_be_bytes(),
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)?;
|
||||||
|
Ok(data.try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
|
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
|
||||||
self.link.execute_command(b'D', [error as u32, 0], &[])?;
|
self.link.execute_command(b'D', [error as u32, 0], &[])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -389,6 +445,7 @@ impl SC64 {
|
|||||||
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||||
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
|
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
|
||||||
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
|
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
|
||||||
|
SaveType::FlashramFake => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||||
};
|
};
|
||||||
|
|
||||||
if length != save_length {
|
if length != save_length {
|
||||||
@ -413,6 +470,7 @@ impl SC64 {
|
|||||||
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||||
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
|
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
|
||||||
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
|
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
|
||||||
|
SaveType::FlashramFake => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.memory_read_chunked(writer, address, save_length)
|
self.memory_read_chunked(writer, address, save_length)
|
||||||
@ -430,7 +488,10 @@ impl SC64 {
|
|||||||
self.memory_read_chunked(writer, address, length)
|
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)? {
|
let (address, cic_seed, boot_seed) = match get_config!(self, BootMode)? {
|
||||||
BootMode::Menu => (BOOTLOADER_ADDRESS, None, None),
|
BootMode::Menu => (BOOTLOADER_ADDRESS, None, None),
|
||||||
BootMode::Rom => (BOOTLOADER_ADDRESS, None, custom_seed),
|
BootMode::Rom => (BOOTLOADER_ADDRESS, None, custom_seed),
|
||||||
@ -439,12 +500,12 @@ impl SC64 {
|
|||||||
BootMode::DirectDdIpl => (DDIPL_ADDRESS, custom_seed, None),
|
BootMode::DirectDdIpl => (DDIPL_ADDRESS, custom_seed, None),
|
||||||
};
|
};
|
||||||
let ipl3 = self.command_memory_read(address + IPL3_OFFSET, IPL3_LENGTH)?;
|
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)?;
|
self.command_cic_params_set(false, seed, checksum)?;
|
||||||
if let Some(seed) = boot_seed {
|
if let Some(seed) = boot_seed {
|
||||||
self.command_config_set(Config::CicSeed(CicSeed::Seed(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> {
|
pub fn set_boot_mode(&mut self, boot_mode: BootMode) -> Result<(), Error> {
|
||||||
@ -489,6 +550,7 @@ impl SC64 {
|
|||||||
button_mode: get_config!(self, ButtonMode)?,
|
button_mode: get_config!(self, ButtonMode)?,
|
||||||
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
|
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
|
||||||
led_enable: get_setting!(self, LedEnable)?,
|
led_enable: get_setting!(self, LedEnable)?,
|
||||||
|
sd_card_status: self.get_sd_card_status()?,
|
||||||
datetime: self.get_datetime()?,
|
datetime: self.get_datetime()?,
|
||||||
fpga_debug_data: self.command_fpga_debug_data_get()?,
|
fpga_debug_data: self.command_fpga_debug_data_get()?,
|
||||||
diagnostic_data: self.command_diagnostic_data_get()?,
|
diagnostic_data: self.command_diagnostic_data_get()?,
|
||||||
@ -606,6 +668,83 @@ impl SC64 {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init_sd_card(&mut self) -> Result<SdCardResult, Error> {
|
||||||
|
self.command_sd_card_operation(SdCardOp::Init)
|
||||||
|
.map(|packet| packet.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit_sd_card(&mut self) -> Result<SdCardResult, Error> {
|
||||||
|
self.command_sd_card_operation(SdCardOp::Deinit)
|
||||||
|
.map(|packet| packet.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sd_card_status(&mut self) -> Result<SdCardStatus, Error> {
|
||||||
|
self.command_sd_card_operation(SdCardOp::GetStatus)
|
||||||
|
.map(|reply| reply.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_sd_card_info(&mut self) -> Result<SdCardInfo, Error> {
|
||||||
|
const SD_CARD_INFO_BUFFER_ADDRESS: u32 = 0x0500_2BE0;
|
||||||
|
let info =
|
||||||
|
match self.command_sd_card_operation(SdCardOp::GetInfo(SD_CARD_INFO_BUFFER_ADDRESS))? {
|
||||||
|
SdCardOpPacket {
|
||||||
|
result: SdCardResult::OK,
|
||||||
|
status: _,
|
||||||
|
} => self.command_memory_read(SD_CARD_INFO_BUFFER_ADDRESS, 32)?,
|
||||||
|
packet => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Couldn't get SD card info registers: {}", packet.result).as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(info.try_into()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_sd_card(&mut self, data: &mut [u8], sector: u32) -> Result<SdCardResult, Error> {
|
||||||
|
if data.len() % SD_CARD_SECTOR_SIZE != 0 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"SD card read length not aligned to the sector size",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_sector = sector;
|
||||||
|
|
||||||
|
for mut chunk in data.chunks_mut(SD_CARD_BUFFER_LENGTH) {
|
||||||
|
let sectors = (chunk.len() / SD_CARD_SECTOR_SIZE) as u32;
|
||||||
|
match self.command_sd_card_read(SD_CARD_BUFFER_ADDRESS, current_sector, sectors)? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => return Ok(result),
|
||||||
|
}
|
||||||
|
let data = self.command_memory_read(SD_CARD_BUFFER_ADDRESS, chunk.len())?;
|
||||||
|
chunk.write_all(&data)?;
|
||||||
|
current_sector += sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SdCardResult::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_sd_card(&mut self, data: &[u8], sector: u32) -> Result<SdCardResult, Error> {
|
||||||
|
if data.len() % SD_CARD_SECTOR_SIZE != 0 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"SD card write length not aligned to the sector size",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_sector = sector;
|
||||||
|
|
||||||
|
for chunk in data.chunks(SD_CARD_BUFFER_LENGTH) {
|
||||||
|
let sectors = (chunk.len() / SD_CARD_SECTOR_SIZE) as u32;
|
||||||
|
self.command_memory_write(SD_CARD_BUFFER_ADDRESS, chunk)?;
|
||||||
|
match self.command_sd_card_write(SD_CARD_BUFFER_ADDRESS, current_sector, sectors)? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => return Ok(result),
|
||||||
|
}
|
||||||
|
current_sector += sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SdCardResult::OK)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_device(&mut self) -> Result<(), Error> {
|
pub fn check_device(&mut self) -> Result<(), Error> {
|
||||||
let identifier = self.command_identifier_get().map_err(|e| {
|
let identifier = self.command_identifier_get().map_err(|e| {
|
||||||
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())
|
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())
|
||||||
@ -634,6 +773,14 @@ impl SC64 {
|
|||||||
self.command_state_reset()
|
self.command_state_reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_console_powered_on(&mut self) -> Result<bool, Error> {
|
||||||
|
let debug_data = self.command_fpga_debug_data_get()?;
|
||||||
|
Ok(match debug_data.cic_step {
|
||||||
|
CicStep::Unavailable | CicStep::PowerOff => false,
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn backup_firmware(&mut self) -> Result<Vec<u8>, Error> {
|
pub fn backup_firmware(&mut self) -> Result<Vec<u8>, Error> {
|
||||||
self.command_state_reset()?;
|
self.command_state_reset()?;
|
||||||
let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS_SDRAM)?;
|
let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS_SDRAM)?;
|
||||||
@ -738,6 +885,46 @@ impl SC64 {
|
|||||||
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn test_sd_card(&mut self) -> Result<f64, Error> {
|
||||||
|
const TEST_LENGTH: usize = 4 * 1024 * 1024;
|
||||||
|
const MIB_DIVIDER: f64 = 1024.0 * 1024.0;
|
||||||
|
|
||||||
|
let mut data = vec![0x00; TEST_LENGTH];
|
||||||
|
|
||||||
|
match self.init_sd_card()? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Init SD card failed: {result}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = std::time::Instant::now();
|
||||||
|
|
||||||
|
match self.read_sd_card(&mut data, 0)? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Read SD card failed: {result}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let elapsed = time.elapsed();
|
||||||
|
|
||||||
|
match self.deinit_sd_card()? {
|
||||||
|
SdCardResult::OK => {}
|
||||||
|
result => {
|
||||||
|
return Err(Error::new(
|
||||||
|
format!("Deinit SD card failed: {result}").as_str(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn test_sdram_pattern(
|
pub fn test_sdram_pattern(
|
||||||
&mut self,
|
&mut self,
|
||||||
pattern: MemoryTestPattern,
|
pattern: MemoryTestPattern,
|
||||||
|
@ -288,6 +288,7 @@ pub enum SaveType {
|
|||||||
Flashram,
|
Flashram,
|
||||||
SramBanked,
|
SramBanked,
|
||||||
Sram1m,
|
Sram1m,
|
||||||
|
FlashramFake,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for SaveType {
|
impl Display for SaveType {
|
||||||
@ -300,6 +301,7 @@ impl Display for SaveType {
|
|||||||
Self::SramBanked => "SRAM 768k",
|
Self::SramBanked => "SRAM 768k",
|
||||||
Self::Flashram => "FlashRAM 1M",
|
Self::Flashram => "FlashRAM 1M",
|
||||||
Self::Sram1m => "SRAM 1M",
|
Self::Sram1m => "SRAM 1M",
|
||||||
|
Self::FlashramFake => "FakeFlashRAM 1M"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,6 +317,7 @@ impl TryFrom<u32> for SaveType {
|
|||||||
4 => Self::Flashram,
|
4 => Self::Flashram,
|
||||||
5 => Self::SramBanked,
|
5 => Self::SramBanked,
|
||||||
6 => Self::Sram1m,
|
6 => Self::Sram1m,
|
||||||
|
7 => Self::FlashramFake,
|
||||||
_ => return Err(Error::new("Unknown save type code")),
|
_ => return Err(Error::new("Unknown save type code")),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -330,6 +333,7 @@ impl From<SaveType> for u32 {
|
|||||||
SaveType::Flashram => 4,
|
SaveType::Flashram => 4,
|
||||||
SaveType::SramBanked => 5,
|
SaveType::SramBanked => 5,
|
||||||
SaveType::Sram1m => 6,
|
SaveType::Sram1m => 6,
|
||||||
|
SaveType::FlashramFake => 7,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -737,6 +741,241 @@ impl TryFrom<Vec<u8>> for DiskPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SdCardOp {
|
||||||
|
Deinit,
|
||||||
|
Init,
|
||||||
|
GetStatus,
|
||||||
|
GetInfo(u32),
|
||||||
|
ByteSwapOn,
|
||||||
|
ByteSwapOff,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SdCardOp> for [u32; 2] {
|
||||||
|
fn from(value: SdCardOp) -> Self {
|
||||||
|
match value {
|
||||||
|
SdCardOp::Deinit => [0, 0],
|
||||||
|
SdCardOp::Init => [0, 1],
|
||||||
|
SdCardOp::GetStatus => [0, 2],
|
||||||
|
SdCardOp::GetInfo(address) => [address, 3],
|
||||||
|
SdCardOp::ByteSwapOn => [0, 4],
|
||||||
|
SdCardOp::ByteSwapOff => [0, 5],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SdCardResult {
|
||||||
|
OK,
|
||||||
|
NoCardInSlot,
|
||||||
|
NotInitialized,
|
||||||
|
InvalidArgument,
|
||||||
|
InvalidAddress,
|
||||||
|
InvalidOperation,
|
||||||
|
Cmd2IO,
|
||||||
|
Cmd3IO,
|
||||||
|
Cmd6CheckIO,
|
||||||
|
Cmd6CheckCRC,
|
||||||
|
Cmd6CheckTimeout,
|
||||||
|
Cmd6CheckResponse,
|
||||||
|
Cmd6SwitchIO,
|
||||||
|
Cmd6SwitchCRC,
|
||||||
|
Cmd6SwitchTimeout,
|
||||||
|
Cmd6SwitchResponse,
|
||||||
|
Cmd7IO,
|
||||||
|
Cmd8IO,
|
||||||
|
Cmd9IO,
|
||||||
|
Cmd10IO,
|
||||||
|
Cmd18IO,
|
||||||
|
Cmd18CRC,
|
||||||
|
Cmd18Timeout,
|
||||||
|
Cmd25IO,
|
||||||
|
Cmd25CRC,
|
||||||
|
Cmd25Timeout,
|
||||||
|
Acmd6IO,
|
||||||
|
Acmd41IO,
|
||||||
|
Acmd41OCR,
|
||||||
|
Acmd41Timeout,
|
||||||
|
Locked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SdCardResult {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
Self::OK => "OK",
|
||||||
|
Self::NoCardInSlot => "No card in slot",
|
||||||
|
Self::NotInitialized => "Not initialized",
|
||||||
|
Self::InvalidArgument => "Invalid argument",
|
||||||
|
Self::InvalidAddress => "Invalid address",
|
||||||
|
Self::InvalidOperation => "Invalid operation",
|
||||||
|
Self::Cmd2IO => "CMD2 I/O",
|
||||||
|
Self::Cmd3IO => "CMD3 I/O",
|
||||||
|
Self::Cmd6CheckIO => "CMD6 I/O (check)",
|
||||||
|
Self::Cmd6CheckCRC => "CMD6 CRC (check)",
|
||||||
|
Self::Cmd6CheckTimeout => "CMD6 timeout (check)",
|
||||||
|
Self::Cmd6CheckResponse => "CMD6 invalid response (check)",
|
||||||
|
Self::Cmd6SwitchIO => "CMD6 I/O (switch)",
|
||||||
|
Self::Cmd6SwitchCRC => "CMD6 CRC (switch)",
|
||||||
|
Self::Cmd6SwitchTimeout => "CMD6 timeout (switch)",
|
||||||
|
Self::Cmd6SwitchResponse => "CMD6 invalid response (switch)",
|
||||||
|
Self::Cmd7IO => "CMD7 I/O",
|
||||||
|
Self::Cmd8IO => "CMD8 I/O",
|
||||||
|
Self::Cmd9IO => "CMD9 I/O",
|
||||||
|
Self::Cmd10IO => "CMD10 I/O",
|
||||||
|
Self::Cmd18IO => "CMD18 I/O",
|
||||||
|
Self::Cmd18CRC => "CMD18 CRC",
|
||||||
|
Self::Cmd18Timeout => "CMD18 timeout",
|
||||||
|
Self::Cmd25IO => "CMD25 I/O",
|
||||||
|
Self::Cmd25CRC => "CMD25 CRC",
|
||||||
|
Self::Cmd25Timeout => "CMD25 timeout",
|
||||||
|
Self::Acmd6IO => "ACMD6 I/O",
|
||||||
|
Self::Acmd41IO => "ACMD41 I/O",
|
||||||
|
Self::Acmd41OCR => "ACMD41 OCR",
|
||||||
|
Self::Acmd41Timeout => "ACMD41 timeout",
|
||||||
|
Self::Locked => "SD card is locked by the N64 side",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for SdCardResult {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() < 4 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Incorrect data length for SD card result data packet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(match u32::from_be_bytes(value[0..4].try_into().unwrap()) {
|
||||||
|
0 => Self::OK,
|
||||||
|
1 => Self::NoCardInSlot,
|
||||||
|
2 => Self::NotInitialized,
|
||||||
|
3 => Self::InvalidArgument,
|
||||||
|
4 => Self::InvalidAddress,
|
||||||
|
5 => Self::InvalidOperation,
|
||||||
|
6 => Self::Cmd2IO,
|
||||||
|
7 => Self::Cmd3IO,
|
||||||
|
8 => Self::Cmd6CheckIO,
|
||||||
|
9 => Self::Cmd6CheckCRC,
|
||||||
|
10 => Self::Cmd6CheckTimeout,
|
||||||
|
11 => Self::Cmd6CheckResponse,
|
||||||
|
12 => Self::Cmd6SwitchIO,
|
||||||
|
13 => Self::Cmd6SwitchCRC,
|
||||||
|
14 => Self::Cmd6SwitchTimeout,
|
||||||
|
15 => Self::Cmd6SwitchResponse,
|
||||||
|
16 => Self::Cmd7IO,
|
||||||
|
17 => Self::Cmd8IO,
|
||||||
|
18 => Self::Cmd9IO,
|
||||||
|
19 => Self::Cmd10IO,
|
||||||
|
20 => Self::Cmd18IO,
|
||||||
|
21 => Self::Cmd18CRC,
|
||||||
|
22 => Self::Cmd18Timeout,
|
||||||
|
23 => Self::Cmd25IO,
|
||||||
|
24 => Self::Cmd25CRC,
|
||||||
|
25 => Self::Cmd25Timeout,
|
||||||
|
26 => Self::Acmd6IO,
|
||||||
|
27 => Self::Acmd41IO,
|
||||||
|
28 => Self::Acmd41OCR,
|
||||||
|
29 => Self::Acmd41Timeout,
|
||||||
|
30 => Self::Locked,
|
||||||
|
_ => return Err(Error::new("Unknown SD card result code")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SdCardStatus {
|
||||||
|
pub byte_swap: bool,
|
||||||
|
pub clock_mode_50mhz: bool,
|
||||||
|
pub card_type_block: bool,
|
||||||
|
pub card_initialized: bool,
|
||||||
|
pub card_inserted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SdCardStatus {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if self.card_initialized {
|
||||||
|
f.write_str("Initialized, ")?;
|
||||||
|
f.write_str(if self.clock_mode_50mhz {
|
||||||
|
"50 MHz"
|
||||||
|
} else {
|
||||||
|
"25 MHz"
|
||||||
|
})?;
|
||||||
|
if !self.card_type_block {
|
||||||
|
f.write_str(", byte addressed")?;
|
||||||
|
}
|
||||||
|
if self.byte_swap {
|
||||||
|
f.write_str(", byte swap enabled")?;
|
||||||
|
}
|
||||||
|
} else if self.card_inserted {
|
||||||
|
f.write_str("Not initialized")?;
|
||||||
|
} else {
|
||||||
|
f.write_str("Not inserted")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SdCardOpPacket {
|
||||||
|
pub result: SdCardResult,
|
||||||
|
pub status: SdCardStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for SdCardStatus {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != 8 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Incorrect data length for SD card status data packet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let status = u32::from_be_bytes(value[4..8].try_into().unwrap());
|
||||||
|
return Ok(Self {
|
||||||
|
byte_swap: status & (1 << 4) != 0,
|
||||||
|
clock_mode_50mhz: status & (1 << 3) != 0,
|
||||||
|
card_type_block: status & (1 << 2) != 0,
|
||||||
|
card_initialized: status & (1 << 1) != 0,
|
||||||
|
card_inserted: status & (1 << 0) != 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SdCardInfo {
|
||||||
|
pub sectors: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Vec<u8>> for SdCardInfo {
|
||||||
|
type Error = Error;
|
||||||
|
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != 32 {
|
||||||
|
return Err(Error::new(
|
||||||
|
"Incorrect data length for SD card info data packet",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let csd = u128::from_be_bytes(value[0..16].try_into().unwrap());
|
||||||
|
let csd_structure = (csd >> 126) & 0x3;
|
||||||
|
match csd_structure {
|
||||||
|
0 => {
|
||||||
|
let c_size = ((csd >> 62) & 0xFFF) as u64;
|
||||||
|
let c_size_mult = ((csd >> 47) & 0x7) as u32;
|
||||||
|
let read_bl_len = ((csd >> 80) & 0xF) as u32;
|
||||||
|
Ok(SdCardInfo {
|
||||||
|
sectors: (c_size + 1) * 2u64.pow(c_size_mult + 2) * 2u64.pow(read_bl_len) / 512,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
let c_size = ((csd >> 48) & 0x3FFFFF) as u64;
|
||||||
|
Ok(SdCardInfo {
|
||||||
|
sectors: (c_size + 1) * 1024,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
let c_size = ((csd >> 48) & 0xFFFFFFF) as u64;
|
||||||
|
Ok(SdCardInfo {
|
||||||
|
sectors: (c_size + 1) * 1024,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => Err(Error::new("Unknown CSD structure value")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DiskBlock {
|
pub struct DiskBlock {
|
||||||
pub address: u32,
|
pub address: u32,
|
||||||
pub track: u32,
|
pub track: u32,
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<menu class="mobile-hidden">
|
<menu class="mobile-hidden">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/">Home</a></li>
|
||||||
<li><a href="/features.html">Features</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://menu.summercart64.dev">Menu</a></li>
|
||||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</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>
|
<li class="active"><a href="/bom.html">Bill of materials</a></li>
|
||||||
@ -39,7 +40,7 @@
|
|||||||
<iframe class="bom" src="/sc64v2_bom.html"></iframe>
|
<iframe class="bom" src="/sc64v2_bom.html"></iframe>
|
||||||
|
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
247
web/faq.html
Normal file
247
web/faq.html
Normal 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>
|
@ -28,6 +28,7 @@
|
|||||||
<menu class="mobile-hidden">
|
<menu class="mobile-hidden">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/">Home</a></li>
|
||||||
<li class="active"><a href="/features.html">Features</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://menu.summercart64.dev">Menu</a></li>
|
||||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</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="/bom.html">Bill of materials</a></li>
|
||||||
@ -181,7 +182,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
<menu class="mobile-hidden">
|
<menu class="mobile-hidden">
|
||||||
<li class="active"><a href="/">Home</a></li>
|
<li class="active"><a href="/">Home</a></li>
|
||||||
<li><a href="/features.html">Features</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://menu.summercart64.dev">Menu</a></li>
|
||||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</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="/bom.html">Bill of materials</a></li>
|
||||||
@ -38,12 +39,12 @@
|
|||||||
|
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
<main>
|
<main>
|
||||||
<h1>What is SummerCart64?</h1>
|
<h1>What is the SummerCart64?</h1>
|
||||||
<section>
|
<section>
|
||||||
<img class="sc64-logo" src="sc64.svg">
|
<img class="sc64-logo" src="sc64.svg">
|
||||||
<p>SummerCart64 is a custom made cartridge (commonly referred as a flashcart) that allows you to develop
|
<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
|
and play games on the N64 console. It is the first N64 flashcart that is feature complete and, at
|
||||||
completely open source.</p>
|
the same time, completely open source.</p>
|
||||||
<p>With fast I/O, integrated support in <a href="https://libdragon.dev">libdragon</a> / <a
|
<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/devwizard64/libcart">libcart</a> / <a
|
||||||
href="https://github.com/buu342/N64-UNFLoader">UNFLoader</a>, and well documented API, testing
|
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>
|
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
|
<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>
|
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>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@ -94,13 +97,10 @@
|
|||||||
|
|
||||||
<h1>Support the project</h1>
|
<h1>Support the project</h1>
|
||||||
<section>
|
<section>
|
||||||
<p>Like the project? Consider supporting it on the <a
|
<p>Like the project? Consider supporting the effort on <a
|
||||||
href="https://github.com/sponsors/Polprzewodnikowy">GitHub Sponsors</a>, <a
|
href="https://ko-fi.com/polprzewodnikowy">Ko-fi</a>, or click a button below. Not necessary
|
||||||
href="https://ko-fi.com/polprzewodnikowy">Ko-fi</a>, or click the buttons below. Not necessary
|
but greatly appreciated. Tips are non-refundable, support responsibly.</p>
|
||||||
but greatly appreciated.</p>
|
|
||||||
<div class="sponsor-buttons">
|
<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'
|
<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>
|
src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||||
</div>
|
</div>
|
||||||
@ -109,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
--sponsor-buttons-height: 32px;
|
--sponsor-buttons-height: 32px;
|
||||||
|
|
||||||
|
--details-bg-color: rgb(56, 56, 128);
|
||||||
|
|
||||||
--footer-padding: 16px 32px 16px 32px;
|
--footer-padding: 16px 32px 16px 32px;
|
||||||
--footer-font-size: 14px;
|
--footer-font-size: 14px;
|
||||||
}
|
}
|
||||||
@ -57,6 +59,7 @@ a:visited {
|
|||||||
a:active,
|
a:active,
|
||||||
a:hover {
|
a:hover {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
@ -259,6 +262,22 @@ main .separator {
|
|||||||
margin: var(--main-separator-margin) 0;
|
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 {
|
iframe.bom {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user