mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2024-11-22 14:09:16 +01:00
Merge remote-tracking branch 'origin/main' into pyinstaller
This commit is contained in:
commit
e627e11bb7
29
.github/ISSUE_TEMPLATE.md
vendored
29
.github/ISSUE_TEMPLATE.md
vendored
@ -1,29 +0,0 @@
|
||||
## Expected Behavior
|
||||
<!--- Provide a headline summary of the issue in the Title above -->
|
||||
<!--- Tell us what should happen -->
|
||||
|
||||
## Current Behavior
|
||||
<!--- Tell us what happens instead of the expected behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
|
||||
## Steps to Reproduce
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context (Environment)
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Detailed Description
|
||||
<!--- Provide a detailed description of the change or addition you are proposing -->
|
||||
|
||||
## Possible Implementation
|
||||
<!--- Not obligatory, but suggest an idea for implementing addition or change -->
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
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.
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
|
||||
about: Please use for QUESTIONS, conversations or discussions.
|
||||
- name: N64brew Discord
|
||||
url: https://discord.gg/8xvRPqCAFC
|
||||
about: Alternative channel for asking QUESTIONS.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
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.md
vendored
20
.github/PULL_REQUEST_TEMPLATE.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 appropriate):
|
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
**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/config.yml
vendored
8
.github/config.yml
vendored
@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
|
||||
about: Please use for QUESTIONS, conversations or discussions.
|
||||
- name: N64brew Discord
|
||||
url: https://discord.gg/8xvRPqCAFC
|
||||
about: Alternative channel for asking QUESTIONS.
|
1
build.sh
1
build.sh
@ -5,7 +5,6 @@ set -e
|
||||
PACKAGE_FILE_NAME="SC64"
|
||||
|
||||
TOP_FILES=(
|
||||
"./sw/pc/dd64.py"
|
||||
"./sw/pc/primer.py"
|
||||
"./sw/pc/requirements.txt"
|
||||
"./sw/pc/sc64.py"
|
||||
|
@ -6,6 +6,7 @@
|
||||
- [Running 64DD games](#running-64dd-games)
|
||||
- [Direct boot option](#direct-boot-option)
|
||||
- [Debug terminal](#debug-terminal)
|
||||
- [LED blink patters](#led-blink-patters)
|
||||
|
||||
---
|
||||
|
||||
@ -24,9 +25,9 @@
|
||||
|
||||
Keeping SC64 firmware up to date is highly recommended. `sc64.py` script is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||
|
||||
To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_backup_package.bin`
|
||||
To download and backup current version of SC64 firmware run `python3 sc64.py --backup-firmware sc64_firmware_backup.bin`
|
||||
|
||||
To update SC64 firmware run `python3 sc64.py --update-firmware sc64_update_package.bin`
|
||||
To update SC64 firmware run `python3 sc64.py --update-firmware sc64_firmware.bin`
|
||||
|
||||
---
|
||||
|
||||
@ -71,3 +72,21 @@ Run `python3 sc64.py --boot direct-rom --rom path_to_rom.n64` to disable bootloa
|
||||
## Debug terminal
|
||||
|
||||
`sc64.py` supports UNFLoader protocol and has same functionality implemented as aforementioned program. Use argument `--debug` to activate it.
|
||||
|
||||
---
|
||||
|
||||
## 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 patters meaning:
|
||||
|
||||
| Pattern | Meaning |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| Nx [Short ON - Short OFF] | SD card is being accessed (initialization or data read/write) or save writeback is in progress |
|
||||
| Nx [Medium ON - Long OFF] | CIC region didn't 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 |
|
||||
| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated |
|
||||
| 30x [Long ON - Long OFF] | There was serious problem during firmware update, device is most likely bricked |
|
||||
|
||||
Nx means that blink count is varied.
|
||||
|
||||
LED blinking on SD card access can be disabled through `sc64.py` script. Please refer to included help for option to change the LED behavior.
|
||||
|
@ -5,7 +5,7 @@
|
||||
- [SC64 registers](#sc64-registers)
|
||||
- [`0x1FFF_0000`: **STATUS/COMMAND**](#0x1fff_0000-statuscommand)
|
||||
- [`0x1FFF_0004`: **DATA0** and `0x1FFF_0008`: **DATA1**](#0x1fff_0004-data0-and-0x1fff_0008-data1)
|
||||
- [`0x1FFF_000C`: **VERSION**](#0x1fff_000c-version)
|
||||
- [`0x1FFF_000C`: **IDENTIFIER**](#0x1fff_000c-identifier)
|
||||
- [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key)
|
||||
- [Command execution flow](#command-execution-flow)
|
||||
|
||||
@ -87,13 +87,13 @@ SC64 contains small register region used for communication between N64 and contr
|
||||
Protocol is command based with support for up to 256 diferrent commands and two 32-bit argument/result values per operation.
|
||||
Support for interrupts is provided but currently no command relies on it, 64DD IRQ is handled separately.
|
||||
|
||||
| name | address | size | access | usage |
|
||||
| ------------------ | ------------- | ------- | ------ | -------------------------------- |
|
||||
| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status |
|
||||
| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 |
|
||||
| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 |
|
||||
| **VERSION** | `0x1FFF_000C` | 4 bytes | RW | Hardware version and IRQ clear |
|
||||
| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock |
|
||||
| name | address | size | access | usage |
|
||||
| ------------------ | ------------- | ------- | ------ | ---------------------------------- |
|
||||
| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status |
|
||||
| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 |
|
||||
| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 |
|
||||
| **IDENTIFIER** | `0x1FFF_000C` | 4 bytes | RW | Flashcart identifier and IRQ clear |
|
||||
| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock |
|
||||
|
||||
---
|
||||
|
||||
@ -121,11 +121,11 @@ Note: Result is valid only when command has executed and `CMD_BUSY` bit is reset
|
||||
|
||||
---
|
||||
|
||||
#### `0x1FFF_000C`: **VERSION**
|
||||
#### `0x1FFF_000C`: **IDENTIFIER**
|
||||
|
||||
| name | bits | access | meaning |
|
||||
| --------- | ------ | ------ | --------------------------------------------- |
|
||||
| `VERSION` | [31:0] | RW | Hardware version (ASCII `SCv2`) and IRQ clear |
|
||||
| name | bits | access | meaning |
|
||||
| ------------ | ------ | ------ | ------------------------------------------------- |
|
||||
| `IDENTIFIER` | [31:0] | RW | Flashcart identifier (ASCII `SCv2`) and IRQ clear |
|
||||
|
||||
Note: Writing any value to this register will clear pending flashcart interrupt.
|
||||
|
||||
|
@ -4,25 +4,25 @@
|
||||
|
||||
## USB commands
|
||||
|
||||
| id | name | arg0 | arg1 | data | response | description |
|
||||
| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | --------------------------------------------------- |
|
||||
| `v` | **HW_VERSION_GET** | --- | --- | --- | hw_version | Get HW version |
|
||||
| `V` | **API_VERSION_GET** | --- | --- | --- | api_version | Get USB command API version |
|
||||
| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset entire flashcart state |
|
||||
| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC disable/mode/seed/checksum |
|
||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | --- | Set config option |
|
||||
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting |
|
||||
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting |
|
||||
| `t` | **TIME_GET** | --- | --- | --- | time | Get current RTC value |
|
||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set RTC value |
|
||||
| `m` | **MEMORY_READ** | address | length | --- | data | Read data from specified memory address |
|
||||
| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address |
|
||||
| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||
| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 |
|
||||
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
||||
| `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 |
|
||||
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
||||
| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage |
|
||||
| id | name | arg0 | arg1 | data | response | description |
|
||||
| --- | ---------------------- | ------------ | ------------ | ---- | ---------------- | ------------------------------------------------------------- |
|
||||
| `v` | **IDENTIFIER_GET** | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
||||
| `V` | **VERSION_GET** | --- | --- | --- | version | Get flashcart firmware version |
|
||||
| `R` | **STATE_RESET** | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
||||
| `B` | **CIC_PARAMS_SET** | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
|
||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | --- | Set config option |
|
||||
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||
| `t` | **TIME_GET** | --- | --- | --- | time | Get current RTC value |
|
||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||
| `m` | **MEMORY_READ** | address | length | --- | data | Read data from specified memory address |
|
||||
| `M` | **MEMORY_WRITE** | address | length | data | --- | Write data to specified memory address |
|
||||
| `U` | **USB_WRITE** | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||
| `D` | **DD_SET_BLOCK_READY** | success | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
||||
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||
| `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 |
|
||||
| `%` | **STACK_USAGE_GET** | --- | --- | --- | stack_usage | Get per task stack usage |
|
||||
|
@ -6,14 +6,14 @@
|
||||
|
||||
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
||||
| --- | --------------------- | ---------- | ------------ | ---------------- | -------------- | -------------------------------------------------- |
|
||||
| `v` | **HW_VERSION_GET** | --- | --- | hw_version | --- | Get HW version |
|
||||
| `V` | **API_VERSION_GET** | --- | --- | api_version | --- | Get N64 command API version |
|
||||
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
||||
| `V` | **VERSION_GET** | --- | --- | version | --- | Get flashcart firmware version |
|
||||
| `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` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||
| `C` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set 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_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 |
|
||||
@ -27,4 +27,3 @@
|
||||
| `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_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
||||
| `?` | **DEBUG_GET** | --- | --- | debug_data_0 | debug_data_1 | Get internal FPGA debug info |
|
||||
|
@ -57,6 +57,8 @@ There are no special requirements for soldering components to board. All chips a
|
||||
|
||||
### **Initial programming**
|
||||
|
||||
**Please read the following instructions carefully before proceeding with programming.**
|
||||
|
||||
For initial programming you are going to need a PC and a USB to UART (serial) adapter (3.3V signaling is required). These steps assume you are using modern Windows OS (version 10 or higher).
|
||||
|
||||
As for software here's list of required applications:
|
||||
@ -83,11 +85,11 @@ Second, program FPGA, microcontroller and Flash memory:
|
||||
4. Check in device manager which port number `COMx` is assigned to serial adapter
|
||||
5. Connect SC64 board to the PC with USB-C cable (***IMPORTANT:*** connect it to the same computer as serial adapter)
|
||||
6. Locate `primer.py` script in root folder
|
||||
7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `dd64.py`, `sc64_firmware.bin`
|
||||
7. Make sure these files are located in the same folder as `primer.py` script: `requirements.txt`, `sc64.py`, `sc64_firmware.bin`
|
||||
8. Run `pip3 install -r requirements.txt` to install required python packages
|
||||
9. Run `python3 primer.py COMx sc64_firmware.bin` (replace `COMx` with port located in step **4**)
|
||||
10. Follow the instructions on the screen
|
||||
11. Wait until programming process has finished
|
||||
11. Wait until programming process has finished (**DO NOT STOP PROGRAMMING PROCESS OR DISCONNECT SC64 BOARD FROM PC**, doing so might irrecoverably break programming through UART header and you would need to program microcontroller, FPGA and bootloader with separate dedicated programming interfaces through *Tag-Connect* connector on the PCB)
|
||||
|
||||
Congratulations! Your SC64 flashcart should be ready for use!
|
||||
|
||||
|
@ -332,7 +332,7 @@ module mcu_top (
|
||||
REG_CFG_DATA_0,
|
||||
REG_CFG_DATA_1,
|
||||
REG_CFG_CMD,
|
||||
REG_CFG_VERSION,
|
||||
REG_CFG_IDENTIFIER,
|
||||
REG_FLASHRAM_SCR,
|
||||
REG_FLASH_SCR,
|
||||
REG_RTC_SCR,
|
||||
@ -363,7 +363,7 @@ module mcu_top (
|
||||
|
||||
logic bootloader_skip;
|
||||
|
||||
assign n64_scb.cfg_version = 32'h53437632;
|
||||
assign n64_scb.cfg_identifier = 32'h53437632;
|
||||
|
||||
logic dd_bm_ack;
|
||||
|
||||
@ -460,8 +460,8 @@ module mcu_top (
|
||||
};
|
||||
end
|
||||
|
||||
REG_CFG_VERSION: begin
|
||||
reg_rdata <= n64_scb.cfg_version;
|
||||
REG_CFG_IDENTIFIER: begin
|
||||
reg_rdata <= n64_scb.cfg_identifier;
|
||||
end
|
||||
|
||||
REG_FLASHRAM_SCR: begin
|
||||
|
@ -16,8 +16,8 @@ module n64_cfg (
|
||||
REG_DATA_0_L,
|
||||
REG_DATA_1_H,
|
||||
REG_DATA_1_L,
|
||||
REG_VERSION_H,
|
||||
REG_VERSION_L,
|
||||
REG_IDENTIFIER_H,
|
||||
REG_IDENTIFIER_L,
|
||||
REG_KEY_H,
|
||||
REG_KEY_L
|
||||
} e_reg;
|
||||
@ -39,8 +39,8 @@ module n64_cfg (
|
||||
REG_DATA_0_L: reg_bus.rdata = n64_scb.cfg_wdata[0][15:0];
|
||||
REG_DATA_1_H: reg_bus.rdata = n64_scb.cfg_wdata[1][31:16];
|
||||
REG_DATA_1_L: reg_bus.rdata = n64_scb.cfg_wdata[1][15:0];
|
||||
REG_VERSION_H: reg_bus.rdata = n64_scb.cfg_version[31:16];
|
||||
REG_VERSION_L: reg_bus.rdata = n64_scb.cfg_version[15:0];
|
||||
REG_IDENTIFIER_H: reg_bus.rdata = n64_scb.cfg_identifier[31:16];
|
||||
REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0];
|
||||
REG_KEY_H: reg_bus.rdata = 16'd0;
|
||||
REG_KEY_L: reg_bus.rdata = 16'd0;
|
||||
endcase
|
||||
@ -83,7 +83,7 @@ module n64_cfg (
|
||||
REG_DATA_0_L: n64_scb.cfg_rdata[0][15:0] <= reg_bus.wdata;
|
||||
REG_DATA_1_H: n64_scb.cfg_rdata[1][31:16] <= reg_bus.wdata;
|
||||
REG_DATA_1_L: n64_scb.cfg_rdata[1][15:0] <= reg_bus.wdata;
|
||||
REG_VERSION_H: irq <= 1'b0;
|
||||
REG_IDENTIFIER_H: irq <= 1'b0;
|
||||
REG_KEY_H, REG_KEY_L: begin
|
||||
lock_sequence_counter <= lock_sequence_counter + 1'd1;
|
||||
if (reg_bus.wdata != 16'hFFFF) begin
|
||||
|
@ -51,7 +51,7 @@ interface n64_scb ();
|
||||
logic [7:0] cfg_cmd;
|
||||
logic [31:0] cfg_rdata [0:1];
|
||||
logic [31:0] cfg_wdata [0:1];
|
||||
logic [31:0] cfg_version;
|
||||
logic [31:0] cfg_identifier;
|
||||
|
||||
logic [15:0] save_count;
|
||||
|
||||
@ -94,7 +94,7 @@ interface n64_scb ();
|
||||
input cfg_cmd,
|
||||
input cfg_rdata,
|
||||
output cfg_wdata,
|
||||
output cfg_version,
|
||||
output cfg_identifier,
|
||||
|
||||
input save_count,
|
||||
|
||||
@ -194,7 +194,7 @@ interface n64_scb ();
|
||||
output cfg_cmd,
|
||||
output cfg_rdata,
|
||||
input cfg_wdata,
|
||||
input cfg_version
|
||||
input cfg_identifier
|
||||
);
|
||||
|
||||
modport save_counter (
|
||||
|
@ -5,7 +5,7 @@
|
||||
typedef struct {
|
||||
io32_t SR_CMD;
|
||||
io32_t DATA[2];
|
||||
io32_t VERSION;
|
||||
io32_t IDENTIFIER;
|
||||
io32_t KEY;
|
||||
} sc64_regs_t;
|
||||
|
||||
@ -16,7 +16,7 @@ typedef struct {
|
||||
#define SC64_SR_CMD_ERROR (1 << 30)
|
||||
#define SC64_SR_CPU_BUSY (1 << 31)
|
||||
|
||||
#define SC64_VERSION_2 (0x53437632)
|
||||
#define SC64_V2_IDENTIFIER (0x53437632)
|
||||
|
||||
#define SC64_KEY_RESET (0x00000000UL)
|
||||
#define SC64_KEY_UNLOCK_1 (0x5F554E4CUL)
|
||||
@ -24,8 +24,8 @@ typedef struct {
|
||||
#define SC64_KEY_LOCK (0xFFFFFFFFUL)
|
||||
|
||||
typedef enum {
|
||||
SC64_CMD_HW_VERSION_GET = 'v',
|
||||
SC64_CMD_API_VERSION_GET = 'V',
|
||||
SC64_CMD_IDENTIFIER_GET = 'v',
|
||||
SC64_CMD_VERSION_GET = 'V',
|
||||
SC64_CMD_CONFIG_GET = 'c',
|
||||
SC64_CMD_CONFIG_SET = 'C',
|
||||
SC64_CMD_SETTING_GET = 'a',
|
||||
@ -45,7 +45,6 @@ typedef enum {
|
||||
SC64_CMD_FLASH_PROGRAM = 'K',
|
||||
SC64_CMD_FLASH_WAIT_BUSY = 'p',
|
||||
SC64_CMD_FLASH_ERASE_BLOCK = 'P',
|
||||
SC64_CMD_DEBUG_GET = '?',
|
||||
} cmd_id_t;
|
||||
|
||||
typedef enum {
|
||||
@ -98,8 +97,8 @@ void sc64_lock (void) {
|
||||
}
|
||||
|
||||
bool sc64_check_presence (void) {
|
||||
uint32_t version = pi_io_read(&SC64_REGS->VERSION);
|
||||
if (version == SC64_VERSION_2) {
|
||||
uint32_t identifier = pi_io_read(&SC64_REGS->IDENTIFIER);
|
||||
if (identifier == SC64_V2_IDENTIFIER) {
|
||||
sc64_wait_cpu_busy();
|
||||
return true;
|
||||
}
|
||||
@ -114,7 +113,7 @@ bool sc64_irq_pending (void) {
|
||||
}
|
||||
|
||||
void sc64_irq_clear (void) {
|
||||
pi_io_write(&SC64_REGS->VERSION, 0);
|
||||
pi_io_write(&SC64_REGS->IDENTIFIER, 0);
|
||||
}
|
||||
|
||||
uint32_t sc64_get_config (sc64_cfg_id_t id) {
|
||||
|
@ -1,92 +1,122 @@
|
||||
#include <stddef.h>
|
||||
#include "display.h"
|
||||
#include "io.h"
|
||||
#include "sc64.h"
|
||||
#include "test.h"
|
||||
|
||||
|
||||
static void test_rtc (void) {
|
||||
sc64_rtc_time_t t;
|
||||
const char *weekdays[8] = { "", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
|
||||
|
||||
sc64_get_time(&t);
|
||||
|
||||
display_printf("RTC current time:\n");
|
||||
display_printf(" %02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second));
|
||||
display_printf(" %s ", weekdays[FROM_BCD(t.weekday)]);
|
||||
display_printf("%d.%02d.%04d", FROM_BCD(t.day), FROM_BCD(t.month), 2000 + FROM_BCD(t.year));
|
||||
display_printf("\n");
|
||||
}
|
||||
|
||||
static void test_sd_card (void) {
|
||||
sc64_sd_card_status_t card_status;
|
||||
uint8_t card_info[32] __attribute__((aligned(8)));
|
||||
|
||||
card_status = sc64_sd_card_get_status();
|
||||
|
||||
if (card_status & SD_CARD_STATUS_INSERTED) {
|
||||
display_printf("SD card is inserted\n");
|
||||
} else {
|
||||
display_printf("SD card is not inserted\n");
|
||||
}
|
||||
|
||||
if (sc64_sd_card_init()) {
|
||||
display_printf("SD card init error!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
card_status = sc64_sd_card_get_status();
|
||||
|
||||
if (card_status & SD_CARD_STATUS_INITIALIZED) {
|
||||
display_printf("SD card is initialized\n");
|
||||
}
|
||||
if (card_status & SD_CARD_STATUS_TYPE_BLOCK) {
|
||||
display_printf("SD card type is block\n");
|
||||
}
|
||||
if (card_status & SD_CARD_STATUS_50MHZ_MODE) {
|
||||
display_printf("SD card runs at 50 MHz clock speed\n");
|
||||
}
|
||||
|
||||
if (sc64_sd_card_get_info((uint32_t *) (SC64_BUFFERS->BUFFER))) {
|
||||
display_printf("SD card get info error!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), card_info, sizeof(card_info));
|
||||
|
||||
display_printf("SD Card registers:\n CSD: 0x");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
display_printf("%02X", card_info[i]);
|
||||
}
|
||||
display_printf("\n CID: 0x");
|
||||
for (int i = 16; i < 32; i++) {
|
||||
display_printf("%02X", card_info[i]);
|
||||
}
|
||||
display_printf("\n ");
|
||||
for (int i = 16; i < 32; i++) {
|
||||
display_printf("%c ", card_info[i] >= ' ' ? card_info[i] : 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool test_check (void) {
|
||||
if (OS_INFO->reset_type != OS_INFO_RESET_TYPE_COLD) {
|
||||
return false;
|
||||
}
|
||||
return sc64_get_config(CFG_ID_BUTTON_STATE);
|
||||
}
|
||||
|
||||
void test_execute (void) {
|
||||
display_init(NULL);
|
||||
display_printf("SC64 Test suite\n\n");
|
||||
|
||||
display_printf("[ RTC tests ]\n");
|
||||
test_rtc();
|
||||
display_printf("\n");
|
||||
|
||||
display_printf("[ SD card tests ]\n");
|
||||
test_sd_card();
|
||||
display_printf("\n");
|
||||
|
||||
while (1);
|
||||
}
|
||||
#include <stddef.h>
|
||||
#include "display.h"
|
||||
#include "io.h"
|
||||
#include "sc64.h"
|
||||
#include "test.h"
|
||||
|
||||
|
||||
static void test_rtc (void) {
|
||||
sc64_rtc_time_t t;
|
||||
const char *weekdays[8] = {
|
||||
"",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday",
|
||||
};
|
||||
|
||||
sc64_get_time(&t);
|
||||
|
||||
display_printf("RTC current time:\n ");
|
||||
display_printf("%04d-%02d-%02d", 2000 + FROM_BCD(t.year), FROM_BCD(t.month), FROM_BCD(t.day));
|
||||
display_printf("T");
|
||||
display_printf("%02d:%02d:%02d", FROM_BCD(t.hour), FROM_BCD(t.minute), FROM_BCD(t.second));
|
||||
display_printf(" (%s)", weekdays[FROM_BCD(t.weekday)]);
|
||||
display_printf("\n");
|
||||
}
|
||||
|
||||
static void test_sd_card (void) {
|
||||
sc64_sd_card_status_t card_status;
|
||||
uint8_t card_info[32] __attribute__((aligned(8)));
|
||||
uint8_t sector[512] __attribute__((aligned(8)));
|
||||
|
||||
card_status = sc64_sd_card_get_status();
|
||||
|
||||
if (card_status & SD_CARD_STATUS_INSERTED) {
|
||||
display_printf("SD card is inserted\n");
|
||||
} else {
|
||||
display_printf("SD card is not inserted\n");
|
||||
}
|
||||
|
||||
if (sc64_sd_card_init()) {
|
||||
display_printf("SD card init error!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
card_status = sc64_sd_card_get_status();
|
||||
|
||||
if (card_status & SD_CARD_STATUS_INITIALIZED) {
|
||||
display_printf("SD card is initialized\n");
|
||||
}
|
||||
if (card_status & SD_CARD_STATUS_TYPE_BLOCK) {
|
||||
display_printf("SD card type is block\n");
|
||||
}
|
||||
if (card_status & SD_CARD_STATUS_50MHZ_MODE) {
|
||||
display_printf("SD card runs at 50 MHz clock speed\n");
|
||||
}
|
||||
|
||||
if (sc64_sd_card_get_info((uint32_t *) (SC64_BUFFERS->BUFFER))) {
|
||||
display_printf("SD card get info error!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), card_info, sizeof(card_info));
|
||||
|
||||
display_printf("SD Card registers:\n CSD: 0x");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
display_printf("%02X ", card_info[i]);
|
||||
}
|
||||
display_printf("\n CID: 0x");
|
||||
for (int i = 16; i < 32; i++) {
|
||||
display_printf("%02X ", card_info[i]);
|
||||
}
|
||||
display_printf("\n ");
|
||||
for (int i = 16; i < 32; i++) {
|
||||
display_printf(" %c ", card_info[i] >= ' ' ? card_info[i] : 0xFF);
|
||||
}
|
||||
display_printf("\n");
|
||||
|
||||
if (sc64_sd_read_sectors((void *) (SC64_BUFFERS->BUFFER), 0, 1)) {
|
||||
display_printf("SD card read sector 0 error!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
pi_dma_read((io32_t *) (SC64_BUFFERS->BUFFER), sector, sizeof(sector));
|
||||
|
||||
display_printf("Sector 0 (0x1BE-0x1DD), partition entry 1/2:\n 0x");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
display_printf("%02X ", sector[0x1BE + i]);
|
||||
}
|
||||
display_printf("\n 0x");
|
||||
for (int i = 0; i < 16; i++) {
|
||||
display_printf("%02X ", sector[0x1CE + i]);
|
||||
}
|
||||
display_printf("\n");
|
||||
display_printf(" Boot signature: 0x%02X%02X\n", sector[510], sector[511]);
|
||||
}
|
||||
|
||||
|
||||
bool test_check (void) {
|
||||
if (OS_INFO->reset_type != OS_INFO_RESET_TYPE_COLD) {
|
||||
return false;
|
||||
}
|
||||
return sc64_get_config(CFG_ID_BUTTON_STATE);
|
||||
}
|
||||
|
||||
void test_execute (void) {
|
||||
display_init(NULL);
|
||||
display_printf("SC64 Test suite\n\n");
|
||||
|
||||
display_printf("[ RTC tests ]\n");
|
||||
test_rtc();
|
||||
display_printf("\n");
|
||||
|
||||
display_printf("[ SD card tests ]\n");
|
||||
test_sd_card();
|
||||
display_printf("\n");
|
||||
|
||||
while (1);
|
||||
}
|
||||
|
@ -221,8 +221,8 @@ static bool cfg_set_save_type (save_type_t save_type) {
|
||||
}
|
||||
|
||||
|
||||
uint32_t cfg_get_version (void) {
|
||||
return fpga_reg_get(REG_CFG_VERSION);
|
||||
uint32_t cfg_get_identifier (void) {
|
||||
return fpga_reg_get(REG_CFG_IDENTIFIER);
|
||||
}
|
||||
|
||||
bool cfg_query (uint32_t *args) {
|
||||
@ -468,11 +468,11 @@ void cfg_process (void) {
|
||||
|
||||
switch (cmd) {
|
||||
case 'v':
|
||||
args[0] = cfg_get_version();
|
||||
args[0] = cfg_get_identifier();
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
args[0] = version_api(API_N64);
|
||||
args[0] = version_firmware();
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
@ -666,11 +666,6 @@ void cfg_process (void) {
|
||||
}
|
||||
break;
|
||||
|
||||
case '?':
|
||||
args[0] = fpga_reg_get(REG_DEBUG_0);
|
||||
args[1] = fpga_reg_get(REG_DEBUG_1);
|
||||
break;
|
||||
|
||||
default:
|
||||
cfg_set_error(CFG_ERROR_UNKNOWN_CMD);
|
||||
return;
|
||||
|
@ -16,7 +16,7 @@ typedef enum {
|
||||
} save_type_t;
|
||||
|
||||
|
||||
uint32_t cfg_get_version (void);
|
||||
uint32_t cfg_get_identifier (void);
|
||||
bool cfg_query (uint32_t *args);
|
||||
bool cfg_update (uint32_t *args);
|
||||
bool cfg_query_setting (uint32_t *args);
|
||||
|
@ -28,7 +28,7 @@ typedef enum {
|
||||
REG_CFG_DATA_0,
|
||||
REG_CFG_DATA_1,
|
||||
REG_CFG_CMD,
|
||||
REG_CFG_VERSION,
|
||||
REG_CFG_IDENTIFIER,
|
||||
REG_FLASHRAM_SCR,
|
||||
REG_FLASH_SCR,
|
||||
REG_RTC_SCR,
|
||||
|
@ -1,478 +1,473 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "fpga.h"
|
||||
#include "hw.h"
|
||||
#include "led.h"
|
||||
#include "sd.h"
|
||||
|
||||
|
||||
#define SD_INIT_BUFFER_ADDRESS (0x05002800UL)
|
||||
|
||||
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
||||
#define CMD6_ARG_SWITCH_HS (0x80FFFFF1UL)
|
||||
|
||||
#define CMD8_ARG_SUPPLY_VOLTAGE_27_36_V (1 << 8)
|
||||
#define CMD8_ARG_CHECK_PATTERN (0xAA << 0)
|
||||
|
||||
#define ACMD6_ARG_BUS_WIDTH_4BIT (2 << 0)
|
||||
|
||||
#define ACMD41_ARG_OCR (0x300000 << 0)
|
||||
#define ACMD41_ARG_HCS (1 << 30)
|
||||
|
||||
#define R3_OCR (0x300000 << 0)
|
||||
#define R3_CCS (1 << 30)
|
||||
#define R3_BUSY (1 << 31)
|
||||
|
||||
#define R6_RCA_MASK (0xFFFF0000UL)
|
||||
|
||||
#define R7_SUPPLY_VOLTAGE_27_36_V (1 << 8)
|
||||
#define R7_CHECK_PATTERN (0xAA << 0)
|
||||
|
||||
#define SWITCH_FUNCTION_CURRENT_LIMIT (SD_INIT_BUFFER_ADDRESS + 0)
|
||||
#define SWITCH_FUNCTION_GROUP_1 (SD_INIT_BUFFER_ADDRESS + 12)
|
||||
#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1)
|
||||
|
||||
#define DAT_BLOCK_MAX_COUNT (256)
|
||||
#define DAT_TIMEOUT_MS (1000)
|
||||
|
||||
|
||||
typedef enum {
|
||||
CLOCK_STOP,
|
||||
CLOCK_400KHZ,
|
||||
CLOCK_25MHZ,
|
||||
CLOCK_50MHZ,
|
||||
} sd_clock_t;
|
||||
|
||||
typedef enum {
|
||||
RSP_NONE,
|
||||
RSP_R1,
|
||||
RSP_R1b,
|
||||
RSP_R2,
|
||||
RSP_R3,
|
||||
RSP_R6,
|
||||
RSP_R7,
|
||||
} rsp_type_t;
|
||||
|
||||
typedef enum {
|
||||
DAT_READ,
|
||||
DAT_WRITE,
|
||||
} dat_mode_t;
|
||||
|
||||
|
||||
struct process {
|
||||
bool card_initialized;
|
||||
bool card_type_block;
|
||||
uint32_t rca;
|
||||
uint8_t csd[16];
|
||||
uint8_t cid[16];
|
||||
volatile bool timeout;
|
||||
};
|
||||
|
||||
|
||||
static struct process p;
|
||||
|
||||
|
||||
static void sd_trigger_timeout (void) {
|
||||
p.timeout = true;
|
||||
}
|
||||
|
||||
static void sd_prepare_timeout (uint16_t value) {
|
||||
p.timeout = false;
|
||||
hw_tim_setup(TIM_ID_SD, value, sd_trigger_timeout);
|
||||
}
|
||||
|
||||
static bool sd_did_timeout (void) {
|
||||
return p.timeout;
|
||||
}
|
||||
|
||||
static void sd_clear_timeout (void) {
|
||||
hw_tim_stop(TIM_ID_SD);
|
||||
p.timeout = false;
|
||||
}
|
||||
|
||||
static void sd_set_clock (sd_clock_t mode) {
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_OFF);
|
||||
|
||||
switch (mode) {
|
||||
case CLOCK_400KHZ:
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_400KHZ);
|
||||
break;
|
||||
case CLOCK_25MHZ:
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_25MHZ);
|
||||
break;
|
||||
case CLOCK_50MHZ:
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_50MHZ);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
|
||||
uint32_t scr;
|
||||
uint32_t cmd_data;
|
||||
|
||||
cmd_data = ((cmd << SD_CMD_INDEX_BIT) & SD_CMD_INDEX_MASK);
|
||||
switch (rsp_type) {
|
||||
case RSP_NONE:
|
||||
cmd_data |= SD_CMD_SKIP_RESPONSE;
|
||||
break;
|
||||
case RSP_R2:
|
||||
cmd_data |= (SD_CMD_LONG_RESPONSE | SD_CMD_RESERVED_RESPONSE);
|
||||
break;
|
||||
case RSP_R3:
|
||||
cmd_data |= (SD_CMD_IGNORE_CRC | SD_CMD_RESERVED_RESPONSE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_SD_ARG, arg);
|
||||
fpga_reg_set(REG_SD_CMD, cmd_data);
|
||||
|
||||
do {
|
||||
scr = fpga_reg_get(REG_SD_SCR);
|
||||
} while (scr & SD_SCR_CMD_BUSY);
|
||||
|
||||
if (rsp != NULL) {
|
||||
if (cmd_data & SD_CMD_LONG_RESPONSE) {
|
||||
uint8_t *rsp_8 = (uint8_t *) (rsp);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint32_t rsp_data = fpga_reg_get(REG_SD_RSP_3 - i);
|
||||
uint8_t *rsp_data_8 = (uint8_t *) (&rsp_data);
|
||||
rsp_data = SWAP32(rsp_data);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
*rsp_8++ = *rsp_data_8++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(*(uint32_t *) (rsp)) = fpga_reg_get(REG_SD_RSP_0);
|
||||
}
|
||||
}
|
||||
|
||||
if (rsp_type == RSP_R1b) {
|
||||
do {
|
||||
scr = fpga_reg_get(REG_SD_SCR);
|
||||
} while (scr & SD_SCR_CARD_BUSY);
|
||||
}
|
||||
|
||||
return (scr & SD_SCR_CMD_ERROR);
|
||||
}
|
||||
|
||||
static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
|
||||
if (sd_cmd(55, p.rca, RSP_R1, NULL)) {
|
||||
return true;
|
||||
}
|
||||
if (sd_cmd(acmd, arg, rsp_type, rsp)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sd_dat_prepare (uint32_t address, uint32_t count, dat_mode_t mode) {
|
||||
uint32_t length = (count * SD_SECTOR_SIZE);
|
||||
uint32_t sd_dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_FIFO_FLUSH);
|
||||
uint32_t sd_dma_scr = DMA_SCR_START;
|
||||
|
||||
if (mode == DAT_READ) {
|
||||
sd_dat |= SD_DAT_START_READ;
|
||||
sd_dma_scr |= DMA_SCR_DIRECTION;
|
||||
} else {
|
||||
sd_dat |= SD_DAT_START_WRITE;
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_SD_DAT, sd_dat);
|
||||
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
|
||||
fpga_reg_set(REG_SD_DMA_LENGTH, length);
|
||||
fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr);
|
||||
}
|
||||
|
||||
static void sd_dat_abort (void) {
|
||||
fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP);
|
||||
fpga_reg_set(REG_SD_DAT, SD_DAT_STOP | SD_DAT_FIFO_FLUSH);
|
||||
}
|
||||
|
||||
static bool sd_dat_wait (uint16_t timeout) {
|
||||
sd_prepare_timeout(timeout);
|
||||
|
||||
do {
|
||||
uint32_t sd_dat = fpga_reg_get(REG_SD_DAT);
|
||||
uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR);
|
||||
led_blink_act();
|
||||
if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) {
|
||||
sd_clear_timeout();
|
||||
return (sd_dat & SD_DAT_ERROR);
|
||||
}
|
||||
} while (!sd_did_timeout());
|
||||
|
||||
sd_dat_abort();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool sd_card_init (void) {
|
||||
uint32_t arg;
|
||||
uint32_t rsp;
|
||||
uint16_t tmp;
|
||||
|
||||
if (p.card_initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.card_initialized = true;
|
||||
p.rca = 0;
|
||||
|
||||
led_blink_act();
|
||||
|
||||
sd_set_clock(CLOCK_400KHZ);
|
||||
|
||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||
|
||||
arg = (CMD8_ARG_SUPPLY_VOLTAGE_27_36_V | CMD8_ARG_CHECK_PATTERN);
|
||||
if (sd_cmd(8, arg, RSP_R7, &rsp)) {
|
||||
arg = ACMD41_ARG_OCR;
|
||||
} else {
|
||||
if (rsp != (R7_SUPPLY_VOLTAGE_27_36_V | R7_CHECK_PATTERN)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
arg = (ACMD41_ARG_HCS | ACMD41_ARG_OCR);
|
||||
}
|
||||
|
||||
sd_prepare_timeout(1000);
|
||||
do {
|
||||
if (sd_did_timeout()) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
if (sd_acmd(41, arg, RSP_R3, &rsp)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
if (rsp & R3_BUSY) {
|
||||
if ((rsp & R3_OCR) == 0) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
p.card_type_block = (rsp & R3_CCS);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
sd_clear_timeout();
|
||||
|
||||
if (sd_cmd(2, 0, RSP_R2, NULL)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sd_cmd(3, 0, RSP_R6, &rsp)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
p.rca = (rsp & R6_RCA_MASK);
|
||||
|
||||
if (sd_cmd(9, p.rca, RSP_R2, p.csd)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sd_cmd(10, p.rca, RSP_R2, p.cid)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sd_cmd(7, p.rca, RSP_R1b, NULL)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
sd_set_clock(CLOCK_25MHZ);
|
||||
|
||||
if (sd_acmd(6, ACMD6_ARG_BUS_WIDTH_4BIT, RSP_R1, NULL)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ);
|
||||
if (sd_cmd(6, CMD6_ARG_CHECK_HS, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
sd_dat_wait(DAT_TIMEOUT_MS);
|
||||
if (sd_did_timeout()) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
fpga_mem_read(SWITCH_FUNCTION_CURRENT_LIMIT, 2, (uint8_t *) (&tmp));
|
||||
if (SWAP16(tmp) == 0) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp));
|
||||
if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) {
|
||||
sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ);
|
||||
if (sd_cmd(6, CMD6_ARG_SWITCH_HS, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
sd_dat_wait(DAT_TIMEOUT_MS);
|
||||
if (sd_did_timeout()) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp));
|
||||
if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) {
|
||||
sd_set_clock(CLOCK_50MHZ);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sd_card_deinit (void) {
|
||||
if (p.card_initialized) {
|
||||
p.card_initialized = false;
|
||||
sd_set_clock(CLOCK_400KHZ);
|
||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||
sd_set_clock(CLOCK_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
bool sd_card_is_inserted (void) {
|
||||
return (fpga_reg_get(REG_SD_SCR) & SD_SCR_CARD_INSERTED);
|
||||
}
|
||||
|
||||
uint32_t sd_card_get_status (void) {
|
||||
uint32_t scr = fpga_reg_get(REG_SD_SCR);
|
||||
uint32_t clock_mode_50mhz = ((scr & SD_SCR_CLOCK_MODE_MASK) == SD_SCR_CLOCK_MODE_50MHZ) ? 1 : 0;
|
||||
uint32_t card_type_block = p.card_type_block ? 1 : 0;
|
||||
uint32_t card_initialized = p.card_initialized ? 1 : 0;
|
||||
uint32_t card_inserted = (scr & SD_SCR_CARD_INSERTED) ? 1 : 0;
|
||||
return (
|
||||
(clock_mode_50mhz << 3) |
|
||||
(card_type_block << 2) |
|
||||
(card_initialized << 1) |
|
||||
(card_inserted << 0)
|
||||
);
|
||||
}
|
||||
|
||||
bool sd_card_get_info (uint32_t address) {
|
||||
if (!p.card_initialized) {
|
||||
return true;
|
||||
}
|
||||
fpga_mem_write(address, sizeof(p.csd), p.csd);
|
||||
address += sizeof(p.csd);
|
||||
fpga_mem_write(address, sizeof(p.cid), p.cid);
|
||||
address += sizeof(p.cid);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
||||
if (!p.card_initialized || (count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!p.card_type_block) {
|
||||
sector *= SD_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||
led_blink_act();
|
||||
if (sd_cmd(23, blocks, RSP_R1, NULL)) {
|
||||
return true;
|
||||
}
|
||||
if (sd_cmd(25, sector, RSP_R1, NULL)) {
|
||||
return true;
|
||||
}
|
||||
sd_dat_prepare(address, blocks, DAT_WRITE);
|
||||
if (sd_dat_wait(DAT_TIMEOUT_MS)) {
|
||||
sd_dat_abort();
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
return true;
|
||||
}
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
address += (blocks * SD_SECTOR_SIZE);
|
||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||
count -= blocks;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
||||
if (!p.card_initialized || (count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!p.card_type_block) {
|
||||
sector *= SD_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||
led_blink_act();
|
||||
sd_dat_prepare(address, blocks, DAT_READ);
|
||||
if (sd_cmd(23, blocks, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
return true;
|
||||
}
|
||||
if (sd_cmd(18, sector, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
return true;
|
||||
}
|
||||
if (sd_dat_wait(DAT_TIMEOUT_MS)) {
|
||||
if (sd_did_timeout()) {
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
address += (blocks * SD_SECTOR_SIZE);
|
||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||
count -= blocks;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors) {
|
||||
uint32_t starting_sector = 0;
|
||||
uint32_t sectors_to_process = 0;
|
||||
|
||||
if (count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (sector_table[i] == 0) {
|
||||
return true;
|
||||
}
|
||||
sectors_to_process += 1;
|
||||
if ((i < (count - 1)) && ((sector_table[i] + 1) == sector_table[i + 1])) {
|
||||
continue;
|
||||
}
|
||||
bool error = sd_process_sectors(address, sector_table[starting_sector], sectors_to_process);
|
||||
if (error) {
|
||||
return true;
|
||||
}
|
||||
address += (sectors_to_process * SD_SECTOR_SIZE);
|
||||
starting_sector += sectors_to_process;
|
||||
sectors_to_process = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sd_init (void) {
|
||||
p.card_initialized = false;
|
||||
sd_set_clock(CLOCK_STOP);
|
||||
}
|
||||
|
||||
void sd_process (void) {
|
||||
if (!sd_card_is_inserted()) {
|
||||
sd_card_deinit();
|
||||
}
|
||||
}
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "fpga.h"
|
||||
#include "hw.h"
|
||||
#include "led.h"
|
||||
#include "sd.h"
|
||||
|
||||
|
||||
#define SD_INIT_BUFFER_ADDRESS (0x05002800UL)
|
||||
|
||||
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
||||
#define CMD6_ARG_SWITCH_HS (0x80FFFFF1UL)
|
||||
|
||||
#define CMD8_ARG_SUPPLY_VOLTAGE_27_36_V (1 << 8)
|
||||
#define CMD8_ARG_CHECK_PATTERN (0xAA << 0)
|
||||
|
||||
#define ACMD6_ARG_BUS_WIDTH_4BIT (2 << 0)
|
||||
|
||||
#define ACMD41_ARG_OCR (0x300000 << 0)
|
||||
#define ACMD41_ARG_HCS (1 << 30)
|
||||
|
||||
#define R3_OCR (0x300000 << 0)
|
||||
#define R3_CCS (1 << 30)
|
||||
#define R3_BUSY (1 << 31)
|
||||
|
||||
#define R6_RCA_MASK (0xFFFF0000UL)
|
||||
|
||||
#define R7_SUPPLY_VOLTAGE_27_36_V (1 << 8)
|
||||
#define R7_CHECK_PATTERN (0xAA << 0)
|
||||
|
||||
#define SWITCH_FUNCTION_CURRENT_LIMIT (SD_INIT_BUFFER_ADDRESS + 0)
|
||||
#define SWITCH_FUNCTION_GROUP_1 (SD_INIT_BUFFER_ADDRESS + 12)
|
||||
#define SWITCH_FUNCTION_GROUP_1_HS (1 << 1)
|
||||
|
||||
#define DAT_BLOCK_MAX_COUNT (256)
|
||||
#define DAT_TIMEOUT_INIT_MS (2000)
|
||||
#define DAT_TIMEOUT_DATA_MS (5000)
|
||||
|
||||
|
||||
typedef enum {
|
||||
CLOCK_STOP,
|
||||
CLOCK_400KHZ,
|
||||
CLOCK_25MHZ,
|
||||
CLOCK_50MHZ,
|
||||
} sd_clock_t;
|
||||
|
||||
typedef enum {
|
||||
RSP_NONE,
|
||||
RSP_R1,
|
||||
RSP_R1b,
|
||||
RSP_R2,
|
||||
RSP_R3,
|
||||
RSP_R6,
|
||||
RSP_R7,
|
||||
} rsp_type_t;
|
||||
|
||||
typedef enum {
|
||||
DAT_READ,
|
||||
DAT_WRITE,
|
||||
} dat_mode_t;
|
||||
|
||||
|
||||
struct process {
|
||||
bool card_initialized;
|
||||
bool card_type_block;
|
||||
uint32_t rca;
|
||||
uint8_t csd[16];
|
||||
uint8_t cid[16];
|
||||
volatile bool timeout;
|
||||
};
|
||||
|
||||
|
||||
static struct process p;
|
||||
|
||||
|
||||
static void sd_trigger_timeout (void) {
|
||||
p.timeout = true;
|
||||
}
|
||||
|
||||
static void sd_prepare_timeout (uint16_t value) {
|
||||
p.timeout = false;
|
||||
hw_tim_setup(TIM_ID_SD, value, sd_trigger_timeout);
|
||||
}
|
||||
|
||||
static bool sd_did_timeout (void) {
|
||||
return p.timeout;
|
||||
}
|
||||
|
||||
static void sd_clear_timeout (void) {
|
||||
hw_tim_stop(TIM_ID_SD);
|
||||
p.timeout = false;
|
||||
}
|
||||
|
||||
static void sd_set_clock (sd_clock_t mode) {
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_OFF);
|
||||
|
||||
switch (mode) {
|
||||
case CLOCK_400KHZ:
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_400KHZ);
|
||||
break;
|
||||
case CLOCK_25MHZ:
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_25MHZ);
|
||||
break;
|
||||
case CLOCK_50MHZ:
|
||||
fpga_reg_set(REG_SD_SCR, SD_SCR_CLOCK_MODE_50MHZ);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
|
||||
uint32_t scr;
|
||||
uint32_t cmd_data;
|
||||
|
||||
cmd_data = ((cmd << SD_CMD_INDEX_BIT) & SD_CMD_INDEX_MASK);
|
||||
switch (rsp_type) {
|
||||
case RSP_NONE:
|
||||
cmd_data |= SD_CMD_SKIP_RESPONSE;
|
||||
break;
|
||||
case RSP_R2:
|
||||
cmd_data |= (SD_CMD_LONG_RESPONSE | SD_CMD_RESERVED_RESPONSE);
|
||||
break;
|
||||
case RSP_R3:
|
||||
cmd_data |= (SD_CMD_IGNORE_CRC | SD_CMD_RESERVED_RESPONSE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_SD_ARG, arg);
|
||||
fpga_reg_set(REG_SD_CMD, cmd_data);
|
||||
|
||||
do {
|
||||
scr = fpga_reg_get(REG_SD_SCR);
|
||||
} while (scr & SD_SCR_CMD_BUSY);
|
||||
|
||||
if (rsp != NULL) {
|
||||
if (cmd_data & SD_CMD_LONG_RESPONSE) {
|
||||
uint8_t *rsp_8 = (uint8_t *) (rsp);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
uint32_t rsp_data = fpga_reg_get(REG_SD_RSP_3 - i);
|
||||
uint8_t *rsp_data_8 = (uint8_t *) (&rsp_data);
|
||||
rsp_data = SWAP32(rsp_data);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
*rsp_8++ = *rsp_data_8++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(*(uint32_t *) (rsp)) = fpga_reg_get(REG_SD_RSP_0);
|
||||
}
|
||||
}
|
||||
|
||||
if (rsp_type == RSP_R1b) {
|
||||
do {
|
||||
scr = fpga_reg_get(REG_SD_SCR);
|
||||
} while (scr & SD_SCR_CARD_BUSY);
|
||||
}
|
||||
|
||||
return (scr & SD_SCR_CMD_ERROR);
|
||||
}
|
||||
|
||||
static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
|
||||
if (sd_cmd(55, p.rca, RSP_R1, NULL)) {
|
||||
return true;
|
||||
}
|
||||
if (sd_cmd(acmd, arg, rsp_type, rsp)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sd_dat_prepare (uint32_t address, uint32_t count, dat_mode_t mode) {
|
||||
uint32_t length = (count * SD_SECTOR_SIZE);
|
||||
uint32_t sd_dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_FIFO_FLUSH);
|
||||
uint32_t sd_dma_scr = DMA_SCR_START;
|
||||
|
||||
if (mode == DAT_READ) {
|
||||
sd_dat |= SD_DAT_START_READ;
|
||||
sd_dma_scr |= DMA_SCR_DIRECTION;
|
||||
} else {
|
||||
sd_dat |= SD_DAT_START_WRITE;
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_SD_DAT, sd_dat);
|
||||
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
|
||||
fpga_reg_set(REG_SD_DMA_LENGTH, length);
|
||||
fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr);
|
||||
}
|
||||
|
||||
static void sd_dat_abort (void) {
|
||||
fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP);
|
||||
fpga_reg_set(REG_SD_DAT, SD_DAT_STOP | SD_DAT_FIFO_FLUSH);
|
||||
}
|
||||
|
||||
static bool sd_dat_wait (uint16_t timeout) {
|
||||
sd_prepare_timeout(timeout);
|
||||
|
||||
do {
|
||||
uint32_t sd_dat = fpga_reg_get(REG_SD_DAT);
|
||||
uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR);
|
||||
led_blink_act();
|
||||
if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) {
|
||||
sd_clear_timeout();
|
||||
return (sd_dat & SD_DAT_ERROR);
|
||||
}
|
||||
} while (!sd_did_timeout());
|
||||
|
||||
sd_dat_abort();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool sd_card_init (void) {
|
||||
uint32_t arg;
|
||||
uint32_t rsp;
|
||||
uint16_t tmp;
|
||||
|
||||
if (p.card_initialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
p.card_initialized = true;
|
||||
p.rca = 0;
|
||||
|
||||
led_blink_act();
|
||||
|
||||
sd_set_clock(CLOCK_400KHZ);
|
||||
|
||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||
|
||||
arg = (CMD8_ARG_SUPPLY_VOLTAGE_27_36_V | CMD8_ARG_CHECK_PATTERN);
|
||||
if (sd_cmd(8, arg, RSP_R7, &rsp)) {
|
||||
arg = ACMD41_ARG_OCR;
|
||||
} else {
|
||||
if (rsp != (R7_SUPPLY_VOLTAGE_27_36_V | R7_CHECK_PATTERN)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
arg = (ACMD41_ARG_HCS | ACMD41_ARG_OCR);
|
||||
}
|
||||
|
||||
sd_prepare_timeout(1000);
|
||||
do {
|
||||
if (sd_did_timeout()) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
if (sd_acmd(41, arg, RSP_R3, &rsp)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
if (rsp & R3_BUSY) {
|
||||
if ((rsp & R3_OCR) == 0) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
p.card_type_block = (rsp & R3_CCS);
|
||||
break;
|
||||
}
|
||||
} while (1);
|
||||
sd_clear_timeout();
|
||||
|
||||
if (sd_cmd(2, 0, RSP_R2, NULL)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sd_cmd(3, 0, RSP_R6, &rsp)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
p.rca = (rsp & R6_RCA_MASK);
|
||||
|
||||
if (sd_cmd(9, p.rca, RSP_R2, p.csd)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sd_cmd(10, p.rca, RSP_R2, p.cid)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sd_cmd(7, p.rca, RSP_R1b, NULL)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
sd_set_clock(CLOCK_25MHZ);
|
||||
|
||||
if (sd_acmd(6, ACMD6_ARG_BUS_WIDTH_4BIT, RSP_R1, NULL)) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
|
||||
sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ);
|
||||
if (sd_cmd(6, CMD6_ARG_CHECK_HS, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
sd_dat_wait(DAT_TIMEOUT_INIT_MS);
|
||||
if (sd_did_timeout()) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
fpga_mem_read(SWITCH_FUNCTION_CURRENT_LIMIT, 2, (uint8_t *) (&tmp));
|
||||
if (SWAP16(tmp) == 0) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp));
|
||||
if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) {
|
||||
sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ);
|
||||
if (sd_cmd(6, CMD6_ARG_SWITCH_HS, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
sd_dat_wait(DAT_TIMEOUT_INIT_MS);
|
||||
if (sd_did_timeout()) {
|
||||
sd_card_deinit();
|
||||
return true;
|
||||
}
|
||||
fpga_mem_read(SWITCH_FUNCTION_GROUP_1, 2, (uint8_t *) (&tmp));
|
||||
if (SWAP16(tmp) & SWITCH_FUNCTION_GROUP_1_HS) {
|
||||
sd_set_clock(CLOCK_50MHZ);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sd_card_deinit (void) {
|
||||
if (p.card_initialized) {
|
||||
p.card_initialized = false;
|
||||
sd_set_clock(CLOCK_400KHZ);
|
||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||
sd_set_clock(CLOCK_STOP);
|
||||
}
|
||||
}
|
||||
|
||||
bool sd_card_is_inserted (void) {
|
||||
return (fpga_reg_get(REG_SD_SCR) & SD_SCR_CARD_INSERTED);
|
||||
}
|
||||
|
||||
uint32_t sd_card_get_status (void) {
|
||||
uint32_t scr = fpga_reg_get(REG_SD_SCR);
|
||||
uint32_t clock_mode_50mhz = ((scr & SD_SCR_CLOCK_MODE_MASK) == SD_SCR_CLOCK_MODE_50MHZ) ? 1 : 0;
|
||||
uint32_t card_type_block = p.card_type_block ? 1 : 0;
|
||||
uint32_t card_initialized = p.card_initialized ? 1 : 0;
|
||||
uint32_t card_inserted = (scr & SD_SCR_CARD_INSERTED) ? 1 : 0;
|
||||
return (
|
||||
(clock_mode_50mhz << 3) |
|
||||
(card_type_block << 2) |
|
||||
(card_initialized << 1) |
|
||||
(card_inserted << 0)
|
||||
);
|
||||
}
|
||||
|
||||
bool sd_card_get_info (uint32_t address) {
|
||||
if (!p.card_initialized) {
|
||||
return true;
|
||||
}
|
||||
fpga_mem_write(address, sizeof(p.csd), p.csd);
|
||||
address += sizeof(p.csd);
|
||||
fpga_mem_write(address, sizeof(p.cid), p.cid);
|
||||
address += sizeof(p.cid);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
||||
if (!p.card_initialized || (count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!p.card_type_block) {
|
||||
sector *= SD_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||
led_blink_act();
|
||||
if (sd_cmd(25, sector, RSP_R1, NULL)) {
|
||||
return true;
|
||||
}
|
||||
sd_dat_prepare(address, blocks, DAT_WRITE);
|
||||
if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) {
|
||||
sd_dat_abort();
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
return true;
|
||||
}
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
address += (blocks * SD_SECTOR_SIZE);
|
||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||
count -= blocks;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
||||
if (!p.card_initialized || (count == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!p.card_type_block) {
|
||||
sector *= SD_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
while (count > 0) {
|
||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||
led_blink_act();
|
||||
sd_dat_prepare(address, blocks, DAT_READ);
|
||||
if (sd_cmd(18, sector, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
return true;
|
||||
}
|
||||
if (sd_dat_wait(DAT_TIMEOUT_DATA_MS)) {
|
||||
if (sd_did_timeout()) {
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
address += (blocks * SD_SECTOR_SIZE);
|
||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||
count -= blocks;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors) {
|
||||
uint32_t starting_sector = 0;
|
||||
uint32_t sectors_to_process = 0;
|
||||
|
||||
if (count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (sector_table[i] == 0) {
|
||||
return true;
|
||||
}
|
||||
sectors_to_process += 1;
|
||||
if ((i < (count - 1)) && ((sector_table[i] + 1) == sector_table[i + 1])) {
|
||||
continue;
|
||||
}
|
||||
bool error = sd_process_sectors(address, sector_table[starting_sector], sectors_to_process);
|
||||
if (error) {
|
||||
return true;
|
||||
}
|
||||
address += (sectors_to_process * SD_SECTOR_SIZE);
|
||||
starting_sector += sectors_to_process;
|
||||
sectors_to_process = 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void sd_init (void) {
|
||||
p.card_initialized = false;
|
||||
sd_set_clock(CLOCK_STOP);
|
||||
}
|
||||
|
||||
void sd_process (void) {
|
||||
if (!sd_card_is_inserted()) {
|
||||
sd_card_deinit();
|
||||
}
|
||||
}
|
||||
|
@ -160,14 +160,14 @@ static void usb_rx_process (void) {
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_info.data_length = 4;
|
||||
p.response_info.data[0] = cfg_get_version();
|
||||
p.response_info.data[0] = cfg_get_identifier();
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_info.data_length = 4;
|
||||
p.response_info.data[0] = version_api(API_USB);
|
||||
p.response_info.data[0] = version_firmware();
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
@ -247,12 +247,6 @@ static void usb_rx_process (void) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
dd_set_block_ready(p.rx_args[0] == 0);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case 'U':
|
||||
if ((p.read_length > 0) && usb_dma_ready()) {
|
||||
uint32_t length = (p.read_length > p.rx_args[1]) ? p.rx_args[1] : p.read_length;
|
||||
@ -274,6 +268,28 @@ static void usb_rx_process (void) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'D':
|
||||
dd_set_block_ready(p.rx_args[0] == 0);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (p.rx_args[0]) {
|
||||
flash_wait_busy();
|
||||
}
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_info.data_length = 4;
|
||||
p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
p.response_error = flash_erase_block(p.rx_args[0]);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case 'f': {
|
||||
bool rom_write_enable_restore = cfg_set_rom_write_enable(false);
|
||||
p.response_info.data[0] = update_backup(p.rx_args[0], &p.response_info.data[1]);
|
||||
@ -300,22 +316,6 @@ static void usb_rx_process (void) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
if (p.rx_args[0]) {
|
||||
flash_wait_busy();
|
||||
}
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_info.data_length = 4;
|
||||
p.response_info.data[0] = FLASH_ERASE_BLOCK_SIZE;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
p.response_error = flash_erase_block(p.rx_args[0]);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
|
@ -1,17 +1,10 @@
|
||||
#include "version.h"
|
||||
|
||||
|
||||
#define VERSION_API_USB (2)
|
||||
#define VERSION_API_N64 (2)
|
||||
#define VERSION_MAJOR (2)
|
||||
#define VERSION_MINOR (12)
|
||||
|
||||
|
||||
uint32_t version_api (version_api_type_t type) {
|
||||
switch (type) {
|
||||
case API_USB:
|
||||
return VERSION_API_USB;
|
||||
case API_N64:
|
||||
return VERSION_API_N64;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
uint32_t version_firmware (void) {
|
||||
return ((VERSION_MAJOR << 16) | (VERSION_MINOR));
|
||||
}
|
||||
|
@ -5,13 +5,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef enum {
|
||||
API_USB,
|
||||
API_N64,
|
||||
} version_api_type_t;
|
||||
|
||||
|
||||
uint32_t version_api (version_api_type_t type);
|
||||
uint32_t version_firmware (void);
|
||||
|
||||
|
||||
#endif
|
||||
|
236
sw/pc/dd64.py
236
sw/pc/dd64.py
@ -1,236 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
from io import BufferedReader
|
||||
from typing import Optional
|
||||
|
||||
|
||||
|
||||
class BadBlockError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DD64Image:
|
||||
__DISK_HEADS = 2
|
||||
__DISK_TRACKS = 1175
|
||||
__DISK_BLOCKS_PER_TRACK = 2
|
||||
__DISK_SECTORS_PER_BLOCK = 85
|
||||
__DISK_BAD_TRACKS_PER_ZONE = 12
|
||||
__DISK_SYSTEM_SECTOR_SIZE = 232
|
||||
__DISK_ZONES = [
|
||||
(0, 232, 158, 0),
|
||||
(0, 216, 158, 158),
|
||||
(0, 208, 149, 316),
|
||||
(0, 192, 149, 465),
|
||||
(0, 176, 149, 614),
|
||||
(0, 160, 149, 763),
|
||||
(0, 144, 149, 912),
|
||||
(0, 128, 114, 1061),
|
||||
(1, 216, 158, 157),
|
||||
(1, 208, 158, 315),
|
||||
(1, 192, 149, 464),
|
||||
(1, 176, 149, 613),
|
||||
(1, 160, 149, 762),
|
||||
(1, 144, 149, 911),
|
||||
(1, 128, 149, 1060),
|
||||
(1, 112, 114, 1174),
|
||||
]
|
||||
__DISK_VZONE_TO_PZONE = [
|
||||
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
|
||||
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
|
||||
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
|
||||
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
|
||||
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
|
||||
]
|
||||
__DISK_DRIVE_TYPES = [(
|
||||
'development',
|
||||
192,
|
||||
[11, 10, 3, 2],
|
||||
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||
), (
|
||||
'retail',
|
||||
232,
|
||||
[9, 8, 1, 0],
|
||||
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||
)]
|
||||
|
||||
__file: Optional[BufferedReader]
|
||||
__drive_type: Optional[str]
|
||||
__block_info_table: list[tuple[int, int]]
|
||||
loaded: bool = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__file = None
|
||||
self.__drive_type = None
|
||||
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
|
||||
self.__block_info_table = [None] * block_info_table_length
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.unload()
|
||||
|
||||
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
|
||||
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
|
||||
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||
system_data = system_block_data[:sector_size]
|
||||
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
|
||||
sector_data = system_block_data[(sector * sector_size):][:sector_size]
|
||||
if (system_data != sector_data):
|
||||
return (False, None)
|
||||
if (check_disk_type):
|
||||
if (system_data[4] != 0x10):
|
||||
return (False, None)
|
||||
if ((system_data[5] & 0xF0) != 0x10):
|
||||
return (False, None)
|
||||
return (True, system_data)
|
||||
|
||||
def __parse_disk(self) -> None:
|
||||
disk_system_data = None
|
||||
disk_id_data = None
|
||||
disk_bad_lbas = []
|
||||
|
||||
drive_index = 0
|
||||
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
|
||||
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
|
||||
disk_bad_lbas.clear()
|
||||
disk_bad_lbas.extend(bad_lbas)
|
||||
for system_lba in system_data_lbas:
|
||||
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
|
||||
if (valid):
|
||||
self.__drive_type = drive_type
|
||||
disk_system_data = system_data
|
||||
else:
|
||||
disk_bad_lbas.append(system_lba)
|
||||
drive_index += 1
|
||||
|
||||
for id_lba in [15, 14]:
|
||||
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
|
||||
if (valid):
|
||||
disk_id_data = id_data
|
||||
else:
|
||||
disk_bad_lbas.append(id_lba)
|
||||
|
||||
if not (disk_system_data and disk_id_data):
|
||||
raise ValueError('Provided 64DD disk file is not valid')
|
||||
|
||||
disk_zone_bad_tracks = []
|
||||
|
||||
for zone in range(len(self.__DISK_ZONES)):
|
||||
zone_bad_tracks = []
|
||||
start = 0 if zone == 0 else system_data[0x07 + zone]
|
||||
stop = system_data[0x07 + zone + 1]
|
||||
for offset in range(start, stop):
|
||||
zone_bad_tracks.append(system_data[0x20 + offset])
|
||||
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
|
||||
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
|
||||
disk_zone_bad_tracks.append(zone_bad_tracks)
|
||||
|
||||
disk_type = disk_system_data[5] & 0x0F
|
||||
|
||||
current_lba = 0
|
||||
starting_block = 0
|
||||
disk_file_offset = 0
|
||||
|
||||
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
|
||||
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
|
||||
|
||||
for zone_track in range(tracks):
|
||||
current_zone_track = (
|
||||
(tracks - 1) - zone_track) if head else zone_track
|
||||
|
||||
if (current_zone_track in disk_zone_bad_tracks[zone]):
|
||||
track += (-1) if head else 1
|
||||
continue
|
||||
|
||||
for block in range(self.__DISK_BLOCKS_PER_TRACK):
|
||||
index = (track << 2) | (head << 1) | (starting_block ^ block)
|
||||
if (current_lba not in disk_bad_lbas):
|
||||
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||
else:
|
||||
self.__block_info_table[index] = None
|
||||
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
|
||||
current_lba += 1
|
||||
|
||||
track += (-1) if head else 1
|
||||
starting_block ^= 1
|
||||
|
||||
def __check_track_head_block(self, track: int, head: int, block: int) -> None:
|
||||
if (track < 0 or track >= self.__DISK_TRACKS):
|
||||
raise ValueError('Track outside of possible range')
|
||||
if (head < 0 or head >= self.__DISK_HEADS):
|
||||
raise ValueError('Head outside of possible range')
|
||||
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
|
||||
raise ValueError('Block outside of possible range')
|
||||
|
||||
def __get_table_index(self, track: int, head: int, block: int) -> int:
|
||||
return (track << 2) | (head << 1) | (block)
|
||||
|
||||
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
|
||||
if (self.__file.closed):
|
||||
return None
|
||||
self.__check_track_head_block(track, head, block)
|
||||
index = self.__get_table_index(track, head, block)
|
||||
return self.__block_info_table[index]
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
self.unload()
|
||||
self.__file = open(path, 'rb+')
|
||||
self.__parse_disk()
|
||||
self.loaded = True
|
||||
|
||||
def unload(self) -> None:
|
||||
self.loaded = False
|
||||
if (self.__file != None and not self.__file.closed):
|
||||
self.__file.close()
|
||||
self.__drive_type = None
|
||||
|
||||
def get_block_info_table(self) -> list[tuple[int, int]]:
|
||||
return self.__block_info_table
|
||||
|
||||
def get_drive_type(self) -> str:
|
||||
return self.__drive_type
|
||||
|
||||
def read_block(self, track: int, head: int, block: int) -> bytes:
|
||||
info = self.__get_block_info(track, head, block)
|
||||
if (info == None):
|
||||
raise BadBlockError
|
||||
(offset, block_size) = info
|
||||
self.__file.seek(offset)
|
||||
return self.__file.read(block_size)
|
||||
|
||||
def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
|
||||
info = self.__get_block_info(track, head, block)
|
||||
if (info == None):
|
||||
raise BadBlockError
|
||||
(offset, block_size) = info
|
||||
if (len(data) != block_size):
|
||||
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
|
||||
self.__file.seek(offset)
|
||||
self.__file.write(data)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
id_lba_locations = [
|
||||
(7, 0, 1),
|
||||
(7, 0, 0)
|
||||
]
|
||||
if (len(sys.argv) >= 2):
|
||||
dd = DD64Image()
|
||||
dd.load(sys.argv[1])
|
||||
print(dd.get_drive_type())
|
||||
for (track, head, block) in id_lba_locations:
|
||||
try:
|
||||
print(dd.read_block(track, head, block)[:4])
|
||||
except BadBlockError:
|
||||
print(f'Bad ID block [track: {track}, head: {head}, block: {block}]')
|
||||
if (len(sys.argv) >= 3):
|
||||
with open(sys.argv[2], 'wb') as f:
|
||||
block_info_table = dd.get_block_info_table()
|
||||
for block in block_info_table:
|
||||
offset = 0xFFFFFFFF if block == None else block[0]
|
||||
f.write(offset.to_bytes(4, byteorder='big'))
|
||||
dd.unload()
|
||||
else:
|
||||
print(f'[{sys.argv[0]}]: Expected disk image path as first argument')
|
309
sw/pc/sc64.py
309
sw/pc/sc64.py
@ -9,8 +9,8 @@ import sys
|
||||
import time
|
||||
from binascii import crc32
|
||||
from datetime import datetime
|
||||
from dd64 import BadBlockError, DD64Image
|
||||
from enum import Enum, IntEnum
|
||||
from io import BufferedReader
|
||||
from serial.tools import list_ports
|
||||
from threading import Thread
|
||||
from typing import Callable, Optional
|
||||
@ -18,6 +18,210 @@ from PIL import Image
|
||||
|
||||
|
||||
|
||||
class BadBlockError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class DD64Image:
|
||||
__DISK_HEADS = 2
|
||||
__DISK_TRACKS = 1175
|
||||
__DISK_BLOCKS_PER_TRACK = 2
|
||||
__DISK_SECTORS_PER_BLOCK = 85
|
||||
__DISK_BAD_TRACKS_PER_ZONE = 12
|
||||
__DISK_SYSTEM_SECTOR_SIZE = 232
|
||||
__DISK_ZONES = [
|
||||
(0, 232, 158, 0),
|
||||
(0, 216, 158, 158),
|
||||
(0, 208, 149, 316),
|
||||
(0, 192, 149, 465),
|
||||
(0, 176, 149, 614),
|
||||
(0, 160, 149, 763),
|
||||
(0, 144, 149, 912),
|
||||
(0, 128, 114, 1061),
|
||||
(1, 216, 158, 157),
|
||||
(1, 208, 158, 315),
|
||||
(1, 192, 149, 464),
|
||||
(1, 176, 149, 613),
|
||||
(1, 160, 149, 762),
|
||||
(1, 144, 149, 911),
|
||||
(1, 128, 149, 1060),
|
||||
(1, 112, 114, 1174),
|
||||
]
|
||||
__DISK_VZONE_TO_PZONE = [
|
||||
[0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10],
|
||||
[0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11],
|
||||
[0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12],
|
||||
[0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13],
|
||||
[0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15],
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8],
|
||||
]
|
||||
__DISK_DRIVE_TYPES = [(
|
||||
'development',
|
||||
192,
|
||||
[11, 10, 3, 2],
|
||||
[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||
), (
|
||||
'retail',
|
||||
232,
|
||||
[9, 8, 1, 0],
|
||||
[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||
)]
|
||||
|
||||
__file: Optional[BufferedReader]
|
||||
__drive_type: Optional[str]
|
||||
__block_info_table: list[tuple[int, int]]
|
||||
loaded: bool = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__file = None
|
||||
self.__drive_type = None
|
||||
block_info_table_length = self.__DISK_HEADS * self.__DISK_TRACKS * self.__DISK_BLOCKS_PER_TRACK
|
||||
self.__block_info_table = [None] * block_info_table_length
|
||||
|
||||
def __del__(self) -> None:
|
||||
self.unload()
|
||||
|
||||
def __check_system_block(self, lba: int, sector_size: int, check_disk_type: bool) -> tuple[bool, bytes]:
|
||||
self.__file.seek(lba * self.__DISK_SYSTEM_SECTOR_SIZE * self.__DISK_SECTORS_PER_BLOCK)
|
||||
system_block_data = self.__file.read(sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||
system_data = system_block_data[:sector_size]
|
||||
for sector in range(1, self.__DISK_SECTORS_PER_BLOCK):
|
||||
sector_data = system_block_data[(sector * sector_size):][:sector_size]
|
||||
if (system_data != sector_data):
|
||||
return (False, None)
|
||||
if (check_disk_type):
|
||||
if (system_data[4] != 0x10):
|
||||
return (False, None)
|
||||
if ((system_data[5] & 0xF0) != 0x10):
|
||||
return (False, None)
|
||||
return (True, system_data)
|
||||
|
||||
def __parse_disk(self) -> None:
|
||||
disk_system_data = None
|
||||
disk_id_data = None
|
||||
disk_bad_lbas = []
|
||||
|
||||
drive_index = 0
|
||||
while (disk_system_data == None) and (drive_index < len(self.__DISK_DRIVE_TYPES)):
|
||||
(drive_type, system_sector_size, system_data_lbas, bad_lbas) = self.__DISK_DRIVE_TYPES[drive_index]
|
||||
disk_bad_lbas.clear()
|
||||
disk_bad_lbas.extend(bad_lbas)
|
||||
for system_lba in system_data_lbas:
|
||||
(valid, system_data) = self.__check_system_block(system_lba, system_sector_size, check_disk_type=True)
|
||||
if (valid):
|
||||
self.__drive_type = drive_type
|
||||
disk_system_data = system_data
|
||||
else:
|
||||
disk_bad_lbas.append(system_lba)
|
||||
drive_index += 1
|
||||
|
||||
for id_lba in [15, 14]:
|
||||
(valid, id_data) = self.__check_system_block(id_lba, self.__DISK_SYSTEM_SECTOR_SIZE, check_disk_type=False)
|
||||
if (valid):
|
||||
disk_id_data = id_data
|
||||
else:
|
||||
disk_bad_lbas.append(id_lba)
|
||||
|
||||
if not (disk_system_data and disk_id_data):
|
||||
raise ValueError('Provided 64DD disk file is not valid')
|
||||
|
||||
disk_zone_bad_tracks = []
|
||||
|
||||
for zone in range(len(self.__DISK_ZONES)):
|
||||
zone_bad_tracks = []
|
||||
start = 0 if zone == 0 else system_data[0x07 + zone]
|
||||
stop = system_data[0x07 + zone + 1]
|
||||
for offset in range(start, stop):
|
||||
zone_bad_tracks.append(system_data[0x20 + offset])
|
||||
for ignored_track in range(self.__DISK_BAD_TRACKS_PER_ZONE - len(zone_bad_tracks)):
|
||||
zone_bad_tracks.append(self.__DISK_ZONES[zone][2] - ignored_track - 1)
|
||||
disk_zone_bad_tracks.append(zone_bad_tracks)
|
||||
|
||||
disk_type = disk_system_data[5] & 0x0F
|
||||
|
||||
current_lba = 0
|
||||
starting_block = 0
|
||||
disk_file_offset = 0
|
||||
|
||||
for zone in self.__DISK_VZONE_TO_PZONE[disk_type]:
|
||||
(head, sector_size, tracks, track) = self.__DISK_ZONES[zone]
|
||||
|
||||
for zone_track in range(tracks):
|
||||
current_zone_track = (
|
||||
(tracks - 1) - zone_track) if head else zone_track
|
||||
|
||||
if (current_zone_track in disk_zone_bad_tracks[zone]):
|
||||
track += (-1) if head else 1
|
||||
continue
|
||||
|
||||
for block in range(self.__DISK_BLOCKS_PER_TRACK):
|
||||
index = (track << 2) | (head << 1) | (starting_block ^ block)
|
||||
if (current_lba not in disk_bad_lbas):
|
||||
self.__block_info_table[index] = (disk_file_offset, sector_size * self.__DISK_SECTORS_PER_BLOCK)
|
||||
else:
|
||||
self.__block_info_table[index] = None
|
||||
disk_file_offset += sector_size * self.__DISK_SECTORS_PER_BLOCK
|
||||
current_lba += 1
|
||||
|
||||
track += (-1) if head else 1
|
||||
starting_block ^= 1
|
||||
|
||||
def __check_track_head_block(self, track: int, head: int, block: int) -> None:
|
||||
if (track < 0 or track >= self.__DISK_TRACKS):
|
||||
raise ValueError('Track outside of possible range')
|
||||
if (head < 0 or head >= self.__DISK_HEADS):
|
||||
raise ValueError('Head outside of possible range')
|
||||
if (block < 0 or block >= self.__DISK_BLOCKS_PER_TRACK):
|
||||
raise ValueError('Block outside of possible range')
|
||||
|
||||
def __get_table_index(self, track: int, head: int, block: int) -> int:
|
||||
return (track << 2) | (head << 1) | (block)
|
||||
|
||||
def __get_block_info(self, track: int, head: int, block: int) -> Optional[tuple[int, int]]:
|
||||
if (self.__file.closed):
|
||||
return None
|
||||
self.__check_track_head_block(track, head, block)
|
||||
index = self.__get_table_index(track, head, block)
|
||||
return self.__block_info_table[index]
|
||||
|
||||
def load(self, path: str) -> None:
|
||||
self.unload()
|
||||
self.__file = open(path, 'rb+')
|
||||
self.__parse_disk()
|
||||
self.loaded = True
|
||||
|
||||
def unload(self) -> None:
|
||||
self.loaded = False
|
||||
if (self.__file != None and not self.__file.closed):
|
||||
self.__file.close()
|
||||
self.__drive_type = None
|
||||
|
||||
def get_block_info_table(self) -> list[tuple[int, int]]:
|
||||
return self.__block_info_table
|
||||
|
||||
def get_drive_type(self) -> str:
|
||||
return self.__drive_type
|
||||
|
||||
def read_block(self, track: int, head: int, block: int) -> bytes:
|
||||
info = self.__get_block_info(track, head, block)
|
||||
if (info == None):
|
||||
raise BadBlockError
|
||||
(offset, block_size) = info
|
||||
self.__file.seek(offset)
|
||||
return self.__file.read(block_size)
|
||||
|
||||
def write_block(self, track: int, head: int, block: int, data: bytes) -> None:
|
||||
info = self.__get_block_info(track, head, block)
|
||||
if (info == None):
|
||||
raise BadBlockError
|
||||
(offset, block_size) = info
|
||||
if (len(data) != block_size):
|
||||
raise ValueError(f'Provided data block size is different than expected ({len(data)} != {block_size})')
|
||||
self.__file.seek(offset)
|
||||
self.__file.write(data)
|
||||
|
||||
|
||||
class ConnectionException(Exception):
|
||||
pass
|
||||
|
||||
@ -200,6 +404,7 @@ class SC64:
|
||||
FLASH = 0x0400_0000
|
||||
BUFFER = 0x0500_0000
|
||||
EEPROM = 0x0500_2000
|
||||
END = 0x0500_297F
|
||||
FIRMWARE = 0x0200_0000
|
||||
DDIPL = 0x03BC_0000
|
||||
SAVE = 0x03FE_0000
|
||||
@ -321,7 +526,8 @@ class SC64:
|
||||
SCREENSHOT = 4
|
||||
GDB = 0xDB
|
||||
|
||||
__MIN_SUPPORTED_API_VERSION = 2
|
||||
__SUPPORTED_MAJOR_VERSION = 2
|
||||
__SUPPORTED_MINOR_VERSION = 12
|
||||
|
||||
__isv_line_buffer: bytes = b''
|
||||
__debug_header: Optional[bytes] = None
|
||||
@ -329,21 +535,25 @@ class SC64:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__link = SC64Serial()
|
||||
version = self.__link.execute_cmd(cmd=b'v')
|
||||
if (version != b'SCv2'):
|
||||
raise ConnectionException('Unknown SC64 HW version')
|
||||
identifier = self.__link.execute_cmd(cmd=b'v')
|
||||
if (identifier != b'SCv2'):
|
||||
raise ConnectionException('Unknown SC64 v2 identifier')
|
||||
|
||||
def __get_int(self, data: bytes) -> int:
|
||||
return int.from_bytes(data[:4], byteorder='big')
|
||||
|
||||
def check_api_version(self) -> None:
|
||||
def check_firmware_version(self) -> tuple[str, bool]:
|
||||
try:
|
||||
data = self.__link.execute_cmd(cmd=b'V')
|
||||
version = self.__link.execute_cmd(cmd=b'V')
|
||||
major = self.__get_int(version[0:2])
|
||||
minor = self.__get_int(version[2:4])
|
||||
if (major != self.__SUPPORTED_MAJOR_VERSION):
|
||||
raise ConnectionException()
|
||||
if (minor < self.__SUPPORTED_MINOR_VERSION):
|
||||
raise ConnectionException()
|
||||
return (f'{major}.{minor}', minor > self.__SUPPORTED_MINOR_VERSION)
|
||||
except ConnectionException:
|
||||
raise ConnectionException('Outdated SC64 API, please update firmware')
|
||||
version = self.__get_int(data)
|
||||
if (version < self.__MIN_SUPPORTED_API_VERSION):
|
||||
raise ConnectionException('Unsupported SC64 API version, please update firmware')
|
||||
raise ConnectionException(f'Unsupported SC64 version [{major}.{minor}], please update firmware')
|
||||
|
||||
def __set_config(self, config: __CfgId, value: int) -> None:
|
||||
try:
|
||||
@ -461,8 +671,10 @@ class SC64:
|
||||
raise ValueError('Debug data size too big')
|
||||
self.__link.execute_cmd(cmd=b'U', args=[datatype, len(data)], data=data, response=False)
|
||||
|
||||
def download_memory(self) -> bytes:
|
||||
return self.__read_memory(self.__Address.MEMORY, self.__Length.MEMORY)
|
||||
def download_memory(self, address: int, length: int) -> bytes:
|
||||
if ((address < 0) or (length < 0) or ((address + length) > self.__Address.END)):
|
||||
raise ValueError('Invalid address or length')
|
||||
return self.__read_memory(address, length)
|
||||
|
||||
def upload_rom(self, data: bytes, use_shadow: bool=True) -> None:
|
||||
rom_length = len(data)
|
||||
@ -582,7 +794,7 @@ class SC64:
|
||||
raise ConnectionException('Error while getting firmware backup')
|
||||
return self.__read_memory(address, length)
|
||||
|
||||
def set_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]:
|
||||
def update_cic_parameters(self, seed: Optional[int]=None, disabled=False) -> tuple[int, int, bool]:
|
||||
if ((seed != None) and (seed < 0 or seed > 0xFF)):
|
||||
raise ValueError('CIC seed outside of allowed values')
|
||||
boot_mode = self.__get_config(self.__CfgId.BOOT_MODE)
|
||||
@ -884,7 +1096,7 @@ class SC64:
|
||||
current_image = 0
|
||||
next_image = 0
|
||||
|
||||
self.__set_config(self.__CfgId.ROM_WRITE_ENABLE, isv)
|
||||
self.__set_config(self.__CfgId.ROM_WRITE_ENABLE, 1 if (isv != 0) else 0)
|
||||
self.__set_config(self.__CfgId.ISV_ADDRESS, isv)
|
||||
if (isv != 0):
|
||||
print(f'IS-Viewer64 support set to [ENABLED] at ROM offset [0x{(isv):08X}]')
|
||||
@ -992,16 +1204,24 @@ if __name__ == '__main__':
|
||||
disabled = bool(int(params[1])) if len(params) >= 2 else None
|
||||
return (seed, disabled)
|
||||
|
||||
def download_memory_type(argument: str):
|
||||
params = argument.split(',')
|
||||
if (len(params) < 2 or len(params) > 3):
|
||||
raise argparse.ArgumentError()
|
||||
address = int(params[0], 0)
|
||||
length = int(params[1], 0)
|
||||
file = params[2] if len(params) >= 3 else 'sc64dump.bin'
|
||||
return (address, length, file)
|
||||
|
||||
parser = argparse.ArgumentParser(description='SC64 control software')
|
||||
parser.add_argument('--backup-firmware', metavar='file', help='backup SC64 firmware and write it to specified file')
|
||||
parser.add_argument('--update-firmware', metavar='file', help='update SC64 firmware from specified file')
|
||||
parser.add_argument('--update-bootloader', metavar='file', help='update SC64 bootloader (not recommended, use --update-firmware instead)')
|
||||
parser.add_argument('--reset-state', action='store_true', help='reset SC64 internal state')
|
||||
parser.add_argument('--print-state', action='store_true', help='print SC64 internal state')
|
||||
parser.add_argument('--led-enabled', metavar='{true,false}', help='disable or enable LED I/O activity blinking')
|
||||
parser.add_argument('--led-blink', metavar='{yes,no}', help='disable or enable LED I/O activity blinking')
|
||||
parser.add_argument('--boot', type=SC64.BootMode, action=EnumAction, help='set boot mode')
|
||||
parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value')
|
||||
parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value')
|
||||
parser.add_argument('--tv', type=SC64.TVType, action=EnumAction, help='force TV type to set value, not used when direct boot mode is enabled')
|
||||
parser.add_argument('--cic', type=SC64.CICSeed, action=EnumAction, help='force CIC seed to set value, not used when direct boot mode is enabled')
|
||||
parser.add_argument('--cic-params', metavar='seed,[disabled]', type=cic_params_type, help='set CIC emulation parameters')
|
||||
parser.add_argument('--rtc', action='store_true', help='update clock in SC64 to system time')
|
||||
parser.add_argument('--no-shadow', action='store_false', help='do not put last 128 kB of ROM inside flash memory (can corrupt non EEPROM saves)')
|
||||
@ -1014,7 +1234,7 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--isv', type=lambda x: int(x, 0), default=0, help='enable IS-Viewer64 support at provided ROM offset')
|
||||
parser.add_argument('--gdb', metavar='port', type=int, help='expose socket port for GDB debugging')
|
||||
parser.add_argument('--debug', action='store_true', help='run debug loop')
|
||||
parser.add_argument('--download-memory', metavar='file', help='download whole memory space and write it to specified file')
|
||||
parser.add_argument('--download-memory', metavar='address,length,[file]', type=download_memory_type, help='download specified memory region and write it to file')
|
||||
|
||||
if (len(sys.argv) <= 1):
|
||||
parser.print_help()
|
||||
@ -1039,13 +1259,15 @@ if __name__ == '__main__':
|
||||
sc64.update_firmware(f.read(), status_callback)
|
||||
print('done')
|
||||
|
||||
sc64.check_api_version()
|
||||
(version, script_outdated) = sc64.check_firmware_version()
|
||||
|
||||
if (args.update_bootloader):
|
||||
with open(args.update_bootloader, 'rb') as f:
|
||||
print('Uploading Bootloader... ', end='', flush=True)
|
||||
sc64.upload_bootloader(f.read())
|
||||
print('done')
|
||||
print(f'SC64 firmware version: [{version}]')
|
||||
if (script_outdated):
|
||||
print('\x1b[33m')
|
||||
print('[ SC64 firmware is newer than last known version. ]')
|
||||
print('[ Consider downloading latest script from ]')
|
||||
print('[ https://github.com/Polprzewodnikowy/SummerCart64/releases ]')
|
||||
print('\x1b[0m')
|
||||
|
||||
if (args.reset_state):
|
||||
sc64.reset_state()
|
||||
@ -1059,10 +1281,10 @@ if __name__ == '__main__':
|
||||
value = getattr(value, 'name')
|
||||
print(f' {key}: {value}')
|
||||
|
||||
if (args.led_enabled != None):
|
||||
value = (args.led_enabled == 'true')
|
||||
sc64.set_led_enable(value)
|
||||
print(f'LED blinking set to [{"ENABLED" if value else "DISABLED"}]')
|
||||
if (args.led_blink):
|
||||
blink = (args.led_blink == 'yes')
|
||||
sc64.set_led_enable(blink)
|
||||
print(f'LED blinking set to [{"ENABLED" if blink else "DISABLED"}]')
|
||||
|
||||
if (args.tv != None):
|
||||
sc64.set_tv_type(args.tv)
|
||||
@ -1085,6 +1307,12 @@ if __name__ == '__main__':
|
||||
autodetected_save_type = sc64.autodetect_save_type(rom_data)
|
||||
print('done')
|
||||
|
||||
if (args.ddipl):
|
||||
with open(args.ddipl, 'rb') as f:
|
||||
print('Uploading 64DD IPL... ', end='', flush=True)
|
||||
sc64.upload_ddipl(f.read())
|
||||
print('done')
|
||||
|
||||
if (args.save_type != None or autodetected_save_type != None):
|
||||
save_type = args.save_type if args.save_type != None else autodetected_save_type
|
||||
sc64.set_save_type(save_type)
|
||||
@ -1096,26 +1324,20 @@ if __name__ == '__main__':
|
||||
sc64.upload_save(f.read())
|
||||
print('done')
|
||||
|
||||
if (args.ddipl):
|
||||
with open(args.ddipl, 'rb') as f:
|
||||
print('Uploading 64DD IPL... ', end='', flush=True)
|
||||
sc64.upload_ddipl(f.read())
|
||||
print('done')
|
||||
|
||||
if (args.boot != None):
|
||||
sc64.set_boot_mode(args.boot)
|
||||
print(f'Boot mode set to [{args.boot.name}]')
|
||||
|
||||
if (args.cic_params != None):
|
||||
(seed, disabled) = args.cic_params
|
||||
(seed, checksum, dd_mode) = sc64.set_cic_parameters(seed, disabled)
|
||||
(seed, checksum, dd_mode) = sc64.update_cic_parameters(seed, disabled)
|
||||
print('CIC parameters set to [', end='')
|
||||
print(f'{"DISABLED" if disabled else "ENABLED"}, ', end='')
|
||||
print(f'{"DDIPL" if dd_mode else "ROM"}, ', end='')
|
||||
print(f'seed: 0x{seed:02X}, checksum: 0x{checksum:012X}', end='')
|
||||
print(']')
|
||||
else:
|
||||
sc64.set_cic_parameters()
|
||||
sc64.update_cic_parameters()
|
||||
|
||||
if (args.debug or args.isv or args.disk or args.gdb):
|
||||
sc64.debug_loop(isv=args.isv, disks=args.disk, gdb_port=args.gdb)
|
||||
@ -1126,12 +1348,13 @@ if __name__ == '__main__':
|
||||
f.write(sc64.download_save())
|
||||
print('done')
|
||||
|
||||
if (args.download_memory):
|
||||
with open(args.download_memory, 'wb') as f:
|
||||
if (args.download_memory != None):
|
||||
(address, length, file) = args.download_memory
|
||||
with open(file, 'wb') as f:
|
||||
print('Downloading memory... ', end='', flush=True)
|
||||
f.write(sc64.download_memory())
|
||||
f.write(sc64.download_memory(address, length))
|
||||
print('done')
|
||||
except ValueError as e:
|
||||
print(f'\nValue error: {e}')
|
||||
print(f'\n\x1b[31mValue error: {e}\x1b[0m\n')
|
||||
except ConnectionException as e:
|
||||
print(f'\nSC64 error: {e}')
|
||||
print(f'\n\x1b[31mSC64 error: {e}\x1b[0m\n')
|
||||
|
Loading…
Reference in New Issue
Block a user