Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18b6ee2c41 | |||
| dfe68131c2 | |||
| af23c47fc7 | |||
| 32e0e27ca7 | |||
| bdc4780bcc | |||
| 7df10e6ea1 | |||
| 306ef90094 | |||
| fcd971a7d3 | |||
| c09d75b602 | |||
| c72ba780d4 | |||
| 5472293907 | |||
| 80ad791061 | |||
| f78f159fde | |||
| 1d66fcd077 | |||
| 7bf6322fc2 | |||
| 90e4604187 | |||
| 34727bf48e | |||
| 3bd62fc1eb | |||
| 11b37e9c2b | |||
| 9ed6ac32ce | |||
| 7eeedec527 | |||
| d30273aa0c | |||
| 79c6aea160 | |||
| b85e2cf1a3 | |||
| 78e2dd23f9 | |||
| df887d5665 | |||
| 62eebc6770 | |||
| 5a44b3f658 | |||
| c85f492c03 | |||
| 20102a2b7a | |||
| 110adc8a63 | |||
| 6fe5ac5edb | |||
| 923f988a32 | |||
| a6c40c5fa9 | |||
| 21a930dc09 | |||
| 8215eec0cf | |||
| bf6f492c49 | |||
| f7074bcf7d | |||
| 349509c90c | |||
| c38c98dfb0 | |||
| 69537126ec | |||
| 004e862715 | |||
| f1312c1b17 | |||
| 602448b456 | |||
| 21cafe4668 | |||
| 00f64ad2be | |||
| 41af66433a | |||
| 7dc471d687 | |||
| 41078f9150 | |||
| e9a56f73af | |||
| 7f91e039cb | |||
| 9e185fe353 | |||
| b17470a12b | |||
| 2a6012cbc3 | |||
| b786a67d38 | |||
| b9b0d7b1bc | |||
| 56692b7dae | |||
| 06f08b2378 | |||
| 0722681674 | |||
| caf9c49422 | |||
| 3890795aa9 | |||
| b9a64b76a4 | |||
| c51e0c782a | |||
| 70fa1db75f | |||
| a196d6c139 | |||
| 530fc7a8dc | |||
| d2372ad41c | |||
| 9cdec1ffba | |||
| 5e4e687403 | |||
| d07ffb3ae1 | |||
| 332779326a | |||
| 558f38682d | |||
| 3263b9964d | |||
| 0d11210173 | |||
| 9c187bd657 | |||
| a2d1efde46 | |||
| 16ecc6e71c | |||
| ecabeb1a4b | |||
| 7494036099 | |||
| 21427bf019 | |||
| fc1fdada3e | |||
| 3f01b61770 | |||
| dd97bc3177 | |||
| efd55042b2 | |||
| 8bafdc20e7 | |||
| 401643f8ec | |||
| 2b11516bf5 | |||
| 703af93bb9 | |||
| 1fe16466ab | |||
| 47a17e7550 | |||
| af025c5490 | |||
| c15e3828b7 | |||
| 9ef548e1bc | |||
| 3eb8467bf5 | |||
| 4112e336ef | |||
| a58d398d82 | |||
| fd178e1d3b | |||
| e8a6c88e2b | |||
| 8606bcbd63 | |||
| 2b6e27875f | |||
| fa6f7d836e | |||
| b7093706f5 | |||
| 56131100e7 | |||
| 9357464db0 | |||
| 79161ffe58 | |||
| 7c3051db46 | |||
| 89ee6d7451 | |||
| 3b23e9be05 | |||
| d34143ceef | |||
| 1abbe7b9c9 | |||
| 802ba665c7 | |||
| 3ba6c63554 | |||
| e3a05cc650 | |||
| 0583c9231d | |||
| 4ec2f2bae3 | |||
| 0d6713f248 | |||
| 7affec2cd5 | |||
| 7d4f5769a2 | |||
| c160303729 | |||
| 1082fcc921 | |||
| 8582c33eed | |||
| 1c51d1e9ab | |||
| dcec28922e | |||
| 084a2ff9a9 | |||
| 1d5fbe6d0e | |||
| 2aefb73a36 | |||
| e43cab0312 | |||
| b5f5ccb52c | |||
| 25c326c3f1 | |||
| 116cb496d9 | |||
| 777634b914 | |||
| cab37a87a3 | |||
| 62cc54fd9b | |||
| d445454a54 | |||
| 0235e1d769 | |||
| df0fc1acc3 | |||
| 52e17c739d | |||
| 1553737c77 | |||
| 893d2e870e | |||
| 13dd737666 | |||
| 1add03abd5 | |||
| 06702e2c92 | |||
| fcb171c093 | |||
| 0aa86cd085 | |||
| a8339153c4 | |||
| b394da69f3 | |||
| 064f99ddea | |||
| 2e1efd154d | |||
| c53b6ae2bb | |||
| 709f9555ef | |||
|
16af746e35
|
|||
|
bdadadd928
|
|||
|
c7f8aa8657
|
|||
|
9596cf4dae
|
|||
| 0fa69f9eb9 | |||
| 687be0e515 | |||
| 4e2da0a790 | |||
| 0206d15b2e | |||
|
98aedf2249
|
|||
|
c6be77c098
|
|||
|
a68d3d1784
|
|||
| 20092410c0 | |||
| a20ad9f621 |
@@ -2,7 +2,7 @@
|
||||
# The following two env variables need to be set for the supplied runner.sh script to work.
|
||||
|
||||
# Absolute path to the Vitis install directory.
|
||||
# AMD_TOOLS = "/tools/Xilinx/Vitis/2024.1"
|
||||
# AMD_TOOLS = "/tools/2025.2/Vitis"
|
||||
# Absolute path to the PS7 initialization TCL script.
|
||||
# TCL_INIT_SCRIPT = "/home/$user/$project/$sdt_dir/ps7_init.tcl"
|
||||
|
||||
@@ -10,3 +10,4 @@
|
||||
# running the application. You only need to do this once for unchanged bitstream as long as you
|
||||
# do not reset the whole board.
|
||||
# ZYNQ_BITSTREAM = "/home/$user/$project/$sdt_dir/bitstream.bit"
|
||||
# HW_SERVER_IP = "localhost"
|
||||
|
||||
+16
-21
@@ -8,14 +8,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: extractions/setup-just@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- run: just check zynq
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: just check tools
|
||||
- run: just check zynq7000-boot-image
|
||||
with:
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just check-dir firmware
|
||||
- run: just check-dir host
|
||||
|
||||
build:
|
||||
name: Check build
|
||||
@@ -24,14 +21,11 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: extractions/setup-just@v3
|
||||
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- run: just build zynq
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: just build tools
|
||||
- run: just build zynq7000-boot-image
|
||||
with:
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just build-zynq
|
||||
- run: just build-dir host
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
@@ -42,9 +36,9 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- run: just fmt zynq
|
||||
- run: just fmt tools
|
||||
- run: just fmt zynq7000-boot-image
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just check-fmt-dir firmware
|
||||
- run: just check-fmt-dir host
|
||||
|
||||
docs:
|
||||
name: Check Documentation Build
|
||||
@@ -55,6 +49,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just docs-zynq
|
||||
|
||||
clippy:
|
||||
@@ -63,13 +58,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: extractions/setup-just@v3
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy, rust-src
|
||||
- run: just clippy zynq
|
||||
targets: "armv7a-none-eabihf"
|
||||
- run: just clippy-dir firmware
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- run: just clippy tools
|
||||
- run: just clippy zynq7000-boot-image
|
||||
- run: just clippy-dir host
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
target
|
||||
xsct-output.log
|
||||
app.map
|
||||
|
||||
/app.map
|
||||
/xsct-output.log
|
||||
/.vscode
|
||||
/Cargo.lock
|
||||
/.cargo/config.toml
|
||||
|
||||
@@ -4,52 +4,55 @@ Zynq 7000 Bare-Metal Rust Support
|
||||
This crate collection provides support to write bare-metal Rust applications for the AMD Zynq 7000
|
||||
family of SoCs.
|
||||
|
||||
<p align="center">
|
||||
<img src="./ferris-zedboard.jpeg" alt="Ferris on the Zedboard" width="400" />
|
||||
</p>
|
||||
|
||||
# List of crates
|
||||
|
||||
This project contains the following crates:
|
||||
|
||||
## [Zynq Workspace](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq)
|
||||
## [Firmware Workspace](./firmware)
|
||||
|
||||
This workspace contains libraries and application which can only be run on the target system.
|
||||
|
||||
- The [`zynq7000-rt`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-rt)
|
||||
- The [`zynq7000-rt`](./firmware/zynq7000-rt)
|
||||
run-time crate containing basic low-level startup code necessary to boot a Rust app on the
|
||||
Zynq7000.
|
||||
- The [`zynq7000`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000) PAC
|
||||
crate containing basic low-level register definitions.
|
||||
- The [`zynq7000-mmu`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal)
|
||||
- The [`zynq7000`](./firmware/zynq7000) PAC crate containing basic low-level register access API.
|
||||
- The [`zynq7000-mmu`](./firmware/zynq7000-mmu)
|
||||
crate containing common MMU abstractions used by both the HAL and the run-time crate.
|
||||
- The [`zynq7000-hal`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-hal)
|
||||
HAL crate containing higher-level abstractions on top of the PAC register crate.
|
||||
- The [`zynq7000-embassy`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000-embassy)
|
||||
crate containing support for running the embassy-rs asynchronous run-time.
|
||||
- The [`zynq7000-hal`](./firmware/zynq7000-hal) HAL crate containing higher-level abstractions on
|
||||
top of the PAC register crate.
|
||||
- The [`zynq7000-embassy`](./firmware/zynq7000-embassy) crate containing an embassy-rs time driver
|
||||
using the global timer counter peripheral.
|
||||
|
||||
This project was developed using a Zedboard, so there are several crates available targeted towards
|
||||
this board:
|
||||
|
||||
- The [`zedboard-bsp`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-bsp)
|
||||
- The [`zedboard-bsp`](./firmware/zedboard-bsp)
|
||||
crate containing board specific components for the Zedboard.
|
||||
- The [`zedboard-fsbl`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-fsbl)
|
||||
- The [`zedboard-fsbl`](./firmware/zedboard-fsbl)
|
||||
contains a simple first-stage bootloader application for the Zedboard.
|
||||
- The [`zedboard-qspi-flasher`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zedboard-qspi-flasher)
|
||||
- The [`zedboard-qspi-flasher`](./firmware/zedboard-qspi-flasher)
|
||||
contains an application which is able to flash a boot binary from DDR to the QSPI.
|
||||
|
||||
It also contains the following helper crates:
|
||||
|
||||
- The [`examples`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/examples)
|
||||
- The [`examples`](./firmware/examples)
|
||||
folder contains various example applications crates using the HAL and the PAC.
|
||||
This folder also contains dedicated example applications using the
|
||||
[`embassy`](https://github.com/embassy-rs/embassy) native Rust RTOS.
|
||||
|
||||
## Other libraries and tools
|
||||
|
||||
- The [`zedboard-fpga-design`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zedboard-fpga-design)
|
||||
- The [`zedboard-fpga-design`](./zedboard-fpga-design)
|
||||
folder contains a sample FPGA design and block design which was used in some of the provided software examples. The project was created with Vivado version 2024.1.
|
||||
The folder contains a README with all the steps required to load this project from a TCL script.
|
||||
- The [`zynq7000-boot-image`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-boot-image)
|
||||
- The [`zynq7000-boot-image`](./host/zynq7000-boot-image)
|
||||
library contains generic helpers to interface with the AMD
|
||||
[boot binary](https://docs.amd.com/r/en-US/ug1283-bootgen-user-guide).
|
||||
- The [`tools/zynq7000-ps7init-extract`](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/tools/zynq7000-ps7init-extract)
|
||||
- The [`zynq7000-ps7init-extract`](./host/zynq7000-ps7init-extract)
|
||||
tool allows extracting configuration from the AMD generated `ps7init.tcl` file which contains
|
||||
static configuration parameters for DDR initialization.
|
||||
|
||||
@@ -77,14 +80,6 @@ You can then adapt the files in `.vscode` to your needs.
|
||||
|
||||
# Building the blinky example
|
||||
|
||||
Building an application requires `nightly` to build the `core` and `alloc` library
|
||||
because the `thumbv7a-none-eabihf` Rust target only has Tier 3 support
|
||||
If you have not installed it yet, you can do so with
|
||||
|
||||
```sh
|
||||
rustup toolchain install nightly
|
||||
```
|
||||
|
||||
Assuming you have the following segments inside your `.cargo/config.toml`
|
||||
|
||||
```toml
|
||||
@@ -100,10 +95,6 @@ rustflags = [
|
||||
# "-Clink-args=-Map=app.map"
|
||||
]
|
||||
|
||||
# Tier 3 target, so no pre-compiled artifacts included.
|
||||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
|
||||
[build]
|
||||
target = "armv7a-none-eabihf"
|
||||
```
|
||||
@@ -111,7 +102,8 @@ target = "armv7a-none-eabihf"
|
||||
You can build the blinky example app using
|
||||
|
||||
```sh
|
||||
cargo build --example simple
|
||||
cd zynq
|
||||
cargo build --bin blinky
|
||||
```
|
||||
|
||||
|
||||
@@ -168,7 +160,7 @@ Zedboard are configured for JTAG boot.
|
||||
|
||||
You can use the `-tui` argument to also have a terminal UI.
|
||||
This repository provides a `scripts/runner.sh` which performs all the steps specified above.
|
||||
The `.cargo/def-config.toml` script contains the runner and some template environmental
|
||||
The `.cargo/config.toml.template` script contains the runner and some template environmental
|
||||
variables that need to be set for this to work. The command above also loaded the app, but
|
||||
this task can be performed by the `zynq7000-init.py` wrapper as well.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 414 KiB |
@@ -6,15 +6,16 @@ rustflags = [
|
||||
"-Ctarget-feature=+vfp3",
|
||||
"-Ctarget-feature=+neon",
|
||||
"-Clink-arg=-Tlink.x",
|
||||
# Breaks builds not using/including defmt..
|
||||
# "-Clink-arg=-Tdefmt.x",
|
||||
# If this is not enabled, debugging / stepping can become problematic.
|
||||
"-Cforce-frame-pointers=yes",
|
||||
# Can be useful for debugging.
|
||||
# "-Clink-args=-Map=app.map"
|
||||
]
|
||||
|
||||
# Tier 3 target, so no pre-compiled artifacts included.
|
||||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
|
||||
[build]
|
||||
target = "armv7a-none-eabihf"
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "info"
|
||||
@@ -0,0 +1,51 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"zynq7000-rt",
|
||||
"zynq7000-mmu",
|
||||
"zynq7000",
|
||||
"zynq7000-hal",
|
||||
"zynq7000-embassy",
|
||||
|
||||
"examples/simple",
|
||||
"examples/embassy",
|
||||
"examples/zedboard",
|
||||
"examples/defmt",
|
||||
"examples/multiprio",
|
||||
|
||||
"zedboard-bsp",
|
||||
"zedboard-qspi-flasher",
|
||||
]
|
||||
|
||||
exclude = [
|
||||
# Exclude, can not be built with debug optimization level, too large.
|
||||
"zedboard-fsbl",
|
||||
]
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
# default is opt-level = '0', but that makes very
|
||||
# verbose machine code
|
||||
opt-level = 's'
|
||||
# trade compile speed for slightly better optimisations
|
||||
codegen-units = 1
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
# Optimize for maximum speed.
|
||||
opt-level = 3
|
||||
# trade compile speed for slightly better optimisations
|
||||
codegen-units = 1
|
||||
# Use Link Time Optimisations to further inline things across
|
||||
# crates
|
||||
lto = 'fat'
|
||||
# Leave the debug symbols in (default is no debug info)
|
||||
debug = 2
|
||||
|
||||
[patch.crates-io]
|
||||
embassy-executor = { path = "../../embassy/embassy-executor" }
|
||||
embassy-executor-macros = { path = "../../embassy/embassy-executor-macros" }
|
||||
embassy-time-driver = { path = "../../embassy/embassy-time-driver" }
|
||||
embassy-time = { path = "../../embassy/embassy-time" }
|
||||
embassy-executor-timer-queue = { path = "../../embassy/embassy-executor-timer-queue" }
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "defmt"
|
||||
version = "0.1.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "DEFMT test app"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal", features = ["defmt"] }
|
||||
defmt = "1"
|
||||
defmt-rtt = "1"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
@@ -0,0 +1,32 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rustc-link-arg=-Tdefmt.x");
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
@@ -3,11 +3,18 @@ MEMORY
|
||||
/* Zedboard: 512 MB DDR3. Only use 63 MB for now, should be plenty for a bare-metal app.
|
||||
Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This is
|
||||
recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M
|
||||
/*CODE(rx) : ORIGIN = 0x00100000, LENGTH = 63M*/
|
||||
/* CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
/* STACK: ORIGIN = 0x3F00000, LENGTH = 1M */
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
/* Use the upper OCM as the stack */
|
||||
REGION_ALIAS("STACKS", OCM_UPPER);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
@@ -20,3 +27,10 @@ SECTIONS
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 1K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
@@ -0,0 +1,83 @@
|
||||
//! Simple blinky app, showing a PAC variant and a HAL variant.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use defmt_rtt as _;
|
||||
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
|
||||
use zynq7000_hal::{
|
||||
InteruptConfig,
|
||||
clocks::Clocks,
|
||||
gpio::{Output, PinState, mio},
|
||||
priv_tim::CpuPrivateTimer,
|
||||
time::Hertz,
|
||||
};
|
||||
|
||||
pub const LIB: Lib = Lib::Hal;
|
||||
|
||||
// Define the clock frequency as a constant.
|
||||
//
|
||||
// Not required for the PAC mode, is required for clean delays in HAL mode.
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Lib {
|
||||
Pac,
|
||||
Hal,
|
||||
}
|
||||
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
let dp = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.expect("Failed to initialize Zynq7000");
|
||||
|
||||
defmt::println!("-- Zynq7000 defmt test application --");
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
defmt::info!("clocks {:?}", clocks);
|
||||
// Unwrap okay, we only call this once on core 0 here.
|
||||
let mut cpu_tim = CpuPrivateTimer::take(clocks.arm_clocks()).unwrap();
|
||||
let mio_pins = mio::Pins::new(dp.gpio);
|
||||
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::High);
|
||||
loop {
|
||||
defmt::info!("toggling LED!");
|
||||
led.toggle().unwrap();
|
||||
cpu_tim.delay_ms(1000);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
@@ -11,44 +11,27 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
cortex-ar = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
zynq7000-embassy = { path = "../../zynq7000-embassy" }
|
||||
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
|
||||
embedded-hal-async = "1"
|
||||
embassy-sync = "0.8"
|
||||
static_cell = "2"
|
||||
critical-section = "1"
|
||||
heapless = "0.9"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
|
||||
embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", features = [
|
||||
"arch-cortex-ar",
|
||||
embassy-executor = { version = "0.10", features = [
|
||||
"platform-z7",
|
||||
"executor-thread",
|
||||
]}
|
||||
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
# default is opt-level = '0', but that makes very
|
||||
# verbose machine code
|
||||
opt-level = 's'
|
||||
# trade compile speed for slightly better optimisations
|
||||
codegen-units = 1
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
# default is opt-level = '3', but that makes quite
|
||||
# verbose machine code
|
||||
opt-level = 's'
|
||||
# trade compile speed for slightly better optimisations
|
||||
codegen-units = 1
|
||||
# Use Link Time Optimisations to further inline things across
|
||||
# crates
|
||||
lto = 'fat'
|
||||
# Leave the debug symbols in (default is no debug info)
|
||||
debug = 2
|
||||
# embassy-executor = { git = "https://github.com/robamu/embassy.git", branch = "add-z7-arch-support", features = ["platform-z7", "executor-thread"] }
|
||||
# embassy-executor = { git = "https://github.com/robamu/embassy.git", features = ["platform-cortex-ar", "executor-thread"] }
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
|
||||
@@ -0,0 +1,25 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
|
||||
MMU. This is recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
+17
-38
@@ -2,8 +2,8 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
|
||||
@@ -12,7 +12,8 @@ use log::{error, info, warn};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{Flex, Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -21,28 +22,23 @@ use zynq7000_hal::{
|
||||
};
|
||||
|
||||
use zynq7000::Peripherals;
|
||||
use zynq7000_rt as _;
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
/// Try to talk to a DHT22 sensor connected at MIO0.
|
||||
const DHT22_AT_MIO0: bool = true;
|
||||
|
||||
/// Open drain pin testing. MIO9 needs to be tied to MIO14.
|
||||
const OPEN_DRAIN_PINS_MIO9_TO_MIO14: bool = false;
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -50,7 +46,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -67,7 +63,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = Uart::new_with_mio(
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
@@ -76,13 +72,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
uart.write_all(b"-- Zynq 7000 DHT22 --\n\r").unwrap();
|
||||
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let mut delay = Delay;
|
||||
|
||||
@@ -162,23 +152,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
+14
-34
@@ -1,31 +1,29 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{BootMode, InteruptConfig, clocks, gic, gpio, gtc, time::Hertz, uart};
|
||||
use zynq7000_hal::{
|
||||
BootMode, InteruptConfig, clocks, generic_interrupt_handler, gpio, gtc, time::Hertz, uart,
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
@@ -45,7 +43,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio(
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
@@ -53,14 +51,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r")
|
||||
.unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
@@ -75,23 +66,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = gic::GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
gic::Interrupt::Sgi(_) => (),
|
||||
gic::Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
+47
-46
@@ -2,23 +2,24 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use log::info;
|
||||
use zynq7000::Peripherals;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
time::Hertz,
|
||||
uart::{ClockConfig, Config, TxAsync, Uart, on_interrupt_tx},
|
||||
uart::{self, ClockConfig, Config, TxAsync, Uart},
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
@@ -26,16 +27,12 @@ use zynq7000_rt as _;
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
@@ -44,7 +41,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -61,7 +58,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = Uart::new_with_mio(
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
@@ -70,57 +67,60 @@ async fn main(spawner: Spawner) -> ! {
|
||||
uart.write_all(b"-- Zynq 7000 Logging example --\n\r")
|
||||
.unwrap();
|
||||
uart.flush().unwrap();
|
||||
let (tx, _rx) = uart.split();
|
||||
let mut logger = TxAsync::new(tx);
|
||||
|
||||
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
|
||||
let (tx, _rx) = uart.split();
|
||||
let logger = TxAsync::new(tx, true);
|
||||
|
||||
let mut log_runner =
|
||||
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, logger).unwrap();
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
let led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
|
||||
spawner.spawn(led_task(led).unwrap());
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
|
||||
loop {
|
||||
let next_frame_len = frame_queue.receive().await;
|
||||
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
|
||||
logger.write(&log_buf[0..next_frame_len]).await;
|
||||
}
|
||||
spawner.spawn(hello_task().unwrap());
|
||||
|
||||
log_runner.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn led_task(mut mio_led: Output) {
|
||||
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
info!("Toggling LED");
|
||||
info!(
|
||||
"Toggling LED ({})",
|
||||
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
#[embassy_executor::task]
|
||||
async fn hello_task() {
|
||||
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
info!(
|
||||
"Hello from another task ({})",
|
||||
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => {
|
||||
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
|
||||
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
|
||||
}
|
||||
}
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
@@ -147,6 +147,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
|
||||
writeln!(uart, "panic: {}\r", info).ok();
|
||||
loop {}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs as _;
|
||||
use embedded_io::Write as _;
|
||||
use log::error;
|
||||
use zynq7000_hal::{
|
||||
InteruptConfig, clocks,
|
||||
executor::InterruptExecutor,
|
||||
generic_interrupt_handler,
|
||||
gic::{SgiInterrupt, TargetCpus},
|
||||
gpio::{self, Output},
|
||||
gtc,
|
||||
time::Hertz,
|
||||
uart,
|
||||
};
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
static INTERUPT_EXECUTOR: InterruptExecutor = InterruptExecutor::new();
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main(executor = "zynq7000_hal::executor::Executor")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
|
||||
let led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
|
||||
let sgi_interrupt = SgiInterrupt::new(0).unwrap();
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Interrupt Executor --\n\r")
|
||||
.unwrap();
|
||||
uart.flush().unwrap();
|
||||
|
||||
//let (tx, _rx) = uart.split();
|
||||
// let mut logger = uart::TxAsync::new(tx, true);
|
||||
//let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap();
|
||||
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"interrupt counter: {}",
|
||||
CNTR.load(core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
|
||||
let spawner = INTERUPT_EXECUTOR.start(sgi_interrupt, TargetCpus::CPU_0);
|
||||
zynq7000_hal::register_interrupt(
|
||||
zynq7000_hal::Interrupt::Sgi(sgi_interrupt),
|
||||
on_interrupt_low_prio,
|
||||
);
|
||||
spawner.spawn(led_task(led).unwrap());
|
||||
|
||||
log::info!(
|
||||
"interrupt counter: {}",
|
||||
CNTR.load(core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
|
||||
//Delay.delay_ms(1).await;
|
||||
|
||||
/*
|
||||
log::info!(
|
||||
"interrupt counter: {}",
|
||||
CNTR.load(core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
*/
|
||||
|
||||
//let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
loop {
|
||||
log::info!("THR alive");
|
||||
/*
|
||||
let read_bytes = log_reader.read(&mut log_buf).await;
|
||||
if read_bytes > 0 {
|
||||
// Unwrap okay, checked that size is larger than 0.
|
||||
logger.write(&log_buf[0..read_bytes]).unwrap().await;
|
||||
}
|
||||
*/
|
||||
Ticker::every(Duration::from_millis(1000)).next().await;
|
||||
}
|
||||
}
|
||||
|
||||
static CNTR: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
unsafe fn interrupt_handler(_ctx: *mut ()) {
|
||||
CNTR.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
unsafe {
|
||||
INTERUPT_EXECUTOR.on_interrupt();
|
||||
}
|
||||
CNTR.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn led_task(mut mio_led: Output) {
|
||||
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
log::info!(
|
||||
"Toggling LED ({})",
|
||||
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
log::info!(
|
||||
"Interrupt handled, counter: {}",
|
||||
CNTR.load(core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::{digital::StatefulOutputPin, pwm::SetDutyCycle};
|
||||
@@ -18,7 +18,8 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -32,17 +33,13 @@ use zynq7000_rt as _;
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -50,7 +47,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -74,22 +71,15 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = Uart::new_with_mio(
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Embassy Hello World --\n\r")
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 PWM example--\n\r").unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
@@ -112,23 +102,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
@@ -1,30 +1,24 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use log::error;
|
||||
use zynq7000_hal::{InteruptConfig, clocks, gic, gpio, gtc, time::Hertz};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
use zynq7000_hal::{InteruptConfig, clocks, generic_interrupt_handler, gpio, gtc, time::Hertz};
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
@@ -48,22 +42,11 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
let mut gic_helper = gic::GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
gic::Interrupt::Sgi(_) => (),
|
||||
gic::Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "embassy-multiprio"
|
||||
version = "0.1.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Embassy examples for the Zynq7000 SoC"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
zynq7000-embassy = { path = "../../zynq7000-embassy" }
|
||||
dht-sensor = { git = "https://github.com/michaelbeaumont/dht-sensor.git", rev = "10319bdeae9ace3bb0fc79a15da2869c5bf50f52", features = ["async"] }
|
||||
embedded-hal-async = "1"
|
||||
embassy-sync = "0.8"
|
||||
static_cell = "2"
|
||||
critical-section = "1"
|
||||
heapless = "0.9"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
arbitrary-int = "2"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
|
||||
embassy-executor = { version = "0.10", features = [
|
||||
"platform-z7",
|
||||
"executor-thread",
|
||||
"executor-interrupt",
|
||||
]}
|
||||
@@ -0,0 +1,25 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
|
||||
MMU. This is recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::u5;
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::{InterruptExecutor, Spawner};
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write as _;
|
||||
use log::error;
|
||||
use zynq7000_hal::{
|
||||
InteruptConfig, clocks, generic_interrupt_handler,
|
||||
gic::SgiInterrupt,
|
||||
gpio::{self, Output},
|
||||
gtc,
|
||||
time::Hertz,
|
||||
uart,
|
||||
};
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
static INTERUPT_EXECUTOR_LOW_PRIO: InterruptExecutor = InterruptExecutor::new();
|
||||
static INTERUPT_EXECUTOR_MED_PRIO: InterruptExecutor = InterruptExecutor::new();
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
let mut gic = unsafe { zynq7000_hal::gic::Configurator::steal() };
|
||||
gic.set_all_sgi_interrupt_targets_cpu0();
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
let mio_pins = gpio::mio::Pins::new(periphs.gpio);
|
||||
let led = gpio::Output::new_for_mio(mio_pins.mio7, gpio::PinState::Low);
|
||||
let sgi_interrupt_low_prio = SgiInterrupt::new(0).unwrap();
|
||||
let sgi_interrupt_med_prio = SgiInterrupt::new(1).unwrap();
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Interrupt Executor --\n\r")
|
||||
.unwrap();
|
||||
uart.flush().unwrap();
|
||||
|
||||
let (tx, _rx) = uart.split();
|
||||
let mut logger = uart::TxAsync::new(tx, true);
|
||||
let log_reader = zynq7000_hal::log::asynch::init(log::LevelFilter::Trace).unwrap();
|
||||
|
||||
gic.set_sgi_interrupt_priority(sgi_interrupt_low_prio, u5::new(2));
|
||||
gic.set_sgi_interrupt_priority(sgi_interrupt_med_prio, u5::new(1));
|
||||
|
||||
zynq7000_hal::register_interrupt(
|
||||
zynq7000_hal::Interrupt::Sgi(sgi_interrupt_low_prio),
|
||||
on_interrupt_low_prio,
|
||||
);
|
||||
zynq7000_hal::register_interrupt(
|
||||
zynq7000_hal::Interrupt::Sgi(sgi_interrupt_med_prio),
|
||||
on_interrupt_med_prio,
|
||||
);
|
||||
let spawner = INTERUPT_EXECUTOR_LOW_PRIO.start(sgi_interrupt_low_prio.as_u4());
|
||||
spawner.spawn(led_task(led).unwrap());
|
||||
let spawner = INTERUPT_EXECUTOR_MED_PRIO.start(sgi_interrupt_med_prio.as_u4());
|
||||
spawner.spawn(hello_task().unwrap());
|
||||
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
loop {
|
||||
let read_bytes = log_reader.read(&mut log_buf).await;
|
||||
if read_bytes > 0 {
|
||||
// Unwrap okay, checked that size is larger than 0.
|
||||
logger.write(&log_buf[0..read_bytes]).unwrap().await;
|
||||
}
|
||||
Ticker::every(Duration::from_millis(1000)).next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn led_task(mut mio_led: Output) {
|
||||
static ATOMIC_COUNTER: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
log::info!(
|
||||
"Toggling LED ({})",
|
||||
ATOMIC_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed)
|
||||
);
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn hello_task() {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
log::info!("Hello from the low priority task");
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn on_interrupt_low_prio() {
|
||||
unsafe {
|
||||
INTERUPT_EXECUTOR_LOW_PRIO.on_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn on_interrupt_med_prio() {
|
||||
unsafe {
|
||||
INTERUPT_EXECUTOR_MED_PRIO.on_interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
@@ -9,16 +9,11 @@ repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-ar = "0.3"
|
||||
aarch32-cpu = { version = "0.3" }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = true
|
||||
lto = true
|
||||
@@ -0,0 +1,31 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
|
||||
MMU. This is recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 1K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
+5
-15
@@ -2,8 +2,8 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_hal::{delay::DelayNs, digital::StatefulOutputPin};
|
||||
use zynq7000::Peripherals;
|
||||
use zynq7000_hal::{
|
||||
@@ -13,7 +13,6 @@ use zynq7000_hal::{
|
||||
priv_tim::CpuPrivateTimer,
|
||||
time::Hertz,
|
||||
};
|
||||
use zynq7000_rt as _;
|
||||
|
||||
pub const LIB: Lib = Lib::Hal;
|
||||
|
||||
@@ -31,21 +30,12 @@ pub enum Lib {
|
||||
Hal,
|
||||
}
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
pub fn main() -> ! {
|
||||
l2_cache::init_with_defaults(&mut unsafe { zynq7000::l2_cache::L2Cache::new_mmio_fixed() });
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
l2_cache::init_with_defaults(&mut unsafe { zynq7000::l2_cache::Registers::new_mmio_fixed() });
|
||||
match LIB {
|
||||
Lib::Pac => {
|
||||
let mut gpio = unsafe { zynq7000::gpio::Gpio::new_mmio_fixed() };
|
||||
let mut gpio = unsafe { zynq7000::gpio::Registers::new_mmio_fixed() };
|
||||
gpio.bank_0().modify_dirm(|v| v | ZEDBOARD_LED_MASK);
|
||||
gpio.bank_0().modify_out_en(|v| v | ZEDBOARD_LED_MASK);
|
||||
loop {
|
||||
+10
-27
@@ -2,14 +2,15 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::{panic::PanicInfo, sync::atomic::AtomicU64};
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
Interrupt,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
gic,
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -18,31 +19,20 @@ use zynq7000_hal::{
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_333);
|
||||
|
||||
static MS_TICKS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
pub fn main() -> ! {
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
let mut dp = zynq7000::Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = gic::Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -62,7 +52,7 @@ pub fn main() -> ! {
|
||||
|
||||
// This structure holds all MIO pins.
|
||||
let mio_pins = mio::Pins::new(dp.gpio);
|
||||
let mut uart = Uart::new_with_mio(
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
@@ -70,14 +60,7 @@ pub fn main() -> ! {
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 GTC Ticks example --\n\r")
|
||||
.unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
|
||||
loop {
|
||||
@@ -94,8 +77,8 @@ pub fn main() -> ! {
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
let mut gic_helper = gic::InterruptGuard::new();
|
||||
let irq_info = gic_helper.interrupt_info();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
+9
-27
@@ -2,15 +2,15 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::{panic::PanicInfo, sync::atomic::AtomicU64};
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
gic::{self, Configurator, Interrupt},
|
||||
gpio::{Output, PinState, mio},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -19,31 +19,20 @@ use zynq7000_hal::{
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
static MS_TICKS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
pub fn main() -> ! {
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
let mut dp = zynq7000::Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -62,7 +51,7 @@ pub fn main() -> ! {
|
||||
gtc.enable();
|
||||
let mio_pins = mio::Pins::new(dp.gpio);
|
||||
|
||||
let mut uart = Uart::new_with_mio(
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
@@ -71,13 +60,7 @@ pub fn main() -> ! {
|
||||
uart.write_all(b"-- Zynq 7000 Logging example --\n\r")
|
||||
.unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {boot_mode:?}");
|
||||
@@ -96,13 +79,12 @@ pub fn main() -> ! {
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
let mut gic_helper = gic::InterruptGuard::new();
|
||||
let irq_info = gic_helper.interrupt_info();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
// TODO: Call embassy on interrupt handler here soon.
|
||||
MS_TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//! Example which uses the UART1 to send log messages.
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::{panic::PanicInfo, sync::atomic::AtomicU64};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
gic::{self, Configurator, Interrupt, SgiInterrupt},
|
||||
gpio::{Output, PinState, mio},
|
||||
l2_cache,
|
||||
time::Hertz,
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
};
|
||||
|
||||
// Define the clock frequency as a constant
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
|
||||
static SGI_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
let mut dp = zynq7000::Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.set_all_sgi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
// Enable interrupt exception.
|
||||
unsafe { gic.enable_interrupts() };
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mio_pins = mio::Pins::new(dp.gpio);
|
||||
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Software Interrupt example --\n\r")
|
||||
.unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {boot_mode:?}");
|
||||
|
||||
let mut led = Output::new_for_mio(mio_pins.mio7, PinState::Low);
|
||||
loop {
|
||||
let gtc = SGI_COUNTER.load(core::sync::atomic::Ordering::Relaxed);
|
||||
info!("Hello, world!");
|
||||
info!("SGI counter: {gtc}");
|
||||
gic.trigger_software_interrupt(SgiInterrupt::new(0).unwrap());
|
||||
led.toggle().unwrap();
|
||||
for _ in 0..5_000_000 {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = gic::InterruptGuard::new();
|
||||
let irq_info = gic_helper.interrupt_info();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_sgi) => {
|
||||
// TODO: Send ID to main thread.
|
||||
SGI_COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
Interrupt::Ppi(_ppi_interrupt) => (),
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
@@ -2,23 +2,13 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use zynq7000_rt as _;
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
pub fn main() -> ! {
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
loop {
|
||||
cortex_ar::asm::nop();
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ keywords = ["no-std", "arm", "cortex-a", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
cortex-ar = "0.3"
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../../zynq7000-rt" }
|
||||
zynq7000 = { path = "../../zynq7000" }
|
||||
zynq7000-hal = { path = "../../zynq7000-hal" }
|
||||
@@ -20,32 +20,29 @@ zedboard-bsp = { path = "../../zedboard-bsp" }
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
l3gd20 = { git = "https://github.com/us-irs/l3gd20.git", branch = "add-async-if" }
|
||||
embedded-io = "0.7"
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
embedded-io-async = "0.6"
|
||||
embedded-io-async = "0.7"
|
||||
critical-section = "1"
|
||||
static_cell = "2"
|
||||
embedded-alloc = "0.6"
|
||||
embedded-alloc = "0.7"
|
||||
embedded-hal = "1"
|
||||
embedded-hal-bus = { version = "0.3", features = ["async"] }
|
||||
embedded-hal-async = "1"
|
||||
fugit = "0.3"
|
||||
dummy-pin = "1"
|
||||
fugit = "0.4"
|
||||
fugit-03 = { version = "0.3", package = "fugit" }
|
||||
embedded-graphics = "0.8"
|
||||
log = "0.4"
|
||||
rand = { version = "0.9", default-features = false, features = ["small_rng"] }
|
||||
ssd1306 = { version = "0.10", features = ["async"] }
|
||||
tinybmp = "0.7"
|
||||
rand = { version = "0.10", default-features = false }
|
||||
|
||||
embassy-executor = { git = "https://github.com/us-irs/embassy.git", branch = "cortex-ar-update", features = [
|
||||
"arch-cortex-ar",
|
||||
"executor-thread",
|
||||
]}
|
||||
# TODO: Remove generic-queue-16 feature as soon as upstream executor is used again.
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000", "generic-queue-16"] }
|
||||
embassy-net = { version = "0.7", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp"] }
|
||||
embassy-sync = { version = "0.7" }
|
||||
# TODO: Bump as soon as new compatible smoltcp/embassy-net version is released.
|
||||
heapless = "0.8"
|
||||
axi-uartlite = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uartlite.git" }
|
||||
axi-uart16550 = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550.git" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = true
|
||||
lto = true
|
||||
embassy-executor = { version = "0.10", features = ["platform-cortex-ar", "executor-thread"] }
|
||||
embassy-time = { version = "0.5", features = ["tick-hz-1_000_000"] }
|
||||
embassy-net = { version = "0.9", features = ["dhcpv4", "packet-trace", "medium-ethernet", "icmp", "tcp", "udp", "auto-icmp-echo-reply"] }
|
||||
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
|
||||
embassy-sync = { version = "0.8" }
|
||||
heapless = "0.9"
|
||||
axi-uartlite = { version = "0.1" }
|
||||
axi-uart16550 = { version = "0.1" }
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 402 B |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="0.16in" height="0.106667in"
|
||||
viewBox="0 0 48 32">
|
||||
<defs>
|
||||
</defs>
|
||||
<image id="raster0"
|
||||
x="0"
|
||||
y="0"
|
||||
width="48"
|
||||
height="32"
|
||||
opacity="1.000000"
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAgCAQAAAD+3TOXAAAHDmVYSWZJSSoACAAAAAAADgAAAAkA/gAEAAEAAAABAAAAAAEEAAEAAAAAAQAAAQEEAAEAAACqAAAAAgEDAAMAAACAAAAAAwEDAAEAAAAGAAAABgEDAAEAAAAGAAAAFQEDAAEAAAADAAAAAQIEAAEAAACGAAAAAgIEAAEAAACIBgAAAAAAAAgACAAIAP/Y/+AAEEpGSUYAAQEAAAEAAQAA/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAqgEAAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A+f6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr6/+CX/ACSHQv8At4/9KJK+QK+v/gl/ySHQv+3j/wBKJKAPnzxr8Jte8B6NDqmqXemzQS3C26rayOzBirNk7kUYwh7+lcHX0/8AtHf8k80//sKx/wDoqWvmCgAroPAn/JQ/DX/YVtf/AEatc/XQeBP+Sh+Gv+wra/8Ao1aAPqP4s+CtS8eeFbXS9LntIZ4r1LhmunZVKhHXA2qxzlx29a+VPFPhu88I+I7vQ7+SCS6tdm94GJQ7kVxgkA9GHavuevkD42/8le13/t3/APSeOgDz+iiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK+g/hL8WdB0rw74f8Hz2mpNqD3BtxIkaGLdLOxU5Lg4+cZ49etfPlWLC+uNM1G2v7OTy7q1lSaF9oO11IKnB4OCB1oA+968j+J3xZ0HSofEXg+e01JtQeye3EiRoYt0sOVOS4OPnGePXrXjn/AAu34h/9DD/5JW//AMbrj9b1vUfEesT6tq1x9ovp9vmS7FTdtUKOFAA4AHAoAz6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAP/9mZmF3EAAAAAXNSR0IB2cksfwAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAhhJREFUSMft1T1I1WEUBvDfVQn1hoHoDQlMqCSlaLAiCKMhbIpqKLCtTQKJoLWh76GgpQ+isKWMgoiiocgpKoKEoCHCoHJJ1GjRAvu4p8GPrt57Ta/W5HmW9/w553ne9z3nf14WbJ6tXMu/or7vPM76aoVavbbMH3W/NtXSwnU/hJd6hA5LPXZ0btQbJNQLYVBMQdqgcBc1agoVCD1uZ1FnYtAt313+O9WiPAIzw8UpeUnnbEZy1C1WpskzqzJCLvnozowF3nnkm4ax3OU+CJU4IQU3bFMuDGuaEDgyY/I/GN1vwhshUKbf3hJb7TOiy4CUbgmstER61vXqsxpvbZo4yXEpQZuQtt4LIdTjaQG7H0erPWOrDmFENc1C6NUlhEYcKJj+s3IpPyf8U6NHuTDxoQ8NDhUs8FqzSmfGvIdKxu+vzZAQ2rXnS45pvEkYViuE04oyS1TlldAs6WM+gcjrTcJJXcL27C6o0yvtis45CaQ9Fw7mbrRlHsyhf/7gZiZpccZ6SNKOeZi+a5R6kutPapEWwq95OMW+cdLMSo9IgGO+ueTLdNMvhw1M8u7JOz2HVQuh1G7XsnsqR3n7ddqpGKFp6g4SWRKw2LAKQ6DGOiusVaF1SuxVfd7q1pORW+mT0izWHLdQ53ABz1OxEMr/HhiSNs6SvkSjIu9zFyiXRCG2KzuvKGdgwn4VBQhU5eHLI7Jg/8d+A9OUD/TqXTt5AAAAAElFTkSuQmCCAA==" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 80 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 274 B |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
@@ -0,0 +1,32 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
|
||||
MMU. This is recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 1K);
|
||||
PROVIDE(_irq_stack_size = 2K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
+37
-46
@@ -24,15 +24,15 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::{net::Ipv4Addr, panic::PanicInfo};
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_net::{Ipv4Cidr, StaticConfigV4, tcp::TcpSocket, udp::UdpSocket};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_io::Write;
|
||||
use embedded_io_async::Write as _;
|
||||
use log::{LevelFilter, debug, error, info, warn};
|
||||
use rand::{RngCore, SeedableRng};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zedboard_bsp::phy_marvell;
|
||||
use zynq7000_hal::{
|
||||
@@ -42,7 +42,8 @@ use zynq7000_hal::{
|
||||
eth::{
|
||||
AlignedBuffer, ClockDivSet, EthernetConfig, EthernetLowLevel, embassy_net::InterruptResult,
|
||||
},
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
generic_interrupt_handler,
|
||||
gic::{Configurator, Interrupt},
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -104,12 +105,9 @@ pub enum IpMode {
|
||||
StackReady,
|
||||
}
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
@@ -204,7 +202,6 @@ async fn tcp_task(mut tcp: TcpSocket<'static>) -> ! {
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -220,7 +217,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -237,7 +234,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = Uart::new_with_mio(
|
||||
let mut uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
@@ -245,7 +242,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe { zynq7000_hal::log::uart_blocking::init_unsafe_single_core(uart, LOG_LEVEL, false) };
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, LOG_LEVEL, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
@@ -282,6 +279,12 @@ async fn main(spawner: Spawner) -> ! {
|
||||
"Calculated RGMII clock configuration: {:?}, errors (missmatch from ideal rate in hertz): {:?}",
|
||||
clk_divs, clk_errors
|
||||
);
|
||||
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Eth0),
|
||||
custom_eth_interupt_handler,
|
||||
);
|
||||
|
||||
// Unwrap okay, we use a standard clock config, and the clock config should never fail.
|
||||
let eth_cfg = EthernetConfig::new(
|
||||
zynq7000_hal::eth::ClockConfig::new(clk_divs.cfg_1000_mbps),
|
||||
@@ -290,7 +293,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
);
|
||||
// Configures all the physical pins for ethernet operation and sets up the
|
||||
// ethernet peripheral.
|
||||
let mut eth = zynq7000_hal::eth::Ethernet::new_with_mio(
|
||||
let mut eth = zynq7000_hal::eth::Ethernet::new_with_mio_eth_0(
|
||||
eth_ll,
|
||||
eth_cfg,
|
||||
gpio_pins.mio.mio16,
|
||||
@@ -377,9 +380,9 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let tcp_socket = TcpSocket::new(stack, RX_TCP_BUFS.take(), TX_TCP_BUFS.take());
|
||||
|
||||
// Spawn all embassy tasks.
|
||||
spawner.spawn(embassy_net_task(runner)).unwrap();
|
||||
spawner.spawn(udp_task(udp_socket)).unwrap();
|
||||
spawner.spawn(tcp_task(tcp_socket)).unwrap();
|
||||
spawner.spawn(embassy_net_task(runner).unwrap());
|
||||
spawner.spawn(udp_task(udp_socket).unwrap());
|
||||
spawner.spawn(tcp_task(tcp_socket).unwrap());
|
||||
|
||||
let mut mio_led = Output::new_for_mio(gpio_pins.mio.mio7, PinState::Low);
|
||||
|
||||
@@ -465,36 +468,24 @@ async fn main(spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => {
|
||||
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Eth0 {
|
||||
// This generic library provided interrupt handler takes care of waking
|
||||
// the driver on received or sent frames while also reporting anomalies
|
||||
// and errors.
|
||||
let result = zynq7000_hal::eth::embassy_net::on_interrupt(
|
||||
zynq7000_hal::eth::EthernetId::Eth0,
|
||||
);
|
||||
if result.has_errors() {
|
||||
ETH_ERR_QUEUE.try_send(result).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
// Safety: Only called by interrupt handler, registered in global interrupt handler map.
|
||||
unsafe fn custom_eth_interupt_handler() {
|
||||
// This generic library provided interrupt handler takes care of waking
|
||||
// the driver on received or sent frames while also reporting anomalies
|
||||
// and errors.
|
||||
let result = zynq7000_hal::eth::embassy_net::on_interrupt(zynq7000_hal::eth::EthernetId::Eth0);
|
||||
if result.has_errors() {
|
||||
ETH_ERR_QUEUE.try_send(result).ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
+16
-37
@@ -8,8 +8,8 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
@@ -20,8 +20,8 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
i2c, l2_cache,
|
||||
@@ -36,17 +36,13 @@ use zynq7000_rt as _;
|
||||
const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
const I2C_ADDR_SEL: I2cAddr = I2cAddr::Sa0Low;
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -58,7 +54,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -76,7 +72,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio(
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
@@ -84,14 +80,8 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Zedboard I2C L3GD20H example --\n\r")
|
||||
.unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
@@ -157,24 +147,13 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
+46
-64
@@ -9,8 +9,8 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
@@ -20,14 +20,15 @@ use log::{error, info};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler,
|
||||
gic::Configurator,
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
spi::{self, SpiAsync, SpiId, SpiWithHwCs, SpiWithHwCsAsync, on_interrupt},
|
||||
log::asynch::UartLoggerRunner,
|
||||
spi::{self, SpiAsync, SpiWithHwCs, SpiWithHwCsAsync},
|
||||
time::Hertz,
|
||||
uart::{self, TxAsync, on_interrupt_tx},
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use zynq7000::{Peripherals, slcr::LevelShifterConfig, spi::DelayControl};
|
||||
@@ -39,17 +40,13 @@ const PS_CLOCK_FREQUENCY: Hertz = Hertz::from_raw(33_333_300);
|
||||
const DEBUG_SPI_CLK_CONFIG: bool = false;
|
||||
const BLOCKING: bool = false;
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -60,15 +57,20 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let mut clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
// SPI reference clock must be larger than the CPU 1x clock.
|
||||
let spi_ref_clk_div = spi::calculate_largest_allowed_spi_ref_clk_divisor(&clocks)
|
||||
.unwrap()
|
||||
.value()
|
||||
- 1;
|
||||
spi::configure_spi_ref_clk(&mut clocks, arbitrary_int::u6::new(spi_ref_clk_div as u8));
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
assert!(
|
||||
clocks.io_clocks().spi_clk().to_raw()
|
||||
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
|
||||
"SPI reference clock must be larger than CPU 1x clock"
|
||||
);
|
||||
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -86,7 +88,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio(
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
@@ -94,30 +96,32 @@ async fn main(spawner: Spawner) -> ! {
|
||||
.unwrap();
|
||||
uart.write_all(b"-- Zynq 7000 Zedboard SPI L3GD20H example --\n\r")
|
||||
.unwrap();
|
||||
zynq7000_hal::log::rb::init(log::LevelFilter::Trace);
|
||||
|
||||
let (tx, _) = uart.split();
|
||||
let tx_async = TxAsync::new(tx, true);
|
||||
let log_runner =
|
||||
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async).unwrap();
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
if DEBUG_SPI_CLK_CONFIG {
|
||||
info!(
|
||||
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}, DIV: {:?}",
|
||||
"SPI Clock Information: CPU 1x: {:?}, IO Ref Clk: {:?}, SPI Ref Clk: {:?}",
|
||||
clocks.arm_clocks().cpu_1x_clk(),
|
||||
clocks.io_clocks().ref_clk(),
|
||||
clocks.io_clocks().spi_clk(),
|
||||
spi_ref_clk_div
|
||||
);
|
||||
}
|
||||
|
||||
let mut spi = spi::Spi::new_one_hw_cs(
|
||||
dp.spi_1,
|
||||
clocks.io_clocks(),
|
||||
spi::Config::new(
|
||||
// 10 MHz maximum rating of the sensor.
|
||||
zynq7000::spi::BaudDivSel::By64,
|
||||
//l3gd20::MODE,
|
||||
// l3gd20::MODE,
|
||||
embedded_hal::spi::MODE_3,
|
||||
spi::SlaveSelectConfig::AutoWithAutoStart,
|
||||
spi::SlaveSelectConfig::AutoCsAutoStart,
|
||||
),
|
||||
(
|
||||
gpio_pins.mio.mio12,
|
||||
@@ -127,10 +131,13 @@ async fn main(spawner: Spawner) -> ! {
|
||||
gpio_pins.mio.mio13,
|
||||
)
|
||||
.unwrap();
|
||||
let sclk = Hertz::from_raw(
|
||||
clocks.io_clocks().spi_clk().to_raw() / zynq7000::spi::BaudDivSel::By64.div_value() as u32,
|
||||
);
|
||||
let mod_id = spi.regs().read_mod_id();
|
||||
assert_eq!(mod_id, spi::MODULE_ID);
|
||||
assert!(spi.sclk() <= Hertz::from_raw(10_000_000));
|
||||
let min_delay = (spi.sclk().raw() * 5) / 1_000_000_000;
|
||||
assert!(sclk <= Hertz::from_raw(10_000_000));
|
||||
let min_delay = (sclk.to_raw() * 5) / 1_000_000_000;
|
||||
spi.inner().configure_delays(
|
||||
DelayControl::builder()
|
||||
.with_inter_word_cs_deassert(0)
|
||||
@@ -159,25 +166,17 @@ async fn main(spawner: Spawner) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
spawner.spawn(logger_task(uart)).unwrap();
|
||||
spawner.spawn(logger_task(log_runner).unwrap());
|
||||
if BLOCKING {
|
||||
blocking_application(mio_led, emio_leds, spi).await;
|
||||
blocking_application(mio_led, emio_leds, spi).await
|
||||
} else {
|
||||
non_blocking_application(mio_led, emio_leds, spi).await;
|
||||
non_blocking_application(mio_led, emio_leds, spi).await
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(uart: uart::Uart) {
|
||||
let (tx, _) = uart.split();
|
||||
let mut tx_async = TxAsync::new(tx);
|
||||
let frame_queue = zynq7000_hal::log::rb::get_frame_queue();
|
||||
let mut log_buf: [u8; 2048] = [0; 2048];
|
||||
loop {
|
||||
let next_frame_len = frame_queue.receive().await;
|
||||
zynq7000_hal::log::rb::read_next_frame(next_frame_len, &mut log_buf);
|
||||
tx_async.write(&log_buf[0..next_frame_len]).await;
|
||||
}
|
||||
pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! {
|
||||
log_runner.run().await
|
||||
}
|
||||
|
||||
pub async fn blocking_application(
|
||||
@@ -241,30 +240,13 @@ pub async fn non_blocking_application(
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => {
|
||||
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Spi1 {
|
||||
on_interrupt(SpiId::Spi1);
|
||||
} else if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Uart1 {
|
||||
on_interrupt_tx(zynq7000_hal::uart::UartId::Uart1);
|
||||
}
|
||||
}
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -0,0 +1,276 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use dummy_pin::DummyPin;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_graphics::{Drawable as _, geometry::Point};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs as _;
|
||||
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
|
||||
use embedded_io::Write;
|
||||
use log::info;
|
||||
use ssd1306::{Ssd1306Async, prelude::*};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
|
||||
log::asynch::UartLoggerRunner,
|
||||
spi::{self, SpiAsync},
|
||||
time::Hertz,
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use embedded_graphics::image::Image;
|
||||
use tinybmp::Bmp;
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard Async OLED example --\n\r";
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
assert!(
|
||||
clocks.io_clocks().spi_clk().to_raw()
|
||||
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
|
||||
"SPI reference clock must be larger than CPU 1x clock"
|
||||
);
|
||||
|
||||
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
uart.flush().unwrap();
|
||||
|
||||
let (tx, _) = uart.split();
|
||||
let tx_async = TxAsync::new(tx, true);
|
||||
let log_runner =
|
||||
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async)
|
||||
.expect("Failed to initialize async logger");
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
info!(
|
||||
"SPI reference clock speed: {:?}",
|
||||
clocks.io_clocks().spi_clk()
|
||||
);
|
||||
|
||||
spawner.spawn(logger_task(log_runner).unwrap());
|
||||
|
||||
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
let emio_leds: [gpio::Output; 8] = [
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
|
||||
];
|
||||
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
|
||||
|
||||
let mut reset_pin =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vdd_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vbat_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
let spi = spi::Spi::new_for_emio(
|
||||
periphs.spi_0,
|
||||
spi::Config::calculate_for_io_clock(
|
||||
Hertz::MHz(8),
|
||||
clocks.io_clocks(),
|
||||
embedded_hal::spi::MODE_0,
|
||||
spi::SlaveSelectConfig::AutoCsManualStart,
|
||||
),
|
||||
)
|
||||
.expect("Failed to initialize SPI");
|
||||
let spi_asynch = SpiAsync::new(spi);
|
||||
let exclusive_device = ExclusiveDevice::new(spi_asynch, DummyPin::new_high(), NoDelay)
|
||||
.expect("Failed to create exclusive SPI device");
|
||||
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
|
||||
let mut ssd1306 = Ssd1306Async::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
|
||||
|
||||
oled_vdd_switch.set_low();
|
||||
oled_vbat_switch.set_low();
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
ssd1306
|
||||
.reset(&mut reset_pin, &mut embassy_time::Delay {})
|
||||
.await
|
||||
.expect("display reset error");
|
||||
let mut display = ssd1306.into_buffered_graphics_mode();
|
||||
display.init().await.expect("display init error");
|
||||
|
||||
// Include the BMP file data.
|
||||
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
|
||||
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
|
||||
// Parse the BMP file.
|
||||
let bmp_rust = Bmp::from_slice(rust_logo_data).expect("BMP loading error");
|
||||
let bmp_ferris = Bmp::from_slice(ferris_data).expect("BMP loading error");
|
||||
// Draw the image with the top left corner at (10, 20) by wrapping it in
|
||||
// an embedded-graphics `Image`.
|
||||
Image::new(&bmp_rust, Point::new(0, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image drawing error");
|
||||
Image::new(&bmp_ferris, Point::new(32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image drawing error");
|
||||
display.flush().await.unwrap();
|
||||
let mut ticker = Ticker::every(Duration::from_millis(50));
|
||||
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
|
||||
|
||||
loop {
|
||||
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image drawing error");
|
||||
display.flush().await.expect("flush error");
|
||||
ferris.step();
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FerrisMovement {
|
||||
pub pos: u16,
|
||||
pub dir: Direction,
|
||||
pub left_threshold: u16,
|
||||
pub right_threshold: u16,
|
||||
}
|
||||
|
||||
impl FerrisMovement {
|
||||
/// Creates a new movement controller.
|
||||
/// Assumes: left_threshold <= right_threshold and pos is within that range.
|
||||
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
dir,
|
||||
left_threshold,
|
||||
right_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
/// Move one tick and "bounce" between thresholds.
|
||||
pub fn step(&mut self) {
|
||||
match self.dir {
|
||||
Direction::Right => {
|
||||
if self.pos >= self.right_threshold {
|
||||
self.dir = Direction::Left; // flip at right boundary
|
||||
} else {
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
Direction::Left => {
|
||||
if self.pos <= self.left_threshold {
|
||||
self.dir = Direction::Right; // flip at left boundary
|
||||
} else {
|
||||
self.pos -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! {
|
||||
log_runner.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
// Create a wave pattern for emio_leds
|
||||
for led in emio_leds.iter_mut() {
|
||||
led.toggle().unwrap();
|
||||
ticker.next().await; // Wait for the next ticker for each toggle
|
||||
}
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
|
||||
writeln!(uart, "panic: {}\r", info).ok();
|
||||
loop {}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use dummy_pin::DummyPin;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_graphics::{Drawable as _, geometry::Point};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs as _;
|
||||
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
|
||||
use embedded_io::Write;
|
||||
use log::info;
|
||||
use ssd1306::{Ssd1306, prelude::*};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
|
||||
log::asynch::UartLoggerRunner,
|
||||
spi,
|
||||
time::Hertz,
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use embedded_graphics::image::Image;
|
||||
use tinybmp::Bmp;
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard OLED example --\n\r";
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
assert!(
|
||||
clocks.io_clocks().spi_clk().to_raw()
|
||||
> (clocks.arm_clocks().cpu_1x_clk().to_raw() as f32 * 1.2) as u32,
|
||||
"SPI reference clock must be larger than CPU 1x clock"
|
||||
);
|
||||
|
||||
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
uart.flush().unwrap();
|
||||
|
||||
let (tx, _) = uart.split();
|
||||
let tx_async = TxAsync::new(tx, true);
|
||||
let log_runner =
|
||||
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_async)
|
||||
.expect("Failed to initialize async logger");
|
||||
spawner.spawn(logger_task(log_runner).unwrap());
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
info!(
|
||||
"SPI reference clock speed: {:?}",
|
||||
clocks.io_clocks().spi_clk()
|
||||
);
|
||||
|
||||
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
let emio_leds: [gpio::Output; 8] = [
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
|
||||
];
|
||||
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
let dc_pin = gpio::Output::new_for_emio(gpio_pins.emio.take(11).unwrap(), gpio::PinState::High);
|
||||
|
||||
let mut reset_pin =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(12).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vdd_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(13).unwrap(), gpio::PinState::High);
|
||||
let mut oled_vbat_switch =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(14).unwrap(), gpio::PinState::High);
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
let spi = spi::Spi::new_for_emio(
|
||||
periphs.spi_0,
|
||||
spi::Config::calculate_for_io_clock(
|
||||
Hertz::MHz(8),
|
||||
clocks.io_clocks(),
|
||||
embedded_hal::spi::MODE_0,
|
||||
spi::SlaveSelectConfig::AutoCsManualStart,
|
||||
),
|
||||
)
|
||||
.expect("Failed to initialize SPI");
|
||||
let exclusive_device = ExclusiveDevice::new(spi, DummyPin::new_high(), NoDelay)
|
||||
.expect("Failed to create exclusive SPI device");
|
||||
let spi_if = SPIInterface::new(exclusive_device, dc_pin);
|
||||
let mut ssd1306 = Ssd1306::new(spi_if, DisplaySize128x32, DisplayRotation::Rotate180);
|
||||
|
||||
oled_vdd_switch.set_low();
|
||||
oled_vbat_switch.set_low();
|
||||
Delay.delay_ms(100).await;
|
||||
|
||||
ssd1306.reset(&mut reset_pin, &mut embassy_time::Delay {});
|
||||
let mut display = ssd1306.into_buffered_graphics_mode();
|
||||
display.init().expect("display init error");
|
||||
|
||||
// Include the BMP file data.
|
||||
let ferris_data = include_bytes!("../../assets/ferris-flat-happy-small.bmp");
|
||||
let rust_logo_data = include_bytes!("../../assets/rust-logo-single-path.bmp");
|
||||
// Parse the BMP file.
|
||||
let bmp_rust = Bmp::from_slice(rust_logo_data).unwrap();
|
||||
let bmp_ferris = Bmp::from_slice(ferris_data).unwrap();
|
||||
// Draw the image with the top left corner at (10, 20) by wrapping it in
|
||||
// an embedded-graphics `Image`.
|
||||
Image::new(&bmp_rust, Point::new(0, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image draw error");
|
||||
Image::new(&bmp_ferris, Point::new(32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image draw error");
|
||||
display.flush().expect("display flush error");
|
||||
let mut ticker = Ticker::every(Duration::from_millis(50));
|
||||
let mut ferris = FerrisMovement::new(32, Direction::Right, 32, 76);
|
||||
|
||||
loop {
|
||||
Image::new(&bmp_ferris, Point::new(ferris.pos as i32, 0))
|
||||
.draw(&mut display)
|
||||
.expect("image draw error");
|
||||
display.flush().expect("display flush error");
|
||||
ferris.step();
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Direction {
|
||||
Right,
|
||||
Left,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FerrisMovement {
|
||||
pub pos: u16,
|
||||
pub dir: Direction,
|
||||
pub left_threshold: u16,
|
||||
pub right_threshold: u16,
|
||||
}
|
||||
|
||||
impl FerrisMovement {
|
||||
/// Creates a new movement controller.
|
||||
/// Assumes: left_threshold <= right_threshold and pos is within that range.
|
||||
pub fn new(pos: u16, dir: Direction, left_threshold: u16, right_threshold: u16) -> Self {
|
||||
Self {
|
||||
pos,
|
||||
dir,
|
||||
left_threshold,
|
||||
right_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
/// Move one tick and "bounce" between thresholds.
|
||||
pub fn step(&mut self) {
|
||||
match self.dir {
|
||||
Direction::Right => {
|
||||
if self.pos >= self.right_threshold {
|
||||
self.dir = Direction::Left; // flip at right boundary
|
||||
} else {
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
Direction::Left => {
|
||||
if self.pos <= self.left_threshold {
|
||||
self.dir = Direction::Right; // flip at left boundary
|
||||
} else {
|
||||
self.pos -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(mut log_runner: UartLoggerRunner) -> ! {
|
||||
log_runner.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
// Create a wave pattern for emio_leds
|
||||
for led in emio_leds.iter_mut() {
|
||||
led.toggle().unwrap();
|
||||
ticker.next().await; // Wait for the next ticker for each toggle
|
||||
}
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
|
||||
writeln!(uart, "panic: {}\r", info).ok();
|
||||
loop {}
|
||||
}
|
||||
+59
-45
@@ -1,8 +1,9 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::{traits::Integer as _, u2};
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
@@ -10,10 +11,13 @@ use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zedboard_bsp::qspi_spansion;
|
||||
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, prelude::*, qspi, uart};
|
||||
use zynq7000_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc, prelude::*, qspi, uart,
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const DISPLAY_CLOCK_CONFIG: bool = false;
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI example --\n\r";
|
||||
const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombination {
|
||||
vendor: qspi::QspiVendor::WinbondAndSpansion,
|
||||
@@ -21,19 +25,16 @@ const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombin
|
||||
two_devices: false,
|
||||
};
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
const ERASE_PROGRAM_READ_TEST: bool = false;
|
||||
const TEST_QSPI_BASE: u32 = 0x20000;
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
@@ -54,24 +55,20 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio(
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
if DISPLAY_CLOCK_CONFIG {
|
||||
log::debug!("clock config: {:?}", clocks);
|
||||
}
|
||||
|
||||
let qspi_clock_config =
|
||||
qspi::ClockConfig::calculate_with_loopback(qspi::SrcSelIo::IoPll, &clocks, 100.MHz())
|
||||
@@ -94,7 +91,14 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
|
||||
qspi_io_mode,
|
||||
qspi_spansion::Config {
|
||||
set_quad_bit_if_necessary: true,
|
||||
latency_config: Some(u2::ZERO),
|
||||
clear_write_protection: true,
|
||||
},
|
||||
);
|
||||
|
||||
let rdid = spansion_qspi.read_rdid_extended();
|
||||
info!(
|
||||
@@ -107,27 +111,48 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
);
|
||||
let cr1 = spansion_qspi.read_configuration_register();
|
||||
info!("QSPI Configuration Register 1: {:?}", cr1);
|
||||
let sr = spansion_qspi.read_status_register_1();
|
||||
info!("QSPI Status Register: {:?}", sr);
|
||||
|
||||
let mut write_buf: [u8; u8::MAX as usize + 1] = [0x0; u8::MAX as usize + 1];
|
||||
let mut write_buf: [u8; 100 * qspi_spansion::PAGE_SIZE] = [0x0; 100 * qspi_spansion::PAGE_SIZE];
|
||||
for (idx, byte) in write_buf.iter_mut().enumerate() {
|
||||
*byte = idx as u8;
|
||||
*byte = (idx % u8::MAX as usize) as u8;
|
||||
}
|
||||
let mut read_buf = [0u8; 256];
|
||||
let mut read_buf = [0u8; 100 * qspi_spansion::PAGE_SIZE];
|
||||
|
||||
if ERASE_PROGRAM_READ_TEST {
|
||||
info!("performing erase, program, read test");
|
||||
spansion_qspi
|
||||
.erase_sector(0x10000)
|
||||
.erase_sector(TEST_QSPI_BASE)
|
||||
.expect("erasing sector failed");
|
||||
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
|
||||
spansion_qspi.read_fast_read(TEST_QSPI_BASE, &mut read_buf, true);
|
||||
for read in read_buf.iter() {
|
||||
assert_eq!(*read, 0xFF);
|
||||
}
|
||||
read_buf.fill(0);
|
||||
spansion_qspi.program_page(0x10000, &write_buf).unwrap();
|
||||
spansion_qspi.read_page_fast_read(0x10000, &mut read_buf, true);
|
||||
for (read, written) in read_buf.iter().zip(write_buf.iter()) {
|
||||
assert_eq!(read, written);
|
||||
let mut current_offset = 0_usize;
|
||||
let mut current_qspi_offset = TEST_QSPI_BASE;
|
||||
for (idx, chunk) in write_buf
|
||||
.chunks(qspi_spansion::RECOMMENDED_PROGRAM_PAGE_SIZE)
|
||||
.enumerate()
|
||||
{
|
||||
spansion_qspi
|
||||
.program(current_qspi_offset, chunk)
|
||||
.expect("programming failed");
|
||||
spansion_qspi.read_fast_read(
|
||||
current_qspi_offset,
|
||||
&mut read_buf[current_offset..current_offset + chunk.len()],
|
||||
true,
|
||||
);
|
||||
assert_eq!(
|
||||
chunk,
|
||||
&read_buf[current_offset..current_offset + chunk.len()],
|
||||
"read and write missmatch at chunk index {}, data offset {}",
|
||||
idx,
|
||||
current_offset
|
||||
);
|
||||
current_offset += chunk.len();
|
||||
current_qspi_offset += chunk.len() as u32;
|
||||
}
|
||||
info!("test successful");
|
||||
}
|
||||
@@ -137,7 +162,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let guard = spansion_lqspi.read_guard();
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(
|
||||
(qspi::QSPI_START_ADDRESS + 0x10000) as *const u8,
|
||||
(qspi::QSPI_START_ADDRESS + TEST_QSPI_BASE as usize) as *const u8,
|
||||
read_buf.as_mut_ptr(),
|
||||
256,
|
||||
);
|
||||
@@ -163,23 +188,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = gic::GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
gic::Interrupt::Sgi(_) => (),
|
||||
gic::Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
@@ -0,0 +1,258 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::error;
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::gpio::Input;
|
||||
use zynq7000_hal::sd::SdClockConfig;
|
||||
use zynq7000_hal::{BootMode, clocks, gpio, gtc, sd::SdCardUninit, uart};
|
||||
use zynq7000_hal::{generic_interrupt_handler, prelude::*};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard SDIO example --\n\r";
|
||||
|
||||
// These are off by default because they write to the SD card as well.
|
||||
const LOW_LEVEL_TESTS: bool = false;
|
||||
const SDMMC_RS_TESTS: bool = false;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DummyTimeSource;
|
||||
|
||||
impl embedded_sdmmc::TimeSource for DummyTimeSource {
|
||||
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
|
||||
embedded_sdmmc::Timestamp::from_calendar(1970, 1, 1, 0, 0, 0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let gpio_pins = gpio::GpioPins::new(periphs.gpio);
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let sdio_clock_config =
|
||||
SdClockConfig::calculate_for_io_clock(clocks.io_clocks(), 100.MHz(), 10.MHz()).unwrap();
|
||||
log::info!("SDIO clock config: {:?}", sdio_clock_config);
|
||||
let sd_card_uninit = SdCardUninit::new_for_sdio_0(
|
||||
periphs.sdio_0,
|
||||
sdio_clock_config,
|
||||
// On the zedboard, the bank has a 1.8 V voltage which is shifted up to 3.3 V by a
|
||||
// level shifter.
|
||||
zynq7000_hal::sd::IoType::LvCmos18,
|
||||
gpio_pins.mio.mio40,
|
||||
gpio_pins.mio.mio41,
|
||||
(
|
||||
gpio_pins.mio.mio42,
|
||||
gpio_pins.mio.mio43,
|
||||
gpio_pins.mio.mio44,
|
||||
gpio_pins.mio.mio45,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let card_detect = Input::new_for_mio(gpio_pins.mio.mio47).unwrap();
|
||||
let write_protect = Input::new_for_mio(gpio_pins.mio.mio46).unwrap();
|
||||
// The card detect being active low makes sense according to the Zedboard docs. Not sure
|
||||
// about write-protect though.. It seems that write protect on means that the
|
||||
// the pin is pulled high.
|
||||
log::info!("Card detect state: {:?}", card_detect.is_low());
|
||||
log::info!("Write protect state: {:?}", write_protect.is_high());
|
||||
|
||||
let capabilities = sd_card_uninit.ll().capabilities();
|
||||
log::debug!("SDIO Capabilities: {:?}", capabilities);
|
||||
|
||||
let present_state = sd_card_uninit.ll().read_present_state();
|
||||
log::debug!("SD present state: {:?}", present_state);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
log::info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
|
||||
let mut mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
|
||||
let sd_result = sd_card_uninit.initialize();
|
||||
let sd_card = match sd_result {
|
||||
Ok(card) => {
|
||||
log::info!("SD card info: {:?}", card.card_info());
|
||||
card
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("SDIO init error: {e:?}");
|
||||
}
|
||||
};
|
||||
|
||||
let mut buf: [u8; 4096] = [0; 4096];
|
||||
|
||||
if LOW_LEVEL_TESTS {
|
||||
log::info!("doing SD card low-level tests");
|
||||
|
||||
let mut cache_buf: [u8; 4096] = [0; 4096];
|
||||
|
||||
// cache the data, will be written back later..
|
||||
sd_card
|
||||
.read_multiple_blocks(&mut cache_buf, 0x1000)
|
||||
.unwrap();
|
||||
|
||||
let mut write_data: [u8; 4096] = [0; 4096];
|
||||
for chunk in write_data.chunks_mut(u8::MAX as usize) {
|
||||
for (idx, byte) in chunk.iter_mut().enumerate() {
|
||||
*byte = idx as u8;
|
||||
}
|
||||
}
|
||||
sd_card.write_multiple_blocks(&write_data, 0x1000).unwrap();
|
||||
|
||||
sd_card.read_multiple_blocks(&mut buf, 0x1000).unwrap();
|
||||
for chunk in buf.chunks(u8::MAX as usize) {
|
||||
for (idx, byte) in chunk.iter().enumerate() {
|
||||
assert_eq!(idx as u8, *byte);
|
||||
}
|
||||
}
|
||||
|
||||
sd_card.write_multiple_blocks(&cache_buf, 0x1000).unwrap();
|
||||
|
||||
log::info!("SD card low-level tests success");
|
||||
}
|
||||
|
||||
buf.fill(0);
|
||||
|
||||
if SDMMC_RS_TESTS {
|
||||
log::info!("doing SD card embedded-sdmmc-rs tests");
|
||||
|
||||
// Now let's look for volumes (also known as partitions) on our block device.
|
||||
// To do this we need a Volume Manager. It will take ownership of the block device.
|
||||
let volume_mgr = embedded_sdmmc::VolumeManager::new(sd_card, DummyTimeSource);
|
||||
// Try and access Volume 0 (i.e. the first partition).
|
||||
// The volume object holds information about the filesystem on that volume.
|
||||
let volume0 = volume_mgr
|
||||
.open_volume(embedded_sdmmc::VolumeIdx(0))
|
||||
.unwrap();
|
||||
|
||||
// Open the root directory (mutably borrows from the volume).
|
||||
let mut current_dir = volume0.open_root_dir().unwrap();
|
||||
log::info!("iterating root directory");
|
||||
current_dir
|
||||
.iterate_dir(|entry| {
|
||||
log::info!("{:?}", entry);
|
||||
core::ops::ControlFlow::Continue(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let new_file = current_dir
|
||||
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadWriteCreateOrTruncate)
|
||||
.unwrap();
|
||||
let string = "test string\n";
|
||||
new_file.write(string.as_bytes()).unwrap();
|
||||
new_file.close().unwrap();
|
||||
|
||||
let read_new = current_dir
|
||||
.open_file_in_dir("__T.TXT", embedded_sdmmc::Mode::ReadOnly)
|
||||
.unwrap();
|
||||
assert_eq!(read_new.length(), string.len() as u32);
|
||||
read_new.read(&mut buf).unwrap();
|
||||
let buf_as_str = core::str::from_utf8(&buf[0..string.len()]).unwrap();
|
||||
assert_eq!(buf_as_str, string);
|
||||
read_new.close().unwrap();
|
||||
|
||||
current_dir.delete_entry_in_dir("__T.TXT").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
current_dir.find_directory_entry("__T.TXT").unwrap_err(),
|
||||
embedded_sdmmc::Error::NotFound
|
||||
);
|
||||
|
||||
if current_dir.find_directory_entry("_TDIR").is_ok() {
|
||||
current_dir.delete_entry_in_dir("_TDIR").unwrap();
|
||||
}
|
||||
|
||||
current_dir.make_dir_in_dir("_TDIR").unwrap();
|
||||
current_dir.change_dir("_TDIR").unwrap();
|
||||
current_dir.change_dir("..").unwrap();
|
||||
current_dir.delete_entry_in_dir("_TDIR").unwrap();
|
||||
|
||||
current_dir.close().unwrap();
|
||||
|
||||
log::info!("SD card embedded-sdmmc-rs success");
|
||||
}
|
||||
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
ticker.next().await; // Wait for the next cycle of the ticker
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
loop {}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::{cell::RefCell, panic::PanicInfo, sync::atomic::AtomicU8};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
|
||||
use embassy_time::{Delay, Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_hal_async::delay::DelayNs as _;
|
||||
use embedded_io::Write;
|
||||
use embedded_io_async::Read as _;
|
||||
use log::info;
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000::spi::FifoWrite;
|
||||
use zynq7000_hal::{
|
||||
BootMode, clocks, generic_interrupt_handler, gpio, gtc,
|
||||
log::asynch::UartLoggerRunner,
|
||||
spi::{self, SpiAsync},
|
||||
uart::{self, TxAsync},
|
||||
};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TestType {
|
||||
Blocking,
|
||||
Async,
|
||||
}
|
||||
|
||||
const TEST_TYPE: TestType = TestType::Async;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 SPI slave example --\n\r";
|
||||
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(zynq7000_hal::LevelShifterConfig::EnableAll),
|
||||
interrupt_config: Some(zynq7000_hal::InteruptConfig::AllInterruptsToCpu0),
|
||||
})
|
||||
.unwrap();
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let mut clocks = clocks::Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
|
||||
let target_spi_ref_clock = clocks.arm_clocks().cpu_1x_clk() * 2;
|
||||
// SPI reference clock must be larger than the CPU 1x clock. Also, taking the largest value
|
||||
// actually seems to be problematic. We take 200 MHz here, which is significantly larger than
|
||||
// the CPU 1x clock which is around 110 MHz.
|
||||
spi::configure_spi_ref_clock(&mut clocks, target_spi_ref_clock);
|
||||
|
||||
let mut gpio_pins = gpio::GpioPins::new(periphs.gpio);
|
||||
|
||||
// Set up global timer counter and embassy time driver.
|
||||
let gtc = gtc::GlobalTimerCounter::new(periphs.gtc, clocks.arm_clocks());
|
||||
zynq7000_embassy::init(clocks.arm_clocks(), gtc);
|
||||
|
||||
// Set up the UART, we are logging with it.
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
uart.flush().unwrap();
|
||||
Delay.delay_ms(1).await;
|
||||
|
||||
let (tx, _rx) = uart.split();
|
||||
let tx_asynch = TxAsync::new(tx, true);
|
||||
let logger_runner =
|
||||
zynq7000_hal::log::asynch::init_with_uart_tx(log::LevelFilter::Trace, tx_asynch)
|
||||
.expect("Failed to initialize async logger");
|
||||
spawner.spawn(logger_task(logger_runner).unwrap());
|
||||
|
||||
// This pin is used to select between a routing from SPI0 to OLED, or SPI0 to SPI1. Low selects
|
||||
// the SPI0 to OLED interface.
|
||||
let _spi_mux_pin =
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(15).unwrap(), gpio::PinState::High);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
|
||||
let mio_led = gpio::Output::new_for_mio(gpio_pins.mio.mio7, gpio::PinState::Low);
|
||||
let emio_leds: [gpio::Output; 8] = [
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(0).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(1).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(2).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(3).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(4).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(5).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(6).unwrap(), gpio::PinState::Low),
|
||||
gpio::Output::new_for_emio(gpio_pins.emio.take(7).unwrap(), gpio::PinState::Low),
|
||||
];
|
||||
|
||||
spawner.spawn(blinky_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
// Enable loopback.
|
||||
spi::enable_spi0_to_spi1_loopback();
|
||||
|
||||
let mut spi_slave = spi::SpiSlave::new(
|
||||
spi::SpiId::Spi1,
|
||||
periphs.spi_1,
|
||||
spi::SlaveConfig {
|
||||
mode: spi::MODE_0,
|
||||
enable_modefail: true,
|
||||
},
|
||||
);
|
||||
zynq7000_hal::register_interrupt(spi_slave.interrupt_id(), spi_interrupt_handler);
|
||||
|
||||
let spi_master = spi::Spi::new_for_emio(
|
||||
periphs.spi_0,
|
||||
spi::Config::new(
|
||||
// 10 MHz maximum rating of the sensor.
|
||||
zynq7000::spi::BaudDivSel::By64,
|
||||
// l3gd20::MODE,
|
||||
embedded_hal::spi::MODE_0,
|
||||
spi::SlaveSelectConfig::AutoCsManualStart,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let cs_pin = spi::ChipSelectPin::new(spi_master.id(), spi::ChipSelect::Slave0);
|
||||
|
||||
// We pre-load the FIFO with a fixed, known sequence.
|
||||
for i in 0..spi::FIFO_DEPTH / 2 {
|
||||
spi_slave.write_tx_data(i as u8);
|
||||
}
|
||||
log::info!("SPI slave initialized, enabling interrupts and peripheral");
|
||||
spi_slave.enable_interrupts(true);
|
||||
spi_slave.enable();
|
||||
|
||||
static RX_DATA_PIPE: static_cell::ConstStaticCell<
|
||||
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, 256>,
|
||||
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
|
||||
|
||||
let pipe = RX_DATA_PIPE.take();
|
||||
let (mut slave_rx_reader, writer) = pipe.split();
|
||||
critical_section::with(|cs| {
|
||||
RX_DATA_WRITER.borrow(cs).replace(Some(writer));
|
||||
});
|
||||
|
||||
if TEST_TYPE == TestType::Blocking {
|
||||
let mut spi_master =
|
||||
embedded_hal_bus::spi::ExclusiveDevice::new(spi_master, cs_pin, embassy_time::Delay)
|
||||
.expect("creating exlusive SPI master failed");
|
||||
|
||||
let current_rx_val = blocking_tests_small(&mut spi_master, &mut slave_rx_reader).await;
|
||||
log::info!("blocking tests with small data blocks done");
|
||||
Delay.delay_ms(10).await;
|
||||
blocking_tests_large(&mut spi_master, current_rx_val, &mut slave_rx_reader).await;
|
||||
log::info!("blocking tests with large data blocks done");
|
||||
} else {
|
||||
let spi_master = SpiAsync::new(spi_master);
|
||||
let mut spi_master =
|
||||
embedded_hal_bus::spi::ExclusiveDevice::new(spi_master, cs_pin, embassy_time::Delay)
|
||||
.expect("creating exlusive SPI master failed");
|
||||
let current_rx_val =
|
||||
blocking_tests_small_async(&mut spi_master, &mut slave_rx_reader).await;
|
||||
log::info!("async tests with small data blocks done");
|
||||
Delay.delay_ms(10).await;
|
||||
blocking_tests_large_async(&mut spi_master, current_rx_val, &mut slave_rx_reader).await;
|
||||
log::info!("blocking tests with large data blocks done");
|
||||
}
|
||||
|
||||
log::info!("SPI slave tests done");
|
||||
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
loop {
|
||||
ticker.next().await; // Wait for the next cycle of the ticker
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn blocking_tests_small(
|
||||
spi_master: &mut impl embedded_hal::spi::SpiDevice,
|
||||
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
|
||||
) -> u8 {
|
||||
let mut tx_buf = [0; 64];
|
||||
let mut buf = [0; 64];
|
||||
|
||||
// Write test.
|
||||
// We should read back 0,1,2,3 here but the sent values are ignored.
|
||||
spi_master.write(&[1, 2, 3, 4]).unwrap();
|
||||
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
|
||||
assert_eq!(
|
||||
&buf[0..4],
|
||||
&[1, 2, 3, 4],
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
|
||||
// Read test.
|
||||
spi_master.read(&mut buf[0..4]).unwrap();
|
||||
// Now we expect 4,5,6,7 sent by the SPI slave
|
||||
assert_eq!(
|
||||
&buf[0..4],
|
||||
&[4, 5, 6, 7],
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
// Slave should receive dummy data
|
||||
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
|
||||
assert_eq!(
|
||||
&buf[0..4],
|
||||
&[0, 0, 0, 0],
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
|
||||
// Transfer test.
|
||||
for (i, item) in tx_buf.iter_mut().enumerate().take(16) {
|
||||
*item = (i * 2) as u8;
|
||||
}
|
||||
spi_master
|
||||
.transfer(&mut buf[0..16], &tx_buf[0..16])
|
||||
.unwrap();
|
||||
for i in 8..24 {
|
||||
assert_eq!(
|
||||
buf[i - 8],
|
||||
i as u8,
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
}
|
||||
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
|
||||
for (i, item) in buf.iter().enumerate().take(16) {
|
||||
assert_eq!(
|
||||
*item,
|
||||
(i * 2) as u8,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
}
|
||||
|
||||
// Transfer in place test.
|
||||
for (i, item) in buf.iter_mut().enumerate().take(16) {
|
||||
*item = (i * 2) as u8;
|
||||
}
|
||||
spi_master.transfer_in_place(&mut buf[0..16]).unwrap();
|
||||
for i in 24..40 {
|
||||
assert_eq!(
|
||||
buf[i - 24],
|
||||
i as u8,
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
}
|
||||
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
|
||||
for (i, item) in buf.iter().enumerate().take(16) {
|
||||
assert_eq!(
|
||||
*item,
|
||||
(i * 2) as u8,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
}
|
||||
40
|
||||
}
|
||||
|
||||
pub async fn blocking_tests_large(
|
||||
spi_master: &mut impl embedded_hal::spi::SpiDevice,
|
||||
mut current_rx_val: u8,
|
||||
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
|
||||
) -> u8 {
|
||||
const BUF_LEN: usize = 164;
|
||||
let mut buf = [0; BUF_LEN];
|
||||
|
||||
// Write test.
|
||||
for (i, item) in buf.iter_mut().enumerate() {
|
||||
*item = i as u8;
|
||||
}
|
||||
spi_master.write(&buf).unwrap();
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for (i, item) in buf.iter().enumerate() {
|
||||
assert_eq!(*item, i as u8, "slave did not receive the expected data");
|
||||
}
|
||||
current_rx_val = current_rx_val.wrapping_add(buf.len() as u8);
|
||||
|
||||
// Read test.
|
||||
spi_master.read(&mut buf).unwrap();
|
||||
for item in &buf {
|
||||
assert_eq!(
|
||||
*item, current_rx_val,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
current_rx_val = current_rx_val.wrapping_add(1);
|
||||
}
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for item in &buf {
|
||||
assert_eq!(*item, 0, "slave did not receive the expected data");
|
||||
}
|
||||
|
||||
// Transfer test.
|
||||
let mut tx_buf = [0; BUF_LEN];
|
||||
for (i, item) in tx_buf.iter_mut().enumerate() {
|
||||
*item = i as u8;
|
||||
}
|
||||
spi_master.transfer(&mut buf, &tx_buf).unwrap();
|
||||
for item in &buf {
|
||||
assert_eq!(
|
||||
*item, current_rx_val,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
current_rx_val = current_rx_val.wrapping_add(1);
|
||||
}
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for (i, item) in buf.iter().enumerate() {
|
||||
assert_eq!(*item, i as u8, "slave did not receive the expected data");
|
||||
}
|
||||
|
||||
// Transfer in place test.
|
||||
for (i, item) in buf.iter_mut().enumerate() {
|
||||
*item = i as u8;
|
||||
}
|
||||
spi_master.transfer_in_place(&mut buf).unwrap();
|
||||
for item in &buf {
|
||||
assert_eq!(
|
||||
*item, current_rx_val,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
current_rx_val = current_rx_val.wrapping_add(1);
|
||||
}
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for (i, item) in buf.iter().enumerate() {
|
||||
assert_eq!(*item, i as u8, "slave did not receive the expected data");
|
||||
}
|
||||
current_rx_val
|
||||
}
|
||||
|
||||
pub async fn blocking_tests_small_async(
|
||||
spi_master: &mut impl embedded_hal_async::spi::SpiDevice,
|
||||
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
|
||||
) -> u8 {
|
||||
let mut tx_buf = [0; 64];
|
||||
let mut buf = [0; 64];
|
||||
|
||||
// Write test.
|
||||
// We should read back 0,1,2,3 here but the sent values are ignored.
|
||||
spi_master.write(&[1, 2, 3, 4]).await.unwrap();
|
||||
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
|
||||
assert_eq!(
|
||||
&buf[0..4],
|
||||
&[1, 2, 3, 4],
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
|
||||
// Read test.
|
||||
spi_master.read(&mut buf[0..4]).await.unwrap();
|
||||
// Now we expect 4,5,6,7 sent by the SPI slave
|
||||
assert_eq!(
|
||||
&buf[0..4],
|
||||
&[4, 5, 6, 7],
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
// Slave should receive dummy data
|
||||
slave_rx_reader.read_exact(&mut buf[0..4]).await.unwrap();
|
||||
assert_eq!(
|
||||
&buf[0..4],
|
||||
&[0, 0, 0, 0],
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
|
||||
// Transfer test.
|
||||
for (i, item) in tx_buf.iter_mut().enumerate().take(16) {
|
||||
*item = (i * 2) as u8;
|
||||
}
|
||||
spi_master
|
||||
.transfer(&mut buf[0..16], &tx_buf[0..16])
|
||||
.await
|
||||
.unwrap();
|
||||
for i in 8..24 {
|
||||
assert_eq!(
|
||||
buf[i - 8],
|
||||
i as u8,
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
}
|
||||
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
|
||||
for (i, item) in buf.iter().enumerate().take(16) {
|
||||
assert_eq!(
|
||||
*item,
|
||||
(i * 2) as u8,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
}
|
||||
|
||||
// Transfer in place test.
|
||||
for (i, item) in buf.iter_mut().enumerate().take(16) {
|
||||
*item = (i * 2) as u8;
|
||||
}
|
||||
spi_master.transfer_in_place(&mut buf[0..16]).await.unwrap();
|
||||
for i in 24..40 {
|
||||
assert_eq!(
|
||||
buf[i - 24],
|
||||
i as u8,
|
||||
"slave did not receive the expected data"
|
||||
);
|
||||
}
|
||||
slave_rx_reader.read_exact(&mut buf[0..16]).await.unwrap();
|
||||
for (i, item) in buf.iter().enumerate().take(16) {
|
||||
assert_eq!(
|
||||
*item,
|
||||
(i * 2) as u8,
|
||||
"slave did not send the expected data"
|
||||
);
|
||||
}
|
||||
40
|
||||
}
|
||||
|
||||
pub async fn blocking_tests_large_async(
|
||||
spi_master: &mut impl embedded_hal_async::spi::SpiDevice,
|
||||
mut current_rx_val: u8,
|
||||
slave_rx_reader: &mut embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, 256>,
|
||||
) -> u8 {
|
||||
const BUF_LEN: usize = 164;
|
||||
let mut buf = [0; BUF_LEN];
|
||||
|
||||
// Write test.
|
||||
for (i, item) in buf.iter_mut().enumerate() {
|
||||
*item = i as u8;
|
||||
}
|
||||
spi_master.write(&buf).await.unwrap();
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for (i, &item) in buf.iter().enumerate() {
|
||||
assert_eq!(item, i as u8, "slave did not receive the expected data");
|
||||
}
|
||||
current_rx_val = current_rx_val.wrapping_add(buf.len() as u8);
|
||||
|
||||
// Read test.
|
||||
spi_master.read(&mut buf).await.unwrap();
|
||||
for &item in &buf {
|
||||
assert_eq!(item, current_rx_val, "slave did not send the expected data");
|
||||
current_rx_val = current_rx_val.wrapping_add(1);
|
||||
}
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for &item in &buf {
|
||||
assert_eq!(item, 0, "slave did not receive the expected data");
|
||||
}
|
||||
|
||||
// Transfer test.
|
||||
let mut tx_buf = [0; BUF_LEN];
|
||||
for (i, item) in tx_buf.iter_mut().enumerate() {
|
||||
*item = i as u8;
|
||||
}
|
||||
spi_master.transfer(&mut buf, &tx_buf).await.unwrap();
|
||||
for &item in &buf {
|
||||
assert_eq!(item, current_rx_val, "slave did not send the expected data");
|
||||
current_rx_val = current_rx_val.wrapping_add(1);
|
||||
}
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for (i, &item) in buf.iter().enumerate() {
|
||||
assert_eq!(item, i as u8, "slave did not receive the expected data");
|
||||
}
|
||||
|
||||
// Transfer in place test.
|
||||
for (i, item) in buf.iter_mut().enumerate() {
|
||||
*item = i as u8;
|
||||
}
|
||||
spi_master.transfer_in_place(&mut buf).await.unwrap();
|
||||
for &item in &buf {
|
||||
assert_eq!(item, current_rx_val, "slave did not send the expected data");
|
||||
current_rx_val = current_rx_val.wrapping_add(1);
|
||||
}
|
||||
slave_rx_reader
|
||||
.read_exact(&mut buf[0..BUF_LEN])
|
||||
.await
|
||||
.unwrap();
|
||||
for (i, &item) in buf.iter().enumerate() {
|
||||
assert_eq!(item, i as u8, "slave did not receive the expected data");
|
||||
}
|
||||
current_rx_val
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn logger_task(mut logger_task: UartLoggerRunner) {
|
||||
logger_task.run().await;
|
||||
}
|
||||
|
||||
static RX_DATA_WRITER: critical_section::Mutex<
|
||||
RefCell<Option<embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, 256>>>,
|
||||
> = critical_section::Mutex::new(RefCell::new(None));
|
||||
|
||||
unsafe fn spi_interrupt_handler() {
|
||||
static CURRENT_TX_VAL: AtomicU8 = AtomicU8::new((spi::FIFO_DEPTH / 2) as u8);
|
||||
|
||||
let mut buf = [0; 64];
|
||||
let mut current_val = CURRENT_TX_VAL.load(core::sync::atomic::Ordering::Relaxed);
|
||||
let mut spi = unsafe { spi::SpiLowLevel::steal(spi::SpiId::Spi1) };
|
||||
|
||||
let status = spi.read_interrupt_status();
|
||||
spi.write_interrupt_status(status);
|
||||
|
||||
let mut index = 0;
|
||||
while spi.read_interrupt_status().rx_not_empty() && index < buf.len() {
|
||||
buf[index] = spi.read_rx_data().value();
|
||||
index += 1;
|
||||
}
|
||||
while !spi.read_interrupt_status().tx_full() {
|
||||
spi.write_tx_data(FifoWrite::new(current_val));
|
||||
current_val = current_val.wrapping_add(1);
|
||||
}
|
||||
CURRENT_TX_VAL.store(current_val, core::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
critical_section::with(|cs| {
|
||||
let opt_writer = RX_DATA_WRITER.borrow(cs).borrow();
|
||||
if let Some(writer) = opt_writer.as_ref() {
|
||||
// In a real application, you would read the received data from the SPI peripheral here.
|
||||
// For this example, we just write a dummy value to the pipe to demonstrate the concept.
|
||||
let _ = writer.try_write(&buf[0..index]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn blinky_task(mut mio_led: gpio::Output, mut emio_leds: [gpio::Output; 8]) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(200));
|
||||
loop {
|
||||
mio_led.toggle().unwrap();
|
||||
|
||||
// Create a wave pattern for emio_leds
|
||||
for led in emio_leds.iter_mut() {
|
||||
led.toggle().unwrap();
|
||||
ticker.next().await; // Wait for the next ticker for each toggle
|
||||
}
|
||||
ticker.next().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(Undefined)]
|
||||
fn undefined_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(PrefetchAbort)]
|
||||
fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
nop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
|
||||
writeln!(uart, "panic: {}\r", info).ok();
|
||||
loop {}
|
||||
}
|
||||
+15
-37
@@ -1,22 +1,20 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use axi_uart16550::AxiUart16550;
|
||||
use axi_uartlite::AxiUartlite;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use fugit::RateExtU32;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler, gic,
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
@@ -31,12 +29,9 @@ const INIT_STRING: &str = "-- Zynq 7000 Zedboard blocking UART example --\n\r";
|
||||
const AXI_UARTLITE_BASE_ADDR: u32 = 0x42C0_0000;
|
||||
const AXI_UAR16550_BASE_ADDR: u32 = 0x43C0_0000;
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
@@ -99,7 +94,6 @@ impl UartMultiplexer {
|
||||
}
|
||||
}
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -109,7 +103,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = gic::Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -126,7 +120,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut log_uart = Uart::new_with_mio(
|
||||
let mut log_uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
@@ -135,13 +129,7 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
log_uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
|
||||
// Safety: Co-operative multi-tasking is used.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
log_uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(log_uart, log::LevelFilter::Trace, false);
|
||||
|
||||
// UART0 routed through EMIO to PL pins.
|
||||
let mut uart_0 =
|
||||
@@ -151,7 +139,8 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
|
||||
// TODO: Can we determine/read the clock frequency to the FPGAs as well?
|
||||
let (clk_config, error) =
|
||||
axi_uart16550::ClkConfig::new_autocalc_with_error(100.MHz(), 115200).unwrap();
|
||||
axi_uart16550::ClockConfig::new_autocalc_with_error(fugit_03::HertzU32::MHz(100), 115200)
|
||||
.unwrap();
|
||||
assert!(error < 0.02);
|
||||
let mut uart_16550 = unsafe {
|
||||
AxiUart16550::new(
|
||||
@@ -219,23 +208,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(_spi_interrupt) => (),
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
+84
-75
@@ -25,11 +25,11 @@
|
||||
#![no_main]
|
||||
extern crate alloc;
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use alloc::format;
|
||||
use axi_uart16550::AxiUart16550;
|
||||
use axi_uartlite::AxiUartlite;
|
||||
use core::{cell::RefCell, panic::PanicInfo};
|
||||
use cortex_ar::asm::nop;
|
||||
use critical_section::Mutex;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
@@ -37,19 +37,20 @@ use embedded_alloc::LlffHeap as Heap;
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write as _;
|
||||
use heapless::spsc::Queue;
|
||||
use log::{error, info, warn};
|
||||
use log::{info, warn};
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::Clocks,
|
||||
configure_level_shifter,
|
||||
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
|
||||
configure_level_shifter, generic_interrupt_handler,
|
||||
gic::{Configurator, Interrupt},
|
||||
gpio::{GpioPins, Output, PinState},
|
||||
gtc::GlobalTimerCounter,
|
||||
l2_cache,
|
||||
time::Hertz,
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
uart::{self, ClockConfig, Config, Uart},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum UartMode {
|
||||
Uart0ToUartlite,
|
||||
Uart0ToUart16550,
|
||||
@@ -74,6 +75,14 @@ const AXI_UAR16550_BASE_ADDR: u32 = 0x43C0_0000;
|
||||
pub const UARTLITE_PL_INT_ID: usize = 0;
|
||||
pub const UART16550_PL_INT_ID: usize = 1;
|
||||
|
||||
pub const UART_SPEED: u32 = 115_200;
|
||||
|
||||
// Other common baud rates to test with:
|
||||
|
||||
// pub const UART_SPEED: u32 = 9600;
|
||||
// pub const UART_SPEED: u32 = 230_400;
|
||||
// pub const UART_SPEED: u32 = 912_600;
|
||||
|
||||
const RB_SIZE: usize = 512;
|
||||
|
||||
// These queues are used to send all data received in the UART interrupt handlers to the main
|
||||
@@ -88,19 +97,16 @@ static QUEUE_UART16550: static_cell::ConstStaticCell<heapless::spsc::Queue<u8, R
|
||||
// Those are all used by the interrupt handler, so we have to do the Mutex dance.
|
||||
static RX_UART_0: Mutex<RefCell<Option<zynq7000_hal::uart::Rx>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
|
||||
static UART_0_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
|
||||
static UARTLITE_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8, RB_SIZE>>>> =
|
||||
static UART16550_PROD: Mutex<RefCell<Option<heapless::spsc::Producer<'static, u8>>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
@@ -164,7 +170,6 @@ impl UartMultiplexer {
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(spawner: Spawner) -> ! {
|
||||
let mut dp = Peripherals::take().unwrap();
|
||||
l2_cache::init_with_defaults(&mut dp.l2c);
|
||||
@@ -175,7 +180,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLOCK_FREQUENCY).unwrap();
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = GicConfigurator::new_with_init(dp.gicc, dp.gicd);
|
||||
let mut gic = Configurator::new_with_init(dp.gicc, dp.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
// AXI UARTLite documentation mentions that a rising-edge sensitive interrupt is generated,
|
||||
@@ -197,7 +202,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut log_uart = Uart::new_with_mio(
|
||||
let mut log_uart = Uart::new_with_mio_for_uart_1(
|
||||
dp.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
@@ -214,14 +219,21 @@ async fn main(spawner: Spawner) -> ! {
|
||||
unsafe { HEAP.init(&raw mut HEAP_MEM as usize, HEAP_SIZE) }
|
||||
}
|
||||
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
log_uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
// Register the interrupts for the PL.
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Pl0),
|
||||
on_interrupt_axi_uartlite,
|
||||
);
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Pl1),
|
||||
on_interrupt_axi_16550,
|
||||
);
|
||||
zynq7000_hal::register_interrupt(
|
||||
Interrupt::Spi(zynq7000_hal::gic::SpiInterrupt::Uart0),
|
||||
on_interrupt_uart_0,
|
||||
);
|
||||
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(log_uart, log::LevelFilter::Trace, false);
|
||||
|
||||
// Set up UART multiplexing before creating and configuring the UARTs.
|
||||
let mut uart_mux = UartMultiplexer::new([
|
||||
@@ -229,24 +241,42 @@ async fn main(spawner: Spawner) -> ! {
|
||||
Output::new_for_emio(gpio_pins.emio.take(9).unwrap(), PinState::Low),
|
||||
Output::new_for_emio(gpio_pins.emio.take(10).unwrap(), PinState::Low),
|
||||
]);
|
||||
let mut uart_speed = UART_SPEED;
|
||||
match UART_MODE {
|
||||
UartMode::Uart0ToUartlite => uart_mux.select(UartSel::Uart0ToUartlite),
|
||||
UartMode::Uart0ToUart16550 => uart_mux.select(UartSel::Uart0ToUart16550),
|
||||
UartMode::UartliteToUart16550 => uart_mux.select(UartSel::UartliteToUart16550),
|
||||
}
|
||||
if (UART_MODE == UartMode::Uart0ToUartlite || UART_MODE == UartMode::UartliteToUart16550)
|
||||
&& uart_speed != 115200
|
||||
{
|
||||
log::warn!("UARTLITE speed is not configurable. Hardcoding UART speed to 115200");
|
||||
uart_speed = 115200;
|
||||
}
|
||||
|
||||
let uart0_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), uart_speed)
|
||||
.unwrap()
|
||||
.0;
|
||||
// UART0 routed through EMIO to PL pins.
|
||||
let uart_0 =
|
||||
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart_clk_config)).unwrap();
|
||||
Uart::new_with_emio(dp.uart_0, Config::new_with_clk_config(uart0_clk_config)).unwrap();
|
||||
// Safety: Valid address of AXI UARTLITE.
|
||||
let mut uartlite = unsafe { AxiUartlite::new(AXI_UARTLITE_BASE_ADDR) };
|
||||
// We need to call this before splitting the structure, because the interrupt signal is
|
||||
// used for both TX and RX, so the API is only exposed for this structure.
|
||||
uartlite.enable_interrupt();
|
||||
|
||||
let (clk_config, error) =
|
||||
axi_uart16550::ClkConfig::new_autocalc_with_error(clocks.pl_clocks()[0], 115200).unwrap();
|
||||
assert!(error < 0.02);
|
||||
let (clk_config, error) = axi_uart16550::ClockConfig::new_autocalc_with_error(
|
||||
fugit_03::HertzU32::from_raw(clocks.pl_clocks()[0].to_raw()),
|
||||
uart_speed,
|
||||
)
|
||||
.unwrap();
|
||||
if error > 0.02 {
|
||||
log::warn!(
|
||||
"Calculated clock config for AXI UART16550 has error of {} %, which is higher than 2%. This may lead to incorrect baud rate. Consider changing the input clock or the target baud rate.",
|
||||
(error * 100.0)
|
||||
);
|
||||
}
|
||||
let _uart_16550 = unsafe {
|
||||
AxiUart16550::new(
|
||||
AXI_UAR16550_BASE_ADDR,
|
||||
@@ -276,7 +306,7 @@ async fn main(spawner: Spawner) -> ! {
|
||||
let (uartlite_prod, mut uartlite_cons) = QUEUE_UARTLITE.take().split();
|
||||
let (uart16550_prod, mut uart16550_cons) = QUEUE_UART16550.take().split();
|
||||
// Use our helper function to start RX handling.
|
||||
uart_0_rx.start_interrupt_driven_reception();
|
||||
uart_0_rx.start_interrupt_driven_reception(0xFF);
|
||||
// Use our helper function to start RX handling.
|
||||
uart_16550_rx.start_interrupt_driven_reception();
|
||||
critical_section::with(|cs| {
|
||||
@@ -288,20 +318,20 @@ async fn main(spawner: Spawner) -> ! {
|
||||
.replace(uart16550_prod);
|
||||
RX_UART_0.borrow(cs).borrow_mut().replace(uart_0_rx);
|
||||
});
|
||||
spawner.spawn(led_task(mio_led, emio_leds)).unwrap();
|
||||
spawner.spawn(led_task(mio_led, emio_leds).unwrap());
|
||||
|
||||
match UART_MODE {
|
||||
UartMode::Uart0ToUartlite => {
|
||||
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
|
||||
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
|
||||
spawner.spawn(uartlite_task(uartlite_tx).unwrap());
|
||||
spawner.spawn(uart_0_task(uart_0_tx).unwrap());
|
||||
}
|
||||
UartMode::Uart0ToUart16550 => {
|
||||
spawner.spawn(uart_0_task(uart_0_tx)).unwrap();
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
|
||||
spawner.spawn(uart_0_task(uart_0_tx).unwrap());
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx).unwrap());
|
||||
}
|
||||
UartMode::UartliteToUart16550 => {
|
||||
spawner.spawn(uartlite_task(uartlite_tx)).unwrap();
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx)).unwrap();
|
||||
spawner.spawn(uartlite_task(uartlite_tx).unwrap());
|
||||
spawner.spawn(uart_16550_task(uart_16550_tx).unwrap());
|
||||
}
|
||||
}
|
||||
let mut read_buf: [u8; RB_SIZE] = [0; RB_SIZE];
|
||||
@@ -391,7 +421,8 @@ async fn uartlite_task(uartlite: axi_uartlite::Tx) {
|
||||
#[embassy_executor::task]
|
||||
async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
|
||||
let mut ticker = Ticker::every(Duration::from_millis(1000));
|
||||
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx);
|
||||
let mut tx_async = zynq7000_hal::uart::TxAsync::new(uart_tx, false);
|
||||
|
||||
let str0 = build_print_string("UART0:", "Hello World");
|
||||
let str1 = build_print_string(
|
||||
"UART0:",
|
||||
@@ -400,7 +431,7 @@ async fn uart_0_task(uart_tx: zynq7000_hal::uart::Tx) {
|
||||
let mut idx = 0;
|
||||
let print_strs = [str0.as_bytes(), str1.as_bytes()];
|
||||
loop {
|
||||
tx_async.write(print_strs[idx]).await;
|
||||
tx_async.write(print_strs[idx]).unwrap().await;
|
||||
idx += 1;
|
||||
if idx == 2 {
|
||||
idx = 0;
|
||||
@@ -433,36 +464,12 @@ async fn uart_16550_task(uart_tx: axi_uart16550::Tx) {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
|
||||
match irq_info.interrupt() {
|
||||
Interrupt::Sgi(_) => (),
|
||||
Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == zynq7000_hal::gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
Interrupt::Spi(spi_interrupt) => match spi_interrupt {
|
||||
zynq7000_hal::gic::SpiInterrupt::Pl0 => {
|
||||
on_interrupt_axi_uartlite();
|
||||
}
|
||||
zynq7000_hal::gic::SpiInterrupt::Pl1 => {
|
||||
on_interrupt_axi_16550();
|
||||
}
|
||||
zynq7000_hal::gic::SpiInterrupt::Uart0 => {
|
||||
on_interrupt_uart_0();
|
||||
}
|
||||
|
||||
_ => (),
|
||||
},
|
||||
Interrupt::Invalid(_) => (),
|
||||
Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
fn on_interrupt_axi_uartlite() {
|
||||
@@ -492,19 +499,19 @@ fn on_interrupt_axi_16550() {
|
||||
let iir = rx.read_iir();
|
||||
if let Ok(int_id) = iir.int_id() {
|
||||
match int_id {
|
||||
axi_uart16550::registers::IntId2::ReceiverLineStatus => {
|
||||
axi_uart16550::registers::InterruptId2::ReceiverLineStatus => {
|
||||
let errors = rx.on_interrupt_receiver_line_status(iir);
|
||||
warn!("Receiver line status error: {errors:?}");
|
||||
}
|
||||
axi_uart16550::registers::IntId2::RxDataAvailable
|
||||
| axi_uart16550::registers::IntId2::CharTimeout => {
|
||||
axi_uart16550::registers::InterruptId2::RxDataAvailable
|
||||
| axi_uart16550::registers::InterruptId2::CharTimeout => {
|
||||
read_bytes = rx.on_interrupt_data_available_or_char_timeout(int_id, &mut buf);
|
||||
}
|
||||
axi_uart16550::registers::IntId2::ThrEmpty => {
|
||||
axi_uart16550::registers::InterruptId2::ThrEmpty => {
|
||||
let mut tx = unsafe { axi_uart16550::Tx::steal(AXI_UAR16550_BASE_ADDR as usize) };
|
||||
axi_uart16550::tx_async::on_interrupt_tx(&mut tx, 0);
|
||||
}
|
||||
axi_uart16550::registers::IntId2::ModemStatus => (),
|
||||
axi_uart16550::registers::InterruptId2::ModemStatus => (),
|
||||
}
|
||||
}
|
||||
// Send received RX data to main task.
|
||||
@@ -531,8 +538,9 @@ fn on_interrupt_uart_0() {
|
||||
.on_interrupt(&mut buf, true)
|
||||
.read_bytes();
|
||||
});
|
||||
// Safety: This function is only called once inside the interrupt handler.
|
||||
// Handle TX next: Handle pending asynchronous TX operations.
|
||||
zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0);
|
||||
unsafe { zynq7000_hal::uart::on_interrupt_tx(zynq7000_hal::uart::UartId::Uart0) };
|
||||
// Send received RX data to main task.
|
||||
if read_bytes > 0 {
|
||||
critical_section::with(|cs| {
|
||||
@@ -569,6 +577,7 @@ fn prefetch_handler(_faulting_addr: usize) -> ! {
|
||||
/// Panic handler
|
||||
#[panic_handler]
|
||||
fn panic(info: &PanicInfo) -> ! {
|
||||
error!("Panic: {info:?}");
|
||||
let mut uart = unsafe { uart::Uart::steal(uart::UartId::Uart1) };
|
||||
writeln!(uart, "panic: {}\r", info).ok();
|
||||
loop {}
|
||||
}
|
||||
@@ -1,31 +1,27 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_time::{Duration, Ticker};
|
||||
use embedded_hal::digital::StatefulOutputPin;
|
||||
use embedded_io::Write;
|
||||
use log::{error, info};
|
||||
use zedboard::PS_CLOCK_FREQUENCY;
|
||||
use zynq7000_hal::{BootMode, clocks, gic, gpio, gtc, uart};
|
||||
use zynq7000_hal::{BootMode, clocks, generic_interrupt_handler, gpio, gtc, uart};
|
||||
|
||||
use zynq7000_rt as _;
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard GPIO blinky example --\n\r";
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
/// Entry point which calls the embassy main method.
|
||||
#[zynq7000_rt::entry]
|
||||
fn entry_point() -> ! {
|
||||
main();
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
#[unsafe(export_name = "main")]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
@@ -46,21 +42,15 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio(
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Trace, false);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
@@ -92,23 +82,12 @@ async fn main(_spawner: Spawner) -> ! {
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
fn irq_handler() {
|
||||
let mut gic_helper = gic::GicInterruptHelper::new();
|
||||
let irq_info = gic_helper.acknowledge_interrupt();
|
||||
match irq_info.interrupt() {
|
||||
gic::Interrupt::Sgi(_) => (),
|
||||
gic::Interrupt::Ppi(ppi_interrupt) => {
|
||||
if ppi_interrupt == gic::PpiInterrupt::GlobalTimer {
|
||||
unsafe {
|
||||
zynq7000_embassy::on_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
gic::Interrupt::Spi(_spi_interrupt) => (),
|
||||
gic::Interrupt::Invalid(_) => (),
|
||||
gic::Interrupt::Spurious => (),
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
panic!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
gic_helper.end_of_interrupt(irq_info);
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
@@ -5,4 +5,4 @@ break main
|
||||
|
||||
load
|
||||
|
||||
continue
|
||||
# continue
|
||||
@@ -0,0 +1,29 @@
|
||||
Change Log
|
||||
=======
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
## Fixed
|
||||
|
||||
- QSPI robustness fixes. Read, fast-read and write operations are now chunked according to the 252
|
||||
byte limit specified in the TRM.
|
||||
|
||||
## Added
|
||||
|
||||
- QSPI constructor can now optionally clear block protection and set latency configuration.
|
||||
|
||||
## Changed
|
||||
|
||||
- Alignment rules of Spansion QSPI page program now only require 4 byte aligned size.
|
||||
|
||||
# [v0.1.0]
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zedboard-bsp-v0.1.0...HEAD
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zedboard-bsp-v0.1.0
|
||||
@@ -10,9 +10,14 @@ keywords = ["no-std", "zedboard", "bare-metal", "amd", "zynq7000"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
zynq7000 = { path = "../zynq7000", version = "0.1" }
|
||||
zynq7000 = { path = "../zynq7000", version = "0.4" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal", version = "0.1" }
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
log = "0.4"
|
||||
arbitrary-int = "2"
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
# program."]
|
||||
# program."]
|
||||
#![doc = r""]
|
||||
#![doc = r"This configuration file contains static DDR configuration parameters extracted from the"]
|
||||
#![doc = r"AMD ps7init.tcl file"]
|
||||
#![doc = r"AMD ps7init.tcl file. It was generated for the MT41K128M16JT-125 DDR chip."]
|
||||
use zynq7000::ddrc::regs;
|
||||
use zynq7000_hal::ddr::DdrcConfigSet;
|
||||
pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
|
||||
@@ -34,7 +34,7 @@ pub const DDRC_CONFIG_ZEDBOARD: DdrcConfigSet = DdrcConfigSet {
|
||||
ctrl_reg5: regs::CtrlReg5::new_with_raw_value(0x00466111),
|
||||
ctrl_reg6: regs::CtrlReg6::new_with_raw_value(0x00032222),
|
||||
che_t_zq: regs::CheTZq::new_with_raw_value(0x10200802),
|
||||
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x10200802),
|
||||
che_t_zq_short_interval_reg: regs::CheTZqShortInterval::new_with_raw_value(0x0690cb73),
|
||||
deep_powerdown: regs::DeepPowerdown::new_with_raw_value(0x000001fe),
|
||||
reg_2c: regs::Reg2c::new_with_raw_value(0x1cffffff),
|
||||
reg_2d: regs::Reg2d::new_with_raw_value(0x00000200),
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
# program."]
|
||||
# program."]
|
||||
#![doc = r""]
|
||||
#![doc = r"This configuration file contains static DDRIOB configuration parameters extracted from the"]
|
||||
#![doc = r"AMD ps7init.tcl file"]
|
||||
#![doc = r"AMD ps7init.tcl file. It was generated for the MT41K128M16JT-125 DDR chip."]
|
||||
use zynq7000::ddrc::regs;
|
||||
use zynq7000_hal::ddr::DdriobConfigSet;
|
||||
pub const DDRIOB_CONFIG_SET_ZEDBOARD: DdriobConfigSet = DdriobConfigSet {
|
||||
ddr_control: zynq7000::slcr::ddriob::DdrControl::new_with_raw_value(0x00000260),
|
||||
addr0: regs::DdriobConfig::new_with_raw_value(0x00000600),
|
||||
addr1: regs::DdriobConfig::new_with_raw_value(0x00000600),
|
||||
data0: regs::DdriobConfig::new_with_raw_value(0x00000672),
|
||||
+259
-153
@@ -2,10 +2,17 @@ use core::cell::RefCell;
|
||||
|
||||
use arbitrary_int::{prelude::*, u24};
|
||||
use zynq7000_hal::qspi::{
|
||||
FIFO_DEPTH, LinearQspiConfig, QspiIoMode, QspiIoTransferGuard, QspiLinearAddressing,
|
||||
FIFO_DEPTH, LinearQspiConfig, MAX_BYTES_PER_TRANSFER_IO_MODE, QspiIoMode, QspiLinearAddressing,
|
||||
QspiLinearReadGuard,
|
||||
};
|
||||
|
||||
/// 4 bytes are reserved for command byte and address. Rounded down at a 16 byte boundary,
|
||||
/// recommended by flash memory datasheet.
|
||||
pub const MAX_DATA_BYTES_PER_WRITE: usize = 240;
|
||||
/// Probably the most performant chunk/program size to program the chip without crossing page
|
||||
/// boundaries without exceeding the FIFO size.
|
||||
pub const RECOMMENDED_PROGRAM_PAGE_SIZE: usize = 128;
|
||||
|
||||
pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination =
|
||||
zynq7000_hal::qspi::QspiDeviceCombination {
|
||||
vendor: zynq7000_hal::qspi::QspiVendor::WinbondAndSpansion,
|
||||
@@ -13,7 +20,8 @@ pub const QSPI_DEV_COMBINATION_REV_F: zynq7000_hal::qspi::QspiDeviceCombination
|
||||
two_devices: false,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum RegisterId {
|
||||
/// WRR
|
||||
WriteRegisters = 0x01,
|
||||
@@ -64,7 +72,8 @@ pub enum SectorArchictecture {
|
||||
Hybrid = 0x01,
|
||||
}
|
||||
|
||||
pub const PAGE_SIZE: usize = 256;
|
||||
pub const PAGE_SIZE: usize = 0x100;
|
||||
pub const SECTOR_SIZE: usize = 0x10000;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BaseDeviceId {
|
||||
@@ -159,8 +168,7 @@ impl ExtendedDeviceId {
|
||||
}
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u8)]
|
||||
#[derive(Debug)]
|
||||
#[bitbybit::bitfield(u8, debug, forbid_overlaps)]
|
||||
pub struct StatusRegister1 {
|
||||
#[bit(7, rw)]
|
||||
status_register_write_disable: bool,
|
||||
@@ -168,25 +176,18 @@ pub struct StatusRegister1 {
|
||||
programming_error: bool,
|
||||
#[bit(5, r)]
|
||||
erase_error: bool,
|
||||
#[bit(4, r)]
|
||||
bp_2: bool,
|
||||
#[bit(3, r)]
|
||||
bp_1: bool,
|
||||
#[bit(2, r)]
|
||||
bp_0: bool,
|
||||
#[bits(2..=4, rw)]
|
||||
block_protection: u3,
|
||||
#[bit(1, r)]
|
||||
write_enable_latch: bool,
|
||||
#[bit(0, r)]
|
||||
write_in_progress: bool,
|
||||
}
|
||||
|
||||
#[bitbybit::bitfield(u8)]
|
||||
#[derive(Debug)]
|
||||
#[bitbybit::bitfield(u8, debug, forbid_overlaps)]
|
||||
pub struct ConfigRegister1 {
|
||||
#[bit(7, rw)]
|
||||
latency_code_1: bool,
|
||||
#[bit(6, rw)]
|
||||
latency_code_0: bool,
|
||||
#[bits(6..=7, rw)]
|
||||
latency_code: u2,
|
||||
/// This is an OTP bit. It can not be set back to 0 once it has been set to 1!
|
||||
#[bit(5, rw)]
|
||||
tbprot: bool,
|
||||
@@ -223,27 +224,60 @@ pub enum ProgramPageError {
|
||||
ProgrammingErrorBitSet,
|
||||
#[error("address error: {0}")]
|
||||
Addr(#[from] AddrError),
|
||||
#[error("data is larger than page size {PAGE_SIZE}")]
|
||||
DataLargerThanPage,
|
||||
#[error("program data is larger than page size {PAGE_SIZE}")]
|
||||
DataTooLarge,
|
||||
#[error("program data is not aligned to 4 bytes")]
|
||||
NotAligned,
|
||||
#[error("program data crosses page boundary")]
|
||||
CrossesPageBoundary,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct Config {
|
||||
pub set_quad_bit_if_necessary: bool,
|
||||
pub latency_config: Option<u2>,
|
||||
pub clear_write_protection: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn sr_or_cr_update_possibly_required(&self) -> bool {
|
||||
self.set_quad_bit_if_necessary
|
||||
|| self.latency_config.is_some()
|
||||
|| self.clear_write_protection
|
||||
}
|
||||
}
|
||||
|
||||
pub struct QspiSpansionS25Fl256SIoMode(RefCell<QspiIoMode>);
|
||||
|
||||
impl QspiSpansionS25Fl256SIoMode {
|
||||
pub fn new(qspi: QspiIoMode, set_quad_bit_if_necessary: bool) -> Self {
|
||||
pub fn new(qspi: QspiIoMode, config: Config) -> Self {
|
||||
let mut spansion_qspi = QspiSpansionS25Fl256SIoMode(RefCell::new(qspi));
|
||||
if set_quad_bit_if_necessary {
|
||||
spansion_qspi.clear_status();
|
||||
let mut write_required = false;
|
||||
if config.sr_or_cr_update_possibly_required() {
|
||||
let mut cr1 = spansion_qspi.read_configuration_register();
|
||||
if cr1.quad() {
|
||||
// Quad bit is already set.
|
||||
return spansion_qspi;
|
||||
if config.set_quad_bit_if_necessary && !cr1.quad() {
|
||||
cr1.set_quad(true);
|
||||
write_required = true;
|
||||
}
|
||||
if let Some(latency_config) = config.latency_config
|
||||
&& cr1.latency_code() != latency_config
|
||||
{
|
||||
cr1.set_latency_code(latency_config);
|
||||
write_required = true;
|
||||
}
|
||||
cr1.set_quad(true);
|
||||
// Preserve the status register by reading it first.
|
||||
let sr1 = spansion_qspi.read_status_register_1();
|
||||
// Safety: Only the QUAD bit was set while all other bits are preserved.
|
||||
unsafe {
|
||||
spansion_qspi.write_status_and_config_register(sr1, cr1);
|
||||
let mut sr1 = spansion_qspi.read_status_register_1();
|
||||
if config.clear_write_protection && sr1.block_protection() != u3::ZERO {
|
||||
sr1.set_status_register_write_disable(false);
|
||||
sr1.set_block_protection(u3::ZERO);
|
||||
write_required = true;
|
||||
}
|
||||
if write_required {
|
||||
// Safety: Only the QUAD bit was set while all other bits are preserved.
|
||||
unsafe {
|
||||
spansion_qspi.write_status_and_config_register(sr1, cr1);
|
||||
}
|
||||
}
|
||||
}
|
||||
spansion_qspi
|
||||
@@ -257,6 +291,16 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
QspiSpansionS25Fl256SLinearMode(qspi)
|
||||
}
|
||||
|
||||
pub fn set_write_protection(&mut self, write_protection: u3) {
|
||||
unsafe {
|
||||
self.modify_status_and_config_register(|mut sr, cr| {
|
||||
sr.set_status_register_write_disable(false);
|
||||
sr.set_block_protection(write_protection);
|
||||
(sr, cr)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_enable(&mut self) {
|
||||
let qspi = self.0.get_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
@@ -301,7 +345,38 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
/// # Safety
|
||||
///
|
||||
/// Misuse of this API does not lead to undefined behavior. However, it writes the
|
||||
/// configuration register, which as OTP bits. Changing these bits from 0 to 1 is an
|
||||
/// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an
|
||||
/// irreversible operation.
|
||||
pub unsafe fn modify_status_and_config_register(
|
||||
&mut self,
|
||||
f: impl FnOnce(StatusRegister1, ConfigRegister1) -> (StatusRegister1, ConfigRegister1),
|
||||
) {
|
||||
self.write_enable();
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
let sr1 = self.read_status_register_1();
|
||||
let cr1 = self.read_configuration_register();
|
||||
let (sr1, cr1) = f(sr1, cr1);
|
||||
transfer.write_word_txd_11(u32::from_ne_bytes([
|
||||
RegisterId::WriteRegisters as u8,
|
||||
sr1.raw_value(),
|
||||
cr1.raw_value(),
|
||||
0x00,
|
||||
]));
|
||||
transfer.start();
|
||||
while !transfer.read_status().rx_above_threshold() {}
|
||||
transfer.read_rx_data();
|
||||
}
|
||||
|
||||
/// Write a new value for the status register. It is strongly recommended to read both
|
||||
/// the status and config register first and preserve all unchanged bits.
|
||||
///
|
||||
/// This API must be used if the QUAD bit (CR1\[1\]) is set.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Misuse of this API does not lead to undefined behavior. However, it writes the
|
||||
/// configuration register, which is OTP bits. Changing these bits from 0 to 1 is an
|
||||
/// irreversible operation.
|
||||
pub unsafe fn write_status_and_config_register(
|
||||
&mut self,
|
||||
@@ -388,10 +463,10 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
|
||||
/// This function will block until the operation has completed.
|
||||
pub fn erase_sector(&mut self, addr: u32) -> Result<(), EraseError> {
|
||||
if addr + 0x10000 > u24::MAX.as_u32() {
|
||||
if addr + SECTOR_SIZE as u32 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if !addr.is_multiple_of(0x10000) {
|
||||
if !addr.is_multiple_of(SECTOR_SIZE as u32) {
|
||||
return Err(AddrError::Alignment.into());
|
||||
}
|
||||
self.write_enable();
|
||||
@@ -429,19 +504,35 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// This function also takes care of enabling writes before programming the page.
|
||||
/// This function will block until the operation has completed.
|
||||
///
|
||||
/// The data length max not exceed the page size [PAGE_SIZE].
|
||||
pub fn program_page(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
|
||||
pub fn write_pages(&mut self, mut addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
|
||||
if addr + data.len() as u32 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if !addr.is_multiple_of(0x100) {
|
||||
return Err(AddrError::Alignment.into());
|
||||
for chunk in data.chunks(RECOMMENDED_PROGRAM_PAGE_SIZE) {
|
||||
self.program(addr, chunk)?;
|
||||
addr += chunk.len() as u32;
|
||||
}
|
||||
if data.len() > PAGE_SIZE {
|
||||
return Err(ProgramPageError::DataLargerThanPage);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function also takes care of enabling writes before programming the page.
|
||||
/// This function will block until the operation has completed.
|
||||
///
|
||||
/// The data length may not exceed [MAX_DATA_BYTES_PER_WRITE]. Furthermore, the data needs
|
||||
/// to be aligned to 4 bytes and the programming operation is not allowed to cross a page.
|
||||
/// boundary. It is recommended to program in 128 byte chunks.
|
||||
pub fn program(&mut self, addr: u32, data: &[u8]) -> Result<(), ProgramPageError> {
|
||||
if addr + data.len() as u32 > u24::MAX.as_u32() {
|
||||
return Err(AddrError::OutOfRange.into());
|
||||
}
|
||||
if data.len() > MAX_DATA_BYTES_PER_WRITE {
|
||||
return Err(ProgramPageError::DataTooLarge);
|
||||
}
|
||||
if !data.len().is_multiple_of(4) {
|
||||
return Err(ProgramPageError::NotAligned);
|
||||
}
|
||||
if (addr as usize % PAGE_SIZE) + data.len() > PAGE_SIZE {
|
||||
return Err(ProgramPageError::CrossesPageBoundary);
|
||||
}
|
||||
self.write_enable();
|
||||
let qspi = self.0.get_mut();
|
||||
@@ -455,7 +546,8 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
let mut read_index: u32 = 0;
|
||||
let mut current_byte_index = 0;
|
||||
let fifo_writes = data.len().div_ceil(4);
|
||||
// Full four byte writes.
|
||||
let fifo_writes = data.len() / 4;
|
||||
// Fill the FIFO until it is full.
|
||||
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(
|
||||
@@ -465,52 +557,14 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
));
|
||||
current_byte_index += 4;
|
||||
}
|
||||
|
||||
transfer.start();
|
||||
|
||||
let mut wait_for_tx_slot = |transfer: &mut QspiIoTransferGuard| loop {
|
||||
let status = transfer.read_status();
|
||||
if status.rx_above_threshold() {
|
||||
transfer.read_rx_data();
|
||||
read_index = read_index.wrapping_add(4);
|
||||
}
|
||||
if !status.tx_full() {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
while current_byte_index < data.len() {
|
||||
// Immediately fill the FIFO again with the remaining 8 bytes.
|
||||
wait_for_tx_slot(&mut transfer);
|
||||
|
||||
let word = match core::cmp::min(4, data.len() - current_byte_index) {
|
||||
1 => {
|
||||
let mut bytes = [0; 4];
|
||||
bytes[0] = data[current_byte_index];
|
||||
u32::from_ne_bytes(bytes)
|
||||
}
|
||||
2 => {
|
||||
let mut bytes = [0; 4];
|
||||
bytes[0..2].copy_from_slice(&data[current_byte_index..current_byte_index + 2]);
|
||||
u32::from_ne_bytes(bytes)
|
||||
}
|
||||
3 => {
|
||||
let mut bytes = [0; 4];
|
||||
bytes[0..3].copy_from_slice(&data[current_byte_index..current_byte_index + 3]);
|
||||
u32::from_ne_bytes(bytes)
|
||||
}
|
||||
4 => u32::from_ne_bytes(
|
||||
data[current_byte_index..current_byte_index + 4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
transfer.write_word_txd_00(word);
|
||||
current_byte_index += 4;
|
||||
}
|
||||
|
||||
// Wait until the transfer is done by waiting until all RX bytes have been received.
|
||||
while read_index < data.len() as u32 {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
// Double read to avoid RX underflows as specified in TRM.
|
||||
let status_read = transfer.read_status();
|
||||
if status_read.rx_above_threshold() && transfer.read_status().rx_above_threshold() {
|
||||
transfer.read_rx_data();
|
||||
read_index = read_index.wrapping_add(4);
|
||||
}
|
||||
@@ -533,84 +587,134 @@ impl QspiSpansionS25Fl256SIoMode {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_page_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
|
||||
fn generic_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool, fast_read: bool) {
|
||||
let mut offset = 0;
|
||||
let reg_id = if fast_read {
|
||||
RegisterId::FastRead
|
||||
} else {
|
||||
RegisterId::Read
|
||||
};
|
||||
let mut qspi = self.0.borrow_mut();
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
let raw_word: [u8; 4] = [
|
||||
RegisterId::FastRead as u8,
|
||||
((addr >> 16) & 0xff) as u8,
|
||||
((addr >> 8) & 0xff) as u8,
|
||||
(addr & 0xff) as u8,
|
||||
];
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
let mut read_index = 0;
|
||||
let mut written_words = 0;
|
||||
let mut bytes_to_write = buf.len();
|
||||
let mut max_chunk_size = MAX_BYTES_PER_TRANSFER_IO_MODE - 4;
|
||||
if dummy_byte {
|
||||
bytes_to_write += 1;
|
||||
}
|
||||
let fifo_writes = bytes_to_write.div_ceil(4);
|
||||
// Fill the FIFO until it is full or all 0 bytes have been written.
|
||||
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 1;
|
||||
max_chunk_size -= 1;
|
||||
}
|
||||
|
||||
transfer.start();
|
||||
let mut reply_word_index = 0;
|
||||
while offset < buf.len() {
|
||||
// Calculate the size of the current chunk (max 248 bytes)
|
||||
let chunk_size = core::cmp::min(max_chunk_size, buf.len() - offset);
|
||||
let current_addr = addr + offset as u32;
|
||||
|
||||
while read_index < buf.len() {
|
||||
if transfer.read_status().rx_above_threshold() {
|
||||
let reply = transfer.read_rx_data();
|
||||
if reply_word_index == 0 {
|
||||
reply_word_index += 1;
|
||||
continue;
|
||||
// Create a mutable slice for the current chunk
|
||||
let chunk_slice = &mut buf[offset..offset + chunk_size];
|
||||
|
||||
// This ensures the hardware transaction (Chip Select, etc.) restarts for each chunk.
|
||||
{
|
||||
let mut transfer = qspi.transfer_guard();
|
||||
|
||||
let raw_word: [u8; 4] = [
|
||||
reg_id as u8,
|
||||
((current_addr >> 16) & 0xff) as u8,
|
||||
((current_addr >> 8) & 0xff) as u8,
|
||||
(current_addr & 0xff) as u8,
|
||||
];
|
||||
transfer.write_word_txd_00(u32::from_ne_bytes(raw_word));
|
||||
|
||||
let mut read_index = 0;
|
||||
let mut written_words = 0;
|
||||
// Use chunk_size instead of the full buffer length
|
||||
let mut bytes_to_write = chunk_size;
|
||||
|
||||
if dummy_byte {
|
||||
bytes_to_write += 1;
|
||||
}
|
||||
let reply_as_bytes = reply.to_ne_bytes();
|
||||
let reply_size = core::cmp::min(buf.len() - read_index, 4);
|
||||
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
|
||||
(1, false) => {
|
||||
buf[read_index] = reply_as_bytes[0];
|
||||
1
|
||||
let fifo_writes = bytes_to_write.div_ceil(4);
|
||||
|
||||
// Fill the FIFO until it is full or all 0 bytes have been written.
|
||||
for _ in 0..core::cmp::min(fifo_writes, FIFO_DEPTH - 1) {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 1;
|
||||
}
|
||||
|
||||
transfer.start();
|
||||
let mut reply_word_index = 0;
|
||||
|
||||
// Loop based on the current chunk's size
|
||||
while read_index < chunk_size {
|
||||
let rx_is_above_threshold = transfer.read_status().rx_above_threshold();
|
||||
|
||||
// See p.374 of the TRM: Do a double read to ensure this is correct information.
|
||||
if rx_is_above_threshold && transfer.read_status().rx_above_threshold() {
|
||||
let reply = transfer.read_rx_data();
|
||||
if reply_word_index == 0 {
|
||||
reply_word_index += 1;
|
||||
continue;
|
||||
}
|
||||
let reply_as_bytes = reply.to_ne_bytes();
|
||||
// Calculate remaining bytes in this specific chunk
|
||||
let reply_size = core::cmp::min(chunk_size - read_index, 4);
|
||||
read_index += match (reply_size, reply_word_index == 1 && dummy_byte) {
|
||||
(1, false) => {
|
||||
chunk_slice[read_index] = reply_as_bytes[0];
|
||||
1
|
||||
}
|
||||
(1, true) => {
|
||||
chunk_slice[read_index] = reply_as_bytes[1];
|
||||
1
|
||||
}
|
||||
(2, false) => {
|
||||
chunk_slice[read_index..read_index + 2]
|
||||
.copy_from_slice(&reply_as_bytes[0..2]);
|
||||
2
|
||||
}
|
||||
(2, true) => {
|
||||
chunk_slice[read_index..read_index + 2]
|
||||
.copy_from_slice(&reply_as_bytes[1..3]);
|
||||
2
|
||||
}
|
||||
(3, false) => {
|
||||
chunk_slice[read_index..read_index + 3]
|
||||
.copy_from_slice(&reply_as_bytes[0..3]);
|
||||
3
|
||||
}
|
||||
(3, true) => {
|
||||
chunk_slice[read_index..read_index + 3]
|
||||
.copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
(4, false) => {
|
||||
chunk_slice[read_index..read_index + 4]
|
||||
.copy_from_slice(&reply_as_bytes[0..4]);
|
||||
4
|
||||
}
|
||||
(4, true) => {
|
||||
chunk_slice[read_index..read_index + 3]
|
||||
.copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
reply_word_index += 1;
|
||||
}
|
||||
(1, true) => {
|
||||
buf[read_index] = reply_as_bytes[1];
|
||||
1
|
||||
if written_words < fifo_writes && !transfer.read_status().tx_full() {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 1;
|
||||
}
|
||||
(2, false) => {
|
||||
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[0..2]);
|
||||
2
|
||||
}
|
||||
(2, true) => {
|
||||
buf[read_index..read_index + 2].copy_from_slice(&reply_as_bytes[1..3]);
|
||||
2
|
||||
}
|
||||
(3, false) => {
|
||||
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[0..3]);
|
||||
3
|
||||
}
|
||||
(3, true) => {
|
||||
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
(4, false) => {
|
||||
buf[read_index..read_index + 4].copy_from_slice(&reply_as_bytes[0..4]);
|
||||
4
|
||||
}
|
||||
(4, true) => {
|
||||
buf[read_index..read_index + 3].copy_from_slice(&reply_as_bytes[1..4]);
|
||||
3
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
reply_word_index += 1;
|
||||
}
|
||||
if written_words < fifo_writes && !transfer.read_status().tx_full() {
|
||||
transfer.write_word_txd_00(0);
|
||||
written_words += 1;
|
||||
}
|
||||
}
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_fast_read(&self, addr: u32, buf: &mut [u8], dummy_byte: bool) {
|
||||
self.generic_read(addr, buf, dummy_byte, true)
|
||||
}
|
||||
|
||||
/// Only works if the clock speed is slower than 50 MHz according to datasheet.
|
||||
pub fn read_page_read(&self, addr: u32, buf: &mut [u8]) {
|
||||
self.generic_read(addr, buf, false, false)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the Spansion QSPI is used in linear addressed mode, no IO operations are allowed.
|
||||
@@ -618,10 +722,12 @@ pub struct QspiSpansionS25Fl256SLinearMode(QspiLinearAddressing);
|
||||
|
||||
impl QspiSpansionS25Fl256SLinearMode {
|
||||
pub const BASE_ADDR: usize = QspiLinearAddressing::BASE_ADDRESS;
|
||||
pub const PAGE_SIZE: usize = PAGE_SIZE;
|
||||
pub const SECTOR_SIZE: usize = SECTOR_SIZE;
|
||||
|
||||
pub fn into_io_mode(self, dual_flash: bool) -> QspiSpansionS25Fl256SIoMode {
|
||||
let qspi = self.0.into_io_mode(dual_flash);
|
||||
QspiSpansionS25Fl256SIoMode::new(qspi, false)
|
||||
QspiSpansionS25Fl256SIoMode::new(qspi, Config::default())
|
||||
}
|
||||
|
||||
pub fn read_guard(&mut self) -> QspiLinearReadGuard<'_> {
|
||||
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "zedboard-fsbl"
|
||||
version = "0.1.0"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Rust First Stage Bootloader for the Zedboard"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
aarch32-cpu = { version = "0.3", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../zynq7000-rt" }
|
||||
zynq7000 = { path = "../zynq7000" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" }
|
||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
fugit = "0.4"
|
||||
log = "0.4"
|
||||
arbitrary-int = "2"
|
||||
|
||||
# cargo build/run
|
||||
[profile.dev]
|
||||
# default is opt-level = '0', but that makes very
|
||||
# verbose machine code
|
||||
opt-level = 's'
|
||||
# trade compile speed for slightly better optimisations
|
||||
codegen-units = 1
|
||||
|
||||
# cargo build/run --release
|
||||
[profile.release]
|
||||
# Optimize for size.
|
||||
opt-level = 's'
|
||||
# trade compile speed for slightly better optimisations
|
||||
codegen-units = 1
|
||||
# Use Link Time Optimisations to further inline things across
|
||||
# crates
|
||||
lto = 'fat'
|
||||
# Leave the debug symbols in (default is no debug info)
|
||||
debug = 2
|
||||
@@ -1,7 +1,7 @@
|
||||
MEMORY
|
||||
{
|
||||
/* The Zynq7000 has 192 kB of OCM memory which can be used for the FSBL */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 192K
|
||||
/* The Zynq7000 has 256 kB of OCM memory of which 196 kB can be used for the FSBL */
|
||||
CODE(rx) : ORIGIN = 0x00000000, LENGTH = 196K
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
/* Leave 1 MB of memory which will be configured as uncached device memory by the MMU. This can
|
||||
be used for something like DMA descriptors, but the DDR needs to be set up first in addition
|
||||
@@ -9,7 +9,10 @@ MEMORY
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE);
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
/* Use the upper OCM as the stack */
|
||||
REGION_ALIAS("STACKS", OCM_UPPER);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
@@ -22,3 +25,9 @@ SECTIONS
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
|
||||
PROVIDE(_und_stack_size = 2K);
|
||||
PROVIDE(_svc_stack_size = 2K);
|
||||
PROVIDE(_abt_stack_size = 2K);
|
||||
PROVIDE(_hyp_stack_size = 2K);
|
||||
PROVIDE(_sys_stack_size = 32K);
|
||||
@@ -8,14 +8,15 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use arbitrary_int::u6;
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::traits::Integer as _;
|
||||
use arbitrary_int::{u2, u6};
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_io::Write as _;
|
||||
use log::{error, info};
|
||||
use zedboard_bsp::qspi_spansion::{self, QspiSpansionS25Fl256SLinearMode};
|
||||
use zynq7000_boot_image::DestinationDevice;
|
||||
use zynq7000_hal::priv_tim;
|
||||
use zynq7000_hal::clocks::ArmClocks;
|
||||
use zynq7000_hal::{
|
||||
BootMode,
|
||||
clocks::{
|
||||
@@ -23,13 +24,13 @@ use zynq7000_hal::{
|
||||
pll::{PllConfig, configure_arm_pll, configure_io_pll},
|
||||
},
|
||||
ddr::{DdrClockSetupConfig, configure_ddr_for_ddr3, memtest},
|
||||
devcfg, gic, gpio, l2_cache,
|
||||
gic, gpio, l2_cache,
|
||||
prelude::*,
|
||||
qspi::{self, QSPI_START_ADDRESS},
|
||||
time::Hertz,
|
||||
uart::{ClockConfig, Config, Uart},
|
||||
};
|
||||
use zynq7000_rt as _;
|
||||
use zynq7000_hal::{generic_interrupt_handler, priv_tim};
|
||||
|
||||
// PS clock input frequency.
|
||||
const PS_CLK: Hertz = Hertz::from_raw(33_333_333);
|
||||
@@ -43,7 +44,7 @@ const IO_CLK: Hertz = Hertz::from_raw(1_000_000_000);
|
||||
const DDR_FREQUENCY: Hertz = Hertz::from_raw(533_333_333);
|
||||
|
||||
/// 1067 MHz.
|
||||
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.raw());
|
||||
const DDR_CLK: Hertz = Hertz::from_raw(2 * DDR_FREQUENCY.to_raw());
|
||||
|
||||
const PERFORM_DDR_MEMTEST: bool = false;
|
||||
|
||||
@@ -60,17 +61,8 @@ pub const ELF_BASE_ADDR: usize = 0x100000;
|
||||
/// 8 MB reserved for application ELF.
|
||||
pub const BOOT_BIN_STAGING_OFFSET: usize = 8 * 1024 * 1024;
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
pub fn main() -> ! {
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
// The unwraps are okay here, the provided clock frequencies are standard values also used
|
||||
// by other Xilinx tools.
|
||||
@@ -84,6 +76,20 @@ pub fn main() -> ! {
|
||||
);
|
||||
|
||||
let mut periphs = zynq7000::Peripherals::take().unwrap();
|
||||
l2_cache::disable();
|
||||
|
||||
// Initialize the ARM clock. Safety: We only run this once.
|
||||
unsafe {
|
||||
ArmClocks::new_with_cpu_clock_init(
|
||||
ARM_CLK,
|
||||
zynq7000_hal::clocks::CpuClockRatio::SixToTwoToOne,
|
||||
u6::new(2),
|
||||
);
|
||||
// This is done by the AMD FSBL.
|
||||
zynq7000_hal::Slcr::with(|val| {
|
||||
val.gpiob().modify_ctrl(|val| val.with_vref_en(true));
|
||||
});
|
||||
}
|
||||
|
||||
// Clock was already initialized by PS7 Init TCL script or FSBL, we just read it.
|
||||
let clocks = Clocks::new_from_regs(PS_CLK).unwrap();
|
||||
@@ -94,7 +100,7 @@ pub fn main() -> ! {
|
||||
let uart_clk_config = ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut logger_uart = Uart::new_with_mio(
|
||||
let mut logger_uart = Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
Config::new_with_clk_config(uart_clk_config),
|
||||
(mio_pins.mio48, mio_pins.mio49),
|
||||
@@ -103,17 +109,10 @@ pub fn main() -> ! {
|
||||
logger_uart
|
||||
.write_all(b"-- Zedboard Rust FSBL --\n\r")
|
||||
.unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
logger_uart,
|
||||
log::LevelFilter::Trace,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(logger_uart, log::LevelFilter::Trace, true);
|
||||
|
||||
// Set up the global interrupt controller.
|
||||
let mut gic = gic::GicConfigurator::new_with_init(periphs.gicc, periphs.gicd);
|
||||
let mut gic = gic::Configurator::new_with_init(periphs.gicc, periphs.gicd);
|
||||
gic.enable_all_interrupts();
|
||||
gic.set_all_spi_interrupt_targets_cpu0();
|
||||
gic.enable();
|
||||
@@ -172,13 +171,21 @@ pub fn main() -> ! {
|
||||
);
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
let spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
|
||||
qspi_io_mode,
|
||||
qspi_spansion::Config {
|
||||
set_quad_bit_if_necessary: true,
|
||||
latency_config: Some(u2::ZERO),
|
||||
clear_write_protection: true,
|
||||
},
|
||||
);
|
||||
let spansion_lqspi =
|
||||
spansion_qspi.into_linear_addressed(qspi_spansion::QSPI_DEV_COMBINATION_REV_F.into());
|
||||
qspi_boot(spansion_lqspi, priv_tim);
|
||||
}
|
||||
|
||||
loop {
|
||||
cortex_ar::asm::nop();
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +273,7 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
};
|
||||
// The DMA will read from the linear mapped QSPI directly, so it
|
||||
// has to be configured for reads using the guard!
|
||||
devcfg::configure_bitstream_non_secure(true, boot_bin_slice)
|
||||
zynq7000_hal::pl::configure_bitstream_non_secure(true, boot_bin_slice)
|
||||
.expect("unexpected unaligned address");
|
||||
log::info!("loaded bitstream successfully");
|
||||
}
|
||||
@@ -314,6 +321,10 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
}
|
||||
}
|
||||
|
||||
// The PL is in reset state after power-up. This method needs to be called in the first-stage
|
||||
// bootloader to put it out of reset.
|
||||
zynq7000_hal::pl::deassert_reset();
|
||||
|
||||
match opt_jump_addr {
|
||||
Some(jump_addr) => {
|
||||
log::info!("jumping to address {}", jump_addr);
|
||||
@@ -321,10 +332,11 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
|
||||
// Some clean up and preparation for jumping to the user application.
|
||||
zynq7000_hal::cache::clean_and_invalidate_data_cache();
|
||||
cortex_ar::register::TlbIAll::write();
|
||||
cortex_ar::register::BpIAll::write();
|
||||
cortex_ar::asm::dsb();
|
||||
cortex_ar::asm::isb();
|
||||
aarch32_cpu::register::TlbIAll::write();
|
||||
aarch32_cpu::register::BpIAll::write();
|
||||
l2_cache::disable();
|
||||
aarch32_cpu::asm::dsb();
|
||||
aarch32_cpu::asm::isb();
|
||||
|
||||
let jump_func: extern "C" fn() -> ! = unsafe { core::mem::transmute(jump_addr) };
|
||||
jump_func();
|
||||
@@ -333,6 +345,15 @@ fn qspi_boot(mut qspi: QspiSpansionS25Fl256SLinearMode, _priv_tim: priv_tim::Cpu
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::irq]
|
||||
pub fn irq_handler() {
|
||||
// Safety: Called here once.
|
||||
let result = unsafe { generic_interrupt_handler() };
|
||||
if let Err(e) = result {
|
||||
log::warn!("Generic interrupt handler failed handling {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
#[zynq7000_rt::exception(DataAbort)]
|
||||
fn data_abort_handler(_faulting_addr: usize) -> ! {
|
||||
loop {
|
||||
@@ -0,0 +1 @@
|
||||
/boot.bin
|
||||
@@ -4,13 +4,14 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
cortex-ar = { version = "0.3" }
|
||||
aarch32-cpu = { version = "0.2", features = ["critical-section-single-core"] }
|
||||
zynq7000-rt = { path = "../zynq7000-rt" }
|
||||
zynq7000 = { path = "../zynq7000" }
|
||||
zynq7000-hal = { path = "../zynq7000-hal" }
|
||||
zynq7000-boot-image = { path = "../../zynq7000-boot-image" }
|
||||
zynq7000-boot-image = { path = "../../host/zynq7000-boot-image" }
|
||||
zedboard-bsp = { path = "../zedboard-bsp" }
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
arbitrary-int = "2"
|
||||
log = "0.4"
|
||||
libm = "0.2"
|
||||
@@ -0,0 +1,19 @@
|
||||
Zedboard QSPI flasher
|
||||
============
|
||||
|
||||
This application flashes a boot binary generated by the AMD `bootgen` utility from DDR
|
||||
to the Zedboard QSPI. This project contains a `qspi-flasher.tcl` script which can be invoked
|
||||
with `xsct` to flash a `boot.bin` and the QSPI flasher to DDR and then run the application.
|
||||
|
||||
The main `justfile` provides a convenience runner:
|
||||
|
||||
```sh
|
||||
just flash-nor-zedboard <path to my boot.bin>
|
||||
```
|
||||
|
||||
Please note that `xsct` must be callable for this to be usable which is part of a Xilinx Vitis installation.
|
||||
|
||||
If the hardware server is running on a remote target the IP address can be specified by setting the environment variable ip_address_hw_server.
|
||||
````sh
|
||||
$ export ip_address_hw_server=<ip-address>
|
||||
````
|
||||
@@ -0,0 +1,31 @@
|
||||
//! This build script copies the `memory.x` file from the crate root into
|
||||
//! a directory where the linker can always find it at build time.
|
||||
//! For many projects this is optional, as the linker always searches the
|
||||
//! project root directory -- wherever `Cargo.toml` is. However, if you
|
||||
//! are using a workspace or have a more complicated build setup, this
|
||||
//! build script becomes required. Additionally, by requesting that
|
||||
//! Cargo re-run the build script whenever `memory.x` is changed,
|
||||
//! updating `memory.x` ensures a rebuild of the application with the
|
||||
//! new memory settings.
|
||||
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
// Put `memory.x` in our output directory and ensure it's
|
||||
// on the linker search path.
|
||||
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
|
||||
File::create(out.join("memory.x"))
|
||||
.unwrap()
|
||||
.write_all(include_bytes!("memory.x"))
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
|
||||
// By default, Cargo will re-run a build script whenever
|
||||
// any file in the project changes. By specifying `memory.x`
|
||||
// here, we ensure the build script is only re-run when
|
||||
// `memory.x` is changed.
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
MEMORY
|
||||
{
|
||||
/* Zedboard: 512 MB DDR3. Only use 62 MB for now, should be plenty for a bare-metal app.
|
||||
1 MB stack memory and 1 MB of memory which will be configured as uncached device memory by the
|
||||
MMU. This is recommended for something like DMA descriptors. */
|
||||
CODE(rx) : ORIGIN = 0x00100000, LENGTH = 62M
|
||||
OCM_UPPER(rx): ORIGIN = 0xFFFF0000, LENGTH = 64K
|
||||
STACKS : ORIGIN = 0x3F00000, LENGTH = 1M
|
||||
UNCACHED(rx): ORIGIN = 0x4000000, LENGTH = 1M
|
||||
}
|
||||
|
||||
REGION_ALIAS("VECTORS", CODE)
|
||||
REGION_ALIAS("DATA", CODE);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Uncached memory */
|
||||
.uncached (NOLOAD) : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_sbss_uncached = .;
|
||||
*(.uncached .uncached.*);
|
||||
. = ALIGN(4);
|
||||
_ebss_uncached = .;
|
||||
} > UNCACHED
|
||||
}
|
||||
+21
-30
@@ -3,8 +3,9 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use aarch32_cpu::asm::nop;
|
||||
use arbitrary_int::{traits::Integer as _, u2};
|
||||
use core::panic::PanicInfo;
|
||||
use cortex_ar::asm::nop;
|
||||
use embedded_hal::{delay::DelayNs as _, digital::StatefulOutputPin as _};
|
||||
use embedded_io::Write as _;
|
||||
use log::{error, info};
|
||||
@@ -36,19 +37,10 @@ const QSPI_DEV_COMBINATION: qspi::QspiDeviceCombination = qspi::QspiDeviceCombin
|
||||
two_devices: false,
|
||||
};
|
||||
|
||||
/// Entry point (not called like a normal main function)
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn boot_core(cpu_id: u32) -> ! {
|
||||
if cpu_id != 0 {
|
||||
panic!("unexpected CPU ID {}", cpu_id);
|
||||
}
|
||||
main();
|
||||
}
|
||||
|
||||
const INIT_STRING: &str = "-- Zynq 7000 Zedboard QSPI flasher --\n\r";
|
||||
|
||||
#[unsafe(export_name = "main")]
|
||||
pub fn main() -> ! {
|
||||
#[zynq7000_rt::entry]
|
||||
fn main() -> ! {
|
||||
let periphs = zynq7000_hal::init(zynq7000_hal::Config {
|
||||
init_l2_cache: true,
|
||||
level_shifter_config: Some(LevelShifterConfig::EnableAll),
|
||||
@@ -66,21 +58,14 @@ pub fn main() -> ! {
|
||||
let uart_clk_config = uart::ClockConfig::new_autocalc_with_error(clocks.io_clocks(), 115200)
|
||||
.unwrap()
|
||||
.0;
|
||||
let mut uart = uart::Uart::new_with_mio(
|
||||
let mut uart = uart::Uart::new_with_mio_for_uart_1(
|
||||
periphs.uart_1,
|
||||
uart::Config::new_with_clk_config(uart_clk_config),
|
||||
(gpio_pins.mio.mio48, gpio_pins.mio.mio49),
|
||||
)
|
||||
.unwrap();
|
||||
uart.write_all(INIT_STRING.as_bytes()).unwrap();
|
||||
// Safety: We are not multi-threaded yet.
|
||||
unsafe {
|
||||
zynq7000_hal::log::uart_blocking::init_unsafe_single_core(
|
||||
uart,
|
||||
log::LevelFilter::Info,
|
||||
false,
|
||||
)
|
||||
};
|
||||
zynq7000_hal::log::uart_blocking::init_with_busy_flag(uart, log::LevelFilter::Info, true);
|
||||
|
||||
let boot_mode = BootMode::new_from_regs();
|
||||
info!("Boot mode: {:?}", boot_mode);
|
||||
@@ -106,7 +91,14 @@ pub fn main() -> ! {
|
||||
|
||||
let qspi_io_mode = qspi.into_io_mode(false);
|
||||
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(qspi_io_mode, true);
|
||||
let mut spansion_qspi = qspi_spansion::QspiSpansionS25Fl256SIoMode::new(
|
||||
qspi_io_mode,
|
||||
qspi_spansion::Config {
|
||||
set_quad_bit_if_necessary: true,
|
||||
latency_config: Some(u2::ZERO),
|
||||
clear_write_protection: true,
|
||||
},
|
||||
);
|
||||
|
||||
let mut boot_bin_slice = unsafe {
|
||||
core::slice::from_raw_parts(BOOT_BIN_BASE_ADDR as *const _, BootHeader::FIXED_SIZED_PART)
|
||||
@@ -130,7 +122,7 @@ pub fn main() -> ! {
|
||||
);
|
||||
|
||||
let mut current_addr = 0;
|
||||
let mut read_buf = [0u8; 256];
|
||||
let mut read_buf = [0u8; qspi_spansion::PAGE_SIZE];
|
||||
let mut next_checkpoint = 0.05;
|
||||
while current_addr < boot_bin_size {
|
||||
if current_addr % 0x10000 == 0 {
|
||||
@@ -146,10 +138,13 @@ pub fn main() -> ! {
|
||||
}
|
||||
}
|
||||
}
|
||||
let write_size = core::cmp::min(256, boot_bin_size - current_addr);
|
||||
let write_size = core::cmp::min(
|
||||
qspi_spansion::RECOMMENDED_PROGRAM_PAGE_SIZE,
|
||||
boot_bin_size - current_addr,
|
||||
);
|
||||
let write_slice = &boot_bin_slice[current_addr..current_addr + write_size];
|
||||
log::debug!("Programming address {:#x}", current_addr);
|
||||
match spansion_qspi.program_page(current_addr as u32, write_slice) {
|
||||
match spansion_qspi.program(current_addr as u32, write_slice) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::error!(
|
||||
@@ -161,11 +156,7 @@ pub fn main() -> ! {
|
||||
}
|
||||
}
|
||||
if VERIFY_PROGRAMMING {
|
||||
spansion_qspi.read_page_fast_read(
|
||||
current_addr as u32,
|
||||
&mut read_buf[0..write_size],
|
||||
true,
|
||||
);
|
||||
spansion_qspi.read_fast_read(current_addr as u32, &mut read_buf[0..write_size], true);
|
||||
if &read_buf[0..write_size] != write_slice {
|
||||
error!(
|
||||
"data verification failed at address {:#x}: wrote {:x?}, read {:x?}",
|
||||
@@ -0,0 +1,21 @@
|
||||
Change Log
|
||||
=======
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
# [v0.1.1] 2026-03-13
|
||||
|
||||
- Try to fix docs build for docs.rs
|
||||
|
||||
# [v0.1.0] 2026-02-14
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-embassy-v0.1.0...HEAD
|
||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-embassy-v0.1.0...zynq7000-embassy-v0.1.1
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-embassy-v0.1.0
|
||||
@@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "zynq7000-embassy"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Embassy-rs support for the Zynq7000 family of SoCs"
|
||||
description = "Embassy time support for the Zynq7000 family of SoCs"
|
||||
homepage = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
repository = "https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -17,3 +17,7 @@ zynq7000-hal = { path = "../zynq7000-hal", version = "0.1" }
|
||||
|
||||
embassy-time-driver = "0.2"
|
||||
embassy-time-queue-utils = "0.3"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
@@ -2,11 +2,10 @@
|
||||
[](https://docs.rs/zynq7000-embassy)
|
||||
[](https://github.com/us-irs/zynq7000-rs/actions/workflows/ci.yml)
|
||||
|
||||
# Embassy-rs support for the AMD Zynq7000 SoC family
|
||||
# Embassy time support for the AMD Zynq7000 SoC family
|
||||
|
||||
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the
|
||||
AMD Zynq7000 SoC family. Currently, it contains the time driver to allow using embassy-rs. It
|
||||
currently provides one driver using the global timer peripheral provided by the Zynq7000 PS for
|
||||
this purpose.
|
||||
This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) time support for
|
||||
the AMD Zynq7000 SoC family. It currently provides one driver using the global timer peripheral
|
||||
provided by the Zynq7000 PS for this purpose.
|
||||
|
||||
The documentation contains more information on how to use this crate.
|
||||
@@ -1,3 +1,10 @@
|
||||
//! # Embassy time support for the AMD Zynq7000 SoC family
|
||||
//!
|
||||
//! This project contains the [embassy-rs](https://github.com/embassy-rs/embassy) time support for
|
||||
//! the AMD Zynq7000 SoC family. It currently provides one driver using the global timer peripheral
|
||||
//! provided by the Zynq7000 PS for this purpose.
|
||||
//!
|
||||
//! The [crate::init] method must be called once for the time driver to work properly.
|
||||
#![no_std]
|
||||
use core::cell::{Cell, RefCell};
|
||||
|
||||
@@ -59,9 +66,19 @@ impl GtcTimerDriver {
|
||||
///
|
||||
/// This has to be called ONCE at system initialization.
|
||||
pub unsafe fn init(&'static self, arm_clock: &ArmClocks, mut gtc: GlobalTimerCounter) {
|
||||
fn safe_interrupt_handler() {
|
||||
// Safety: See safety notes of [zynq7000_hal::generic_interrupt_handler].
|
||||
unsafe {
|
||||
on_interrupt();
|
||||
}
|
||||
}
|
||||
zynq7000_hal::register_interrupt(
|
||||
zynq7000_hal::gic::Interrupt::Ppi(zynq7000_hal::gic::PpiInterrupt::GlobalTimer),
|
||||
safe_interrupt_handler,
|
||||
);
|
||||
CPU_3X2X_CLK.set(arm_clock.cpu_3x2x_clk()).unwrap();
|
||||
SCALE
|
||||
.set(arm_clock.cpu_3x2x_clk().raw() as u64 / TICK_HZ)
|
||||
.set(arm_clock.cpu_3x2x_clk().to_raw() as u64 / TICK_HZ)
|
||||
.unwrap();
|
||||
gtc.set_cpu_3x2x_clock(arm_clock.cpu_3x2x_clk());
|
||||
gtc.set_prescaler(0);
|
||||
@@ -0,0 +1,56 @@
|
||||
Change Log
|
||||
=======
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# [unreleased]
|
||||
|
||||
## Fixed
|
||||
|
||||
- Bugfix for DDR initialization: `calibrate_iob_impedance_for_ddr3` and `calibrate_iob_impedance`
|
||||
now expect a `zynq7000::slcr::ddriob::DdrControl` input argument. This register write was
|
||||
missing
|
||||
- Several bugfixes and improvements for GIC module. Some of the registers previously were
|
||||
completely overwritten instead of only modifying their own bit portions. Also allow targeting
|
||||
interrupts without clearing other CPU target.
|
||||
- Do not reset the UART on TX future creation anymore, which lead to glitches and invalid data.
|
||||
- Robustness improvements for the asynchronous UART TX module.
|
||||
- SPI1 AMBA clock control bits are now enabled and disabled properly
|
||||
|
||||
## Changed
|
||||
|
||||
- Increased reliabily of PS UART interrupt reception, which was proven to be buggy for higher baud
|
||||
rates: Force user to configure RTO value, encouraging non-zero values, and use a RX FIFO trigger
|
||||
value of FIFO depth divided by 2 by default.
|
||||
- `devcfg` moved to `pl` module
|
||||
- Added division by zero check in gtc frequency_to_ticks to avoid runtime panic
|
||||
- Increased UART type safety by providing dedicated MIO constructors for UART 0 and UART 1
|
||||
respectively.
|
||||
- `log::rb` module replaced by `log::asynch` module which uses an asynchronous embassy pipe
|
||||
for logging.
|
||||
- GIC data structures: Removed the `Gic` prefix which already is part of the module name.
|
||||
- Renamed `GicInterruptHelper` to `InterruptGuard`. It acknowledges the end of interrupts on drop.
|
||||
|
||||
## Added
|
||||
|
||||
- Method to de-assert PL reset.
|
||||
- ARM clock initialization for the `ArmClocks` structure
|
||||
- The `ArmClocks` structure now caches the CPU clock ratio
|
||||
- New generic interrupt registry and generic interrupt handler which uses the registry.
|
||||
Primary interface is the `crate::generic_interrupt_handler` function and the
|
||||
`crate::register_interrupt` function.
|
||||
|
||||
# [v0.1.1] 2025-10-10
|
||||
|
||||
Documentation fixes.
|
||||
|
||||
# [v0.1.0] 2025-10-09
|
||||
|
||||
Initial release
|
||||
|
||||
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-hal-v0.1.0...HEAD
|
||||
[v0.1.1]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/compare/zynq7000-hal-v0.1.0...zynq7000-hal-v0.1.1
|
||||
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/tag/zynq7000-hal-v0.1.0
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "zynq7000-hal"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
|
||||
edition = "2024"
|
||||
description = "Hardware Abstraction Layer (HAL) for the Zynq7000 family of SoCs"
|
||||
@@ -11,16 +11,15 @@ keywords = ["no-std", "hal", "amd", "zynq7000", "bare-metal"]
|
||||
categories = ["embedded", "no-std", "hardware-support"]
|
||||
|
||||
[dependencies]
|
||||
cortex-ar = { version = "0.3" }
|
||||
zynq7000 = { path = "../zynq7000", version = "0.1" }
|
||||
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.1" }
|
||||
|
||||
aarch32-cpu = { version = "0.3" }
|
||||
zynq7000 = { path = "../zynq7000", version = "0.4" }
|
||||
zynq7000-mmu = { path = "../zynq7000-mmu", version = "0.2" }
|
||||
static_assertions = "1.1"
|
||||
bitbybit = "1.4"
|
||||
bitbybit = "2"
|
||||
arbitrary-int = "2"
|
||||
thiserror = { version = "2", default-features = false }
|
||||
num_enum = { version = "0.7", default-features = false }
|
||||
ringbuf = { version = "0.4.8", default-features = false }
|
||||
bitflags = "2"
|
||||
embedded-hal-nb = "1"
|
||||
embedded-io = "0.7"
|
||||
embedded-hal = "1"
|
||||
@@ -28,22 +27,27 @@ embedded-hal-async = "1"
|
||||
heapless = "0.9"
|
||||
static_cell = "2"
|
||||
delegate = "0.13"
|
||||
paste = "1"
|
||||
pastey = "0.2.1"
|
||||
nb = "1"
|
||||
fugit = "0.3"
|
||||
fugit = "0.4"
|
||||
critical-section = "1"
|
||||
libm = "0.2"
|
||||
log = "0.4"
|
||||
embassy-sync = "0.7"
|
||||
embassy-sync = "0.8"
|
||||
embassy-net-driver = "0.2"
|
||||
smoltcp = { version = "0.12", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||
smoltcp = { version = "0.13", default-features = false, features = ["proto-ipv4", "medium-ethernet", "socket-raw"] }
|
||||
vcell = "0.1"
|
||||
raw-slicee = "0.1"
|
||||
embedded-io-async = "0.7"
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
defmt = { version = "1", optional = true }
|
||||
embedded-sdmmc = { git = "https://github.com/robamu/embedded-sdmmc-rs.git", branch = "all-features" }
|
||||
bytemuck = "1.25"
|
||||
|
||||
[features]
|
||||
std = ["thiserror/std", "alloc"]
|
||||
alloc = []
|
||||
defmt = ["dep:defmt", "fugit/defmt", "zynq7000/defmt"]
|
||||
# These devices have a lower pin count.
|
||||
7z010-7z007s-clg225 = []
|
||||
|
||||
@@ -52,4 +56,5 @@ approx = "0.5"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["alloc"]
|
||||
targets = ["armv7a-none-eabihf"]
|
||||
rustdoc-args = ["--generate-link-to-definition"]
|
||||
@@ -5,12 +5,12 @@
|
||||
# HAL for the AMD Zynq 7000 SoC family
|
||||
|
||||
This repository contains the **H**ardware **A**bstraction **L**ayer (HAL), which is an additional
|
||||
hardware abstraction on top of the [peripheral access API](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq/zynq7000).
|
||||
hardware abstraction on top of the [peripheral access API](../zynq7000).
|
||||
|
||||
It is the result of reading the datasheet for the device and encoding a type-safe layer over the
|
||||
raw PAC. This crate also implements traits specified by the
|
||||
[embedded-hal](https://github.com/rust-embedded/embedded-hal) project, making it compatible with
|
||||
various drivers in the embedded rust ecosystem.
|
||||
|
||||
The [top-level README](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs) and the documentation
|
||||
The [top-level README](../../README.md) and the documentation
|
||||
contain more information on how to use this crate.
|
||||
@@ -1,13 +1,17 @@
|
||||
//! # Cache management module
|
||||
//!
|
||||
//! A lot of cache maintenance operations for this SoC have to be performed on both the L1 and the
|
||||
//! L2 cache in the correct order. This module provides commonly required operations.
|
||||
use core::sync::atomic::compiler_fence;
|
||||
|
||||
use cortex_ar::{
|
||||
use aarch32_cpu::{
|
||||
asm::dsb,
|
||||
cache::{
|
||||
clean_and_invalidate_data_cache_line_to_poc, clean_data_cache_line_to_poc,
|
||||
invalidate_data_cache_line_to_poc,
|
||||
},
|
||||
};
|
||||
use zynq7000::l2_cache::{L2Cache, MmioL2Cache};
|
||||
use zynq7000::l2_cache::{MmioRegisters, Registers};
|
||||
|
||||
pub const CACHE_LINE_SIZE: usize = 32;
|
||||
|
||||
@@ -15,7 +19,7 @@ pub const CACHE_LINE_SIZE: usize = 32;
|
||||
#[error("alignment error, addresses and lengths must be aligned to 32 byte cache line length")]
|
||||
pub struct AlignmentError;
|
||||
|
||||
pub fn clean_and_invalidate_l2c_line(l2c: &mut MmioL2Cache<'static>, addr: u32) {
|
||||
pub fn clean_and_invalidate_l2c_line(l2c: &mut MmioRegisters<'static>, addr: u32) {
|
||||
l2c.write_clean_by_pa(addr);
|
||||
l2c.write_invalidate_by_pa(addr);
|
||||
}
|
||||
@@ -24,16 +28,16 @@ pub fn clean_and_invalidate_l2c_line(l2c: &mut MmioL2Cache<'static>, addr: u32)
|
||||
pub fn clean_and_invalidate_data_cache() {
|
||||
dsb();
|
||||
|
||||
cortex_ar::cache::clean_l1_data_cache::<2, 5, 8>();
|
||||
aarch32_cpu::cache::clean_l1_data_cache::<2, 5, 8>();
|
||||
dsb();
|
||||
|
||||
// Clean all ways in L2 cache.
|
||||
let mut l2c = unsafe { L2Cache::new_mmio_fixed() };
|
||||
let mut l2c = unsafe { Registers::new_mmio_fixed() };
|
||||
l2c.write_clean_invalidate_by_way(0xffff);
|
||||
while l2c.read_cache_sync().busy() {}
|
||||
compiler_fence(core::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
cortex_ar::cache::clean_and_invalidate_l1_data_cache::<2, 5, 8>();
|
||||
aarch32_cpu::cache::clean_and_invalidate_l1_data_cache::<2, 5, 8>();
|
||||
dsb();
|
||||
}
|
||||
|
||||
@@ -50,7 +54,7 @@ pub fn invalidate_data_cache_range(addr: u32, len: usize) -> Result<(), Alignmen
|
||||
}
|
||||
let mut current_addr = addr;
|
||||
let end_addr = addr.saturating_add(len as u32);
|
||||
let mut l2c = unsafe { L2Cache::new_mmio_fixed() };
|
||||
let mut l2c = unsafe { Registers::new_mmio_fixed() };
|
||||
|
||||
dsb();
|
||||
// Invalidate outer caches lines first, see chapter 3.3.10 of the L2C technical reference
|
||||
@@ -99,7 +103,7 @@ pub fn clean_and_invalidate_data_cache_range(addr: u32, len: usize) -> Result<()
|
||||
dsb();
|
||||
|
||||
// Clean and invalidates outer cache.
|
||||
let mut l2c = unsafe { L2Cache::new_mmio_fixed() };
|
||||
let mut l2c = unsafe { Registers::new_mmio_fixed() };
|
||||
current_addr = addr;
|
||||
while current_addr < end_addr {
|
||||
// ARM errate 588369 specifies that clean and invalidate need to be separate, but the
|
||||
@@ -151,7 +155,7 @@ pub fn clean_data_cache_range(addr: u32, len: usize) -> Result<(), AlignmentErro
|
||||
dsb();
|
||||
|
||||
// Clean and invalidates outer cache.
|
||||
let mut l2c = unsafe { L2Cache::new_mmio_fixed() };
|
||||
let mut l2c = unsafe { Registers::new_mmio_fixed() };
|
||||
current_addr = addr;
|
||||
while current_addr < end_addr {
|
||||
l2c.write_clean_by_pa(current_addr);
|
||||
@@ -1,21 +1,24 @@
|
||||
//! Clock module.
|
||||
//! # Clock module
|
||||
use arbitrary_int::{prelude::*, u6};
|
||||
|
||||
pub mod pll;
|
||||
|
||||
pub use zynq7000::slcr::clocks::CpuClockRatio;
|
||||
use zynq7000::slcr::{
|
||||
ClockControl,
|
||||
ClockControlRegisters,
|
||||
clocks::{
|
||||
ClockkRatioSelect, DualCommonPeriphIoClockControl, FpgaClockControl, GigEthClockControl,
|
||||
SingleCommonPeriphIoClockControl,
|
||||
ArmClockControl, ClockRatioSelectReg, DualCommonPeriphIoClockControl, FpgaClockControl,
|
||||
GigEthClockControl, SingleCommonPeriphIoClockControl,
|
||||
},
|
||||
};
|
||||
|
||||
use super::time::Hertz;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct ArmClocks {
|
||||
ref_clk: Hertz,
|
||||
ratio: CpuClockRatio,
|
||||
cpu_1x_clk: Hertz,
|
||||
cpu_2x_clk: Hertz,
|
||||
cpu_3x2x_clk: Hertz,
|
||||
@@ -23,29 +26,89 @@ pub struct ArmClocks {
|
||||
}
|
||||
|
||||
impl ArmClocks {
|
||||
/// Configure the ARM clocks based on the ARM PLL input clock.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This changes the CPU clock frequency. You must pass the ARM PLL clock frequency and
|
||||
/// you must ensure that this is only run once during system initialization, for example
|
||||
/// in the first-stage bootloader.
|
||||
pub unsafe fn new_with_cpu_clock_init(
|
||||
arm_pll_clk: Hertz,
|
||||
clock_ratio: CpuClockRatio,
|
||||
divisor: u6,
|
||||
) -> Self {
|
||||
unsafe {
|
||||
crate::slcr::Slcr::with(|slcr| {
|
||||
slcr.clk_ctrl().write_clk_ratio_select(
|
||||
ClockRatioSelectReg::builder().with_sel(clock_ratio).build(),
|
||||
);
|
||||
slcr.clk_ctrl().write_arm_clk_ctrl(
|
||||
ArmClockControl::builder()
|
||||
.with_cpu_peri_clk_act(true)
|
||||
.with_cpu_1x_clk_act(true)
|
||||
.with_cpu_2x_clk_act(true)
|
||||
.with_cpu_3or2x_clk_act(true)
|
||||
.with_cpu_6or4x_clk_act(true)
|
||||
.with_divisor(divisor)
|
||||
.with_srcsel(zynq7000::slcr::clocks::SrcSelArm::ArmPll)
|
||||
.build(),
|
||||
);
|
||||
});
|
||||
}
|
||||
let cpu_6x4x_clk = arm_pll_clk / divisor.as_u32();
|
||||
let cpu_1x_clk = match clock_ratio {
|
||||
CpuClockRatio::FourToTwoToOne => cpu_6x4x_clk / 4,
|
||||
CpuClockRatio::SixToTwoToOne => cpu_6x4x_clk / 6,
|
||||
};
|
||||
|
||||
Self {
|
||||
ref_clk: arm_pll_clk,
|
||||
ratio: clock_ratio,
|
||||
cpu_1x_clk,
|
||||
cpu_2x_clk: cpu_1x_clk * 2,
|
||||
cpu_3x2x_clk: match clock_ratio {
|
||||
CpuClockRatio::SixToTwoToOne => cpu_1x_clk * 3,
|
||||
CpuClockRatio::FourToTwoToOne => cpu_1x_clk * 2,
|
||||
},
|
||||
cpu_6x4x_clk,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn ratio(&self) -> CpuClockRatio {
|
||||
self.ratio
|
||||
}
|
||||
|
||||
/// Reference clock provided by ARM PLL which is used to calculate all other clock frequencies.
|
||||
#[inline]
|
||||
pub const fn ref_clk(&self) -> Hertz {
|
||||
self.ref_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_1x_clk(&self) -> Hertz {
|
||||
self.cpu_1x_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_2x_clk(&self) -> Hertz {
|
||||
self.cpu_2x_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_3x2x_clk(&self) -> Hertz {
|
||||
self.cpu_3x2x_clk
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn cpu_6x4x_clk(&self) -> Hertz {
|
||||
self.cpu_6x4x_clk
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct DdrClocks {
|
||||
/// DDR reference clock generated by the DDR PLL.
|
||||
ref_clk: Hertz,
|
||||
@@ -114,6 +177,7 @@ impl DdrClocks {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct IoClocks {
|
||||
/// Reference clock provided by IO PLL which is used to calculate all other clock frequencies.
|
||||
ref_clk: Hertz,
|
||||
@@ -194,7 +258,9 @@ impl IoClocks {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Display impl for clock config.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Clocks {
|
||||
ps_clk: Hertz,
|
||||
arm_pll_out: Hertz,
|
||||
@@ -250,7 +316,7 @@ impl Clocks {
|
||||
/// It assumes that the clock already has been configured, for example by a first-stage
|
||||
/// bootloader, or the PS7 initialization script.
|
||||
pub fn new_from_regs(ps_clk_freq: Hertz) -> Result<Self, ClockReadError> {
|
||||
let mut clk_regs = unsafe { ClockControl::new_mmio_fixed() };
|
||||
let mut clk_regs = unsafe { ClockControlRegisters::new_mmio_fixed() };
|
||||
|
||||
let arm_pll_cfg = clk_regs.read_arm_pll_ctrl();
|
||||
let io_pll_cfg = clk_regs.read_io_pll_ctrl();
|
||||
@@ -273,25 +339,27 @@ impl Clocks {
|
||||
zynq7000::slcr::clocks::SrcSelArm::DdrPll => ddr_pll_out,
|
||||
zynq7000::slcr::clocks::SrcSelArm::IoPll => io_pll_out,
|
||||
};
|
||||
let clk_sel = clk_regs.read_clk_621_true();
|
||||
let clk_sel = clk_regs.read_clk_ratio_select();
|
||||
if arm_clk_ctrl.divisor().as_u32() == 0 {
|
||||
return Err(ClockReadError::DivisorZero(DivisorZero(ClockModuleId::Arm)));
|
||||
}
|
||||
let arm_clk_divided = arm_base_clk / arm_clk_ctrl.divisor().as_u32();
|
||||
let arm_clks = match clk_sel.sel() {
|
||||
ClockkRatioSelect::FourToTwoToOne => ArmClocks {
|
||||
CpuClockRatio::FourToTwoToOne => ArmClocks {
|
||||
ref_clk: arm_pll_out,
|
||||
cpu_1x_clk: arm_clk_divided / 4,
|
||||
cpu_2x_clk: arm_clk_divided / 2,
|
||||
cpu_3x2x_clk: arm_clk_divided / 2,
|
||||
cpu_6x4x_clk: arm_clk_divided,
|
||||
ratio: clk_sel.sel(),
|
||||
},
|
||||
ClockkRatioSelect::SixToTwoToOne => ArmClocks {
|
||||
CpuClockRatio::SixToTwoToOne => ArmClocks {
|
||||
ref_clk: arm_pll_out,
|
||||
cpu_1x_clk: arm_clk_divided / 6,
|
||||
cpu_2x_clk: arm_clk_divided / 3,
|
||||
cpu_3x2x_clk: arm_clk_divided / 2,
|
||||
cpu_6x4x_clk: arm_clk_divided,
|
||||
ratio: clk_sel.sel(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -501,7 +569,7 @@ impl Clocks {
|
||||
/// The reference clock will only be the RX clock in loopback mode. For the TX block,
|
||||
/// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
|
||||
pub fn calculate_gem_0_ref_clock(&self) -> Result<Hertz, DivisorZero> {
|
||||
let clk_regs = unsafe { ClockControl::new_mmio_fixed() };
|
||||
let clk_regs = unsafe { ClockControlRegisters::new_mmio_fixed() };
|
||||
self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem0)
|
||||
}
|
||||
|
||||
@@ -514,7 +582,7 @@ impl Clocks {
|
||||
/// The reference clock will only be the RX clock in loopback mode. For the TX block,
|
||||
/// the reference clock is used if the EMIO enable bit `GEM{0,1}_CLK_CTRL[6]` is set to 0.
|
||||
pub fn calculate_gem_1_ref_clock(&self) -> Result<Hertz, DivisorZero> {
|
||||
let clk_regs = unsafe { ClockControl::new_mmio_fixed() };
|
||||
let clk_regs = unsafe { ClockControlRegisters::new_mmio_fixed() };
|
||||
self.calculate_gem_ref_clock(clk_regs.read_gem_0_clk_ctrl(), ClockModuleId::Gem1)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
//! # PLL support module
|
||||
use core::sync::atomic::AtomicBool;
|
||||
|
||||
use arbitrary_int::{u4, u7, u10};
|
||||
@@ -37,7 +38,7 @@ impl PllConfig {
|
||||
ps_clk: Hertz,
|
||||
target_clk: Hertz,
|
||||
) -> Result<Self, PllConfigCtorError> {
|
||||
if ps_clk.raw() == 0 {
|
||||
if ps_clk.to_raw() == 0 {
|
||||
return Err(PllConfigCtorError::InvalidInput);
|
||||
}
|
||||
let mul = target_clk / ps_clk;
|
||||
@@ -195,7 +196,7 @@ impl PllConfig {
|
||||
|
||||
/// This function configures the ARM PLL based on the provided [PllConfig].
|
||||
pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
|
||||
if ARM_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
@@ -204,20 +205,20 @@ pub fn configure_arm_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
|
||||
/// This function configures the IO PLL based on the provided [PllConfig].
|
||||
pub fn configure_io_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
|
||||
if IO_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
unsafe { configure_io_pll_unchecked(boot_mode, pll_config) };
|
||||
}
|
||||
|
||||
/// This function configures the DDR PLL based on the provided [PllConfig].
|
||||
pub fn configure_ddr_pll(boot_mode: BootMode, pll_config: PllConfig) {
|
||||
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::SeqCst) {
|
||||
if DDR_PLL_INIT.swap(true, core::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
// Safety: This will only run at most once because of the atomic boolean check.
|
||||
unsafe { configure_arm_pll_unchecked(boot_mode, pll_config) };
|
||||
unsafe { configure_ddr_pll_unchecked(boot_mode, pll_config) };
|
||||
}
|
||||
|
||||
/// This function configures the ARM PLL based on the provided [PllConfig].
|
||||
@@ -309,7 +310,7 @@ unsafe fn configure_pll_unchecked(
|
||||
boot_mode: BootMode,
|
||||
cfg: PllConfig,
|
||||
pll_type: PllType,
|
||||
slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
|
||||
slcr: &mut zynq7000::slcr::MmioRegisters<'static>,
|
||||
pll_ctrl_reg: *mut zynq7000::slcr::clocks::PllControl,
|
||||
pll_cfg_reg: *mut zynq7000::slcr::clocks::PllConfig,
|
||||
) {
|
||||
@@ -346,9 +347,7 @@ unsafe fn configure_pll_unchecked(
|
||||
while ((slcr.clk_ctrl().read_pll_status().raw_value() >> pll_type.bit_offset_pll_locked())
|
||||
& 0b1)
|
||||
!= 1
|
||||
{
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
{}
|
||||
|
||||
pll_ctrl = unsafe { core::ptr::read_volatile(pll_ctrl_reg) };
|
||||
pll_ctrl.set_bypass_force(false);
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Low-level DDR configuration module.
|
||||
use arbitrary_int::{prelude::*, u2, u3, u6};
|
||||
use zynq7000::ddrc::{MmioDdrController, regs::*};
|
||||
use zynq7000::ddrc::{MmioRegisters, regs::*};
|
||||
use zynq7000::slcr::{clocks::DciClockControl, ddriob::DdriobConfig};
|
||||
|
||||
use crate::{clocks::DdrClocks, time::Hertz};
|
||||
@@ -28,7 +28,7 @@ pub fn calculate_dci_divisors(ddr_clks: &DdrClocks) -> DciClkConfig {
|
||||
|
||||
/// Calculate the required DCI divisors for the given DDR clock frequency.
|
||||
pub fn calculate_dci_divisors_with_ddr_clk(ddr_clk: Hertz) -> DciClkConfig {
|
||||
let target_div = ddr_clk.raw().div_ceil(DCI_MAX_FREQ.raw());
|
||||
let target_div = ddr_clk.to_raw().div_ceil(DCI_MAX_FREQ.to_raw());
|
||||
let mut config = DciClkConfig {
|
||||
div0: u6::new(u6::MAX.value()),
|
||||
div1: u6::new(u6::MAX.value()),
|
||||
@@ -86,20 +86,42 @@ pub unsafe fn configure_dci(ddr_clk: &DdrClocks) {
|
||||
///
|
||||
/// This function writes to the DDR IOB related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_for_done: bool) {
|
||||
pub unsafe fn calibrate_iob_impedance_for_ddr3(
|
||||
ddr_control: zynq7000::slcr::ddriob::DdrControl,
|
||||
dci_clk_cfg: DciClkConfig,
|
||||
poll_for_done: bool,
|
||||
) {
|
||||
unsafe {
|
||||
calibrate_iob_impedance(
|
||||
ddr_control,
|
||||
dci_clk_cfg,
|
||||
u3::new(0),
|
||||
u2::new(0),
|
||||
u3::new(0b001),
|
||||
u3::new(0),
|
||||
u2::new(0),
|
||||
CalibrationParams::new_ddr3(),
|
||||
poll_for_done,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// DDR IOB impedance calibration parameters.
|
||||
pub struct CalibrationParams {
|
||||
pub pref_opt2: u3,
|
||||
pub pref_opt1: u2,
|
||||
pub nref_opt4: u3,
|
||||
pub nref_opt2: u3,
|
||||
pub nref_opt1: u2,
|
||||
}
|
||||
|
||||
impl CalibrationParams {
|
||||
pub const fn new_ddr3() -> Self {
|
||||
Self {
|
||||
pref_opt2: u3::new(0),
|
||||
pref_opt1: u2::new(0),
|
||||
nref_opt4: u3::new(0b001),
|
||||
nref_opt2: u3::new(0),
|
||||
nref_opt1: u2::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calibrates the IOB impedance according to to TRM p.325, DDR IOB Impedance calibration.
|
||||
///
|
||||
/// This function will also enable the DCI clock with the provided clock configuration.
|
||||
@@ -115,12 +137,9 @@ pub unsafe fn calibrate_iob_impedance_for_ddr3(dci_clk_cfg: DciClkConfig, poll_f
|
||||
/// This function writes to the DDR IOB related registers. It should only be called once during
|
||||
/// DDR initialization.
|
||||
pub unsafe fn calibrate_iob_impedance(
|
||||
ddr_control: zynq7000::slcr::ddriob::DdrControl,
|
||||
dci_clk_cfg: DciClkConfig,
|
||||
pref_opt2: u3,
|
||||
pref_opt1: u2,
|
||||
nref_opt4: u3,
|
||||
nref_opt2: u3,
|
||||
nref_opt1: u2,
|
||||
calibration_params: CalibrationParams,
|
||||
poll_for_done: bool,
|
||||
) {
|
||||
// Safety: Only writes to DDR IOB related registers.
|
||||
@@ -134,45 +153,35 @@ pub unsafe fn calibrate_iob_impedance(
|
||||
.build(),
|
||||
);
|
||||
let mut ddriob = slcr.ddriob();
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(true);
|
||||
ddriob.write_ddr_control(ddr_control);
|
||||
ddriob.modify_dci_control(|val| val.with_reset(true));
|
||||
ddriob.modify_dci_control(|val| val.with_reset(false));
|
||||
ddriob.modify_dci_control(|val| val.with_reset(true));
|
||||
ddriob.modify_dci_control(|mut val| {
|
||||
val.set_pref_opt2(calibration_params.pref_opt2);
|
||||
val.set_pref_opt1(calibration_params.pref_opt1);
|
||||
val.set_nref_opt4(calibration_params.nref_opt4);
|
||||
val.set_nref_opt2(calibration_params.nref_opt2);
|
||||
val.set_nref_opt1(calibration_params.nref_opt1);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(false);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_reset(true);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
val.set_pref_opt2(pref_opt2);
|
||||
val.set_pref_opt1(pref_opt1);
|
||||
val.set_nref_opt4(nref_opt4);
|
||||
val.set_nref_opt2(nref_opt2);
|
||||
val.set_nref_opt1(nref_opt1);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
ddriob.modify_dci_control(|mut val| {
|
||||
val.set_update_control(false);
|
||||
val
|
||||
});
|
||||
ddriob.modify_dci_ctrl(|mut val| {
|
||||
ddriob.modify_dci_control(|mut val| {
|
||||
val.set_enable(true);
|
||||
val
|
||||
});
|
||||
if poll_for_done {
|
||||
while !slcr.ddriob().read_dci_status().done() {
|
||||
// Wait for the DDR IOB impedance calibration to complete.
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
while !slcr.ddriob().read_dci_status().done() {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Static configuration for DDR IOBs.
|
||||
pub struct DdriobConfigSet {
|
||||
pub ddr_control: zynq7000::slcr::ddriob::DdrControl,
|
||||
pub addr0: DdriobConfig,
|
||||
pub addr1: DdriobConfig,
|
||||
pub data0: DdriobConfig,
|
||||
@@ -272,7 +281,7 @@ pub struct DdrcConfigSet {
|
||||
///
|
||||
/// It does NOT take care of taking the DDR controller out of reset and polling for DDR
|
||||
/// configuration completion.
|
||||
pub fn configure_ddr_config(ddrc: &mut MmioDdrController<'static>, cfg_set: &DdrcConfigSet) {
|
||||
pub fn configure_ddr_config(ddrc: &mut MmioRegisters<'static>, cfg_set: &DdrcConfigSet) {
|
||||
ddrc.write_ddrc_ctrl(cfg_set.ctrl);
|
||||
// Write all configuration registers.
|
||||
ddrc.write_two_rank_cfg(cfg_set.two_rank);
|
||||
@@ -1,5 +1,6 @@
|
||||
//! # DDR module
|
||||
use arbitrary_int::u6;
|
||||
use zynq7000::ddrc::MmioDdrController;
|
||||
use zynq7000::ddrc::MmioRegisters;
|
||||
|
||||
use crate::{
|
||||
BootMode,
|
||||
@@ -43,7 +44,7 @@ impl DdrClockSetupConfig {
|
||||
/// This function consumes the DDRC register block once and thus provides a safe interface for DDR
|
||||
/// initialization.
|
||||
pub fn configure_ddr_for_ddr3(
|
||||
mut ddrc_regs: MmioDdrController<'static>,
|
||||
mut ddrc_regs: MmioRegisters<'static>,
|
||||
boot_mode: BootMode,
|
||||
clk_setup_cfg: DdrClockSetupConfig,
|
||||
ddriob_cfg: &DdriobConfigSet,
|
||||
@@ -75,7 +76,7 @@ pub fn configure_ddr_for_ddr3(
|
||||
ll::configure_iob(ddriob_cfg);
|
||||
// Do not wait for completion, it takes a bit of time. We can set all the DDR config registers
|
||||
// before polling for completion.
|
||||
ll::calibrate_iob_impedance_for_ddr3(dci_clk_cfg, false);
|
||||
ll::calibrate_iob_impedance_for_ddr3(ddriob_cfg.ddr_control, dci_clk_cfg, false);
|
||||
}
|
||||
ll::configure_ddr_config(&mut ddrc_regs, ddr_cfg);
|
||||
// Safety: This is only called once during DDR initialization, and we only modify DDR related
|
||||
@@ -84,7 +85,7 @@ pub fn configure_ddr_for_ddr3(
|
||||
let ddriob_shared = slcr.regs().ddriob_shared();
|
||||
// Wait for DDR IOB impedance calibration to complete first.
|
||||
while !ddriob_shared.read_dci_status().done() {
|
||||
cortex_ar::asm::nop();
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
log::debug!("DDR IOB impedance calib done");
|
||||
|
||||
@@ -98,7 +99,7 @@ pub fn configure_ddr_for_ddr3(
|
||||
!= zynq7000::ddrc::regs::OperatingMode::NormalOperation
|
||||
{
|
||||
// Wait for the soft reset to complete.
|
||||
cortex_ar::asm::nop();
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +129,7 @@ pub mod memtest {
|
||||
/// This tests writes and reads on a memory block starting at the base address
|
||||
/// with the size `words` times 4.
|
||||
pub unsafe fn walking_one_test(base_addr: usize, words: usize) -> Result<(), MemTestError> {
|
||||
unsafe { walking_value_test(true, base_addr, words) }
|
||||
unsafe { walking_value_test(false, base_addr, words) }
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
@@ -8,11 +8,6 @@ use crate::{clocks::IoClocks, enable_amba_peripheral_clock, slcr::Slcr, time::He
|
||||
|
||||
use super::{EthernetId, PsEthernet as _};
|
||||
|
||||
pub struct EthernetLowLevel {
|
||||
id: EthernetId,
|
||||
pub regs: zynq7000::eth::MmioEthernet<'static>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Speed {
|
||||
Mbps10,
|
||||
@@ -52,7 +47,10 @@ impl ClockDivisors {
|
||||
|
||||
/// Calls [Self::calculate_for_rgmii], assuming that the IO clock is the reference clock,
|
||||
/// which is the default clock for the Ethernet module.
|
||||
pub fn calculate_for_rgmii_and_io_clock(io_clks: IoClocks, target_speed: Speed) -> (Self, u32) {
|
||||
pub fn calculate_for_rgmii_and_io_clock(
|
||||
io_clks: &IoClocks,
|
||||
target_speed: Speed,
|
||||
) -> (Self, u32) {
|
||||
Self::calculate_for_rgmii(io_clks.ref_clk(), target_speed)
|
||||
}
|
||||
|
||||
@@ -71,8 +69,8 @@ impl ClockDivisors {
|
||||
let mut best_div_1 = u6::new(0);
|
||||
for div_1 in 1..=u6::MAX.as_usize() {
|
||||
for div_0 in 1..=u6::MAX.as_usize() {
|
||||
let clk_rate = ref_clk.raw() / div_0 as u32 / div_1 as u32;
|
||||
let diff = (target_speed.raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
|
||||
let clk_rate = ref_clk.to_raw() / div_0 as u32 / div_1 as u32;
|
||||
let diff = (target_speed.to_raw() as i64 - clk_rate as i64).unsigned_abs() as u32;
|
||||
if diff < smallest_diff {
|
||||
smallest_diff = diff;
|
||||
best_div_0 = u6::new(div_0 as u8);
|
||||
@@ -174,10 +172,19 @@ impl ClockDivSet {
|
||||
/// Ethernet low-level interface.
|
||||
///
|
||||
/// Basic building block for higher-level abstraction.
|
||||
pub struct EthernetLowLevel {
|
||||
id: EthernetId,
|
||||
/// Register block. Direct public access is allowed to allow low-level operations.
|
||||
pub regs: zynq7000::eth::MmioRegisters<'static>,
|
||||
}
|
||||
|
||||
impl EthernetLowLevel {
|
||||
/// Creates a new instance of the Ethernet low-level interface.
|
||||
///
|
||||
/// Returns [None] if the given registers block base address does not correspond to a valid
|
||||
/// Ethernet peripheral.
|
||||
#[inline]
|
||||
pub fn new(regs: zynq7000::eth::MmioEthernet<'static>) -> Option<Self> {
|
||||
pub fn new(regs: zynq7000::eth::MmioRegisters<'static>) -> Option<Self> {
|
||||
regs.id()?;
|
||||
Some(EthernetLowLevel {
|
||||
id: regs.id().unwrap(),
|
||||
@@ -196,41 +203,15 @@ impl EthernetLowLevel {
|
||||
id,
|
||||
regs: unsafe {
|
||||
match id {
|
||||
EthernetId::Eth0 => zynq7000::eth::Ethernet::new_mmio_fixed_0(),
|
||||
EthernetId::Eth1 => zynq7000::eth::Ethernet::new_mmio_fixed_1(),
|
||||
EthernetId::Eth0 => zynq7000::eth::Registers::new_mmio_fixed_0(),
|
||||
EthernetId::Eth1 => zynq7000::eth::Registers::new_mmio_fixed_1(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, cycles: usize) {
|
||||
let assert_reset = match self.id {
|
||||
EthernetId::Eth0 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(false)
|
||||
.with_gem0_ref_rst(true)
|
||||
.with_gem1_rx_rst(false)
|
||||
.with_gem0_rx_rst(true)
|
||||
.with_gem1_cpu1x_rst(false)
|
||||
.with_gem0_cpu1x_rst(true)
|
||||
.build(),
|
||||
EthernetId::Eth1 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(true)
|
||||
.with_gem0_ref_rst(false)
|
||||
.with_gem1_rx_rst(true)
|
||||
.with_gem0_rx_rst(false)
|
||||
.with_gem1_cpu1x_rst(true)
|
||||
.with_gem0_cpu1x_rst(false)
|
||||
.build(),
|
||||
};
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_eth(assert_reset);
|
||||
for _ in 0..cycles {
|
||||
cortex_ar::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
|
||||
});
|
||||
}
|
||||
reset(self.id, cycles);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -383,3 +364,34 @@ impl EthernetLowLevel {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the Ethernet peripheral with the given ID.
|
||||
pub fn reset(id: EthernetId, cycles: usize) {
|
||||
let assert_reset = match id {
|
||||
EthernetId::Eth0 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(false)
|
||||
.with_gem0_ref_rst(true)
|
||||
.with_gem1_rx_rst(false)
|
||||
.with_gem0_rx_rst(true)
|
||||
.with_gem1_cpu1x_rst(false)
|
||||
.with_gem0_cpu1x_rst(true)
|
||||
.build(),
|
||||
EthernetId::Eth1 => EthernetReset::builder()
|
||||
.with_gem1_ref_rst(true)
|
||||
.with_gem0_ref_rst(false)
|
||||
.with_gem1_rx_rst(true)
|
||||
.with_gem0_rx_rst(false)
|
||||
.with_gem1_cpu1x_rst(true)
|
||||
.with_gem0_cpu1x_rst(false)
|
||||
.build(),
|
||||
};
|
||||
unsafe {
|
||||
Slcr::with(|regs| {
|
||||
regs.reset_ctrl().write_eth(assert_reset);
|
||||
for _ in 0..cycles {
|
||||
aarch32_cpu::asm::nop();
|
||||
}
|
||||
regs.reset_ctrl().write_eth(EthernetReset::DEFAULT);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use zynq7000::eth::{MdcClockDivisor, PhyMaintenance};
|
||||
use super::{EthernetId, ll::EthernetLowLevel};
|
||||
|
||||
pub struct Mdio {
|
||||
regs: zynq7000::eth::MmioEthernet<'static>,
|
||||
regs: zynq7000::eth::MmioRegisters<'static>,
|
||||
clause22: bool,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! # Ethernet module
|
||||
use arbitrary_int::{u2, u3};
|
||||
pub use zynq7000::eth::MdcClockDivisor;
|
||||
use zynq7000::eth::{
|
||||
BurstLength, DmaRxBufSize, GEM_0_BASE_ADDR, GEM_1_BASE_ADDR, InterruptControl, InterruptStatus,
|
||||
MmioEthernet, RxStatus, TxStatus,
|
||||
MmioRegisters, RxStatus, TxStatus,
|
||||
};
|
||||
|
||||
pub use ll::{ClockConfig, ClockDivSet, Duplex, EthernetLowLevel, Speed};
|
||||
@@ -53,18 +54,18 @@ impl EthernetId {
|
||||
/// # Safety
|
||||
///
|
||||
/// Circumvents ownership and safety guarantees of the HAL.
|
||||
pub const unsafe fn steal_regs(&self) -> zynq7000::eth::MmioEthernet<'static> {
|
||||
pub const unsafe fn steal_regs(&self) -> zynq7000::eth::MmioRegisters<'static> {
|
||||
unsafe {
|
||||
match self {
|
||||
EthernetId::Eth0 => zynq7000::eth::Ethernet::new_mmio_fixed_0(),
|
||||
EthernetId::Eth1 => zynq7000::eth::Ethernet::new_mmio_fixed_1(),
|
||||
EthernetId::Eth0 => zynq7000::eth::Registers::new_mmio_fixed_0(),
|
||||
EthernetId::Eth1 => zynq7000::eth::Registers::new_mmio_fixed_1(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clk_config_regs(
|
||||
&self,
|
||||
slcr: &mut zynq7000::slcr::MmioSlcr<'static>,
|
||||
slcr: &mut zynq7000::slcr::MmioRegisters<'static>,
|
||||
) -> (
|
||||
*mut zynq7000::slcr::clocks::GigEthClockControl,
|
||||
*mut zynq7000::slcr::clocks::GigEthRclkControl,
|
||||
@@ -83,13 +84,13 @@ impl EthernetId {
|
||||
}
|
||||
|
||||
pub trait PsEthernet {
|
||||
fn reg_block(&self) -> MmioEthernet<'static>;
|
||||
fn reg_block(&self) -> MmioRegisters<'static>;
|
||||
fn id(&self) -> Option<EthernetId>;
|
||||
}
|
||||
|
||||
impl PsEthernet for MmioEthernet<'static> {
|
||||
impl PsEthernet for MmioRegisters<'static> {
|
||||
#[inline]
|
||||
fn reg_block(&self) -> MmioEthernet<'static> {
|
||||
fn reg_block(&self) -> MmioRegisters<'static> {
|
||||
unsafe { self.clone() }
|
||||
}
|
||||
|
||||
@@ -105,42 +106,31 @@ impl PsEthernet for MmioEthernet<'static> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TxClockPin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxControlPin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData0Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData1Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData2Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait TxData3Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxClockPin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxControlPin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData0Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData1Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData2Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait RxData3Pin: MioPin {
|
||||
const ETH_ID: EthernetId;
|
||||
}
|
||||
pub trait Eth0TxClockPin: MioPin {}
|
||||
pub trait Eth0TxControlPin: MioPin {}
|
||||
pub trait Eth0TxData0Pin: MioPin {}
|
||||
pub trait Eth0TxData1Pin: MioPin {}
|
||||
pub trait Eth0TxData2Pin: MioPin {}
|
||||
pub trait Eth0TxData3Pin: MioPin {}
|
||||
pub trait Eth0RxClockPin: MioPin {}
|
||||
pub trait Eth0RxControlPin: MioPin {}
|
||||
pub trait Eth0RxData0Pin: MioPin {}
|
||||
pub trait Eth0RxData1Pin: MioPin {}
|
||||
pub trait Eth0RxData2Pin: MioPin {}
|
||||
pub trait Eth0RxData3Pin: MioPin {}
|
||||
|
||||
pub trait Eth1TxClockPin: MioPin {}
|
||||
pub trait Eth1TxControlPin: MioPin {}
|
||||
pub trait Eth1TxData0Pin: MioPin {}
|
||||
pub trait Eth1TxData1Pin: MioPin {}
|
||||
pub trait Eth1TxData2Pin: MioPin {}
|
||||
pub trait Eth1TxData3Pin: MioPin {}
|
||||
pub trait Eth1RxClockPin: MioPin {}
|
||||
pub trait Eth1RxControlPin: MioPin {}
|
||||
pub trait Eth1RxData0Pin: MioPin {}
|
||||
pub trait Eth1RxData1Pin: MioPin {}
|
||||
pub trait Eth1RxData2Pin: MioPin {}
|
||||
pub trait Eth1RxData3Pin: MioPin {}
|
||||
|
||||
pub trait MdClockPin: MioPin {}
|
||||
pub trait MdIoPin: MioPin {}
|
||||
@@ -149,95 +139,50 @@ impl MdClockPin for Pin<Mio52> {}
|
||||
impl MdIoPin for Pin<Mio53> {}
|
||||
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxClockPin for Pin<Mio16> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0TxClockPin for Pin<Mio16> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxControlPin for Pin<Mio21> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0TxControlPin for Pin<Mio21> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData0Pin for Pin<Mio17> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0TxData0Pin for Pin<Mio17> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData1Pin for Pin<Mio18> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0TxData1Pin for Pin<Mio18> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData2Pin for Pin<Mio19> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0TxData2Pin for Pin<Mio19> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl TxData3Pin for Pin<Mio20> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0TxData3Pin for Pin<Mio20> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxClockPin for Pin<Mio22> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0RxClockPin for Pin<Mio22> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxControlPin for Pin<Mio27> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0RxControlPin for Pin<Mio27> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData0Pin for Pin<Mio23> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0RxData0Pin for Pin<Mio23> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData1Pin for Pin<Mio24> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0RxData1Pin for Pin<Mio24> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData2Pin for Pin<Mio25> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0RxData2Pin for Pin<Mio25> {}
|
||||
#[cfg(not(feature = "7z010-7z007s-clg225"))]
|
||||
impl RxData3Pin for Pin<Mio26> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth0;
|
||||
}
|
||||
impl Eth0RxData3Pin for Pin<Mio26> {}
|
||||
|
||||
impl TxClockPin for Pin<Mio28> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxControlPin for Pin<Mio33> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData0Pin for Pin<Mio29> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData1Pin for Pin<Mio30> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData2Pin for Pin<Mio31> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl TxData3Pin for Pin<Mio32> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxClockPin for Pin<Mio34> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxControlPin for Pin<Mio39> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData0Pin for Pin<Mio35> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData1Pin for Pin<Mio36> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData2Pin for Pin<Mio37> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl RxData3Pin for Pin<Mio38> {
|
||||
const ETH_ID: EthernetId = EthernetId::Eth1;
|
||||
}
|
||||
impl Eth1TxClockPin for Pin<Mio28> {}
|
||||
impl Eth1TxControlPin for Pin<Mio33> {}
|
||||
impl Eth1TxData0Pin for Pin<Mio29> {}
|
||||
impl Eth1TxData1Pin for Pin<Mio30> {}
|
||||
impl Eth1TxData2Pin for Pin<Mio31> {}
|
||||
impl Eth1TxData3Pin for Pin<Mio32> {}
|
||||
impl Eth1RxClockPin for Pin<Mio34> {}
|
||||
impl Eth1RxControlPin for Pin<Mio39> {}
|
||||
impl Eth1RxData0Pin for Pin<Mio35> {}
|
||||
impl Eth1RxData1Pin for Pin<Mio36> {}
|
||||
impl Eth1RxData2Pin for Pin<Mio37> {}
|
||||
impl Eth1RxData3Pin for Pin<Mio38> {}
|
||||
|
||||
/// Calculate the CPU 1x clock divisor required to achieve a clock speed which is below
|
||||
/// 2.5 MHz, as specified by the 802.3 standard.
|
||||
pub fn calculate_mdc_clk_div(arm_clks: &ArmClocks) -> Option<MdcClockDivisor> {
|
||||
let div = arm_clks.cpu_1x_clk().raw().div_ceil(MAX_MDC_SPEED.raw());
|
||||
let div = arm_clks
|
||||
.cpu_1x_clk()
|
||||
.to_raw()
|
||||
.div_ceil(MAX_MDC_SPEED.to_raw());
|
||||
match div {
|
||||
0..8 => Some(MdcClockDivisor::Div8),
|
||||
8..16 => Some(MdcClockDivisor::Div16),
|
||||
@@ -324,26 +269,26 @@ const IRQ_CLEAR_ALL: InterruptStatus = InterruptStatus::builder()
|
||||
.build();
|
||||
|
||||
impl Ethernet {
|
||||
/// Creates a new Ethernet instance with the given configuration while also
|
||||
/// configuring all the necessary MIO pins.
|
||||
/// Creates a new Ethernet instance for the Ethernet 0 block with the given configuration while
|
||||
/// also configuring all the necessary MIO pins.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_with_mio<
|
||||
TxClock: TxClockPin,
|
||||
TxControl: TxControlPin,
|
||||
TxData0: TxData0Pin,
|
||||
TxData1: TxData1Pin,
|
||||
TxData2: TxData2Pin,
|
||||
TxData3: TxData3Pin,
|
||||
RxClock: RxClockPin,
|
||||
RxControl: RxControlPin,
|
||||
RxData0: RxData0Pin,
|
||||
RxData1: RxData1Pin,
|
||||
RxData2: RxData2Pin,
|
||||
RxData3: RxData3Pin,
|
||||
pub fn new_with_mio_eth_0<
|
||||
TxClock: Eth0TxClockPin,
|
||||
TxControl: Eth0TxControlPin,
|
||||
TxData0: Eth0TxData0Pin,
|
||||
TxData1: Eth0TxData1Pin,
|
||||
TxData2: Eth0TxData2Pin,
|
||||
TxData3: Eth0TxData3Pin,
|
||||
RxClock: Eth0RxClockPin,
|
||||
RxControl: Eth0RxControlPin,
|
||||
RxData0: Eth0RxData0Pin,
|
||||
RxData1: Eth0RxData1Pin,
|
||||
RxData2: Eth0RxData2Pin,
|
||||
RxData3: Eth0RxData3Pin,
|
||||
MdClock: MdClockPin,
|
||||
MdIo: MdIoPin,
|
||||
>(
|
||||
mut ll: ll::EthernetLowLevel,
|
||||
ll: ll::EthernetLowLevel,
|
||||
config: EthernetConfig,
|
||||
tx_clk: TxClock,
|
||||
tx_ctrl: TxControl,
|
||||
@@ -352,6 +297,59 @@ impl Ethernet {
|
||||
rx_ctrl: RxControl,
|
||||
rx_data: (RxData0, RxData1, RxData2, RxData3),
|
||||
md_pins: Option<(MdClock, MdIo)>,
|
||||
) -> Self {
|
||||
Self::new_with_mio(
|
||||
ll, config, tx_clk, tx_ctrl, tx_data, rx_clk, rx_ctrl, rx_data, md_pins,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new Ethernet instance for the Ethernet 1 block with the given configuration while
|
||||
/// also configuring all the necessary MIO pins.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_with_mio_eth_1<
|
||||
TxClock: Eth1TxClockPin,
|
||||
TxControl: Eth1TxControlPin,
|
||||
TxData0: Eth1TxData0Pin,
|
||||
TxData1: Eth1TxData1Pin,
|
||||
TxData2: Eth1TxData2Pin,
|
||||
TxData3: Eth1TxData3Pin,
|
||||
RxClock: Eth1RxClockPin,
|
||||
RxControl: Eth1RxControlPin,
|
||||
RxData0: Eth1RxData0Pin,
|
||||
RxData1: Eth1RxData1Pin,
|
||||
RxData2: Eth1RxData2Pin,
|
||||
RxData3: Eth1RxData3Pin,
|
||||
MdClock: MdClockPin,
|
||||
MdIo: MdIoPin,
|
||||
>(
|
||||
ll: ll::EthernetLowLevel,
|
||||
config: EthernetConfig,
|
||||
tx_clk: TxClock,
|
||||
tx_ctrl: TxControl,
|
||||
tx_data: (TxData0, TxData1, TxData2, TxData3),
|
||||
rx_clk: RxClock,
|
||||
rx_ctrl: RxControl,
|
||||
rx_data: (RxData0, RxData1, RxData2, RxData3),
|
||||
md_pins: Option<(MdClock, MdIo)>,
|
||||
) -> Self {
|
||||
Self::new_with_mio(
|
||||
ll, config, tx_clk, tx_ctrl, tx_data, rx_clk, rx_ctrl, rx_data, md_pins,
|
||||
)
|
||||
}
|
||||
|
||||
// Creates a new Ethernet instance with the given configuration while also
|
||||
// configuring all the necessary MIO pins.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_with_mio<MdClock: MdClockPin, MdIo: MdIoPin>(
|
||||
mut ll: ll::EthernetLowLevel,
|
||||
config: EthernetConfig,
|
||||
tx_clk: impl MioPin,
|
||||
tx_ctrl: impl MioPin,
|
||||
tx_data: (impl MioPin, impl MioPin, impl MioPin, impl MioPin),
|
||||
rx_clk: impl MioPin,
|
||||
rx_ctrl: impl MioPin,
|
||||
rx_data: (impl MioPin, impl MioPin, impl MioPin, impl MioPin),
|
||||
md_pins: Option<(MdClock, MdIo)>,
|
||||
) -> Self {
|
||||
Self::common_init(&mut ll, config.mac_address);
|
||||
let tx_mio_config = zynq7000::slcr::mio::Config::builder()
|
||||
@@ -594,7 +592,7 @@ impl Ethernet {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn regs(&mut self) -> &MmioEthernet<'static> {
|
||||
pub fn regs(&mut self) -> &MmioRegisters<'static> {
|
||||
&self.ll.regs
|
||||
}
|
||||
|
||||
@@ -608,7 +606,7 @@ impl Ethernet {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn regs_mut(&mut self) -> &mut MmioEthernet<'static> {
|
||||
pub fn regs_mut(&mut self) -> &mut MmioRegisters<'static> {
|
||||
&mut self.ll.regs
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user