commit ba8d817a737098492d3578e2e00a4c20236d10cd Author: Robin Mueller Date: Tue Apr 22 13:45:36 2025 +0200 init commit diff --git a/.cargo/.gitignore b/.cargo/.gitignore new file mode 100644 index 0000000..5b6c096 --- /dev/null +++ b/.cargo/.gitignore @@ -0,0 +1 @@ +config.toml diff --git a/.cargo/def-config.toml b/.cargo/def-config.toml new file mode 100644 index 0000000..a075fc5 --- /dev/null +++ b/.cargo/def-config.toml @@ -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" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5a7660 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..445eb9d --- /dev/null +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4db5d2b --- /dev/null +++ b/Cargo.toml @@ -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"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c9da24 --- /dev/null +++ b/README.md @@ -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). diff --git a/src/clock.rs b/src/clock.rs new file mode 100644 index 0000000..c8d5ab9 --- /dev/null +++ b/src/clock.rs @@ -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 + } +} diff --git a/src/embassy.rs b/src/embassy.rs new file mode 100644 index 0000000..72b7e91 --- /dev/null +++ b/src/embassy.rs @@ -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, +} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +unsafe impl Send for AlarmState {} + +static SCALE: OnceCell = OnceCell::new(); +static TIMEKEEPER_TIM: OnceCell = OnceCell::new(); +static ALARM_TIM: OnceCell = OnceCell::new(); + +pub struct TimerDriver { + periods: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +impl TimerDriver { + #[cfg(feature = "vor1x")] + #[doc(hidden)] + pub fn __init( + &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( + &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()); + } + } + }) + } +} diff --git a/src/gpio/asynch.rs b/src/gpio/asynch.rs new file mode 100644 index 0000000..36f3632 --- /dev/null +++ b/src/gpio/asynch.rs @@ -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 { + 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 { + 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 { + 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(()) + } +} diff --git a/src/gpio/ll.rs b/src/gpio/ll.rs new file mode 100644 index 0000000..ba8e193 --- /dev/null +++ b/src/gpio/ll.rs @@ -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 { + 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 { + 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(_pin: Pin) -> 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) { + 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() + } +} diff --git a/src/gpio/mod.rs b/src/gpio/mod.rs new file mode 100644 index 0000000..54ae665 --- /dev/null +++ b/src/gpio/mod.rs @@ -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 { + phantom: core::marker::PhantomData, +} + +impl Pin { + #[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(_pin: Pin, 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 { + Ok(self.0.is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + 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(_pin: Pin) -> Self { + let mut ll = ll::LowLevelGpio::new(I::ID); + ll.configure_as_input_floating(); + Input(ll) + } + + pub fn new_with_pull(_pin: Pin, 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 { + Ok(self.0.is_low()) + } + + fn is_high(&mut self) -> Result { + 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(_pin: Pin) -> 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 { + Ok(self.ll.is_low()) + } + + /// Reads the input state of the pin, regardless of configured mode. + fn is_high(&mut self) -> Result { + 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 { + 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 { + 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( + _pin: Pin, + fun_sel: FunSel, + pull: Option, + ) -> 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) -> 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 + } +} diff --git a/src/gpio/regs.rs b/src/gpio/regs.rs new file mode 100644 index 0000000..e0ae9db --- /dev/null +++ b/src/gpio/regs.rs @@ -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::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 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"), + } + } +} diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs new file mode 100644 index 0000000..39bd3b7 --- /dev/null +++ b/src/i2c/mod.rs @@ -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 { + 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 { + calc_clk_div_generic(clks.apb1(), speed_mode) +} + +#[cfg(feature = "vor1x")] +fn calc_clk_div(sys_clk: Hertz, speed_mode: I2cSpeed) -> Result { + 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, + /// See [I2cMaster::set_clock_low_timeout] documentation. + pub timeout: Option, + // 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 { + id: Bank, + regs: regs::MmioI2c<'static>, + addr: PhantomData, +} + +impl I2cMaster { + pub fn new( + _i2c: I2c, + #[cfg(feature = "vor1x")] sysclk: Hertz, + #[cfg(feature = "vor4x")] clks: &crate::clock::Clocks, + cfg: MasterConfig, + speed_mode: I2cSpeed, + ) -> Result { + 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 { + type Error = Error; +} + +impl embedded_hal::i2c::I2c for I2cMaster { + 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 { + type Error = Error; +} + +impl embedded_hal::i2c::I2c for I2cMaster { + 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) + } +} diff --git a/src/i2c/regs.rs b/src/i2c/regs.rs new file mode 100644 index 0000000..7c56e5c --- /dev/null +++ b/src/i2c/regs.rs @@ -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); + +impl Words { + pub const fn new(value: u11) -> Self { + Words(arbitrary_int::UInt::::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); + +impl Data { + pub const fn new(value: u8) -> Self { + Data(arbitrary_int::UInt::::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); + +#[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); + +impl ClkTimeoutLimit { + pub fn new(value: u20) -> Self { + ClkTimeoutLimit(arbitrary_int::UInt::::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::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 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), + } + } +} diff --git a/src/ioconfig/mod.rs b/src/ioconfig/mod.rs new file mode 100644 index 0000000..d5b7d8e --- /dev/null +++ b/src/ioconfig/mod.rs @@ -0,0 +1,3 @@ +pub use regs::{FilterClkSel, FilterType}; + +pub mod regs; diff --git a/src/ioconfig/regs.rs b/src/ioconfig/regs.rs new file mode 100644 index 0000000..943eb3d --- /dev/null +++ b/src/ioconfig/regs.rs @@ -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, +} + +#[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::(), 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 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) }, + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c3514d0 --- /dev/null +++ b/src/lib.rs @@ -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); + + impl TriggerLevel { + pub const fn new(value: u5) -> Self { + TriggerLevel(arbitrary_int::UInt::::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, + } +} diff --git a/src/pins.rs b/src/pins.rs new file mode 100644 index 0000000..c947706 --- /dev/null +++ b/src/pins.rs @@ -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 Sealed for Pin {} + +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, + pub pa1: Pin, + pub pa2: Pin, + pub pa3: Pin, + pub pa4: Pin, + pub pa5: Pin, + pub pa6: Pin, + pub pa7: Pin, + pub pa8: Pin, + pub pa9: Pin, + pub pa10: Pin, + pub pa11: Pin, + pub pa12: Pin, + pub pa13: Pin, + pub pa14: Pin, + pub pa15: Pin, + #[cfg(feature = "vor1x")] + pub pa16: Pin, + #[cfg(feature = "vor1x")] + pub pa17: Pin, + #[cfg(feature = "vor1x")] + pub pa18: Pin, + #[cfg(feature = "vor1x")] + pub pa19: Pin, + #[cfg(feature = "vor1x")] + pub pa20: Pin, + #[cfg(feature = "vor1x")] + pub pa21: Pin, + #[cfg(feature = "vor1x")] + pub pa22: Pin, + #[cfg(feature = "vor1x")] + pub pa23: Pin, + #[cfg(feature = "vor1x")] + pub pa24: Pin, + #[cfg(feature = "vor1x")] + pub pa25: Pin, + #[cfg(feature = "vor1x")] + pub pa26: Pin, + #[cfg(feature = "vor1x")] + pub pa27: Pin, + #[cfg(feature = "vor1x")] + pub pa28: Pin, + #[cfg(feature = "vor1x")] + pub pa29: Pin, + #[cfg(feature = "vor1x")] + pub pa30: Pin, + #[cfg(feature = "vor1x")] + pub pa31: Pin, +} + +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, + pub pb1: Pin, + pub pb2: Pin, + pub pb3: Pin, + pub pb4: Pin, + #[cfg(not(feature = "va41628"))] + pub pb5: Pin, + #[cfg(not(feature = "va41628"))] + pub pb6: Pin, + #[cfg(not(feature = "va41628"))] + pub pb7: Pin, + #[cfg(not(feature = "va41628"))] + pub pb8: Pin, + #[cfg(not(feature = "va41628"))] + pub pb9: Pin, + #[cfg(not(feature = "va41628"))] + pub pb10: Pin, + #[cfg(not(feature = "va41628"))] + pub pb11: Pin, + pub pb12: Pin, + pub pb13: Pin, + pub pb14: Pin, + pub pb15: Pin, + #[cfg(feature = "vor1x")] + pub pb16: Pin, + #[cfg(feature = "vor1x")] + pub pb17: Pin, + #[cfg(feature = "vor1x")] + pub pb18: Pin, + #[cfg(feature = "vor1x")] + pub pb19: Pin, + #[cfg(feature = "vor1x")] + pub pb20: Pin, + #[cfg(feature = "vor1x")] + pub pb21: Pin, + #[cfg(feature = "vor1x")] + pub pb22: Pin, + #[cfg(feature = "vor1x")] + pub pb23: Pin, +} + +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, + pub pc1: Pin, + pub pc2: Pin, + pub pc3: Pin, + pub pc4: Pin, + pub pc5: Pin, + pub pc6: Pin, + pub pc7: Pin, + pub pc8: Pin, + pub pc9: Pin, + pub pc10: Pin, + pub pc11: Pin, + pub pc12: Pin, + #[cfg(not(feature = "va41628"))] + pub pc13: Pin, + pub pc14: Pin, + #[cfg(not(feature = "va41628"))] + pub pc15: Pin, + } + + 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, + #[cfg(not(feature = "va41628"))] + pub pd1: Pin, + #[cfg(not(feature = "va41628"))] + pub pd2: Pin, + #[cfg(not(feature = "va41628"))] + pub pd3: Pin, + #[cfg(not(feature = "va41628"))] + pub pd4: Pin, + #[cfg(not(feature = "va41628"))] + pub pd5: Pin, + #[cfg(not(feature = "va41628"))] + pub pd6: Pin, + #[cfg(not(feature = "va41628"))] + pub pd7: Pin, + #[cfg(not(feature = "va41628"))] + pub pd8: Pin, + #[cfg(not(feature = "va41628"))] + pub pd9: Pin, + pub pd10: Pin, + pub pd11: Pin, + pub pd12: Pin, + pub pd13: Pin, + pub pd14: Pin, + pub pd15: Pin, + } + + 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, + pub pe1: Pin, + pub pe2: Pin, + pub pe3: Pin, + pub pe4: Pin, + pub pe5: Pin, + pub pe6: Pin, + pub pe7: Pin, + pub pe8: Pin, + pub pe9: Pin, + #[cfg(not(feature = "va41628"))] + pub pe10: Pin, + #[cfg(not(feature = "va41628"))] + pub pe11: Pin, + pub pe12: Pin, + pub pe13: Pin, + pub pe14: Pin, + pub pe15: Pin, + } + + 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, + pub pf1: Pin, + #[cfg(not(feature = "va41628"))] + pub pf2: Pin, + #[cfg(not(feature = "va41628"))] + pub pf3: Pin, + #[cfg(not(feature = "va41628"))] + pub pf4: Pin, + #[cfg(not(feature = "va41628"))] + pub pf5: Pin, + #[cfg(not(feature = "va41628"))] + pub pf6: Pin, + #[cfg(not(feature = "va41628"))] + pub pf7: Pin, + #[cfg(not(feature = "va41628"))] + pub pf8: Pin, + pub pf9: Pin, + #[cfg(not(feature = "va41628"))] + pub pf10: Pin, + pub pf11: Pin, + pub pf12: Pin, + pub pf13: Pin, + pub pf14: Pin, + pub pf15: Pin, + } + + 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, + pub pg1: Pin, + pub pg2: Pin, + pub pg3: Pin, + pub pg4: Pin, + pub pg5: Pin, + pub pg6: Pin, + pub pg7: Pin, + } + + 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(), + } + } + } + } +} diff --git a/src/pwm.rs b/src/pwm.rs new file mode 100644 index 0000000..408024b --- /dev/null +++ b/src/pwm.rs @@ -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 { + 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, +} + +impl PwmPin { + /// Create a new PWM pin + pub fn new( + _pin: Pin, + _tim: Tim, + #[cfg(feature = "vor1x")] sys_clk: Hertz, + #[cfg(feature = "vor4x")] clks: &crate::clock::Clocks, + initial_frequency: Hertz, + ) -> Result { + 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) { + 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> for PwmPin { + fn from(other: PwmPin) -> 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> for PwmPin { + fn from(other: PwmPin) -> 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 { + #[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 + } +} diff --git a/src/spi/mod.rs b/src/spi/mod.rs new file mode 100644 index 0000000..e9fcf12 --- /dev/null +++ b/src/spi/mod.rs @@ -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(_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, + pub mode: Option, + 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, +} + +impl TransferConfig { + pub fn new_with_hw_cs( + clk_cfg: Option, + mode: Option, + 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 + TryFrom + '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; + + /// 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 { + spi_clk_config_from_div(div) + } + + #[cfg(feature = "vor1x")] + pub fn from_clk(sys_clk: Hertz, spi_clk: Hertz) -> Option { + 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::from_apb1_clk(clks.apb1(), spi_clk) + } + + #[cfg(feature = "vor4x")] + pub fn from_apb1_clk(apb1_clk: Hertz, spi_clk: Hertz) -> Option { + 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 { + 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 { + 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 { + id: Bank, + regs: regs::MmioSpi<'static>, + cfg: SpiConfig, + /// Fill word for read-only SPI transactions. + fill_word: Word, + blockmode: bool, + bmstall: bool, + word: PhantomData, +} + +impl Spi +where + >::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( + spi: SpiI, + spi_cfg: SpiConfig, + ) -> Result { + #[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( + spi: SpiI, + _pins: (Sck, Miso, Mosi), + spi_cfg: SpiConfig, + ) -> Result { + 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(_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 SpiLowLevel for Spi +where + >::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 { + 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 embedded_hal::spi::ErrorType for Spi { + type Error = Infallible; +} + +impl embedded_hal::spi::SpiBus for Spi +where + >::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> for Spi { + fn from(mut old_spi: Spi) -> 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> for Spi { + fn from(mut old_spi: Spi) -> 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, + } + } +} diff --git a/src/spi/pins_vor1x.rs b/src/spi/pins_vor1x.rs new file mode 100644 index 0000000..15f0bea --- /dev/null +++ b/src/spi/pins_vor1x.rs @@ -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 { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + 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 { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +pub type SpiBPortASck = Pin; +pub type SpiBPortAMosi = Pin; +pub type SpiBPortAMiso = Pin; + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + 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 +); diff --git a/src/spi/pins_vor4x.rs b/src/spi/pins_vor4x.rs new file mode 100644 index 0000000..4c8a9df --- /dev/null +++ b/src/spi/pins_vor4x.rs @@ -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 { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl PinMiso for Pin { + 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 { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +#[cfg(not(feature = "va41628"))] +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +#[cfg(not(feature = "va41628"))] +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +#[cfg(not(feature = "va41628"))] +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +#[cfg(not(feature = "va41628"))] +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +#[cfg(not(feature = "va41628"))] +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + 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 { + const SPI_ID: Bank = Bank::Spi2; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi2; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl PinMiso for Pin { + const SPI_ID: Bank = Bank::Spi2; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +#[cfg(not(feature = "va41628"))] +impl PinSck for Pin { + const SPI_ID: Bank = Bank::Spi2; + const FUN_SEL: FunSel = FunSel::Sel2; +} +#[cfg(not(feature = "va41628"))] +impl PinMosi for Pin { + const SPI_ID: Bank = Bank::Spi2; + const FUN_SEL: FunSel = FunSel::Sel2; +} +#[cfg(not(feature = "va41628"))] +impl PinMiso for Pin { + 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 +); diff --git a/src/spi/regs.rs b/src/spi/regs.rs new file mode 100644 index 0000000..1ca5fb0 --- /dev/null +++ b/src/spi/regs.rs @@ -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, +} + +#[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); + +impl ClkPrescaler { + pub const fn new(value: u8) -> Self { + ClkPrescaler(arbitrary_int::UInt::::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::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 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), + } + } +} diff --git a/src/sysconfig.rs b/src/sysconfig.rs new file mode 100644 index 0000000..5548419 --- /dev/null +++ b/src/sysconfig.rs @@ -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); +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..9808028 --- /dev/null +++ b/src/time.rs @@ -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; diff --git a/src/timer/mod.rs b/src/timer/mod.rs new file mode 100644 index 0000000..af89408 --- /dev/null +++ b/src/timer/mod.rs @@ -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: 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: 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: 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) { + 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) { + 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); +} diff --git a/src/timer/pins_vor1x.rs b/src/timer/pins_vor1x.rs new file mode 100644 index 0000000..3d4a480 --- /dev/null +++ b/src/timer/pins_vor1x.rs @@ -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); diff --git a/src/timer/pins_vor4x.rs b/src/timer/pins_vor4x.rs new file mode 100644 index 0000000..6ab1856 --- /dev/null +++ b/src/timer/pins_vor4x.rs @@ -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); diff --git a/src/timer/regs.rs b/src/timer/regs.rs new file mode 100644 index 0000000..3f3636c --- /dev/null +++ b/src/timer/regs.rs @@ -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, + #[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); + +impl EnableControl { + pub fn new_disable() -> Self { + EnableControl(arbitrary_int::UInt::::from_u32(0)) + } + + pub fn new_enable() -> Self { + EnableControl(arbitrary_int::UInt::::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 { + let port_check = |base: u8, id: u8, len: u8| -> Result { + 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 { + let port_check = |base: u8, id: u8| -> Result { + 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 { + 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 { + 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 { + let id = source.id()?; + Ok(Self::new_with_raw_value(id.as_u32())) + } + + pub fn as_cascade_source(&self) -> Result { + 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::(), 0x1000); + } else if #[cfg(feature = "vor4x")] { + static_assertions::const_assert_eq!(core::mem::size_of::(), 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 { + 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, 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)) + } + } +} diff --git a/src/uart/mod.rs b/src/uart/mod.rs new file mode 100644 index 0000000..62bcf2e --- /dev/null +++ b/src/uart/mod.rs @@ -0,0 +1,1306 @@ +//! # API for the UART peripheral +//! +//! The core of this API are the [Uart], [Rx] and [Tx] structures. +//! The RX structure also has a dedicated [RxWithInterrupt] variant which allows reading the receiver +//! using interrupts. +//! +//! The [rx_asynch] and [tx_asynch] modules provide an asynchronous non-blocking API for the UART +//! peripheral. +//! +//! ## Examples +//! +//! - [UART simple example](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/simple/examples/uart.rs) +//! - [UART with IRQ and RTIC](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/rtic/src/bin/uart-echo-rtic.rs) +//! - [Flashloader exposing a CCSDS interface via UART](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/flashloader) +use core::convert::Infallible; +pub mod regs; +#[cfg(feature = "vor1x")] +use crate::InterruptConfig; +use crate::{FunSel, gpio::IoPeriphPin, pins::PinMarker, sealed::Sealed}; +use arbitrary_int::{Number, u6, u18}; +use fugit::RateExtU32; +use regs::{ClkScale, Control, Data, Enable, FifoClear, InterruptClear, MmioUart}; + +use crate::{PeripheralSelect, enable_nvic_interrupt, enable_peripheral_clock, time::Hertz}; +use embedded_hal_nb::serial::Read; +pub use regs::{Bank, Stopbits, WordSize}; + +#[cfg(feature = "vor1x")] +mod pins_vor1x; +#[cfg(feature = "vor4x")] +mod pins_vor4x; + +#[cfg(feature = "vor4x")] +use crate::clock::Clocks; +#[cfg(feature = "vor1x")] +use va108xx as pac; +#[cfg(feature = "vor4x")] +use va416xx as pac; + +pub mod tx_asynch; +pub use tx_asynch::*; + +pub mod rx_asynch; +pub use rx_asynch::*; + +//================================================================================================== +// Type-Level support +//================================================================================================== + +pub trait TxPin: PinMarker { + const BANK: Bank; + const FUN_SEL: FunSel; +} +pub trait RxPin: PinMarker { + const BANK: Bank; + const FUN_SEL: FunSel; +} + +//================================================================================================== +// Regular Definitions +//================================================================================================== + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("no interrupt ID was set")] +pub struct NoInterruptIdWasSet; + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("transer is pending")] +pub struct TransferPendingError; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Event { + // Receiver FIFO interrupt enable. Generates interrupt + // when FIFO is at least half full. Half full is defined as FIFO + // count >= RXFIFOIRQTRG + RxFifoHalfFull, + // Framing error, Overrun error, Parity Error and Break error + RxError, + // Event for timeout condition: Data in the FIFO and no receiver + // FIFO activity for 4 character times + RxTimeout, + + // Transmitter FIFO interrupt enable. Generates interrupt + // when FIFO is at least half full. Half full is defined as FIFO + // count >= TXFIFOIRQTRG + TxFifoHalfFull, + // FIFO overflow error + TxError, + // Generate interrupt when transmit FIFO is empty and TXBUSY is 0 + TxEmpty, + // Interrupt when CTSn changes value + TxCts, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Parity { + None, + Odd, + Even, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + pub baudrate: Hertz, + pub parity: Parity, + pub stopbits: Stopbits, + // When false, use standard 16x baud clock, other 8x baud clock + pub baud8: bool, + pub wordsize: WordSize, + pub enable_tx: bool, + pub enable_rx: bool, +} + +impl Config { + pub fn baudrate(mut self, baudrate: Hertz) -> Self { + self.baudrate = baudrate; + self + } + + pub fn parity_none(mut self) -> Self { + self.parity = Parity::None; + self + } + + pub fn parity_even(mut self) -> Self { + self.parity = Parity::Even; + self + } + + pub fn parity_odd(mut self) -> Self { + self.parity = Parity::Odd; + self + } + + pub fn stopbits(mut self, stopbits: Stopbits) -> Self { + self.stopbits = stopbits; + self + } + + pub fn wordsize(mut self, wordsize: WordSize) -> Self { + self.wordsize = wordsize; + self + } + + pub fn baud8(mut self, baud: bool) -> Self { + self.baud8 = baud; + self + } +} + +impl Default for Config { + fn default() -> Config { + let baudrate = 115_200_u32.Hz(); + Config { + baudrate, + parity: Parity::None, + stopbits: Stopbits::One, + baud8: false, + wordsize: WordSize::Eight, + enable_tx: true, + enable_rx: true, + } + } +} + +impl From for Config { + fn from(baud: Hertz) -> Self { + Config::default().baudrate(baud) + } +} + +//================================================================================================== +// IRQ Definitions +//================================================================================================== + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IrqContextTimeoutOrMaxSize { + rx_idx: usize, + mode: IrqReceptionMode, + pub max_len: usize, +} + +impl IrqContextTimeoutOrMaxSize { + pub fn new(max_len: usize) -> Self { + IrqContextTimeoutOrMaxSize { + rx_idx: 0, + max_len, + mode: IrqReceptionMode::Idle, + } + } +} + +impl IrqContextTimeoutOrMaxSize { + pub fn reset(&mut self) { + self.rx_idx = 0; + self.mode = IrqReceptionMode::Idle; + } +} + +/// This struct is used to return the default IRQ handler result to the user +#[derive(Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IrqResult { + pub bytes_read: usize, + pub errors: Option, +} + +/// This struct is used to return the default IRQ handler result to the user +#[derive(Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct IrqResultMaxSizeOrTimeout { + complete: bool, + timeout: bool, + pub errors: Option, + pub bytes_read: usize, +} + +impl IrqResultMaxSizeOrTimeout { + pub fn new() -> Self { + IrqResultMaxSizeOrTimeout { + complete: false, + timeout: false, + errors: None, + bytes_read: 0, + } + } +} +impl IrqResultMaxSizeOrTimeout { + #[inline] + pub fn has_errors(&self) -> bool { + self.errors.is_some() + } + + #[inline] + pub fn overflow_error(&self) -> bool { + self.errors.is_some_and(|e| e.overflow) + } + + #[inline] + pub fn framing_error(&self) -> bool { + self.errors.is_some_and(|e| e.framing) + } + + #[inline] + pub fn parity_error(&self) -> bool { + self.errors.is_some_and(|e| e.parity) + } + + #[inline] + pub fn timeout(&self) -> bool { + self.timeout + } + + #[inline] + pub fn complete(&self) -> bool { + self.complete + } +} + +#[derive(Debug, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum IrqReceptionMode { + Idle, + Pending, +} + +#[derive(Default, Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UartErrors { + overflow: bool, + framing: bool, + parity: bool, + other: bool, +} + +impl UartErrors { + #[inline(always)] + pub fn overflow(&self) -> bool { + self.overflow + } + + #[inline(always)] + pub fn framing(&self) -> bool { + self.framing + } + + #[inline(always)] + pub fn parity(&self) -> bool { + self.parity + } + + #[inline(always)] + pub fn other(&self) -> bool { + self.other + } +} + +impl UartErrors { + #[inline(always)] + pub fn error(&self) -> bool { + self.overflow || self.framing || self.parity || self.other + } +} + +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BufferTooShortError { + found: usize, + expected: usize, +} + +//================================================================================================== +// UART peripheral wrapper +//================================================================================================== + +pub trait UartPeripheralMarker: Sealed { + const ID: Bank; + const PERIPH_SEL: PeripheralSelect; +} + +#[cfg(feature = "vor1x")] +pub type Uart0 = pac::Uarta; +#[cfg(feature = "vor4x")] +pub type Uart0 = pac::Uart0; + +impl UartPeripheralMarker for Uart0 { + const ID: Bank = Bank::Uart0; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart0; +} +impl Sealed for Uart0 {} + +#[cfg(feature = "vor1x")] +pub type Uart1 = pac::Uartb; +#[cfg(feature = "vor4x")] +pub type Uart1 = pac::Uart1; + +impl UartPeripheralMarker for Uart1 { + const ID: Bank = Bank::Uart1; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart1; +} +impl Sealed for Uart1 {} + +#[cfg(feature = "vor4x")] +impl UartPeripheralMarker for pac::Uart2 { + const ID: Bank = Bank::Uart2; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Uart2; +} +#[cfg(feature = "vor4x")] +impl Sealed for pac::Uart2 {} + +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[error("UART ID missmatch between peripheral and pins.")] +pub struct UartIdMissmatchError; + +//================================================================================================== +// UART implementation +//================================================================================================== + +/// UART driver structure. +pub struct Uart { + tx: Tx, + rx: Rx, +} + +impl Uart { + cfg_if::cfg_if! { + if #[cfg(feature = "vor1x")] { + /// Calls [Self::new] with the interrupt configuration to some valid value. + pub fn new_with_interrupt( + uart: UartI, + tx_pin: TxPinI, + rx_pin: RxPinI, + sys_clk: Hertz, + config: Config, + irq_cfg: InterruptConfig, + ) -> Result { + Self::new(uart, tx_pin, rx_pin, sys_clk, config, Some(irq_cfg)) + } + + /// Calls [Self::new] with the interrupt configuration to [None]. + pub fn new_without_interrupt( + uart: UartI, + tx_pin: TxPinI, + rx_pin: RxPinI, + sys_clk: Hertz, + config: Config, + ) -> Result { + Self::new(uart, tx_pin, rx_pin, sys_clk, config, None) + } + + /// Create a new UART peripheral driver with an interrupt configuration. + /// + /// # Arguments + /// + /// - `syscfg`: The system configuration register block + /// - `sys_clk`: The system clock frequency + /// - `uart`: The concrete UART peripheral instance. + /// - `pins`: UART TX and RX pin tuple. + /// - `config`: UART specific configuration parameters like baudrate. + /// - `irq_cfg`: Optional interrupt configuration. This should be a valid value if the plan + /// is to use TX or RX functionality relying on interrupts. If only the blocking API without + /// any interrupt support is used, this can be [None]. + pub fn new( + uart: UartI, + tx_pin: TxPinI, + rx_pin: RxPinI, + sys_clk: Hertz, + config: Config, + opt_irq_cfg: Option, + ) -> Result { + Self::new_internal(uart, (tx_pin, rx_pin), sys_clk, config, opt_irq_cfg) + } + } else if #[cfg(feature = "vor4x")] { + /// Create a new UART peripheral driver. + /// + /// # Arguments + /// + /// - `clks`: Frozen system clock configuration. + /// - `uart`: The concrete UART peripheral instance. + /// - `pins`: UART TX and RX pin tuple. + /// - `config`: UART specific configuration parameters like baudrate. + pub fn new( + uart: UartI, + tx_pin: TxPinI, + rx_pin: RxPinI, + clks: &Clocks, + config: Config, + ) -> Result { + if UartI::ID == Bank::Uart2 { + Self::new_internal(uart, (tx_pin, rx_pin), clks.apb1(), config) + } else { + Self::new_internal(uart, (tx_pin, rx_pin), clks.apb2(), config) + } + } + + /// Create a new UART peripheral driver given a reference clock. + /// + /// # Arguments + /// + /// - `ref_clk`: APB1 clock for UART2, APB2 clock otherwise. + /// - `uart`: The concrete UART peripheral instance. + /// - `pins`: UART TX and RX pin tuple. + /// - `config`: UART specific configuration parameters like baudrate. + pub fn new_with_ref_clk( + uart: UartI, + tx_pin: TxPinI, + rx_pin: RxPinI, + ref_clk: Hertz, + config: Config, + ) -> Result { + Self::new_internal(uart,(tx_pin, rx_pin),ref_clk, config) + } + } + } + + fn new_internal( + _uart: UartI, + _pins: (TxPinI, RxPinI), + ref_clk: Hertz, + config: Config, + #[cfg(feature = "vor1x")] opt_irq_cfg: Option, + ) -> Result { + if UartI::ID != TxPinI::BANK || UartI::ID != RxPinI::BANK { + return Err(UartIdMissmatchError); + } + IoPeriphPin::new(TxPinI::ID, TxPinI::FUN_SEL, None); + IoPeriphPin::new(RxPinI::ID, TxPinI::FUN_SEL, None); + enable_peripheral_clock(UartI::PERIPH_SEL); + + let mut reg_block = regs::Uart::new_mmio(UartI::ID); + let baud_multiplier = match config.baud8 { + false => 16, + true => 8, + }; + + // This is the calculation: (64.0 * (x - integer_part as f32) + 0.5) as u32 without floating + // point calculations. + let frac = ((ref_clk.raw() % (config.baudrate.raw() * 16)) * 64 + + (config.baudrate.raw() * 8)) + / (config.baudrate.raw() * 16); + // Calculations here are derived from chapter 4.8.5 (p.79) of the datasheet. + let x = ref_clk.raw() as f32 / (config.baudrate.raw() * baud_multiplier) as f32; + let integer_part = x as u32; + reg_block.write_clkscale( + ClkScale::builder() + .with_int(u18::new(integer_part)) + .with_frac(u6::new(frac as u8)) + .build(), + ); + + let (paren, pareven) = match config.parity { + Parity::None => (false, false), + Parity::Odd => (true, false), + Parity::Even => (true, true), + }; + reg_block.write_ctrl( + Control::builder() + .with_baud8(config.baud8) + .with_auto_rts(false) + .with_def_rts(false) + .with_auto_cts(false) + .with_loopback_block(false) + .with_loopback(false) + .with_wordsize(config.wordsize) + .with_stopbits(config.stopbits) + .with_parity_manual(false) + .with_parity_even(pareven) + .with_parity_enable(paren) + .build(), + ); + // Clear the FIFO + reg_block.write_fifo_clr(FifoClear::builder().with_tx(true).with_rx(true).build()); + reg_block.write_enable( + Enable::builder() + .with_tx(config.enable_tx) + .with_rx(config.enable_rx) + .build(), + ); + + #[cfg(feature = "vor1x")] + if let Some(irq_cfg) = opt_irq_cfg { + if irq_cfg.route { + enable_peripheral_clock(PeripheralSelect::Irqsel); + unsafe { va108xx::Irqsel::steal() } + .uart0(UartI::ID as usize) + .write(|w| unsafe { w.bits(irq_cfg.id as u32) }); + } + if irq_cfg.enable_in_nvic { + // Safety: User has specifically configured this. + unsafe { enable_nvic_interrupt(irq_cfg.id) }; + } + } + + Ok(Uart { + tx: Tx::new(UartI::ID), + rx: Rx::new(UartI::ID), + }) + } + + #[inline] + pub fn perid(&self) -> u32 { + self.tx.perid() + } + + #[inline] + pub fn enable_rx(&mut self) { + self.rx.enable(); + } + + #[inline] + pub fn disable_rx(&mut self) { + self.rx.disable(); + } + + #[inline] + pub fn enable_tx(&mut self) { + self.tx.enable(); + } + + #[inline] + pub fn disable_tx(&mut self) { + self.tx.disable(); + } + + /// This also clears status conditons for the RX FIFO. + #[inline] + pub fn clear_rx_fifo(&mut self) { + self.rx.clear_fifo(); + } + + /// This also clears status conditons for the TX FIFO. + #[inline] + pub fn clear_tx_fifo(&mut self) { + self.tx.clear_fifo(); + } + + pub fn listen(&mut self, event: Event) { + self.tx.regs.modify_irq_enabled(|mut value| { + match event { + Event::RxError => value.set_rx_status(true), + Event::RxFifoHalfFull => value.set_rx(true), + Event::RxTimeout => value.set_rx_timeout(true), + Event::TxEmpty => value.set_tx_empty(true), + Event::TxError => value.set_tx_status(true), + Event::TxFifoHalfFull => value.set_tx(true), + Event::TxCts => value.set_tx_cts(true), + } + value + }); + } + + pub fn unlisten(&mut self, event: Event) { + self.tx.regs.modify_irq_enabled(|mut value| { + match event { + Event::RxError => value.set_rx_status(false), + Event::RxFifoHalfFull => value.set_rx(false), + Event::RxTimeout => value.set_rx_timeout(false), + Event::TxEmpty => value.set_tx_empty(false), + Event::TxError => value.set_tx_status(false), + Event::TxFifoHalfFull => value.set_tx(false), + Event::TxCts => value.set_tx_cts(false), + } + value + }); + } + + /// Poll receiver errors. + pub fn poll_rx_errors(&self) -> Option { + self.rx.poll_errors() + } + + pub fn split(self) -> (Tx, Rx) { + (self.tx, self.rx) + } +} + +impl embedded_io::ErrorType for Uart { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::ErrorType for Uart { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Read for Uart { + fn read(&mut self) -> nb::Result { + self.rx.read() + } +} + +impl embedded_hal_nb::serial::Write for Uart { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.tx.write(word).map_err(|e| { + if let nb::Error::Other(_) = e { + unreachable!() + } + nb::Error::WouldBlock + }) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.tx.flush().map_err(|e| { + if let nb::Error::Other(_) = e { + unreachable!() + } + nb::Error::WouldBlock + }) + } +} + +#[inline(always)] +pub fn enable_rx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_rx(true); + value + }); +} + +#[inline(always)] +pub fn disable_rx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_rx(false); + value + }); +} + +#[inline(always)] +pub fn enable_rx_interrupts(uart: &mut MmioUart<'static>, timeout: bool) { + uart.modify_irq_enabled(|mut value| { + value.set_rx_status(true); + value.set_rx(true); + if timeout { + value.set_rx_timeout(true); + } + value + }); +} + +#[inline(always)] +pub fn disable_rx_interrupts(uart: &mut MmioUart<'static>) { + uart.modify_irq_enabled(|mut value| { + value.set_rx_status(false); + value.set_rx(false); + value.set_rx_timeout(false); + value + }); +} + +/// Serial receiver. +/// +/// Can be created by using the [Uart::split] API. +pub struct Rx { + id: Bank, + regs: regs::MmioUart<'static>, +} + +impl Rx { + /// Retrieve a TX pin without expecting an explicit UART structure + /// + /// # Safety + /// + /// Circumvents the HAL safety guarantees. + #[inline(always)] + pub unsafe fn steal(id: Bank) -> Self { + Self::new(id) + } + + #[inline(always)] + fn new(id: Bank) -> Self { + Self { + id, + regs: regs::Uart::new_mmio(id), + } + } + + pub fn poll_errors(&self) -> Option { + let mut errors = UartErrors::default(); + + let status = self.regs.read_rx_status(); + if status.overrun_error() { + errors.overflow = true; + } else if status.framing_error() { + errors.framing = true; + } else if status.parity_error() { + errors.parity = true; + } else { + return None; + }; + Some(errors) + } + + #[inline] + pub fn perid(&self) -> u32 { + self.regs.read_perid() + } + + #[inline] + pub fn clear_fifo(&mut self) { + self.regs + .write_fifo_clr(FifoClear::builder().with_tx(false).with_rx(true).build()); + } + + #[inline] + pub fn disable_interrupts(&mut self) { + disable_rx_interrupts(&mut self.regs); + } + + #[inline] + pub fn enable_interrupts( + &mut self, + #[cfg(feature = "vor4x")] enable_in_nvic: bool, + timeout: bool, + ) { + #[cfg(feature = "vor4x")] + if enable_in_nvic { + unsafe { + enable_nvic_interrupt(self.id.interrupt_id_rx()); + } + } + enable_rx_interrupts(&mut self.regs, timeout); + } + + #[inline] + pub fn enable(&mut self) { + enable_rx(&mut self.regs); + } + + #[inline] + pub fn disable(&mut self) { + disable_rx(&mut self.regs); + } + + /// Low level function to read a word from the UART FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + /// + /// Please note that you might have to mask the returned value with 0xff to retrieve the actual + /// value if you use the manual parity mode. See chapter 4.6.2 for more information. + #[inline(always)] + pub fn read_fifo(&mut self) -> nb::Result { + if !self.regs.read_rx_status().data_available() { + return Err(nb::Error::WouldBlock); + } + Ok(self.read_fifo_unchecked()) + } + + /// Low level function to read a word from from the UART 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. + /// + /// Please note that you might have to mask the returned value with 0xff to retrieve the actual + /// value if you use the manual parity mode. See chapter 4.6.2 for more information. + #[inline(always)] + pub fn read_fifo_unchecked(&mut self) -> u32 { + self.regs.read_data().raw_value() + } + + pub fn into_rx_with_irq(self) -> RxWithInterrupt { + RxWithInterrupt::new(self) + } +} + +impl embedded_io::ErrorType for Rx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::ErrorType for Rx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Read for Rx { + fn read(&mut self) -> nb::Result { + self.read_fifo().map(|val| (val & 0xff) as u8).map_err(|e| { + if let nb::Error::Other(_) = e { + unreachable!() + } + nb::Error::WouldBlock + }) + } +} + +impl embedded_io::Read for Rx { + fn read(&mut self, buf: &mut [u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + let mut read = 0; + loop { + if self.regs.read_rx_status().data_available() { + break; + } + } + for byte in buf.iter_mut() { + match >::read(self) { + Ok(w) => { + *byte = w; + read += 1; + } + Err(nb::Error::WouldBlock) => break, + } + } + + Ok(read) + } +} + +#[inline(always)] +pub fn enable_tx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_tx(true); + value + }); +} + +#[inline(always)] +pub fn disable_tx(uart: &mut MmioUart<'static>) { + uart.modify_enable(|mut value| { + value.set_tx(false); + value + }); +} + +#[inline(always)] +pub fn enable_tx_interrupts(uart: &mut MmioUart<'static>) { + uart.modify_irq_enabled(|mut value| { + value.set_tx(true); + value.set_tx_empty(true); + value.set_tx_status(true); + value + }); +} + +#[inline(always)] +pub fn disable_tx_interrupts(uart: &mut MmioUart<'static>) { + uart.modify_irq_enabled(|mut value| { + value.set_tx(false); + value.set_tx_empty(false); + value.set_tx_status(false); + value + }); +} + +/// Serial transmitter +/// +/// Can be created by using the [Uart::split] API. +pub struct Tx { + id: Bank, + regs: regs::MmioUart<'static>, +} + +impl Tx { + /// Retrieve a TX pin without expecting an explicit UART structure + /// + /// # Safety + /// + /// Circumvents the HAL safety guarantees. + #[inline(always)] + pub unsafe fn steal(id: Bank) -> Self { + Self::new(id) + } + + #[inline(always)] + fn new(id: Bank) -> Self { + Self { + id, + regs: regs::Uart::new_mmio(id), + } + } + + #[inline] + pub fn perid(&self) -> u32 { + self.regs.read_perid() + } + + #[inline] + pub fn clear_fifo(&mut self) { + self.regs + .write_fifo_clr(FifoClear::builder().with_tx(true).with_rx(false).build()); + } + + #[inline] + pub fn enable(&mut self) { + self.regs.modify_enable(|mut value| { + value.set_tx(true); + value + }); + } + + #[inline] + pub fn disable(&mut self) { + self.regs.modify_enable(|mut value| { + value.set_tx(false); + value + }); + } + + /// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty. + /// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow + /// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal + /// is 0 + #[inline] + pub fn enable_interrupts(&mut self, #[cfg(feature = "vor4x")] enable_in_nvic: bool) { + #[cfg(feature = "vor4x")] + if enable_in_nvic { + unsafe { enable_nvic_interrupt(self.id.interrupt_id_tx()) }; + } + // Safety: We own the UART structure + enable_tx_interrupts(&mut self.regs); + } + + /// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts. + /// + /// [Self::enable_interrupts] documents the interrupts. + #[inline] + pub fn disable_interrupts(&mut self) { + // Safety: We own the UART structure + disable_tx_interrupts(&mut self.regs); + } + + /// Low level function to write a word to the UART FIFO. + /// + /// Uses the [nb] API to allow usage in blocking and non-blocking contexts. + /// + /// Please note that you might have to mask the returned value with 0xff to retrieve the actual + /// value if you use the manual parity mode. See chapter 11.4.1 for more information. + #[inline(always)] + pub fn write_fifo(&mut self, data: u32) -> nb::Result<(), Infallible> { + if !self.regs.read_tx_status().ready() { + return Err(nb::Error::WouldBlock); + } + self.write_fifo_unchecked(data); + Ok(()) + } + + /// Low level function to write a word to the UART FIFO. + /// + /// This does not necesarily mean that the FIFO can process another word because it might be + /// full. + /// Use the [Self::write_fifo] function to write a word to the FIFO reliably using the [nb] + /// API. + #[inline(always)] + pub fn write_fifo_unchecked(&mut self, data: u32) { + self.regs.write_data(Data::new_with_raw_value(data)); + } + + pub fn into_async(self) -> TxAsync { + TxAsync::new(self) + } +} + +impl embedded_io::ErrorType for Tx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::ErrorType for Tx { + type Error = Infallible; +} + +impl embedded_hal_nb::serial::Write for Tx { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.write_fifo(word as u32) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + // SAFETY: Only TX related registers are used. + if self.regs.read_tx_status().write_busy() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl embedded_io::Write for Tx { + fn write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + loop { + if self.regs.read_tx_status().ready() { + break; + } + } + let mut written = 0; + for byte in buf.iter() { + match >::write(self, *byte) { + Ok(_) => written += 1, + Err(nb::Error::WouldBlock) => return Ok(written), + } + } + + Ok(written) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + nb::block!(>::flush(self)) + } +} + +/// Serial receiver, using interrupts to offload reading to the hardware. +/// +/// You can use [Rx::into_rx_with_irq] to convert a normal [Rx] structure into this structure. +/// This structure provides two distinct ways to read the UART RX using interrupts. It should +/// be noted that the interrupt service routine (ISR) still has to be provided by the user. However, +/// this structure provides API calls which can be used inside the ISRs to simplify the reading +/// of the UART. +/// +/// 1. The first way simply empties the FIFO on an interrupt into a user provided buffer. You +/// can simply use [Self::start] to prepare the peripheral and then call the +/// [Self::on_interrupt] in the interrupt service routine. +/// 2. The second way reads packets bounded by a maximum size or a baudtick based timeout. You +/// can use [Self::read_fixed_len_or_timeout_based_using_irq] to prepare the peripheral and +/// then call the [Self::on_interrupt_max_size_or_timeout_based] in the interrupt service +/// routine. You have to call [Self::read_fixed_len_or_timeout_based_using_irq] in the ISR to +/// start reading the next packet. +pub struct RxWithInterrupt(Rx); + +impl RxWithInterrupt { + pub fn new(rx: Rx) -> Self { + Self(rx) + } + + /// This function should be called once at initialization time if the regular + /// [Self::on_interrupt] is used to read the UART receiver to enable and start the receiver. + pub fn start(&mut self) { + #[cfg(feature = "vor4x")] + self.enable_interrupts(true, true); + #[cfg(feature = "vor1x")] + self.enable_interrupts(true); + self.0.enable(); + } + + #[inline(always)] + pub fn rx(&self) -> &Rx { + &self.0 + } + + /// This function is used together with the [Self::on_interrupt_max_size_or_timeout_based] + /// function to read packets with a maximum size or variable sized packets by using the + /// receive timeout of the hardware. + /// + /// This function should be called once at initialization to initiate the context state + /// and to [Self::start] the receiver. After that, it should be called after each + /// completed [Self::on_interrupt_max_size_or_timeout_based] call to restart the reception + /// of a packet. + pub fn read_fixed_len_or_timeout_based_using_irq( + &mut self, + context: &mut IrqContextTimeoutOrMaxSize, + ) -> Result<(), TransferPendingError> { + if context.mode != IrqReceptionMode::Idle { + return Err(TransferPendingError); + } + context.mode = IrqReceptionMode::Pending; + context.rx_idx = 0; + self.start(); + Ok(()) + } + + #[inline] + fn enable_interrupts(&mut self, #[cfg(feature = "vor4x")] enable_in_nvic: bool, timeout: bool) { + #[cfg(feature = "vor4x")] + self.0.enable_interrupts(enable_in_nvic, timeout); + #[cfg(feature = "vor1x")] + self.0.enable_interrupts(timeout); + } + + #[inline] + fn disable_interrupts(&mut self) { + self.0.disable_interrupts(); + } + + pub fn cancel_transfer(&mut self) { + self.disable_interrupts(); + self.0.clear_fifo(); + } + + /// This function should be called in the user provided UART interrupt handler. + /// + /// It simply empties any bytes in the FIFO into the user provided buffer and returns the + /// result of the operation. + /// + /// This function will not disable the RX interrupts, so you don't need to call any other + /// API after calling this function to continue emptying the FIFO. RX errors are handled + /// as partial errors and are returned as part of the [IrqResult]. + pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> IrqResult { + let mut result = IrqResult::default(); + + let irq_status = self.0.regs.read_irq_status(); + let irq_enabled = self.0.regs.read_irq_enabled(); + let rx_enabled = irq_enabled.rx(); + + // Half-Full interrupt. We have a guaranteed amount of data we can read. + if irq_status.rx() { + let available_bytes = self.0.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 { + buf[result.bytes_read] = (self.0.read_fifo_unchecked() & 0xff) as u8; + result.bytes_read += 1; + } + } + + // Timeout, empty the FIFO completely. + if irq_status.rx_timeout() { + // While there is data in the FIFO, write it into the reception buffer + while let Ok(byte) = self.0.read_fifo() { + buf[result.bytes_read] = byte as u8; + result.bytes_read += 1; + } + } + + // RX transfer not complete, check for RX errors + if rx_enabled { + self.check_for_errors(&mut result.errors); + } + + // Clear the interrupt status bits + self.0.regs.write_irq_clr( + InterruptClear::builder() + .with_rx_overrun(true) + .with_tx_overrun(false) + .build(), + ); + result + } + + /// This function should be called in the user provided UART interrupt handler. + /// + /// This function is used to read packets which either have a maximum size or variable sized + /// packet which are bounded by sufficient delays between them, triggering a hardware timeout. + /// + /// If either the maximum number of packets have been read or a timeout occured, the transfer + /// will be deemed completed. The state information of the transfer is tracked in the + /// [IrqContextTimeoutOrMaxSize] structure. + /// + /// If passed buffer is equal to or larger than the specified maximum length, an + /// [BufferTooShortError] will be returned. Other RX errors are treated as partial errors + /// and returned inside the [IrqResultMaxSizeOrTimeout] structure. + pub fn on_interrupt_max_size_or_timeout_based( + &mut self, + context: &mut IrqContextTimeoutOrMaxSize, + buf: &mut [u8], + ) -> Result { + if buf.len() < context.max_len { + return Err(BufferTooShortError { + found: buf.len(), + expected: context.max_len, + }); + } + let mut result = IrqResultMaxSizeOrTimeout::default(); + + let irq_status = self.0.regs.read_irq_status(); + let rx_enabled = self.0.regs.read_enable().rx(); + + // Half-Full interrupt. We have a guaranteed amount of data we can read. + if irq_status.rx() { + // Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO. + // We use this trick/hack because the timeout feature of the peripheral relies on data + // being in the RX FIFO. If data continues arriving, another half-full IRQ will fire. + // If not, the last byte(s) is/are emptied by the timeout interrupt. + let available_bytes = self.0.regs.read_rx_fifo_trigger().level().as_usize(); + + let bytes_to_read = core::cmp::min( + available_bytes.saturating_sub(1), + context.max_len - context.rx_idx, + ); + + // If this interrupt bit is set, the trigger level is available at the very least. + // Read everything as fast as possible + for _ in 0..bytes_to_read { + buf[context.rx_idx] = (self.0.read_fifo_unchecked() & 0xff) as u8; + context.rx_idx += 1; + } + + // On high-baudrates, data might be available immediately, and we possible have to + // read continuosly? Then again, the CPU should always be faster than that. I'd rather + // rely on the hardware firing another IRQ. I have not tried baudrates higher than + // 115200 so far. + } + // Timeout, empty the FIFO completely. + if irq_status.rx_timeout() { + // While there is data in the FIFO, write it into the reception buffer + loop { + if context.rx_idx == context.max_len { + break; + } + // While there is data in the FIFO, write it into the reception buffer + match self.0.read() { + Ok(byte) => { + buf[context.rx_idx] = byte; + context.rx_idx += 1; + } + Err(_) => break, + } + } + self.irq_completion_handler_max_size_timeout(&mut result, context); + return Ok(result); + } + + // RX transfer not complete, check for RX errors + if (context.rx_idx < context.max_len) && rx_enabled { + self.check_for_errors(&mut result.errors); + } + + // Clear the interrupt status bits + self.0.regs.write_irq_clr( + InterruptClear::builder() + .with_rx_overrun(true) + .with_tx_overrun(false) + .build(), + ); + Ok(result) + } + + fn check_for_errors(&self, errors: &mut Option) { + let rx_status = self.0.regs.read_rx_status(); + + if rx_status.overrun_error() || rx_status.framing_error() || rx_status.parity_error() { + let err = errors.get_or_insert(UartErrors::default()); + + if rx_status.overrun_error() { + err.overflow = true; + } + if rx_status.framing_error() { + err.framing = true; + } + if rx_status.parity_error() { + err.parity = true; + } + } + } + + fn irq_completion_handler_max_size_timeout( + &mut self, + res: &mut IrqResultMaxSizeOrTimeout, + context: &mut IrqContextTimeoutOrMaxSize, + ) { + self.disable_interrupts(); + self.0.disable(); + res.bytes_read = context.rx_idx; + res.complete = true; + context.mode = IrqReceptionMode::Idle; + context.rx_idx = 0; + } + + /// # Safety + /// + /// This API allows creating multiple UART instances when releasing the TX structure as well. + /// The user must ensure that these instances are not used to create multiple overlapping + /// UART drivers. + pub unsafe fn release(mut self) -> Rx { + self.disable_interrupts(); + self.0 + } +} diff --git a/src/uart/pins_vor1x.rs b/src/uart/pins_vor1x.rs new file mode 100644 index 0000000..c7c0a95 --- /dev/null +++ b/src/uart/pins_vor1x.rs @@ -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 { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +// UART B pins + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} diff --git a/src/uart/pins_vor4x.rs b/src/uart/pins_vor4x.rs new file mode 100644 index 0000000..3710534 --- /dev/null +++ b/src/uart/pins_vor4x.rs @@ -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 { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel2; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart0; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +// UART 1 pins + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel3; +} + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart1; + const FUN_SEL: FunSel = FunSel::Sel1; +} + +// UART 2 pins + +impl TxPin for Pin { + const BANK: Bank = Bank::Uart2; + const FUN_SEL: FunSel = FunSel::Sel2; +} +#[cfg(not(feature = "va41628"))] +impl RxPin for Pin { + const BANK: Bank = Bank::Uart2; + const FUN_SEL: FunSel = FunSel::Sel2; +} + +#[cfg(not(feature = "va41628"))] +impl TxPin for Pin { + const BANK: Bank = Bank::Uart2; + const FUN_SEL: FunSel = FunSel::Sel1; +} +impl RxPin for Pin { + const BANK: Bank = Bank::Uart2; + const FUN_SEL: FunSel = FunSel::Sel1; +} diff --git a/src/uart/regs.rs b/src/uart/regs.rs new file mode 100644 index 0000000..ebe43b2 --- /dev/null +++ b/src/uart/regs.rs @@ -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::(), 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), + } + } +} diff --git a/src/uart/rx_asynch.rs b/src/uart/rx_asynch.rs new file mode 100644 index 0000000..f354654 --- /dev/null +++ b/src/uart/rx_asynch.rs @@ -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 { + 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 { + 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 { + 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( + bank: Bank, + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> Result<(), AsyncUartErrors> { + on_interrupt_rx_async_heapless_queue_overwriting(bank, prod, shared_consumer) +} + +pub fn on_interrupt_rx_async_heapless_queue_overwriting( + bank: Bank, + prod: &mut heapless::spsc::Producer, + shared_consumer: &Mutex>>>, +) -> 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( + 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( + 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 { + 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(Option>); + +impl ErrorType for RxAsync { + /// 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 RxAsync { + /// 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 Drop for RxAsync { + fn drop(&mut self) { + self.stop(); + } +} + +impl embedded_io_async::Read for RxAsync { + async fn read(&mut self, buf: &mut [u8]) -> Result { + 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 { + rx: Rx, + pub shared_consumer: &'static Mutex>>>, +} + +/// 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(Option>); + +impl ErrorType for RxAsyncOverwriting { + /// Error reporting is done using the result of the interrupt functions. + type Error = Infallible; +} + +impl RxAsyncOverwriting { + /// 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>>>, + ) -> 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 Drop for RxAsyncOverwriting { + fn drop(&mut self) { + self.stop(); + } +} + +impl embedded_io_async::Read for RxAsyncOverwriting { + async fn read(&mut self, buf: &mut [u8]) -> Result { + 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| { + 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) + } +} diff --git a/src/uart/tx_asynch.rs b/src/uart/tx_asynch.rs new file mode 100644 index 0000000..52871f5 --- /dev/null +++ b/src/uart/tx_asynch.rs @@ -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>; 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; + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + 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 { + let fut = unsafe { TxFuture::new(&mut self.0, buf) }; + fut.await + } +}