init commit
This commit is contained in:
commit
ba8d817a73
1
.cargo/.gitignore
vendored
Normal file
1
.cargo/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
config.toml
|
41
.cargo/def-config.toml
Normal file
41
.cargo/def-config.toml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
|
# uncomment ONE of these three option to make `cargo run` start a GDB session
|
||||||
|
# which option to pick depends on your system
|
||||||
|
# runner = "arm-none-eabi-gdb -q -x openocd.gdb"
|
||||||
|
# runner = "gdb-multiarch -q -x openocd.gdb"
|
||||||
|
# runner = "gdb -q -x openocd.gdb"
|
||||||
|
# runner = "gdb-multiarch -q -x jlink.gdb"
|
||||||
|
|
||||||
|
runner = "probe-rs run --chip VA108xx_RAM --protocol jtag"
|
||||||
|
# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"]
|
||||||
|
|
||||||
|
rustflags = [
|
||||||
|
# This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
|
||||||
|
# See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
|
||||||
|
"-C", "link-arg=--nmagic",
|
||||||
|
|
||||||
|
# LLD (shipped with the Rust toolchain) is used as the default linker
|
||||||
|
"-C", "link-arg=-Tlink.x",
|
||||||
|
|
||||||
|
# knurling-rs tooling. If you want to use flip-link, ensure it is installed first.
|
||||||
|
"-C", "linker=flip-link",
|
||||||
|
# Unfortunately, defmt is clunky to use without probe-rs..
|
||||||
|
"-C", "link-arg=-Tdefmt.x",
|
||||||
|
|
||||||
|
# Can be useful for debugging.
|
||||||
|
# "-Clink-args=-Map=app.map"
|
||||||
|
]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
# Pick ONE of these compilation targets
|
||||||
|
target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ for VA108xx
|
||||||
|
# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) for VA416xx
|
||||||
|
|
||||||
|
[alias]
|
||||||
|
re = "run --example"
|
||||||
|
rb = "run --bin"
|
||||||
|
rrb = "run --release --bin"
|
||||||
|
ut = "test --target x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
DEFMT_LOG = "info"
|
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
# Keep config.toml configurable.
|
||||||
|
.cargo/config.toml
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
/app.map
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
# JetBrains IDEs
|
||||||
|
/.idea
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
/Embed.toml
|
16
CHANGELOG.md
Normal file
16
CHANGELOG.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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.0]
|
||||||
|
|
||||||
|
Init commit.
|
||||||
|
|
||||||
|
[unreleased]: https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/compare/v0.1.0...HEAD
|
||||||
|
[v0.1.0]: https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs/src/tag/v0.1.0
|
48
Cargo.toml
Normal file
48
Cargo.toml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
[package]
|
||||||
|
name = "vorago-shared-periphs"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Peripheral drivers shared between Vorago families"
|
||||||
|
edition = "2024"
|
||||||
|
homepage = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs"
|
||||||
|
repository = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cortex-m = { version = "0.7" }
|
||||||
|
cfg-if = "1"
|
||||||
|
derive-mmio = { git = "https://github.com/knurling-rs/derive-mmio.git" }
|
||||||
|
bitbybit = "1.3"
|
||||||
|
arbitrary-int = "1.3"
|
||||||
|
static_assertions = "1.1"
|
||||||
|
nb = "1"
|
||||||
|
heapless = "0.8"
|
||||||
|
critical-section = "1"
|
||||||
|
embedded-hal = "1.0"
|
||||||
|
embedded-hal-async = "1"
|
||||||
|
embedded-hal-nb = "1"
|
||||||
|
embedded-io = "0.6"
|
||||||
|
embedded-io-async = "0.6"
|
||||||
|
raw-slicee = "0.1"
|
||||||
|
thiserror = { version = "2", default-features = false }
|
||||||
|
paste = "1"
|
||||||
|
fugit = "0.3"
|
||||||
|
defmt = { version = "1", optional = true }
|
||||||
|
va108xx = { version = "0.5", default-features = false, optional = true }
|
||||||
|
va416xx = { version = "0.4", default-features = false, optional = true }
|
||||||
|
portable-atomic = "1"
|
||||||
|
|
||||||
|
embassy-sync = "0.6"
|
||||||
|
embassy-time-driver = "0.2"
|
||||||
|
embassy-time-queue-utils = "0.1"
|
||||||
|
once_cell = { version = "1", default-features = false, features = ["critical-section"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
vor1x = ["_family-selected", "dep:va108xx"]
|
||||||
|
vor4x = ["_family-selected", "dep:va416xx"]
|
||||||
|
va41628 = []
|
||||||
|
defmt = ["dep:defmt", "arbitrary-int/defmt", "fugit/defmt", "embedded-hal/defmt-03"]
|
||||||
|
|
||||||
|
_family-selected = []
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["raw-slicee"]
|
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
8
README.md
Normal file
8
README.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Vorago Shared Peripherals
|
||||||
|
========
|
||||||
|
|
||||||
|
Peripheral drivers shared between Vorago families.
|
||||||
|
|
||||||
|
This library should not used directly. Instead, use the re-exported modules of the repective
|
||||||
|
[VA108xx HAL](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/va108xx-hal) and
|
||||||
|
[VA416xx HAL](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs).
|
62
src/clock.rs
Normal file
62
src/clock.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use crate::time::Hertz;
|
||||||
|
|
||||||
|
pub const HBO_FREQ: Hertz = Hertz::from_raw(20_000_000);
|
||||||
|
|
||||||
|
/// Frozen clock frequencies
|
||||||
|
///
|
||||||
|
/// The existence of this value indicates that the clock configuration can no longer be changed.
|
||||||
|
/// The [self] module documentation gives some more information on how to retrieve an instance
|
||||||
|
/// of this structure.
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct Clocks {
|
||||||
|
sysclk: Hertz,
|
||||||
|
apb1: Hertz,
|
||||||
|
apb2: Hertz,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
adc_clk: Hertz,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clocks {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn __new(final_sysclk: Hertz, #[cfg(not(feature = "va41628"))] adc_clk: Hertz) -> Self {
|
||||||
|
Self {
|
||||||
|
sysclk: final_sysclk,
|
||||||
|
apb1: final_sysclk / 2,
|
||||||
|
apb2: final_sysclk / 4,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
adc_clk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the frequency of the HBO clock
|
||||||
|
pub const fn hbo(&self) -> Hertz {
|
||||||
|
HBO_FREQ
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the frequency of the APB0 which is equal to the system clock.
|
||||||
|
pub const fn apb0(&self) -> Hertz {
|
||||||
|
self.sysclk()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns system clock divied by 2.
|
||||||
|
pub const fn apb1(&self) -> Hertz {
|
||||||
|
self.apb1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns system clock divied by 4.
|
||||||
|
pub const fn apb2(&self) -> Hertz {
|
||||||
|
self.apb2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the system (core) frequency
|
||||||
|
pub const fn sysclk(&self) -> Hertz {
|
||||||
|
self.sysclk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ADC clock frequency which has a separate divider.
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub const fn adc_clk(&self) -> Hertz {
|
||||||
|
self.adc_clk
|
||||||
|
}
|
||||||
|
}
|
340
src/embassy.rs
Normal file
340
src/embassy.rs
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
use core::cell::{Cell, RefCell};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
enable_nvic_interrupt,
|
||||||
|
timer::{
|
||||||
|
TimId, TimMarker, assert_tim_reset_for_cycles, enable_tim_clk,
|
||||||
|
regs::{EnableControl, MmioTimer},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use critical_section::{CriticalSection, Mutex};
|
||||||
|
use embassy_time_driver::TICK_HZ;
|
||||||
|
use embassy_time_driver::{Driver, time_driver_impl};
|
||||||
|
use embassy_time_queue_utils::Queue;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use portable_atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use crate::time::Hertz;
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use crate::{PeripheralSelect, enable_peripheral_clock};
|
||||||
|
|
||||||
|
time_driver_impl!(
|
||||||
|
static TIME_DRIVER: TimerDriver = TimerDriver {
|
||||||
|
periods: AtomicU32::new(0),
|
||||||
|
alarms: Mutex::new(AlarmState::new()),
|
||||||
|
queue: Mutex::new(RefCell::new(Queue::new())),
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Expose the time driver so the user can specify the IRQ handlers themselves.
|
||||||
|
pub fn time_driver() -> &'static TimerDriver {
|
||||||
|
&TIME_DRIVER
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AlarmState {
|
||||||
|
timestamp: Cell<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AlarmState {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
timestamp: Cell::new(u64::MAX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for AlarmState {}
|
||||||
|
|
||||||
|
static SCALE: OnceCell<u64> = OnceCell::new();
|
||||||
|
static TIMEKEEPER_TIM: OnceCell<TimId> = OnceCell::new();
|
||||||
|
static ALARM_TIM: OnceCell<TimId> = OnceCell::new();
|
||||||
|
|
||||||
|
pub struct TimerDriver {
|
||||||
|
periods: AtomicU32,
|
||||||
|
/// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled.
|
||||||
|
alarms: Mutex<AlarmState>,
|
||||||
|
queue: Mutex<RefCell<Queue>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimerDriver {
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn __init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
|
||||||
|
&self,
|
||||||
|
sysclk: Hertz,
|
||||||
|
_timekeeper_tim: TimekeeperTim,
|
||||||
|
_alarm_tim: AlarmTim,
|
||||||
|
timekeeper_irq: va108xx::Interrupt,
|
||||||
|
alarm_irq: va108xx::Interrupt,
|
||||||
|
) {
|
||||||
|
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ALARM_TIM.set(AlarmTim::ID).ok();
|
||||||
|
TIMEKEEPER_TIM.set(TimekeeperTim::ID).ok();
|
||||||
|
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||||
|
enable_tim_clk(TimekeeperTim::ID);
|
||||||
|
assert_tim_reset_for_cycles(TimekeeperTim::ID, 2);
|
||||||
|
|
||||||
|
let mut timekeeper_reg_block = unsafe { TimekeeperTim::ID.steal_regs() };
|
||||||
|
let mut alarm_tim_reg_block = unsafe { AlarmTim::ID.steal_regs() };
|
||||||
|
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
|
||||||
|
SCALE.set((sysclk.raw() / TICK_HZ as u32) as u64).unwrap();
|
||||||
|
timekeeper_reg_block.write_reset_value(u32::MAX);
|
||||||
|
// Decrementing counter.
|
||||||
|
timekeeper_reg_block.write_count_value(u32::MAX);
|
||||||
|
let irqsel = unsafe { va108xx::Irqsel::steal() };
|
||||||
|
// Switch on. Timekeeping should always be done.
|
||||||
|
irqsel
|
||||||
|
.tim0(TimekeeperTim::ID.value() as usize)
|
||||||
|
.write(|w| unsafe { w.bits(timekeeper_irq as u32) });
|
||||||
|
unsafe {
|
||||||
|
enable_nvic_interrupt(timekeeper_irq);
|
||||||
|
}
|
||||||
|
timekeeper_reg_block.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
timekeeper_reg_block.write_enable_control(EnableControl::new_enable());
|
||||||
|
|
||||||
|
enable_tim_clk(AlarmTim::ID);
|
||||||
|
assert_tim_reset_for_cycles(AlarmTim::ID, 2);
|
||||||
|
|
||||||
|
// Explicitely disable alarm timer until needed.
|
||||||
|
alarm_tim_reg_block.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(false);
|
||||||
|
value.set_enable(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
|
||||||
|
unsafe {
|
||||||
|
enable_nvic_interrupt(alarm_irq);
|
||||||
|
}
|
||||||
|
irqsel
|
||||||
|
.tim0(AlarmTim::ID.value() as usize)
|
||||||
|
.write(|w| unsafe { w.bits(alarm_irq as u32) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn __init<TimekeeperTim: TimMarker, AlarmTim: TimMarker>(
|
||||||
|
&self,
|
||||||
|
_timekeeper_tim: TimekeeperTim,
|
||||||
|
_alarm_tim: AlarmTim,
|
||||||
|
clocks: &crate::clock::Clocks,
|
||||||
|
) {
|
||||||
|
if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ALARM_TIM.set(AlarmTim::ID).ok();
|
||||||
|
TIMEKEEPER_TIM.set(TimekeeperTim::ID).ok();
|
||||||
|
let mut timekeeper_regs = unsafe { TimekeeperTim::ID.steal_regs() };
|
||||||
|
let mut alarm_regs = unsafe { AlarmTim::ID.steal_regs() };
|
||||||
|
|
||||||
|
enable_tim_clk(TimekeeperTim::ID);
|
||||||
|
assert_tim_reset_for_cycles(TimekeeperTim::ID, 2);
|
||||||
|
|
||||||
|
// Initiate scale value here. This is required to convert timer ticks back to a timestamp.
|
||||||
|
|
||||||
|
SCALE
|
||||||
|
.set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64)
|
||||||
|
.unwrap();
|
||||||
|
timekeeper_regs.write_reset_value(u32::MAX);
|
||||||
|
// Decrementing counter.
|
||||||
|
timekeeper_regs.write_count_value(u32::MAX);
|
||||||
|
// Switch on. Timekeeping should always be done.
|
||||||
|
unsafe {
|
||||||
|
enable_nvic_interrupt(TimekeeperTim::IRQ);
|
||||||
|
}
|
||||||
|
timekeeper_regs.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
timekeeper_regs.write_enable_control(EnableControl::new_enable());
|
||||||
|
|
||||||
|
enable_tim_clk(AlarmTim::ID);
|
||||||
|
assert_tim_reset_for_cycles(AlarmTim::ID, 2);
|
||||||
|
// Explicitely disable alarm timer until needed.
|
||||||
|
alarm_regs.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(false);
|
||||||
|
value.set_enable(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
// Enable general interrupts. The IRQ enable of the peripheral remains cleared.
|
||||||
|
unsafe {
|
||||||
|
enable_nvic_interrupt(AlarmTim::IRQ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timekeeper_tim() -> MmioTimer<'static> {
|
||||||
|
TIMEKEEPER_TIM
|
||||||
|
.get()
|
||||||
|
.map(|tim| unsafe { tim.steal_regs() })
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
fn alarm_tim() -> MmioTimer<'static> {
|
||||||
|
ALARM_TIM
|
||||||
|
.get()
|
||||||
|
.map(|tim| unsafe { tim.steal_regs() })
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should be called inside the IRQ of the timekeeper timer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function has to be called once by the TIM IRQ used for the timekeeping.
|
||||||
|
pub unsafe fn on_interrupt_timekeeping(&self) {
|
||||||
|
self.next_period();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Should be called inside the IRQ of the alarm timer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
///This function has to be called once by the TIM IRQ used for the timekeeping.
|
||||||
|
pub unsafe fn on_interrupt_alarm(&self) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
if self.alarms.borrow(cs).timestamp.get() <= self.now() {
|
||||||
|
self.trigger_alarm(cs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_period(&self) {
|
||||||
|
let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1;
|
||||||
|
let t = (period as u64) << 32;
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let alarm = &self.alarms.borrow(cs);
|
||||||
|
let at = alarm.timestamp.get();
|
||||||
|
if at < t {
|
||||||
|
self.trigger_alarm(cs);
|
||||||
|
} else {
|
||||||
|
let mut alarm_tim = Self::alarm_tim();
|
||||||
|
|
||||||
|
let remaining_ticks = (at - t).checked_mul(*SCALE.get().unwrap());
|
||||||
|
if remaining_ticks.is_some_and(|v| v <= u32::MAX as u64) {
|
||||||
|
alarm_tim.write_enable_control(EnableControl::new_disable());
|
||||||
|
alarm_tim.write_count_value(remaining_ticks.unwrap() as u32);
|
||||||
|
alarm_tim.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
alarm_tim.write_enable_control(EnableControl::new_enable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trigger_alarm(&self, cs: CriticalSection) {
|
||||||
|
Self::alarm_tim().modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(false);
|
||||||
|
value.set_enable(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
let alarm = &self.alarms.borrow(cs);
|
||||||
|
// Setting the maximum value disables the alarm.
|
||||||
|
alarm.timestamp.set(u64::MAX);
|
||||||
|
|
||||||
|
// Call after clearing alarm, so the callback can set another alarm.
|
||||||
|
let mut next = self
|
||||||
|
.queue
|
||||||
|
.borrow(cs)
|
||||||
|
.borrow_mut()
|
||||||
|
.next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = self
|
||||||
|
.queue
|
||||||
|
.borrow(cs)
|
||||||
|
.borrow_mut()
|
||||||
|
.next_expiration(self.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool {
|
||||||
|
if SCALE.get().is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut alarm_tim = Self::alarm_tim();
|
||||||
|
alarm_tim.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(false);
|
||||||
|
value.set_enable(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
let alarm = self.alarms.borrow(cs);
|
||||||
|
alarm.timestamp.set(timestamp);
|
||||||
|
|
||||||
|
let t = self.now();
|
||||||
|
if timestamp <= t {
|
||||||
|
alarm.timestamp.set(u64::MAX);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it hasn't triggered yet, setup the relevant reset value, regardless of whether
|
||||||
|
// the interrupts are enabled or not. When they are enabled at a later point, the
|
||||||
|
// right value is already set.
|
||||||
|
|
||||||
|
// If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm
|
||||||
|
// is not missed.
|
||||||
|
//
|
||||||
|
// This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed
|
||||||
|
// by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time,
|
||||||
|
// and we don't do that here.
|
||||||
|
let safe_timestamp = timestamp.max(t + 3);
|
||||||
|
let timer_ticks = (safe_timestamp - t).checked_mul(*SCALE.get().unwrap());
|
||||||
|
alarm_tim.write_reset_value(u32::MAX);
|
||||||
|
if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) {
|
||||||
|
alarm_tim.write_count_value(timer_ticks.unwrap() as u32);
|
||||||
|
alarm_tim.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(true);
|
||||||
|
value.set_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If it's too far in the future, don't enable timer yet.
|
||||||
|
// It will be enabled later by `next_period`.
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Driver for TimerDriver {
|
||||||
|
fn now(&self) -> u64 {
|
||||||
|
if SCALE.get().is_none() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut period1: u32;
|
||||||
|
let mut period2: u32;
|
||||||
|
let mut counter_val: u32;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// Acquire ensures that we get the latest value of `periods` and
|
||||||
|
// no instructions can be reordered before the load.
|
||||||
|
period1 = self.periods.load(Ordering::Acquire);
|
||||||
|
|
||||||
|
counter_val = u32::MAX - Self::timekeeper_tim().read_count_value();
|
||||||
|
|
||||||
|
// Double read to protect against race conditions when the counter is overflowing.
|
||||||
|
period2 = self.periods.load(Ordering::Relaxed);
|
||||||
|
if period1 == period2 {
|
||||||
|
let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap();
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut queue = self.queue.borrow(cs).borrow_mut();
|
||||||
|
|
||||||
|
if queue.schedule_wake(at, waker) {
|
||||||
|
let mut next = queue.next_expiration(self.now());
|
||||||
|
while !self.set_alarm(cs, next) {
|
||||||
|
next = queue.next_expiration(self.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
333
src/gpio/asynch.rs
Normal file
333
src/gpio/asynch.rs
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
//! # Async GPIO functionality for the Vorago GPIO peripherals.
|
||||||
|
//!
|
||||||
|
//! This module provides the [InputPinAsync] which implements
|
||||||
|
//! the [embedded_hal_async::digital::Wait] trait. These types allow for asynchronous waiting
|
||||||
|
//! on GPIO pins. Please note that this module does not specify/declare the interrupt handlers
|
||||||
|
//! which must be provided for async support to work. However, it provides the
|
||||||
|
//! [on_interrupt_for_async_gpio_for_port] generic interrupt handler. This should be called in all
|
||||||
|
//! IRQ functions which handle any GPIO interrupts with the corresponding [Port] argument.
|
||||||
|
use core::future::Future;
|
||||||
|
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
use embedded_hal_async::digital::Wait;
|
||||||
|
use portable_atomic::AtomicBool;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use crate::NUM_PORT_DEFAULT;
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use crate::{InterruptConfig, NUM_PORT_A, NUM_PORT_B};
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use super::ll::PortDoesNotSupportInterrupts;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
|
||||||
|
pub use super::ll::InterruptEdge;
|
||||||
|
use super::{
|
||||||
|
Input, Port,
|
||||||
|
ll::{LowLevelGpio, PinId},
|
||||||
|
};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PORT_A] = [const { AtomicWaker::new() }; NUM_PORT_A];
|
||||||
|
static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PORT_B] = [const { AtomicWaker::new() }; NUM_PORT_B];
|
||||||
|
static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PORT_A] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_A];
|
||||||
|
static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PORT_B] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_B];
|
||||||
|
} else {
|
||||||
|
static WAKERS_FOR_PORT_A: [AtomicWaker; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicWaker::new() }; NUM_PORT_DEFAULT];
|
||||||
|
static WAKERS_FOR_PORT_B: [AtomicWaker; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicWaker::new() }; NUM_PORT_DEFAULT];
|
||||||
|
static WAKERS_FOR_PORT_C: [AtomicWaker; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicWaker::new() }; NUM_PORT_DEFAULT];
|
||||||
|
static WAKERS_FOR_PORT_D: [AtomicWaker; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicWaker::new() }; NUM_PORT_DEFAULT];
|
||||||
|
static WAKERS_FOR_PORT_E: [AtomicWaker; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicWaker::new() }; NUM_PORT_DEFAULT];
|
||||||
|
static WAKERS_FOR_PORT_F: [AtomicWaker; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicWaker::new() }; NUM_PORT_DEFAULT];
|
||||||
|
|
||||||
|
static EDGE_DETECTION_PORT_A: [AtomicBool; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_DEFAULT];
|
||||||
|
static EDGE_DETECTION_PORT_B: [AtomicBool; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_DEFAULT];
|
||||||
|
static EDGE_DETECTION_PORT_C: [AtomicBool; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_DEFAULT];
|
||||||
|
static EDGE_DETECTION_PORT_D: [AtomicBool; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_DEFAULT];
|
||||||
|
static EDGE_DETECTION_PORT_E: [AtomicBool; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_DEFAULT];
|
||||||
|
static EDGE_DETECTION_PORT_F: [AtomicBool; NUM_PORT_DEFAULT] =
|
||||||
|
[const { AtomicBool::new(false) }; NUM_PORT_DEFAULT];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pin_group_to_waker_and_edge_detection_group(
|
||||||
|
port: Port,
|
||||||
|
) -> (&'static [AtomicWaker], &'static [AtomicBool]) {
|
||||||
|
match port {
|
||||||
|
Port::A => (WAKERS_FOR_PORT_A.as_ref(), EDGE_DETECTION_PORT_A.as_ref()),
|
||||||
|
Port::B => (WAKERS_FOR_PORT_B.as_ref(), EDGE_DETECTION_PORT_B.as_ref()),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::C => (WAKERS_FOR_PORT_C.as_ref(), EDGE_DETECTION_PORT_C.as_ref()),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::D => (WAKERS_FOR_PORT_D.as_ref(), EDGE_DETECTION_PORT_D.as_ref()),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::E => (WAKERS_FOR_PORT_E.as_ref(), EDGE_DETECTION_PORT_E.as_ref()),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::F => (WAKERS_FOR_PORT_F.as_ref(), EDGE_DETECTION_PORT_F.as_ref()),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::G => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic interrupt handler for GPIO interrupts on a specific port to support async functionalities
|
||||||
|
///
|
||||||
|
/// This function should be called in all interrupt handlers which handle any GPIO interrupts
|
||||||
|
/// matching the [Port] argument.
|
||||||
|
/// The handler will wake the corresponding wakers for the pins that triggered an interrupts
|
||||||
|
/// as well as update the static edge detection structures. This allows the pin future to complete
|
||||||
|
/// complete async operations.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn on_interrupt_for_async_gpio_for_port(port: Port) {
|
||||||
|
on_interrupt_for_async_gpio_for_port_generic(port);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn on_interrupt_for_async_gpio_for_port(
|
||||||
|
port: Port,
|
||||||
|
) -> Result<(), PortDoesNotSupportInterrupts> {
|
||||||
|
if port == Port::G {
|
||||||
|
return Err(PortDoesNotSupportInterrupts);
|
||||||
|
}
|
||||||
|
on_interrupt_for_async_gpio_for_port_generic(port);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_interrupt_for_async_gpio_for_port_generic(port: Port) {
|
||||||
|
let gpio = unsafe { port.steal_gpio() };
|
||||||
|
|
||||||
|
let irq_enb = gpio.read_irq_enable();
|
||||||
|
let edge_status = gpio.read_edge_status();
|
||||||
|
let (wakers, edge_detection) = pin_group_to_waker_and_edge_detection_group(port);
|
||||||
|
|
||||||
|
on_interrupt_for_port(irq_enb, edge_status, wakers, edge_detection);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn on_interrupt_for_port(
|
||||||
|
mut irq_enb: u32,
|
||||||
|
edge_status: u32,
|
||||||
|
wakers: &'static [AtomicWaker],
|
||||||
|
edge_detection: &'static [AtomicBool],
|
||||||
|
) {
|
||||||
|
while irq_enb != 0 {
|
||||||
|
let bit_pos = irq_enb.trailing_zeros() as usize;
|
||||||
|
let bit_mask = 1 << bit_pos;
|
||||||
|
|
||||||
|
wakers[bit_pos].wake();
|
||||||
|
|
||||||
|
if edge_status & bit_mask != 0 {
|
||||||
|
edge_detection[bit_pos].store(true, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
|
// Clear the processed bit
|
||||||
|
irq_enb &= !bit_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input pin future which implements the [Future] trait.
|
||||||
|
///
|
||||||
|
/// Generally, you want to use the [InputPinAsync] types instead of this
|
||||||
|
/// which also implements the [embedded_hal_async::digital::Wait] trait. However, access to this
|
||||||
|
/// struture is granted to allow writing custom async structures.
|
||||||
|
pub struct InputPinFuture {
|
||||||
|
id: PinId,
|
||||||
|
waker_group: &'static [AtomicWaker],
|
||||||
|
edge_detection_group: &'static [AtomicBool],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputPinFuture {
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn new_with_input_pin(pin: &mut Input, irq: pac::Interrupt, edge: InterruptEdge) -> Self {
|
||||||
|
let (waker_group, edge_detection_group) =
|
||||||
|
pin_group_to_waker_and_edge_detection_group(pin.id().port());
|
||||||
|
edge_detection_group[pin.id().offset()].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
pin.configure_edge_interrupt(edge);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin.enable_interrupt(InterruptConfig::new(irq, true, true));
|
||||||
|
Self {
|
||||||
|
id: pin.id(),
|
||||||
|
waker_group,
|
||||||
|
edge_detection_group,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn new_with_input_pin(
|
||||||
|
pin: &mut Input,
|
||||||
|
edge: InterruptEdge,
|
||||||
|
) -> Result<Self, PortDoesNotSupportInterrupts> {
|
||||||
|
let (waker_group, edge_detection_group) =
|
||||||
|
pin_group_to_waker_and_edge_detection_group(pin.id().port());
|
||||||
|
pin.configure_edge_interrupt(edge);
|
||||||
|
pin.enable_interrupt(true)?;
|
||||||
|
Ok(Self {
|
||||||
|
id: pin.id(),
|
||||||
|
waker_group,
|
||||||
|
edge_detection_group,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InputPinFuture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut ll = LowLevelGpio::new(self.id);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
ll.disable_interrupt(false);
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
ll.disable_interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for InputPinFuture {
|
||||||
|
type Output = ();
|
||||||
|
fn poll(
|
||||||
|
self: core::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut core::task::Context<'_>,
|
||||||
|
) -> core::task::Poll<Self::Output> {
|
||||||
|
let idx = self.id.offset();
|
||||||
|
self.waker_group[idx].register(cx.waker());
|
||||||
|
if self.edge_detection_group[idx].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||||
|
return core::task::Poll::Ready(());
|
||||||
|
}
|
||||||
|
core::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct InputPinAsync {
|
||||||
|
pin: Input,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
irq: va108xx::Interrupt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputPinAsync {
|
||||||
|
/// Create a new asynchronous input pin from an [Input] pin. The interrupt ID to be used must be
|
||||||
|
/// passed as well and is used to route and enable the interrupt.
|
||||||
|
///
|
||||||
|
/// Please note that the interrupt handler itself must be provided by the user and the
|
||||||
|
/// generic [on_interrupt_for_async_gpio_for_port] function must be called inside that function
|
||||||
|
/// for the asynchronous functionality to work.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn new(pin: Input, irq: va108xx::Interrupt) -> Self {
|
||||||
|
Self { pin, irq }
|
||||||
|
}
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn new(pin: Input) -> Result<Self, PortDoesNotSupportInterrupts> {
|
||||||
|
if pin.id().port() == Port::G {
|
||||||
|
return Err(PortDoesNotSupportInterrupts);
|
||||||
|
}
|
||||||
|
Ok(Self { pin })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously wait until the pin is high.
|
||||||
|
///
|
||||||
|
/// This returns immediately if the pin is already high.
|
||||||
|
pub async fn wait_for_high(&mut self) {
|
||||||
|
// Unwrap okay, checked pin in constructor.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
let fut =
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh);
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
let fut =
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, InterruptEdge::LowToHigh).unwrap();
|
||||||
|
if self.pin.is_high() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fut.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously wait until the pin is low.
|
||||||
|
///
|
||||||
|
/// This returns immediately if the pin is already high.
|
||||||
|
pub async fn wait_for_low(&mut self) {
|
||||||
|
// Unwrap okay, checked pin in constructor.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
let fut =
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow);
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
let fut =
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, InterruptEdge::HighToLow).unwrap();
|
||||||
|
if self.pin.is_low() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fut.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously wait until the pin sees a falling edge.
|
||||||
|
pub async fn wait_for_falling_edge(&mut self) {
|
||||||
|
// Unwrap okay, checked pin in constructor.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::HighToLow).await;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, InterruptEdge::HighToLow)
|
||||||
|
.unwrap()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously wait until the pin sees a rising edge.
|
||||||
|
pub async fn wait_for_rising_edge(&mut self) {
|
||||||
|
// Unwrap okay, checked pin in constructor.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::LowToHigh).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asynchronously wait until the pin sees any edge (either rising or falling).
|
||||||
|
pub async fn wait_for_any_edge(&mut self) {
|
||||||
|
// Unwrap okay, checked pin in constructor.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, self.irq, InterruptEdge::BothEdges).await;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
InputPinFuture::new_with_input_pin(&mut self.pin, InterruptEdge::BothEdges)
|
||||||
|
.unwrap()
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self) -> Input {
|
||||||
|
self.pin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::ErrorType for InputPinAsync {
|
||||||
|
type Error = core::convert::Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wait for InputPinAsync {
|
||||||
|
async fn wait_for_high(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.wait_for_high().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_low(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.wait_for_low().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.wait_for_rising_edge().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.wait_for_falling_edge().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.wait_for_any_edge().await;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
584
src/gpio/ll.rs
Normal file
584
src/gpio/ll.rs
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
pub use embedded_hal::digital::PinState;
|
||||||
|
|
||||||
|
use crate::ioconfig::FilterClkSel;
|
||||||
|
use crate::ioconfig::FilterType;
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use crate::{PeripheralSelect, sysconfig::enable_peripheral_clock};
|
||||||
|
|
||||||
|
pub use crate::InvalidOffsetError;
|
||||||
|
pub use crate::Port;
|
||||||
|
pub use crate::ioconfig::regs::Pull;
|
||||||
|
use crate::ioconfig::regs::{FunSel, IoConfig, MmioIoConfig};
|
||||||
|
|
||||||
|
use super::Pin;
|
||||||
|
use super::PinIdProvider;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum InterruptEdge {
|
||||||
|
HighToLow,
|
||||||
|
LowToHigh,
|
||||||
|
BothEdges,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum InterruptLevel {
|
||||||
|
Low = 0,
|
||||||
|
High = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pin identifier for all physical pins exposed by Vorago MCUs.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct PinId {
|
||||||
|
port: Port,
|
||||||
|
/// Offset within the port.
|
||||||
|
offset: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[error("port G does not support interrupts")]
|
||||||
|
pub struct PortDoesNotSupportInterrupts;
|
||||||
|
|
||||||
|
impl PinId {
|
||||||
|
/// Unchecked constructor which panics on invalid offsets.
|
||||||
|
pub const fn new_unchecked(port: Port, offset: usize) -> Self {
|
||||||
|
if offset >= port.max_offset() {
|
||||||
|
panic!("Pin ID construction: offset is out of range");
|
||||||
|
}
|
||||||
|
PinId {
|
||||||
|
port,
|
||||||
|
offset: offset as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new(port: Port, offset: usize) -> Result<Self, InvalidOffsetError> {
|
||||||
|
if offset >= port.max_offset() {
|
||||||
|
return Err(InvalidOffsetError { offset, port });
|
||||||
|
}
|
||||||
|
Ok(PinId {
|
||||||
|
port,
|
||||||
|
offset: offset as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn port(&self) -> Port {
|
||||||
|
self.port
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn offset(&self) -> usize {
|
||||||
|
self.offset as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function panics if the port is [Port::G].
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn irq(&self) -> Result<va416xx::Interrupt, PortDoesNotSupportInterrupts> {
|
||||||
|
if self.port() == Port::G {
|
||||||
|
return Err(PortDoesNotSupportInterrupts);
|
||||||
|
}
|
||||||
|
Ok(self.irq_unchecked())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function panics if the port is [Port::G].
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub const fn irq_unchecked(&self) -> va416xx::Interrupt {
|
||||||
|
match self.port() {
|
||||||
|
Port::A => match self.offset() {
|
||||||
|
0 => va416xx::Interrupt::PORTA0,
|
||||||
|
1 => va416xx::Interrupt::PORTA1,
|
||||||
|
2 => va416xx::Interrupt::PORTA2,
|
||||||
|
3 => va416xx::Interrupt::PORTA3,
|
||||||
|
4 => va416xx::Interrupt::PORTA4,
|
||||||
|
5 => va416xx::Interrupt::PORTA5,
|
||||||
|
6 => va416xx::Interrupt::PORTA6,
|
||||||
|
7 => va416xx::Interrupt::PORTA7,
|
||||||
|
8 => va416xx::Interrupt::PORTA8,
|
||||||
|
9 => va416xx::Interrupt::PORTA9,
|
||||||
|
10 => va416xx::Interrupt::PORTA10,
|
||||||
|
11 => va416xx::Interrupt::PORTA11,
|
||||||
|
12 => va416xx::Interrupt::PORTA12,
|
||||||
|
13 => va416xx::Interrupt::PORTA13,
|
||||||
|
14 => va416xx::Interrupt::PORTA14,
|
||||||
|
15 => va416xx::Interrupt::PORTA15,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Port::B => match self.offset() {
|
||||||
|
0 => va416xx::Interrupt::PORTB0,
|
||||||
|
1 => va416xx::Interrupt::PORTB1,
|
||||||
|
2 => va416xx::Interrupt::PORTB2,
|
||||||
|
3 => va416xx::Interrupt::PORTB3,
|
||||||
|
4 => va416xx::Interrupt::PORTB4,
|
||||||
|
5 => va416xx::Interrupt::PORTB5,
|
||||||
|
6 => va416xx::Interrupt::PORTB6,
|
||||||
|
7 => va416xx::Interrupt::PORTB7,
|
||||||
|
8 => va416xx::Interrupt::PORTB8,
|
||||||
|
9 => va416xx::Interrupt::PORTB9,
|
||||||
|
10 => va416xx::Interrupt::PORTB10,
|
||||||
|
11 => va416xx::Interrupt::PORTB11,
|
||||||
|
12 => va416xx::Interrupt::PORTB12,
|
||||||
|
13 => va416xx::Interrupt::PORTB13,
|
||||||
|
14 => va416xx::Interrupt::PORTB14,
|
||||||
|
15 => va416xx::Interrupt::PORTB15,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Port::C => match self.offset() {
|
||||||
|
0 => va416xx::Interrupt::PORTC0,
|
||||||
|
1 => va416xx::Interrupt::PORTC1,
|
||||||
|
2 => va416xx::Interrupt::PORTC2,
|
||||||
|
3 => va416xx::Interrupt::PORTC3,
|
||||||
|
4 => va416xx::Interrupt::PORTC4,
|
||||||
|
5 => va416xx::Interrupt::PORTC5,
|
||||||
|
6 => va416xx::Interrupt::PORTC6,
|
||||||
|
7 => va416xx::Interrupt::PORTC7,
|
||||||
|
8 => va416xx::Interrupt::PORTC8,
|
||||||
|
9 => va416xx::Interrupt::PORTC9,
|
||||||
|
10 => va416xx::Interrupt::PORTC10,
|
||||||
|
11 => va416xx::Interrupt::PORTC11,
|
||||||
|
12 => va416xx::Interrupt::PORTC12,
|
||||||
|
13 => va416xx::Interrupt::PORTC13,
|
||||||
|
14 => va416xx::Interrupt::PORTC14,
|
||||||
|
15 => va416xx::Interrupt::PORTC15,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Port::D => match self.offset() {
|
||||||
|
0 => va416xx::Interrupt::PORTD0,
|
||||||
|
1 => va416xx::Interrupt::PORTD1,
|
||||||
|
2 => va416xx::Interrupt::PORTD2,
|
||||||
|
3 => va416xx::Interrupt::PORTD3,
|
||||||
|
4 => va416xx::Interrupt::PORTD4,
|
||||||
|
5 => va416xx::Interrupt::PORTD5,
|
||||||
|
6 => va416xx::Interrupt::PORTD6,
|
||||||
|
7 => va416xx::Interrupt::PORTD7,
|
||||||
|
8 => va416xx::Interrupt::PORTD8,
|
||||||
|
9 => va416xx::Interrupt::PORTD9,
|
||||||
|
10 => va416xx::Interrupt::PORTD10,
|
||||||
|
11 => va416xx::Interrupt::PORTD11,
|
||||||
|
12 => va416xx::Interrupt::PORTD12,
|
||||||
|
13 => va416xx::Interrupt::PORTD13,
|
||||||
|
14 => va416xx::Interrupt::PORTD14,
|
||||||
|
15 => va416xx::Interrupt::PORTD15,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Port::E => match self.offset() {
|
||||||
|
0 => va416xx::Interrupt::PORTE0,
|
||||||
|
1 => va416xx::Interrupt::PORTE1,
|
||||||
|
2 => va416xx::Interrupt::PORTE2,
|
||||||
|
3 => va416xx::Interrupt::PORTE3,
|
||||||
|
4 => va416xx::Interrupt::PORTE4,
|
||||||
|
5 => va416xx::Interrupt::PORTE5,
|
||||||
|
6 => va416xx::Interrupt::PORTE6,
|
||||||
|
7 => va416xx::Interrupt::PORTE7,
|
||||||
|
8 => va416xx::Interrupt::PORTE8,
|
||||||
|
9 => va416xx::Interrupt::PORTE9,
|
||||||
|
10 => va416xx::Interrupt::PORTE10,
|
||||||
|
11 => va416xx::Interrupt::PORTE11,
|
||||||
|
12 => va416xx::Interrupt::PORTE12,
|
||||||
|
13 => va416xx::Interrupt::PORTE13,
|
||||||
|
14 => va416xx::Interrupt::PORTE14,
|
||||||
|
15 => va416xx::Interrupt::PORTE15,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Port::F => match self.offset() {
|
||||||
|
0 => va416xx::Interrupt::PORTF0,
|
||||||
|
1 => va416xx::Interrupt::PORTF1,
|
||||||
|
2 => va416xx::Interrupt::PORTF2,
|
||||||
|
3 => va416xx::Interrupt::PORTF3,
|
||||||
|
4 => va416xx::Interrupt::PORTF4,
|
||||||
|
5 => va416xx::Interrupt::PORTF5,
|
||||||
|
6 => va416xx::Interrupt::PORTF6,
|
||||||
|
7 => va416xx::Interrupt::PORTF7,
|
||||||
|
8 => va416xx::Interrupt::PORTF8,
|
||||||
|
9 => va416xx::Interrupt::PORTF9,
|
||||||
|
10 => va416xx::Interrupt::PORTF10,
|
||||||
|
11 => va416xx::Interrupt::PORTF11,
|
||||||
|
12 => va416xx::Interrupt::PORTF12,
|
||||||
|
13 => va416xx::Interrupt::PORTF13,
|
||||||
|
14 => va416xx::Interrupt::PORTF14,
|
||||||
|
15 => va416xx::Interrupt::PORTF15,
|
||||||
|
_ => unreachable!(),
|
||||||
|
},
|
||||||
|
Port::G => panic!("port G does not have interrupts"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Low-level driver structure for GPIO pins.
|
||||||
|
pub struct LowLevelGpio {
|
||||||
|
gpio: super::regs::MmioGpio<'static>,
|
||||||
|
ioconfig: MmioIoConfig<'static>,
|
||||||
|
id: PinId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Debug for LowLevelGpio {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.debug_struct("LowLevelGpio")
|
||||||
|
.field("gpio", &self.gpio.port())
|
||||||
|
.field("id", &self.id)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LowLevelGpio {
|
||||||
|
/// Create a new low-level GPIO pin instance from a given [Pin].
|
||||||
|
///
|
||||||
|
/// Can be used for performing resource management of the [Pin]s.
|
||||||
|
pub fn new_with_pin<I: PinIdProvider>(_pin: Pin<I>) -> Self {
|
||||||
|
Self::new(I::ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new low-level GPIO pin instance using only the [PinId].
|
||||||
|
pub fn new(id: PinId) -> Self {
|
||||||
|
LowLevelGpio {
|
||||||
|
gpio: super::regs::Gpio::new_mmio(id.port),
|
||||||
|
ioconfig: IoConfig::new_mmio(),
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> PinId {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn port(&self) -> Port {
|
||||||
|
self.id.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.id.offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_as_input_floating(&mut self) {
|
||||||
|
self.ioconfig.modify_pin_config(self.id, |mut config| {
|
||||||
|
config.set_funsel(FunSel::Sel0);
|
||||||
|
config.set_io_disable(false);
|
||||||
|
config.set_invert_input(false);
|
||||||
|
config.set_open_drain(false);
|
||||||
|
config.set_pull_enable(false);
|
||||||
|
config.set_pull_when_output_active(false);
|
||||||
|
config.set_invert_output(false);
|
||||||
|
config.set_input_enable_when_output(false);
|
||||||
|
config
|
||||||
|
});
|
||||||
|
self.gpio.modify_dir(|mut dir| {
|
||||||
|
dir &= !(1 << self.id.offset());
|
||||||
|
dir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_as_input_with_pull(&mut self, pull: Pull) {
|
||||||
|
self.ioconfig.modify_pin_config(self.id, |mut config| {
|
||||||
|
config.set_funsel(FunSel::Sel0);
|
||||||
|
config.set_io_disable(false);
|
||||||
|
config.set_invert_input(false);
|
||||||
|
config.set_open_drain(false);
|
||||||
|
config.set_pull_enable(true);
|
||||||
|
config.set_pull_dir(pull);
|
||||||
|
config.set_pull_when_output_active(false);
|
||||||
|
config.set_invert_output(false);
|
||||||
|
config.set_input_enable_when_output(false);
|
||||||
|
config
|
||||||
|
});
|
||||||
|
self.gpio.modify_dir(|mut dir| {
|
||||||
|
dir &= !(1 << self.id.offset());
|
||||||
|
dir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_as_output_push_pull(&mut self, init_level: PinState) {
|
||||||
|
self.ioconfig.modify_pin_config(self.id, |mut config| {
|
||||||
|
config.set_funsel(FunSel::Sel0);
|
||||||
|
config.set_io_disable(false);
|
||||||
|
config.set_invert_input(false);
|
||||||
|
config.set_open_drain(false);
|
||||||
|
config.set_pull_enable(false);
|
||||||
|
config.set_pull_when_output_active(false);
|
||||||
|
config.set_invert_output(false);
|
||||||
|
config.set_input_enable_when_output(true);
|
||||||
|
config
|
||||||
|
});
|
||||||
|
match init_level {
|
||||||
|
PinState::Low => self.gpio.write_clr_out(self.mask_32()),
|
||||||
|
PinState::High => self.gpio.write_set_out(self.mask_32()),
|
||||||
|
}
|
||||||
|
self.gpio.modify_dir(|mut dir| {
|
||||||
|
dir |= 1 << self.id.offset();
|
||||||
|
dir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_as_output_open_drain(&mut self, init_level: PinState) {
|
||||||
|
self.ioconfig.modify_pin_config(self.id, |mut config| {
|
||||||
|
config.set_funsel(FunSel::Sel0);
|
||||||
|
config.set_io_disable(false);
|
||||||
|
config.set_invert_input(false);
|
||||||
|
config.set_open_drain(true);
|
||||||
|
config.set_pull_enable(true);
|
||||||
|
config.set_pull_dir(Pull::Up);
|
||||||
|
config.set_pull_when_output_active(false);
|
||||||
|
config.set_invert_output(false);
|
||||||
|
config.set_input_enable_when_output(true);
|
||||||
|
config
|
||||||
|
});
|
||||||
|
let mask32 = self.mask_32();
|
||||||
|
match init_level {
|
||||||
|
PinState::Low => self.gpio.write_clr_out(mask32),
|
||||||
|
PinState::High => self.gpio.write_set_out(mask32),
|
||||||
|
}
|
||||||
|
self.gpio.modify_dir(|mut dir| {
|
||||||
|
dir |= mask32;
|
||||||
|
dir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_as_peripheral_pin(&mut self, fun_sel: FunSel, pull: Option<Pull>) {
|
||||||
|
self.ioconfig.modify_pin_config(self.id, |mut config| {
|
||||||
|
config.set_funsel(fun_sel);
|
||||||
|
config.set_io_disable(false);
|
||||||
|
config.set_invert_input(false);
|
||||||
|
config.set_open_drain(false);
|
||||||
|
config.set_pull_enable(pull.is_some());
|
||||||
|
config.set_pull_dir(pull.unwrap_or(Pull::Up));
|
||||||
|
config.set_invert_output(false);
|
||||||
|
config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_high(&self) -> bool {
|
||||||
|
(self.gpio.read_data_in() >> self.offset()) & 1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_low(&self) -> bool {
|
||||||
|
!self.is_high()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_high(&mut self) {
|
||||||
|
self.gpio.write_set_out(self.mask_32());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_low(&mut self) {
|
||||||
|
self.gpio.write_clr_out(self.mask_32());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_set_high(&self) -> bool {
|
||||||
|
(self.gpio.read_data_out() >> self.offset()) & 1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_set_low(&self) -> bool {
|
||||||
|
!self.is_set_high()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn toggle(&mut self) {
|
||||||
|
self.gpio.write_tog_out(self.mask_32());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) {
|
||||||
|
if irq_cfg.route {
|
||||||
|
self.configure_irqsel(irq_cfg.id);
|
||||||
|
}
|
||||||
|
if irq_cfg.enable_in_nvic {
|
||||||
|
unsafe { crate::enable_nvic_interrupt(irq_cfg.id) };
|
||||||
|
}
|
||||||
|
self.gpio.modify_irq_enable(|mut value| {
|
||||||
|
value |= 1 << self.id.offset;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn enable_interrupt(
|
||||||
|
&mut self,
|
||||||
|
enable_in_nvic: bool,
|
||||||
|
) -> Result<(), PortDoesNotSupportInterrupts> {
|
||||||
|
if enable_in_nvic {
|
||||||
|
unsafe { crate::enable_nvic_interrupt(self.id().irq_unchecked()) };
|
||||||
|
}
|
||||||
|
self.gpio.modify_irq_enable(|mut value| {
|
||||||
|
value |= 1 << self.id.offset;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn disable_interrupt(&mut self, reset_irqsel: bool) {
|
||||||
|
if reset_irqsel {
|
||||||
|
self.reset_irqsel();
|
||||||
|
}
|
||||||
|
// We only manipulate our own bit.
|
||||||
|
self.gpio.modify_irq_enable(|mut value| {
|
||||||
|
value &= !(1 << self.id.offset);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn disable_interrupt(&mut self) {
|
||||||
|
self.gpio.modify_irq_enable(|mut value| {
|
||||||
|
value &= !(1 << self.id.offset);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure
|
||||||
|
/// When using edge mode, it is possible to generate interrupts on both edges as well
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_edge_interrupt(&mut self, edge_type: InterruptEdge) {
|
||||||
|
let mask32 = self.mask_32();
|
||||||
|
self.gpio.modify_irq_sen(|mut value| {
|
||||||
|
value &= !mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
match edge_type {
|
||||||
|
InterruptEdge::HighToLow => {
|
||||||
|
self.gpio.modify_irq_evt(|mut value| {
|
||||||
|
value &= !mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InterruptEdge::LowToHigh => {
|
||||||
|
self.gpio.modify_irq_evt(|mut value| {
|
||||||
|
value |= mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InterruptEdge::BothEdges => {
|
||||||
|
self.gpio.modify_irq_edge(|mut value| {
|
||||||
|
value |= mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure which edge or level type triggers an interrupt
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_level_interrupt(&mut self, level: InterruptLevel) {
|
||||||
|
let mask32 = self.mask_32();
|
||||||
|
self.gpio.modify_irq_sen(|mut value| {
|
||||||
|
value |= mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
if level == InterruptLevel::Low {
|
||||||
|
self.gpio.modify_irq_evt(|mut value| {
|
||||||
|
value &= !mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.gpio.modify_irq_evt(|mut value| {
|
||||||
|
value |= mask32;
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for input pins
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
|
||||||
|
self.ioconfig.modify_pin_config(self.id, |mut config| {
|
||||||
|
config.set_filter_type(filter);
|
||||||
|
config.set_filter_clk_sel(clksel);
|
||||||
|
config
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for output pins.
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) {
|
||||||
|
self.gpio.modify_pulse(|mut value| {
|
||||||
|
if enable {
|
||||||
|
value |= 1 << self.id.offset;
|
||||||
|
} else {
|
||||||
|
value &= !(1 << self.id.offset);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
});
|
||||||
|
self.gpio.modify_pulsebase(|mut value| {
|
||||||
|
if default_state == PinState::High {
|
||||||
|
value |= 1 << self.id.offset;
|
||||||
|
} else {
|
||||||
|
value &= !(1 << self.id.offset);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Only useful for output pins
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
|
||||||
|
self.gpio.modify_delay1(|mut value| {
|
||||||
|
if delay_1 {
|
||||||
|
value |= 1 << self.id.offset;
|
||||||
|
} else {
|
||||||
|
value &= !(1 << self.id.offset);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
});
|
||||||
|
self.gpio.modify_delay2(|mut value| {
|
||||||
|
if delay_2 {
|
||||||
|
value |= 1 << self.id.offset;
|
||||||
|
} else {
|
||||||
|
value &= !(1 << self.id.offset);
|
||||||
|
}
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
/// Configure the IRQSEL peripheral for this particular pin with the given interrupt ID.
|
||||||
|
pub fn configure_irqsel(&mut self, id: va108xx::Interrupt) {
|
||||||
|
let irqsel = unsafe { va108xx::Irqsel::steal() };
|
||||||
|
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||||
|
match self.id().port() {
|
||||||
|
// Set the correct interrupt number in the IRQSEL register
|
||||||
|
super::Port::A => {
|
||||||
|
irqsel
|
||||||
|
.porta0(self.id().offset())
|
||||||
|
.write(|w| unsafe { w.bits(id as u32) });
|
||||||
|
}
|
||||||
|
super::Port::B => {
|
||||||
|
irqsel
|
||||||
|
.portb0(self.id().offset())
|
||||||
|
.write(|w| unsafe { w.bits(id as u32) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
/// Reset the IRQSEL peripheral value for this particular pin.
|
||||||
|
pub fn reset_irqsel(&mut self) {
|
||||||
|
let irqsel = unsafe { va108xx::Irqsel::steal() };
|
||||||
|
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||||
|
match self.id().port() {
|
||||||
|
// Set the correct interrupt number in the IRQSEL register
|
||||||
|
super::Port::A => {
|
||||||
|
irqsel
|
||||||
|
.porta0(self.id().offset())
|
||||||
|
.write(|w| unsafe { w.bits(u32::MAX) });
|
||||||
|
}
|
||||||
|
super::Port::B => {
|
||||||
|
irqsel
|
||||||
|
.portb0(self.id().offset())
|
||||||
|
.write(|w| unsafe { w.bits(u32::MAX) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub const fn mask_32(&self) -> u32 {
|
||||||
|
1 << self.id.offset()
|
||||||
|
}
|
||||||
|
}
|
394
src/gpio/mod.rs
Normal file
394
src/gpio/mod.rs
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
//! GPIO support module.
|
||||||
|
use core::convert::Infallible;
|
||||||
|
|
||||||
|
pub use crate::ioconfig::{regs::FunSel, FilterClkSel, FilterType};
|
||||||
|
pub use embedded_hal::digital::PinState;
|
||||||
|
pub use ll::{InterruptEdge, InterruptLevel, PinId, Port, Pull};
|
||||||
|
|
||||||
|
pub mod asynch;
|
||||||
|
pub mod ll;
|
||||||
|
pub mod regs;
|
||||||
|
|
||||||
|
/// Trait implemented by data structures assocaited with pin identifiacation.
|
||||||
|
pub trait PinIdProvider {
|
||||||
|
const ID: ll::PinId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Primary Pin structure for the physical pins exposed by Vorago MCUs.
|
||||||
|
///
|
||||||
|
/// This pin structure is only used for resource management and does not do anything on its
|
||||||
|
/// own.
|
||||||
|
pub struct Pin<I: PinIdProvider> {
|
||||||
|
phantom: core::marker::PhantomData<I>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinIdProvider> Pin<I> {
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const fn __new() -> Self {
|
||||||
|
Self {
|
||||||
|
phantom: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new pin instance.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This circumvents ownership rules of the HAL and allows creating multiple instances
|
||||||
|
/// of the same pin.
|
||||||
|
pub const unsafe fn steal() -> Self {
|
||||||
|
Self::__new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push-Pull output pin.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Output(ll::LowLevelGpio);
|
||||||
|
|
||||||
|
impl Output {
|
||||||
|
pub fn new<I: PinIdProvider>(_pin: Pin<I>, init_level: PinState) -> Self {
|
||||||
|
let mut ll = ll::LowLevelGpio::new(I::ID);
|
||||||
|
ll.configure_as_output_push_pull(init_level);
|
||||||
|
Output(ll)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn port(&self) -> Port {
|
||||||
|
self.0.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.0.offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_high(&mut self) {
|
||||||
|
self.0.set_high();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_low(&mut self) {
|
||||||
|
self.0.set_low();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_set_high(&self) -> bool {
|
||||||
|
self.0.is_set_high()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_set_low(&self) -> bool {
|
||||||
|
self.0.is_set_low()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle pin output with dedicated HW feature.
|
||||||
|
#[inline]
|
||||||
|
pub fn toggle(&mut self) {
|
||||||
|
self.0.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_pulse_mode(&mut self, enable: bool, default_state: PinState) {
|
||||||
|
self.0.configure_pulse_mode(enable, default_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
|
||||||
|
self.0.configure_delay(delay_1, delay_2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::ErrorType for Output {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::OutputPin for Output {
|
||||||
|
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.0.set_low();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.0.set_high();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::StatefulOutputPin for Output {
|
||||||
|
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.0.is_set_high())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.0.is_set_low())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle pin output with dedicated HW feature.
|
||||||
|
fn toggle(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.0.toggle();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input pin.
|
||||||
|
///
|
||||||
|
/// Can be created as a floating input pin or as an input pin with pull-up or pull-down.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Input(ll::LowLevelGpio);
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn new_floating<I: PinIdProvider>(_pin: Pin<I>) -> Self {
|
||||||
|
let mut ll = ll::LowLevelGpio::new(I::ID);
|
||||||
|
ll.configure_as_input_floating();
|
||||||
|
Input(ll)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_pull<I: PinIdProvider>(_pin: Pin<I>, pull: Pull) -> Self {
|
||||||
|
let mut ll = ll::LowLevelGpio::new(I::ID);
|
||||||
|
ll.configure_as_input_with_pull(pull);
|
||||||
|
Input(ll)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn id(&self) -> PinId {
|
||||||
|
self.0.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
#[inline]
|
||||||
|
pub fn enable_interrupt(&mut self, irq_cfg: crate::InterruptConfig) {
|
||||||
|
self.0.enable_interrupt(irq_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[inline]
|
||||||
|
pub fn enable_interrupt(
|
||||||
|
&mut self,
|
||||||
|
enable_in_nvic: bool,
|
||||||
|
) -> Result<(), ll::PortDoesNotSupportInterrupts> {
|
||||||
|
self.0.enable_interrupt(enable_in_nvic)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_edge_interrupt(&mut self, edge: InterruptEdge) {
|
||||||
|
self.0.configure_edge_interrupt(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_level_interrupt(&mut self, edge: InterruptLevel) {
|
||||||
|
self.0.configure_level_interrupt(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_delay(&mut self, delay_1: bool, delay_2: bool) {
|
||||||
|
self.0.configure_delay(delay_1, delay_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn configure_filter_type(&mut self, filter: FilterType, clksel: FilterClkSel) {
|
||||||
|
self.0.configure_filter_type(filter, clksel);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_low(&self) -> bool {
|
||||||
|
self.0.is_low()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_high(&self) -> bool {
|
||||||
|
self.0.is_high()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::ErrorType for Input {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::InputPin for Input {
|
||||||
|
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.0.is_low())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.0.is_high())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum PinMode {
|
||||||
|
InputFloating,
|
||||||
|
InputWithPull(Pull),
|
||||||
|
OutputPushPull,
|
||||||
|
OutputOpenDrain,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinMode {
|
||||||
|
pub fn is_input(&self) -> bool {
|
||||||
|
matches!(self, PinMode::InputFloating | PinMode::InputWithPull(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_output(&self) -> bool {
|
||||||
|
!self.is_input()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Flex pin abstraction which can be dynamically re-configured.
|
||||||
|
///
|
||||||
|
/// The following functions can be configured at run-time:
|
||||||
|
///
|
||||||
|
/// - Input Floating
|
||||||
|
/// - Input with Pull-Up
|
||||||
|
/// - Output Push-Pull
|
||||||
|
/// - Output Open-Drain.
|
||||||
|
///
|
||||||
|
/// Flex pins are always floating input pins after construction.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Flex {
|
||||||
|
ll: ll::LowLevelGpio,
|
||||||
|
mode: PinMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Flex {
|
||||||
|
pub fn new<I: PinIdProvider>(_pin: Pin<I>) -> Self {
|
||||||
|
let mut ll = ll::LowLevelGpio::new(I::ID);
|
||||||
|
ll.configure_as_input_floating();
|
||||||
|
Flex {
|
||||||
|
ll,
|
||||||
|
mode: PinMode::InputFloating,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn port(&self) -> Port {
|
||||||
|
self.ll.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.ll.offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the input state of the pin, regardless of configured mode.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_low(&self) -> bool {
|
||||||
|
self.ll.is_low()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the input state of the pin, regardless of configured mode.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_high(&self) -> bool {
|
||||||
|
self.ll.is_high()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the pin is configured as an input pin, this function does nothing.
|
||||||
|
#[inline]
|
||||||
|
pub fn set_low(&mut self) {
|
||||||
|
if !self.mode.is_input() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.ll.set_low();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the pin is configured as an input pin, this function does nothing.
|
||||||
|
#[inline]
|
||||||
|
pub fn set_high(&mut self) {
|
||||||
|
if !self.mode.is_input() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.ll.set_high();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::ErrorType for Flex {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::InputPin for Flex {
|
||||||
|
/// Reads the input state of the pin, regardless of configured mode.
|
||||||
|
fn is_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.ll.is_low())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the input state of the pin, regardless of configured mode.
|
||||||
|
fn is_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.ll.is_high())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::OutputPin for Flex {
|
||||||
|
/// If the pin is configured as an input pin, this function does nothing.
|
||||||
|
fn set_low(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.set_low();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the pin is configured as an input pin, this function does nothing.
|
||||||
|
fn set_high(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.set_high();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::digital::StatefulOutputPin for Flex {
|
||||||
|
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
|
||||||
|
/// of this function is undefined.
|
||||||
|
fn is_set_high(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.ll.is_set_high())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
|
||||||
|
/// of this function is undefined.
|
||||||
|
fn is_set_low(&mut self) -> Result<bool, Self::Error> {
|
||||||
|
Ok(self.ll.is_set_low())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggle pin output.
|
||||||
|
///
|
||||||
|
/// If the pin is not configured as a stateful output pin like Output Push-Pull, the result
|
||||||
|
/// of this function is undefined.
|
||||||
|
fn toggle(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.ll.toggle();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IO peripheral pin structure.
|
||||||
|
///
|
||||||
|
/// Can be used to configure pins as IO peripheral pins.
|
||||||
|
pub struct IoPeriphPin {
|
||||||
|
ll: ll::LowLevelGpio,
|
||||||
|
fun_sel: FunSel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoPeriphPin {
|
||||||
|
pub fn new_with_pin<I: PinIdProvider>(
|
||||||
|
_pin: Pin<I>,
|
||||||
|
fun_sel: FunSel,
|
||||||
|
pull: Option<Pull>,
|
||||||
|
) -> Self {
|
||||||
|
let mut ll = ll::LowLevelGpio::new(I::ID);
|
||||||
|
ll.configure_as_peripheral_pin(fun_sel, pull);
|
||||||
|
IoPeriphPin { ll, fun_sel }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(pin_id: PinId, fun_sel: FunSel, pull: Option<Pull>) -> Self {
|
||||||
|
let mut ll = ll::LowLevelGpio::new(pin_id);
|
||||||
|
ll.configure_as_peripheral_pin(fun_sel, pull);
|
||||||
|
IoPeriphPin { ll, fun_sel }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn port(&self) -> Port {
|
||||||
|
self.ll.port()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.ll.offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fun_sel(&self) -> FunSel {
|
||||||
|
self.fun_sel
|
||||||
|
}
|
||||||
|
}
|
126
src/gpio/regs.rs
Normal file
126
src/gpio/regs.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use crate::Port;
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
/// PORT A base address.
|
||||||
|
pub const GPIO_0_BASE: usize = 0x5000_0000;
|
||||||
|
/// PORT B base address.
|
||||||
|
pub const GPIO_1_BASE: usize = 0x5000_1000;
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
/// PORT A base address.
|
||||||
|
pub const GPIO_0_BASE: usize = 0x4001_2000;
|
||||||
|
/// PORT B base address.
|
||||||
|
pub const GPIO_1_BASE: usize = 0x4001_2400;
|
||||||
|
/// PORT C base address.
|
||||||
|
pub const GPIO_2_BASE: usize = 0x4001_2800;
|
||||||
|
/// PORT D base address.
|
||||||
|
pub const GPIO_3_BASE: usize = 0x4001_2C00;
|
||||||
|
/// PORT E base address.
|
||||||
|
pub const GPIO_4_BASE: usize = 0x4001_3000;
|
||||||
|
/// PORT F base address.
|
||||||
|
pub const GPIO_5_BASE: usize = 0x4001_3400;
|
||||||
|
/// PORT G base address.
|
||||||
|
pub const GPIO_6_BASE: usize = 0x4001_3800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[mmio(no_ctors)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Gpio {
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
data_in: u32,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
data_in_raw: u32,
|
||||||
|
data_out: u32,
|
||||||
|
data_out_raw: u32,
|
||||||
|
#[mmio(Write)]
|
||||||
|
set_out: u32,
|
||||||
|
#[mmio(Write)]
|
||||||
|
clr_out: u32,
|
||||||
|
#[mmio(Write)]
|
||||||
|
tog_out: u32,
|
||||||
|
data_mask: u32,
|
||||||
|
/// Direction bits. 1 for output, 0 for input.
|
||||||
|
dir: u32,
|
||||||
|
pulse: u32,
|
||||||
|
pulsebase: u32,
|
||||||
|
delay1: u32,
|
||||||
|
delay2: u32,
|
||||||
|
irq_sen: u32,
|
||||||
|
irq_edge: u32,
|
||||||
|
irq_evt: u32,
|
||||||
|
irq_enable: u32,
|
||||||
|
/// Raw interrupt status. This register is not latched and may not indicated edge sensitive
|
||||||
|
/// events.
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_raw: u32,
|
||||||
|
/// Read-only register which shows enabled and active interrupts. Called IRQ_end by Vorago.
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_status: u32,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
edge_status: u32,
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
_reserved: [u32; 0x3eb],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
_reserved: [u32; 0xeb],
|
||||||
|
|
||||||
|
/// Peripheral ID. Vorago 1x reset value: 0x0040_07e1. Vorago 4x reset value: 0x0210_07E9.
|
||||||
|
perid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Gpio>(), 0x1000);
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Gpio>(), 0x400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gpio {
|
||||||
|
const fn new_mmio_at(base: usize) -> MmioGpio<'static> {
|
||||||
|
MmioGpio {
|
||||||
|
ptr: base as *mut _,
|
||||||
|
phantom: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_mmio(port: Port) -> MmioGpio<'static> {
|
||||||
|
match port {
|
||||||
|
Port::A => Self::new_mmio_at(GPIO_0_BASE),
|
||||||
|
Port::B => Self::new_mmio_at(GPIO_1_BASE),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::C => Self::new_mmio_at(GPIO_2_BASE),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::D => Self::new_mmio_at(GPIO_3_BASE),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::E => Self::new_mmio_at(GPIO_4_BASE),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::F => Self::new_mmio_at(GPIO_5_BASE),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::G => Self::new_mmio_at(GPIO_6_BASE),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MmioGpio<'_> {
|
||||||
|
pub fn port(&self) -> Port {
|
||||||
|
match unsafe { self.ptr() } as usize {
|
||||||
|
GPIO_0_BASE => Port::A,
|
||||||
|
GPIO_1_BASE => Port::B,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
GPIO_2_BASE => Port::C,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
GPIO_3_BASE => Port::D,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
GPIO_4_BASE => Port::E,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
GPIO_5_BASE => Port::F,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
GPIO_6_BASE => Port::G,
|
||||||
|
// Constructors were disabled, so this should really not happen.
|
||||||
|
_ => panic!("unexpected base address of GPIO register block"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
705
src/i2c/mod.rs
Normal file
705
src/i2c/mod.rs
Normal file
@ -0,0 +1,705 @@
|
|||||||
|
pub mod regs;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
PeripheralSelect, enable_peripheral_clock, sealed::Sealed,
|
||||||
|
sysconfig::reset_peripheral_for_cycles, time::Hertz,
|
||||||
|
};
|
||||||
|
use arbitrary_int::{u4, u10, u11, u20};
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
use embedded_hal::i2c::{self, Operation, SevenBitAddress, TenBitAddress};
|
||||||
|
use regs::ClkTimeoutLimit;
|
||||||
|
pub use regs::{Bank, I2cSpeed, RxFifoFullMode, TxFifoEmptyMode};
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use va416xx as pac;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Defintions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
const CLK_100K: Hertz = Hertz::from_raw(100_000);
|
||||||
|
const CLK_400K: Hertz = Hertz::from_raw(400_000);
|
||||||
|
const MIN_CLK_400K: Hertz = Hertz::from_raw(8_000_000);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[error("clock too slow for fast I2C mode")]
|
||||||
|
pub struct ClockTooSlowForFastI2cError;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||||
|
#[error("invalid timing parameters")]
|
||||||
|
pub struct InvalidTimingParamsError;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("arbitration lost")]
|
||||||
|
ArbitrationLost,
|
||||||
|
#[error("nack address")]
|
||||||
|
NackAddr,
|
||||||
|
/// Data not acknowledged in write operation
|
||||||
|
#[error("data not acknowledged in write operation")]
|
||||||
|
NackData,
|
||||||
|
/// Not enough data received in read operation
|
||||||
|
#[error("insufficient data received")]
|
||||||
|
InsufficientDataReceived,
|
||||||
|
/// Number of bytes in transfer too large (larger than 0x7fe)
|
||||||
|
#[error("data too large (larger than 0x7fe)")]
|
||||||
|
DataTooLarge,
|
||||||
|
#[error("clock timeout, SCL was low for {0} clock cycles")]
|
||||||
|
ClockTimeout(u20),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum InitError {
|
||||||
|
/// Wrong address used in constructor
|
||||||
|
#[error("wrong address mode")]
|
||||||
|
WrongAddrMode,
|
||||||
|
/// APB1 clock is too slow for fast I2C mode.
|
||||||
|
#[error("clock too slow for fast I2C mode: {0}")]
|
||||||
|
ClkTooSlow(#[from] ClockTooSlowForFastI2cError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::i2c::Error for Error {
|
||||||
|
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
|
||||||
|
match self {
|
||||||
|
Error::ArbitrationLost => embedded_hal::i2c::ErrorKind::ArbitrationLoss,
|
||||||
|
Error::NackAddr => {
|
||||||
|
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address)
|
||||||
|
}
|
||||||
|
Error::NackData => {
|
||||||
|
embedded_hal::i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data)
|
||||||
|
}
|
||||||
|
Error::DataTooLarge | Error::InsufficientDataReceived | Error::ClockTimeout(_) => {
|
||||||
|
embedded_hal::i2c::ErrorKind::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum I2cCmd {
|
||||||
|
Start = 0b01,
|
||||||
|
Stop = 0b10,
|
||||||
|
StartWithStop = 0b11,
|
||||||
|
Cancel = 0b100,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum I2cAddress {
|
||||||
|
Regular(u8),
|
||||||
|
TenBit(u16),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I2cAddress {
|
||||||
|
pub fn ten_bit_addr(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
I2cAddress::Regular(_) => false,
|
||||||
|
I2cAddress::TenBit(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn raw(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
I2cAddress::Regular(addr) => *addr as u16,
|
||||||
|
I2cAddress::TenBit(addr) => *addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||||
|
/// format is the same for all SPI blocks.
|
||||||
|
pub trait I2cMarker: Sealed {
|
||||||
|
const ID: Bank;
|
||||||
|
const PERIPH_SEL: PeripheralSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub type I2c0 = pac::I2ca;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub type I2c0 = pac::I2c0;
|
||||||
|
|
||||||
|
impl I2cMarker for I2c0 {
|
||||||
|
const ID: Bank = Bank::I2c0;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c0;
|
||||||
|
}
|
||||||
|
impl Sealed for I2c0 {}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub type I2c1 = pac::I2cb;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub type I2c1 = pac::I2c1;
|
||||||
|
|
||||||
|
impl I2cMarker for I2c1 {
|
||||||
|
const ID: Bank = Bank::I2c1;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::I2c1;
|
||||||
|
}
|
||||||
|
impl Sealed for I2c1 {}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Config
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
fn calc_clk_div_generic(
|
||||||
|
ref_clk: Hertz,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<u8, ClockTooSlowForFastI2cError> {
|
||||||
|
if speed_mode == I2cSpeed::Regular100khz {
|
||||||
|
Ok(((ref_clk.raw() / CLK_100K.raw() / 20) - 1) as u8)
|
||||||
|
} else {
|
||||||
|
if ref_clk.raw() < MIN_CLK_400K.raw() {
|
||||||
|
return Err(ClockTooSlowForFastI2cError);
|
||||||
|
}
|
||||||
|
Ok(((ref_clk.raw() / CLK_400K.raw() / 25) - 1) as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
fn calc_clk_div(
|
||||||
|
clks: &crate::clock::Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<u8, ClockTooSlowForFastI2cError> {
|
||||||
|
calc_clk_div_generic(clks.apb1(), speed_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
fn calc_clk_div(sys_clk: Hertz, speed_mode: I2cSpeed) -> Result<u8, ClockTooSlowForFastI2cError> {
|
||||||
|
calc_clk_div_generic(sys_clk, speed_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct TimingConfig {
|
||||||
|
pub t_rise: u4,
|
||||||
|
pub t_fall: u4,
|
||||||
|
pub t_high: u4,
|
||||||
|
pub t_low: u4,
|
||||||
|
pub tsu_stop: u4,
|
||||||
|
pub tsu_start: u4,
|
||||||
|
pub thd_start: u4,
|
||||||
|
pub t_buf: u4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default configuration are the register reset value which are used by default.
|
||||||
|
impl Default for TimingConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
TimingConfig {
|
||||||
|
t_rise: u4::new(0b0010),
|
||||||
|
t_fall: u4::new(0b0001),
|
||||||
|
t_high: u4::new(0b1000),
|
||||||
|
t_low: u4::new(0b1001),
|
||||||
|
tsu_stop: u4::new(0b1000),
|
||||||
|
tsu_start: u4::new(0b1010),
|
||||||
|
thd_start: u4::new(0b1000),
|
||||||
|
t_buf: u4::new(0b1010),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct MasterConfig {
|
||||||
|
pub tx_empty_mode: TxFifoEmptyMode,
|
||||||
|
pub rx_full_mode: RxFifoFullMode,
|
||||||
|
/// Enable the analog delay glitch filter
|
||||||
|
pub alg_filt: bool,
|
||||||
|
/// Enable the digital glitch filter
|
||||||
|
pub dlg_filt: bool,
|
||||||
|
pub timing_config: Option<TimingConfig>,
|
||||||
|
/// See [I2cMaster::set_clock_low_timeout] documentation.
|
||||||
|
pub timeout: Option<u20>,
|
||||||
|
// Loopback mode
|
||||||
|
// lbm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MasterConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
MasterConfig {
|
||||||
|
tx_empty_mode: TxFifoEmptyMode::Stall,
|
||||||
|
rx_full_mode: RxFifoFullMode::Stall,
|
||||||
|
alg_filt: false,
|
||||||
|
dlg_filt: false,
|
||||||
|
timeout: None,
|
||||||
|
timing_config: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for MasterConfig {}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum WriteCompletionCondition {
|
||||||
|
Idle,
|
||||||
|
Waiting,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimeoutGuard {
|
||||||
|
clk_timeout_enabled: bool,
|
||||||
|
regs: regs::MmioI2c<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimeoutGuard {
|
||||||
|
fn new(regs: ®s::MmioI2c<'static>) -> Self {
|
||||||
|
let clk_timeout_enabled = regs.read_clk_timeout_limit().value().value() > 0;
|
||||||
|
let mut guard = TimeoutGuard {
|
||||||
|
clk_timeout_enabled,
|
||||||
|
regs: unsafe { regs.clone() },
|
||||||
|
};
|
||||||
|
if clk_timeout_enabled {
|
||||||
|
// Clear any interrupts which might be pending.
|
||||||
|
guard.regs.write_irq_clear(
|
||||||
|
regs::InterruptClear::builder()
|
||||||
|
.with_clock_timeout(true)
|
||||||
|
.with_tx_overflow(false)
|
||||||
|
.with_rx_overflow(false)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
guard.regs.modify_irq_enb(|mut value| {
|
||||||
|
value.set_clock_timeout(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
guard
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeout_enabled(&self) -> bool {
|
||||||
|
self.clk_timeout_enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TimeoutGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.clk_timeout_enabled {
|
||||||
|
self.regs.modify_irq_enb(|mut value| {
|
||||||
|
value.set_clock_timeout(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//==================================================================================================
|
||||||
|
// I2C Master
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub struct I2cMaster<Addr = SevenBitAddress> {
|
||||||
|
id: Bank,
|
||||||
|
regs: regs::MmioI2c<'static>,
|
||||||
|
addr: PhantomData<Addr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Addr> I2cMaster<Addr> {
|
||||||
|
pub fn new<I2c: I2cMarker>(
|
||||||
|
_i2c: I2c,
|
||||||
|
#[cfg(feature = "vor1x")] sysclk: Hertz,
|
||||||
|
#[cfg(feature = "vor4x")] clks: &crate::clock::Clocks,
|
||||||
|
cfg: MasterConfig,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<Self, ClockTooSlowForFastI2cError> {
|
||||||
|
reset_peripheral_for_cycles(I2c::PERIPH_SEL, 2);
|
||||||
|
enable_peripheral_clock(I2c::PERIPH_SEL);
|
||||||
|
let mut regs = regs::I2c::new_mmio(I2c::ID);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
let clk_div = calc_clk_div(sysclk, speed_mode)?;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
let clk_div = calc_clk_div(clks, speed_mode)?;
|
||||||
|
regs.write_clkscale(
|
||||||
|
regs::ClkScale::builder()
|
||||||
|
.with_div(clk_div)
|
||||||
|
.with_fastmode(speed_mode)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
regs.modify_control(|mut value| {
|
||||||
|
value.set_tx_fifo_empty_mode(cfg.tx_empty_mode);
|
||||||
|
value.set_rx_fifo_full_mode(cfg.rx_full_mode);
|
||||||
|
value.set_analog_filter(cfg.alg_filt);
|
||||||
|
value.set_digital_filter(cfg.dlg_filt);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(ref timing_cfg) = cfg.timing_config {
|
||||||
|
regs.modify_control(|mut value| {
|
||||||
|
value.set_enable_timing_config(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
regs.write_timing_config(
|
||||||
|
regs::TimingConfig::builder()
|
||||||
|
.with_t_rise(timing_cfg.t_rise)
|
||||||
|
.with_t_fall(timing_cfg.t_fall)
|
||||||
|
.with_t_high(timing_cfg.t_high)
|
||||||
|
.with_t_low(timing_cfg.t_low)
|
||||||
|
.with_tsu_stop(timing_cfg.tsu_stop)
|
||||||
|
.with_tsu_start(timing_cfg.tsu_start)
|
||||||
|
.with_thd_start(timing_cfg.thd_start)
|
||||||
|
.with_t_buf(timing_cfg.t_buf)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
regs.write_fifo_clear(
|
||||||
|
regs::FifoClear::builder()
|
||||||
|
.with_tx_fifo(true)
|
||||||
|
.with_rx_fifo(true)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
if let Some(timeout) = cfg.timeout {
|
||||||
|
regs.write_clk_timeout_limit(ClkTimeoutLimit::new(timeout));
|
||||||
|
}
|
||||||
|
let mut i2c_master = I2cMaster {
|
||||||
|
addr: PhantomData,
|
||||||
|
id: I2c::ID,
|
||||||
|
regs,
|
||||||
|
};
|
||||||
|
i2c_master.enable();
|
||||||
|
Ok(i2c_master)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn id(&self) -> Bank {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32 {
|
||||||
|
self.regs.read_perid()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures the clock scale for a given speed mode setting
|
||||||
|
pub fn set_clk_scale(
|
||||||
|
&mut self,
|
||||||
|
#[cfg(feature = "vor1x")] sys_clk: Hertz,
|
||||||
|
#[cfg(feature = "vor4x")] clks: &crate::clock::Clocks,
|
||||||
|
speed_mode: I2cSpeed,
|
||||||
|
) -> Result<(), ClockTooSlowForFastI2cError> {
|
||||||
|
self.disable();
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
let clk_div = calc_clk_div(sys_clk, speed_mode)?;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
let clk_div = calc_clk_div(clks, speed_mode)?;
|
||||||
|
self.regs.write_clkscale(
|
||||||
|
regs::ClkScale::builder()
|
||||||
|
.with_div(clk_div)
|
||||||
|
.with_fastmode(speed_mode)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
self.enable();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cancel_transfer(&mut self) {
|
||||||
|
self.regs.write_cmd(
|
||||||
|
regs::Command::builder()
|
||||||
|
.with_start(false)
|
||||||
|
.with_stop(false)
|
||||||
|
.with_cancel(true)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_tx_fifo(&mut self) {
|
||||||
|
self.regs.write_fifo_clear(
|
||||||
|
regs::FifoClear::builder()
|
||||||
|
.with_tx_fifo(true)
|
||||||
|
.with_rx_fifo(false)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_rx_fifo(&mut self) {
|
||||||
|
self.regs.write_fifo_clear(
|
||||||
|
regs::FifoClear::builder()
|
||||||
|
.with_tx_fifo(false)
|
||||||
|
.with_rx_fifo(true)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure a timeout limit on the amount of time the I2C clock is seen to be low.
|
||||||
|
/// The timeout is specified as I2C clock cycles.
|
||||||
|
///
|
||||||
|
/// If the timeout is enabled, the blocking transaction handlers provided by the [I2cMaster]
|
||||||
|
/// will poll the interrupt status register to check for timeouts. This can be used to avoid
|
||||||
|
/// hang-ups of the I2C bus.
|
||||||
|
#[inline]
|
||||||
|
pub fn set_clock_low_timeout(&mut self, clock_cycles: u20) {
|
||||||
|
self.regs
|
||||||
|
.write_clk_timeout_limit(ClkTimeoutLimit::new(clock_cycles));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_clock_low_timeout(&mut self) {
|
||||||
|
self.regs
|
||||||
|
.write_clk_timeout_limit(ClkTimeoutLimit::new(u20::new(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_enable(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_fifo_unchecked(&mut self, word: u8) {
|
||||||
|
self.regs.write_data(regs::Data::new(word));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_fifo_unchecked(&self) -> u8 {
|
||||||
|
self.regs.read_data().data()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn read_status(&mut self) -> regs::Status {
|
||||||
|
self.regs.read_status()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn write_command(&mut self, cmd: I2cCmd) {
|
||||||
|
self.regs
|
||||||
|
.write_cmd(regs::Command::new_with_raw_value(cmd as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn write_address(&mut self, addr: I2cAddress, dir: regs::Direction) {
|
||||||
|
self.regs.write_address(
|
||||||
|
regs::Address::builder()
|
||||||
|
.with_direction(dir)
|
||||||
|
.with_address(u10::new(addr.raw()))
|
||||||
|
.with_a10_mode(addr.ten_bit_addr())
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_handler_write(&mut self, init_cmd: I2cCmd) {
|
||||||
|
if init_cmd == I2cCmd::Start {
|
||||||
|
self.write_command(I2cCmd::Stop);
|
||||||
|
}
|
||||||
|
// The other case is start with stop where, so a CANCEL command should not be necessary
|
||||||
|
// because the hardware takes care of it.
|
||||||
|
self.clear_tx_fifo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocking write transaction on the I2C bus.
|
||||||
|
pub fn write_blocking(&mut self, addr: I2cAddress, output: &[u8]) -> Result<(), Error> {
|
||||||
|
self.write_blocking_generic(
|
||||||
|
I2cCmd::StartWithStop,
|
||||||
|
addr,
|
||||||
|
output,
|
||||||
|
WriteCompletionCondition::Idle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocking read transaction on the I2C bus.
|
||||||
|
pub fn read_blocking(&mut self, addr: I2cAddress, buffer: &mut [u8]) -> Result<(), Error> {
|
||||||
|
let len = buffer.len();
|
||||||
|
if len > 0x7fe {
|
||||||
|
return Err(Error::DataTooLarge);
|
||||||
|
}
|
||||||
|
// Clear the receive FIFO
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
|
||||||
|
let timeout_guard = TimeoutGuard::new(&self.regs);
|
||||||
|
|
||||||
|
// Load number of words
|
||||||
|
self.regs
|
||||||
|
.write_words(regs::Words::new(u11::new(len as u16)));
|
||||||
|
// Load address
|
||||||
|
self.write_address(addr, regs::Direction::Receive);
|
||||||
|
|
||||||
|
let mut buf_iter = buffer.iter_mut();
|
||||||
|
let mut read_bytes = 0;
|
||||||
|
// Start receive transfer
|
||||||
|
self.write_command(I2cCmd::StartWithStop);
|
||||||
|
loop {
|
||||||
|
let status = self.read_status();
|
||||||
|
if status.arb_lost() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
return Err(Error::ArbitrationLost);
|
||||||
|
}
|
||||||
|
if status.nack_addr() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
return Err(Error::NackAddr);
|
||||||
|
}
|
||||||
|
if status.idle() {
|
||||||
|
if read_bytes != len {
|
||||||
|
return Err(Error::InsufficientDataReceived);
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if timeout_guard.timeout_enabled() && self.regs.read_irq_status().clock_timeout() {
|
||||||
|
return Err(Error::ClockTimeout(
|
||||||
|
self.regs.read_clk_timeout_limit().value(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if status.rx_not_empty() {
|
||||||
|
if let Some(next_byte) = buf_iter.next() {
|
||||||
|
*next_byte = self.read_fifo_unchecked();
|
||||||
|
}
|
||||||
|
read_bytes += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_blocking_generic(
|
||||||
|
&mut self,
|
||||||
|
init_cmd: I2cCmd,
|
||||||
|
addr: I2cAddress,
|
||||||
|
output: &[u8],
|
||||||
|
end_condition: WriteCompletionCondition,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let len = output.len();
|
||||||
|
if len > 0x7fe {
|
||||||
|
return Err(Error::DataTooLarge);
|
||||||
|
}
|
||||||
|
// Clear the send FIFO
|
||||||
|
self.clear_tx_fifo();
|
||||||
|
|
||||||
|
let timeout_guard = TimeoutGuard::new(&self.regs);
|
||||||
|
|
||||||
|
// Load number of words
|
||||||
|
self.regs
|
||||||
|
.write_words(regs::Words::new(u11::new(len as u16)));
|
||||||
|
let mut bytes = output.iter();
|
||||||
|
// FIFO has a depth of 16. We load slightly above the trigger level
|
||||||
|
// but not all of it because the transaction might fail immediately
|
||||||
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
|
let mut current_index = core::cmp::min(FILL_DEPTH, len);
|
||||||
|
// load the FIFO
|
||||||
|
for _ in 0..current_index {
|
||||||
|
self.write_fifo_unchecked(*bytes.next().unwrap());
|
||||||
|
}
|
||||||
|
self.write_address(addr, regs::Direction::Send);
|
||||||
|
self.write_command(init_cmd);
|
||||||
|
loop {
|
||||||
|
let status = self.regs.read_status();
|
||||||
|
if status.arb_lost() {
|
||||||
|
self.error_handler_write(init_cmd);
|
||||||
|
return Err(Error::ArbitrationLost);
|
||||||
|
}
|
||||||
|
if status.nack_addr() {
|
||||||
|
self.error_handler_write(init_cmd);
|
||||||
|
return Err(Error::NackAddr);
|
||||||
|
}
|
||||||
|
if status.nack_data() {
|
||||||
|
self.error_handler_write(init_cmd);
|
||||||
|
return Err(Error::NackData);
|
||||||
|
}
|
||||||
|
match end_condition {
|
||||||
|
WriteCompletionCondition::Idle => {
|
||||||
|
if status.idle() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteCompletionCondition::Waiting => {
|
||||||
|
if status.waiting() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timeout_guard.timeout_enabled() && self.regs.read_irq_status().clock_timeout() {
|
||||||
|
return Err(Error::ClockTimeout(
|
||||||
|
self.regs.read_clk_timeout_limit().value(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if status.tx_not_full() && current_index < len {
|
||||||
|
self.write_fifo_unchecked(output[current_index]);
|
||||||
|
current_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocking write-read transaction on the I2C bus.
|
||||||
|
pub fn write_read_blocking(
|
||||||
|
&mut self,
|
||||||
|
address: I2cAddress,
|
||||||
|
write: &[u8],
|
||||||
|
read: &mut [u8],
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
self.write_blocking_generic(
|
||||||
|
I2cCmd::Start,
|
||||||
|
address,
|
||||||
|
write,
|
||||||
|
WriteCompletionCondition::Waiting,
|
||||||
|
)?;
|
||||||
|
self.read_blocking(address, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//======================================================================================
|
||||||
|
// Embedded HAL I2C implementations
|
||||||
|
//======================================================================================
|
||||||
|
|
||||||
|
impl embedded_hal::i2c::ErrorType for I2cMaster<SevenBitAddress> {
|
||||||
|
type Error = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::i2c::I2c for I2cMaster<SevenBitAddress> {
|
||||||
|
fn transaction(
|
||||||
|
&mut self,
|
||||||
|
address: SevenBitAddress,
|
||||||
|
operations: &mut [Operation<'_>],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
for operation in operations {
|
||||||
|
match operation {
|
||||||
|
Operation::Read(buf) => self.read_blocking(I2cAddress::Regular(address), buf)?,
|
||||||
|
Operation::Write(buf) => self.write_blocking(I2cAddress::Regular(address), buf)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_read(
|
||||||
|
&mut self,
|
||||||
|
address: u8,
|
||||||
|
write: &[u8],
|
||||||
|
read: &mut [u8],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let addr = I2cAddress::Regular(address);
|
||||||
|
self.write_read_blocking(addr, write, read)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::i2c::ErrorType for I2cMaster<TenBitAddress> {
|
||||||
|
type Error = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::i2c::I2c<TenBitAddress> for I2cMaster<TenBitAddress> {
|
||||||
|
fn transaction(
|
||||||
|
&mut self,
|
||||||
|
address: TenBitAddress,
|
||||||
|
operations: &mut [Operation<'_>],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
for operation in operations {
|
||||||
|
match operation {
|
||||||
|
Operation::Read(buf) => self.read_blocking(I2cAddress::TenBit(address), buf)?,
|
||||||
|
Operation::Write(buf) => self.write_blocking(I2cAddress::TenBit(address), buf)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_read(
|
||||||
|
&mut self,
|
||||||
|
address: TenBitAddress,
|
||||||
|
write: &[u8],
|
||||||
|
read: &mut [u8],
|
||||||
|
) -> Result<(), Self::Error> {
|
||||||
|
let addr = I2cAddress::TenBit(address);
|
||||||
|
self.write_read_blocking(addr, write, read)
|
||||||
|
}
|
||||||
|
}
|
671
src/i2c/regs.rs
Normal file
671
src/i2c/regs.rs
Normal file
@ -0,0 +1,671 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use arbitrary_int::{u4, u5, u9, u10, u11, u20};
|
||||||
|
|
||||||
|
pub use crate::shared::{FifoClear, TriggerLevel};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
/// I2C A base address
|
||||||
|
pub const BASE_ADDR_0: usize = 0x4006_0000;
|
||||||
|
/// I2C B base address
|
||||||
|
pub const BASE_ADDR_1: usize = 0x4006_1000;
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
/// I2C 0 base address
|
||||||
|
pub const BASE_ADDR_0: usize = 0x4001_6000;
|
||||||
|
/// I2C 1 base address
|
||||||
|
pub const BASE_ADDR_1: usize = 0x4001_6400;
|
||||||
|
/// I2C 2 base address
|
||||||
|
pub const BASE_ADDR_2: usize = 0x4001_6800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Bank {
|
||||||
|
I2c0 = 0,
|
||||||
|
I2c1 = 1,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
I2c2 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
/// Unsafely steal the I2C peripheral block for the given port.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents ownership and safety guarantees by the HAL.
|
||||||
|
pub unsafe fn steal_regs(&self) -> MmioI2c<'static> {
|
||||||
|
I2c::new_mmio(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum TxFifoEmptyMode {
|
||||||
|
/// I2C clock is stretched until data is available.
|
||||||
|
#[default]
|
||||||
|
Stall = 0,
|
||||||
|
EndTransaction = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum RxFifoFullMode {
|
||||||
|
/// I2C clock is stretched until data is available.
|
||||||
|
#[default]
|
||||||
|
Stall = 0,
|
||||||
|
Nack = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Control {
|
||||||
|
#[bit(0, r)]
|
||||||
|
clk_enabled: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
enabled: bool,
|
||||||
|
#[bit(2, rw)]
|
||||||
|
enable: bool,
|
||||||
|
#[bit(3, rw)]
|
||||||
|
tx_fifo_empty_mode: TxFifoEmptyMode,
|
||||||
|
#[bit(4, rw)]
|
||||||
|
rx_fifo_full_mode: RxFifoFullMode,
|
||||||
|
/// Enables the analog delay glitch filter.
|
||||||
|
#[bit(5, rw)]
|
||||||
|
analog_filter: bool,
|
||||||
|
/// Enables the digital glitch filter.
|
||||||
|
#[bit(6, rw)]
|
||||||
|
digital_filter: bool,
|
||||||
|
#[bit(8, rw)]
|
||||||
|
loopback: bool,
|
||||||
|
#[bit(9, rw)]
|
||||||
|
enable_timing_config: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum I2cSpeed {
|
||||||
|
Regular100khz = 0,
|
||||||
|
Fast400khz = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ClkScale {
|
||||||
|
/// Clock divide value. Reset value: 0x18.
|
||||||
|
#[bits(0..=7, rw)]
|
||||||
|
div: u8,
|
||||||
|
#[bit(31, rw)]
|
||||||
|
fastmode: I2cSpeed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Words(arbitrary_int::UInt<u32, 11>);
|
||||||
|
|
||||||
|
impl Words {
|
||||||
|
pub const fn new(value: u11) -> Self {
|
||||||
|
Words(arbitrary_int::UInt::<u32, 11>::new(value.value() as u32))
|
||||||
|
}
|
||||||
|
pub const fn value(&self) -> u11 {
|
||||||
|
u11::new(self.0.value() as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Direction {
|
||||||
|
#[default]
|
||||||
|
Send = 0,
|
||||||
|
Receive = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Address {
|
||||||
|
#[bit(0, rw)]
|
||||||
|
direction: Direction,
|
||||||
|
#[bits(1..=10, rw)]
|
||||||
|
address: u10,
|
||||||
|
/// Enables 10-bit addressing mode.
|
||||||
|
#[bit(15, rw)]
|
||||||
|
a10_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub struct Data(arbitrary_int::UInt<u32, 8>);
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
pub const fn new(value: u8) -> Self {
|
||||||
|
Data(arbitrary_int::UInt::<u32, 8>::new(value as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn data(&self) -> u8 {
|
||||||
|
self.0.value() as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Command {
|
||||||
|
#[bit(0, w)]
|
||||||
|
start: bool,
|
||||||
|
#[bit(1, w)]
|
||||||
|
stop: bool,
|
||||||
|
#[bit(2, w)]
|
||||||
|
cancel: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Status {
|
||||||
|
#[bit(0, r)]
|
||||||
|
i2c_idle: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
stalled: bool,
|
||||||
|
#[bit(4, r)]
|
||||||
|
arb_lost: bool,
|
||||||
|
#[bit(5, r)]
|
||||||
|
nack_addr: bool,
|
||||||
|
#[bit(6, r)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(8, r)]
|
||||||
|
rx_not_empty: bool,
|
||||||
|
#[bit(9, r)]
|
||||||
|
rx_full: bool,
|
||||||
|
#[bit(11, r)]
|
||||||
|
rx_trigger: bool,
|
||||||
|
#[bit(12, r)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(13, r)]
|
||||||
|
tx_not_full: bool,
|
||||||
|
#[bit(15, r)]
|
||||||
|
tx_trigger: bool,
|
||||||
|
#[bit(30, r)]
|
||||||
|
raw_sda: bool,
|
||||||
|
#[bit(31, r)]
|
||||||
|
raw_scl: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct State {
|
||||||
|
#[bits(0..=3, rw)]
|
||||||
|
state: u4,
|
||||||
|
#[bits(4..=7, rw)]
|
||||||
|
step: u4,
|
||||||
|
#[bits(8..=12, rw)]
|
||||||
|
rx_fifo: u5,
|
||||||
|
#[bits(14..=18, rw)]
|
||||||
|
tx_fifo: u5,
|
||||||
|
#[bits(20..=28, rw)]
|
||||||
|
bitstate: u9,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct DataCount(arbitrary_int::UInt<u32, 11>);
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptControl {
|
||||||
|
#[bit(0, rw)]
|
||||||
|
i2c_idle: bool,
|
||||||
|
#[bit(1, rw)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, rw)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, rw)]
|
||||||
|
stalled: bool,
|
||||||
|
#[bit(4, rw)]
|
||||||
|
arb_lost: bool,
|
||||||
|
#[bit(5, rw)]
|
||||||
|
nack_addr: bool,
|
||||||
|
#[bit(6, rw)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(7, rw)]
|
||||||
|
clock_timeout: bool,
|
||||||
|
#[bit(10, rw)]
|
||||||
|
tx_overflow: bool,
|
||||||
|
#[bit(11, rw)]
|
||||||
|
rx_overflow: bool,
|
||||||
|
#[bit(12, rw)]
|
||||||
|
tx_ready: bool,
|
||||||
|
#[bit(13, rw)]
|
||||||
|
rx_ready: bool,
|
||||||
|
#[bit(14, rw)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(15, rw)]
|
||||||
|
rx_full: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptStatus {
|
||||||
|
#[bit(0, r)]
|
||||||
|
i2c_idle: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
stalled: bool,
|
||||||
|
#[bit(4, r)]
|
||||||
|
arb_lost: bool,
|
||||||
|
#[bit(5, r)]
|
||||||
|
nack_addr: bool,
|
||||||
|
#[bit(6, r)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(7, r)]
|
||||||
|
clock_timeout: bool,
|
||||||
|
#[bit(10, r)]
|
||||||
|
tx_overflow: bool,
|
||||||
|
#[bit(11, r)]
|
||||||
|
rx_overflow: bool,
|
||||||
|
#[bit(12, r)]
|
||||||
|
tx_ready: bool,
|
||||||
|
#[bit(13, r)]
|
||||||
|
rx_ready: bool,
|
||||||
|
#[bit(14, r)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(15, r)]
|
||||||
|
rx_full: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptClear {
|
||||||
|
#[bit(7, w)]
|
||||||
|
clock_timeout: bool,
|
||||||
|
#[bit(10, w)]
|
||||||
|
tx_overflow: bool,
|
||||||
|
#[bit(11, w)]
|
||||||
|
rx_overflow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TimingConfig {
|
||||||
|
/// Rise time.
|
||||||
|
#[bits(0..=3, rw)]
|
||||||
|
t_rise: u4,
|
||||||
|
/// Fall time.
|
||||||
|
#[bits(4..=7, rw)]
|
||||||
|
t_fall: u4,
|
||||||
|
/// Duty cycle high time of SCL.
|
||||||
|
#[bits(8..=11, rw)]
|
||||||
|
t_high: u4,
|
||||||
|
/// Duty cycle low time of SCL.
|
||||||
|
#[bits(12..=15, rw)]
|
||||||
|
t_low: u4,
|
||||||
|
/// Setup time for STOP.
|
||||||
|
#[bits(16..=19, rw)]
|
||||||
|
tsu_stop: u4,
|
||||||
|
/// Setup time for START.
|
||||||
|
#[bits(20..=23, rw)]
|
||||||
|
tsu_start: u4,
|
||||||
|
/// Data hold time.
|
||||||
|
#[bits(24..=27, rw)]
|
||||||
|
thd_start: u4,
|
||||||
|
/// TBus free time between STOP and START.
|
||||||
|
#[bits(28..=31, rw)]
|
||||||
|
t_buf: u4,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClkTimeoutLimit(pub arbitrary_int::UInt<u32, 20>);
|
||||||
|
|
||||||
|
impl ClkTimeoutLimit {
|
||||||
|
pub fn new(value: u20) -> Self {
|
||||||
|
ClkTimeoutLimit(arbitrary_int::UInt::<u32, 20>::new(value.value()))
|
||||||
|
}
|
||||||
|
pub fn value(&self) -> u20 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod slave {
|
||||||
|
use super::{Data, DataCount, FifoClear, RxFifoFullMode, TriggerLevel, TxFifoEmptyMode};
|
||||||
|
use arbitrary_int::{u3, u4, u5, u10, u11};
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Control {
|
||||||
|
#[bit(0, r)]
|
||||||
|
clk_enabled: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
enabled: bool,
|
||||||
|
#[bit(2, rw)]
|
||||||
|
enable: bool,
|
||||||
|
#[bit(3, rw)]
|
||||||
|
tx_fifo_empty_mode: TxFifoEmptyMode,
|
||||||
|
#[bit(4, rw)]
|
||||||
|
rx_fifo_full_mode: RxFifoFullMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Maxwords {
|
||||||
|
#[bits(0..=10, rw)]
|
||||||
|
maxwords: u11,
|
||||||
|
#[bit(31, rw)]
|
||||||
|
enable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Address {
|
||||||
|
#[bit(0, rw)]
|
||||||
|
rw: bool,
|
||||||
|
#[bits(1..=10, rw)]
|
||||||
|
address: u10,
|
||||||
|
#[bit(15, rw)]
|
||||||
|
a10_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AddressMask {
|
||||||
|
/// Will normally be 0 to match both read and write addresses.
|
||||||
|
#[bit(0, rw)]
|
||||||
|
rw_mask: bool,
|
||||||
|
/// Reset value 0x3FF.
|
||||||
|
#[bits(1..=10, rw)]
|
||||||
|
mask: u10,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Direction {
|
||||||
|
#[default]
|
||||||
|
MasterSend = 0,
|
||||||
|
MasterReceive = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LastAddress {
|
||||||
|
#[bit(0, rw)]
|
||||||
|
direction: Direction,
|
||||||
|
#[bits(1..=10, rw)]
|
||||||
|
address: u10,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Status {
|
||||||
|
#[bit(0, r)]
|
||||||
|
completed: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
tx_stalled: bool,
|
||||||
|
#[bit(4, r)]
|
||||||
|
rx_stalled: bool,
|
||||||
|
#[bit(5, r)]
|
||||||
|
address_match: bool,
|
||||||
|
#[bit(6, r)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(7, r)]
|
||||||
|
rx_data_first: bool,
|
||||||
|
#[bit(8, r)]
|
||||||
|
rx_not_empty: bool,
|
||||||
|
#[bit(9, r)]
|
||||||
|
rx_full: bool,
|
||||||
|
#[bit(11, r)]
|
||||||
|
rx_trigger: bool,
|
||||||
|
#[bit(12, r)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(13, r)]
|
||||||
|
tx_not_full: bool,
|
||||||
|
#[bit(15, r)]
|
||||||
|
tx_trigger: bool,
|
||||||
|
#[bit(28, r)]
|
||||||
|
raw_busy: bool,
|
||||||
|
#[bit(30, r)]
|
||||||
|
raw_sda: bool,
|
||||||
|
#[bit(31, r)]
|
||||||
|
raw_scl: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct State {
|
||||||
|
#[bits(0..=2, rw)]
|
||||||
|
state: u3,
|
||||||
|
#[bits(4..=7, rw)]
|
||||||
|
step: u4,
|
||||||
|
#[bits(8..=12, rw)]
|
||||||
|
rx_fifo: u5,
|
||||||
|
#[bits(14..=18, rw)]
|
||||||
|
tx_fifo: u5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptControl {
|
||||||
|
#[bit(0, rw)]
|
||||||
|
completed: bool,
|
||||||
|
#[bit(1, rw)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, rw)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, rw)]
|
||||||
|
tx_stalled: bool,
|
||||||
|
#[bit(4, rw)]
|
||||||
|
rx_stalled: bool,
|
||||||
|
#[bit(5, rw)]
|
||||||
|
address_match: bool,
|
||||||
|
#[bit(6, rw)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(7, rw)]
|
||||||
|
rx_data_first: bool,
|
||||||
|
|
||||||
|
#[bit(8, rw)]
|
||||||
|
i2c_start: bool,
|
||||||
|
#[bit(9, rw)]
|
||||||
|
i2c_stop: bool,
|
||||||
|
#[bit(10, rw)]
|
||||||
|
tx_underflow: bool,
|
||||||
|
#[bit(11, rw)]
|
||||||
|
rx_underflow: bool,
|
||||||
|
#[bit(12, rw)]
|
||||||
|
tx_ready: bool,
|
||||||
|
#[bit(13, rw)]
|
||||||
|
rx_ready: bool,
|
||||||
|
#[bit(14, rw)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(15, rw)]
|
||||||
|
rx_full: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptStatus {
|
||||||
|
#[bit(0, r)]
|
||||||
|
completed: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
tx_stalled: bool,
|
||||||
|
#[bit(4, r)]
|
||||||
|
rx_stalled: bool,
|
||||||
|
#[bit(5, r)]
|
||||||
|
address_match: bool,
|
||||||
|
#[bit(6, r)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(7, r)]
|
||||||
|
rx_data_first: bool,
|
||||||
|
|
||||||
|
#[bit(8, r)]
|
||||||
|
i2c_start: bool,
|
||||||
|
#[bit(9, r)]
|
||||||
|
i2c_stop: bool,
|
||||||
|
#[bit(10, r)]
|
||||||
|
tx_underflow: bool,
|
||||||
|
#[bit(11, r)]
|
||||||
|
rx_underflow: bool,
|
||||||
|
#[bit(12, r)]
|
||||||
|
tx_ready: bool,
|
||||||
|
#[bit(13, r)]
|
||||||
|
rx_ready: bool,
|
||||||
|
#[bit(14, r)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(15, r)]
|
||||||
|
rx_full: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptClear {
|
||||||
|
#[bit(0, w)]
|
||||||
|
completed: bool,
|
||||||
|
#[bit(1, w)]
|
||||||
|
idle: bool,
|
||||||
|
#[bit(2, w)]
|
||||||
|
waiting: bool,
|
||||||
|
#[bit(3, w)]
|
||||||
|
tx_stalled: bool,
|
||||||
|
#[bit(4, w)]
|
||||||
|
rx_stalled: bool,
|
||||||
|
#[bit(5, w)]
|
||||||
|
address_match: bool,
|
||||||
|
#[bit(6, w)]
|
||||||
|
nack_data: bool,
|
||||||
|
#[bit(7, w)]
|
||||||
|
rx_data_first: bool,
|
||||||
|
|
||||||
|
#[bit(8, w)]
|
||||||
|
i2c_start: bool,
|
||||||
|
#[bit(9, w)]
|
||||||
|
i2c_stop: bool,
|
||||||
|
#[bit(10, w)]
|
||||||
|
tx_underflow: bool,
|
||||||
|
#[bit(11, w)]
|
||||||
|
rx_underflow: bool,
|
||||||
|
#[bit(12, w)]
|
||||||
|
tx_ready: bool,
|
||||||
|
#[bit(13, w)]
|
||||||
|
rx_ready: bool,
|
||||||
|
#[bit(14, w)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(15, w)]
|
||||||
|
rx_full: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct I2cSlave {
|
||||||
|
s0_ctrl: Control,
|
||||||
|
s0_maxwords: Maxwords,
|
||||||
|
s0_address: Address,
|
||||||
|
s0_addressmask: AddressMask,
|
||||||
|
s0_data: Data,
|
||||||
|
s0_lastaddress: LastAddress,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
s0_status: Status,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
s0_state: State,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
s0_tx_count: DataCount,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
s0_rx_count: DataCount,
|
||||||
|
s0_irq_enb: InterruptControl,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
s0_irq_raw: InterruptStatus,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
s0_irq_status: InterruptStatus,
|
||||||
|
#[mmio(Write)]
|
||||||
|
s0_irq_clear: InterruptClear,
|
||||||
|
s0_rx_fifo_trigger: TriggerLevel,
|
||||||
|
s0_tx_fifo_trigger: TriggerLevel,
|
||||||
|
#[mmio(Write)]
|
||||||
|
s0_fifo_clear: FifoClear,
|
||||||
|
s0_address_b: Address,
|
||||||
|
s0_addressmask_b: AddressMask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[mmio(no_ctors)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct I2c {
|
||||||
|
control: Control,
|
||||||
|
clkscale: ClkScale,
|
||||||
|
words: Words,
|
||||||
|
address: Address,
|
||||||
|
data: Data,
|
||||||
|
#[mmio(Write)]
|
||||||
|
cmd: Command,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
status: Status,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
state: State,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
tx_count: DataCount,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
rx_count: DataCount,
|
||||||
|
irq_enb: InterruptControl,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_raw: InterruptStatus,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_status: InterruptStatus,
|
||||||
|
#[mmio(Write)]
|
||||||
|
irq_clear: InterruptClear,
|
||||||
|
rx_fifo_trigger: TriggerLevel,
|
||||||
|
tx_fifo_trigger: TriggerLevel,
|
||||||
|
#[mmio(Write)]
|
||||||
|
fifo_clear: FifoClear,
|
||||||
|
timing_config: TimingConfig,
|
||||||
|
clk_timeout_limit: ClkTimeoutLimit,
|
||||||
|
|
||||||
|
_reserved_0: [u32; 0x2D],
|
||||||
|
|
||||||
|
#[mmio(inner)]
|
||||||
|
slave: slave::I2cSlave,
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
_reserved_1: [u32; 0x3AC],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
_reserved_1: [u32; 0xAC],
|
||||||
|
|
||||||
|
/// Vorago 4x: 0x0214_07E9. Vorago 1x: 0x0014_07E1.
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
perid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<I2c>(), 0x1000);
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<I2c>(), 0x400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl I2c {
|
||||||
|
fn new_mmio_at(base: usize) -> MmioI2c<'static> {
|
||||||
|
MmioI2c {
|
||||||
|
ptr: base as *mut _,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_mmio(bank: Bank) -> MmioI2c<'static> {
|
||||||
|
match bank {
|
||||||
|
Bank::I2c0 => Self::new_mmio_at(BASE_ADDR_0),
|
||||||
|
Bank::I2c1 => Self::new_mmio_at(BASE_ADDR_1),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Bank::I2c2 => Self::new_mmio_at(BASE_ADDR_2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
src/ioconfig/mod.rs
Normal file
3
src/ioconfig/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub use regs::{FilterClkSel, FilterType};
|
||||||
|
|
||||||
|
pub mod regs;
|
177
src/ioconfig/regs.rs
Normal file
177
src/ioconfig/regs.rs
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::{NUM_PORT_A, NUM_PORT_B, gpio::PinId};
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use crate::{NUM_PORT_DEFAULT, NUM_PORT_G};
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub const BASE_ADDR: usize = 0x4000_2000;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub const BASE_ADDR: usize = 0x4001_1000;
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u3)]
|
||||||
|
pub enum FilterType {
|
||||||
|
SysClk = 0,
|
||||||
|
DirectInput = 1,
|
||||||
|
FilterOneCycle = 2,
|
||||||
|
FilterTwoCycles = 3,
|
||||||
|
FilterThreeCycles = 4,
|
||||||
|
FilterFourCycles = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum FilterClkSel {
|
||||||
|
SysClk = 0,
|
||||||
|
Clk1 = 1,
|
||||||
|
Clk2 = 2,
|
||||||
|
Clk3 = 3,
|
||||||
|
Clk4 = 4,
|
||||||
|
Clk5 = 5,
|
||||||
|
Clk6 = 6,
|
||||||
|
Clk7 = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Pull {
|
||||||
|
Up = 0,
|
||||||
|
Down = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum FunSel {
|
||||||
|
Sel0 = 0b00,
|
||||||
|
Sel1 = 0b01,
|
||||||
|
Sel2 = 0b10,
|
||||||
|
Sel3 = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
pub struct Config {
|
||||||
|
#[bit(16, rw)]
|
||||||
|
io_disable: bool,
|
||||||
|
#[bits(13..=14, rw)]
|
||||||
|
funsel: FunSel,
|
||||||
|
#[bit(12, rw)]
|
||||||
|
pull_when_output_active: bool,
|
||||||
|
#[bit(11, rw)]
|
||||||
|
pull_enable: bool,
|
||||||
|
#[bit(10, rw)]
|
||||||
|
pull_dir: Pull,
|
||||||
|
#[bit(9, rw)]
|
||||||
|
invert_output: bool,
|
||||||
|
#[bit(8, rw)]
|
||||||
|
open_drain: bool,
|
||||||
|
/// IEWO bit. Allows monitoring of output values.
|
||||||
|
#[bit(7, rw)]
|
||||||
|
input_enable_when_output: bool,
|
||||||
|
#[bit(6, rw)]
|
||||||
|
invert_input: bool,
|
||||||
|
#[bits(3..=5, rw)]
|
||||||
|
filter_clk_sel: FilterClkSel,
|
||||||
|
#[bits(0..=2, rw)]
|
||||||
|
filter_type: Option<FilterType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[mmio(no_ctors)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct IoConfig {
|
||||||
|
port_a: [Config; NUM_PORT_A],
|
||||||
|
port_b: [Config; NUM_PORT_B],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
port_c: [Config; NUM_PORT_DEFAULT],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
port_d: [Config; NUM_PORT_DEFAULT],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
port_e: [Config; NUM_PORT_DEFAULT],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
port_f: [Config; NUM_PORT_DEFAULT],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
port_g: [Config; NUM_PORT_G],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
_reserved0: [u32; 0x8],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
clk_div_0: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_1: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_2: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_3: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_4: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_5: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_6: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
clk_div_7: u32,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
_reserved1: [u32; 0x387],
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
_reserved1: [u32; 0x3c7],
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
/// Reset value: 0x0282_07E9 for Vorago 4x, and 0x0182_07E1 for Vorago 1x
|
||||||
|
perid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<IoConfig>(), 0x1000);
|
||||||
|
|
||||||
|
impl IoConfig {
|
||||||
|
pub const fn new_mmio() -> MmioIoConfig<'static> {
|
||||||
|
MmioIoConfig {
|
||||||
|
ptr: BASE_ADDR as *mut _,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MmioIoConfig<'_> {
|
||||||
|
pub fn read_pin_config(&self, id: PinId) -> Config {
|
||||||
|
let offset = id.offset();
|
||||||
|
match id.port() {
|
||||||
|
crate::Port::A => unsafe { self.read_port_a_unchecked(offset) },
|
||||||
|
crate::Port::B => unsafe { self.read_port_b_unchecked(offset) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::C => unsafe { self.read_port_c_unchecked(offset) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::D => unsafe { self.read_port_d_unchecked(offset) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::E => unsafe { self.read_port_e_unchecked(offset) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::F => unsafe { self.read_port_f_unchecked(offset) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::G => unsafe { self.read_port_g_unchecked(offset) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn modify_pin_config<F: FnOnce(Config) -> Config>(&mut self, id: PinId, f: F) {
|
||||||
|
let config = self.read_pin_config(id);
|
||||||
|
self.write_pin_config(id, f(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_pin_config(&mut self, id: PinId, config: Config) {
|
||||||
|
let offset = id.offset();
|
||||||
|
match id.port() {
|
||||||
|
crate::Port::A => unsafe { self.write_port_a_unchecked(offset, config) },
|
||||||
|
crate::Port::B => unsafe { self.write_port_b_unchecked(offset, config) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::C => unsafe { self.write_port_c_unchecked(offset, config) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::D => unsafe { self.write_port_d_unchecked(offset, config) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::E => unsafe { self.write_port_e_unchecked(offset, config) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::F => unsafe { self.write_port_f_unchecked(offset, config) },
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
crate::Port::G => unsafe { self.write_port_g_unchecked(offset, config) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
227
src/lib.rs
Normal file
227
src/lib.rs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
#![no_std]
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub mod clock;
|
||||||
|
pub mod embassy;
|
||||||
|
pub mod gpio;
|
||||||
|
pub mod i2c;
|
||||||
|
pub mod ioconfig;
|
||||||
|
pub mod pins;
|
||||||
|
pub mod pwm;
|
||||||
|
pub mod spi;
|
||||||
|
pub mod sysconfig;
|
||||||
|
pub mod time;
|
||||||
|
pub mod timer;
|
||||||
|
pub mod uart;
|
||||||
|
|
||||||
|
pub use sysconfig::{
|
||||||
|
assert_peripheral_reset, deassert_peripheral_reset, disable_peripheral_clock,
|
||||||
|
enable_peripheral_clock, reset_peripheral_for_cycles,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "_family-selected"))]
|
||||||
|
compile_error!("no Vorago CPU family was select. Choices: vor1x or vor4x");
|
||||||
|
|
||||||
|
pub use ioconfig::regs::FunSel;
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use va416xx as pac;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum PeripheralSelect {
|
||||||
|
PortA = 0,
|
||||||
|
PortB = 1,
|
||||||
|
Spi0 = 4,
|
||||||
|
Spi1 = 5,
|
||||||
|
Spi2 = 6,
|
||||||
|
Uart0 = 8,
|
||||||
|
Uart1 = 9,
|
||||||
|
I2c0 = 16,
|
||||||
|
I2c1 = 17,
|
||||||
|
Irqsel = 21,
|
||||||
|
IoConfig = 22,
|
||||||
|
Utility = 23,
|
||||||
|
Gpio = 24,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum PeripheralSelect {
|
||||||
|
Spi0 = 0,
|
||||||
|
Spi1 = 1,
|
||||||
|
Spi2 = 2,
|
||||||
|
Spi3 = 3,
|
||||||
|
Uart0 = 4,
|
||||||
|
Uart1 = 5,
|
||||||
|
Uart2 = 6,
|
||||||
|
I2c0 = 7,
|
||||||
|
I2c1 = 8,
|
||||||
|
I2c2 = 9,
|
||||||
|
Can0 = 10,
|
||||||
|
Can1 = 11,
|
||||||
|
Rng = 12,
|
||||||
|
Adc = 13,
|
||||||
|
Dac = 14,
|
||||||
|
Dma = 15,
|
||||||
|
Ebi = 16,
|
||||||
|
Eth = 17,
|
||||||
|
Spw = 18,
|
||||||
|
Clkgen = 19,
|
||||||
|
IrqRouter = 20,
|
||||||
|
IoConfig = 21,
|
||||||
|
Utility = 22,
|
||||||
|
Watchdog = 23,
|
||||||
|
PortA = 24,
|
||||||
|
PortB = 25,
|
||||||
|
PortC = 26,
|
||||||
|
PortD = 27,
|
||||||
|
PortE = 28,
|
||||||
|
PortF = 29,
|
||||||
|
PortG = 30,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
/// Number of GPIO ports and IOCONFIG registers for PORT A
|
||||||
|
pub const NUM_PORT_A: usize = 32;
|
||||||
|
/// Number of GPIO ports and IOCONFIG registers for PORT B
|
||||||
|
pub const NUM_PORT_B: usize = 24;
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
/// Number of GPIO ports and IOCONFIG registers for PORT C to Port F
|
||||||
|
pub const NUM_PORT_DEFAULT: usize = 16;
|
||||||
|
/// Number of GPIO ports and IOCONFIG registers for PORT A
|
||||||
|
pub const NUM_PORT_A: usize = NUM_PORT_DEFAULT;
|
||||||
|
/// Number of GPIO ports and IOCONFIG registers for PORT B
|
||||||
|
pub const NUM_PORT_B: usize = NUM_PORT_DEFAULT;
|
||||||
|
/// Number of GPIO ports and IOCONFIG registers for PORT G
|
||||||
|
pub const NUM_PORT_G: usize = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GPIO port enumeration.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Port {
|
||||||
|
A = 0,
|
||||||
|
B = 1,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
C = 2,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
D = 3,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
E = 4,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
F = 5,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
G = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Port {
|
||||||
|
pub const fn max_offset(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Port::A => NUM_PORT_A,
|
||||||
|
Port::B => NUM_PORT_B,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::C | Port::D | Port::E | Port::F => NUM_PORT_DEFAULT,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Port::G => NUM_PORT_G,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unsafely steal the GPIO peripheral block for the given port.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents ownership and safety guarantees by the HAL.
|
||||||
|
pub unsafe fn steal_gpio(&self) -> gpio::regs::MmioGpio<'static> {
|
||||||
|
gpio::regs::Gpio::new_mmio(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[error("invalid GPIO offset {offset} for port {port:?}")]
|
||||||
|
pub struct InvalidOffsetError {
|
||||||
|
offset: usize,
|
||||||
|
port: Port,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic interrupt config which can be used to specify whether the HAL driver will
|
||||||
|
/// use the IRQSEL register to route an interrupt, and whether the IRQ will be unmasked in the
|
||||||
|
/// Cortex-M0 NVIC. Both are generally necessary for IRQs to work, but the user might want to
|
||||||
|
/// perform those steps themselves.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct InterruptConfig {
|
||||||
|
/// Interrupt target vector. Should always be set, might be required for disabling IRQs
|
||||||
|
pub id: va108xx::Interrupt,
|
||||||
|
/// Specfiy whether IRQ should be routed to an IRQ vector using the IRQSEL peripheral.
|
||||||
|
pub route: bool,
|
||||||
|
/// Specify whether the IRQ is unmasked in the Cortex-M NVIC. If an interrupt is used for
|
||||||
|
/// multiple purposes, the user can enable the interrupts themselves.
|
||||||
|
pub enable_in_nvic: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
impl InterruptConfig {
|
||||||
|
pub fn new(id: va108xx::Interrupt, route: bool, enable_in_nvic: bool) -> Self {
|
||||||
|
InterruptConfig {
|
||||||
|
id,
|
||||||
|
route,
|
||||||
|
enable_in_nvic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable a specific interrupt using the NVIC peripheral.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn enable_nvic_interrupt(irq: pac::Interrupt) {
|
||||||
|
unsafe {
|
||||||
|
cortex_m::peripheral::NVIC::unmask(irq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable a specific interrupt using the NVIC peripheral.
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_nvic_interrupt(irq: pac::Interrupt) {
|
||||||
|
cortex_m::peripheral::NVIC::mask(irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) mod sealed {
|
||||||
|
pub trait Sealed {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod shared {
|
||||||
|
use arbitrary_int::u5;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TriggerLevel(arbitrary_int::UInt<u32, 5>);
|
||||||
|
|
||||||
|
impl TriggerLevel {
|
||||||
|
pub const fn new(value: u5) -> Self {
|
||||||
|
TriggerLevel(arbitrary_int::UInt::<u32, 5>::new(value.value() as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn value(&self) -> u5 {
|
||||||
|
u5::new(self.0.value() as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FifoClear {
|
||||||
|
#[bit(1, w)]
|
||||||
|
tx_fifo: bool,
|
||||||
|
#[bit(0, w)]
|
||||||
|
rx_fifo: bool,
|
||||||
|
}
|
||||||
|
}
|
718
src/pins.rs
Normal file
718
src/pins.rs
Normal file
@ -0,0 +1,718 @@
|
|||||||
|
use crate::sysconfig::reset_peripheral_for_cycles;
|
||||||
|
|
||||||
|
pub use crate::gpio::{Pin, PinId, PinIdProvider, Port};
|
||||||
|
|
||||||
|
use crate::PeripheralSelect;
|
||||||
|
use crate::sealed::Sealed;
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use va416xx as pac;
|
||||||
|
|
||||||
|
pub trait PinMarker: Sealed {
|
||||||
|
const ID: PinId;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! pin_id {
|
||||||
|
($Id:ident, $Port:path, $num:literal) => {
|
||||||
|
// Need paste macro to use ident in doc attribute
|
||||||
|
paste::paste! {
|
||||||
|
#[doc = "Pin ID representing pin " $Id]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum $Id {}
|
||||||
|
|
||||||
|
impl $crate::sealed::Sealed for $Id {}
|
||||||
|
impl PinIdProvider for $Id {
|
||||||
|
const ID: PinId = PinId::new_unchecked($Port, $num);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinMarker for Pin<$Id> {
|
||||||
|
const ID: PinId = $Id::ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: PinIdProvider + Sealed> Sealed for Pin<I> {}
|
||||||
|
|
||||||
|
pin_id!(Pa0, Port::A, 0);
|
||||||
|
pin_id!(Pa1, Port::A, 1);
|
||||||
|
pin_id!(Pa2, Port::A, 2);
|
||||||
|
pin_id!(Pa3, Port::A, 3);
|
||||||
|
pin_id!(Pa4, Port::A, 4);
|
||||||
|
pin_id!(Pa5, Port::A, 5);
|
||||||
|
pin_id!(Pa6, Port::A, 6);
|
||||||
|
pin_id!(Pa7, Port::A, 7);
|
||||||
|
pin_id!(Pa8, Port::A, 8);
|
||||||
|
pin_id!(Pa9, Port::A, 9);
|
||||||
|
pin_id!(Pa10, Port::A, 10);
|
||||||
|
pin_id!(Pa11, Port::A, 11);
|
||||||
|
pin_id!(Pa12, Port::A, 12);
|
||||||
|
pin_id!(Pa13, Port::A, 13);
|
||||||
|
pin_id!(Pa14, Port::A, 14);
|
||||||
|
pin_id!(Pa15, Port::A, 15);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa16, Port::A, 16);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa17, Port::A, 17);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa18, Port::A, 18);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa19, Port::A, 19);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa20, Port::A, 20);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa21, Port::A, 21);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa22, Port::A, 22);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa23, Port::A, 23);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa24, Port::A, 24);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa25, Port::A, 25);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa26, Port::A, 26);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa27, Port::A, 27);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa28, Port::A, 28);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa29, Port::A, 29);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa30, Port::A, 30);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pa31, Port::A, 31);
|
||||||
|
|
||||||
|
pin_id!(Pb0, Port::B, 0);
|
||||||
|
pin_id!(Pb1, Port::B, 1);
|
||||||
|
pin_id!(Pb2, Port::B, 2);
|
||||||
|
pin_id!(Pb3, Port::B, 3);
|
||||||
|
pin_id!(Pb4, Port::B, 4);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb5, Port::B, 5);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb6, Port::B, 6);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb7, Port::B, 7);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb8, Port::B, 8);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb9, Port::B, 9);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb10, Port::B, 10);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pb11, Port::B, 11);
|
||||||
|
pin_id!(Pb12, Port::B, 12);
|
||||||
|
pin_id!(Pb13, Port::B, 13);
|
||||||
|
pin_id!(Pb14, Port::B, 14);
|
||||||
|
pin_id!(Pb15, Port::B, 15);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb16, Port::B, 16);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb17, Port::B, 17);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb18, Port::B, 18);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb19, Port::B, 19);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb20, Port::B, 20);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb21, Port::B, 21);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb22, Port::B, 22);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pin_id!(Pb23, Port::B, 23);
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor4x")] {
|
||||||
|
pin_id!(Pc0, Port::C, 0);
|
||||||
|
pin_id!(Pc1, Port::C, 1);
|
||||||
|
pin_id!(Pc2, Port::C, 2);
|
||||||
|
pin_id!(Pc3, Port::C, 3);
|
||||||
|
pin_id!(Pc4, Port::C, 4);
|
||||||
|
pin_id!(Pc5, Port::C, 5);
|
||||||
|
pin_id!(Pc6, Port::C, 6);
|
||||||
|
pin_id!(Pc7, Port::C, 7);
|
||||||
|
pin_id!(Pc8, Port::C, 8);
|
||||||
|
pin_id!(Pc9, Port::C, 9);
|
||||||
|
pin_id!(Pc10, Port::C, 10);
|
||||||
|
pin_id!(Pc11, Port::C, 11);
|
||||||
|
pin_id!(Pc12, Port::C, 12);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pc13, Port::C, 13);
|
||||||
|
pin_id!(Pc14, Port::C, 14);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pc15, Port::C, 15);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd0, Port::D, 0);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd1, Port::D, 1);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd2, Port::D, 2);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd3, Port::D, 3);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd4, Port::D, 4);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd5, Port::D, 5);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd6, Port::D, 6);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd7, Port::D, 7);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd8, Port::D, 8);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pd9, Port::D, 9);
|
||||||
|
pin_id!(Pd10, Port::D, 10);
|
||||||
|
pin_id!(Pd11, Port::D, 11);
|
||||||
|
pin_id!(Pd12, Port::D, 12);
|
||||||
|
pin_id!(Pd13, Port::D, 13);
|
||||||
|
pin_id!(Pd14, Port::D, 14);
|
||||||
|
pin_id!(Pd15, Port::D, 15);
|
||||||
|
|
||||||
|
pin_id!(Pe0, Port::E, 0);
|
||||||
|
pin_id!(Pe1, Port::E, 1);
|
||||||
|
pin_id!(Pe2, Port::E, 2);
|
||||||
|
pin_id!(Pe3, Port::E, 3);
|
||||||
|
pin_id!(Pe4, Port::E, 4);
|
||||||
|
pin_id!(Pe5, Port::E, 5);
|
||||||
|
pin_id!(Pe6, Port::E, 6);
|
||||||
|
pin_id!(Pe7, Port::E, 7);
|
||||||
|
pin_id!(Pe8, Port::E, 8);
|
||||||
|
pin_id!(Pe9, Port::E, 9);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pe10, Port::E, 10);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pe11, Port::E, 11);
|
||||||
|
pin_id!(Pe12, Port::E, 12);
|
||||||
|
pin_id!(Pe13, Port::E, 13);
|
||||||
|
pin_id!(Pe14, Port::E, 14);
|
||||||
|
pin_id!(Pe15, Port::E, 15);
|
||||||
|
|
||||||
|
pin_id!(Pf0, Port::F, 0);
|
||||||
|
pin_id!(Pf1, Port::F, 1);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf2, Port::F, 2);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf3, Port::F, 3);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf4, Port::F, 4);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf5, Port::F, 5);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf6, Port::F, 6);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf7, Port::F, 7);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf8, Port::F, 8);
|
||||||
|
pin_id!(Pf9, Port::F, 9);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_id!(Pf10, Port::F, 10);
|
||||||
|
pin_id!(Pf11, Port::F, 11);
|
||||||
|
pin_id!(Pf12, Port::F, 12);
|
||||||
|
pin_id!(Pf13, Port::F, 13);
|
||||||
|
pin_id!(Pf14, Port::F, 14);
|
||||||
|
pin_id!(Pf15, Port::F, 15);
|
||||||
|
|
||||||
|
pin_id!(Pg0, Port::G, 0);
|
||||||
|
pin_id!(Pg1, Port::G, 1);
|
||||||
|
pin_id!(Pg2, Port::G, 2);
|
||||||
|
pin_id!(Pg3, Port::G, 3);
|
||||||
|
pin_id!(Pg4, Port::G, 4);
|
||||||
|
pin_id!(Pg5, Port::G, 5);
|
||||||
|
pin_id!(Pg6, Port::G, 6);
|
||||||
|
pin_id!(Pg7, Port::G, 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource management singleton for GPIO PORT A.
|
||||||
|
pub struct PinsA {
|
||||||
|
pub pa0: Pin<Pa0>,
|
||||||
|
pub pa1: Pin<Pa1>,
|
||||||
|
pub pa2: Pin<Pa2>,
|
||||||
|
pub pa3: Pin<Pa3>,
|
||||||
|
pub pa4: Pin<Pa4>,
|
||||||
|
pub pa5: Pin<Pa5>,
|
||||||
|
pub pa6: Pin<Pa6>,
|
||||||
|
pub pa7: Pin<Pa7>,
|
||||||
|
pub pa8: Pin<Pa8>,
|
||||||
|
pub pa9: Pin<Pa9>,
|
||||||
|
pub pa10: Pin<Pa10>,
|
||||||
|
pub pa11: Pin<Pa11>,
|
||||||
|
pub pa12: Pin<Pa12>,
|
||||||
|
pub pa13: Pin<Pa13>,
|
||||||
|
pub pa14: Pin<Pa14>,
|
||||||
|
pub pa15: Pin<Pa15>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa16: Pin<Pa16>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa17: Pin<Pa17>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa18: Pin<Pa18>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa19: Pin<Pa19>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa20: Pin<Pa20>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa21: Pin<Pa21>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa22: Pin<Pa22>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa23: Pin<Pa23>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa24: Pin<Pa24>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa25: Pin<Pa25>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa26: Pin<Pa26>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa27: Pin<Pa27>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa28: Pin<Pa28>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa29: Pin<Pa29>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa30: Pin<Pa30>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pa31: Pin<Pa31>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsA {
|
||||||
|
pub fn new(_port_a: pac::Porta) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortA, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.porta().set_bit();
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
w.gpio().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
pa0: Pin::__new(),
|
||||||
|
pa1: Pin::__new(),
|
||||||
|
pa2: Pin::__new(),
|
||||||
|
pa3: Pin::__new(),
|
||||||
|
pa4: Pin::__new(),
|
||||||
|
pa5: Pin::__new(),
|
||||||
|
pa6: Pin::__new(),
|
||||||
|
pa7: Pin::__new(),
|
||||||
|
pa8: Pin::__new(),
|
||||||
|
pa9: Pin::__new(),
|
||||||
|
pa10: Pin::__new(),
|
||||||
|
pa11: Pin::__new(),
|
||||||
|
pa12: Pin::__new(),
|
||||||
|
pa13: Pin::__new(),
|
||||||
|
pa14: Pin::__new(),
|
||||||
|
pa15: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa16: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa17: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa18: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa19: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa20: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa21: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa22: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa23: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa24: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa25: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa26: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa27: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa28: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa29: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa30: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pa31: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource management singleton for GPIO PORT B.
|
||||||
|
pub struct PinsB {
|
||||||
|
pub pb0: Pin<Pb0>,
|
||||||
|
pub pb1: Pin<Pb1>,
|
||||||
|
pub pb2: Pin<Pb2>,
|
||||||
|
pub pb3: Pin<Pb3>,
|
||||||
|
pub pb4: Pin<Pb4>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb5: Pin<Pb5>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb6: Pin<Pb6>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb7: Pin<Pb7>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb8: Pin<Pb8>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb9: Pin<Pb9>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb10: Pin<Pb10>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pb11: Pin<Pb11>,
|
||||||
|
pub pb12: Pin<Pb12>,
|
||||||
|
pub pb13: Pin<Pb13>,
|
||||||
|
pub pb14: Pin<Pb14>,
|
||||||
|
pub pb15: Pin<Pb15>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb16: Pin<Pb16>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb17: Pin<Pb17>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb18: Pin<Pb18>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb19: Pin<Pb19>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb20: Pin<Pb20>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb21: Pin<Pb21>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb22: Pin<Pb22>,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub pb23: Pin<Pb23>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsB {
|
||||||
|
pub fn new(_port_b: pac::Portb) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortB, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.portb().set_bit();
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
w.gpio().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
pb0: Pin::__new(),
|
||||||
|
pb1: Pin::__new(),
|
||||||
|
pb2: Pin::__new(),
|
||||||
|
pb3: Pin::__new(),
|
||||||
|
pb4: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb5: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb6: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb7: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb8: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb9: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb10: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pb11: Pin::__new(),
|
||||||
|
pb12: Pin::__new(),
|
||||||
|
pb13: Pin::__new(),
|
||||||
|
pb14: Pin::__new(),
|
||||||
|
pb15: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb16: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb17: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb18: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb19: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb20: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb21: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb22: Pin::__new(),
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pb23: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor4x")] {
|
||||||
|
/// Resource management singleton for GPIO PORT C.
|
||||||
|
pub struct PinsC {
|
||||||
|
pub pc0: Pin<Pc0>,
|
||||||
|
pub pc1: Pin<Pc1>,
|
||||||
|
pub pc2: Pin<Pc2>,
|
||||||
|
pub pc3: Pin<Pc3>,
|
||||||
|
pub pc4: Pin<Pc4>,
|
||||||
|
pub pc5: Pin<Pc5>,
|
||||||
|
pub pc6: Pin<Pc6>,
|
||||||
|
pub pc7: Pin<Pc7>,
|
||||||
|
pub pc8: Pin<Pc8>,
|
||||||
|
pub pc9: Pin<Pc9>,
|
||||||
|
pub pc10: Pin<Pc10>,
|
||||||
|
pub pc11: Pin<Pc11>,
|
||||||
|
pub pc12: Pin<Pc12>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pc13: Pin<Pc13>,
|
||||||
|
pub pc14: Pin<Pc14>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pc15: Pin<Pc15>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsC {
|
||||||
|
pub fn new(_port_c: pac::Portc) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortC, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.portc().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
pc0: Pin::__new(),
|
||||||
|
pc1: Pin::__new(),
|
||||||
|
pc2: Pin::__new(),
|
||||||
|
pc3: Pin::__new(),
|
||||||
|
pc4: Pin::__new(),
|
||||||
|
pc5: Pin::__new(),
|
||||||
|
pc6: Pin::__new(),
|
||||||
|
pc7: Pin::__new(),
|
||||||
|
pc8: Pin::__new(),
|
||||||
|
pc9: Pin::__new(),
|
||||||
|
pc10: Pin::__new(),
|
||||||
|
pc11: Pin::__new(),
|
||||||
|
pc12: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pc13: Pin::__new(),
|
||||||
|
pc14: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pc15: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource management singleton for GPIO PORT D.
|
||||||
|
pub struct PinsD {
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd0: Pin<Pd0>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd1: Pin<Pd1>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd2: Pin<Pd2>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd3: Pin<Pd3>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd4: Pin<Pd4>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd5: Pin<Pd5>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd6: Pin<Pd6>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd7: Pin<Pd7>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd8: Pin<Pd8>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pd9: Pin<Pd9>,
|
||||||
|
pub pd10: Pin<Pd10>,
|
||||||
|
pub pd11: Pin<Pd11>,
|
||||||
|
pub pd12: Pin<Pd12>,
|
||||||
|
pub pd13: Pin<Pd13>,
|
||||||
|
pub pd14: Pin<Pd14>,
|
||||||
|
pub pd15: Pin<Pd15>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsD {
|
||||||
|
pub fn new(_port_d: pac::Portd) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortD, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.portd().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd0: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd1: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd2: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd3: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd4: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd5: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd6: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd7: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd8: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pd9: Pin::__new(),
|
||||||
|
pd10: Pin::__new(),
|
||||||
|
pd11: Pin::__new(),
|
||||||
|
pd12: Pin::__new(),
|
||||||
|
pd13: Pin::__new(),
|
||||||
|
pd14: Pin::__new(),
|
||||||
|
pd15: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource management singleton for GPIO PORT E.
|
||||||
|
pub struct PinsE {
|
||||||
|
pub pe0: Pin<Pe0>,
|
||||||
|
pub pe1: Pin<Pe1>,
|
||||||
|
pub pe2: Pin<Pe2>,
|
||||||
|
pub pe3: Pin<Pe3>,
|
||||||
|
pub pe4: Pin<Pe4>,
|
||||||
|
pub pe5: Pin<Pe5>,
|
||||||
|
pub pe6: Pin<Pe6>,
|
||||||
|
pub pe7: Pin<Pe7>,
|
||||||
|
pub pe8: Pin<Pe8>,
|
||||||
|
pub pe9: Pin<Pe9>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pe10: Pin<Pe10>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pe11: Pin<Pe11>,
|
||||||
|
pub pe12: Pin<Pe12>,
|
||||||
|
pub pe13: Pin<Pe13>,
|
||||||
|
pub pe14: Pin<Pe14>,
|
||||||
|
pub pe15: Pin<Pe15>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsE {
|
||||||
|
pub fn new(_port_e: pac::Porte) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortE, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.porte().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
pe0: Pin::__new(),
|
||||||
|
pe1: Pin::__new(),
|
||||||
|
pe2: Pin::__new(),
|
||||||
|
pe3: Pin::__new(),
|
||||||
|
pe4: Pin::__new(),
|
||||||
|
pe5: Pin::__new(),
|
||||||
|
pe6: Pin::__new(),
|
||||||
|
pe7: Pin::__new(),
|
||||||
|
pe8: Pin::__new(),
|
||||||
|
pe9: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pe10: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pe11: Pin::__new(),
|
||||||
|
pe12: Pin::__new(),
|
||||||
|
pe13: Pin::__new(),
|
||||||
|
pe14: Pin::__new(),
|
||||||
|
pe15: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource management singleton for GPIO PORT F.
|
||||||
|
pub struct PinsF {
|
||||||
|
pub pf0: Pin<Pf0>,
|
||||||
|
pub pf1: Pin<Pf1>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf2: Pin<Pf2>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf3: Pin<Pf3>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf4: Pin<Pf4>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf5: Pin<Pf5>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf6: Pin<Pf6>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf7: Pin<Pf7>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf8: Pin<Pf8>,
|
||||||
|
pub pf9: Pin<Pf9>,
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pub pf10: Pin<Pf10>,
|
||||||
|
pub pf11: Pin<Pf11>,
|
||||||
|
pub pf12: Pin<Pf12>,
|
||||||
|
pub pf13: Pin<Pf13>,
|
||||||
|
pub pf14: Pin<Pf14>,
|
||||||
|
pub pf15: Pin<Pf15>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsF {
|
||||||
|
pub fn new(_port_f: pac::Portf) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortF, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.portf().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
pf0: Pin::__new(),
|
||||||
|
pf1: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf2: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf3: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf4: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf5: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf6: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf7: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf8: Pin::__new(),
|
||||||
|
pf9: Pin::__new(),
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pf10: Pin::__new(),
|
||||||
|
pf11: Pin::__new(),
|
||||||
|
pf12: Pin::__new(),
|
||||||
|
pf13: Pin::__new(),
|
||||||
|
pf14: Pin::__new(),
|
||||||
|
pf15: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resource management singleton for GPIO PORT G.
|
||||||
|
pub struct PinsG {
|
||||||
|
pub pg0: Pin<Pg0>,
|
||||||
|
pub pg1: Pin<Pg1>,
|
||||||
|
pub pg2: Pin<Pg2>,
|
||||||
|
pub pg3: Pin<Pg3>,
|
||||||
|
pub pg4: Pin<Pg4>,
|
||||||
|
pub pg5: Pin<Pg5>,
|
||||||
|
pub pg6: Pin<Pg6>,
|
||||||
|
pub pg7: Pin<Pg7>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinsG {
|
||||||
|
pub fn new(_port_g: pac::Portg) -> Self {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
reset_peripheral_for_cycles(PeripheralSelect::PortG, 2);
|
||||||
|
syscfg.peripheral_clk_enable().modify(|_, w| {
|
||||||
|
w.portg().set_bit();
|
||||||
|
w.ioconfig().set_bit()
|
||||||
|
});
|
||||||
|
Self {
|
||||||
|
pg0: Pin::__new(),
|
||||||
|
pg1: Pin::__new(),
|
||||||
|
pg2: Pin::__new(),
|
||||||
|
pg3: Pin::__new(),
|
||||||
|
pg4: Pin::__new(),
|
||||||
|
pg5: Pin::__new(),
|
||||||
|
pg6: Pin::__new(),
|
||||||
|
pg7: Pin::__new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
254
src/pwm.rs
Normal file
254
src/pwm.rs
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
use core::convert::Infallible;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::gpio::IoPeriphPin;
|
||||||
|
use crate::timer::enable_tim_clk;
|
||||||
|
use crate::timer::regs::{EnableControl, StatusSelect};
|
||||||
|
use crate::{PeripheralSelect, enable_peripheral_clock};
|
||||||
|
|
||||||
|
use crate::time::Hertz;
|
||||||
|
use crate::timer::{self, TimId, TimMarker, TimPin};
|
||||||
|
|
||||||
|
const DUTY_MAX: u16 = u16::MAX;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum PwmA {}
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum PwmB {}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[error("pin tim ID {pin_tim:?} and timer tim id {tim_id:?} do not match")]
|
||||||
|
pub struct TimMissmatchError {
|
||||||
|
pin_tim: TimId,
|
||||||
|
tim_id: TimId,
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// PWM pin
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Reduced version where type information is deleted
|
||||||
|
pub struct PwmPin<Mode = PwmA> {
|
||||||
|
tim_id: TimId,
|
||||||
|
regs: timer::regs::MmioTimer<'static>,
|
||||||
|
ref_clk: Hertz,
|
||||||
|
/// For PWMB, this is the upper limit
|
||||||
|
current_duty: u16,
|
||||||
|
/// For PWMA, this value will not be used
|
||||||
|
current_lower_limit: u16,
|
||||||
|
current_period: Hertz,
|
||||||
|
current_rst_val: u32,
|
||||||
|
mode: PhantomData<Mode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Mode> PwmPin<Mode> {
|
||||||
|
/// Create a new PWM pin
|
||||||
|
pub fn new<Pin: TimPin, Tim: TimMarker>(
|
||||||
|
_pin: Pin,
|
||||||
|
_tim: Tim,
|
||||||
|
#[cfg(feature = "vor1x")] sys_clk: Hertz,
|
||||||
|
#[cfg(feature = "vor4x")] clks: &crate::clock::Clocks,
|
||||||
|
initial_frequency: Hertz,
|
||||||
|
) -> Result<Self, TimMissmatchError> {
|
||||||
|
if Pin::TIM_ID != Tim::ID {
|
||||||
|
return Err(TimMissmatchError {
|
||||||
|
pin_tim: Pin::TIM_ID,
|
||||||
|
tim_id: Tim::ID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
IoPeriphPin::new(Pin::PIN_ID, Pin::FUN_SEL, None);
|
||||||
|
let mut pin = PwmPin {
|
||||||
|
tim_id: Tim::ID,
|
||||||
|
regs: timer::regs::Timer::new_mmio(Tim::ID),
|
||||||
|
current_duty: 0,
|
||||||
|
current_lower_limit: 0,
|
||||||
|
current_period: initial_frequency,
|
||||||
|
current_rst_val: 0,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
ref_clk: sys_clk,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
ref_clk: clks.apb1(),
|
||||||
|
mode: PhantomData,
|
||||||
|
};
|
||||||
|
// For Vorago 4x, the presence of the pin structure ensures that its respective peripheral
|
||||||
|
// clock was already enabled.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
enable_peripheral_clock(PeripheralSelect::Gpio);
|
||||||
|
enable_peripheral_clock(PeripheralSelect::IoConfig);
|
||||||
|
enable_tim_clk(Tim::ID);
|
||||||
|
pin.enable_pwm_a();
|
||||||
|
pin.set_period(initial_frequency);
|
||||||
|
Ok(pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable_pwm_a(&mut self) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_status_sel(StatusSelect::PwmaOutput);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn enable_pwm_b(&mut self) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_status_sel(StatusSelect::PwmbOutput);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_period(&self) -> Hertz {
|
||||||
|
self.current_period
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn set_period(&mut self, period: impl Into<Hertz>) {
|
||||||
|
self.current_period = period.into();
|
||||||
|
// Avoid division by 0
|
||||||
|
if self.current_period.raw() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.current_rst_val = self.ref_clk.raw() / self.current_period.raw();
|
||||||
|
self.regs.write_reset_value(self.current_rst_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.regs.write_enable_control(EnableControl::new_disable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.regs.write_enable_control(EnableControl::new_enable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn period(&self) -> Hertz {
|
||||||
|
self.current_period
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn duty(&self) -> u16 {
|
||||||
|
self.current_duty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PwmPin<PwmA>> for PwmPin<PwmB> {
|
||||||
|
fn from(other: PwmPin<PwmA>) -> Self {
|
||||||
|
let mut pwmb = Self {
|
||||||
|
mode: PhantomData,
|
||||||
|
regs: other.regs,
|
||||||
|
tim_id: other.tim_id,
|
||||||
|
ref_clk: other.ref_clk,
|
||||||
|
current_duty: other.current_duty,
|
||||||
|
current_lower_limit: other.current_lower_limit,
|
||||||
|
current_period: other.current_period,
|
||||||
|
current_rst_val: other.current_rst_val,
|
||||||
|
};
|
||||||
|
pwmb.enable_pwm_b();
|
||||||
|
pwmb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PwmPin<PwmB>> for PwmPin<PwmA> {
|
||||||
|
fn from(other: PwmPin<PwmB>) -> Self {
|
||||||
|
let mut pwmb = Self {
|
||||||
|
mode: PhantomData,
|
||||||
|
tim_id: other.tim_id,
|
||||||
|
regs: other.regs,
|
||||||
|
ref_clk: other.ref_clk,
|
||||||
|
current_duty: other.current_duty,
|
||||||
|
current_lower_limit: other.current_lower_limit,
|
||||||
|
current_period: other.current_period,
|
||||||
|
current_rst_val: other.current_rst_val,
|
||||||
|
};
|
||||||
|
pwmb.enable_pwm_a();
|
||||||
|
pwmb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// PWMB implementations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
impl PwmPin<PwmB> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn pwmb_lower_limit(&self) -> u16 {
|
||||||
|
self.current_lower_limit
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn pwmb_upper_limit(&self) -> u16 {
|
||||||
|
self.current_duty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the lower limit for PWMB
|
||||||
|
///
|
||||||
|
/// The PWM signal will be 1 as long as the current RST counter is larger than
|
||||||
|
/// the lower limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||||
|
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||||
|
/// state
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_pwmb_lower_limit(&mut self, duty: u16) {
|
||||||
|
self.current_lower_limit = duty;
|
||||||
|
let pwmb_val: u64 =
|
||||||
|
(self.current_rst_val as u64 * self.current_lower_limit as u64) / DUTY_MAX as u64;
|
||||||
|
self.regs.write_pwmb_value(pwmb_val as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the higher limit for PWMB
|
||||||
|
///
|
||||||
|
/// The PWM signal will be 1 as long as the current RST counter is smaller than
|
||||||
|
/// the higher limit. For example, with a lower limit of 0.5 and and an upper limit
|
||||||
|
/// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high
|
||||||
|
/// state
|
||||||
|
pub fn set_pwmb_upper_limit(&mut self, duty: u16) {
|
||||||
|
self.current_duty = duty;
|
||||||
|
let pwma_val: u64 =
|
||||||
|
(self.current_rst_val as u64 * self.current_duty as u64) / DUTY_MAX as u64;
|
||||||
|
self.regs.write_pwma_value(pwma_val as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Embedded HAL implementation: PWMA only
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
impl embedded_hal::pwm::ErrorType for PwmPin {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_hal::pwm::SetDutyCycle for PwmPin {
|
||||||
|
#[inline]
|
||||||
|
fn max_duty_cycle(&self) -> u16 {
|
||||||
|
DUTY_MAX
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> {
|
||||||
|
self.current_duty = duty;
|
||||||
|
let pwma_val: u64 = (self.current_rst_val as u64
|
||||||
|
* (DUTY_MAX as u64 - self.current_duty as u64))
|
||||||
|
/ DUTY_MAX as u64;
|
||||||
|
self.regs.write_pwma_value(pwma_val as u32);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the corresponding u16 duty cycle from a percent value ranging between 0.0 and 1.0.
|
||||||
|
///
|
||||||
|
/// Please note that this might load a lot of floating point code because this processor does not
|
||||||
|
/// have a FPU
|
||||||
|
pub fn get_duty_from_percent(percent: f32) -> u16 {
|
||||||
|
if percent > 1.0 {
|
||||||
|
DUTY_MAX
|
||||||
|
} else if percent <= 0.0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
(percent * DUTY_MAX as f32) as u16
|
||||||
|
}
|
||||||
|
}
|
952
src/spi/mod.rs
Normal file
952
src/spi/mod.rs
Normal file
@ -0,0 +1,952 @@
|
|||||||
|
use crate::FunSel;
|
||||||
|
use crate::gpio::{IoPeriphPin, PinId};
|
||||||
|
use crate::{
|
||||||
|
PeripheralSelect, enable_peripheral_clock, pins::PinMarker, sealed::Sealed, time::Hertz,
|
||||||
|
};
|
||||||
|
use core::{convert::Infallible, fmt::Debug, marker::PhantomData};
|
||||||
|
use embedded_hal::spi::{MODE_0, Mode};
|
||||||
|
|
||||||
|
use regs::{ClkPrescaler, Data, FifoClear, WordSize};
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use va416xx as pac;
|
||||||
|
|
||||||
|
pub use regs::{Bank, HwChipSelectId};
|
||||||
|
|
||||||
|
pub mod regs;
|
||||||
|
|
||||||
|
pub fn configure_pin_as_hw_cs_pin<P: PinMarker + HwCsProvider>(_pin: P) -> HwChipSelectId {
|
||||||
|
IoPeriphPin::new(P::ID, P::FUN_SEL, None);
|
||||||
|
P::CS_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Pins and traits.
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait PinSck: PinMarker {
|
||||||
|
const SPI_ID: Bank;
|
||||||
|
const FUN_SEL: FunSel;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PinMosi: PinMarker {
|
||||||
|
const SPI_ID: Bank;
|
||||||
|
const FUN_SEL: FunSel;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PinMiso: PinMarker {
|
||||||
|
const SPI_ID: Bank;
|
||||||
|
const FUN_SEL: FunSel;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait HwCsProvider {
|
||||||
|
const PIN_ID: PinId;
|
||||||
|
const SPI_ID: Bank;
|
||||||
|
const FUN_SEL: FunSel;
|
||||||
|
const CS_ID: HwChipSelectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros {
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
macro_rules! hw_cs_multi_pin {
|
||||||
|
(
|
||||||
|
// name of the newtype wrapper struct
|
||||||
|
$name:ident,
|
||||||
|
// Pb0
|
||||||
|
$pin_id:ident,
|
||||||
|
// SpiId::B
|
||||||
|
$spi_id:path,
|
||||||
|
// FunSel::Sel1
|
||||||
|
$fun_sel:path,
|
||||||
|
// HwChipSelectId::Id2
|
||||||
|
$cs_id:path
|
||||||
|
) => {
|
||||||
|
#[doc = concat!(
|
||||||
|
"Newtype wrapper to use [Pin] [`", stringify!($pin_id), "`] as a HW CS pin for [`", stringify!($spi_id), "`] with [`", stringify!($cs_id), "`]."
|
||||||
|
)]
|
||||||
|
pub struct $name(Pin<$pin_id>);
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
pub fn new(pin: Pin<$pin_id>) -> Self {
|
||||||
|
Self(pin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::sealed::Sealed for $name {}
|
||||||
|
|
||||||
|
impl HwCsProvider for $name {
|
||||||
|
const PIN_ID: PinId = <$pin_id as PinIdProvider>::ID;
|
||||||
|
const SPI_ID: Bank = $spi_id;
|
||||||
|
const FUN_SEL: FunSel = $fun_sel;
|
||||||
|
const CS_ID: HwChipSelectId = $cs_id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! hw_cs_pins {
|
||||||
|
($SpiId:path, $(($Px:ident, $FunSel:path, $HwCsIdent:path)$(,)?)+) => {
|
||||||
|
$(
|
||||||
|
impl HwCsProvider for Pin<$Px> {
|
||||||
|
const PIN_ID: PinId = $Px::ID;
|
||||||
|
const SPI_ID: Bank = $SpiId;
|
||||||
|
const FUN_SEL: FunSel = $FunSel;
|
||||||
|
const CS_ID: HwChipSelectId = $HwCsIdent;
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub mod pins_vor1x;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub mod pins_vor4x;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Defintions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
// FIFO has a depth of 16.
|
||||||
|
const FILL_DEPTH: usize = 12;
|
||||||
|
|
||||||
|
pub const BMSTART_BMSTOP_MASK: u32 = 1 << 31;
|
||||||
|
pub const BMSKIPDATA_MASK: u32 = 1 << 30;
|
||||||
|
|
||||||
|
pub const DEFAULT_CLK_DIV: u16 = 2;
|
||||||
|
|
||||||
|
/// Common trait implemented by all PAC peripheral access structures. The register block
|
||||||
|
/// format is the same for all SPI blocks.
|
||||||
|
pub trait SpiMarker: Sealed {
|
||||||
|
const ID: Bank;
|
||||||
|
const PERIPH_SEL: PeripheralSelect;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub type Spi0 = pac::Spia;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub type Spi0 = pac::Spi0;
|
||||||
|
|
||||||
|
impl SpiMarker for Spi0 {
|
||||||
|
const ID: Bank = Bank::Spi0;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi0;
|
||||||
|
}
|
||||||
|
impl Sealed for Spi0 {}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub type Spi1 = pac::Spib;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub type Spi1 = pac::Spi1;
|
||||||
|
|
||||||
|
impl SpiMarker for Spi1 {
|
||||||
|
const ID: Bank = Bank::Spi1;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi1;
|
||||||
|
}
|
||||||
|
impl Sealed for Spi1 {}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub type Spi2 = pac::Spic;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub type Spi2 = pac::Spi2;
|
||||||
|
|
||||||
|
impl SpiMarker for Spi2 {
|
||||||
|
const ID: Bank = Bank::Spi2;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi2;
|
||||||
|
}
|
||||||
|
impl Sealed for Spi2 {}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
impl SpiMarker for pac::Spi3 {
|
||||||
|
const ID: Bank = Bank::Spi3;
|
||||||
|
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Spi3;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
impl Sealed for pac::Spi3 {}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Config
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait TransferConfigProvider {
|
||||||
|
fn sod(&mut self, sod: bool);
|
||||||
|
fn blockmode(&mut self, blockmode: bool);
|
||||||
|
fn mode(&mut self, mode: Mode);
|
||||||
|
fn clk_cfg(&mut self, clk_cfg: SpiClkConfig);
|
||||||
|
fn hw_cs_id(&self) -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type erased variant of the transfer configuration. This is required to avoid generics in
|
||||||
|
/// the SPI constructor.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct TransferConfig {
|
||||||
|
pub clk_cfg: Option<SpiClkConfig>,
|
||||||
|
pub mode: Option<Mode>,
|
||||||
|
pub sod: bool,
|
||||||
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
|
/// duration of multiple data words
|
||||||
|
pub blockmode: bool,
|
||||||
|
/// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
|
||||||
|
/// is set on a written word.
|
||||||
|
pub bmstall: bool,
|
||||||
|
pub hw_cs: Option<HwChipSelectId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransferConfig {
|
||||||
|
pub fn new_with_hw_cs(
|
||||||
|
clk_cfg: Option<SpiClkConfig>,
|
||||||
|
mode: Option<Mode>,
|
||||||
|
blockmode: bool,
|
||||||
|
bmstall: bool,
|
||||||
|
sod: bool,
|
||||||
|
hw_cs_id: HwChipSelectId,
|
||||||
|
) -> Self {
|
||||||
|
TransferConfig {
|
||||||
|
clk_cfg,
|
||||||
|
mode,
|
||||||
|
sod,
|
||||||
|
blockmode,
|
||||||
|
bmstall,
|
||||||
|
hw_cs: Some(hw_cs_id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configuration options for the whole SPI bus. See Programmer Guide p.92 for more details
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct SpiConfig {
|
||||||
|
clk: SpiClkConfig,
|
||||||
|
// SPI mode configuration
|
||||||
|
pub init_mode: Mode,
|
||||||
|
/// If this is enabled, all data in the FIFO is transmitted in a single frame unless
|
||||||
|
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
|
||||||
|
/// duration of multiple data words. Defaults to true.
|
||||||
|
pub blockmode: bool,
|
||||||
|
/// This enables the stalling of the SPI SCK if in blockmode and the FIFO is empty.
|
||||||
|
/// Currently enabled by default.
|
||||||
|
pub bmstall: bool,
|
||||||
|
/// Slave output disable. Useful if separate GPIO pins or decoders are used for CS control
|
||||||
|
pub slave_output_disable: bool,
|
||||||
|
/// Loopback mode. If you use this, don't connect MISO to MOSI, they will be tied internally
|
||||||
|
pub loopback_mode: bool,
|
||||||
|
/// Enable Master Delayer Capture Mode. See Programmers Guide p.92 for more details
|
||||||
|
pub master_delayer_capture: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SpiConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
init_mode: MODE_0,
|
||||||
|
blockmode: true,
|
||||||
|
bmstall: true,
|
||||||
|
// Default value is definitely valid.
|
||||||
|
clk: SpiClkConfig::from_div(DEFAULT_CLK_DIV).unwrap(),
|
||||||
|
slave_output_disable: Default::default(),
|
||||||
|
loopback_mode: Default::default(),
|
||||||
|
master_delayer_capture: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiConfig {
|
||||||
|
pub fn loopback(mut self, enable: bool) -> Self {
|
||||||
|
self.loopback_mode = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blockmode(mut self, enable: bool) -> Self {
|
||||||
|
self.blockmode = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bmstall(mut self, enable: bool) -> Self {
|
||||||
|
self.bmstall = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode(mut self, mode: Mode) -> Self {
|
||||||
|
self.init_mode = mode;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
|
||||||
|
self.clk = clk_cfg;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slave_output_disable(mut self, sod: bool) -> Self {
|
||||||
|
self.slave_output_disable = sod;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Word Size
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Configuration trait for the Word Size
|
||||||
|
/// used by the SPI peripheral
|
||||||
|
pub trait WordProvider: Copy + Default + Into<u32> + TryFrom<u32> + 'static {
|
||||||
|
const MASK: u32;
|
||||||
|
const WORD_SIZE: regs::WordSize;
|
||||||
|
fn word_reg() -> u8;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WordProvider for u8 {
|
||||||
|
const MASK: u32 = 0xff;
|
||||||
|
const WORD_SIZE: regs::WordSize = regs::WordSize::EightBits;
|
||||||
|
fn word_reg() -> u8 {
|
||||||
|
0x07
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WordProvider for u16 {
|
||||||
|
const MASK: u32 = 0xffff;
|
||||||
|
const WORD_SIZE: regs::WordSize = regs::WordSize::SixteenBits;
|
||||||
|
fn word_reg() -> u8 {
|
||||||
|
0x0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Spi
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Low level access trait for the SPI peripheral.
|
||||||
|
pub trait SpiLowLevel {
|
||||||
|
/// Low level function to write a word to the SPI FIFO but also checks whether
|
||||||
|
/// there is actually data in the FIFO.
|
||||||
|
///
|
||||||
|
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
|
||||||
|
fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible>;
|
||||||
|
|
||||||
|
/// Low level function to write a word to the SPI FIFO without checking whether
|
||||||
|
/// there FIFO is full.
|
||||||
|
///
|
||||||
|
/// This does not necesarily mean there is a space in the FIFO available.
|
||||||
|
/// Use [Self::write_fifo] function to write a word into the FIFO reliably.
|
||||||
|
fn write_fifo_unchecked(&mut self, data: u32);
|
||||||
|
|
||||||
|
/// Low level function to read a word from the SPI FIFO. Must be preceeded by a
|
||||||
|
/// [Self::write_fifo] call.
|
||||||
|
///
|
||||||
|
/// Uses the [nb] API to allow usage in blocking and non-blocking contexts.
|
||||||
|
fn read_fifo(&mut self) -> nb::Result<u32, Infallible>;
|
||||||
|
|
||||||
|
/// Low level function to read a word from from the SPI FIFO.
|
||||||
|
///
|
||||||
|
/// This does not necesarily mean there is a word in the FIFO available.
|
||||||
|
/// Use the [Self::read_fifo] function to read a word from the FIFO reliably using the [nb]
|
||||||
|
/// API.
|
||||||
|
/// You might also need to mask the value to ignore the BMSTART/BMSTOP bit.
|
||||||
|
fn read_fifo_unchecked(&mut self) -> u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn mode_to_cpo_cph_bit(mode: embedded_hal::spi::Mode) -> (bool, bool) {
|
||||||
|
match mode {
|
||||||
|
embedded_hal::spi::MODE_0 => (false, false),
|
||||||
|
embedded_hal::spi::MODE_1 => (false, true),
|
||||||
|
embedded_hal::spi::MODE_2 => (true, false),
|
||||||
|
embedded_hal::spi::MODE_3 => (true, true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct SpiClkConfig {
|
||||||
|
prescale_val: u8,
|
||||||
|
scrdv: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiClkConfig {
|
||||||
|
pub fn prescale_val(&self) -> u8 {
|
||||||
|
self.prescale_val
|
||||||
|
}
|
||||||
|
pub fn scrdv(&self) -> u8 {
|
||||||
|
self.scrdv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiClkConfig {
|
||||||
|
pub fn new(prescale_val: u8, scrdv: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
prescale_val,
|
||||||
|
scrdv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_div(div: u16) -> Result<Self, SpiClkConfigError> {
|
||||||
|
spi_clk_config_from_div(div)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn from_clk(sys_clk: Hertz, spi_clk: Hertz) -> Option<Self> {
|
||||||
|
clk_div_for_target_clock(sys_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn from_clks(clks: &crate::clock::Clocks, spi_clk: Hertz) -> Option<Self> {
|
||||||
|
Self::from_apb1_clk(clks.apb1(), spi_clk)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn from_apb1_clk(apb1_clk: Hertz, spi_clk: Hertz) -> Option<Self> {
|
||||||
|
clk_div_for_target_clock(apb1_clk, spi_clk).map(|div| spi_clk_config_from_div(div).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum SpiClkConfigError {
|
||||||
|
#[error("division by zero")]
|
||||||
|
DivIsZero,
|
||||||
|
#[error("divide value is not even")]
|
||||||
|
DivideValueNotEven,
|
||||||
|
#[error("scrdv value is too large")]
|
||||||
|
ScrdvValueTooLarge,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn spi_clk_config_from_div(mut div: u16) -> Result<SpiClkConfig, SpiClkConfigError> {
|
||||||
|
if div == 0 {
|
||||||
|
return Err(SpiClkConfigError::DivIsZero);
|
||||||
|
}
|
||||||
|
if div % 2 != 0 {
|
||||||
|
return Err(SpiClkConfigError::DivideValueNotEven);
|
||||||
|
}
|
||||||
|
let mut prescale_val = 0;
|
||||||
|
|
||||||
|
// find largest (even) prescale value that divides into div
|
||||||
|
for i in (2..=0xfe).rev().step_by(2) {
|
||||||
|
if div % i == 0 {
|
||||||
|
prescale_val = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if prescale_val == 0 {
|
||||||
|
return Err(SpiClkConfigError::DivideValueNotEven);
|
||||||
|
}
|
||||||
|
|
||||||
|
div /= prescale_val;
|
||||||
|
if div > u8::MAX as u16 + 1 {
|
||||||
|
return Err(SpiClkConfigError::ScrdvValueTooLarge);
|
||||||
|
}
|
||||||
|
Ok(SpiClkConfig {
|
||||||
|
prescale_val: prescale_val as u8,
|
||||||
|
scrdv: (div - 1) as u8,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clk_div_for_target_clock(sys_clk: Hertz, spi_clk: Hertz) -> Option<u16> {
|
||||||
|
if spi_clk > sys_clk {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Calculate raw divider.
|
||||||
|
let raw_div = sys_clk.raw() / spi_clk.raw();
|
||||||
|
let remainder = sys_clk.raw() % spi_clk.raw();
|
||||||
|
|
||||||
|
// Step 2: Round up if necessary.
|
||||||
|
let mut rounded_div = if remainder * 2 >= spi_clk.raw() {
|
||||||
|
raw_div + 1
|
||||||
|
} else {
|
||||||
|
raw_div
|
||||||
|
};
|
||||||
|
|
||||||
|
if rounded_div % 2 != 0 {
|
||||||
|
// Take slower clock conservatively.
|
||||||
|
rounded_div += 1;
|
||||||
|
}
|
||||||
|
if rounded_div > u16::MAX as u32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(rounded_div as u16)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("peripheral or peripheral pin ID is not consistent")]
|
||||||
|
pub struct SpiIdMissmatchError;
|
||||||
|
|
||||||
|
/// SPI peripheral driver structure.
|
||||||
|
pub struct Spi<Word = u8> {
|
||||||
|
id: Bank,
|
||||||
|
regs: regs::MmioSpi<'static>,
|
||||||
|
cfg: SpiConfig,
|
||||||
|
/// Fill word for read-only SPI transactions.
|
||||||
|
fill_word: Word,
|
||||||
|
blockmode: bool,
|
||||||
|
bmstall: bool,
|
||||||
|
word: PhantomData<Word>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Word: WordProvider> Spi<Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
/// Create a new SPI struct for using SPI with the fixed ROM SPI pins.
|
||||||
|
///
|
||||||
|
/// ## Arguments
|
||||||
|
///
|
||||||
|
/// * `spi` - SPI bus to use
|
||||||
|
/// * `spi_cfg` - Configuration specific to the SPI bus
|
||||||
|
pub fn new_for_rom<SpiI: SpiMarker>(
|
||||||
|
spi: SpiI,
|
||||||
|
spi_cfg: SpiConfig,
|
||||||
|
) -> Result<Self, SpiIdMissmatchError> {
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
if SpiI::ID != Bank::Spi2 {
|
||||||
|
return Err(SpiIdMissmatchError);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
if SpiI::ID != Bank::Spi3 {
|
||||||
|
return Err(SpiIdMissmatchError);
|
||||||
|
}
|
||||||
|
Ok(Self::new_generic(spi, spi_cfg))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new SPI peripheral driver.
|
||||||
|
///
|
||||||
|
/// ## Arguments
|
||||||
|
///
|
||||||
|
/// * `spi` - SPI bus to use
|
||||||
|
/// * `pins` - Pins to be used for SPI transactions. These pins are consumed
|
||||||
|
/// to ensure the pins can not be used for other purposes anymore
|
||||||
|
/// * `spi_cfg` - Configuration specific to the SPI bus
|
||||||
|
pub fn new<SpiI: SpiMarker, Sck: PinSck, Miso: PinMiso, Mosi: PinMosi>(
|
||||||
|
spi: SpiI,
|
||||||
|
_pins: (Sck, Miso, Mosi),
|
||||||
|
spi_cfg: SpiConfig,
|
||||||
|
) -> Result<Self, SpiIdMissmatchError> {
|
||||||
|
if SpiI::ID != Sck::SPI_ID || SpiI::ID != Miso::SPI_ID || SpiI::ID != Mosi::SPI_ID {
|
||||||
|
return Err(SpiIdMissmatchError);
|
||||||
|
}
|
||||||
|
IoPeriphPin::new(Sck::ID, Sck::FUN_SEL, None);
|
||||||
|
IoPeriphPin::new(Miso::ID, Miso::FUN_SEL, None);
|
||||||
|
IoPeriphPin::new(Mosi::ID, Mosi::FUN_SEL, None);
|
||||||
|
Ok(Self::new_generic(spi, spi_cfg))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_generic<SpiI: SpiMarker>(_spi: SpiI, spi_cfg: SpiConfig) -> Self {
|
||||||
|
enable_peripheral_clock(SpiI::PERIPH_SEL);
|
||||||
|
let mut regs = regs::Spi::new_mmio(SpiI::ID);
|
||||||
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(spi_cfg.init_mode);
|
||||||
|
regs.write_ctrl0(
|
||||||
|
regs::Control0::builder()
|
||||||
|
.with_scrdv(spi_cfg.clk.scrdv)
|
||||||
|
.with_sph(cph_bit)
|
||||||
|
.with_spo(cpo_bit)
|
||||||
|
.with_word_size(Word::WORD_SIZE)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
regs.write_ctrl1(
|
||||||
|
regs::Control1::builder()
|
||||||
|
.with_mtxpause(false)
|
||||||
|
.with_mdlycap(spi_cfg.master_delayer_capture)
|
||||||
|
.with_bm_stall(spi_cfg.bmstall)
|
||||||
|
.with_bm_start(false)
|
||||||
|
.with_blockmode(spi_cfg.blockmode)
|
||||||
|
.with_ss(HwChipSelectId::Id0)
|
||||||
|
.with_sod(spi_cfg.slave_output_disable)
|
||||||
|
.with_slave_mode(false)
|
||||||
|
.with_enable(false)
|
||||||
|
.with_lbm(spi_cfg.loopback_mode)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
regs.write_clkprescale(ClkPrescaler::new(spi_cfg.clk.prescale_val));
|
||||||
|
regs.write_fifo_clear(
|
||||||
|
FifoClear::builder()
|
||||||
|
.with_tx_fifo(true)
|
||||||
|
.with_rx_fifo(true)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
// Enable the peripheral as the last step as recommended in the
|
||||||
|
// programmers guide
|
||||||
|
regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
Spi {
|
||||||
|
id: SpiI::ID,
|
||||||
|
regs: regs::Spi::new_mmio(SpiI::ID),
|
||||||
|
cfg: spi_cfg,
|
||||||
|
fill_word: Default::default(),
|
||||||
|
bmstall: spi_cfg.bmstall,
|
||||||
|
blockmode: spi_cfg.blockmode,
|
||||||
|
word: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_clock(&mut self, cfg: SpiClkConfig) {
|
||||||
|
self.regs.modify_ctrl0(|mut value| {
|
||||||
|
value.set_scrdv(cfg.scrdv);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
self.regs
|
||||||
|
.write_clkprescale(regs::ClkPrescaler::new(cfg.prescale_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_fill_word(&mut self, fill_word: Word) {
|
||||||
|
self.fill_word = fill_word;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_clock_from_div(&mut self, div: u16) -> Result<(), SpiClkConfigError> {
|
||||||
|
let val = spi_clk_config_from_div(div)?;
|
||||||
|
self.cfg_clock(val);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_mode(&mut self, mode: Mode) {
|
||||||
|
let (cpo_bit, cph_bit) = mode_to_cpo_cph_bit(mode);
|
||||||
|
self.regs.modify_ctrl0(|mut value| {
|
||||||
|
value.set_spo(cpo_bit);
|
||||||
|
value.set_sph(cph_bit);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn fill_word(&self) -> Word {
|
||||||
|
self.fill_word
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_tx_fifo(&mut self) {
|
||||||
|
self.regs.write_fifo_clear(
|
||||||
|
regs::FifoClear::builder()
|
||||||
|
.with_tx_fifo(true)
|
||||||
|
.with_rx_fifo(false)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn clear_rx_fifo(&mut self) {
|
||||||
|
self.regs.write_fifo_clear(
|
||||||
|
regs::FifoClear::builder()
|
||||||
|
.with_tx_fifo(false)
|
||||||
|
.with_rx_fifo(true)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32 {
|
||||||
|
self.regs.read_perid()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the hardware chip select given a hardware chip select ID.
|
||||||
|
///
|
||||||
|
/// The pin also needs to be configured to be used as a HW CS pin. This can be done
|
||||||
|
/// by using the [configure_pin_as_hw_cs_pin] function which also returns the
|
||||||
|
/// corresponding [HwChipSelectId].
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_hw_cs(&mut self, hw_cs: HwChipSelectId) {
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_sod(false);
|
||||||
|
value.set_ss(hw_cs);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables the hardware chip select functionality. This can be used when performing
|
||||||
|
/// external chip select handling, for example with GPIO pins.
|
||||||
|
#[inline]
|
||||||
|
pub fn cfg_hw_cs_disable(&mut self) {
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_sod(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Utility function to configure all relevant transfer parameters in one go.
|
||||||
|
/// This is useful if multiple devices with different clock and mode configurations
|
||||||
|
/// are connected to one bus.
|
||||||
|
pub fn cfg_transfer(&mut self, transfer_cfg: &TransferConfig) {
|
||||||
|
if let Some(trans_clk_div) = transfer_cfg.clk_cfg {
|
||||||
|
self.cfg_clock(trans_clk_div);
|
||||||
|
}
|
||||||
|
if let Some(mode) = transfer_cfg.mode {
|
||||||
|
self.cfg_mode(mode);
|
||||||
|
}
|
||||||
|
self.blockmode = transfer_cfg.blockmode;
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
if transfer_cfg.sod {
|
||||||
|
value.set_sod(transfer_cfg.sod);
|
||||||
|
} else {
|
||||||
|
value.set_sod(false);
|
||||||
|
if let Some(hw_cs) = transfer_cfg.hw_cs {
|
||||||
|
value.set_ss(hw_cs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.set_blockmode(transfer_cfg.blockmode);
|
||||||
|
value.set_bm_stall(transfer_cfg.bmstall);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_internal(&mut self) {
|
||||||
|
let mut status_reg = self.regs.read_status();
|
||||||
|
while !status_reg.tx_empty() || status_reg.rx_not_empty() || status_reg.busy() {
|
||||||
|
if status_reg.rx_not_empty() {
|
||||||
|
self.read_fifo_unchecked();
|
||||||
|
}
|
||||||
|
status_reg = self.regs.read_status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_preparation(&mut self, words: &[Word]) -> Result<(), Infallible> {
|
||||||
|
if words.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.flush_internal();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
|
||||||
|
// initialization. Returns the amount of written bytes.
|
||||||
|
fn initial_send_fifo_pumping_with_words(&mut self, words: &[Word]) -> usize {
|
||||||
|
//let reg_block = self.reg_block();
|
||||||
|
if self.blockmode {
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_mtxpause(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Fill the first half of the write FIFO
|
||||||
|
let mut current_write_idx = 0;
|
||||||
|
let smaller_idx = core::cmp::min(FILL_DEPTH, words.len());
|
||||||
|
for _ in 0..smaller_idx {
|
||||||
|
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
|
||||||
|
self.write_fifo_unchecked(words[current_write_idx].into() | BMSTART_BMSTOP_MASK);
|
||||||
|
} else {
|
||||||
|
self.write_fifo_unchecked(words[current_write_idx].into());
|
||||||
|
}
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if self.blockmode {
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_mtxpause(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
current_write_idx
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FIFO can hold a guaranteed amount of data, so we can pump it on transfer
|
||||||
|
// initialization.
|
||||||
|
fn initial_send_fifo_pumping_with_fill_words(&mut self, send_len: usize) -> usize {
|
||||||
|
if self.blockmode {
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_mtxpause(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Fill the first half of the write FIFO
|
||||||
|
let mut current_write_idx = 0;
|
||||||
|
let smaller_idx = core::cmp::min(FILL_DEPTH, send_len);
|
||||||
|
for _ in 0..smaller_idx {
|
||||||
|
if current_write_idx == smaller_idx.saturating_sub(1) && self.bmstall {
|
||||||
|
self.write_fifo_unchecked(self.fill_word.into() | BMSTART_BMSTOP_MASK);
|
||||||
|
} else {
|
||||||
|
self.write_fifo_unchecked(self.fill_word.into());
|
||||||
|
}
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if self.blockmode {
|
||||||
|
self.regs.modify_ctrl1(|mut value| {
|
||||||
|
value.set_mtxpause(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
current_write_idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Word: WordProvider> SpiLowLevel for Spi<Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible> {
|
||||||
|
if !self.regs.read_status().tx_not_full() {
|
||||||
|
return Err(nb::Error::WouldBlock);
|
||||||
|
}
|
||||||
|
self.write_fifo_unchecked(data);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn write_fifo_unchecked(&mut self, data: u32) {
|
||||||
|
self.regs.write_data(Data::new_with_raw_value(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_fifo(&mut self) -> nb::Result<u32, Infallible> {
|
||||||
|
if !self.regs.read_status().rx_not_empty() {
|
||||||
|
return Err(nb::Error::WouldBlock);
|
||||||
|
}
|
||||||
|
Ok(self.read_fifo_unchecked())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn read_fifo_unchecked(&mut self) -> u32 {
|
||||||
|
self.regs.read_data().raw_value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Word: WordProvider> embedded_hal::spi::ErrorType for Spi<Word> {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Word: WordProvider> embedded_hal::spi::SpiBus<Word> for Spi<Word>
|
||||||
|
where
|
||||||
|
<Word as TryFrom<u32>>::Error: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(words)?;
|
||||||
|
let mut current_read_idx = 0;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_fill_words(words.len());
|
||||||
|
loop {
|
||||||
|
if current_read_idx < words.len() {
|
||||||
|
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
current_read_idx += 1;
|
||||||
|
}
|
||||||
|
if current_write_idx < words.len() {
|
||||||
|
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||||
|
nb::block!(self.write_fifo(self.fill_word.into() | BMSTART_BMSTOP_MASK))?;
|
||||||
|
} else {
|
||||||
|
nb::block!(self.write_fifo(self.fill_word.into()))?;
|
||||||
|
}
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx >= words.len() && current_write_idx >= words.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(words)?;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||||
|
while current_write_idx < words.len() {
|
||||||
|
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||||
|
nb::block!(self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK))?;
|
||||||
|
} else {
|
||||||
|
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
|
||||||
|
}
|
||||||
|
current_write_idx += 1;
|
||||||
|
// Ignore received words.
|
||||||
|
if self.regs.read_status().rx_not_empty() {
|
||||||
|
self.clear_rx_fifo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(write)?;
|
||||||
|
let mut current_read_idx = 0;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(write);
|
||||||
|
while current_read_idx < read.len() || current_write_idx < write.len() {
|
||||||
|
if current_write_idx < write.len() {
|
||||||
|
if current_write_idx == write.len() - 1 && self.bmstall {
|
||||||
|
nb::block!(
|
||||||
|
self.write_fifo(write[current_write_idx].into() | BMSTART_BMSTOP_MASK)
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
nb::block!(self.write_fifo(write[current_write_idx].into()))?;
|
||||||
|
}
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx < read.len() {
|
||||||
|
read[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
current_read_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
|
||||||
|
self.transfer_preparation(words)?;
|
||||||
|
let mut current_read_idx = 0;
|
||||||
|
let mut current_write_idx = self.initial_send_fifo_pumping_with_words(words);
|
||||||
|
|
||||||
|
while current_read_idx < words.len() || current_write_idx < words.len() {
|
||||||
|
if current_write_idx < words.len() {
|
||||||
|
if current_write_idx == words.len() - 1 && self.bmstall {
|
||||||
|
nb::block!(
|
||||||
|
self.write_fifo(words[current_write_idx].into() | BMSTART_BMSTOP_MASK)
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
nb::block!(self.write_fifo(words[current_write_idx].into()))?;
|
||||||
|
}
|
||||||
|
current_write_idx += 1;
|
||||||
|
}
|
||||||
|
if current_read_idx < words.len() && current_read_idx < current_write_idx {
|
||||||
|
words[current_read_idx] = (nb::block!(self.read_fifo())? & Word::MASK)
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
current_read_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.flush_internal();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changing the word size also requires a type conversion
|
||||||
|
impl From<Spi<u8>> for Spi<u16> {
|
||||||
|
fn from(mut old_spi: Spi<u8>) -> Self {
|
||||||
|
old_spi.regs.modify_ctrl0(|mut value| {
|
||||||
|
value.set_word_size(WordSize::SixteenBits);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
Spi {
|
||||||
|
id: old_spi.id,
|
||||||
|
regs: old_spi.regs,
|
||||||
|
cfg: old_spi.cfg,
|
||||||
|
blockmode: old_spi.blockmode,
|
||||||
|
fill_word: Default::default(),
|
||||||
|
bmstall: old_spi.bmstall,
|
||||||
|
word: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Spi<u16>> for Spi<u8> {
|
||||||
|
fn from(mut old_spi: Spi<u16>) -> Self {
|
||||||
|
old_spi.regs.modify_ctrl0(|mut value| {
|
||||||
|
value.set_word_size(WordSize::EightBits);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
Spi {
|
||||||
|
id: old_spi.id,
|
||||||
|
regs: old_spi.regs,
|
||||||
|
cfg: old_spi.cfg,
|
||||||
|
blockmode: old_spi.blockmode,
|
||||||
|
fill_word: Default::default(),
|
||||||
|
bmstall: old_spi.bmstall,
|
||||||
|
word: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
303
src/spi/pins_vor1x.rs
Normal file
303
src/spi/pins_vor1x.rs
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
use super::{HwCsProvider, PinMiso, PinMosi, PinSck};
|
||||||
|
use crate::FunSel;
|
||||||
|
use crate::gpio::{PinId, PinIdProvider};
|
||||||
|
|
||||||
|
use crate::pins::{
|
||||||
|
Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa16, Pa17, Pa18, Pa19, Pa20, Pa21, Pa22, Pa23, Pa24, Pa25,
|
||||||
|
Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb2, Pb3, Pb4, Pb5, Pb6, Pb7, Pb8, Pb9, Pb10,
|
||||||
|
Pb11, Pb12, Pb13, Pb14, Pb15, Pb16, Pb17, Pb18, Pb19, Pb22, Pb23, Pin,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Bank, HwChipSelectId};
|
||||||
|
|
||||||
|
// SPIA
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pa31> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pa30> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pa29> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pb9> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pb8> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pb7> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi0,
|
||||||
|
(Pb0, FunSel::Sel2, HwChipSelectId::Id1),
|
||||||
|
(Pb1, FunSel::Sel2, HwChipSelectId::Id2),
|
||||||
|
(Pb2, FunSel::Sel2, HwChipSelectId::Id3),
|
||||||
|
(Pb3, FunSel::Sel2, HwChipSelectId::Id4),
|
||||||
|
(Pb4, FunSel::Sel2, HwChipSelectId::Id5),
|
||||||
|
(Pb5, FunSel::Sel2, HwChipSelectId::Id6),
|
||||||
|
(Pb6, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
(Pa24, FunSel::Sel1, HwChipSelectId::Id4),
|
||||||
|
(Pa25, FunSel::Sel1, HwChipSelectId::Id3),
|
||||||
|
(Pa26, FunSel::Sel1, HwChipSelectId::Id2),
|
||||||
|
(Pa27, FunSel::Sel1, HwChipSelectId::Id1),
|
||||||
|
(Pa28, FunSel::Sel1, HwChipSelectId::Id0),
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb0SpiaHwCsId1,
|
||||||
|
Pb0,
|
||||||
|
Bank::Spi0,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id1
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb1SpiaHwCsId2,
|
||||||
|
Pb1,
|
||||||
|
Bank::Spi0,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id2
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb2SpiaHwCsId3,
|
||||||
|
Pb2,
|
||||||
|
Bank::Spi0,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id3
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa21SpiaHwCsId7,
|
||||||
|
Pa21,
|
||||||
|
Bank::Spi0,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id7
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa22SpiaHwCsId6,
|
||||||
|
Pa22,
|
||||||
|
Bank::Spi0,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id6
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa23SpiaHwCsId5,
|
||||||
|
Pa23,
|
||||||
|
Bank::Spi0,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id5
|
||||||
|
);
|
||||||
|
|
||||||
|
// SPIB
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pa20> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pa19> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pa18> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SpiBPortASck = Pin<Pa20>;
|
||||||
|
pub type SpiBPortAMosi = Pin<Pa19>;
|
||||||
|
pub type SpiBPortAMiso = Pin<Pa18>;
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pb19> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pb18> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pb17> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pb5> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pb4> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pb3> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Need to deal with these duplications..
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi1,
|
||||||
|
(Pb16, FunSel::Sel1, HwChipSelectId::Id0),
|
||||||
|
(Pb15, FunSel::Sel1, HwChipSelectId::Id1),
|
||||||
|
(Pb14, FunSel::Sel1, HwChipSelectId::Id2),
|
||||||
|
(Pb13, FunSel::Sel1, HwChipSelectId::Id3),
|
||||||
|
(Pa17, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
(Pa16, FunSel::Sel2, HwChipSelectId::Id1),
|
||||||
|
(Pa15, FunSel::Sel2, HwChipSelectId::Id2),
|
||||||
|
(Pa14, FunSel::Sel2, HwChipSelectId::Id3),
|
||||||
|
(Pa13, FunSel::Sel2, HwChipSelectId::Id4),
|
||||||
|
(Pa12, FunSel::Sel2, HwChipSelectId::Id5),
|
||||||
|
(Pa11, FunSel::Sel2, HwChipSelectId::Id6),
|
||||||
|
(Pa10, FunSel::Sel2, HwChipSelectId::Id7),
|
||||||
|
(Pa23, FunSel::Sel2, HwChipSelectId::Id5),
|
||||||
|
(Pa22, FunSel::Sel2, HwChipSelectId::Id6),
|
||||||
|
(Pa21, FunSel::Sel2, HwChipSelectId::Id7),
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb0SpibHwCsId2,
|
||||||
|
Pb0,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id2
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb1SpibHwCsId1,
|
||||||
|
Pb1,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id1
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb2SpibHwCsId0,
|
||||||
|
Pb2,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id0
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb10SpibHwCsId6,
|
||||||
|
Pb10,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id6
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb11SpibHwCsId5,
|
||||||
|
Pb11,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id5
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb12SpibHwCsId4,
|
||||||
|
Pb12,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id4
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb10SpibHwCsId2,
|
||||||
|
Pb10,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id2
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb11SpibHwCsId1,
|
||||||
|
Pb11,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id1
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPb12SpibHwCsId0,
|
||||||
|
Pb12,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id0
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa21SpibHwCsId7,
|
||||||
|
Pa21,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id7
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa22SpibHwCsId6,
|
||||||
|
Pa22,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id6
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa23SpibHwCsId5,
|
||||||
|
Pa23,
|
||||||
|
Bank::Spi1,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id5
|
||||||
|
);
|
||||||
|
|
||||||
|
// SPIC
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi2,
|
||||||
|
(Pb9, FunSel::Sel3, HwChipSelectId::Id1),
|
||||||
|
(Pb8, FunSel::Sel3, HwChipSelectId::Id2),
|
||||||
|
(Pb7, FunSel::Sel3, HwChipSelectId::Id3),
|
||||||
|
(Pb23, FunSel::Sel3, HwChipSelectId::Id2),
|
||||||
|
(Pb22, FunSel::Sel3, HwChipSelectId::Id1),
|
||||||
|
(Pa20, FunSel::Sel1, HwChipSelectId::Id1),
|
||||||
|
(Pa19, FunSel::Sel1, HwChipSelectId::Id2),
|
||||||
|
(Pb18, FunSel::Sel1, HwChipSelectId::Id3),
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa21SpicHwCsId3,
|
||||||
|
Pa21,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel3,
|
||||||
|
HwChipSelectId::Id3
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa22SpicHwCsId2,
|
||||||
|
Pa22,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel3,
|
||||||
|
HwChipSelectId::Id2
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa23SpicHwCsId1,
|
||||||
|
Pa23,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel3,
|
||||||
|
HwChipSelectId::Id1
|
||||||
|
);
|
||||||
|
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa20SpicHwCsId1,
|
||||||
|
Pa20,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id1
|
||||||
|
);
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPa20SpicHwCsId4,
|
||||||
|
Pa20,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel3,
|
||||||
|
HwChipSelectId::Id4
|
||||||
|
);
|
205
src/spi/pins_vor4x.rs
Normal file
205
src/spi/pins_vor4x.rs
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
use crate::{
|
||||||
|
FunSel,
|
||||||
|
gpio::{Pin, PinId, PinIdProvider},
|
||||||
|
pins::{
|
||||||
|
Pa0, Pa1, Pa2, Pa3, Pa4, Pa5, Pa6, Pa7, Pa8, Pa9, Pb0, Pb1, Pb2, Pb3, Pb4, Pb12, Pb13,
|
||||||
|
Pb14, Pb15, Pc0, Pc1, Pc7, Pc8, Pc9, Pc10, Pc11, Pe5, Pe6, Pe7, Pe8, Pe9, Pe12, Pe13, Pe14,
|
||||||
|
Pe15, Pf0, Pf1, Pg2, Pg3, Pg4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
use crate::pins::{Pb5, Pb6, Pb7, Pb8, Pb9, Pb10, Pb11, Pe10, Pe11, Pf2, Pf3, Pf4, Pf5, Pf6, Pf7};
|
||||||
|
|
||||||
|
use super::{Bank, HwChipSelectId, HwCsProvider, PinMiso, PinMosi, PinSck};
|
||||||
|
|
||||||
|
// SPI0
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pb15> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pc1> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pc0> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi0,
|
||||||
|
(Pb14, FunSel::Sel1, HwChipSelectId::Id0),
|
||||||
|
(Pb13, FunSel::Sel1, HwChipSelectId::Id1),
|
||||||
|
(Pb12, FunSel::Sel1, HwChipSelectId::Id2),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
hw_cs_pins!(Bank::Spi0, (Pb11, FunSel::Sel1, HwChipSelectId::Id3));
|
||||||
|
|
||||||
|
// SPI1
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinSck for Pin<Pb8> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinMosi for Pin<Pb10> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinMiso for Pin<Pb9> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pc9> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pc11> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pc10> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pe13> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pe15> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pe14> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinSck for Pin<Pf3> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinMosi for Pin<Pf5> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinMiso for Pin<Pf4> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pg3> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pg4> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi1,
|
||||||
|
(Pb4, FunSel::Sel3, HwChipSelectId::Id3),
|
||||||
|
(Pb3, FunSel::Sel3, HwChipSelectId::Id4),
|
||||||
|
(Pb2, FunSel::Sel3, HwChipSelectId::Id5),
|
||||||
|
(Pb1, FunSel::Sel3, HwChipSelectId::Id6),
|
||||||
|
(Pb0, FunSel::Sel3, HwChipSelectId::Id7),
|
||||||
|
(Pc8, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
(Pc7, FunSel::Sel2, HwChipSelectId::Id1),
|
||||||
|
(Pe12, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
(Pe9, FunSel::Sel2, HwChipSelectId::Id3),
|
||||||
|
(Pe8, FunSel::Sel2, HwChipSelectId::Id4),
|
||||||
|
(Pe7, FunSel::Sel3, HwChipSelectId::Id5),
|
||||||
|
(Pe6, FunSel::Sel3, HwChipSelectId::Id6),
|
||||||
|
(Pe5, FunSel::Sel3, HwChipSelectId::Id7),
|
||||||
|
(Pg2, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi1,
|
||||||
|
(Pb7, FunSel::Sel3, HwChipSelectId::Id0),
|
||||||
|
(Pb6, FunSel::Sel3, HwChipSelectId::Id1),
|
||||||
|
(Pb5, FunSel::Sel3, HwChipSelectId::Id2),
|
||||||
|
(Pe11, FunSel::Sel2, HwChipSelectId::Id1),
|
||||||
|
(Pe10, FunSel::Sel2, HwChipSelectId::Id2),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPf2Spi1HwCsId0,
|
||||||
|
Pf2,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel1,
|
||||||
|
HwChipSelectId::Id0
|
||||||
|
);
|
||||||
|
|
||||||
|
// SPI2
|
||||||
|
|
||||||
|
impl PinSck for Pin<Pa5> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMosi for Pin<Pa7> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl PinMiso for Pin<Pa6> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinSck for Pin<Pf5> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinMosi for Pin<Pf7> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl PinMiso for Pin<Pf6> {
|
||||||
|
const SPI_ID: Bank = Bank::Spi2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi1,
|
||||||
|
(Pa4, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
(Pa3, FunSel::Sel2, HwChipSelectId::Id1),
|
||||||
|
(Pa2, FunSel::Sel2, HwChipSelectId::Id2),
|
||||||
|
(Pa1, FunSel::Sel2, HwChipSelectId::Id3),
|
||||||
|
(Pa0, FunSel::Sel2, HwChipSelectId::Id4),
|
||||||
|
(Pa8, FunSel::Sel2, HwChipSelectId::Id5),
|
||||||
|
(Pa9, FunSel::Sel2, HwChipSelectId::Id6),
|
||||||
|
(Pf0, FunSel::Sel2, HwChipSelectId::Id4),
|
||||||
|
(Pf1, FunSel::Sel2, HwChipSelectId::Id3),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
hw_cs_pins!(
|
||||||
|
Bank::Spi1,
|
||||||
|
(Pf3, FunSel::Sel2, HwChipSelectId::Id1),
|
||||||
|
(Pf4, FunSel::Sel2, HwChipSelectId::Id0),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
hw_cs_multi_pin!(
|
||||||
|
PinPf2Spi2HwCsId2,
|
||||||
|
Pf2,
|
||||||
|
Bank::Spi2,
|
||||||
|
FunSel::Sel2,
|
||||||
|
HwChipSelectId::Id2
|
||||||
|
);
|
279
src/spi/regs.rs
Normal file
279
src/spi/regs.rs
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
pub use crate::shared::{FifoClear, TriggerLevel};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
/// SPI A base address
|
||||||
|
pub const BASE_ADDR_0: usize = 0x4005_0000;
|
||||||
|
/// SPI B base address
|
||||||
|
pub const BASE_ADDR_1: usize = 0x4005_1000;
|
||||||
|
/// SPI C base address
|
||||||
|
pub const BASE_ADDR_2: usize = 0x4005_2000;
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
/// SPI 0 base address
|
||||||
|
pub const BASE_ADDR_0: usize = 0x4001_5000;
|
||||||
|
/// SPI 1 base address
|
||||||
|
pub const BASE_ADDR_1: usize = 0x4001_5400;
|
||||||
|
/// SPI 2 base address
|
||||||
|
pub const BASE_ADDR_2: usize = 0x4001_5800;
|
||||||
|
/// SPI 3 base address
|
||||||
|
pub const BASE_ADDR_3: usize = 0x4001_5C00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Bank {
|
||||||
|
Spi0,
|
||||||
|
Spi1,
|
||||||
|
Spi2,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Spi3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
/// Unsafely steal the SPI peripheral block for the given port.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents ownership and safety guarantees by the HAL.
|
||||||
|
pub unsafe fn steal_regs(&self) -> MmioSpi<'static> {
|
||||||
|
Spi::new_mmio(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u4)]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum WordSize {
|
||||||
|
OneBit = 0x00,
|
||||||
|
FourBits = 0x03,
|
||||||
|
EightBits = 0x07,
|
||||||
|
SixteenBits = 0x0f,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[bitbybit::bitenum(u3, exhaustive = true)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum HwChipSelectId {
|
||||||
|
Id0 = 0,
|
||||||
|
Id1 = 1,
|
||||||
|
Id2 = 2,
|
||||||
|
Id3 = 3,
|
||||||
|
Id4 = 4,
|
||||||
|
Id5 = 5,
|
||||||
|
Id6 = 6,
|
||||||
|
Id7 = 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Control0 {
|
||||||
|
#[bits(8..=15, rw)]
|
||||||
|
scrdv: u8,
|
||||||
|
#[bit(7, rw)]
|
||||||
|
sph: bool,
|
||||||
|
#[bit(6, rw)]
|
||||||
|
spo: bool,
|
||||||
|
#[bits(0..=3, rw)]
|
||||||
|
word_size: Option<WordSize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Control1 {
|
||||||
|
#[bit(11, rw)]
|
||||||
|
mtxpause: bool,
|
||||||
|
#[bit(10, rw)]
|
||||||
|
mdlycap: bool,
|
||||||
|
#[bit(9, rw)]
|
||||||
|
bm_stall: bool,
|
||||||
|
#[bit(8, rw)]
|
||||||
|
bm_start: bool,
|
||||||
|
#[bit(7, rw)]
|
||||||
|
blockmode: bool,
|
||||||
|
#[bits(4..=6, rw)]
|
||||||
|
ss: HwChipSelectId,
|
||||||
|
#[bit(3, rw)]
|
||||||
|
sod: bool,
|
||||||
|
#[bit(2, rw)]
|
||||||
|
slave_mode: bool,
|
||||||
|
#[bit(1, rw)]
|
||||||
|
enable: bool,
|
||||||
|
#[bit(0, rw)]
|
||||||
|
lbm: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Data {
|
||||||
|
/// Only used for BLOCKMODE. For received data, this bit indicated that the data was the first
|
||||||
|
/// word after the chip select went active. For transmitted data, setting this bit to 1
|
||||||
|
/// will end an SPI frame (deassert CS) after the specified data word.
|
||||||
|
#[bit(31, rw)]
|
||||||
|
bm_start_stop: bool,
|
||||||
|
/// Only used for BLOCKMODE. Setting this bit to 1 along with the BMSTOP bit will end an SPI
|
||||||
|
/// frame without any additional data to be transmitted. If BMSTOP is not set, this bit is
|
||||||
|
/// ignored.
|
||||||
|
#[bit(30, rw)]
|
||||||
|
bm_skipdata: bool,
|
||||||
|
#[bits(0..=15, rw)]
|
||||||
|
data: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Status {
|
||||||
|
/// TX FIFO below the trigger level.
|
||||||
|
#[bit(7, r)]
|
||||||
|
tx_trigger: bool,
|
||||||
|
/// RX FIFO above or equals the trigger level.
|
||||||
|
#[bit(6, r)]
|
||||||
|
rx_trigger: bool,
|
||||||
|
#[bit(5, r)]
|
||||||
|
rx_data_first: bool,
|
||||||
|
#[bit(4, r)]
|
||||||
|
busy: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
rx_full: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
rx_not_empty: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
tx_not_full: bool,
|
||||||
|
#[bit(0, r)]
|
||||||
|
tx_empty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clock divisor value. Bit 0 is ignored and always 0. This means that only the even values
|
||||||
|
/// are used as clock divisor values, and uneven values are truncated to the next even value.
|
||||||
|
/// A value of 0 acts as a 1 for the divisor value.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ClkPrescaler(arbitrary_int::UInt<u32, 8>);
|
||||||
|
|
||||||
|
impl ClkPrescaler {
|
||||||
|
pub const fn new(value: u8) -> Self {
|
||||||
|
ClkPrescaler(arbitrary_int::UInt::<u32, 8>::new(value as u32))
|
||||||
|
}
|
||||||
|
pub const fn value(&self) -> u8 {
|
||||||
|
self.0.value() as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptControl {
|
||||||
|
/// TX FIFO count <= TX FIFO trigger level.
|
||||||
|
#[bit(3, rw)]
|
||||||
|
tx: bool,
|
||||||
|
/// RX FIFO count >= RX FIFO trigger level.
|
||||||
|
#[bit(2, rw)]
|
||||||
|
rx: bool,
|
||||||
|
/// Occurs when the RX FIFO has not been read within 32 clock ticks of the SPICLKx2 clock
|
||||||
|
/// within the RX FIFO not being empty. Clearing the RX interrupt or reading data from the
|
||||||
|
/// FIFO resets the timeout counter.
|
||||||
|
#[bit(1, rw)]
|
||||||
|
rx_timeout: bool,
|
||||||
|
#[bit(0, rw)]
|
||||||
|
rx_overrun: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptStatus {
|
||||||
|
/// TX FIFO count <= TX FIFO trigger level.
|
||||||
|
#[bit(3, r)]
|
||||||
|
tx: bool,
|
||||||
|
/// RX FIFO count >= RX FIFO trigger level.
|
||||||
|
#[bit(2, r)]
|
||||||
|
rx: bool,
|
||||||
|
/// Occurs when the RX FIFO has not been read within 32 clock ticks of the SPICLKx2 clock
|
||||||
|
/// within the RX FIFO not being empty. Clearing the RX interrupt or reading data from the
|
||||||
|
/// FIFO resets the timeout counter.
|
||||||
|
#[bit(1, r)]
|
||||||
|
rx_timeout: bool,
|
||||||
|
#[bit(0, r)]
|
||||||
|
rx_overrun: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptClear {
|
||||||
|
/// Clearing the RX interrupt or reading data from the FIFO resets the timeout counter.
|
||||||
|
#[bit(1, w)]
|
||||||
|
rx_timeout: bool,
|
||||||
|
#[bit(0, w)]
|
||||||
|
rx_overrun: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct State {
|
||||||
|
#[bits(0..=7, r)]
|
||||||
|
rx_state: u8,
|
||||||
|
#[bits(8..=15, r)]
|
||||||
|
rx_fifo: u8,
|
||||||
|
#[bits(24..=31, r)]
|
||||||
|
tx_fifo: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[mmio(no_ctors)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Spi {
|
||||||
|
ctrl0: Control0,
|
||||||
|
ctrl1: Control1,
|
||||||
|
data: Data,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
status: Status,
|
||||||
|
clkprescale: ClkPrescaler,
|
||||||
|
irq_enb: InterruptControl,
|
||||||
|
/// Raw interrupt status.
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_raw: InterruptStatus,
|
||||||
|
/// Enabled interrupt status.
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_status: InterruptStatus,
|
||||||
|
#[mmio(Write)]
|
||||||
|
irq_clear: InterruptClear,
|
||||||
|
rx_fifo_trigger: TriggerLevel,
|
||||||
|
tx_fifo_trigger: TriggerLevel,
|
||||||
|
#[mmio(Write)]
|
||||||
|
fifo_clear: FifoClear,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
state: u32,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
_reserved: [u32; 0x3F2],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
_reserved: [u32; 0xF2],
|
||||||
|
/// Vorago 1x: 0x0113_07E1. Vorago 4x: 0x0213_07E9.
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
perid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Spi>(), 0x1000);
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Spi>(), 0x400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spi {
|
||||||
|
fn new_mmio_at(base: usize) -> MmioSpi<'static> {
|
||||||
|
MmioSpi {
|
||||||
|
ptr: base as *mut _,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_mmio(bank: Bank) -> MmioSpi<'static> {
|
||||||
|
match bank {
|
||||||
|
Bank::Spi0 => Self::new_mmio_at(BASE_ADDR_0),
|
||||||
|
Bank::Spi1 => Self::new_mmio_at(BASE_ADDR_1),
|
||||||
|
Bank::Spi2 => Self::new_mmio_at(BASE_ADDR_2),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Bank::Spi3 => Self::new_mmio_at(BASE_ADDR_2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/sysconfig.rs
Normal file
43
src/sysconfig.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use va416xx as pac;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn enable_peripheral_clock(clock: crate::PeripheralSelect) {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
syscfg
|
||||||
|
.peripheral_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn disable_peripheral_clock(clock: crate::PeripheralSelect) {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
syscfg
|
||||||
|
.peripheral_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn assert_peripheral_reset(periph_sel: crate::PeripheralSelect) {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
syscfg
|
||||||
|
.peripheral_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << periph_sel as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn deassert_peripheral_reset(periph_sel: crate::PeripheralSelect) {
|
||||||
|
let syscfg = unsafe { pac::Sysconfig::steal() };
|
||||||
|
syscfg
|
||||||
|
.peripheral_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << periph_sel as u8)) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn reset_peripheral_for_cycles(periph_sel: crate::PeripheralSelect, cycles: usize) {
|
||||||
|
assert_peripheral_reset(periph_sel);
|
||||||
|
cortex_m::asm::delay(cycles as u32);
|
||||||
|
deassert_peripheral_reset(periph_sel);
|
||||||
|
}
|
26
src/time.rs
Normal file
26
src/time.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//! Time units
|
||||||
|
|
||||||
|
// Frequency based
|
||||||
|
|
||||||
|
/// Hertz
|
||||||
|
pub type Hertz = fugit::HertzU32;
|
||||||
|
|
||||||
|
/// KiloHertz
|
||||||
|
pub type KiloHertz = fugit::KilohertzU32;
|
||||||
|
|
||||||
|
/// MegaHertz
|
||||||
|
pub type MegaHertz = fugit::MegahertzU32;
|
||||||
|
|
||||||
|
// Period based
|
||||||
|
|
||||||
|
/// Seconds
|
||||||
|
pub type Seconds = fugit::SecsDurationU32;
|
||||||
|
|
||||||
|
/// Milliseconds
|
||||||
|
pub type Milliseconds = fugit::MillisDurationU32;
|
||||||
|
|
||||||
|
/// Microseconds
|
||||||
|
pub type Microseconds = fugit::MicrosDurationU32;
|
||||||
|
|
||||||
|
/// Nanoseconds
|
||||||
|
pub type Nanoseconds = fugit::NanosDurationU32;
|
504
src/timer/mod.rs
Normal file
504
src/timer/mod.rs
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
pub mod regs;
|
||||||
|
|
||||||
|
use core::convert::Infallible;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub use crate::InterruptConfig;
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use crate::sysconfig::enable_peripheral_clock;
|
||||||
|
pub use regs::{CascadeSource, InvalidTimerIndex, TimId};
|
||||||
|
|
||||||
|
use crate::{enable_nvic_interrupt, sealed::Sealed, time::Hertz};
|
||||||
|
use crate::{gpio::PinId, ioconfig::regs::FunSel, pins::PinMarker};
|
||||||
|
use fugit::RateExtU32;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use crate::PeripheralSelect;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
use va108xx as pac;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
use va416xx as pac;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub const TIM_IRQ_OFFSET: usize = 48;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Defintions
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct CascadeControl {
|
||||||
|
/// Enable Cascade 0 signal active as a requirement for counting
|
||||||
|
pub enable_src_0: bool,
|
||||||
|
/// Invert Cascade 0, making it active low
|
||||||
|
pub inv_src_0: regs::CascadeInvert,
|
||||||
|
/// Enable Cascade 1 signal active as a requirement for counting
|
||||||
|
pub enable_src_1: bool,
|
||||||
|
/// Invert Cascade 1, making it active low
|
||||||
|
pub inv_src_1: regs::CascadeInvert,
|
||||||
|
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
|
||||||
|
/// 0 is a logical AND of both cascade signals, 1 is a logical OR
|
||||||
|
pub dual_operation: regs::DualCascadeOp,
|
||||||
|
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
|
||||||
|
/// cascade signal active, but once the counter is active, cascade control will be ignored
|
||||||
|
pub trigger_mode_0: bool,
|
||||||
|
/// Trigger mode, identical to [Self::trigger_mode_0] but for Cascade 1
|
||||||
|
pub trigger_mode_1: bool,
|
||||||
|
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
|
||||||
|
/// to the REQ_STOP control bit, but signalled by a Cascade source
|
||||||
|
pub enable_stop_src_2: bool,
|
||||||
|
/// Invert Cascade 2, making it active low
|
||||||
|
pub inv_src_2: regs::CascadeInvert,
|
||||||
|
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
|
||||||
|
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
|
||||||
|
/// ignored
|
||||||
|
pub trigger_mode_2: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum CascadeSelect {
|
||||||
|
Csd0 = 0,
|
||||||
|
Csd1 = 1,
|
||||||
|
Csd2 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Valid TIM and PIN combinations
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
pub trait TimPin: PinMarker {
|
||||||
|
const PIN_ID: PinId;
|
||||||
|
const FUN_SEL: FunSel;
|
||||||
|
const TIM_ID: TimId;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait TimMarker: Sealed {
|
||||||
|
// TIM ID ranging from 0 to 23 for 24 TIM peripherals
|
||||||
|
const ID: TimId;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
const IRQ: va416xx::Interrupt;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
fn clock(clocks: &crate::clock::Clocks) -> Hertz {
|
||||||
|
if Self::ID.value() <= 15 {
|
||||||
|
clocks.apb1()
|
||||||
|
} else {
|
||||||
|
clocks.apb2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! tim_marker {
|
||||||
|
($TIMX:path, $ID:expr) => {
|
||||||
|
impl TimMarker for $TIMX {
|
||||||
|
const ID: TimId = TimId::new_unchecked($ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for $TIMX {}
|
||||||
|
};
|
||||||
|
($TIMX:path, $ID:expr, $IrqId:ident) => {
|
||||||
|
impl TimMarker for $TIMX {
|
||||||
|
const ID: TimId = TimId::new_unchecked($ID);
|
||||||
|
const IRQ: va416xx::Interrupt = va416xx::Interrupt::$IrqId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sealed for $TIMX {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
tim_marker!(pac::Tim0, 0);
|
||||||
|
tim_marker!(pac::Tim1, 1);
|
||||||
|
tim_marker!(pac::Tim2, 2);
|
||||||
|
tim_marker!(pac::Tim3, 3);
|
||||||
|
tim_marker!(pac::Tim4, 4);
|
||||||
|
tim_marker!(pac::Tim5, 5);
|
||||||
|
tim_marker!(pac::Tim6, 6);
|
||||||
|
tim_marker!(pac::Tim7, 7);
|
||||||
|
tim_marker!(pac::Tim8, 8);
|
||||||
|
tim_marker!(pac::Tim9, 9);
|
||||||
|
tim_marker!(pac::Tim10, 10);
|
||||||
|
tim_marker!(pac::Tim11, 11);
|
||||||
|
tim_marker!(pac::Tim12, 12);
|
||||||
|
tim_marker!(pac::Tim13, 13);
|
||||||
|
tim_marker!(pac::Tim14, 14);
|
||||||
|
tim_marker!(pac::Tim15, 15);
|
||||||
|
tim_marker!(pac::Tim16, 16);
|
||||||
|
tim_marker!(pac::Tim17, 17);
|
||||||
|
tim_marker!(pac::Tim18, 18);
|
||||||
|
tim_marker!(pac::Tim19, 19);
|
||||||
|
tim_marker!(pac::Tim20, 20);
|
||||||
|
tim_marker!(pac::Tim21, 21);
|
||||||
|
tim_marker!(pac::Tim22, 22);
|
||||||
|
tim_marker!(pac::Tim23, 23);
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
tim_marker!(pac::Tim0, 0, TIM0);
|
||||||
|
tim_marker!(pac::Tim1, 1, TIM1);
|
||||||
|
tim_marker!(pac::Tim2, 2, TIM2);
|
||||||
|
tim_marker!(pac::Tim3, 3, TIM3);
|
||||||
|
tim_marker!(pac::Tim4, 4, TIM4);
|
||||||
|
tim_marker!(pac::Tim5, 5, TIM5);
|
||||||
|
tim_marker!(pac::Tim6, 6, TIM6);
|
||||||
|
tim_marker!(pac::Tim7, 7, TIM7);
|
||||||
|
tim_marker!(pac::Tim8, 8, TIM8);
|
||||||
|
tim_marker!(pac::Tim9, 9, TIM9);
|
||||||
|
tim_marker!(pac::Tim10, 10, TIM10);
|
||||||
|
tim_marker!(pac::Tim11, 11, TIM11);
|
||||||
|
tim_marker!(pac::Tim12, 12, TIM12);
|
||||||
|
tim_marker!(pac::Tim13, 13, TIM13);
|
||||||
|
tim_marker!(pac::Tim14, 14, TIM14);
|
||||||
|
tim_marker!(pac::Tim15, 15, TIM15);
|
||||||
|
tim_marker!(pac::Tim16, 16, TIM16);
|
||||||
|
tim_marker!(pac::Tim17, 17, TIM17);
|
||||||
|
tim_marker!(pac::Tim18, 18, TIM18);
|
||||||
|
tim_marker!(pac::Tim19, 19, TIM19);
|
||||||
|
tim_marker!(pac::Tim20, 20, TIM20);
|
||||||
|
tim_marker!(pac::Tim21, 21, TIM21);
|
||||||
|
tim_marker!(pac::Tim22, 22, TIM22);
|
||||||
|
tim_marker!(pac::Tim23, 23, TIM23);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ValidTimAndPin<Pin: TimPin, Tim: TimMarker>: Sealed {}
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros {
|
||||||
|
macro_rules! pin_and_tim {
|
||||||
|
($Px:ident, $FunSel:path, $ID:expr) => {
|
||||||
|
impl TimPin for Pin<$Px>
|
||||||
|
where
|
||||||
|
$Px: PinIdProvider,
|
||||||
|
{
|
||||||
|
const PIN_ID: PinId = $Px::ID;
|
||||||
|
const FUN_SEL: FunSel = $FunSel;
|
||||||
|
const TIM_ID: TimId = TimId::new_unchecked($ID);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub mod pins_vor1x;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub mod pins_vor4x;
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Timers
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
/// Hardware timers
|
||||||
|
pub struct CountdownTimer {
|
||||||
|
id: TimId,
|
||||||
|
regs: regs::MmioTimer<'static>,
|
||||||
|
curr_freq: Hertz,
|
||||||
|
ref_clk: Hertz,
|
||||||
|
rst_val: u32,
|
||||||
|
last_cnt: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CountdownTimer {
|
||||||
|
/// Create a countdown timer structure for a given TIM peripheral.
|
||||||
|
///
|
||||||
|
/// This does not enable the timer. You can use the [Self::load], [Self::start],
|
||||||
|
/// [Self::enable_interrupt] and [Self::enable] API to set up and configure the countdown
|
||||||
|
/// timer.
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn new<Tim: TimMarker>(_tim: Tim, sys_clk: Hertz) -> Self {
|
||||||
|
enable_tim_clk(Tim::ID);
|
||||||
|
assert_tim_reset_for_cycles(Tim::ID, 2);
|
||||||
|
CountdownTimer {
|
||||||
|
id: Tim::ID,
|
||||||
|
regs: regs::Timer::new_mmio(Tim::ID),
|
||||||
|
ref_clk: sys_clk,
|
||||||
|
rst_val: 0,
|
||||||
|
curr_freq: 0.Hz(),
|
||||||
|
last_cnt: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a countdown timer structure for a given TIM peripheral.
|
||||||
|
///
|
||||||
|
/// This does not enable the timer. You can use the [Self::load], [Self::start],
|
||||||
|
/// [Self::enable_interrupt] and [Self::enable] API to set up and configure the countdown
|
||||||
|
/// timer.
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn new<Tim: TimMarker>(_tim: Tim, clks: &crate::clock::Clocks) -> Self {
|
||||||
|
enable_tim_clk(Tim::ID);
|
||||||
|
assert_tim_reset_for_cycles(Tim::ID, 2);
|
||||||
|
CountdownTimer {
|
||||||
|
id: Tim::ID,
|
||||||
|
regs: regs::Timer::new_mmio(Tim::ID),
|
||||||
|
ref_clk: clks.apb1(),
|
||||||
|
rst_val: 0,
|
||||||
|
curr_freq: 0.Hz(),
|
||||||
|
last_cnt: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn perid(&self) -> u32 {
|
||||||
|
self.regs.read_perid()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.regs
|
||||||
|
.write_enable_control(regs::EnableControl::new_enable());
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.regs
|
||||||
|
.write_enable_control(regs::EnableControl::new_disable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn enable_interrupt(&mut self, irq_cfg: InterruptConfig) {
|
||||||
|
if irq_cfg.route {
|
||||||
|
let irqsel = unsafe { pac::Irqsel::steal() };
|
||||||
|
enable_peripheral_clock(PeripheralSelect::Irqsel);
|
||||||
|
irqsel
|
||||||
|
.tim0(self.id.value() as usize)
|
||||||
|
.write(|w| unsafe { w.bits(irq_cfg.id as u32) });
|
||||||
|
}
|
||||||
|
if irq_cfg.enable_in_nvic {
|
||||||
|
unsafe { enable_nvic_interrupt(irq_cfg.id) };
|
||||||
|
}
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_interrupt(&mut self, enable_in_nvic: bool) {
|
||||||
|
if enable_in_nvic {
|
||||||
|
unsafe { enable_nvic_interrupt(self.id.interrupt_id()) };
|
||||||
|
}
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(true);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function only clears the interrupt enable bit.
|
||||||
|
///
|
||||||
|
/// It does not mask the interrupt in the NVIC or un-route the IRQ.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable_interrupt(&mut self) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_irq_enable(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls [Self::load] to configure the specified frequency and then calls [Self::enable].
|
||||||
|
pub fn start(&mut self, frequency: impl Into<Hertz>) {
|
||||||
|
self.load(frequency);
|
||||||
|
self.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
|
||||||
|
/// flag and restart the time if configured correctly
|
||||||
|
pub fn wait(&mut self) -> nb::Result<(), Infallible> {
|
||||||
|
let cnt = self.counter();
|
||||||
|
if (cnt > self.last_cnt) || cnt == 0 {
|
||||||
|
self.last_cnt = self.rst_val;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
self.last_cnt = cnt;
|
||||||
|
Err(nb::Error::WouldBlock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the count down timer with a timeout but do not start it.
|
||||||
|
pub fn load(&mut self, timeout: impl Into<Hertz>) {
|
||||||
|
self.disable();
|
||||||
|
self.curr_freq = timeout.into();
|
||||||
|
self.rst_val = self.ref_clk.raw() / self.curr_freq.raw();
|
||||||
|
self.set_reload(self.rst_val);
|
||||||
|
self.set_count(self.rst_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_reload(&mut self, val: u32) {
|
||||||
|
self.regs.write_reset_value(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_count(&mut self, val: u32) {
|
||||||
|
self.regs.write_count_value(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn counter(&self) -> u32 {
|
||||||
|
self.regs.read_count_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable the counter, setting both enable and active bit to 0
|
||||||
|
#[inline]
|
||||||
|
pub fn auto_disable(&mut self, enable: bool) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_auto_disable(enable);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This option only applies when the Auto-Disable functionality is 0.
|
||||||
|
///
|
||||||
|
/// The active bit is changed to 0 when count reaches 0, but the counter stays
|
||||||
|
/// enabled. When Auto-Disable is 1, Auto-Deactivate is implied
|
||||||
|
#[inline]
|
||||||
|
pub fn auto_deactivate(&mut self, enable: bool) {
|
||||||
|
self.regs.modify_control(|mut value| {
|
||||||
|
value.set_auto_deactivate(enable);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the cascade parameters
|
||||||
|
pub fn cascade_control(&mut self, ctrl: CascadeControl) {
|
||||||
|
self.regs.write_cascade_control(
|
||||||
|
regs::CascadeControl::builder()
|
||||||
|
.with_trigger2(ctrl.trigger_mode_2)
|
||||||
|
.with_inv2(ctrl.inv_src_2)
|
||||||
|
.with_en2(ctrl.enable_stop_src_2)
|
||||||
|
.with_trigger1(ctrl.trigger_mode_1)
|
||||||
|
.with_trigger0(ctrl.trigger_mode_0)
|
||||||
|
.with_dual_cascade_op(ctrl.dual_operation)
|
||||||
|
.with_inv1(ctrl.inv_src_1)
|
||||||
|
.with_en1(ctrl.enable_src_1)
|
||||||
|
.with_inv0(ctrl.inv_src_0)
|
||||||
|
.with_en0(ctrl.enable_src_0)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cascade_source(
|
||||||
|
&mut self,
|
||||||
|
cascade_index: CascadeSelect,
|
||||||
|
src: regs::CascadeSource,
|
||||||
|
) -> Result<(), regs::InvalidCascadeSourceId> {
|
||||||
|
// Safety: Index range safe by enum values.
|
||||||
|
unsafe {
|
||||||
|
self.regs
|
||||||
|
.write_cascade_unchecked(cascade_index as usize, regs::CascadeSourceReg::new(src)?);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn curr_freq(&self) -> Hertz {
|
||||||
|
self.curr_freq
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disables the TIM and the dedicated TIM clock.
|
||||||
|
pub fn stop_with_clock_disable(mut self) {
|
||||||
|
self.disable();
|
||||||
|
unsafe { pac::Sysconfig::steal() }
|
||||||
|
.tim_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.id.value())) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Delay implementations
|
||||||
|
//==================================================================================================
|
||||||
|
//
|
||||||
|
impl embedded_hal::delay::DelayNs for CountdownTimer {
|
||||||
|
fn delay_ns(&mut self, ns: u32) {
|
||||||
|
let ticks = (u64::from(ns)) * (u64::from(self.ref_clk.raw())) / 1_000_000_000;
|
||||||
|
|
||||||
|
let full_cycles = ticks >> 32;
|
||||||
|
let mut last_count;
|
||||||
|
let mut new_count;
|
||||||
|
if full_cycles > 0 {
|
||||||
|
self.set_reload(u32::MAX);
|
||||||
|
self.set_count(u32::MAX);
|
||||||
|
self.enable();
|
||||||
|
|
||||||
|
for _ in 0..full_cycles {
|
||||||
|
// Always ensure that both values are the same at the start.
|
||||||
|
new_count = self.counter();
|
||||||
|
last_count = new_count;
|
||||||
|
loop {
|
||||||
|
new_count = self.counter();
|
||||||
|
if new_count == 0 {
|
||||||
|
// Wait till timer has wrapped.
|
||||||
|
while self.counter() == 0 {
|
||||||
|
cortex_m::asm::nop()
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Timer has definitely wrapped.
|
||||||
|
if new_count > last_count {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_count = new_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let ticks = (ticks & u32::MAX as u64) as u32;
|
||||||
|
self.disable();
|
||||||
|
if ticks > 1 {
|
||||||
|
self.set_reload(ticks);
|
||||||
|
self.set_count(ticks);
|
||||||
|
self.enable();
|
||||||
|
last_count = ticks;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
new_count = self.counter();
|
||||||
|
if new_count == 0 || (new_count > last_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_count = new_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable_tim_clk(id: TimId) {
|
||||||
|
unsafe { pac::Sysconfig::steal() }
|
||||||
|
.tim_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << id.value())) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable_tim_clk(id: TimId) {
|
||||||
|
unsafe { pac::Sysconfig::steal() }
|
||||||
|
.tim_clk_enable()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << (id.value()))) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the reset bit of the TIM, holding it in reset
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Only the bit related to the corresponding TIM peripheral is modified
|
||||||
|
#[inline]
|
||||||
|
pub fn assert_tim_reset(id: TimId) {
|
||||||
|
unsafe { pac::Peripherals::steal() }
|
||||||
|
.sysconfig
|
||||||
|
.tim_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() & !(1 << id.value())) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn deassert_tim_reset(tim: TimId) {
|
||||||
|
unsafe { pac::Peripherals::steal() }
|
||||||
|
.sysconfig
|
||||||
|
.tim_reset()
|
||||||
|
.modify(|r, w| unsafe { w.bits(r.bits() | (1 << tim.value())) });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_tim_reset_for_cycles(tim: TimId, cycles: u32) {
|
||||||
|
assert_tim_reset(tim);
|
||||||
|
cortex_m::asm::delay(cycles);
|
||||||
|
deassert_tim_reset(tim);
|
||||||
|
}
|
56
src/timer/pins_vor1x.rs
Normal file
56
src/timer/pins_vor1x.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use super::{TimId, TimPin};
|
||||||
|
use crate::FunSel;
|
||||||
|
use crate::pins::{
|
||||||
|
Pa0, Pa1, Pa2, Pa3, Pa4, Pa5, Pa6, Pa7, Pa8, Pa9, Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pa24,
|
||||||
|
Pa25, Pa26, Pa27, Pa28, Pa29, Pa30, Pa31, Pb0, Pb1, Pb2, Pb3, Pb4, Pb5, Pb6, Pb10, Pb11, Pb12,
|
||||||
|
Pb13, Pb14, Pb15, Pb16, Pb17, Pb18, Pb19, Pb20, Pb21, Pb22, Pb23, Pin, PinId, PinIdProvider,
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_and_tim!(Pa0, FunSel::Sel1, 0);
|
||||||
|
pin_and_tim!(Pa1, FunSel::Sel1, 1);
|
||||||
|
pin_and_tim!(Pa2, FunSel::Sel1, 2);
|
||||||
|
pin_and_tim!(Pa3, FunSel::Sel1, 3);
|
||||||
|
pin_and_tim!(Pa4, FunSel::Sel1, 4);
|
||||||
|
pin_and_tim!(Pa5, FunSel::Sel1, 5);
|
||||||
|
pin_and_tim!(Pa6, FunSel::Sel1, 6);
|
||||||
|
pin_and_tim!(Pa7, FunSel::Sel1, 7);
|
||||||
|
pin_and_tim!(Pa8, FunSel::Sel1, 8);
|
||||||
|
pin_and_tim!(Pa9, FunSel::Sel1, 9);
|
||||||
|
pin_and_tim!(Pa10, FunSel::Sel1, 10);
|
||||||
|
pin_and_tim!(Pa11, FunSel::Sel1, 11);
|
||||||
|
pin_and_tim!(Pa12, FunSel::Sel1, 12);
|
||||||
|
pin_and_tim!(Pa13, FunSel::Sel1, 13);
|
||||||
|
pin_and_tim!(Pa14, FunSel::Sel1, 14);
|
||||||
|
pin_and_tim!(Pa15, FunSel::Sel1, 15);
|
||||||
|
|
||||||
|
pin_and_tim!(Pa24, FunSel::Sel2, 16);
|
||||||
|
pin_and_tim!(Pa25, FunSel::Sel2, 17);
|
||||||
|
pin_and_tim!(Pa26, FunSel::Sel2, 18);
|
||||||
|
pin_and_tim!(Pa27, FunSel::Sel2, 19);
|
||||||
|
pin_and_tim!(Pa28, FunSel::Sel2, 20);
|
||||||
|
pin_and_tim!(Pa29, FunSel::Sel2, 21);
|
||||||
|
pin_and_tim!(Pa30, FunSel::Sel2, 22);
|
||||||
|
pin_and_tim!(Pa31, FunSel::Sel2, 23);
|
||||||
|
|
||||||
|
pin_and_tim!(Pb0, FunSel::Sel3, 0);
|
||||||
|
pin_and_tim!(Pb1, FunSel::Sel3, 1);
|
||||||
|
pin_and_tim!(Pb2, FunSel::Sel3, 2);
|
||||||
|
pin_and_tim!(Pb3, FunSel::Sel3, 3);
|
||||||
|
pin_and_tim!(Pb4, FunSel::Sel3, 4);
|
||||||
|
pin_and_tim!(Pb5, FunSel::Sel3, 5);
|
||||||
|
pin_and_tim!(Pb6, FunSel::Sel3, 6);
|
||||||
|
|
||||||
|
pin_and_tim!(Pb10, FunSel::Sel3, 10);
|
||||||
|
pin_and_tim!(Pb11, FunSel::Sel3, 11);
|
||||||
|
pin_and_tim!(Pb12, FunSel::Sel3, 12);
|
||||||
|
pin_and_tim!(Pb13, FunSel::Sel3, 13);
|
||||||
|
pin_and_tim!(Pb14, FunSel::Sel3, 14);
|
||||||
|
pin_and_tim!(Pb15, FunSel::Sel3, 15);
|
||||||
|
pin_and_tim!(Pb16, FunSel::Sel3, 16);
|
||||||
|
pin_and_tim!(Pb17, FunSel::Sel3, 17);
|
||||||
|
pin_and_tim!(Pb18, FunSel::Sel3, 18);
|
||||||
|
pin_and_tim!(Pb19, FunSel::Sel3, 19);
|
||||||
|
pin_and_tim!(Pb20, FunSel::Sel3, 20);
|
||||||
|
pin_and_tim!(Pb21, FunSel::Sel3, 21);
|
||||||
|
pin_and_tim!(Pb22, FunSel::Sel3, 22);
|
||||||
|
pin_and_tim!(Pb23, FunSel::Sel3, 23);
|
132
src/timer/pins_vor4x.rs
Normal file
132
src/timer/pins_vor4x.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
use super::{FunSel, TimId, TimPin};
|
||||||
|
use crate::pins::{
|
||||||
|
Pa0, Pa1, Pa2, Pa3, Pa4, Pa5, Pa6, Pa7, Pa8, Pa10, Pa11, Pa12, Pa13, Pa14, Pa15, Pb0, Pb1, Pb2,
|
||||||
|
Pb3, Pb4, Pb12, Pb13, Pb14, Pb15, Pc0, Pc1, Pd10, Pd11, Pd12, Pd13, Pd14, Pd15, Pe0, Pe1, Pe2,
|
||||||
|
Pe3, Pe4, Pe5, Pe6, Pe7, Pe8, Pe9, Pe12, Pe13, Pe14, Pe15, Pf0, Pf1, Pf9, Pf11, Pf12, Pf13,
|
||||||
|
Pf14, Pf15, Pg0, Pg1, Pg2, Pg3, Pg6, Pin, PinId, PinIdProvider,
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
use crate::pins::{
|
||||||
|
Pb5, Pb6, Pb7, Pb8, Pb9, Pb10, Pb11, Pd0, Pd1, Pd2, Pd3, Pd4, Pd5, Pd6, Pd7, Pd8, Pd9, Pe10,
|
||||||
|
Pe11, Pf2, Pf3, Pf4, Pf5, Pf6, Pf7, Pf8, Pf10,
|
||||||
|
};
|
||||||
|
|
||||||
|
pin_and_tim!(Pa0, FunSel::Sel1, 0);
|
||||||
|
pin_and_tim!(Pa1, FunSel::Sel1, 1);
|
||||||
|
pin_and_tim!(Pa2, FunSel::Sel1, 2);
|
||||||
|
pin_and_tim!(Pa3, FunSel::Sel1, 3);
|
||||||
|
pin_and_tim!(Pa4, FunSel::Sel1, 4);
|
||||||
|
pin_and_tim!(Pa5, FunSel::Sel1, 5);
|
||||||
|
pin_and_tim!(Pa6, FunSel::Sel1, 6);
|
||||||
|
pin_and_tim!(Pa7, FunSel::Sel1, 7);
|
||||||
|
pin_and_tim!(Pa8, FunSel::Sel3, 8);
|
||||||
|
pin_and_tim!(Pa10, FunSel::Sel2, 23);
|
||||||
|
pin_and_tim!(Pa11, FunSel::Sel2, 22);
|
||||||
|
pin_and_tim!(Pa12, FunSel::Sel2, 21);
|
||||||
|
pin_and_tim!(Pa13, FunSel::Sel2, 20);
|
||||||
|
pin_and_tim!(Pa14, FunSel::Sel2, 19);
|
||||||
|
pin_and_tim!(Pa15, FunSel::Sel2, 18);
|
||||||
|
|
||||||
|
pin_and_tim!(Pb0, FunSel::Sel2, 17);
|
||||||
|
pin_and_tim!(Pb1, FunSel::Sel2, 16);
|
||||||
|
pin_and_tim!(Pb2, FunSel::Sel2, 15);
|
||||||
|
pin_and_tim!(Pb3, FunSel::Sel2, 14);
|
||||||
|
pin_and_tim!(Pb4, FunSel::Sel2, 13);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb5, FunSel::Sel2, 12);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb6, FunSel::Sel2, 11);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb7, FunSel::Sel2, 10);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb8, FunSel::Sel2, 9);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb9, FunSel::Sel2, 8);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb10, FunSel::Sel2, 7);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pb11, FunSel::Sel2, 6);
|
||||||
|
pin_and_tim!(Pb12, FunSel::Sel2, 5);
|
||||||
|
pin_and_tim!(Pb13, FunSel::Sel2, 4);
|
||||||
|
pin_and_tim!(Pb14, FunSel::Sel2, 3);
|
||||||
|
pin_and_tim!(Pb15, FunSel::Sel2, 2);
|
||||||
|
|
||||||
|
pin_and_tim!(Pc0, FunSel::Sel2, 1);
|
||||||
|
pin_and_tim!(Pc1, FunSel::Sel2, 0);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd0, FunSel::Sel2, 0);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd1, FunSel::Sel2, 1);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd2, FunSel::Sel2, 2);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd3, FunSel::Sel2, 3);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd4, FunSel::Sel2, 4);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd5, FunSel::Sel2, 5);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd6, FunSel::Sel2, 6);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd7, FunSel::Sel2, 7);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd8, FunSel::Sel2, 8);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pd9, FunSel::Sel2, 9);
|
||||||
|
pin_and_tim!(Pd10, FunSel::Sel2, 10);
|
||||||
|
pin_and_tim!(Pd11, FunSel::Sel2, 11);
|
||||||
|
pin_and_tim!(Pd12, FunSel::Sel2, 12);
|
||||||
|
pin_and_tim!(Pd13, FunSel::Sel2, 13);
|
||||||
|
pin_and_tim!(Pd14, FunSel::Sel2, 14);
|
||||||
|
pin_and_tim!(Pd15, FunSel::Sel2, 15);
|
||||||
|
|
||||||
|
pin_and_tim!(Pe0, FunSel::Sel2, 16);
|
||||||
|
pin_and_tim!(Pe1, FunSel::Sel2, 17);
|
||||||
|
pin_and_tim!(Pe2, FunSel::Sel2, 18);
|
||||||
|
pin_and_tim!(Pe3, FunSel::Sel2, 19);
|
||||||
|
pin_and_tim!(Pe4, FunSel::Sel2, 20);
|
||||||
|
pin_and_tim!(Pe5, FunSel::Sel2, 21);
|
||||||
|
pin_and_tim!(Pe6, FunSel::Sel2, 22);
|
||||||
|
pin_and_tim!(Pe7, FunSel::Sel2, 23);
|
||||||
|
pin_and_tim!(Pe8, FunSel::Sel3, 16);
|
||||||
|
pin_and_tim!(Pe9, FunSel::Sel3, 17);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pe10, FunSel::Sel3, 18);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pe11, FunSel::Sel3, 19);
|
||||||
|
pin_and_tim!(Pe12, FunSel::Sel3, 20);
|
||||||
|
pin_and_tim!(Pe13, FunSel::Sel3, 21);
|
||||||
|
pin_and_tim!(Pe14, FunSel::Sel3, 22);
|
||||||
|
pin_and_tim!(Pe15, FunSel::Sel3, 23);
|
||||||
|
|
||||||
|
pin_and_tim!(Pf0, FunSel::Sel3, 0);
|
||||||
|
pin_and_tim!(Pf1, FunSel::Sel3, 1);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf2, FunSel::Sel3, 2);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf3, FunSel::Sel3, 3);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf4, FunSel::Sel3, 4);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf5, FunSel::Sel3, 5);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf6, FunSel::Sel3, 6);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf7, FunSel::Sel3, 7);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf8, FunSel::Sel3, 8);
|
||||||
|
pin_and_tim!(Pf9, FunSel::Sel3, 9);
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
pin_and_tim!(Pf10, FunSel::Sel3, 10);
|
||||||
|
pin_and_tim!(Pf11, FunSel::Sel3, 11);
|
||||||
|
pin_and_tim!(Pf12, FunSel::Sel3, 12);
|
||||||
|
pin_and_tim!(Pf13, FunSel::Sel2, 19);
|
||||||
|
pin_and_tim!(Pf14, FunSel::Sel2, 20);
|
||||||
|
pin_and_tim!(Pf15, FunSel::Sel2, 21);
|
||||||
|
|
||||||
|
pin_and_tim!(Pg0, FunSel::Sel2, 22);
|
||||||
|
pin_and_tim!(Pg1, FunSel::Sel2, 23);
|
||||||
|
pin_and_tim!(Pg2, FunSel::Sel1, 9);
|
||||||
|
pin_and_tim!(Pg3, FunSel::Sel1, 10);
|
||||||
|
pin_and_tim!(Pg6, FunSel::Sel1, 12);
|
428
src/timer/regs.rs
Normal file
428
src/timer/regs.rs
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use arbitrary_int::{Number, u7};
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
const BASE_ADDR: usize = 0x4002_0000;
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
const BASE_ADDR: usize = 0x4001_8000;
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u3)]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum StatusSelect {
|
||||||
|
/// Pulse when timer reaches 0.
|
||||||
|
OneCyclePulse = 0b000,
|
||||||
|
OutputActiveBit = 0b001,
|
||||||
|
/// Creates a divide by two output clock of the timer.
|
||||||
|
ToggleOnEachCycle = 0b010,
|
||||||
|
/// 1 when count value >= PWM A value, 0 otherwise
|
||||||
|
PwmaOutput = 0b011,
|
||||||
|
/// 1 when count value < PWM A value and >= PWM B, 0 when counter value >= PWM A value or < PWM
|
||||||
|
/// B value
|
||||||
|
PwmbOutput = 0b100,
|
||||||
|
EnabledBit = 0b101,
|
||||||
|
/// 1 when counter value <= PWM A value and 0 otherwise.
|
||||||
|
PwmaActiveBit = 0b110,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
pub struct Control {
|
||||||
|
/// The counter is requested to stop on the next normal count cycle.
|
||||||
|
#[bit(9, rw)]
|
||||||
|
request_stop: bool,
|
||||||
|
#[bit(8, rw)]
|
||||||
|
status_invert: bool,
|
||||||
|
#[bits(5..=7, rw)]
|
||||||
|
status_sel: Option<StatusSelect>,
|
||||||
|
#[bit(4, rw)]
|
||||||
|
irq_enable: bool,
|
||||||
|
/// Only applies if the Auto-Disable bit is 0. The ACTIVE bit goes to 0 when the count reaches
|
||||||
|
/// 0, but the timer remains enabled.
|
||||||
|
#[bit(3, rw)]
|
||||||
|
auto_deactivate: bool,
|
||||||
|
/// Counter is fully disabled when count reaches 0, which means that both the ENABLE
|
||||||
|
/// and ACTIVE bits go to 0.
|
||||||
|
#[bit(2, rw)]
|
||||||
|
auto_disable: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
active: bool,
|
||||||
|
#[bit(0, rw)]
|
||||||
|
enable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EnableControl(arbitrary_int::UInt<u32, 1>);
|
||||||
|
|
||||||
|
impl EnableControl {
|
||||||
|
pub fn new_disable() -> Self {
|
||||||
|
EnableControl(arbitrary_int::UInt::<u32, 1>::from_u32(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_enable() -> Self {
|
||||||
|
EnableControl(arbitrary_int::UInt::<u32, 1>::from_u32(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enabled(&self) -> bool {
|
||||||
|
self.0.value() != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum CascadeInvert {
|
||||||
|
#[default]
|
||||||
|
ActiveHigh = 0,
|
||||||
|
ActiveLow = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When two cascade sources are selected, configure the required operation.
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum DualCascadeOp {
|
||||||
|
#[default]
|
||||||
|
LogicalAnd = 0,
|
||||||
|
LogicalOr = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
pub struct CascadeControl {
|
||||||
|
/// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input
|
||||||
|
/// souce is active when the count reaches 0. If the counter is not 0, the cascade control is
|
||||||
|
/// ignored.
|
||||||
|
#[bit(10, rw)]
|
||||||
|
trigger2: bool,
|
||||||
|
#[bit(9, rw)]
|
||||||
|
inv2: CascadeInvert,
|
||||||
|
/// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar
|
||||||
|
/// to the REQ_STOP control bit, but signalled by a Cascade source.
|
||||||
|
#[bit(8, rw)]
|
||||||
|
en2: bool,
|
||||||
|
/// Same as the trigger field for Cascade 0.
|
||||||
|
#[bit(7, rw)]
|
||||||
|
trigger1: bool,
|
||||||
|
/// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected
|
||||||
|
/// cascade signal active, but once the counter is active, cascade control will be ignored.
|
||||||
|
#[bit(6, rw)]
|
||||||
|
trigger0: bool,
|
||||||
|
/// Specify required operation if both Cascade 0 and Cascade 1 are active.
|
||||||
|
/// 0 is a logical AND of both cascade signals, 1 is a logical OR.
|
||||||
|
#[bit(4, rw)]
|
||||||
|
dual_cascade_op: DualCascadeOp,
|
||||||
|
/// Inversion bit for Cascade 1
|
||||||
|
#[bit(3, rw)]
|
||||||
|
inv1: CascadeInvert,
|
||||||
|
/// Enable Cascade 1 signal active as a requirement for counting.
|
||||||
|
#[bit(2, rw)]
|
||||||
|
en1: bool,
|
||||||
|
/// Inversion bit for Cascade 0.
|
||||||
|
#[bit(1, rw)]
|
||||||
|
inv0: CascadeInvert,
|
||||||
|
/// Enable Cascade 0 signal active as a requirement for counting.
|
||||||
|
#[bit(0, rw)]
|
||||||
|
en0: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct InvalidCascadeSourceId;
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum CascadeSource {
|
||||||
|
PortA(u8),
|
||||||
|
PortB(u8),
|
||||||
|
Tim(u8),
|
||||||
|
RamSbe = 96,
|
||||||
|
RamMbe = 97,
|
||||||
|
RomSbe = 98,
|
||||||
|
RomMbe = 99,
|
||||||
|
Txev = 100,
|
||||||
|
ClockDivider(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum CascadeSource {
|
||||||
|
PortA(u8),
|
||||||
|
PortB(u8),
|
||||||
|
PortC(u8),
|
||||||
|
PortD(u8),
|
||||||
|
PortE(u8),
|
||||||
|
Tim(u8),
|
||||||
|
TxEv,
|
||||||
|
AdcIrq,
|
||||||
|
RomSbe,
|
||||||
|
RomMbe,
|
||||||
|
Ram0Sbe,
|
||||||
|
Ram0Mbe,
|
||||||
|
Ram1Sbe,
|
||||||
|
Ram1Mbe,
|
||||||
|
WdogIrq,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CascadeSource {
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn id(&self) -> Result<u7, InvalidCascadeSourceId> {
|
||||||
|
let port_check = |base: u8, id: u8, len: u8| -> Result<u7, InvalidCascadeSourceId> {
|
||||||
|
if id > len - 1 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
Ok(u7::new(base + id))
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
CascadeSource::PortA(id) => port_check(0, *id, 32),
|
||||||
|
CascadeSource::PortB(id) => port_check(32, *id, 32),
|
||||||
|
CascadeSource::Tim(id) => port_check(64, *id, 24),
|
||||||
|
CascadeSource::RamSbe => Ok(u7::new(96)),
|
||||||
|
CascadeSource::RamMbe => Ok(u7::new(97)),
|
||||||
|
CascadeSource::RomSbe => Ok(u7::new(98)),
|
||||||
|
CascadeSource::RomMbe => Ok(u7::new(99)),
|
||||||
|
CascadeSource::Txev => Ok(u7::new(100)),
|
||||||
|
CascadeSource::ClockDivider(id) => port_check(120, *id, 8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
fn id(&self) -> Result<u7, InvalidCascadeSourceId> {
|
||||||
|
let port_check = |base: u8, id: u8| -> Result<u7, InvalidCascadeSourceId> {
|
||||||
|
if id > 15 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
Ok(u7::new(base + id))
|
||||||
|
};
|
||||||
|
match self {
|
||||||
|
CascadeSource::PortA(id) => port_check(0, *id),
|
||||||
|
CascadeSource::PortB(id) => port_check(16, *id),
|
||||||
|
CascadeSource::PortC(id) => port_check(32, *id),
|
||||||
|
CascadeSource::PortD(id) => port_check(48, *id),
|
||||||
|
CascadeSource::PortE(id) => port_check(64, *id),
|
||||||
|
CascadeSource::Tim(id) => {
|
||||||
|
if *id > 23 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
Ok(u7::new(80 + id))
|
||||||
|
}
|
||||||
|
CascadeSource::TxEv => Ok(u7::new(104)),
|
||||||
|
CascadeSource::AdcIrq => Ok(u7::new(105)),
|
||||||
|
CascadeSource::RomSbe => Ok(u7::new(106)),
|
||||||
|
CascadeSource::RomMbe => Ok(u7::new(106)),
|
||||||
|
CascadeSource::Ram0Sbe => Ok(u7::new(108)),
|
||||||
|
CascadeSource::Ram0Mbe => Ok(u7::new(109)),
|
||||||
|
CascadeSource::Ram1Sbe => Ok(u7::new(110)),
|
||||||
|
CascadeSource::Ram1Mbe => Ok(u7::new(111)),
|
||||||
|
CascadeSource::WdogIrq => Ok(u7::new(112)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
pub fn from_raw(raw: u32) -> Result<Self, InvalidCascadeSourceId> {
|
||||||
|
let id = u7::new((raw & 0x7F) as u8);
|
||||||
|
if id.value() > 127 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
let id = id.as_u8();
|
||||||
|
if id < 32 {
|
||||||
|
return Ok(CascadeSource::PortA(id));
|
||||||
|
} else if (32..56).contains(&id) {
|
||||||
|
return Ok(CascadeSource::PortB(id - 32));
|
||||||
|
} else if (64..88).contains(&id) {
|
||||||
|
return Ok(CascadeSource::Tim(id - 64));
|
||||||
|
} else if id > 120 {
|
||||||
|
return Ok(CascadeSource::ClockDivider(id - 120));
|
||||||
|
}
|
||||||
|
match id {
|
||||||
|
96 => Ok(CascadeSource::RamSbe),
|
||||||
|
97 => Ok(CascadeSource::RamMbe),
|
||||||
|
98 => Ok(CascadeSource::RomSbe),
|
||||||
|
99 => Ok(CascadeSource::RomMbe),
|
||||||
|
100 => Ok(CascadeSource::Txev),
|
||||||
|
_ => Err(InvalidCascadeSourceId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub fn from_raw(raw: u32) -> Result<Self, InvalidCascadeSourceId> {
|
||||||
|
use crate::NUM_PORT_DEFAULT;
|
||||||
|
|
||||||
|
let id = u7::new((raw & 0x7F) as u8);
|
||||||
|
if id.value() > 127 {
|
||||||
|
return Err(InvalidCascadeSourceId);
|
||||||
|
}
|
||||||
|
let id = id.as_u8();
|
||||||
|
if id < 16 {
|
||||||
|
return Ok(CascadeSource::PortA(id));
|
||||||
|
} else if (16..16 + NUM_PORT_DEFAULT as u8).contains(&id) {
|
||||||
|
return Ok(CascadeSource::PortB(id - 16));
|
||||||
|
} else if (32..32 + NUM_PORT_DEFAULT as u8).contains(&id) {
|
||||||
|
return Ok(CascadeSource::PortC(id - 32));
|
||||||
|
} else if (48..48 + NUM_PORT_DEFAULT as u8).contains(&id) {
|
||||||
|
return Ok(CascadeSource::PortD(id - 48));
|
||||||
|
} else if (64..64 + NUM_PORT_DEFAULT as u8).contains(&id) {
|
||||||
|
return Ok(CascadeSource::PortE(id - 64));
|
||||||
|
} else if (80..104).contains(&id) {
|
||||||
|
return Ok(CascadeSource::Tim(id - 80));
|
||||||
|
}
|
||||||
|
match id {
|
||||||
|
104 => Ok(CascadeSource::TxEv),
|
||||||
|
105 => Ok(CascadeSource::AdcIrq),
|
||||||
|
106 => Ok(CascadeSource::RomSbe),
|
||||||
|
107 => Ok(CascadeSource::RomMbe),
|
||||||
|
108 => Ok(CascadeSource::Ram0Sbe),
|
||||||
|
109 => Ok(CascadeSource::Ram0Mbe),
|
||||||
|
110 => Ok(CascadeSource::Ram1Sbe),
|
||||||
|
111 => Ok(CascadeSource::Ram1Mbe),
|
||||||
|
112 => Ok(CascadeSource::WdogIrq),
|
||||||
|
_ => Err(InvalidCascadeSourceId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
pub struct CascadeSourceReg {
|
||||||
|
#[bits(0..=6, rw)]
|
||||||
|
raw: u7,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CascadeSourceReg {
|
||||||
|
pub fn new(source: CascadeSource) -> Result<Self, InvalidCascadeSourceId> {
|
||||||
|
let id = source.id()?;
|
||||||
|
Ok(Self::new_with_raw_value(id.as_u32()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_cascade_source(&self) -> Result<CascadeSource, InvalidCascadeSourceId> {
|
||||||
|
CascadeSource::from_raw(self.raw().as_u32())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[mmio(no_ctors)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Timer {
|
||||||
|
control: Control,
|
||||||
|
reset_value: u32,
|
||||||
|
count_value: u32,
|
||||||
|
enable_control: EnableControl,
|
||||||
|
cascade_control: CascadeControl,
|
||||||
|
/// CASCADE0 and CASCADE1 are used to control the counting and activation of the counter.
|
||||||
|
/// CASCADE2 is used to request stopping of the timer.
|
||||||
|
cascade: [CascadeSourceReg; 3],
|
||||||
|
/// PWM A compare value.
|
||||||
|
pwma_value: u32,
|
||||||
|
/// PWM B compare value.
|
||||||
|
pwmb_value: u32,
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
_reserved: [u32; 0x3f5],
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
_reserved: [u32; 0xf5],
|
||||||
|
/// Vorago 1x: 0x0111_07E1. Vorago 4x: 0x0211_07E9
|
||||||
|
perid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Timer>(), 0x1000);
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Timer>(), 0x400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct InvalidTimerIndex(pub usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct TimId(u8);
|
||||||
|
|
||||||
|
impl TimId {
|
||||||
|
pub const fn new(index: usize) -> Result<Self, InvalidTimerIndex> {
|
||||||
|
if index > 23 {
|
||||||
|
return Err(InvalidTimerIndex(index));
|
||||||
|
}
|
||||||
|
Ok(TimId(index as u8))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_unchecked(index: usize) -> Self {
|
||||||
|
if index > 23 {
|
||||||
|
panic!("invalid timer index");
|
||||||
|
}
|
||||||
|
TimId(index as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unsafely steal the TIM peripheral block for the TIM ID.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents ownership and safety guarantees by the HAL.
|
||||||
|
pub const unsafe fn steal_regs(&self) -> MmioTimer<'static> {
|
||||||
|
Timer::new_mmio(*self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn value(&self) -> u8 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub const fn interrupt_id(&self) -> va416xx::Interrupt {
|
||||||
|
match self.value() {
|
||||||
|
0 => va416xx::Interrupt::TIM0,
|
||||||
|
1 => va416xx::Interrupt::TIM1,
|
||||||
|
2 => va416xx::Interrupt::TIM2,
|
||||||
|
3 => va416xx::Interrupt::TIM3,
|
||||||
|
4 => va416xx::Interrupt::TIM4,
|
||||||
|
5 => va416xx::Interrupt::TIM5,
|
||||||
|
6 => va416xx::Interrupt::TIM6,
|
||||||
|
7 => va416xx::Interrupt::TIM7,
|
||||||
|
8 => va416xx::Interrupt::TIM8,
|
||||||
|
9 => va416xx::Interrupt::TIM9,
|
||||||
|
10 => va416xx::Interrupt::TIM10,
|
||||||
|
11 => va416xx::Interrupt::TIM11,
|
||||||
|
12 => va416xx::Interrupt::TIM12,
|
||||||
|
13 => va416xx::Interrupt::TIM13,
|
||||||
|
14 => va416xx::Interrupt::TIM14,
|
||||||
|
15 => va416xx::Interrupt::TIM15,
|
||||||
|
16 => va416xx::Interrupt::TIM16,
|
||||||
|
17 => va416xx::Interrupt::TIM17,
|
||||||
|
18 => va416xx::Interrupt::TIM18,
|
||||||
|
19 => va416xx::Interrupt::TIM19,
|
||||||
|
20 => va416xx::Interrupt::TIM20,
|
||||||
|
21 => va416xx::Interrupt::TIM21,
|
||||||
|
22 => va416xx::Interrupt::TIM22,
|
||||||
|
23 => va416xx::Interrupt::TIM23,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timer {
|
||||||
|
const fn new_mmio_at(base: usize) -> MmioTimer<'static> {
|
||||||
|
MmioTimer {
|
||||||
|
ptr: base as *mut _,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_mmio(id: TimId) -> MmioTimer<'static> {
|
||||||
|
if cfg!(feature = "vor1x") {
|
||||||
|
Timer::new_mmio_at(BASE_ADDR + 0x1000 * id.value() as usize)
|
||||||
|
} else {
|
||||||
|
Timer::new_mmio_at(BASE_ADDR + 0x400 * id.value() as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn new_mmio_with_raw_index(
|
||||||
|
timer_index: usize,
|
||||||
|
) -> Result<MmioTimer<'static>, InvalidTimerIndex> {
|
||||||
|
if timer_index > 23 {
|
||||||
|
return Err(InvalidTimerIndex(timer_index));
|
||||||
|
}
|
||||||
|
if cfg!(feature = "vor1x") {
|
||||||
|
Ok(Timer::new_mmio_at(BASE_ADDR + 0x1000 * timer_index))
|
||||||
|
} else {
|
||||||
|
Ok(Timer::new_mmio_at(BASE_ADDR + 0x400 * timer_index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1306
src/uart/mod.rs
Normal file
1306
src/uart/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
112
src/uart/pins_vor1x.rs
Normal file
112
src/uart/pins_vor1x.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// UART A pins
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
FunSel,
|
||||||
|
pins::{
|
||||||
|
Pa2, Pa3, Pa8, Pa9, Pa16, Pa17, Pa18, Pa19, Pa26, Pa27, Pa30, Pa31, Pb6, Pb7, Pb8, Pb9,
|
||||||
|
Pb18, Pb19, Pb20, Pb21, Pb22, Pb23, Pin,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Bank, RxPin, TxPin};
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa9> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa8> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa17> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa16> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa31> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa30> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pb9> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pb8> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pb23> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pb22> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART B pins
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa3> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa2> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa19> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa18> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa27> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa26> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pb7> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pb6> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pb19> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pb18> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pb21> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pb20> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
98
src/uart/pins_vor4x.rs
Normal file
98
src/uart/pins_vor4x.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
use crate::pins::{Pc15, Pf8};
|
||||||
|
use crate::{
|
||||||
|
FunSel,
|
||||||
|
gpio::Pin,
|
||||||
|
pins::{Pa2, Pa3, Pb14, Pb15, Pc4, Pc5, Pc14, Pd11, Pd12, Pe2, Pe3, Pf9, Pf12, Pf13, Pg0, Pg1},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Bank, RxPin, TxPin};
|
||||||
|
|
||||||
|
// UART 0 pins
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pa2> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pa3> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pc4> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pc5> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pe2> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pe3> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pg0> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pg1> {
|
||||||
|
const BANK: Bank = Bank::Uart0;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART 1 pins
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pb14> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pb15> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pd11> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pd12> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel3;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pf12> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pf13> {
|
||||||
|
const BANK: Bank = Bank::Uart1;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UART 2 pins
|
||||||
|
|
||||||
|
impl TxPin for Pin<Pc14> {
|
||||||
|
const BANK: Bank = Bank::Uart2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl RxPin for Pin<Pc15> {
|
||||||
|
const BANK: Bank = Bank::Uart2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "va41628"))]
|
||||||
|
impl TxPin for Pin<Pf8> {
|
||||||
|
const BANK: Bank = Bank::Uart2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
||||||
|
impl RxPin for Pin<Pf9> {
|
||||||
|
const BANK: Bank = Bank::Uart2;
|
||||||
|
const FUN_SEL: FunSel = FunSel::Sel1;
|
||||||
|
}
|
322
src/uart/regs.rs
Normal file
322
src/uart/regs.rs
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use arbitrary_int::{u5, u6, u18};
|
||||||
|
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(feature = "vor1x")] {
|
||||||
|
/// UART A base address
|
||||||
|
pub const BASE_ADDR_0: usize = 0x4004_0000;
|
||||||
|
/// UART B base address
|
||||||
|
pub const BASE_ADDR_1: usize = 0x4004_1000;
|
||||||
|
} else if #[cfg(feature = "vor4x")] {
|
||||||
|
/// UART 0 base address
|
||||||
|
pub const BASE_ADDR_0: usize = 0x4002_4000;
|
||||||
|
/// UART 1 base address
|
||||||
|
pub const BASE_ADDR_1: usize = 0x4002_5000;
|
||||||
|
/// UART 2 base address
|
||||||
|
pub const BASE_ADDR_2: usize = 0x4001_7000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Bank {
|
||||||
|
Uart0 = 0,
|
||||||
|
Uart1 = 1,
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Uart2 = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Bank {
|
||||||
|
/// Unsafely steal the GPIO peripheral block for the given port.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Circumvents ownership and safety guarantees by the HAL.
|
||||||
|
pub unsafe fn steal_regs(&self) -> MmioUart<'static> {
|
||||||
|
Uart::new_mmio(*self)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub const fn interrupt_id_tx(&self) -> va416xx::Interrupt {
|
||||||
|
match self {
|
||||||
|
Bank::Uart0 => va416xx::Interrupt::UART0_TX,
|
||||||
|
Bank::Uart1 => va416xx::Interrupt::UART1_TX,
|
||||||
|
Bank::Uart2 => va416xx::Interrupt::UART2_TX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
pub const fn interrupt_id_rx(&self) -> va416xx::Interrupt {
|
||||||
|
match self {
|
||||||
|
Bank::Uart0 => va416xx::Interrupt::UART0_RX,
|
||||||
|
Bank::Uart1 => va416xx::Interrupt::UART1_RX,
|
||||||
|
Bank::Uart2 => va416xx::Interrupt::UART2_RX,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Data {
|
||||||
|
#[bit(15, rw)]
|
||||||
|
dparity: bool,
|
||||||
|
#[bits(0..=7, rw)]
|
||||||
|
value: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Enable {
|
||||||
|
#[bit(1, rw)]
|
||||||
|
tx: bool,
|
||||||
|
#[bit(0, rw)]
|
||||||
|
rx: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u1, exhaustive = true)]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Stopbits {
|
||||||
|
One = 0,
|
||||||
|
Two = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitenum(u2, exhaustive = true)]
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum WordSize {
|
||||||
|
Five = 0b00,
|
||||||
|
Six = 0b01,
|
||||||
|
Seven = 0b10,
|
||||||
|
Eight = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Control {
|
||||||
|
#[bit(11, rw)]
|
||||||
|
baud8: bool,
|
||||||
|
#[bit(10, rw)]
|
||||||
|
auto_rts: bool,
|
||||||
|
#[bit(9, rw)]
|
||||||
|
def_rts: bool,
|
||||||
|
#[bit(8, rw)]
|
||||||
|
auto_cts: bool,
|
||||||
|
#[bit(7, rw)]
|
||||||
|
loopback_block: bool,
|
||||||
|
#[bit(6, rw)]
|
||||||
|
loopback: bool,
|
||||||
|
#[bits(4..=5, rw)]
|
||||||
|
wordsize: WordSize,
|
||||||
|
#[bit(3, rw)]
|
||||||
|
stopbits: Stopbits,
|
||||||
|
#[bit(2, rw)]
|
||||||
|
parity_manual: bool,
|
||||||
|
#[bit(1, rw)]
|
||||||
|
parity_even: bool,
|
||||||
|
#[bit(0, rw)]
|
||||||
|
parity_enable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ClkScale {
|
||||||
|
#[bits(6..=23, rw)]
|
||||||
|
int: u18,
|
||||||
|
#[bits(0..=5, rw)]
|
||||||
|
frac: u6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RxStatus {
|
||||||
|
#[bit(15, r)]
|
||||||
|
rx_rtsn: bool,
|
||||||
|
#[bit(9, r)]
|
||||||
|
rx_addr9: bool,
|
||||||
|
#[bit(8, r)]
|
||||||
|
busy_break: bool,
|
||||||
|
#[bit(7, r)]
|
||||||
|
break_error: bool,
|
||||||
|
#[bit(6, r)]
|
||||||
|
parity_error: bool,
|
||||||
|
#[bit(5, r)]
|
||||||
|
framing_error: bool,
|
||||||
|
#[bit(4, r)]
|
||||||
|
overrun_error: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
timeout: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
busy: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
not_full: bool,
|
||||||
|
#[bit(0, r)]
|
||||||
|
data_available: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TxStatus {
|
||||||
|
#[bit(15, r)]
|
||||||
|
tx_ctsn: bool,
|
||||||
|
#[bit(3, r)]
|
||||||
|
wr_lost: bool,
|
||||||
|
#[bit(2, r)]
|
||||||
|
tx_busy: bool,
|
||||||
|
#[bit(1, r)]
|
||||||
|
write_busy: bool,
|
||||||
|
/// There is space in the FIFO to write data.
|
||||||
|
#[bit(0, r)]
|
||||||
|
ready: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FifoClear {
|
||||||
|
#[bit(1, w)]
|
||||||
|
tx: bool,
|
||||||
|
#[bit(0, w)]
|
||||||
|
rx: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptControl {
|
||||||
|
/// Generate an interrrupt when the RX FIFO is at least half-full (FIFO count >= trigger level)
|
||||||
|
#[bit(0, rw)]
|
||||||
|
rx: bool,
|
||||||
|
/// Interrupts for status conditions (overrun, framing, parity and break)
|
||||||
|
#[bit(1, rw)]
|
||||||
|
rx_status: bool,
|
||||||
|
/// Interrupt on timeout conditions.
|
||||||
|
#[bit(2, rw)]
|
||||||
|
rx_timeout: bool,
|
||||||
|
|
||||||
|
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
|
||||||
|
#[bit(4, rw)]
|
||||||
|
tx: bool,
|
||||||
|
/// Generates an interrupt on TX FIFO overflow.
|
||||||
|
#[bit(5, rw)]
|
||||||
|
tx_status: bool,
|
||||||
|
/// Generates an interrupt when the transmit FIFO is empty and TXBUSY is 0.
|
||||||
|
#[bit(6, rw)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(7, rw)]
|
||||||
|
tx_cts: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptStatus {
|
||||||
|
/// Generate an interrrupt when the RX FIFO is at least half-full (FIFO count >= trigger level)
|
||||||
|
#[bit(0, r)]
|
||||||
|
rx: bool,
|
||||||
|
/// Interrupts for status conditions (overrun, framing, parity and break)
|
||||||
|
#[bit(1, r)]
|
||||||
|
rx_status: bool,
|
||||||
|
/// Interrupt on timeout conditions.
|
||||||
|
#[bit(2, r)]
|
||||||
|
rx_timeout: bool,
|
||||||
|
|
||||||
|
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
|
||||||
|
#[bit(4, r)]
|
||||||
|
tx: bool,
|
||||||
|
/// Generates an interrupt on TX FIFO overflow.
|
||||||
|
#[bit(5, r)]
|
||||||
|
tx_status: bool,
|
||||||
|
/// Generates an interrupt when the transmit FIFO is empty and TXBUSY is 0.
|
||||||
|
#[bit(6, r)]
|
||||||
|
tx_empty: bool,
|
||||||
|
#[bit(7, r)]
|
||||||
|
tx_cts: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As specified in the VA416x0 Programmers Guide, only the RX overflow bit can be cleared.
|
||||||
|
#[bitbybit::bitfield(u32, default = 0x0)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InterruptClear {
|
||||||
|
#[bit(1, w)]
|
||||||
|
rx_overrun: bool,
|
||||||
|
/// Not sure if this does anything, the programmer guides are not consistent on this..
|
||||||
|
#[bit(5, w)]
|
||||||
|
tx_overrun: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FifoTrigger {
|
||||||
|
#[bits(0..=4, rw)]
|
||||||
|
level: u5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bitbybit::bitfield(u32)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct State {
|
||||||
|
#[bits(0..=7, r)]
|
||||||
|
rx_state: u8,
|
||||||
|
/// Data count.
|
||||||
|
#[bits(8..=12, r)]
|
||||||
|
rx_fifo: u5,
|
||||||
|
#[bits(16..=23, r)]
|
||||||
|
tx_state: u8,
|
||||||
|
/// Data count.
|
||||||
|
#[bits(24..=28, r)]
|
||||||
|
tx_fifo: u5,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(derive_mmio::Mmio)]
|
||||||
|
#[mmio(no_ctors)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Uart {
|
||||||
|
data: Data,
|
||||||
|
enable: Enable,
|
||||||
|
ctrl: Control,
|
||||||
|
clkscale: ClkScale,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
rx_status: RxStatus,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
tx_status: TxStatus,
|
||||||
|
#[mmio(Write)]
|
||||||
|
fifo_clr: FifoClear,
|
||||||
|
#[mmio(Write)]
|
||||||
|
txbreak: u32,
|
||||||
|
addr9: u32,
|
||||||
|
addr9mask: u32,
|
||||||
|
irq_enabled: InterruptControl,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_raw: InterruptStatus,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
irq_status: InterruptStatus,
|
||||||
|
#[mmio(Write)]
|
||||||
|
irq_clr: InterruptClear,
|
||||||
|
rx_fifo_trigger: FifoTrigger,
|
||||||
|
tx_fifo_trigger: FifoTrigger,
|
||||||
|
rx_fifo_rts_trigger: u32,
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
state: State,
|
||||||
|
_reserved: [u32; 0x3ED],
|
||||||
|
/// Vorago 1x value: 0x0112_07E1. Vorago 4x value: 0x0212_07E9
|
||||||
|
#[mmio(PureRead)]
|
||||||
|
perid: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::const_assert_eq!(core::mem::size_of::<Uart>(), 0x1000);
|
||||||
|
|
||||||
|
impl Uart {
|
||||||
|
fn new_mmio_at(base: usize) -> MmioUart<'static> {
|
||||||
|
MmioUart {
|
||||||
|
ptr: base as *mut _,
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_mmio(bank: Bank) -> MmioUart<'static> {
|
||||||
|
match bank {
|
||||||
|
Bank::Uart0 => Self::new_mmio_at(BASE_ADDR_0),
|
||||||
|
Bank::Uart1 => Self::new_mmio_at(BASE_ADDR_1),
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
Bank::Uart2 => Self::new_mmio_at(BASE_ADDR_2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
443
src/uart/rx_asynch.rs
Normal file
443
src/uart/rx_asynch.rs
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
//! # Async UART reception functionality.
|
||||||
|
//!
|
||||||
|
//! This module provides the [RxAsync] and [RxAsyncOverwriting] struct which both implement the
|
||||||
|
//! [embedded_io_async::Read] trait.
|
||||||
|
//! This trait allows for asynchronous reception of data streams. Please note that this module does
|
||||||
|
//! not specify/declare the interrupt handlers which must be provided for async support to work.
|
||||||
|
//! However, it provides two interrupt handlers:
|
||||||
|
//!
|
||||||
|
//! - [on_interrupt_rx]
|
||||||
|
//! - [on_interrupt_rx_overwriting]
|
||||||
|
//!
|
||||||
|
//! The first two are used for the [RxAsync] struct, while the latter two are used with the
|
||||||
|
//! [RxAsyncOverwriting] struct. The later two will overwrite old values in the used ring buffer.
|
||||||
|
//!
|
||||||
|
//! Error handling is performed in the user interrupt handler by checking the [AsyncUartErrors]
|
||||||
|
//! structure returned by the interrupt handlers.
|
||||||
|
use core::{cell::RefCell, convert::Infallible, future::Future, sync::atomic::Ordering};
|
||||||
|
|
||||||
|
use arbitrary_int::Number;
|
||||||
|
use critical_section::Mutex;
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
use embedded_io::ErrorType;
|
||||||
|
use portable_atomic::AtomicBool;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Bank, Rx, UartErrors,
|
||||||
|
regs::{InterruptClear, MmioUart},
|
||||||
|
};
|
||||||
|
|
||||||
|
static UART_RX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
|
||||||
|
static RX_READ_ACTIVE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||||
|
static RX_HAS_DATA: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||||
|
|
||||||
|
struct RxFuture {
|
||||||
|
id: Bank,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RxFuture {
|
||||||
|
pub fn new(rx: &mut Rx) -> Self {
|
||||||
|
RX_READ_ACTIVE[rx.id as usize].store(true, Ordering::Relaxed);
|
||||||
|
Self { id: rx.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for RxFuture {
|
||||||
|
type Output = Result<(), Infallible>;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: core::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut core::task::Context<'_>,
|
||||||
|
) -> core::task::Poll<Self::Output> {
|
||||||
|
UART_RX_WAKERS[self.id as usize].register(cx.waker());
|
||||||
|
if RX_HAS_DATA[self.id as usize].load(Ordering::Relaxed) {
|
||||||
|
return core::task::Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
core::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct AsyncUartErrors {
|
||||||
|
/// Queue has overflowed, data might have been lost.
|
||||||
|
pub queue_overflow: bool,
|
||||||
|
/// UART errors.
|
||||||
|
pub uart_errors: UartErrors,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_interrupt_handle_rx_errors(uart: &mut MmioUart<'static>) -> Option<UartErrors> {
|
||||||
|
let rx_status = uart.read_rx_status();
|
||||||
|
if rx_status.overrun_error() || rx_status.framing_error() || rx_status.parity_error() {
|
||||||
|
let mut errors_val = UartErrors::default();
|
||||||
|
|
||||||
|
if rx_status.overrun_error() {
|
||||||
|
errors_val.overflow = true;
|
||||||
|
}
|
||||||
|
if rx_status.framing_error() {
|
||||||
|
errors_val.framing = true;
|
||||||
|
}
|
||||||
|
if rx_status.parity_error() {
|
||||||
|
errors_val.parity = true;
|
||||||
|
}
|
||||||
|
return Some(errors_val);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_interrupt_rx_common_post_processing(
|
||||||
|
id: Bank,
|
||||||
|
rx_enabled: bool,
|
||||||
|
read_some_data: bool,
|
||||||
|
) -> Option<UartErrors> {
|
||||||
|
let idx = id as usize;
|
||||||
|
if read_some_data {
|
||||||
|
RX_HAS_DATA[idx].store(true, Ordering::Relaxed);
|
||||||
|
if RX_READ_ACTIVE[idx].load(Ordering::Relaxed) {
|
||||||
|
UART_RX_WAKERS[idx].wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut errors = None;
|
||||||
|
let mut uart_regs = unsafe { id.steal_regs() };
|
||||||
|
// Check for RX errors
|
||||||
|
if rx_enabled {
|
||||||
|
errors = on_interrupt_handle_rx_errors(&mut uart_regs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the interrupt status bits
|
||||||
|
uart_regs.write_irq_clr(
|
||||||
|
InterruptClear::builder()
|
||||||
|
.with_rx_overrun(true)
|
||||||
|
.with_tx_overrun(false)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt handler with overwriting behaviour when the ring buffer is full.
|
||||||
|
///
|
||||||
|
/// Should be called in the user interrupt handler to enable
|
||||||
|
/// asynchronous reception. This variant will overwrite old data in the ring buffer in case
|
||||||
|
/// the ring buffer is full.
|
||||||
|
pub fn on_interrupt_rx_overwriting<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<u8, N>,
|
||||||
|
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_interrupt_rx_async_heapless_queue_overwriting<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<u8, N>,
|
||||||
|
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
let uart_regs = unsafe { bank.steal_regs() };
|
||||||
|
let irq_status = uart_regs.read_irq_status();
|
||||||
|
let irq_enabled = uart_regs.read_irq_enabled();
|
||||||
|
let rx_enabled = irq_enabled.rx();
|
||||||
|
let mut read_some_data = false;
|
||||||
|
let mut queue_overflow = false;
|
||||||
|
|
||||||
|
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
||||||
|
if irq_status.rx() {
|
||||||
|
let available_bytes = uart_regs.read_rx_fifo_trigger().level().as_usize();
|
||||||
|
|
||||||
|
// If this interrupt bit is set, the trigger level is available at the very least.
|
||||||
|
// Read everything as fast as possible
|
||||||
|
for _ in 0..available_bytes {
|
||||||
|
let byte = uart_regs.read_data().value();
|
||||||
|
if !prod.ready() {
|
||||||
|
queue_overflow = true;
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
|
||||||
|
cons_ref.as_mut().unwrap().dequeue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
prod.enqueue(byte).ok();
|
||||||
|
}
|
||||||
|
read_some_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout, empty the FIFO completely.
|
||||||
|
if irq_status.rx_timeout() {
|
||||||
|
while uart_regs.read_rx_status().data_available() {
|
||||||
|
// While there is data in the FIFO, write it into the reception buffer
|
||||||
|
let byte = uart_regs.read_data().value();
|
||||||
|
if !prod.ready() {
|
||||||
|
queue_overflow = true;
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut cons_ref = shared_consumer.borrow(cs).borrow_mut();
|
||||||
|
cons_ref.as_mut().unwrap().dequeue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
prod.enqueue(byte).ok();
|
||||||
|
}
|
||||||
|
read_some_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uart_errors = on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data);
|
||||||
|
if uart_errors.is_some() || queue_overflow {
|
||||||
|
return Err(AsyncUartErrors {
|
||||||
|
queue_overflow,
|
||||||
|
uart_errors: uart_errors.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interrupt handler for asynchronous RX operations.
|
||||||
|
///
|
||||||
|
/// Should be called in the user interrupt handler to enable asynchronous reception.
|
||||||
|
pub fn on_interrupt_rx<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<'_, u8, N>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
on_interrupt_rx_async_heapless_queue(bank, prod)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_interrupt_rx_async_heapless_queue<const N: usize>(
|
||||||
|
bank: Bank,
|
||||||
|
prod: &mut heapless::spsc::Producer<'_, u8, N>,
|
||||||
|
) -> Result<(), AsyncUartErrors> {
|
||||||
|
let uart_regs = unsafe { bank.steal_regs() };
|
||||||
|
let irq_status = uart_regs.read_irq_status();
|
||||||
|
let irq_enabled = uart_regs.read_irq_enabled();
|
||||||
|
let rx_enabled = irq_enabled.rx();
|
||||||
|
let mut read_some_data = false;
|
||||||
|
let mut queue_overflow = false;
|
||||||
|
|
||||||
|
// Half-Full interrupt. We have a guaranteed amount of data we can read.
|
||||||
|
if irq_status.rx() {
|
||||||
|
let available_bytes = uart_regs.read_rx_fifo_trigger().level().as_usize();
|
||||||
|
|
||||||
|
// If this interrupt bit is set, the trigger level is available at the very least.
|
||||||
|
// Read everything as fast as possible
|
||||||
|
for _ in 0..available_bytes {
|
||||||
|
let byte = uart_regs.read_data().value();
|
||||||
|
if !prod.ready() {
|
||||||
|
queue_overflow = true;
|
||||||
|
}
|
||||||
|
prod.enqueue(byte).ok();
|
||||||
|
}
|
||||||
|
read_some_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout, empty the FIFO completely.
|
||||||
|
if irq_status.rx_timeout() {
|
||||||
|
while uart_regs.read_rx_status().data_available() {
|
||||||
|
// While there is data in the FIFO, write it into the reception buffer
|
||||||
|
let byte = uart_regs.read_data().value();
|
||||||
|
if !prod.ready() {
|
||||||
|
queue_overflow = true;
|
||||||
|
}
|
||||||
|
prod.enqueue(byte).ok();
|
||||||
|
}
|
||||||
|
read_some_data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uart_errors = on_interrupt_rx_common_post_processing(bank, rx_enabled, read_some_data);
|
||||||
|
if uart_errors.is_some() || queue_overflow {
|
||||||
|
return Err(AsyncUartErrors {
|
||||||
|
queue_overflow,
|
||||||
|
uart_errors: uart_errors.unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ActiveReadGuard(usize);
|
||||||
|
|
||||||
|
impl Drop for ActiveReadGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
RX_READ_ACTIVE[self.0].store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RxAsyncInner<const N: usize> {
|
||||||
|
rx: Rx,
|
||||||
|
pub queue: heapless::spsc::Consumer<'static, u8, N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core data structure to allow asynchronous UART reception.
|
||||||
|
///
|
||||||
|
/// If the ring buffer becomes full, data will be lost.
|
||||||
|
pub struct RxAsync<const N: usize>(Option<RxAsyncInner<N>>);
|
||||||
|
|
||||||
|
impl<const N: usize> ErrorType for RxAsync<N> {
|
||||||
|
/// Error reporting is done using the result of the interrupt functions.
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_async_rx(rx: &mut Rx) {
|
||||||
|
rx.disable_interrupts();
|
||||||
|
rx.disable();
|
||||||
|
rx.clear_fifo();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> RxAsync<N> {
|
||||||
|
/// Create a new asynchronous receiver.
|
||||||
|
///
|
||||||
|
/// The passed [heapless::spsc::Consumer] will be used to asynchronously receive data which
|
||||||
|
/// is filled by the interrupt handler [on_interrupt_rx].
|
||||||
|
pub fn new(mut rx: Rx, queue: heapless::spsc::Consumer<'static, u8, N>) -> Self {
|
||||||
|
rx.disable_interrupts();
|
||||||
|
rx.disable();
|
||||||
|
rx.clear_fifo();
|
||||||
|
// Enable those together.
|
||||||
|
critical_section::with(|_| {
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
rx.enable_interrupts(true);
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
rx.enable_interrupts(true, true);
|
||||||
|
rx.enable();
|
||||||
|
});
|
||||||
|
Self(Some(RxAsyncInner { rx, queue }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(mut self) -> (Rx, heapless::spsc::Consumer<'static, u8, N>) {
|
||||||
|
self.stop();
|
||||||
|
let inner = self.0.take().unwrap();
|
||||||
|
(inner.rx, inner.queue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Drop for RxAsync<N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> embedded_io_async::Read for RxAsync<N> {
|
||||||
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
let inner = self.0.as_ref().unwrap();
|
||||||
|
// Need to wait for the IRQ to read data and set this flag. If the queue is not
|
||||||
|
// empty, we can read data immediately.
|
||||||
|
if inner.queue.len() == 0 {
|
||||||
|
RX_HAS_DATA[inner.rx.id as usize].store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
let _guard = ActiveReadGuard(inner.rx.id as usize);
|
||||||
|
let mut handle_data_in_queue = |consumer: &mut heapless::spsc::Consumer<'static, u8, N>| {
|
||||||
|
let data_to_read = consumer.len().min(buf.len());
|
||||||
|
for byte in buf.iter_mut().take(data_to_read) {
|
||||||
|
// We own the consumer and we checked that the amount of data is guaranteed to be available.
|
||||||
|
*byte = unsafe { consumer.dequeue_unchecked() };
|
||||||
|
}
|
||||||
|
data_to_read
|
||||||
|
};
|
||||||
|
let mut_ref = self.0.as_mut().unwrap();
|
||||||
|
let fut = RxFuture::new(&mut mut_ref.rx);
|
||||||
|
// Data is available, so read that data immediately.
|
||||||
|
let read_data = handle_data_in_queue(&mut mut_ref.queue);
|
||||||
|
if read_data > 0 {
|
||||||
|
return Ok(read_data);
|
||||||
|
}
|
||||||
|
// Await data.
|
||||||
|
let _ = fut.await;
|
||||||
|
Ok(handle_data_in_queue(&mut mut_ref.queue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RxAsyncOverwritingInner<const N: usize> {
|
||||||
|
rx: Rx,
|
||||||
|
pub shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Core data structure to allow asynchronous UART reception.
|
||||||
|
///
|
||||||
|
/// If the ring buffer becomes full, the oldest data will be overwritten when using the
|
||||||
|
/// [on_interrupt_rx_overwriting] interrupt handlers.
|
||||||
|
pub struct RxAsyncOverwriting<const N: usize>(Option<RxAsyncOverwritingInner<N>>);
|
||||||
|
|
||||||
|
impl<const N: usize> ErrorType for RxAsyncOverwriting<N> {
|
||||||
|
/// Error reporting is done using the result of the interrupt functions.
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> RxAsyncOverwriting<N> {
|
||||||
|
/// Create a new asynchronous receiver.
|
||||||
|
///
|
||||||
|
/// The passed shared [heapless::spsc::Consumer] will be used to asynchronously receive data
|
||||||
|
/// which is filled by the interrupt handler. The shared property allows using it in the
|
||||||
|
/// interrupt handler to overwrite old data.
|
||||||
|
pub fn new(
|
||||||
|
mut rx: Rx,
|
||||||
|
shared_consumer: &'static Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8, N>>>>,
|
||||||
|
) -> Self {
|
||||||
|
rx.disable_interrupts();
|
||||||
|
rx.disable();
|
||||||
|
rx.clear_fifo();
|
||||||
|
// Enable those together.
|
||||||
|
critical_section::with(|_| {
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
rx.enable_interrupts(true, true);
|
||||||
|
#[cfg(feature = "vor1x")]
|
||||||
|
rx.enable_interrupts(true);
|
||||||
|
rx.enable();
|
||||||
|
});
|
||||||
|
Self(Some(RxAsyncOverwritingInner {
|
||||||
|
rx,
|
||||||
|
shared_consumer,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
stop_async_rx(&mut self.0.as_mut().unwrap().rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(mut self) -> Rx {
|
||||||
|
self.stop();
|
||||||
|
let inner = self.0.take().unwrap();
|
||||||
|
inner.rx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Drop for RxAsyncOverwriting<N> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> embedded_io_async::Read for RxAsyncOverwriting<N> {
|
||||||
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
let inner = self.0.as_ref().unwrap();
|
||||||
|
let id = inner.rx.id as usize;
|
||||||
|
// Need to wait for the IRQ to read data and set this flag. If the queue is not
|
||||||
|
// empty, we can read data immediately.
|
||||||
|
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let queue = inner.shared_consumer.borrow(cs);
|
||||||
|
if queue.borrow().as_ref().unwrap().len() == 0 {
|
||||||
|
RX_HAS_DATA[id].store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let _guard = ActiveReadGuard(id);
|
||||||
|
let mut handle_data_in_queue = |inner: &mut RxAsyncOverwritingInner<N>| {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let mut consumer_ref = inner.shared_consumer.borrow(cs).borrow_mut();
|
||||||
|
let consumer = consumer_ref.as_mut().unwrap();
|
||||||
|
let data_to_read = consumer.len().min(buf.len());
|
||||||
|
for byte in buf.iter_mut().take(data_to_read) {
|
||||||
|
// We own the consumer and we checked that the amount of data is guaranteed to be available.
|
||||||
|
*byte = unsafe { consumer.dequeue_unchecked() };
|
||||||
|
}
|
||||||
|
data_to_read
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let fut = RxFuture::new(&mut self.0.as_mut().unwrap().rx);
|
||||||
|
// Data is available, so read that data immediately.
|
||||||
|
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
|
||||||
|
if read_data > 0 {
|
||||||
|
return Ok(read_data);
|
||||||
|
}
|
||||||
|
// Await data.
|
||||||
|
let _ = fut.await;
|
||||||
|
let read_data = handle_data_in_queue(self.0.as_mut().unwrap());
|
||||||
|
Ok(read_data)
|
||||||
|
}
|
||||||
|
}
|
208
src/uart/tx_asynch.rs
Normal file
208
src/uart/tx_asynch.rs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
//! # Async UART transmission functionality.
|
||||||
|
//!
|
||||||
|
//! This module provides the [TxAsync] struct which implements the [embedded_io_async::Write] trait.
|
||||||
|
//! This trait allows for asynchronous sending of data streams. Please note that this module does
|
||||||
|
//! not specify/declare the interrupt handlers which must be provided for async support to work.
|
||||||
|
//! However, it the [on_interrupt_tx] interrupt handler.
|
||||||
|
//!
|
||||||
|
//! This handler should be called in ALL user interrupt handlers which handle UART TX interrupts
|
||||||
|
//! for a given UART bank.
|
||||||
|
use core::{cell::RefCell, future::Future};
|
||||||
|
|
||||||
|
use critical_section::Mutex;
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
use embedded_io_async::Write;
|
||||||
|
use portable_atomic::AtomicBool;
|
||||||
|
use raw_slice::RawBufSlice;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
static UART_TX_WAKERS: [AtomicWaker; 2] = [const { AtomicWaker::new() }; 2];
|
||||||
|
static TX_CONTEXTS: [Mutex<RefCell<TxContext>>; 2] =
|
||||||
|
[const { Mutex::new(RefCell::new(TxContext::new())) }; 2];
|
||||||
|
// Completion flag. Kept outside of the context structure as an atomic to avoid
|
||||||
|
// critical section.
|
||||||
|
static TX_DONE: [AtomicBool; 2] = [const { AtomicBool::new(false) }; 2];
|
||||||
|
|
||||||
|
/// This is a generic interrupt handler to handle asynchronous UART TX operations for a given
|
||||||
|
/// UART bank.
|
||||||
|
///
|
||||||
|
/// The user has to call this once in the interrupt handler responsible for the TX interrupts on
|
||||||
|
/// the given UART bank.
|
||||||
|
pub fn on_interrupt_tx(bank: Bank) {
|
||||||
|
let mut uart = unsafe { bank.steal_regs() };
|
||||||
|
let idx = bank as usize;
|
||||||
|
let irq_enabled = uart.read_irq_enabled();
|
||||||
|
// IRQ is not related to TX.
|
||||||
|
if !irq_enabled.tx() && !irq_enabled.tx_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_status = uart.read_tx_status();
|
||||||
|
let unexpected_overrun = tx_status.wr_lost();
|
||||||
|
let mut context = critical_section::with(|cs| {
|
||||||
|
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||||
|
*context_ref.borrow()
|
||||||
|
});
|
||||||
|
context.tx_overrun = unexpected_overrun;
|
||||||
|
// Safety: We documented that the user provided slice must outlive the future, so we convert
|
||||||
|
// the raw pointer back to the slice here.
|
||||||
|
let slice = unsafe { context.slice.get().unwrap() };
|
||||||
|
if context.progress >= slice.len() && !tx_status.tx_busy() {
|
||||||
|
uart.modify_irq_enabled(|mut value| {
|
||||||
|
value.set_tx(false);
|
||||||
|
value.set_tx_empty(false);
|
||||||
|
value.set_tx_status(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
uart.modify_enable(|mut value| {
|
||||||
|
value.set_tx(false);
|
||||||
|
value
|
||||||
|
});
|
||||||
|
// Write back updated context structure.
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||||
|
*context_ref.borrow_mut() = context;
|
||||||
|
});
|
||||||
|
// Transfer is done.
|
||||||
|
TX_DONE[idx].store(true, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
UART_TX_WAKERS[idx].wake();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while context.progress < slice.len() {
|
||||||
|
if !uart.read_tx_status().ready() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Safety: TX structure is owned by the future which does not write into the the data
|
||||||
|
// register, so we can assume we are the only one writing to the data register.
|
||||||
|
uart.write_data(Data::new_with_raw_value(slice[context.progress] as u32));
|
||||||
|
context.progress += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back updated context structure.
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let context_ref = TX_CONTEXTS[idx].borrow(cs);
|
||||||
|
*context_ref.borrow_mut() = context;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct TxContext {
|
||||||
|
progress: usize,
|
||||||
|
tx_overrun: bool,
|
||||||
|
slice: RawBufSlice,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::new_without_default)]
|
||||||
|
impl TxContext {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
progress: 0,
|
||||||
|
tx_overrun: false,
|
||||||
|
slice: RawBufSlice::new_nulled(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TxFuture {
|
||||||
|
id: Bank,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TxFuture {
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function stores the raw pointer of the passed data slice. The user MUST ensure
|
||||||
|
/// that the slice outlives the data structure.
|
||||||
|
pub unsafe fn new(tx: &mut Tx, data: &[u8]) -> Self {
|
||||||
|
TX_DONE[tx.id as usize].store(false, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
tx.disable_interrupts();
|
||||||
|
tx.disable();
|
||||||
|
tx.clear_fifo();
|
||||||
|
|
||||||
|
let init_fill_count = core::cmp::min(data.len(), 16);
|
||||||
|
// We fill the FIFO.
|
||||||
|
for data in data.iter().take(init_fill_count) {
|
||||||
|
tx.regs.write_data(Data::new_with_raw_value(*data as u32));
|
||||||
|
}
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let context_ref = TX_CONTEXTS[tx.id as usize].borrow(cs);
|
||||||
|
let mut context = context_ref.borrow_mut();
|
||||||
|
unsafe { context.slice.set(data) };
|
||||||
|
context.progress = init_fill_count;
|
||||||
|
|
||||||
|
// Ensure those are enabled inside a critical section at the same time. Can lead to
|
||||||
|
// weird glitches otherwise.
|
||||||
|
tx.enable_interrupts(
|
||||||
|
#[cfg(feature = "vor4x")]
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
tx.enable();
|
||||||
|
});
|
||||||
|
Self { id: tx.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for TxFuture {
|
||||||
|
type Output = Result<usize, TxOverrunError>;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: core::pin::Pin<&mut Self>,
|
||||||
|
cx: &mut core::task::Context<'_>,
|
||||||
|
) -> core::task::Poll<Self::Output> {
|
||||||
|
UART_TX_WAKERS[self.id as usize].register(cx.waker());
|
||||||
|
if TX_DONE[self.id as usize].swap(false, core::sync::atomic::Ordering::Relaxed) {
|
||||||
|
let progress = critical_section::with(|cs| {
|
||||||
|
TX_CONTEXTS[self.id as usize].borrow(cs).borrow().progress
|
||||||
|
});
|
||||||
|
return core::task::Poll::Ready(Ok(progress));
|
||||||
|
}
|
||||||
|
core::task::Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for TxFuture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut reg_block = unsafe { self.id.steal_regs() };
|
||||||
|
|
||||||
|
disable_tx_interrupts(&mut reg_block);
|
||||||
|
disable_tx(&mut reg_block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TxAsync(Tx);
|
||||||
|
|
||||||
|
impl TxAsync {
|
||||||
|
pub fn new(tx: Tx) -> Self {
|
||||||
|
Self(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self) -> Tx {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[error("TX overrun error")]
|
||||||
|
pub struct TxOverrunError;
|
||||||
|
|
||||||
|
impl embedded_io_async::Error for TxOverrunError {
|
||||||
|
fn kind(&self) -> embedded_io_async::ErrorKind {
|
||||||
|
embedded_io_async::ErrorKind::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl embedded_io::ErrorType for TxAsync {
|
||||||
|
type Error = TxOverrunError;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for TxAsync {
|
||||||
|
/// Write a buffer asynchronously.
|
||||||
|
///
|
||||||
|
/// This implementation is not side effect free, and a started future might have already
|
||||||
|
/// written part of the passed buffer.
|
||||||
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||||
|
let fut = unsafe { TxFuture::new(&mut self.0, buf) };
|
||||||
|
fut.await
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user